From 493100ba4d333b2008149d17871d7cc5b51f821d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 20 Aug 2020 20:34:31 +0200 Subject: [PATCH 001/948] consensus/ethash: less lookups of block data --- consensus/ethash/consensus.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index bbc554951d..50bd4efcfd 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -204,15 +204,23 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo number, parent := block.NumberU64()-1, block.ParentHash() for i := 0; i < 7; i++ { - ancestor := chain.GetBlock(parent, number) - if ancestor == nil { + ancestorHeader := chain.GetHeader(parent, number) + if ancestorHeader == nil { break } - ancestors[ancestor.Hash()] = ancestor.Header() - for _, uncle := range ancestor.Uncles() { - uncles.Add(uncle.Hash()) + ancestors[parent] = ancestorHeader + // If the ancestor doesn't have any uncles, we don't have to iterate them + if ancestorHeader.UncleHash != types.EmptyUncleHash { + // Need to add those uncles to the blacklist too + ancestor := chain.GetBlock(parent, number) + if ancestor == nil { + break + } + for _, uncle := range ancestor.Uncles() { + uncles.Add(uncle.Hash()) + } } - parent, number = ancestor.ParentHash(), number-1 + parent, number = ancestorHeader.ParentHash, number-1 } ancestors[block.Hash()] = block.Header() uncles.Add(block.Hash()) From de971cc8457601691e97045f30e1419ecc2ac103 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 7 Sep 2020 10:52:01 +0200 Subject: [PATCH 002/948] eth: added trace_call to trace on top of arbitrary blocks (#21338) * eth: Added TraceTransactionPending * eth: Implement Trace_Call, remove traceTxPending * eth: debug_call -> debug_traceCall, recompute tx environment if pruned * eth: fix nil panic * eth: improve block retrieving logic in tracers * internal/web3ext: add debug_traceCall to console --- eth/api.go | 7 +++++- eth/api_tracer.go | 49 +++++++++++++++++++++++++++++++------ internal/web3ext/web3ext.go | 6 +++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/eth/api.go b/eth/api.go index d65a8efa0d..76118e2d7f 100644 --- a/eth/api.go +++ b/eth/api.go @@ -412,7 +412,12 @@ type storageEntry struct { // StorageRangeAt returns the storage at the given block height and transaction index. func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { - _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0) + // Retrieve the block + block := api.eth.blockchain.GetBlockByHash(blockHash) + if block == nil { + return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) + } + _, _, statedb, err := api.computeTxEnv(block, txIndex, 0) if err != nil { return StorageRangeResult{}, err } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 51c2408c24..748280951c 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -711,7 +711,12 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha if config != nil && config.Reexec != nil { reexec = *config.Reexec } - msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec) + // Retrieve the block + block := api.eth.blockchain.GetBlockByHash(blockHash) + if block == nil { + return nil, fmt.Errorf("block %#x not found", blockHash) + } + msg, vmctx, statedb, err := api.computeTxEnv(block, int(index), reexec) if err != nil { return nil, err } @@ -719,6 +724,40 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha return api.traceTx(ctx, msg, vmctx, statedb, config) } +// TraceCall lets you trace a given eth_call. It collects the structured logs created during the execution of EVM +// if the given transaction was added on top of the provided block and returns them as a JSON object. +// You can provide -2 as a block number to trace on top of the pending block. +func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { + // First try to retrieve the state + statedb, header, err := api.eth.APIBackend.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + // Try to retrieve the specified block + var block *types.Block + if hash, ok := blockNrOrHash.Hash(); ok { + block = api.eth.blockchain.GetBlockByHash(hash) + } else if number, ok := blockNrOrHash.Number(); ok { + block = api.eth.blockchain.GetBlockByNumber(uint64(number)) + } + if block == nil { + return nil, fmt.Errorf("block %v not found: %v", blockNrOrHash, err) + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + _, _, statedb, err = api.computeTxEnv(block, 0, reexec) + if err != nil { + return nil, err + } + } + + // Execute the trace + msg := args.ToMessage(api.eth.APIBackend.RPCGasCap()) + vmctx := core.NewEVMContext(msg, header, api.eth.blockchain, nil) + return api.traceTx(ctx, msg, vmctx, statedb, config) +} + // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. @@ -786,12 +825,8 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } // computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { +func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { // Create the parent state database - block := api.eth.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash) - } parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) @@ -824,5 +859,5 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash) + return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 80ac92fe4a..41d8657787 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -429,6 +429,12 @@ web3._extend({ params: 2, inputFormatter: [null, null] }), + new web3._extend.Method({ + name: 'traceCall', + call: 'debug_traceCall', + params: 3, + inputFormatter: [null, null, null] + }), new web3._extend.Method({ name: 'preimage', call: 'debug_preimage', From c5d28f0b2765e65f1b5cd733d7190d36a7410e32 Mon Sep 17 00:00:00 2001 From: Osoro Bironga Date: Mon, 7 Sep 2020 14:07:15 +0300 Subject: [PATCH 003/948] accounts: abi/bid/backends; cleaned doc errors, camelCase refactors and anonymous variable assignments (#21514) Co-authored-by: Osoro Bironga --- accounts/abi/bind/backends/simulated.go | 132 +++++++++---------- accounts/abi/bind/backends/simulated_test.go | 20 +-- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 973d95531d..a43cb94678 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -45,7 +45,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. +// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend. var _ bind.ContractBackend = (*SimulatedBackend)(nil) var ( @@ -55,7 +55,7 @@ var ( ) // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in -// the background. Its main purpose is to allow easily testing contract bindings. +// the background. Its main purpose is to allow for easy testing of contract bindings. // Simulated backend implements the following interfaces: // ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor, // DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender @@ -123,10 +123,10 @@ func (b *SimulatedBackend) Rollback() { func (b *SimulatedBackend) rollback() { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) - statedb, _ := b.blockchain.State() + stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) } // stateByBlockNumber retrieves a state by a given blocknumber. @@ -146,12 +146,12 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return nil, err } - return statedb.GetCode(contract), nil + return stateDB.GetCode(contract), nil } // BalanceAt returns the wei balance of a certain account in the blockchain. @@ -159,12 +159,12 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return nil, err } - return statedb.GetBalance(contract), nil + return stateDB.GetBalance(contract), nil } // NonceAt returns the nonce of a certain account in the blockchain. @@ -172,12 +172,12 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return 0, err } - return statedb.GetNonce(contract), nil + return stateDB.GetNonce(contract), nil } // StorageAt returns the value of key in the storage of an account in the blockchain. @@ -185,17 +185,17 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return nil, err } - val := statedb.GetState(contract, key) + val := stateDB.GetState(contract, key) return val[:], nil } // TransactionReceipt returns the receipt of a transaction. -func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { +func (b *SimulatedBackend) TransactionReceipt(_ context.Context, txHash common.Hash) (*types.Receipt, error) { b.mu.Lock() defer b.mu.Unlock() @@ -207,7 +207,7 @@ func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common // blockchain. The isPending return value indicates whether the transaction has been // mined yet. Note that the transaction may not be part of the canonical chain even if // it's not pending. -func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { +func (b *SimulatedBackend) TransactionByHash(_ context.Context, txHash common.Hash) (*types.Transaction, bool, error) { b.mu.Lock() defer b.mu.Unlock() @@ -222,8 +222,8 @@ func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common. return nil, false, ethereum.NotFound } -// BlockByHash retrieves a block based on the block hash -func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { +// BlockByHash retrieves a block based on the block hash. +func (b *SimulatedBackend) BlockByHash(_ context.Context, hash common.Hash) (*types.Block, error) { b.mu.Lock() defer b.mu.Unlock() @@ -250,7 +250,7 @@ func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) ( // blockByNumberNoLock retrieves a block from the database by number, caching it // (associated with its hash) if found without Lock. -func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big.Int) (*types.Block, error) { +func (b *SimulatedBackend) blockByNumberNoLock(_ context.Context, number *big.Int) (*types.Block, error) { if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { return b.blockchain.CurrentBlock(), nil } @@ -264,7 +264,7 @@ func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big. } // HeaderByHash returns a block header from the current canonical chain. -func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { +func (b *SimulatedBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { b.mu.Lock() defer b.mu.Unlock() @@ -282,7 +282,7 @@ func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) ( // HeaderByNumber returns a block header from the current canonical chain. If number is // nil, the latest known header is returned. -func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) { +func (b *SimulatedBackend) HeaderByNumber(_ context.Context, block *big.Int) (*types.Header, error) { b.mu.Lock() defer b.mu.Unlock() @@ -293,8 +293,8 @@ func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) ( return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil } -// TransactionCount returns the number of transactions in a given block -func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { +// TransactionCount returns the number of transactions in a given block. +func (b *SimulatedBackend) TransactionCount(_ context.Context, blockHash common.Hash) (uint, error) { b.mu.Lock() defer b.mu.Unlock() @@ -310,8 +310,8 @@ func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash commo return uint(block.Transactions().Len()), nil } -// TransactionInBlock returns the transaction for a specific block at a specific index -func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { +// TransactionInBlock returns the transaction for a specific block at a specific index. +func (b *SimulatedBackend) TransactionInBlock(_ context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { b.mu.Lock() defer b.mu.Unlock() @@ -338,7 +338,7 @@ func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash com } // PendingCodeAt returns the code associated with an account in the pending state. -func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { +func (b *SimulatedBackend) PendingCodeAt(_ context.Context, contract common.Address) ([]byte, error) { b.mu.Lock() defer b.mu.Unlock() @@ -357,14 +357,14 @@ func newRevertError(result *core.ExecutionResult) *revertError { } } -// revertError is an API error that encompassas an EVM revertal with JSON error +// revertError is an API error that encompasses an EVM revert with JSON error // code and a binary data blob. type revertError struct { error reason string // revert reason hex encoded } -// ErrorCode returns the JSON error code for a revertal. +// ErrorCode returns the JSON error code for a revert. // See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal func (e *revertError) ErrorCode() int { return 3 @@ -383,11 +383,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { return nil, errBlockNumberUnsupported } - state, err := b.blockchain.State() + stateDB, err := b.blockchain.State() if err != nil { return nil, err } - res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB) if err != nil { return nil, err } @@ -417,7 +417,7 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving // the nonce currently pending for the account. -func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { +func (b *SimulatedBackend) PendingNonceAt(_ context.Context, account common.Address) (uint64, error) { b.mu.Lock() defer b.mu.Unlock() @@ -426,7 +426,7 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad // SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated // chain doesn't have miners, we just return a gas price of 1 for any call. -func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { +func (b *SimulatedBackend) SuggestGasPrice(_ context.Context) (*big.Int, error) { return big.NewInt(1), nil } @@ -525,7 +525,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs // callContract implements common code between normal and pending contract calls. // state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) { +func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) { // Ensure message is initialized properly. if call.GasPrice == nil { call.GasPrice = big.NewInt(1) @@ -537,23 +537,23 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM call.Value = new(big.Int) } // Set infinite balance to the fake caller account. - from := statedb.GetOrNewStateObject(call.From) + from := stateDB.GetOrNewStateObject(call.From) from.SetBalance(math.MaxBig256) // Execute the call. - msg := callmsg{call} + msg := callMsg{call} evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) - gaspool := new(core.GasPool).AddGas(math.MaxUint64) + vmEnv := vm.NewEVM(evmContext, stateDB, b.config, vm.Config{}) + gasPool := new(core.GasPool).AddGas(math.MaxUint64) - return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() + return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() } // SendTransaction updates the pending block to include the given transaction. // It panics if the transaction is invalid. -func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { +func (b *SimulatedBackend) SendTransaction(_ context.Context, tx *types.Transaction) error { b.mu.Lock() defer b.mu.Unlock() @@ -572,10 +572,10 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa } block.AddTxWithChain(b.blockchain, tx) }) - statedb, _ := b.blockchain.State() + stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) return nil } @@ -589,7 +589,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter // Block filter requested, construct a single-shot filter filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics) } else { - // Initialize unset filter boundaried to run from genesis to chain head + // Initialize unset filter boundaries to run from genesis to chain head from := int64(0) if query.FromBlock != nil { from = query.FromBlock.Int64() @@ -607,15 +607,15 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter return nil, err } res := make([]types.Log, len(logs)) - for i, log := range logs { - res[i] = *log + for i, nLog := range logs { + res[i] = *nLog } return res, nil } // SubscribeFilterLogs creates a background log filtering operation, returning a // subscription immediately, which can be used to stream the found events. -func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { +func (b *SimulatedBackend) SubscribeFilterLogs(_ context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { // Subscribe to contract events sink := make(chan []*types.Log) @@ -629,9 +629,9 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere for { select { case logs := <-sink: - for _, log := range logs { + for _, nlog := range logs { select { - case ch <- *log: + case ch <- *nlog: case err := <-sub.Err(): return err case <-quit: @@ -647,8 +647,8 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere }), nil } -// SubscribeNewHead returns an event subscription for a new header -func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { +// SubscribeNewHead returns an event subscription for a new header. +func (b *SimulatedBackend) SubscribeNewHead(_ context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { // subscribe to a new head sink := make(chan *types.Header) sub := b.events.SubscribeNewHeads(sink) @@ -687,10 +687,10 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { block.OffsetTime(int64(adjustment.Seconds())) }) - statedb, _ := b.blockchain.State() + stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) return nil } @@ -700,19 +700,19 @@ func (b *SimulatedBackend) Blockchain() *core.BlockChain { return b.blockchain } -// callmsg implements core.Message to allow passing it as a transaction simulator. -type callmsg struct { +// callMsg implements core.Message to allow passing it as a transaction simulator. +type callMsg struct { ethereum.CallMsg } -func (m callmsg) From() common.Address { return m.CallMsg.From } -func (m callmsg) Nonce() uint64 { return 0 } -func (m callmsg) CheckNonce() bool { return false } -func (m callmsg) To() *common.Address { return m.CallMsg.To } -func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } -func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } -func (m callmsg) Value() *big.Int { return m.CallMsg.Value } -func (m callmsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) From() common.Address { return m.CallMsg.From } +func (m callMsg) Nonce() uint64 { return 0 } +func (m callMsg) CheckNonce() bool { return false } +func (m callMsg) To() *common.Address { return m.CallMsg.To } +func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callMsg) Value() *big.Int { return m.CallMsg.Value } +func (m callMsg) Data() []byte { return m.CallMsg.Data } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. @@ -724,18 +724,18 @@ type filterBackend struct { func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") } -func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) { +func (fb *filterBackend) HeaderByNumber(_ context.Context, block rpc.BlockNumber) (*types.Header, error) { if block == rpc.LatestBlockNumber { return fb.bc.CurrentHeader(), nil } return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil } -func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { +func (fb *filterBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { return fb.bc.GetHeaderByHash(hash), nil } -func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { +func (fb *filterBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Receipts, error) { number := rawdb.ReadHeaderNumber(fb.db, hash) if number == nil { return nil, nil @@ -743,7 +743,7 @@ func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (typ return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil } -func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { +func (fb *filterBackend) GetLogs(_ context.Context, hash common.Hash) ([][]*types.Log, error) { number := rawdb.ReadHeaderNumber(fb.db, hash) if number == nil { return nil, nil @@ -759,7 +759,7 @@ func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*ty return logs, nil } -func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { +func (fb *filterBackend) SubscribeNewTxsEvent(_ chan<- core.NewTxsEvent) event.Subscription { return nullSubscription() } @@ -775,13 +775,13 @@ func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscr return fb.bc.SubscribeLogsEvent(ch) } -func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { +func (fb *filterBackend) SubscribePendingLogsEvent(_ chan<- []*types.Log) event.Subscription { return nullSubscription() } func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } -func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) { +func (fb *filterBackend) ServiceFilter(_ context.Context, _ *bloombits.MatcherSession) { panic("not supported") } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 9087d74bc6..2dffe26f6e 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -129,8 +129,8 @@ func TestNewSimulatedBackend(t *testing.T) { t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config) } - statedb, _ := sim.blockchain.State() - bal := statedb.GetBalance(testAddr) + stateDB, _ := sim.blockchain.State() + bal := stateDB.GetBalance(testAddr) if bal.Cmp(expectedBal) != 0 { t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) } @@ -521,7 +521,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000) defer sim.Close() - receipant := common.HexToAddress("deadbeef") + recipient := common.HexToAddress("deadbeef") var cases = []struct { name string message ethereum.CallMsg @@ -530,7 +530,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { }{ {"EstimateWithoutPrice", ethereum.CallMsg{ From: addr, - To: &receipant, + To: &recipient, Gas: 0, GasPrice: big.NewInt(0), Value: big.NewInt(1000), @@ -539,7 +539,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { {"EstimateWithPrice", ethereum.CallMsg{ From: addr, - To: &receipant, + To: &recipient, Gas: 0, GasPrice: big.NewInt(1000), Value: big.NewInt(1000), @@ -548,7 +548,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { {"EstimateWithVeryHighPrice", ethereum.CallMsg{ From: addr, - To: &receipant, + To: &recipient, Gas: 0, GasPrice: big.NewInt(1e14), // gascost = 2.1ether Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether @@ -557,7 +557,7 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { {"EstimateWithSuperhighPrice", ethereum.CallMsg{ From: addr, - To: &receipant, + To: &recipient, Gas: 0, GasPrice: big.NewInt(2e14), // gascost = 4.2ether Value: big.NewInt(1000), @@ -1086,12 +1086,12 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) { t.Errorf("result from %v was not nil: %v", key, res) } if val != nil { - rerr, ok := err.(*revertError) + rErr, ok := err.(*revertError) if !ok { t.Errorf("expect revert error") } - if rerr.Error() != "execution reverted: "+val.(string) { - t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) + if rErr.Error() != "execution reverted: "+val.(string) { + t.Errorf("error was malformed: got %v want %v", rErr.Error(), val) } } else { // revert(0x0,0x0) From d54f2f2e5e393741ff887a364877c0fddd5571a5 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 8 Sep 2020 08:47:48 +0000 Subject: [PATCH 004/948] whisper: remove whisper (#21487) * whisper: remove whisper * Update cmd/geth/config.go Co-authored-by: Marius van der Wijden * cmd/geth: warn on enabling whisper + remove more whisper deps * mobile: remove all whisper references Co-authored-by: Marius van der Wijden Co-authored-by: Martin Holst Swende --- cmd/geth/config.go | 27 +- cmd/geth/consolecmd_test.go | 8 +- cmd/geth/usage.go | 2 +- cmd/utils/flags.go | 27 +- cmd/wnode/main.go | 773 ----------- mobile/geth.go | 10 - mobile/shhclient.go | 195 --- mobile/types.go | 93 -- .../009c5adfa4fd685caef58e1ce932fa7fb209730a | Bin 61 -> 0 bytes tests/fuzzers/whisperv6/whisper-fuzzer.go | 90 -- whisper/mailserver/mailserver.go | 209 --- whisper/mailserver/server_test.go | 235 ---- whisper/shhclient/client.go | 193 --- whisper/whisperv6/api.go | 593 --------- whisper/whisperv6/api_test.go | 64 - whisper/whisperv6/benchmarks_test.go | 208 --- whisper/whisperv6/config.go | 31 - whisper/whisperv6/doc.go | 92 -- whisper/whisperv6/envelope.go | 280 ---- whisper/whisperv6/envelope_test.go | 91 -- whisper/whisperv6/filter.go | 262 ---- whisper/whisperv6/filter_test.go | 836 ------------ whisper/whisperv6/gen_criteria_json.go | 66 - whisper/whisperv6/gen_message_json.go | 84 -- whisper/whisperv6/gen_newmessage_json.go | 90 -- whisper/whisperv6/message.go | 355 ----- whisper/whisperv6/message_test.go | 471 ------- whisper/whisperv6/peer.go | 268 ---- whisper/whisperv6/topic.go | 56 - whisper/whisperv6/topic_test.go | 134 -- whisper/whisperv6/whisper.go | 1140 ----------------- whisper/whisperv6/whisper_test.go | 928 -------------- 32 files changed, 17 insertions(+), 7894 deletions(-) delete mode 100644 cmd/wnode/main.go delete mode 100644 mobile/shhclient.go delete mode 100644 tests/fuzzers/whisperv6/corpus/009c5adfa4fd685caef58e1ce932fa7fb209730a delete mode 100644 tests/fuzzers/whisperv6/whisper-fuzzer.go delete mode 100644 whisper/mailserver/mailserver.go delete mode 100644 whisper/mailserver/server_test.go delete mode 100644 whisper/shhclient/client.go delete mode 100644 whisper/whisperv6/api.go delete mode 100644 whisper/whisperv6/api_test.go delete mode 100644 whisper/whisperv6/benchmarks_test.go delete mode 100644 whisper/whisperv6/config.go delete mode 100644 whisper/whisperv6/doc.go delete mode 100644 whisper/whisperv6/envelope.go delete mode 100644 whisper/whisperv6/envelope_test.go delete mode 100644 whisper/whisperv6/filter.go delete mode 100644 whisper/whisperv6/filter_test.go delete mode 100644 whisper/whisperv6/gen_criteria_json.go delete mode 100644 whisper/whisperv6/gen_message_json.go delete mode 100644 whisper/whisperv6/gen_newmessage_json.go delete mode 100644 whisper/whisperv6/message.go delete mode 100644 whisper/whisperv6/message_test.go delete mode 100644 whisper/whisperv6/peer.go delete mode 100644 whisper/whisperv6/topic.go delete mode 100644 whisper/whisperv6/topic_test.go delete mode 100644 whisper/whisperv6/whisper.go delete mode 100644 whisper/whisperv6/whisper_test.go diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 5fa64abe36..2c15a4c832 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -29,9 +29,9 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" "github.com/naoina/toml" ) @@ -75,7 +75,6 @@ type ethstatsConfig struct { type gethConfig struct { Eth eth.Config - Shh whisper.Config Node node.Config Ethstats ethstatsConfig } @@ -110,7 +109,6 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ Eth: eth.DefaultConfig, - Shh: whisper.DefaultConfig, Node: defaultNodeConfig(), } @@ -131,19 +129,18 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } - utils.SetShhConfig(ctx, stack, &cfg.Shh) + utils.SetShhConfig(ctx, stack) return stack, cfg } // enableWhisper returns true in case one of the whisper flags is set. -func enableWhisper(ctx *cli.Context) bool { +func checkWhisper(ctx *cli.Context) { for _, flag := range whisperFlags { if ctx.GlobalIsSet(flag.GetName()) { - return true + log.Warn("deprecated whisper flag detected. Whisper has been moved to github.com/ethereum/whisper") } } - return false } // makeFullNode loads geth configuration and creates the Ethereum backend. @@ -152,21 +149,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { backend := utils.RegisterEthService(stack, &cfg.Eth) - // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode - shhEnabled := enableWhisper(ctx) - shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name) - if shhEnabled || shhAutoEnabled { - if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) { - cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name)) - } - if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) { - cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name) - } - if ctx.GlobalIsSet(utils.WhisperRestrictConnectionBetweenLightClientsFlag.Name) { - cfg.Shh.RestrictConnectionBetweenLightClients = true - } - utils.RegisterShhService(stack, &cfg.Shh) - } + checkWhisper(ctx) // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { utils.RegisterGraphQLService(stack, backend, cfg.Node) diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index a2489892e4..6c100e18d9 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -31,7 +31,7 @@ import ( ) const ( - ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0" + ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0" httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" ) @@ -43,7 +43,7 @@ func TestConsoleWelcome(t *testing.T) { // Start a geth console, make sure it's cleaned up and terminate the console geth := runGeth(t, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--shh", + "--etherbase", coinbase, "console") // Gather all the infos the welcome message needs to contain @@ -83,11 +83,9 @@ func TestIPCAttachWelcome(t *testing.T) { defer os.RemoveAll(ws) ipc = filepath.Join(ws, "geth.ipc") } - // Note: we need --shh because testAttachWelcome checks for default - // list of ipc modules and shh is included there. geth := runGeth(t, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--shh", "--ipcpath", ipc) + "--etherbase", coinbase, "--ipcpath", ipc) defer func() { geth.Interrupt() diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 44f5750fee..5e004c42be 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -211,7 +211,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Flags: metricsFlags, }, { - Name: "WHISPER (EXPERIMENTAL)", + Name: "WHISPER (deprecated)", Flags: whisperFlags, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 403888c12f..e6382c1634 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -63,7 +63,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" pcsclite "github.com/gballet/go-libpcsclite" cli "gopkg.in/urfave/cli.v1" ) @@ -639,12 +638,12 @@ var ( WhisperMaxMessageSizeFlag = cli.IntFlag{ Name: "shh.maxmessagesize", Usage: "Max message size accepted", - Value: int(whisper.DefaultMaxMessageSize), + Value: 1024 * 1024, } WhisperMinPOWFlag = cli.Float64Flag{ Name: "shh.pow", Usage: "Minimum POW accepted", - Value: whisper.DefaultMinimumPoW, + Value: 0.2, } WhisperRestrictConnectionBetweenLightClientsFlag = cli.BoolFlag{ Name: "shh.restrict-light", @@ -1465,15 +1464,12 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { } // SetShhConfig applies shh-related command line flags to the config. -func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) { - if ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) { - cfg.MaxMessageSize = uint32(ctx.GlobalUint(WhisperMaxMessageSizeFlag.Name)) - } - if ctx.GlobalIsSet(WhisperMinPOWFlag.Name) { - cfg.MinimumAcceptedPOW = ctx.GlobalFloat64(WhisperMinPOWFlag.Name) - } - if ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) { - cfg.RestrictConnectionBetweenLightClients = true +func SetShhConfig(ctx *cli.Context, stack *node.Node) { + if ctx.GlobalIsSet(WhisperEnabledFlag.Name) || + ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) || + ctx.GlobalIsSet(WhisperMinPOWFlag.Name) || + ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) { + log.Warn("Whisper support has been deprecated and the code has been moved to github.com/ethereum/whisper") } } @@ -1697,13 +1693,6 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { } } -// RegisterShhService configures Whisper and adds it to the given node. -func RegisterShhService(stack *node.Node, cfg *whisper.Config) { - if _, err := whisper.New(stack, cfg); err != nil { - Fatalf("Failed to register the Whisper service: %v", err) - } -} - // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // the given node. func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) { diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go deleted file mode 100644 index bdb0d306b7..0000000000 --- a/cmd/wnode/main.go +++ /dev/null @@ -1,773 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// This is a simple Whisper node. It could be used as a stand-alone bootstrap node. -// Also, could be used for different test and diagnostics purposes. - -package main - -import ( - "bufio" - "crypto/ecdsa" - crand "crypto/rand" - "crypto/sha512" - "encoding/binary" - "encoding/hex" - "flag" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/console/prompt" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/whisper/mailserver" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" - "golang.org/x/crypto/pbkdf2" -) - -const quitCommand = "~Q" -const entropySize = 32 - -// singletons -var ( - server *p2p.Server - shh *whisper.Whisper - done chan struct{} - mailServer mailserver.WMailServer - entropy [entropySize]byte - - input = bufio.NewReader(os.Stdin) -) - -// encryption -var ( - symKey []byte - pub *ecdsa.PublicKey - asymKey *ecdsa.PrivateKey - nodeid *ecdsa.PrivateKey - topic whisper.TopicType - - asymKeyID string - asymFilterID string - symFilterID string - symPass string - msPassword string -) - -// cmd arguments -var ( - bootstrapMode = flag.Bool("standalone", false, "boostrap node: don't initiate connection to peers, just wait for incoming connections") - forwarderMode = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither encrypt nor decrypt messages") - mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand") - requestMail = flag.Bool("mailclient", false, "request expired messages from the bootstrap server") - asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption") - generateKey = flag.Bool("generatekey", false, "generate and show the private key") - fileExMode = flag.Bool("fileexchange", false, "file exchange mode") - fileReader = flag.Bool("filereader", false, "load and decrypt messages saved as files, display as plain text") - testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics (password, etc.)") - echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics") - - argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level") - argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") - argWorkTime = flag.Uint("work", 5, "work time in seconds") - argMaxSize = flag.Uint("maxsize", uint(whisper.DefaultMaxMessageSize), "max size of message") - argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") - argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request") - - argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") - argPub = flag.String("pub", "", "public key for asymmetric encryption") - argDBPath = flag.String("dbpath", "", "path to the server's DB directory") - argIDFile = flag.String("idfile", "", "file name with node id (private key)") - argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)") - argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)") - argSaveDir = flag.String("savedir", "", "directory where all incoming messages will be saved as files") -) - -func main() { - processArgs() - initialize() - run() - shutdown() -} - -func processArgs() { - flag.Parse() - - if len(*argIDFile) > 0 { - var err error - nodeid, err = crypto.LoadECDSA(*argIDFile) - if err != nil { - utils.Fatalf("Failed to load file [%s]: %s.", *argIDFile, err) - } - } - - const enodePrefix = "enode://" - if len(*argEnode) > 0 { - if (*argEnode)[:len(enodePrefix)] != enodePrefix { - *argEnode = enodePrefix + *argEnode - } - } - - if len(*argTopic) > 0 { - x, err := hex.DecodeString(*argTopic) - if err != nil { - utils.Fatalf("Failed to parse the topic: %s", err) - } - topic = whisper.BytesToTopic(x) - } - - if *asymmetricMode && len(*argPub) > 0 { - var err error - if pub, err = crypto.UnmarshalPubkey(common.FromHex(*argPub)); err != nil { - utils.Fatalf("invalid public key") - } - } - - if len(*argSaveDir) > 0 { - if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) { - utils.Fatalf("Download directory '%s' does not exist", *argSaveDir) - } - } else if *fileExMode { - utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode") - } - - if *echoMode { - echo() - } -} - -func echo() { - fmt.Printf("ttl = %d \n", *argTTL) - fmt.Printf("workTime = %d \n", *argWorkTime) - fmt.Printf("pow = %f \n", *argPoW) - fmt.Printf("mspow = %f \n", *argServerPoW) - fmt.Printf("ip = %s \n", *argIP) - fmt.Printf("pub = %s \n", hexutil.Encode(crypto.FromECDSAPub(pub))) - fmt.Printf("idfile = %s \n", *argIDFile) - fmt.Printf("dbpath = %s \n", *argDBPath) - fmt.Printf("boot = %s \n", *argEnode) -} - -func initialize() { - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) - - done = make(chan struct{}) - var peers []*enode.Node - var err error - - if *generateKey { - key, err := crypto.GenerateKey() - if err != nil { - utils.Fatalf("Failed to generate private key: %s", err) - } - k := hex.EncodeToString(crypto.FromECDSA(key)) - fmt.Printf("Random private key: %s \n", k) - os.Exit(0) - } - - if *testMode { - symPass = "wwww" // ascii code: 0x77777777 - msPassword = "wwww" - } - - if *bootstrapMode { - if len(*argIP) == 0 { - argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ") - } - } else if *fileReader { - *bootstrapMode = true - } else { - if len(*argEnode) == 0 { - argEnode = scanLineA("Please enter the peer's enode: ") - } - peer := enode.MustParse(*argEnode) - peers = append(peers, peer) - } - - if *mailServerMode { - if len(msPassword) == 0 { - msPassword, err = prompt.Stdin.PromptPassword("Please enter the Mail Server password: ") - if err != nil { - utils.Fatalf("Failed to read Mail Server password: %s", err) - } - } - } - - cfg := &whisper.Config{ - MaxMessageSize: uint32(*argMaxSize), - MinimumAcceptedPOW: *argPoW, - } - shh = whisper.StandaloneWhisperService(cfg) - - if *argPoW != whisper.DefaultMinimumPoW { - err := shh.SetMinimumPoW(*argPoW) - if err != nil { - utils.Fatalf("Failed to set PoW: %s", err) - } - } - - if uint32(*argMaxSize) != whisper.DefaultMaxMessageSize { - err := shh.SetMaxMessageSize(uint32(*argMaxSize)) - if err != nil { - utils.Fatalf("Failed to set max message size: %s", err) - } - } - - asymKeyID, err = shh.NewKeyPair() - if err != nil { - utils.Fatalf("Failed to generate a new key pair: %s", err) - } - - asymKey, err = shh.GetPrivateKey(asymKeyID) - if err != nil { - utils.Fatalf("Failed to retrieve a new key pair: %s", err) - } - - if nodeid == nil { - tmpID, err := shh.NewKeyPair() - if err != nil { - utils.Fatalf("Failed to generate a new key pair: %s", err) - } - - nodeid, err = shh.GetPrivateKey(tmpID) - if err != nil { - utils.Fatalf("Failed to retrieve a new key pair: %s", err) - } - } - - maxPeers := 80 - if *bootstrapMode { - maxPeers = 800 - } - - _, err = crand.Read(entropy[:]) - if err != nil { - utils.Fatalf("crypto/rand failed: %s", err) - } - - if *mailServerMode { - shh.RegisterServer(&mailServer) - if err := mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW); err != nil { - utils.Fatalf("Failed to init MailServer: %s", err) - } - } - - server = &p2p.Server{ - Config: p2p.Config{ - PrivateKey: nodeid, - MaxPeers: maxPeers, - Name: common.MakeName("wnode", "6.0"), - Protocols: shh.Protocols(), - ListenAddr: *argIP, - NAT: nat.Any(), - BootstrapNodes: peers, - StaticNodes: peers, - TrustedNodes: peers, - }, - } -} - -func startServer() error { - err := server.Start() - if err != nil { - fmt.Printf("Failed to start Whisper peer: %s.", err) - return err - } - - fmt.Printf("my public key: %s \n", hexutil.Encode(crypto.FromECDSAPub(&asymKey.PublicKey))) - fmt.Println(server.NodeInfo().Enode) - - if *bootstrapMode { - configureNode() - fmt.Println("Bootstrap Whisper node started") - } else { - fmt.Println("Whisper node started") - // first see if we can establish connection, then ask for user input - waitForConnection(true) - configureNode() - } - - if *fileExMode { - fmt.Printf("Please type the file name to be send. To quit type: '%s'\n", quitCommand) - } else if *fileReader { - fmt.Printf("Please type the file name to be decrypted. To quit type: '%s'\n", quitCommand) - } else if !*forwarderMode { - fmt.Printf("Please type the message. To quit type: '%s'\n", quitCommand) - } - return nil -} - -func configureNode() { - var err error - var p2pAccept bool - - if *forwarderMode { - return - } - - if *asymmetricMode { - if len(*argPub) == 0 { - s := scanLine("Please enter the peer's public key: ") - b := common.FromHex(s) - if b == nil { - utils.Fatalf("Error: can not convert hexadecimal string") - } - if pub, err = crypto.UnmarshalPubkey(b); err != nil { - utils.Fatalf("Error: invalid peer public key") - } - } - } - - if *requestMail { - p2pAccept = true - if len(msPassword) == 0 { - msPassword, err = prompt.Stdin.PromptPassword("Please enter the Mail Server password: ") - if err != nil { - utils.Fatalf("Failed to read Mail Server password: %s", err) - } - } - } - - if !*asymmetricMode && !*forwarderMode { - if len(symPass) == 0 { - symPass, err = prompt.Stdin.PromptPassword("Please enter the password for symmetric encryption: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - } - - symKeyID, err := shh.AddSymKeyFromPassword(symPass) - if err != nil { - utils.Fatalf("Failed to create symmetric key: %s", err) - } - symKey, err = shh.GetSymKey(symKeyID) - if err != nil { - utils.Fatalf("Failed to save symmetric key: %s", err) - } - if len(*argTopic) == 0 { - generateTopic([]byte(symPass)) - } - - fmt.Printf("Filter is configured for the topic: %x \n", topic) - } - - if *mailServerMode { - if len(*argDBPath) == 0 { - argDBPath = scanLineA("Please enter the path to DB file: ") - } - } - - symFilter := whisper.Filter{ - KeySym: symKey, - Topics: [][]byte{topic[:]}, - AllowP2P: p2pAccept, - } - symFilterID, err = shh.Subscribe(&symFilter) - if err != nil { - utils.Fatalf("Failed to install filter: %s", err) - } - - asymFilter := whisper.Filter{ - KeyAsym: asymKey, - Topics: [][]byte{topic[:]}, - AllowP2P: p2pAccept, - } - asymFilterID, err = shh.Subscribe(&asymFilter) - if err != nil { - utils.Fatalf("Failed to install filter: %s", err) - } -} - -func generateTopic(password []byte) { - x := pbkdf2.Key(password, password, 4096, 128, sha512.New) - for i := 0; i < len(x); i++ { - topic[i%whisper.TopicLength] ^= x[i] - } -} - -func waitForConnection(timeout bool) { - var cnt int - var connected bool - for !connected { - time.Sleep(time.Millisecond * 50) - connected = server.PeerCount() > 0 - if timeout { - cnt++ - if cnt > 1000 { - utils.Fatalf("Timeout expired, failed to connect") - } - } - } - - fmt.Println("Connected to peer.") -} - -func run() { - err := startServer() - if err != nil { - return - } - defer server.Stop() - shh.Start() - defer shh.Stop() - - if !*forwarderMode { - go messageLoop() - } - - if *requestMail { - requestExpiredMessagesLoop() - } else if *fileExMode { - sendFilesLoop() - } else if *fileReader { - fileReaderLoop() - } else { - sendLoop() - } -} - -func shutdown() { - close(done) - mailServer.Close() -} - -func sendLoop() { - for { - s := scanLine("") - if s == quitCommand { - fmt.Println("Quit command received") - return - } - sendMsg([]byte(s)) - if *asymmetricMode { - // print your own message for convenience, - // because in asymmetric mode it is impossible to decrypt it - timestamp := time.Now().Unix() - from := crypto.PubkeyToAddress(asymKey.PublicKey) - fmt.Printf("\n%d <%x>: %s\n", timestamp, from, s) - } - } -} - -func sendFilesLoop() { - for { - s := scanLine("") - if s == quitCommand { - fmt.Println("Quit command received") - return - } - b, err := ioutil.ReadFile(s) - if err != nil { - fmt.Printf(">>> Error: %s \n", err) - } else { - h := sendMsg(b) - if (h == common.Hash{}) { - fmt.Printf(">>> Error: message was not sent \n") - } else { - timestamp := time.Now().Unix() - from := crypto.PubkeyToAddress(asymKey.PublicKey) - fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h) - } - } - } -} - -func fileReaderLoop() { - watcher1 := shh.GetFilter(symFilterID) - watcher2 := shh.GetFilter(asymFilterID) - if watcher1 == nil && watcher2 == nil { - fmt.Println("Error: neither symmetric nor asymmetric filter is installed") - return - } - - for { - s := scanLine("") - if s == quitCommand { - fmt.Println("Quit command received") - return - } - raw, err := ioutil.ReadFile(s) - if err != nil { - fmt.Printf(">>> Error: %s \n", err) - } else { - env := whisper.Envelope{Data: raw} // the topic is zero - msg := env.Open(watcher1) // force-open envelope regardless of the topic - if msg == nil { - msg = env.Open(watcher2) - } - if msg == nil { - fmt.Printf(">>> Error: failed to decrypt the message \n") - } else { - printMessageInfo(msg) - } - } - } -} - -func scanLine(prompt string) string { - if len(prompt) > 0 { - fmt.Print(prompt) - } - txt, err := input.ReadString('\n') - if err != nil { - utils.Fatalf("input error: %s", err) - } - txt = strings.TrimRight(txt, "\n\r") - return txt -} - -func scanLineA(prompt string) *string { - s := scanLine(prompt) - return &s -} - -func scanUint(prompt string) uint32 { - s := scanLine(prompt) - i, err := strconv.Atoi(s) - if err != nil { - utils.Fatalf("Fail to parse the lower time limit: %s", err) - } - return uint32(i) -} - -func sendMsg(payload []byte) common.Hash { - params := whisper.MessageParams{ - Src: asymKey, - Dst: pub, - KeySym: symKey, - Payload: payload, - Topic: topic, - TTL: uint32(*argTTL), - PoW: *argPoW, - WorkTime: uint32(*argWorkTime), - } - - msg, err := whisper.NewSentMessage(¶ms) - if err != nil { - utils.Fatalf("failed to create new message: %s", err) - } - - envelope, err := msg.Wrap(¶ms) - if err != nil { - fmt.Printf("failed to seal message: %v \n", err) - return common.Hash{} - } - - err = shh.Send(envelope) - if err != nil { - fmt.Printf("failed to send message: %v \n", err) - return common.Hash{} - } - - return envelope.Hash() -} - -func messageLoop() { - sf := shh.GetFilter(symFilterID) - if sf == nil { - utils.Fatalf("symmetric filter is not installed") - } - - af := shh.GetFilter(asymFilterID) - if af == nil { - utils.Fatalf("asymmetric filter is not installed") - } - - ticker := time.NewTicker(time.Millisecond * 50) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - m1 := sf.Retrieve() - m2 := af.Retrieve() - messages := append(m1, m2...) - for _, msg := range messages { - reportedOnce := false - if !*fileExMode && len(msg.Payload) <= 2048 { - printMessageInfo(msg) - reportedOnce = true - } - - // All messages are saved upon specifying argSaveDir. - // fileExMode only specifies how messages are displayed on the console after they are saved. - // if fileExMode == true, only the hashes are displayed, since messages might be too big. - if len(*argSaveDir) > 0 { - writeMessageToFile(*argSaveDir, msg, !reportedOnce) - } - } - case <-done: - return - } - } -} - -func printMessageInfo(msg *whisper.ReceivedMessage) { - timestamp := fmt.Sprintf("%d", msg.Sent) // unix timestamp for diagnostics - text := string(msg.Payload) - - var address common.Address - if msg.Src != nil { - address = crypto.PubkeyToAddress(*msg.Src) - } - - if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { - fmt.Printf("\n%s <%x>: %s\n", timestamp, address, text) // message from myself - } else { - fmt.Printf("\n%s [%x]: %s\n", timestamp, address, text) // message from a peer - } -} - -func writeMessageToFile(dir string, msg *whisper.ReceivedMessage, show bool) { - if len(dir) == 0 { - return - } - - timestamp := fmt.Sprintf("%d", msg.Sent) - name := fmt.Sprintf("%x", msg.EnvelopeHash) - - var address common.Address - if msg.Src != nil { - address = crypto.PubkeyToAddress(*msg.Src) - } - - env := shh.GetEnvelope(msg.EnvelopeHash) - if env == nil { - fmt.Printf("\nUnexpected error: envelope not found: %x\n", msg.EnvelopeHash) - return - } - - // this is a sample code; uncomment if you don't want to save your own messages. - //if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { - // fmt.Printf("\n%s <%x>: message from myself received, not saved: '%s'\n", timestamp, address, name) - // return - //} - - fullpath := filepath.Join(dir, name) - err := ioutil.WriteFile(fullpath, env.Data, 0644) - if err != nil { - fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err) - } else if show { - fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(env.Data)) - } -} - -func requestExpiredMessagesLoop() { - var key, peerID, bloom []byte - var timeLow, timeUpp uint32 - var t string - var xt whisper.TopicType - - keyID, err := shh.AddSymKeyFromPassword(msPassword) - if err != nil { - utils.Fatalf("Failed to create symmetric key for mail request: %s", err) - } - key, err = shh.GetSymKey(keyID) - if err != nil { - utils.Fatalf("Failed to save symmetric key for mail request: %s", err) - } - peerID = extractIDFromEnode(*argEnode) - shh.AllowP2PMessagesFromPeer(peerID) - - for { - timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") - timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ") - t = scanLine("Enter the topic (hex). Press enter to request all messages, regardless of the topic: ") - if len(t) == whisper.TopicLength*2 { - x, err := hex.DecodeString(t) - if err != nil { - fmt.Printf("Failed to parse the topic: %s \n", err) - continue - } - xt = whisper.BytesToTopic(x) - bloom = whisper.TopicToBloom(xt) - obfuscateBloom(bloom) - } else if len(t) == 0 { - bloom = whisper.MakeFullNodeBloom() - } else { - fmt.Println("Error: topic is invalid, request aborted") - continue - } - - if timeUpp == 0 { - timeUpp = 0xFFFFFFFF - } - - data := make([]byte, 8, 8+whisper.BloomFilterSize) - binary.BigEndian.PutUint32(data, timeLow) - binary.BigEndian.PutUint32(data[4:], timeUpp) - data = append(data, bloom...) - - var params whisper.MessageParams - params.PoW = *argServerPoW - params.Payload = data - params.KeySym = key - params.Src = asymKey - params.WorkTime = 5 - - msg, err := whisper.NewSentMessage(¶ms) - if err != nil { - utils.Fatalf("failed to create new message: %s", err) - } - env, err := msg.Wrap(¶ms) - if err != nil { - utils.Fatalf("Wrap failed: %s", err) - } - - err = shh.RequestHistoricMessages(peerID, env) - if err != nil { - utils.Fatalf("Failed to send P2P message: %s", err) - } - - time.Sleep(time.Second * 5) - } -} - -func extractIDFromEnode(s string) []byte { - n, err := enode.Parse(enode.ValidSchemes, s) - if err != nil { - utils.Fatalf("Failed to parse node: %s", err) - } - return n.ID().Bytes() -} - -// obfuscateBloom adds 16 random bits to the bloom -// filter, in order to obfuscate the containing topics. -// it does so deterministically within every session. -// despite additional bits, it will match on average -// 32000 times less messages than full node's bloom filter. -func obfuscateBloom(bloom []byte) { - const half = entropySize / 2 - for i := 0; i < half; i++ { - x := int(entropy[i]) - if entropy[half+i] < 128 { - x += 256 - } - - bloom[x/8] = 1 << uint(x%8) // set the bit number X - } -} diff --git a/mobile/geth.go b/mobile/geth.go index d614f8eb36..ba58507d63 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" ) // NodeConfig represents the collection of configuration values to fine tune the Geth @@ -71,9 +70,6 @@ type NodeConfig struct { // It has the form "nodename:secret@host:port" EthereumNetStats string - // WhisperEnabled specifies whether the node should run the Whisper protocol. - WhisperEnabled bool - // Listening address of pprof server. PprofAddress string } @@ -186,12 +182,6 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { } } } - // Register the Whisper protocol if requested - if config.WhisperEnabled { - if _, err := whisper.New(rawStack, &whisper.DefaultConfig); err != nil { - return nil, fmt.Errorf("whisper init: %v", err) - } - } return &Node{rawStack}, nil } diff --git a/mobile/shhclient.go b/mobile/shhclient.go deleted file mode 100644 index 90a8b83c39..0000000000 --- a/mobile/shhclient.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains a wrapper for the Whisper client. - -package geth - -import ( - "github.com/ethereum/go-ethereum/whisper/shhclient" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" -) - -// WhisperClient provides access to the Ethereum APIs. -type WhisperClient struct { - client *shhclient.Client -} - -// NewWhisperClient connects a client to the given URL. -func NewWhisperClient(rawurl string) (client *WhisperClient, _ error) { - rawClient, err := shhclient.Dial(rawurl) - return &WhisperClient{rawClient}, err -} - -// GetVersion returns the Whisper sub-protocol version. -func (wc *WhisperClient) GetVersion(ctx *Context) (version string, _ error) { - return wc.client.Version(ctx.context) -} - -// Info returns diagnostic information about the whisper node. -func (wc *WhisperClient) GetInfo(ctx *Context) (info *Info, _ error) { - rawInfo, err := wc.client.Info(ctx.context) - return &Info{&rawInfo}, err -} - -// SetMaxMessageSize sets the maximal message size allowed by this node. Incoming -// and outgoing messages with a larger size will be rejected. Whisper message size -// can never exceed the limit imposed by the underlying P2P protocol (10 Mb). -func (wc *WhisperClient) SetMaxMessageSize(ctx *Context, size int32) error { - return wc.client.SetMaxMessageSize(ctx.context, uint32(size)) -} - -// SetMinimumPoW (experimental) sets the minimal PoW required by this node. -// This experimental function was introduced for the future dynamic adjustment of -// PoW requirement. If the node is overwhelmed with messages, it should raise the -// PoW requirement and notify the peers. The new value should be set relative to -// the old value (e.g. double). The old value could be obtained via shh_info call. -func (wc *WhisperClient) SetMinimumPoW(ctx *Context, pow float64) error { - return wc.client.SetMinimumPoW(ctx.context, pow) -} - -// Marks specific peer trusted, which will allow it to send historic (expired) messages. -// Note This function is not adding new nodes, the node needs to exists as a peer. -func (wc *WhisperClient) MarkTrustedPeer(ctx *Context, enode string) error { - return wc.client.MarkTrustedPeer(ctx.context, enode) -} - -// NewKeyPair generates a new public and private key pair for message decryption and encryption. -// It returns an identifier that can be used to refer to the key. -func (wc *WhisperClient) NewKeyPair(ctx *Context) (string, error) { - return wc.client.NewKeyPair(ctx.context) -} - -// AddPrivateKey stored the key pair, and returns its ID. -func (wc *WhisperClient) AddPrivateKey(ctx *Context, key []byte) (string, error) { - return wc.client.AddPrivateKey(ctx.context, key) -} - -// DeleteKeyPair delete the specifies key. -func (wc *WhisperClient) DeleteKeyPair(ctx *Context, id string) (string, error) { - return wc.client.DeleteKeyPair(ctx.context, id) -} - -// HasKeyPair returns an indication if the node has a private key or -// key pair matching the given ID. -func (wc *WhisperClient) HasKeyPair(ctx *Context, id string) (bool, error) { - return wc.client.HasKeyPair(ctx.context, id) -} - -// GetPublicKey return the public key for a key ID. -func (wc *WhisperClient) GetPublicKey(ctx *Context, id string) ([]byte, error) { - return wc.client.PublicKey(ctx.context, id) -} - -// GetPrivateKey return the private key for a key ID. -func (wc *WhisperClient) GetPrivateKey(ctx *Context, id string) ([]byte, error) { - return wc.client.PrivateKey(ctx.context, id) -} - -// NewSymmetricKey generates a random symmetric key and returns its identifier. -// Can be used encrypting and decrypting messages where the key is known to both parties. -func (wc *WhisperClient) NewSymmetricKey(ctx *Context) (string, error) { - return wc.client.NewSymmetricKey(ctx.context) -} - -// AddSymmetricKey stores the key, and returns its identifier. -func (wc *WhisperClient) AddSymmetricKey(ctx *Context, key []byte) (string, error) { - return wc.client.AddSymmetricKey(ctx.context, key) -} - -// GenerateSymmetricKeyFromPassword generates the key from password, stores it, and returns its identifier. -func (wc *WhisperClient) GenerateSymmetricKeyFromPassword(ctx *Context, passwd string) (string, error) { - return wc.client.GenerateSymmetricKeyFromPassword(ctx.context, passwd) -} - -// HasSymmetricKey returns an indication if the key associated with the given id is stored in the node. -func (wc *WhisperClient) HasSymmetricKey(ctx *Context, id string) (bool, error) { - return wc.client.HasSymmetricKey(ctx.context, id) -} - -// GetSymmetricKey returns the symmetric key associated with the given identifier. -func (wc *WhisperClient) GetSymmetricKey(ctx *Context, id string) ([]byte, error) { - return wc.client.GetSymmetricKey(ctx.context, id) -} - -// DeleteSymmetricKey deletes the symmetric key associated with the given identifier. -func (wc *WhisperClient) DeleteSymmetricKey(ctx *Context, id string) error { - return wc.client.DeleteSymmetricKey(ctx.context, id) -} - -// Post a message onto the network. -func (wc *WhisperClient) Post(ctx *Context, message *NewMessage) (string, error) { - return wc.client.Post(ctx.context, *message.newMessage) -} - -// NewHeadHandler is a client-side subscription callback to invoke on events and -// subscription failure. -type NewMessageHandler interface { - OnNewMessage(message *Message) - OnError(failure string) -} - -// SubscribeMessages subscribes to messages that match the given criteria. This method -// is only supported on bi-directional connections such as websockets and IPC. -// NewMessageFilter uses polling and is supported over HTTP. -func (wc *WhisperClient) SubscribeMessages(ctx *Context, criteria *Criteria, handler NewMessageHandler, buffer int) (*Subscription, error) { - // Subscribe to the event internally - ch := make(chan *whisper.Message, buffer) - rawSub, err := wc.client.SubscribeMessages(ctx.context, *criteria.criteria, ch) - if err != nil { - return nil, err - } - // Start up a dispatcher to feed into the callback - go func() { - for { - select { - case message := <-ch: - handler.OnNewMessage(&Message{message}) - - case err := <-rawSub.Err(): - if err != nil { - handler.OnError(err.Error()) - } - return - } - } - }() - return &Subscription{rawSub}, nil -} - -// NewMessageFilter creates a filter within the node. This filter can be used to poll -// for new messages (see FilterMessages) that satisfy the given criteria. A filter can -// timeout when it was polled for in whisper.filterTimeout. -func (wc *WhisperClient) NewMessageFilter(ctx *Context, criteria *Criteria) (string, error) { - return wc.client.NewMessageFilter(ctx.context, *criteria.criteria) -} - -// DeleteMessageFilter removes the filter associated with the given id. -func (wc *WhisperClient) DeleteMessageFilter(ctx *Context, id string) error { - return wc.client.DeleteMessageFilter(ctx.context, id) -} - -// GetFilterMessages retrieves all messages that are received between the last call to -// this function and match the criteria that where given when the filter was created. -func (wc *WhisperClient) GetFilterMessages(ctx *Context, id string) (*Messages, error) { - rawFilterMessages, err := wc.client.FilterMessages(ctx.context, id) - if err != nil { - return nil, err - } - res := make([]*whisper.Message, len(rawFilterMessages)) - copy(res, rawFilterMessages) - return &Messages{res}, nil -} diff --git a/mobile/types.go b/mobile/types.go index b9c44c25d7..9d75520282 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -26,7 +26,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" ) // A Nonce is a 64-bit hash which proves (combined with the mix-hash) that @@ -345,95 +344,3 @@ func (r *Receipt) GetLogs() *Logs { return &Logs{r.receipt.Logs} } func (r *Receipt) GetTxHash() *Hash { return &Hash{r.receipt.TxHash} } func (r *Receipt) GetContractAddress() *Address { return &Address{r.receipt.ContractAddress} } func (r *Receipt) GetGasUsed() int64 { return int64(r.receipt.GasUsed) } - -// Info represents a diagnostic information about the whisper node. -type Info struct { - info *whisper.Info -} - -// NewMessage represents a new whisper message that is posted through the RPC. -type NewMessage struct { - newMessage *whisper.NewMessage -} - -func NewNewMessage() *NewMessage { - nm := &NewMessage{ - newMessage: new(whisper.NewMessage), - } - return nm -} - -func (nm *NewMessage) GetSymKeyID() string { return nm.newMessage.SymKeyID } -func (nm *NewMessage) SetSymKeyID(symKeyID string) { nm.newMessage.SymKeyID = symKeyID } -func (nm *NewMessage) GetPublicKey() []byte { return nm.newMessage.PublicKey } -func (nm *NewMessage) SetPublicKey(publicKey []byte) { - nm.newMessage.PublicKey = common.CopyBytes(publicKey) -} -func (nm *NewMessage) GetSig() string { return nm.newMessage.Sig } -func (nm *NewMessage) SetSig(sig string) { nm.newMessage.Sig = sig } -func (nm *NewMessage) GetTTL() int64 { return int64(nm.newMessage.TTL) } -func (nm *NewMessage) SetTTL(ttl int64) { nm.newMessage.TTL = uint32(ttl) } -func (nm *NewMessage) GetPayload() []byte { return nm.newMessage.Payload } -func (nm *NewMessage) SetPayload(payload []byte) { nm.newMessage.Payload = common.CopyBytes(payload) } -func (nm *NewMessage) GetPowTime() int64 { return int64(nm.newMessage.PowTime) } -func (nm *NewMessage) SetPowTime(powTime int64) { nm.newMessage.PowTime = uint32(powTime) } -func (nm *NewMessage) GetPowTarget() float64 { return nm.newMessage.PowTarget } -func (nm *NewMessage) SetPowTarget(powTarget float64) { nm.newMessage.PowTarget = powTarget } -func (nm *NewMessage) GetTargetPeer() string { return nm.newMessage.TargetPeer } -func (nm *NewMessage) SetTargetPeer(targetPeer string) { nm.newMessage.TargetPeer = targetPeer } -func (nm *NewMessage) GetTopic() []byte { return nm.newMessage.Topic[:] } -func (nm *NewMessage) SetTopic(topic []byte) { nm.newMessage.Topic = whisper.BytesToTopic(topic) } - -// Message represents a whisper message. -type Message struct { - message *whisper.Message -} - -func (m *Message) GetSig() []byte { return m.message.Sig } -func (m *Message) GetTTL() int64 { return int64(m.message.TTL) } -func (m *Message) GetTimestamp() int64 { return int64(m.message.Timestamp) } -func (m *Message) GetPayload() []byte { return m.message.Payload } -func (m *Message) GetPoW() float64 { return m.message.PoW } -func (m *Message) GetHash() []byte { return m.message.Hash } -func (m *Message) GetDst() []byte { return m.message.Dst } - -// Messages represents an array of messages. -type Messages struct { - messages []*whisper.Message -} - -// Size returns the number of messages in the slice. -func (m *Messages) Size() int { - return len(m.messages) -} - -// Get returns the message at the given index from the slice. -func (m *Messages) Get(index int) (message *Message, _ error) { - if index < 0 || index >= len(m.messages) { - return nil, errors.New("index out of bounds") - } - return &Message{m.messages[index]}, nil -} - -// Criteria holds various filter options for inbound messages. -type Criteria struct { - criteria *whisper.Criteria -} - -func NewCriteria(topic []byte) *Criteria { - c := &Criteria{ - criteria: new(whisper.Criteria), - } - encodedTopic := whisper.BytesToTopic(topic) - c.criteria.Topics = []whisper.TopicType{encodedTopic} - return c -} - -func (c *Criteria) GetSymKeyID() string { return c.criteria.SymKeyID } -func (c *Criteria) SetSymKeyID(symKeyID string) { c.criteria.SymKeyID = symKeyID } -func (c *Criteria) GetPrivateKeyID() string { return c.criteria.PrivateKeyID } -func (c *Criteria) SetPrivateKeyID(privateKeyID string) { c.criteria.PrivateKeyID = privateKeyID } -func (c *Criteria) GetSig() []byte { return c.criteria.Sig } -func (c *Criteria) SetSig(sig []byte) { c.criteria.Sig = common.CopyBytes(sig) } -func (c *Criteria) GetMinPow() float64 { return c.criteria.MinPow } -func (c *Criteria) SetMinPow(pow float64) { c.criteria.MinPow = pow } diff --git a/tests/fuzzers/whisperv6/corpus/009c5adfa4fd685caef58e1ce932fa7fb209730a b/tests/fuzzers/whisperv6/corpus/009c5adfa4fd685caef58e1ce932fa7fb209730a deleted file mode 100644 index af2f0826730441cc5abb122074afd090fad2eb95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 XcmaEK>c|9A!Hu2QZfJ0QyTbqgS<53t diff --git a/tests/fuzzers/whisperv6/whisper-fuzzer.go b/tests/fuzzers/whisperv6/whisper-fuzzer.go deleted file mode 100644 index 379e4224fd..0000000000 --- a/tests/fuzzers/whisperv6/whisper-fuzzer.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "bytes" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/whisper/whisperv6" -) - -type MessageParams struct { - Topic whisperv6.TopicType - WorkTime uint32 - TTL uint32 - KeySym []byte - Payload []byte -} - -//export fuzzer_entry -func Fuzz(input []byte) int { - - var paramsDecoded MessageParams - err := rlp.DecodeBytes(input, ¶msDecoded) - if err != nil { - return 0 - } - var params whisperv6.MessageParams - params.KeySym = make([]byte, 32) - if len(paramsDecoded.KeySym) <= 32 { - copy(params.KeySym, paramsDecoded.KeySym) - } - if input[0] == 255 { - params.PoW = 0.01 - params.WorkTime = 1 - } else { - params.PoW = 0 - params.WorkTime = 0 - } - params.TTL = paramsDecoded.TTL - params.Payload = paramsDecoded.Payload - text := make([]byte, 0, 512) - text = append(text, params.Payload...) - params.Topic = paramsDecoded.Topic - params.Src, err = crypto.GenerateKey() - if err != nil { - return 0 - } - msg, err := whisperv6.NewSentMessage(¶ms) - if err != nil { - panic(err) - //return - } - env, err := msg.Wrap(¶ms) - if err != nil { - panic(err) - } - decrypted, err := env.OpenSymmetric(params.KeySym) - if err != nil { - panic(err) - } - if !decrypted.ValidateAndParse() { - panic("ValidateAndParse failed") - } - if !bytes.Equal(text, decrypted.Payload) { - panic("text != decrypted.Payload") - } - if len(decrypted.Signature) != 65 { - panic("Unexpected signature length") - } - if !whisperv6.IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { - panic("Unexpected public key") - } - return 0 -} diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go deleted file mode 100644 index 7312bbe23d..0000000000 --- a/whisper/mailserver/mailserver.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package mailserver provides a naive, example mailserver implementation -package mailserver - -import ( - "encoding/binary" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/opt" - "github.com/syndtr/goleveldb/leveldb/util" -) - -// WMailServer represents the state data of the mailserver. -type WMailServer struct { - db *leveldb.DB - w *whisper.Whisper - pow float64 - key []byte -} - -type DBKey struct { - timestamp uint32 - hash common.Hash - raw []byte -} - -// NewDbKey is a helper function that creates a levelDB -// key from a hash and an integer. -func NewDbKey(t uint32, h common.Hash) *DBKey { - const sz = common.HashLength + 4 - var k DBKey - k.timestamp = t - k.hash = h - k.raw = make([]byte, sz) - binary.BigEndian.PutUint32(k.raw, k.timestamp) - copy(k.raw[4:], k.hash[:]) - return &k -} - -// Init initializes the mail server. -func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) error { - var err error - if len(path) == 0 { - return fmt.Errorf("DB file is not specified") - } - - if len(password) == 0 { - return fmt.Errorf("password is not specified") - } - - s.db, err = leveldb.OpenFile(path, &opt.Options{OpenFilesCacheCapacity: 32}) - if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { - s.db, err = leveldb.RecoverFile(path, nil) - } - if err != nil { - return fmt.Errorf("open DB file: %s", err) - } - - s.w = shh - s.pow = pow - - MailServerKeyID, err := s.w.AddSymKeyFromPassword(password) - if err != nil { - return fmt.Errorf("create symmetric key: %s", err) - } - s.key, err = s.w.GetSymKey(MailServerKeyID) - if err != nil { - return fmt.Errorf("save symmetric key: %s", err) - } - return nil -} - -// Close cleans up before shutdown. -func (s *WMailServer) Close() { - if s.db != nil { - s.db.Close() - } -} - -// Archive stores the -func (s *WMailServer) Archive(env *whisper.Envelope) { - key := NewDbKey(env.Expiry-env.TTL, env.Hash()) - rawEnvelope, err := rlp.EncodeToBytes(env) - if err != nil { - log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err)) - } else { - err = s.db.Put(key.raw, rawEnvelope, nil) - if err != nil { - log.Error(fmt.Sprintf("Writing to DB failed: %s", err)) - } - } -} - -// DeliverMail responds with saved messages upon request by the -// messages' owner. -func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) { - if peer == nil { - log.Error("Whisper peer is nil") - return - } - - ok, lower, upper, bloom := s.validateRequest(peer.ID(), request) - if ok { - s.processRequest(peer, lower, upper, bloom) - } -} - -func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) []*whisper.Envelope { - ret := make([]*whisper.Envelope, 0) - var err error - var zero common.Hash - kl := NewDbKey(lower, zero) - ku := NewDbKey(upper+1, zero) // LevelDB is exclusive, while the Whisper API is inclusive - i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil) - defer i.Release() - - for i.Next() { - var envelope whisper.Envelope - err = rlp.DecodeBytes(i.Value(), &envelope) - if err != nil { - log.Error(fmt.Sprintf("RLP decoding failed: %s", err)) - } - - if whisper.BloomFilterMatch(bloom, envelope.Bloom()) { - if peer == nil { - // used for test purposes - ret = append(ret, &envelope) - } else { - err = s.w.SendP2PDirect(peer, &envelope) - if err != nil { - log.Error(fmt.Sprintf("Failed to send direct message to peer: %s", err)) - return nil - } - } - } - } - - err = i.Error() - if err != nil { - log.Error(fmt.Sprintf("Level DB iterator error: %s", err)) - } - - return ret -} - -func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) (bool, uint32, uint32, []byte) { - if s.pow > 0.0 && request.PoW() < s.pow { - return false, 0, 0, nil - } - - f := whisper.Filter{KeySym: s.key} - decrypted := request.Open(&f) - if decrypted == nil { - log.Warn("Failed to decrypt p2p request") - return false, 0, 0, nil - } - - src := crypto.FromECDSAPub(decrypted.Src) - if len(src)-len(peerID) == 1 { - src = src[1:] - } - - // if you want to check the signature, you can do it here. e.g.: - // if !bytes.Equal(peerID, src) { - if src == nil { - log.Warn("Wrong signature of p2p request") - return false, 0, 0, nil - } - - var bloom []byte - payloadSize := len(decrypted.Payload) - if payloadSize < 8 { - log.Warn("Undersized p2p request") - return false, 0, 0, nil - } else if payloadSize == 8 { - bloom = whisper.MakeFullNodeBloom() - } else if payloadSize < 8+whisper.BloomFilterSize { - log.Warn("Undersized bloom filter in p2p request") - return false, 0, 0, nil - } else { - bloom = decrypted.Payload[8 : 8+whisper.BloomFilterSize] - } - - lower := binary.BigEndian.Uint32(decrypted.Payload[:4]) - upper := binary.BigEndian.Uint32(decrypted.Payload[4:8]) - return true, lower, upper, bloom -} diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go deleted file mode 100644 index 069ec97d09..0000000000 --- a/whisper/mailserver/server_test.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package mailserver - -import ( - "bytes" - "crypto/ecdsa" - "encoding/binary" - "io/ioutil" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/node" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" -) - -const powRequirement = 0.00001 - -var keyID string -var shh *whisper.Whisper -var seed = time.Now().Unix() - -type ServerTestParams struct { - topic whisper.TopicType - low uint32 - upp uint32 - key *ecdsa.PrivateKey -} - -func assert(statement bool, text string, t *testing.T) { - if !statement { - t.Fatal(text) - } -} - -func TestDBKey(t *testing.T) { - var h common.Hash - i := uint32(time.Now().Unix()) - k := NewDbKey(i, h) - assert(len(k.raw) == common.HashLength+4, "wrong DB key length", t) - assert(byte(i%0x100) == k.raw[3], "raw representation should be big endian", t) - assert(byte(i/0x1000000) == k.raw[0], "big endian expected", t) -} - -func generateEnvelope(t *testing.T) *whisper.Envelope { - h := crypto.Keccak256Hash([]byte("test sample data")) - params := &whisper.MessageParams{ - KeySym: h[:], - Topic: whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F}, - Payload: []byte("test payload"), - PoW: powRequirement, - WorkTime: 2, - } - - msg, err := whisper.NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed to wrap with seed %d: %s.", seed, err) - } - return env -} - -func TestMailServer(t *testing.T) { - const password = "password_for_this_test" - const dbPath = "whisper-server-test" - - dir, err := ioutil.TempDir("", dbPath) - if err != nil { - t.Fatal(err) - } - - var server WMailServer - - stack, w := newNode(t) - defer stack.Close() - shh = w - - shh.RegisterServer(&server) - - err = server.Init(shh, dir, password, powRequirement) - if err != nil { - t.Fatal(err) - } - defer server.Close() - - keyID, err = shh.AddSymKeyFromPassword(password) - if err != nil { - t.Fatalf("Failed to create symmetric key for mail request: %s", err) - } - - rand.Seed(seed) - env := generateEnvelope(t) - server.Archive(env) - deliverTest(t, &server, env) -} - -func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) { - id, err := shh.NewKeyPair() - if err != nil { - t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err) - } - testPeerID, err := shh.GetPrivateKey(id) - if err != nil { - t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err) - } - birth := env.Expiry - env.TTL - p := &ServerTestParams{ - topic: env.Topic, - low: birth - 1, - upp: birth + 1, - key: testPeerID, - } - - singleRequest(t, server, env, p, true) - - p.low, p.upp = birth+1, 0xffffffff - singleRequest(t, server, env, p, false) - - p.low, p.upp = 0, birth-1 - singleRequest(t, server, env, p, false) - - p.low = birth - 1 - p.upp = birth + 1 - p.topic[0] = 0xFF - singleRequest(t, server, env, p, false) -} - -func singleRequest(t *testing.T, server *WMailServer, env *whisper.Envelope, p *ServerTestParams, expect bool) { - request := createRequest(t, p) - src := crypto.FromECDSAPub(&p.key.PublicKey) - ok, lower, upper, bloom := server.validateRequest(src, request) - if !ok { - t.Fatalf("request validation failed, seed: %d.", seed) - } - if lower != p.low { - t.Fatalf("request validation failed (lower bound), seed: %d.", seed) - } - if upper != p.upp { - t.Fatalf("request validation failed (upper bound), seed: %d.", seed) - } - expectedBloom := whisper.TopicToBloom(p.topic) - if !bytes.Equal(bloom, expectedBloom) { - t.Fatalf("request validation failed (topic), seed: %d.", seed) - } - - var exist bool - mail := server.processRequest(nil, p.low, p.upp, bloom) - for _, msg := range mail { - if msg.Hash() == env.Hash() { - exist = true - break - } - } - - if exist != expect { - t.Fatalf("error: exist = %v, seed: %d.", exist, seed) - } - - src[0]++ - ok, lower, upper, _ = server.validateRequest(src, request) - if !ok { - // request should be valid regardless of signature - t.Fatalf("request validation false negative, seed: %d (lower: %d, upper: %d).", seed, lower, upper) - } -} - -func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope { - bloom := whisper.TopicToBloom(p.topic) - data := make([]byte, 8) - binary.BigEndian.PutUint32(data, p.low) - binary.BigEndian.PutUint32(data[4:], p.upp) - data = append(data, bloom...) - - key, err := shh.GetSymKey(keyID) - if err != nil { - t.Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err) - } - - params := &whisper.MessageParams{ - KeySym: key, - Topic: p.topic, - Payload: data, - PoW: powRequirement * 2, - WorkTime: 2, - Src: p.key, - } - - msg, err := whisper.NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed to wrap with seed %d: %s.", seed, err) - } - return env -} - -// newNode creates a new node using a default config and -// creates and registers a new Whisper service on it. -func newNode(t *testing.T) (*node.Node, *whisper.Whisper) { - stack, err := node.New(&node.DefaultConfig) - if err != nil { - t.Fatalf("could not create new node: %v", err) - } - w, err := whisper.New(stack, &whisper.DefaultConfig) - if err != nil { - t.Fatalf("could not create new whisper service: %v", err) - } - err = stack.Start() - if err != nil { - t.Fatalf("could not start node: %v", err) - } - return stack, w -} diff --git a/whisper/shhclient/client.go b/whisper/shhclient/client.go deleted file mode 100644 index 4973113674..0000000000 --- a/whisper/shhclient/client.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shhclient - -import ( - "context" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rpc" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" -) - -// Client defines typed wrappers for the Whisper v6 RPC API. -type Client struct { - c *rpc.Client -} - -// Dial connects a client to the given URL. -func Dial(rawurl string) (*Client, error) { - c, err := rpc.Dial(rawurl) - if err != nil { - return nil, err - } - return NewClient(c), nil -} - -// NewClient creates a client that uses the given RPC client. -func NewClient(c *rpc.Client) *Client { - return &Client{c} -} - -// Version returns the Whisper sub-protocol version. -func (sc *Client) Version(ctx context.Context) (string, error) { - var result string - err := sc.c.CallContext(ctx, &result, "shh_version") - return result, err -} - -// Info returns diagnostic information about the whisper node. -func (sc *Client) Info(ctx context.Context) (whisper.Info, error) { - var info whisper.Info - err := sc.c.CallContext(ctx, &info, "shh_info") - return info, err -} - -// SetMaxMessageSize sets the maximal message size allowed by this node. Incoming -// and outgoing messages with a larger size will be rejected. Whisper message size -// can never exceed the limit imposed by the underlying P2P protocol (10 Mb). -func (sc *Client) SetMaxMessageSize(ctx context.Context, size uint32) error { - var ignored bool - return sc.c.CallContext(ctx, &ignored, "shh_setMaxMessageSize", size) -} - -// SetMinimumPoW (experimental) sets the minimal PoW required by this node. -// This experimental function was introduced for the future dynamic adjustment of -// PoW requirement. If the node is overwhelmed with messages, it should raise the -// PoW requirement and notify the peers. The new value should be set relative to -// the old value (e.g. double). The old value could be obtained via shh_info call. -func (sc *Client) SetMinimumPoW(ctx context.Context, pow float64) error { - var ignored bool - return sc.c.CallContext(ctx, &ignored, "shh_setMinPoW", pow) -} - -// MarkTrustedPeer marks specific peer trusted, which will allow it to send historic (expired) messages. -// Note This function is not adding new nodes, the node needs to exists as a peer. -func (sc *Client) MarkTrustedPeer(ctx context.Context, enode string) error { - var ignored bool - return sc.c.CallContext(ctx, &ignored, "shh_markTrustedPeer", enode) -} - -// NewKeyPair generates a new public and private key pair for message decryption and encryption. -// It returns an identifier that can be used to refer to the key. -func (sc *Client) NewKeyPair(ctx context.Context) (string, error) { - var id string - return id, sc.c.CallContext(ctx, &id, "shh_newKeyPair") -} - -// AddPrivateKey stored the key pair, and returns its ID. -func (sc *Client) AddPrivateKey(ctx context.Context, key []byte) (string, error) { - var id string - return id, sc.c.CallContext(ctx, &id, "shh_addPrivateKey", hexutil.Bytes(key)) -} - -// DeleteKeyPair delete the specifies key. -func (sc *Client) DeleteKeyPair(ctx context.Context, id string) (string, error) { - var ignored bool - return id, sc.c.CallContext(ctx, &ignored, "shh_deleteKeyPair", id) -} - -// HasKeyPair returns an indication if the node has a private key or -// key pair matching the given ID. -func (sc *Client) HasKeyPair(ctx context.Context, id string) (bool, error) { - var has bool - return has, sc.c.CallContext(ctx, &has, "shh_hasKeyPair", id) -} - -// PublicKey return the public key for a key ID. -func (sc *Client) PublicKey(ctx context.Context, id string) ([]byte, error) { - var key hexutil.Bytes - return []byte(key), sc.c.CallContext(ctx, &key, "shh_getPublicKey", id) -} - -// PrivateKey return the private key for a key ID. -func (sc *Client) PrivateKey(ctx context.Context, id string) ([]byte, error) { - var key hexutil.Bytes - return []byte(key), sc.c.CallContext(ctx, &key, "shh_getPrivateKey", id) -} - -// NewSymmetricKey generates a random symmetric key and returns its identifier. -// Can be used encrypting and decrypting messages where the key is known to both parties. -func (sc *Client) NewSymmetricKey(ctx context.Context) (string, error) { - var id string - return id, sc.c.CallContext(ctx, &id, "shh_newSymKey") -} - -// AddSymmetricKey stores the key, and returns its identifier. -func (sc *Client) AddSymmetricKey(ctx context.Context, key []byte) (string, error) { - var id string - return id, sc.c.CallContext(ctx, &id, "shh_addSymKey", hexutil.Bytes(key)) -} - -// GenerateSymmetricKeyFromPassword generates the key from password, stores it, and returns its identifier. -func (sc *Client) GenerateSymmetricKeyFromPassword(ctx context.Context, passwd string) (string, error) { - var id string - return id, sc.c.CallContext(ctx, &id, "shh_generateSymKeyFromPassword", passwd) -} - -// HasSymmetricKey returns an indication if the key associated with the given id is stored in the node. -func (sc *Client) HasSymmetricKey(ctx context.Context, id string) (bool, error) { - var found bool - return found, sc.c.CallContext(ctx, &found, "shh_hasSymKey", id) -} - -// GetSymmetricKey returns the symmetric key associated with the given identifier. -func (sc *Client) GetSymmetricKey(ctx context.Context, id string) ([]byte, error) { - var key hexutil.Bytes - return []byte(key), sc.c.CallContext(ctx, &key, "shh_getSymKey", id) -} - -// DeleteSymmetricKey deletes the symmetric key associated with the given identifier. -func (sc *Client) DeleteSymmetricKey(ctx context.Context, id string) error { - var ignored bool - return sc.c.CallContext(ctx, &ignored, "shh_deleteSymKey", id) -} - -// Post a message onto the network. -func (sc *Client) Post(ctx context.Context, message whisper.NewMessage) (string, error) { - var hash string - return hash, sc.c.CallContext(ctx, &hash, "shh_post", message) -} - -// SubscribeMessages subscribes to messages that match the given criteria. This method -// is only supported on bi-directional connections such as websockets and IPC. -// NewMessageFilter uses polling and is supported over HTTP. -func (sc *Client) SubscribeMessages(ctx context.Context, criteria whisper.Criteria, ch chan<- *whisper.Message) (ethereum.Subscription, error) { - return sc.c.ShhSubscribe(ctx, ch, "messages", criteria) -} - -// NewMessageFilter creates a filter within the node. This filter can be used to poll -// for new messages (see FilterMessages) that satisfy the given criteria. A filter can -// timeout when it was polled for in whisper.filterTimeout. -func (sc *Client) NewMessageFilter(ctx context.Context, criteria whisper.Criteria) (string, error) { - var id string - return id, sc.c.CallContext(ctx, &id, "shh_newMessageFilter", criteria) -} - -// DeleteMessageFilter removes the filter associated with the given id. -func (sc *Client) DeleteMessageFilter(ctx context.Context, id string) error { - var ignored bool - return sc.c.CallContext(ctx, &ignored, "shh_deleteMessageFilter", id) -} - -// FilterMessages retrieves all messages that are received between the last call to -// this function and match the criteria that where given when the filter was created. -func (sc *Client) FilterMessages(ctx context.Context, id string) ([]*whisper.Message, error) { - var messages []*whisper.Message - return messages, sc.c.CallContext(ctx, &messages, "shh_getFilterMessages", id) -} diff --git a/whisper/whisperv6/api.go b/whisper/whisperv6/api.go deleted file mode 100644 index d6d4c8d3de..0000000000 --- a/whisper/whisperv6/api.go +++ /dev/null @@ -1,593 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "context" - "crypto/ecdsa" - "errors" - "fmt" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rpc" -) - -// List of errors -var ( - ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") - ErrInvalidSymmetricKey = errors.New("invalid symmetric key") - ErrInvalidPublicKey = errors.New("invalid public key") - ErrInvalidSigningPubKey = errors.New("invalid signing public key") - ErrTooLowPoW = errors.New("message rejected, PoW too low") - ErrNoTopics = errors.New("missing topic(s)") -) - -// PublicWhisperAPI provides the whisper RPC service that can be -// use publicly without security implications. -type PublicWhisperAPI struct { - w *Whisper - - mu sync.Mutex - lastUsed map[string]time.Time // keeps track when a filter was polled for the last time. -} - -// NewPublicWhisperAPI create a new RPC whisper service. -func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI { - api := &PublicWhisperAPI{ - w: w, - lastUsed: make(map[string]time.Time), - } - return api -} - -// Version returns the Whisper sub-protocol version. -func (api *PublicWhisperAPI) Version(ctx context.Context) string { - return ProtocolVersionStr -} - -// Info contains diagnostic information. -type Info struct { - Memory int `json:"memory"` // Memory size of the floating messages in bytes. - Messages int `json:"messages"` // Number of floating messages. - MinPow float64 `json:"minPow"` // Minimal accepted PoW - MaxMessageSize uint32 `json:"maxMessageSize"` // Maximum accepted message size -} - -// Info returns diagnostic information about the whisper node. -func (api *PublicWhisperAPI) Info(ctx context.Context) Info { - stats := api.w.Stats() - return Info{ - Memory: stats.memoryUsed, - Messages: len(api.w.messageQueue) + len(api.w.p2pMsgQueue), - MinPow: api.w.MinPow(), - MaxMessageSize: api.w.MaxMessageSize(), - } -} - -// SetMaxMessageSize sets the maximum message size that is accepted. -// Upper limit is defined by MaxMessageSize. -func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) { - return true, api.w.SetMaxMessageSize(size) -} - -// SetMinPoW sets the minimum PoW, and notifies the peers. -func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { - return true, api.w.SetMinimumPoW(pow) -} - -// SetBloomFilter sets the new value of bloom filter, and notifies the peers. -func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) { - return true, api.w.SetBloomFilter(bloom) -} - -// MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages. -// Note: This function is not adding new nodes, the node needs to exists as a peer. -func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, url string) (bool, error) { - n, err := enode.Parse(enode.ValidSchemes, url) - if err != nil { - return false, err - } - return true, api.w.AllowP2PMessagesFromPeer(n.ID().Bytes()) -} - -// NewKeyPair generates a new public and private key pair for message decryption and encryption. -// It returns an ID that can be used to refer to the keypair. -func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) { - return api.w.NewKeyPair() -} - -// AddPrivateKey imports the given private key. -func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) { - key, err := crypto.ToECDSA(privateKey) - if err != nil { - return "", err - } - return api.w.AddKeyPair(key) -} - -// DeleteKeyPair removes the key with the given key if it exists. -func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) { - if ok := api.w.DeleteKeyPair(key); ok { - return true, nil - } - return false, fmt.Errorf("key pair %s not found", key) -} - -// HasKeyPair returns an indication if the node has a key pair that is associated with the given id. -func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool { - return api.w.HasKeyPair(id) -} - -// GetPublicKey returns the public key associated with the given key. The key is the hex -// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. -func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) { - key, err := api.w.GetPrivateKey(id) - if err != nil { - return hexutil.Bytes{}, err - } - return crypto.FromECDSAPub(&key.PublicKey), nil -} - -// GetPrivateKey returns the private key associated with the given key. The key is the hex -// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. -func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) { - key, err := api.w.GetPrivateKey(id) - if err != nil { - return hexutil.Bytes{}, err - } - return crypto.FromECDSA(key), nil -} - -// NewSymKey generate a random symmetric key. -// It returns an ID that can be used to refer to the key. -// Can be used encrypting and decrypting messages where the key is known to both parties. -func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) { - return api.w.GenerateSymKey() -} - -// AddSymKey import a symmetric key. -// It returns an ID that can be used to refer to the key. -// Can be used encrypting and decrypting messages where the key is known to both parties. -func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) { - return api.w.AddSymKeyDirect([]byte(key)) -} - -// GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID. -func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) { - return api.w.AddSymKeyFromPassword(passwd) -} - -// HasSymKey returns an indication if the node has a symmetric key associated with the given key. -func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool { - return api.w.HasSymKey(id) -} - -// GetSymKey returns the symmetric key associated with the given id. -func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) { - return api.w.GetSymKey(id) -} - -// DeleteSymKey deletes the symmetric key that is associated with the given id. -func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool { - return api.w.DeleteSymKey(id) -} - -// MakeLightClient turns the node into light client, which does not forward -// any incoming messages, and sends only messages originated in this node. -func (api *PublicWhisperAPI) MakeLightClient(ctx context.Context) bool { - api.w.SetLightClientMode(true) - return api.w.LightClientMode() -} - -// CancelLightClient cancels light client mode. -func (api *PublicWhisperAPI) CancelLightClient(ctx context.Context) bool { - api.w.SetLightClientMode(false) - return !api.w.LightClientMode() -} - -//go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go - -// NewMessage represents a new whisper message that is posted through the RPC. -type NewMessage struct { - SymKeyID string `json:"symKeyID"` - PublicKey []byte `json:"pubKey"` - Sig string `json:"sig"` - TTL uint32 `json:"ttl"` - Topic TopicType `json:"topic"` - Payload []byte `json:"payload"` - Padding []byte `json:"padding"` - PowTime uint32 `json:"powTime"` - PowTarget float64 `json:"powTarget"` - TargetPeer string `json:"targetPeer"` -} - -type newMessageOverride struct { - PublicKey hexutil.Bytes - Payload hexutil.Bytes - Padding hexutil.Bytes -} - -// Post posts a message on the Whisper network. -// returns the hash of the message in case of success. -func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) { - var ( - symKeyGiven = len(req.SymKeyID) > 0 - pubKeyGiven = len(req.PublicKey) > 0 - err error - ) - - // user must specify either a symmetric or an asymmetric key - if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { - return nil, ErrSymAsym - } - - params := &MessageParams{ - TTL: req.TTL, - Payload: req.Payload, - Padding: req.Padding, - WorkTime: req.PowTime, - PoW: req.PowTarget, - Topic: req.Topic, - } - - // Set key that is used to sign the message - if len(req.Sig) > 0 { - if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil { - return nil, err - } - } - - // Set symmetric key that is used to encrypt the message - if symKeyGiven { - if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption - return nil, ErrNoTopics - } - if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { - return nil, err - } - if !validateDataIntegrity(params.KeySym, aesKeyLength) { - return nil, ErrInvalidSymmetricKey - } - } - - // Set asymmetric key that is used to encrypt the message - if pubKeyGiven { - if params.Dst, err = crypto.UnmarshalPubkey(req.PublicKey); err != nil { - return nil, ErrInvalidPublicKey - } - } - - // encrypt and sent message - whisperMsg, err := NewSentMessage(params) - if err != nil { - return nil, err - } - - var result []byte - env, err := whisperMsg.Wrap(params) - if err != nil { - return nil, err - } - - // send to specific node (skip PoW check) - if len(req.TargetPeer) > 0 { - n, err := enode.Parse(enode.ValidSchemes, req.TargetPeer) - if err != nil { - return nil, fmt.Errorf("failed to parse target peer: %s", err) - } - err = api.w.SendP2PMessage(n.ID().Bytes(), env) - if err == nil { - hash := env.Hash() - result = hash[:] - } - return result, err - } - - // ensure that the message PoW meets the node's minimum accepted PoW - if req.PowTarget < api.w.MinPow() { - return nil, ErrTooLowPoW - } - - err = api.w.Send(env) - if err == nil { - hash := env.Hash() - result = hash[:] - } - return result, err -} - -//go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go - -// Criteria holds various filter options for inbound messages. -type Criteria struct { - SymKeyID string `json:"symKeyID"` - PrivateKeyID string `json:"privateKeyID"` - Sig []byte `json:"sig"` - MinPow float64 `json:"minPow"` - Topics []TopicType `json:"topics"` - AllowP2P bool `json:"allowP2P"` -} - -type criteriaOverride struct { - Sig hexutil.Bytes -} - -// Messages set up a subscription that fires events when messages arrive that match -// the given set of criteria. -func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) { - var ( - symKeyGiven = len(crit.SymKeyID) > 0 - pubKeyGiven = len(crit.PrivateKeyID) > 0 - err error - ) - - // ensure that the RPC connection supports subscriptions - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return nil, rpc.ErrNotificationsUnsupported - } - - // user must specify either a symmetric or an asymmetric key - if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { - return nil, ErrSymAsym - } - - filter := Filter{ - PoW: crit.MinPow, - Messages: make(map[common.Hash]*ReceivedMessage), - AllowP2P: crit.AllowP2P, - } - - if len(crit.Sig) > 0 { - if filter.Src, err = crypto.UnmarshalPubkey(crit.Sig); err != nil { - return nil, ErrInvalidSigningPubKey - } - } - - for i, bt := range crit.Topics { - if len(bt) == 0 || len(bt) > 4 { - return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt)) - } - filter.Topics = append(filter.Topics, bt[:]) - } - - // listen for message that are encrypted with the given symmetric key - if symKeyGiven { - if len(filter.Topics) == 0 { - return nil, ErrNoTopics - } - key, err := api.w.GetSymKey(crit.SymKeyID) - if err != nil { - return nil, err - } - if !validateDataIntegrity(key, aesKeyLength) { - return nil, ErrInvalidSymmetricKey - } - filter.KeySym = key - filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym) - } - - // listen for messages that are encrypted with the given public key - if pubKeyGiven { - filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID) - if err != nil || filter.KeyAsym == nil { - return nil, ErrInvalidPublicKey - } - } - - id, err := api.w.Subscribe(&filter) - if err != nil { - return nil, err - } - - // create subscription and start waiting for message events - rpcSub := notifier.CreateSubscription() - go func() { - // for now poll internally, refactor whisper internal for channel support - ticker := time.NewTicker(250 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if filter := api.w.GetFilter(id); filter != nil { - for _, rpcMessage := range toMessage(filter.Retrieve()) { - if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil { - log.Error("Failed to send notification", "err", err) - } - } - } - case <-rpcSub.Err(): - api.w.Unsubscribe(id) - return - case <-notifier.Closed(): - api.w.Unsubscribe(id) - return - } - } - }() - - return rpcSub, nil -} - -//go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go - -// Message is the RPC representation of a whisper message. -type Message struct { - Sig []byte `json:"sig,omitempty"` - TTL uint32 `json:"ttl"` - Timestamp uint32 `json:"timestamp"` - Topic TopicType `json:"topic"` - Payload []byte `json:"payload"` - Padding []byte `json:"padding"` - PoW float64 `json:"pow"` - Hash []byte `json:"hash"` - Dst []byte `json:"recipientPublicKey,omitempty"` -} - -type messageOverride struct { - Sig hexutil.Bytes - Payload hexutil.Bytes - Padding hexutil.Bytes - Hash hexutil.Bytes - Dst hexutil.Bytes -} - -// ToWhisperMessage converts an internal message into an API version. -func ToWhisperMessage(message *ReceivedMessage) *Message { - msg := Message{ - Payload: message.Payload, - Padding: message.Padding, - Timestamp: message.Sent, - TTL: message.TTL, - PoW: message.PoW, - Hash: message.EnvelopeHash.Bytes(), - Topic: message.Topic, - } - - if message.Dst != nil { - b := crypto.FromECDSAPub(message.Dst) - if b != nil { - msg.Dst = b - } - } - - if isMessageSigned(message.Raw[0]) { - b := crypto.FromECDSAPub(message.SigToPubKey()) - if b != nil { - msg.Sig = b - } - } - - return &msg -} - -// toMessage converts a set of messages to its RPC representation. -func toMessage(messages []*ReceivedMessage) []*Message { - msgs := make([]*Message, len(messages)) - for i, msg := range messages { - msgs[i] = ToWhisperMessage(msg) - } - return msgs -} - -// GetFilterMessages returns the messages that match the filter criteria and -// are received between the last poll and now. -func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) { - api.mu.Lock() - f := api.w.GetFilter(id) - if f == nil { - api.mu.Unlock() - return nil, fmt.Errorf("filter not found") - } - api.lastUsed[id] = time.Now() - api.mu.Unlock() - - receivedMessages := f.Retrieve() - messages := make([]*Message, 0, len(receivedMessages)) - for _, msg := range receivedMessages { - messages = append(messages, ToWhisperMessage(msg)) - } - - return messages, nil -} - -// DeleteMessageFilter deletes a filter. -func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) { - api.mu.Lock() - defer api.mu.Unlock() - - delete(api.lastUsed, id) - return true, api.w.Unsubscribe(id) -} - -// NewMessageFilter creates a new filter that can be used to poll for -// (new) messages that satisfy the given criteria. -func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) { - var ( - src *ecdsa.PublicKey - keySym []byte - keyAsym *ecdsa.PrivateKey - topics [][]byte - - symKeyGiven = len(req.SymKeyID) > 0 - asymKeyGiven = len(req.PrivateKeyID) > 0 - - err error - ) - - // user must specify either a symmetric or an asymmetric key - if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) { - return "", ErrSymAsym - } - - if len(req.Sig) > 0 { - if src, err = crypto.UnmarshalPubkey(req.Sig); err != nil { - return "", ErrInvalidSigningPubKey - } - } - - if symKeyGiven { - if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { - return "", err - } - if !validateDataIntegrity(keySym, aesKeyLength) { - return "", ErrInvalidSymmetricKey - } - } - - if asymKeyGiven { - if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil { - return "", err - } - } - - if len(req.Topics) > 0 { - topics = make([][]byte, len(req.Topics)) - for i, topic := range req.Topics { - topics[i] = make([]byte, TopicLength) - copy(topics[i], topic[:]) - } - } - - f := &Filter{ - Src: src, - KeySym: keySym, - KeyAsym: keyAsym, - PoW: req.MinPow, - AllowP2P: req.AllowP2P, - Topics: topics, - Messages: make(map[common.Hash]*ReceivedMessage), - } - - id, err := api.w.Subscribe(f) - if err != nil { - return "", err - } - - api.mu.Lock() - api.lastUsed[id] = time.Now() - api.mu.Unlock() - - return id, nil -} diff --git a/whisper/whisperv6/api_test.go b/whisper/whisperv6/api_test.go deleted file mode 100644 index 759ef221ed..0000000000 --- a/whisper/whisperv6/api_test.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "bytes" - "testing" - "time" -) - -func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - keyID, err := w.GenerateSymKey() - if err != nil { - t.Fatalf("Error generating symmetric key: %v", err) - } - api := PublicWhisperAPI{ - w: w, - lastUsed: make(map[string]time.Time), - } - - t1 := [4]byte{0xde, 0xea, 0xbe, 0xef} - t2 := [4]byte{0xca, 0xfe, 0xde, 0xca} - - crit := Criteria{ - SymKeyID: keyID, - Topics: []TopicType{TopicType(t1), TopicType(t2)}, - } - - _, err = api.NewMessageFilter(crit) - if err != nil { - t.Fatalf("Error creating the filter: %v", err) - } - - found := false - candidates := w.filters.getWatchersByTopic(TopicType(t1)) - for _, f := range candidates { - if len(f.Topics) == 2 { - if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) { - found = true - } - } - } - - if !found { - t.Fatalf("Could not find filter with both topics") - } -} diff --git a/whisper/whisperv6/benchmarks_test.go b/whisper/whisperv6/benchmarks_test.go deleted file mode 100644 index 0473179da5..0000000000 --- a/whisper/whisperv6/benchmarks_test.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "crypto/sha256" - "testing" - - "github.com/ethereum/go-ethereum/crypto" - "golang.org/x/crypto/pbkdf2" -) - -func BenchmarkDeriveKeyMaterial(b *testing.B) { - for i := 0; i < b.N; i++ { - pbkdf2.Key([]byte("test"), nil, 65356, aesKeyLength, sha256.New) - } -} - -func BenchmarkEncryptionSym(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - for i := 0; i < b.N; i++ { - msg, _ := NewSentMessage(params) - _, err := msg.Wrap(params) - if err != nil { - b.Errorf("failed Wrap with seed %d: %s.", seed, err) - b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload)) - return - } - } -} - -func BenchmarkEncryptionAsym(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - key, err := crypto.GenerateKey() - if err != nil { - b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - params.KeySym = nil - params.Dst = &key.PublicKey - - for i := 0; i < b.N; i++ { - msg, _ := NewSentMessage(params) - _, err := msg.Wrap(params) - if err != nil { - b.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - } -} - -func BenchmarkDecryptionSymValid(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, _ := NewSentMessage(params) - env, err := msg.Wrap(params) - if err != nil { - b.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - f := Filter{KeySym: params.KeySym} - - for i := 0; i < b.N; i++ { - msg := env.Open(&f) - if msg == nil { - b.Fatalf("failed to open with seed %d.", seed) - } - } -} - -func BenchmarkDecryptionSymInvalid(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, _ := NewSentMessage(params) - env, err := msg.Wrap(params) - if err != nil { - b.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - f := Filter{KeySym: []byte("arbitrary stuff here")} - - for i := 0; i < b.N; i++ { - msg := env.Open(&f) - if msg != nil { - b.Fatalf("opened envelope with invalid key, seed: %d.", seed) - } - } -} - -func BenchmarkDecryptionAsymValid(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - key, err := crypto.GenerateKey() - if err != nil { - b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - f := Filter{KeyAsym: key} - params.KeySym = nil - params.Dst = &key.PublicKey - msg, _ := NewSentMessage(params) - env, err := msg.Wrap(params) - if err != nil { - b.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - for i := 0; i < b.N; i++ { - msg := env.Open(&f) - if msg == nil { - b.Fatalf("fail to open, seed: %d.", seed) - } - } -} - -func BenchmarkDecryptionAsymInvalid(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - key, err := crypto.GenerateKey() - if err != nil { - b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - params.KeySym = nil - params.Dst = &key.PublicKey - msg, _ := NewSentMessage(params) - env, err := msg.Wrap(params) - if err != nil { - b.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - key, err = crypto.GenerateKey() - if err != nil { - b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - f := Filter{KeyAsym: key} - - for i := 0; i < b.N; i++ { - msg := env.Open(&f) - if msg != nil { - b.Fatalf("opened envelope with invalid key, seed: %d.", seed) - } - } -} - -func increment(x []byte) { - for i := 0; i < len(x); i++ { - x[i]++ - if x[i] != 0 { - break - } - } -} - -func BenchmarkPoW(b *testing.B) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - params.Payload = make([]byte, 32) - params.PoW = 10.0 - params.TTL = 1 - - for i := 0; i < b.N; i++ { - increment(params.Payload) - msg, _ := NewSentMessage(params) - _, err := msg.Wrap(params) - if err != nil { - b.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - } -} diff --git a/whisper/whisperv6/config.go b/whisper/whisperv6/config.go deleted file mode 100644 index 38eb9551cc..0000000000 --- a/whisper/whisperv6/config.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -// Config represents the configuration state of a whisper node. -type Config struct { - MaxMessageSize uint32 `toml:",omitempty"` - MinimumAcceptedPOW float64 `toml:",omitempty"` - RestrictConnectionBetweenLightClients bool `toml:",omitempty"` -} - -// DefaultConfig represents (shocker!) the default configuration. -var DefaultConfig = Config{ - MaxMessageSize: DefaultMaxMessageSize, - MinimumAcceptedPOW: DefaultMinimumPoW, - RestrictConnectionBetweenLightClients: true, -} diff --git a/whisper/whisperv6/doc.go b/whisper/whisperv6/doc.go deleted file mode 100644 index 44c0c3271c..0000000000 --- a/whisper/whisperv6/doc.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -Package whisper implements the Whisper protocol (version 6). - -Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP). -As such it may be likened and compared to both, not dissimilar to the -matter/energy duality (apologies to physicists for the blatant abuse of a -fundamental and beautiful natural principle). - -Whisper is a pure identity-based messaging system. Whisper provides a low-level -(non-application-specific) but easily-accessible API without being based upon -or prejudiced by the low-level hardware attributes and characteristics, -particularly the notion of singular endpoints. -*/ - -// Contains the Whisper protocol constant definitions - -package whisperv6 - -import ( - "time" - - "github.com/ethereum/go-ethereum/crypto" -) - -// Whisper protocol parameters -const ( - ProtocolVersion = uint64(6) // Protocol version number - ProtocolVersionStr = "6.0" // The same, as a string - ProtocolName = "shh" // Nickname of the protocol in geth - - // whisper protocol message codes, according to EIP-627 - statusCode = 0 // used by whisper protocol - messagesCode = 1 // normal whisper message - powRequirementCode = 2 // PoW requirement - bloomFilterExCode = 3 // bloom filter exchange - p2pRequestCode = 126 // peer-to-peer message, used by Dapp protocol - p2pMessageCode = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further) - NumberOfMessageCodes = 128 - - SizeMask = byte(3) // mask used to extract the size of payload size field from the flags - signatureFlag = byte(4) - - TopicLength = 4 // in bytes - signatureLength = crypto.SignatureLength // in bytes - aesKeyLength = 32 // in bytes - aesNonceLength = 12 // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize() - keyIDSize = 32 // in bytes - BloomFilterSize = 64 // in bytes - flagsLength = 1 - - EnvelopeHeaderLength = 20 - - MaxMessageSize = uint32(10 * 1024 * 1024) // maximum accepted size of a message. - DefaultMaxMessageSize = uint32(1024 * 1024) - DefaultMinimumPoW = 0.2 - - padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol - messageQueueLimit = 1024 - - expirationCycle = time.Second - transmissionCycle = 300 * time.Millisecond - - DefaultTTL = 50 // seconds - DefaultSyncAllowance = 10 // seconds -) - -// MailServer represents a mail server, capable of -// archiving the old messages for subsequent delivery -// to the peers. Any implementation must ensure that both -// functions are thread-safe. Also, they must return ASAP. -// DeliverMail should use directMessagesCode for delivery, -// in order to bypass the expiry checks. -type MailServer interface { - Archive(env *Envelope) - DeliverMail(whisperPeer *Peer, request *Envelope) -} diff --git a/whisper/whisperv6/envelope.go b/whisper/whisperv6/envelope.go deleted file mode 100644 index 5b6925edb3..0000000000 --- a/whisper/whisperv6/envelope.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the Whisper protocol Envelope element. - -package whisperv6 - -import ( - "crypto/ecdsa" - "encoding/binary" - "fmt" - gmath "math" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/rlp" -) - -// Envelope represents a clear-text data packet to transmit through the Whisper -// network. Its contents may or may not be encrypted and signed. -type Envelope struct { - Expiry uint32 - TTL uint32 - Topic TopicType - Data []byte - Nonce uint64 - - pow float64 // Message-specific PoW as described in the Whisper specification. - - // the following variables should not be accessed directly, use the corresponding function instead: Hash(), Bloom() - hash common.Hash // Cached hash of the envelope to avoid rehashing every time. - bloom []byte -} - -// size returns the size of envelope as it is sent (i.e. public fields only) -func (e *Envelope) size() int { - return EnvelopeHeaderLength + len(e.Data) -} - -// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce. -func (e *Envelope) rlpWithoutNonce() []byte { - res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Data}) - return res -} - -// NewEnvelope wraps a Whisper message with expiration and destination data -// included into an envelope for network forwarding. -func NewEnvelope(ttl uint32, topic TopicType, msg *sentMessage) *Envelope { - env := Envelope{ - Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()), - TTL: ttl, - Topic: topic, - Data: msg.Raw, - Nonce: 0, - } - - return &env -} - -// Seal closes the envelope by spending the requested amount of time as a proof -// of work on hashing the data. -func (e *Envelope) Seal(options *MessageParams) error { - if options.PoW == 0 { - // PoW is not required - return nil - } - - var target, bestLeadingZeros int - if options.PoW < 0 { - // target is not set - the function should run for a period - // of time specified in WorkTime param. Since we can predict - // the execution time, we can also adjust Expiry. - e.Expiry += options.WorkTime - } else { - target = e.powToFirstBit(options.PoW) - } - - rlp := e.rlpWithoutNonce() - buf := make([]byte, len(rlp)+8) - copy(buf, rlp) - asAnInt := new(big.Int) - - finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano() - for nonce := uint64(0); time.Now().UnixNano() < finish; { - for i := 0; i < 1024; i++ { - binary.BigEndian.PutUint64(buf[len(rlp):], nonce) - h := crypto.Keccak256(buf) - asAnInt.SetBytes(h) - leadingZeros := 256 - asAnInt.BitLen() - if leadingZeros > bestLeadingZeros { - e.Nonce, bestLeadingZeros = nonce, leadingZeros - if target > 0 && bestLeadingZeros >= target { - return nil - } - } - nonce++ - } - } - - if target > 0 && bestLeadingZeros < target { - return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime) - } - - return nil -} - -// PoW computes (if necessary) and returns the proof of work target -// of the envelope. -func (e *Envelope) PoW() float64 { - if e.pow == 0 { - e.calculatePoW(0) - } - return e.pow -} - -func (e *Envelope) calculatePoW(diff uint32) { - rlp := e.rlpWithoutNonce() - buf := make([]byte, len(rlp)+8) - copy(buf, rlp) - binary.BigEndian.PutUint64(buf[len(rlp):], e.Nonce) - powHash := new(big.Int).SetBytes(crypto.Keccak256(buf)) - leadingZeroes := 256 - powHash.BitLen() - x := gmath.Pow(2, float64(leadingZeroes)) - x /= float64(len(rlp)) - x /= float64(e.TTL + diff) - e.pow = x -} - -func (e *Envelope) powToFirstBit(pow float64) int { - x := pow - x *= float64(e.size()) - x *= float64(e.TTL) - bits := gmath.Log2(x) - bits = gmath.Ceil(bits) - res := int(bits) - if res < 1 { - res = 1 - } - return res -} - -// Hash returns the SHA3 hash of the envelope, calculating it if not yet done. -func (e *Envelope) Hash() common.Hash { - if (e.hash == common.Hash{}) { - encoded, _ := rlp.EncodeToBytes(e) - e.hash = crypto.Keccak256Hash(encoded) - } - return e.hash -} - -// DecodeRLP decodes an Envelope from an RLP data stream. -func (e *Envelope) DecodeRLP(s *rlp.Stream) error { - raw, err := s.Raw() - if err != nil { - return err - } - // The decoding of Envelope uses the struct fields but also needs - // to compute the hash of the whole RLP-encoded envelope. This - // type has the same structure as Envelope but is not an - // rlp.Decoder (does not implement DecodeRLP function). - // Only public members will be encoded. - type rlpenv Envelope - if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil { - return err - } - e.hash = crypto.Keccak256Hash(raw) - return nil -} - -// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key. -func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) { - message := &ReceivedMessage{Raw: e.Data} - err := message.decryptAsymmetric(key) - switch err { - case nil: - return message, nil - case ecies.ErrInvalidPublicKey: // addressed to somebody else - return nil, err - default: - return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err) - } -} - -// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key. -func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) { - msg = &ReceivedMessage{Raw: e.Data} - err = msg.decryptSymmetric(key) - if err != nil { - msg = nil - } - return msg, err -} - -// Open tries to decrypt an envelope, and populates the message fields in case of success. -func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) { - if watcher == nil { - return nil - } - - // The API interface forbids filters doing both symmetric and asymmetric encryption. - if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() { - return nil - } - - if watcher.expectsAsymmetricEncryption() { - msg, _ = e.OpenAsymmetric(watcher.KeyAsym) - if msg != nil { - msg.Dst = &watcher.KeyAsym.PublicKey - } - } else if watcher.expectsSymmetricEncryption() { - msg, _ = e.OpenSymmetric(watcher.KeySym) - if msg != nil { - msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) - } - } - - if msg != nil { - ok := msg.ValidateAndParse() - if !ok { - return nil - } - msg.Topic = e.Topic - msg.PoW = e.PoW() - msg.TTL = e.TTL - msg.Sent = e.Expiry - e.TTL - msg.EnvelopeHash = e.Hash() - } - return msg -} - -// Bloom maps 4-bytes Topic into 64-byte bloom filter with 3 bits set (at most). -func (e *Envelope) Bloom() []byte { - if e.bloom == nil { - e.bloom = TopicToBloom(e.Topic) - } - return e.bloom -} - -// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes) -func TopicToBloom(topic TopicType) []byte { - b := make([]byte, BloomFilterSize) - var index [3]int - for j := 0; j < 3; j++ { - index[j] = int(topic[j]) - if (topic[3] & (1 << uint(j))) != 0 { - index[j] += 256 - } - } - - for j := 0; j < 3; j++ { - byteIndex := index[j] / 8 - bitIndex := index[j] % 8 - b[byteIndex] = (1 << uint(bitIndex)) - } - return b -} - -// GetEnvelope retrieves an envelope from the message queue by its hash. -// It returns nil if the envelope can not be found. -func (w *Whisper) GetEnvelope(hash common.Hash) *Envelope { - w.poolMu.RLock() - defer w.poolMu.RUnlock() - return w.envelopes[hash] -} diff --git a/whisper/whisperv6/envelope_test.go b/whisper/whisperv6/envelope_test.go deleted file mode 100644 index c0bb4373b8..0000000000 --- a/whisper/whisperv6/envelope_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the tests associated with the Whisper protocol Envelope object. - -package whisperv6 - -import ( - mrand "math/rand" - "testing" - - "github.com/ethereum/go-ethereum/crypto" -) - -func TestPoWCalculationsWithNoLeadingZeros(t *testing.T) { - e := Envelope{ - TTL: 1, - Data: []byte{0xde, 0xad, 0xbe, 0xef}, - Nonce: 100000, - } - - e.calculatePoW(0) - - if e.pow != 0.07692307692307693 { - t.Fatalf("invalid PoW calculation. Expected 0.07692307692307693, got %v", e.pow) - } -} - -func TestPoWCalculationsWith8LeadingZeros(t *testing.T) { - e := Envelope{ - TTL: 1, - Data: []byte{0xde, 0xad, 0xbe, 0xef}, - Nonce: 276, - } - e.calculatePoW(0) - - if e.pow != 19.692307692307693 { - t.Fatalf("invalid PoW calculation. Expected 19.692307692307693, got %v", e.pow) - } -} - -func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) { - symKey := make([]byte, aesKeyLength) - mrand.Read(symKey) - - asymKey, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - - params := MessageParams{ - PoW: 0.01, - WorkTime: 1, - TTL: uint32(mrand.Intn(1024)), - Payload: make([]byte, 50), - KeySym: symKey, - Dst: nil, - } - - mrand.Read(params.Payload) - - msg, err := NewSentMessage(¶ms) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - - e, err := msg.Wrap(¶ms) - if err != nil { - t.Fatalf("Failed to Wrap the message in an envelope with seed %d: %s", seed, err) - } - - f := Filter{KeySym: symKey, KeyAsym: asymKey} - - decrypted := e.Open(&f) - if decrypted != nil { - t.Fatalf("Managed to decrypt a message with an invalid filter, seed %d", seed) - } -} diff --git a/whisper/whisperv6/filter.go b/whisper/whisperv6/filter.go deleted file mode 100644 index 6a5b79674b..0000000000 --- a/whisper/whisperv6/filter.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "crypto/ecdsa" - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" -) - -// Filter represents a Whisper message filter -type Filter struct { - Src *ecdsa.PublicKey // Sender of the message - KeyAsym *ecdsa.PrivateKey // Private Key of recipient - KeySym []byte // Key associated with the Topic - Topics [][]byte // Topics to filter messages with - PoW float64 // Proof of work as described in the Whisper spec - AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages - SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization - id string // unique identifier - - Messages map[common.Hash]*ReceivedMessage - mutex sync.RWMutex -} - -// Filters represents a collection of filters -type Filters struct { - watchers map[string]*Filter - - topicMatcher map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic - allTopicsMatcher map[*Filter]struct{} // list all the filters that will be notified of a new message, no matter what its topic is - - whisper *Whisper - mutex sync.RWMutex -} - -// NewFilters returns a newly created filter collection -func NewFilters(w *Whisper) *Filters { - return &Filters{ - watchers: make(map[string]*Filter), - topicMatcher: make(map[TopicType]map[*Filter]struct{}), - allTopicsMatcher: make(map[*Filter]struct{}), - whisper: w, - } -} - -// Install will add a new filter to the filter collection -func (fs *Filters) Install(watcher *Filter) (string, error) { - if watcher.KeySym != nil && watcher.KeyAsym != nil { - return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys") - } - - if watcher.Messages == nil { - watcher.Messages = make(map[common.Hash]*ReceivedMessage) - } - - id, err := GenerateRandomID() - if err != nil { - return "", err - } - - fs.mutex.Lock() - defer fs.mutex.Unlock() - - if fs.watchers[id] != nil { - return "", fmt.Errorf("failed to generate unique ID") - } - - if watcher.expectsSymmetricEncryption() { - watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym) - } - - watcher.id = id - fs.watchers[id] = watcher - fs.addTopicMatcher(watcher) - return id, err -} - -// Uninstall will remove a filter whose id has been specified from -// the filter collection -func (fs *Filters) Uninstall(id string) bool { - fs.mutex.Lock() - defer fs.mutex.Unlock() - if fs.watchers[id] != nil { - fs.removeFromTopicMatchers(fs.watchers[id]) - delete(fs.watchers, id) - return true - } - return false -} - -// addTopicMatcher adds a filter to the topic matchers. -// If the filter's Topics array is empty, it will be tried on every topic. -// Otherwise, it will be tried on the topics specified. -func (fs *Filters) addTopicMatcher(watcher *Filter) { - if len(watcher.Topics) == 0 { - fs.allTopicsMatcher[watcher] = struct{}{} - } else { - for _, t := range watcher.Topics { - topic := BytesToTopic(t) - if fs.topicMatcher[topic] == nil { - fs.topicMatcher[topic] = make(map[*Filter]struct{}) - } - fs.topicMatcher[topic][watcher] = struct{}{} - } - } -} - -// removeFromTopicMatchers removes a filter from the topic matchers -func (fs *Filters) removeFromTopicMatchers(watcher *Filter) { - delete(fs.allTopicsMatcher, watcher) - for _, topic := range watcher.Topics { - delete(fs.topicMatcher[BytesToTopic(topic)], watcher) - } -} - -// getWatchersByTopic returns a slice containing the filters that -// match a specific topic -func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter { - res := make([]*Filter, 0, len(fs.allTopicsMatcher)) - for watcher := range fs.allTopicsMatcher { - res = append(res, watcher) - } - for watcher := range fs.topicMatcher[topic] { - res = append(res, watcher) - } - return res -} - -// Get returns a filter from the collection with a specific ID -func (fs *Filters) Get(id string) *Filter { - fs.mutex.RLock() - defer fs.mutex.RUnlock() - return fs.watchers[id] -} - -// NotifyWatchers notifies any filter that has declared interest -// for the envelope's topic. -func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) { - var msg *ReceivedMessage - - fs.mutex.RLock() - defer fs.mutex.RUnlock() - - candidates := fs.getWatchersByTopic(env.Topic) - for _, watcher := range candidates { - if p2pMessage && !watcher.AllowP2P { - log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id)) - continue - } - - var match bool - if msg != nil { - match = watcher.MatchMessage(msg) - } else { - match = watcher.MatchEnvelope(env) - if match { - msg = env.Open(watcher) - if msg == nil { - log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id) - } - } else { - log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id) - } - } - - if match && msg != nil { - log.Trace("processing message: decrypted", "hash", env.Hash().Hex()) - if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) { - watcher.Trigger(msg) - } - } - } -} - -func (f *Filter) expectsAsymmetricEncryption() bool { - return f.KeyAsym != nil -} - -func (f *Filter) expectsSymmetricEncryption() bool { - return f.KeySym != nil -} - -// Trigger adds a yet-unknown message to the filter's list of -// received messages. -func (f *Filter) Trigger(msg *ReceivedMessage) { - f.mutex.Lock() - defer f.mutex.Unlock() - - if _, exist := f.Messages[msg.EnvelopeHash]; !exist { - f.Messages[msg.EnvelopeHash] = msg - } -} - -// Retrieve will return the list of all received messages associated -// to a filter. -func (f *Filter) Retrieve() (all []*ReceivedMessage) { - f.mutex.Lock() - defer f.mutex.Unlock() - - all = make([]*ReceivedMessage, 0, len(f.Messages)) - for _, msg := range f.Messages { - all = append(all, msg) - } - - f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages - return all -} - -// MatchMessage checks if the filter matches an already decrypted -// message (i.e. a Message that has already been handled by -// MatchEnvelope when checked by a previous filter). -// Topics are not checked here, since this is done by topic matchers. -func (f *Filter) MatchMessage(msg *ReceivedMessage) bool { - if f.PoW > 0 && msg.PoW < f.PoW { - return false - } - - if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() { - return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst) - } else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() { - return f.SymKeyHash == msg.SymKeyHash - } - return false -} - -// MatchEnvelope checks if it's worth decrypting the message. If -// it returns `true`, client code is expected to attempt decrypting -// the message and subsequently call MatchMessage. -// Topics are not checked here, since this is done by topic matchers. -func (f *Filter) MatchEnvelope(envelope *Envelope) bool { - return f.PoW <= 0 || envelope.pow >= f.PoW -} - -// IsPubKeyEqual checks that two public keys are equal -func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool { - if !ValidatePublicKey(a) { - return false - } else if !ValidatePublicKey(b) { - return false - } - // the curve is always the same, just compare the points - return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 -} diff --git a/whisper/whisperv6/filter_test.go b/whisper/whisperv6/filter_test.go deleted file mode 100644 index c95e506972..0000000000 --- a/whisper/whisperv6/filter_test.go +++ /dev/null @@ -1,836 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "math/big" - mrand "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -var seed int64 - -// InitSingleTest should be called in the beginning of every -// test, which uses RNG, in order to make the tests -// reproduciblity independent of their sequence. -func InitSingleTest() { - seed = time.Now().Unix() - mrand.Seed(seed) -} - -type FilterTestCase struct { - f *Filter - id string - alive bool - msgCnt int -} - -func generateFilter(t *testing.T, symmetric bool) (*Filter, error) { - var f Filter - f.Messages = make(map[common.Hash]*ReceivedMessage) - - const topicNum = 8 - f.Topics = make([][]byte, topicNum) - for i := 0; i < topicNum; i++ { - f.Topics[i] = make([]byte, 4) - mrand.Read(f.Topics[i]) - f.Topics[i][0] = 0x01 - } - - key, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("generateFilter 1 failed with seed %d.", seed) - return nil, err - } - f.Src = &key.PublicKey - - if symmetric { - f.KeySym = make([]byte, aesKeyLength) - mrand.Read(f.KeySym) - f.SymKeyHash = crypto.Keccak256Hash(f.KeySym) - } else { - f.KeyAsym, err = crypto.GenerateKey() - if err != nil { - t.Fatalf("generateFilter 2 failed with seed %d.", seed) - return nil, err - } - } - - // AcceptP2P & PoW are not set - return &f, nil -} - -func generateTestCases(t *testing.T, SizeTestFilters int) []FilterTestCase { - cases := make([]FilterTestCase, SizeTestFilters) - for i := 0; i < SizeTestFilters; i++ { - f, _ := generateFilter(t, true) - cases[i].f = f - cases[i].alive = mrand.Int()&int(1) == 0 - } - return cases -} - -func TestInstallFilters(t *testing.T) { - InitSingleTest() - - const SizeTestFilters = 256 - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - filters := NewFilters(w) - tst := generateTestCases(t, SizeTestFilters) - - var err error - var j string - for i := 0; i < SizeTestFilters; i++ { - j, err = filters.Install(tst[i].f) - if err != nil { - t.Fatalf("seed %d: failed to install filter: %s", seed, err) - } - tst[i].id = j - if len(j) != keyIDSize*2 { - t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j)) - } - } - - for _, testCase := range tst { - if !testCase.alive { - filters.Uninstall(testCase.id) - } - } - - for i, testCase := range tst { - fil := filters.Get(testCase.id) - exist := fil != nil - if exist != testCase.alive { - t.Fatalf("seed %d: failed alive: %d, %v, %v", seed, i, exist, testCase.alive) - } - if exist && fil.PoW != testCase.f.PoW { - t.Fatalf("seed %d: failed Get: %d, %v, %v", seed, i, exist, testCase.alive) - } - } -} - -func TestInstallSymKeyGeneratesHash(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - filters := NewFilters(w) - filter, _ := generateFilter(t, true) - - // save the current SymKeyHash for comparison - initialSymKeyHash := filter.SymKeyHash - - // ensure the SymKeyHash is invalid, for Install to recreate it - var invalid common.Hash - filter.SymKeyHash = invalid - - _, err := filters.Install(filter) - - if err != nil { - t.Fatalf("Error installing the filter: %s", err) - } - - for i, b := range filter.SymKeyHash { - if b != initialSymKeyHash[i] { - t.Fatalf("The filter's symmetric key hash was not properly generated by Install") - } - } -} - -func TestInstallIdenticalFilters(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - filters := NewFilters(w) - filter1, _ := generateFilter(t, true) - - // Copy the first filter since some of its fields - // are randomly gnerated. - filter2 := &Filter{ - KeySym: filter1.KeySym, - Topics: filter1.Topics, - PoW: filter1.PoW, - AllowP2P: filter1.AllowP2P, - Messages: make(map[common.Hash]*ReceivedMessage), - } - - _, err := filters.Install(filter1) - - if err != nil { - t.Fatalf("Error installing the first filter with seed %d: %s", seed, err) - } - - _, err = filters.Install(filter2) - - if err != nil { - t.Fatalf("Error installing the second filter with seed %d: %s", seed, err) - } - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("Error generating message parameters with seed %d: %s", seed, err) - } - - params.KeySym = filter1.KeySym - params.Topic = BytesToTopic(filter1.Topics[0]) - - filter1.Src = ¶ms.Src.PublicKey - filter2.Src = ¶ms.Src.PublicKey - - sentMessage, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := sentMessage.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - msg := env.Open(filter1) - if msg == nil { - t.Fatalf("failed to Open with filter1") - } - - if !filter1.MatchEnvelope(env) { - t.Fatalf("failed matching with the first filter") - } - - if !filter2.MatchEnvelope(env) { - t.Fatalf("failed matching with the first filter") - } - - if !filter1.MatchMessage(msg) { - t.Fatalf("failed matching with the second filter") - } - - if !filter2.MatchMessage(msg) { - t.Fatalf("failed matching with the second filter") - } -} - -func TestInstallFilterWithSymAndAsymKeys(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - filters := NewFilters(w) - filter1, _ := generateFilter(t, true) - - asymKey, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("Unable to create asymetric keys: %v", err) - } - - // Copy the first filter since some of its fields - // are randomly gnerated. - filter := &Filter{ - KeySym: filter1.KeySym, - KeyAsym: asymKey, - Topics: filter1.Topics, - PoW: filter1.PoW, - AllowP2P: filter1.AllowP2P, - Messages: make(map[common.Hash]*ReceivedMessage), - } - - _, err = filters.Install(filter) - - if err == nil { - t.Fatalf("Error detecting that a filter had both an asymmetric and symmetric key, with seed %d", seed) - } -} - -func TestComparePubKey(t *testing.T) { - InitSingleTest() - - key1, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed to generate first key with seed %d: %s.", seed, err) - } - key2, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed to generate second key with seed %d: %s.", seed, err) - } - if IsPubKeyEqual(&key1.PublicKey, &key2.PublicKey) { - t.Fatalf("public keys are equal, seed %d.", seed) - } - - // generate key3 == key1 - mrand.Seed(seed) - key3, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed to generate third key with seed %d: %s.", seed, err) - } - if IsPubKeyEqual(&key1.PublicKey, &key3.PublicKey) { - t.Fatalf("key1 == key3, seed %d.", seed) - } -} - -func TestMatchEnvelope(t *testing.T) { - InitSingleTest() - - fsym, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) - } - - fasym, err := generateFilter(t, false) - if err != nil { - t.Fatalf("failed generateFilter() with seed %d: %s.", seed, err) - } - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - params.Topic[0] = 0xFF // topic mismatch - - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - if _, err = msg.Wrap(params); err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - // encrypt symmetrically - i := mrand.Int() % 4 - fsym.Topics[i] = params.Topic[:] - fasym.Topics[i] = params.Topic[:] - msg, err = NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap() with seed %d: %s.", seed, err) - } - - // symmetric + matching topic: match - match := fsym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed) - } - - // symmetric + matching topic + insufficient PoW: mismatch - fsym.PoW = env.PoW() + 1.0 - match = fsym.MatchEnvelope(env) - if match { - t.Fatalf("failed MatchEnvelope(symmetric + matching topic + insufficient PoW) asymmetric with seed %d.", seed) - } - - // symmetric + matching topic + sufficient PoW: match - fsym.PoW = env.PoW() / 2 - match = fsym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(symmetric + matching topic + sufficient PoW) with seed %d.", seed) - } - - // symmetric + topics are nil (wildcard): match - prevTopics := fsym.Topics - fsym.Topics = nil - match = fsym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(symmetric + topics are nil) with seed %d.", seed) - } - fsym.Topics = prevTopics - - // encrypt asymmetrically - key, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - params.KeySym = nil - params.Dst = &key.PublicKey - msg, err = NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err = msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap() with seed %d: %s.", seed, err) - } - - // encryption method mismatch - match = fsym.MatchEnvelope(env) - if match { - t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed) - } - - // asymmetric + mismatching topic: mismatch - match = fasym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(asymmetric + mismatching topic) with seed %d.", seed) - } - - // asymmetric + matching topic: match - fasym.Topics[i] = fasym.Topics[i+1] - match = fasym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(asymmetric + matching topic) with seed %d.", seed) - } - - // asymmetric + filter without topic (wildcard): match - fasym.Topics = nil - match = fasym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(asymmetric + filter without topic) with seed %d.", seed) - } - - // asymmetric + insufficient PoW: mismatch - fasym.PoW = env.PoW() + 1.0 - match = fasym.MatchEnvelope(env) - if match { - t.Fatalf("failed MatchEnvelope(asymmetric + insufficient PoW) with seed %d.", seed) - } - - // asymmetric + sufficient PoW: match - fasym.PoW = env.PoW() / 2 - match = fasym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(asymmetric + sufficient PoW) with seed %d.", seed) - } - - // filter without topic + envelope without topic: match - env.Topic = TopicType{} - match = fasym.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed) - } - - // filter with topic + envelope without topic: mismatch - fasym.Topics = fsym.Topics - match = fasym.MatchEnvelope(env) - if !match { - // topic mismatch should have no affect, as topics are handled by topic matchers - t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed) - } -} - -func TestMatchMessageSym(t *testing.T) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - f, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) - } - - const index = 1 - params.KeySym = f.KeySym - params.Topic = BytesToTopic(f.Topics[index]) - - sentMessage, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := sentMessage.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - msg := env.Open(f) - if msg == nil { - t.Fatalf("failed Open with seed %d.", seed) - } - - // Src: match - *f.Src.X = *params.Src.PublicKey.X - *f.Src.Y = *params.Src.PublicKey.Y - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(src match) with seed %d.", seed) - } - - // insufficient PoW: mismatch - f.PoW = msg.PoW + 1.0 - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed) - } - - // sufficient PoW: match - f.PoW = msg.PoW / 2 - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed) - } - - // topic mismatch - f.Topics[index][0]++ - if !f.MatchMessage(msg) { - // topic mismatch should have no affect, as topics are handled by topic matchers - t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed) - } - f.Topics[index][0]-- - - // key mismatch - f.SymKeyHash[0]++ - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed) - } - f.SymKeyHash[0]-- - - // Src absent: match - f.Src = nil - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed) - } - - // key hash mismatch - h := f.SymKeyHash - f.SymKeyHash = common.Hash{} - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(key hash mismatch) with seed %d.", seed) - } - f.SymKeyHash = h - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(key hash match) with seed %d.", seed) - } - - // encryption method mismatch - f.KeySym = nil - f.KeyAsym, err = crypto.GenerateKey() - if err != nil { - t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed) - } -} - -func TestMatchMessageAsym(t *testing.T) { - InitSingleTest() - - f, err := generateFilter(t, false) - if err != nil { - t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) - } - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - const index = 1 - params.Topic = BytesToTopic(f.Topics[index]) - params.Dst = &f.KeyAsym.PublicKey - keySymOrig := params.KeySym - params.KeySym = nil - - sentMessage, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := sentMessage.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - msg := env.Open(f) - if msg == nil { - t.Fatalf("failed to open with seed %d.", seed) - } - - // Src: match - *f.Src.X = *params.Src.PublicKey.X - *f.Src.Y = *params.Src.PublicKey.Y - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchMessage(src match) with seed %d.", seed) - } - - // insufficient PoW: mismatch - f.PoW = msg.PoW + 1.0 - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed) - } - - // sufficient PoW: match - f.PoW = msg.PoW / 2 - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed) - } - - // topic mismatch - f.Topics[index][0]++ - if !f.MatchMessage(msg) { - // topic mismatch should have no affect, as topics are handled by topic matchers - t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed) - } - f.Topics[index][0]-- - - // key mismatch - prev := *f.KeyAsym.PublicKey.X - zero := *big.NewInt(0) - *f.KeyAsym.PublicKey.X = zero - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed) - } - *f.KeyAsym.PublicKey.X = prev - - // Src absent: match - f.Src = nil - if !f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed) - } - - // encryption method mismatch - f.KeySym = keySymOrig - f.KeyAsym = nil - if f.MatchMessage(msg) { - t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed) - } -} - -func cloneFilter(orig *Filter) *Filter { - var clone Filter - clone.Messages = make(map[common.Hash]*ReceivedMessage) - clone.Src = orig.Src - clone.KeyAsym = orig.KeyAsym - clone.KeySym = orig.KeySym - clone.Topics = orig.Topics - clone.PoW = orig.PoW - clone.AllowP2P = orig.AllowP2P - clone.SymKeyHash = orig.SymKeyHash - return &clone -} - -func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope { - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - return nil - } - - params.KeySym = f.KeySym - params.Topic = BytesToTopic(f.Topics[2]) - sentMessage, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := sentMessage.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - return nil - } - return env -} - -func TestWatchers(t *testing.T) { - InitSingleTest() - - const NumFilters = 16 - const NumMessages = 256 - var i int - var j uint32 - var e *Envelope - var x, firstID string - var err error - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - filters := NewFilters(w) - tst := generateTestCases(t, NumFilters) - for i = 0; i < NumFilters; i++ { - tst[i].f.Src = nil - x, err = filters.Install(tst[i].f) - if err != nil { - t.Fatalf("failed to install filter with seed %d: %s.", seed, err) - } - tst[i].id = x - if len(firstID) == 0 { - firstID = x - } - } - - lastID := x - - var envelopes [NumMessages]*Envelope - for i = 0; i < NumMessages; i++ { - j = mrand.Uint32() % NumFilters - e = generateCompatibeEnvelope(t, tst[j].f) - envelopes[i] = e - tst[j].msgCnt++ - } - - for i = 0; i < NumMessages; i++ { - filters.NotifyWatchers(envelopes[i], false) - } - - var total int - var mail []*ReceivedMessage - var count [NumFilters]int - - for i = 0; i < NumFilters; i++ { - mail = tst[i].f.Retrieve() - count[i] = len(mail) - total += len(mail) - } - - if total != NumMessages { - t.Fatalf("failed with seed %d: total = %d, want: %d.", seed, total, NumMessages) - } - - for i = 0; i < NumFilters; i++ { - mail = tst[i].f.Retrieve() - if len(mail) != 0 { - t.Fatalf("failed with seed %d: i = %d.", seed, i) - } - - if tst[i].msgCnt != count[i] { - t.Fatalf("failed with seed %d: count[%d]: get %d, want %d.", seed, i, tst[i].msgCnt, count[i]) - } - } - - // another round with a cloned filter - - clone := cloneFilter(tst[0].f) - filters.Uninstall(lastID) - total = 0 - last := NumFilters - 1 - tst[last].f = clone - filters.Install(clone) - for i = 0; i < NumFilters; i++ { - tst[i].msgCnt = 0 - count[i] = 0 - } - - // make sure that the first watcher receives at least one message - e = generateCompatibeEnvelope(t, tst[0].f) - envelopes[0] = e - tst[0].msgCnt++ - for i = 1; i < NumMessages; i++ { - j = mrand.Uint32() % NumFilters - e = generateCompatibeEnvelope(t, tst[j].f) - envelopes[i] = e - tst[j].msgCnt++ - } - - for i = 0; i < NumMessages; i++ { - filters.NotifyWatchers(envelopes[i], false) - } - - for i = 0; i < NumFilters; i++ { - mail = tst[i].f.Retrieve() - count[i] = len(mail) - total += len(mail) - } - - combined := tst[0].msgCnt + tst[last].msgCnt - if total != NumMessages+count[0] { - t.Fatalf("failed with seed %d: total = %d, count[0] = %d.", seed, total, count[0]) - } - - if combined != count[0] { - t.Fatalf("failed with seed %d: combined = %d, count[0] = %d.", seed, combined, count[0]) - } - - if combined != count[last] { - t.Fatalf("failed with seed %d: combined = %d, count[last] = %d.", seed, combined, count[last]) - } - - for i = 1; i < NumFilters-1; i++ { - mail = tst[i].f.Retrieve() - if len(mail) != 0 { - t.Fatalf("failed with seed %d: i = %d.", seed, i) - } - - if tst[i].msgCnt != count[i] { - t.Fatalf("failed with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i]) - } - } - - // test AcceptP2P - - total = 0 - filters.NotifyWatchers(envelopes[0], true) - - for i = 0; i < NumFilters; i++ { - mail = tst[i].f.Retrieve() - total += len(mail) - } - - if total != 0 { - t.Fatalf("failed with seed %d: total: got %d, want 0.", seed, total) - } - - f := filters.Get(firstID) - if f == nil { - t.Fatalf("failed to get the filter with seed %d.", seed) - } - f.AllowP2P = true - total = 0 - filters.NotifyWatchers(envelopes[0], true) - - for i = 0; i < NumFilters; i++ { - mail = tst[i].f.Retrieve() - total += len(mail) - } - - if total != 1 { - t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total) - } -} - -func TestVariableTopics(t *testing.T) { - InitSingleTest() - - const lastTopicByte = 3 - var match bool - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - f, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateFilter with seed %d: %s.", seed, err) - } - - for i := 0; i < 4; i++ { - env.Topic = BytesToTopic(f.Topics[i]) - match = f.MatchEnvelope(env) - if !match { - t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i) - } - - f.Topics[i][lastTopicByte]++ - match = f.MatchEnvelope(env) - if !match { - // topic mismatch should have no affect, as topics are handled by topic matchers - t.Fatalf("MatchEnvelope symmetric with seed %d, step %d.", seed, i) - } - } -} diff --git a/whisper/whisperv6/gen_criteria_json.go b/whisper/whisperv6/gen_criteria_json.go deleted file mode 100644 index 1a428d6df7..0000000000 --- a/whisper/whisperv6/gen_criteria_json.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package whisperv6 - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*criteriaOverride)(nil) - -// MarshalJSON marshals type Criteria to a json string -func (c Criteria) MarshalJSON() ([]byte, error) { - type Criteria struct { - SymKeyID string `json:"symKeyID"` - PrivateKeyID string `json:"privateKeyID"` - Sig hexutil.Bytes `json:"sig"` - MinPow float64 `json:"minPow"` - Topics []TopicType `json:"topics"` - AllowP2P bool `json:"allowP2P"` - } - var enc Criteria - enc.SymKeyID = c.SymKeyID - enc.PrivateKeyID = c.PrivateKeyID - enc.Sig = c.Sig - enc.MinPow = c.MinPow - enc.Topics = c.Topics - enc.AllowP2P = c.AllowP2P - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals type Criteria to a json string -func (c *Criteria) UnmarshalJSON(input []byte) error { - type Criteria struct { - SymKeyID *string `json:"symKeyID"` - PrivateKeyID *string `json:"privateKeyID"` - Sig *hexutil.Bytes `json:"sig"` - MinPow *float64 `json:"minPow"` - Topics []TopicType `json:"topics"` - AllowP2P *bool `json:"allowP2P"` - } - var dec Criteria - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.SymKeyID != nil { - c.SymKeyID = *dec.SymKeyID - } - if dec.PrivateKeyID != nil { - c.PrivateKeyID = *dec.PrivateKeyID - } - if dec.Sig != nil { - c.Sig = *dec.Sig - } - if dec.MinPow != nil { - c.MinPow = *dec.MinPow - } - if dec.Topics != nil { - c.Topics = dec.Topics - } - if dec.AllowP2P != nil { - c.AllowP2P = *dec.AllowP2P - } - return nil -} diff --git a/whisper/whisperv6/gen_message_json.go b/whisper/whisperv6/gen_message_json.go deleted file mode 100644 index 6218f5df6e..0000000000 --- a/whisper/whisperv6/gen_message_json.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package whisperv6 - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*messageOverride)(nil) - -// MarshalJSON marshals type Message to a json string -func (m Message) MarshalJSON() ([]byte, error) { - type Message struct { - Sig hexutil.Bytes `json:"sig,omitempty"` - TTL uint32 `json:"ttl"` - Timestamp uint32 `json:"timestamp"` - Topic TopicType `json:"topic"` - Payload hexutil.Bytes `json:"payload"` - Padding hexutil.Bytes `json:"padding"` - PoW float64 `json:"pow"` - Hash hexutil.Bytes `json:"hash"` - Dst hexutil.Bytes `json:"recipientPublicKey,omitempty"` - } - var enc Message - enc.Sig = m.Sig - enc.TTL = m.TTL - enc.Timestamp = m.Timestamp - enc.Topic = m.Topic - enc.Payload = m.Payload - enc.Padding = m.Padding - enc.PoW = m.PoW - enc.Hash = m.Hash - enc.Dst = m.Dst - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals type Message to a json string -func (m *Message) UnmarshalJSON(input []byte) error { - type Message struct { - Sig *hexutil.Bytes `json:"sig,omitempty"` - TTL *uint32 `json:"ttl"` - Timestamp *uint32 `json:"timestamp"` - Topic *TopicType `json:"topic"` - Payload *hexutil.Bytes `json:"payload"` - Padding *hexutil.Bytes `json:"padding"` - PoW *float64 `json:"pow"` - Hash *hexutil.Bytes `json:"hash"` - Dst *hexutil.Bytes `json:"recipientPublicKey,omitempty"` - } - var dec Message - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.Sig != nil { - m.Sig = *dec.Sig - } - if dec.TTL != nil { - m.TTL = *dec.TTL - } - if dec.Timestamp != nil { - m.Timestamp = *dec.Timestamp - } - if dec.Topic != nil { - m.Topic = *dec.Topic - } - if dec.Payload != nil { - m.Payload = *dec.Payload - } - if dec.Padding != nil { - m.Padding = *dec.Padding - } - if dec.PoW != nil { - m.PoW = *dec.PoW - } - if dec.Hash != nil { - m.Hash = *dec.Hash - } - if dec.Dst != nil { - m.Dst = *dec.Dst - } - return nil -} diff --git a/whisper/whisperv6/gen_newmessage_json.go b/whisper/whisperv6/gen_newmessage_json.go deleted file mode 100644 index 75a1279ae3..0000000000 --- a/whisper/whisperv6/gen_newmessage_json.go +++ /dev/null @@ -1,90 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package whisperv6 - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*newMessageOverride)(nil) - -// MarshalJSON marshals type NewMessage to a json string -func (n NewMessage) MarshalJSON() ([]byte, error) { - type NewMessage struct { - SymKeyID string `json:"symKeyID"` - PublicKey hexutil.Bytes `json:"pubKey"` - Sig string `json:"sig"` - TTL uint32 `json:"ttl"` - Topic TopicType `json:"topic"` - Payload hexutil.Bytes `json:"payload"` - Padding hexutil.Bytes `json:"padding"` - PowTime uint32 `json:"powTime"` - PowTarget float64 `json:"powTarget"` - TargetPeer string `json:"targetPeer"` - } - var enc NewMessage - enc.SymKeyID = n.SymKeyID - enc.PublicKey = n.PublicKey - enc.Sig = n.Sig - enc.TTL = n.TTL - enc.Topic = n.Topic - enc.Payload = n.Payload - enc.Padding = n.Padding - enc.PowTime = n.PowTime - enc.PowTarget = n.PowTarget - enc.TargetPeer = n.TargetPeer - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals type NewMessage to a json string -func (n *NewMessage) UnmarshalJSON(input []byte) error { - type NewMessage struct { - SymKeyID *string `json:"symKeyID"` - PublicKey *hexutil.Bytes `json:"pubKey"` - Sig *string `json:"sig"` - TTL *uint32 `json:"ttl"` - Topic *TopicType `json:"topic"` - Payload *hexutil.Bytes `json:"payload"` - Padding *hexutil.Bytes `json:"padding"` - PowTime *uint32 `json:"powTime"` - PowTarget *float64 `json:"powTarget"` - TargetPeer *string `json:"targetPeer"` - } - var dec NewMessage - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.SymKeyID != nil { - n.SymKeyID = *dec.SymKeyID - } - if dec.PublicKey != nil { - n.PublicKey = *dec.PublicKey - } - if dec.Sig != nil { - n.Sig = *dec.Sig - } - if dec.TTL != nil { - n.TTL = *dec.TTL - } - if dec.Topic != nil { - n.Topic = *dec.Topic - } - if dec.Payload != nil { - n.Payload = *dec.Payload - } - if dec.Padding != nil { - n.Padding = *dec.Padding - } - if dec.PowTime != nil { - n.PowTime = *dec.PowTime - } - if dec.PowTarget != nil { - n.PowTarget = *dec.PowTarget - } - if dec.TargetPeer != nil { - n.TargetPeer = *dec.TargetPeer - } - return nil -} diff --git a/whisper/whisperv6/message.go b/whisper/whisperv6/message.go deleted file mode 100644 index 2d4e862441..0000000000 --- a/whisper/whisperv6/message.go +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the Whisper protocol Message element. - -package whisperv6 - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/ecdsa" - crand "crypto/rand" - "encoding/binary" - "errors" - mrand "math/rand" - "strconv" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/log" -) - -// MessageParams specifies the exact way a message should be wrapped -// into an Envelope. -type MessageParams struct { - TTL uint32 - Src *ecdsa.PrivateKey - Dst *ecdsa.PublicKey - KeySym []byte - Topic TopicType - WorkTime uint32 - PoW float64 - Payload []byte - Padding []byte -} - -// SentMessage represents an end-user data packet to transmit through the -// Whisper protocol. These are wrapped into Envelopes that need not be -// understood by intermediate nodes, just forwarded. -type sentMessage struct { - Raw []byte -} - -// ReceivedMessage represents a data packet to be received through the -// Whisper protocol and successfully decrypted. -type ReceivedMessage struct { - Raw []byte - - Payload []byte - Padding []byte - Signature []byte - Salt []byte - - PoW float64 // Proof of work as described in the Whisper spec - Sent uint32 // Time when the message was posted into the network - TTL uint32 // Maximum time to live allowed for the message - Src *ecdsa.PublicKey // Message recipient (identity used to decode the message) - Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message) - Topic TopicType - - SymKeyHash common.Hash // The Keccak256Hash of the key - EnvelopeHash common.Hash // Message envelope hash to act as a unique id -} - -func isMessageSigned(flags byte) bool { - return (flags & signatureFlag) != 0 -} - -func (msg *ReceivedMessage) isSymmetricEncryption() bool { - return msg.SymKeyHash != common.Hash{} -} - -func (msg *ReceivedMessage) isAsymmetricEncryption() bool { - return msg.Dst != nil -} - -// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message. -func NewSentMessage(params *MessageParams) (*sentMessage, error) { - const payloadSizeFieldMaxSize = 4 - msg := sentMessage{} - msg.Raw = make([]byte, 1, - flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit) - msg.Raw[0] = 0 // set all the flags to zero - msg.addPayloadSizeField(params.Payload) - msg.Raw = append(msg.Raw, params.Payload...) - err := msg.appendPadding(params) - return &msg, err -} - -// addPayloadSizeField appends the auxiliary field containing the size of payload -func (msg *sentMessage) addPayloadSizeField(payload []byte) { - fieldSize := getSizeOfPayloadSizeField(payload) - field := make([]byte, 4) - binary.LittleEndian.PutUint32(field, uint32(len(payload))) - field = field[:fieldSize] - msg.Raw = append(msg.Raw, field...) - msg.Raw[0] |= byte(fieldSize) -} - -// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload -func getSizeOfPayloadSizeField(payload []byte) int { - s := 1 - for i := len(payload); i >= 256; i /= 256 { - s++ - } - return s -} - -// appendPadding appends the padding specified in params. -// If no padding is provided in params, then random padding is generated. -func (msg *sentMessage) appendPadding(params *MessageParams) error { - if len(params.Padding) != 0 { - // padding data was provided by the Dapp, just use it as is - msg.Raw = append(msg.Raw, params.Padding...) - return nil - } - - rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload) - if params.Src != nil { - rawSize += signatureLength - } - odd := rawSize % padSizeLimit - paddingSize := padSizeLimit - odd - pad := make([]byte, paddingSize) - _, err := crand.Read(pad) - if err != nil { - return err - } - if !validateDataIntegrity(pad, paddingSize) { - return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize)) - } - msg.Raw = append(msg.Raw, pad...) - return nil -} - -// sign calculates and sets the cryptographic signature for the message, -// also setting the sign flag. -func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error { - if isMessageSigned(msg.Raw[0]) { - // this should not happen, but no reason to panic - log.Error("failed to sign the message: already signed") - return nil - } - - msg.Raw[0] |= signatureFlag // it is important to set this flag before signing - hash := crypto.Keccak256(msg.Raw) - signature, err := crypto.Sign(hash, key) - if err != nil { - msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag - return err - } - msg.Raw = append(msg.Raw, signature...) - return nil -} - -// encryptAsymmetric encrypts a message with a public key. -func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error { - if !ValidatePublicKey(key) { - return errors.New("invalid public key provided for asymmetric encryption") - } - encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil) - if err == nil { - msg.Raw = encrypted - } - return err -} - -// encryptSymmetric encrypts a message with a topic key, using AES-GCM-256. -// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). -func (msg *sentMessage) encryptSymmetric(key []byte) (err error) { - if !validateDataIntegrity(key, aesKeyLength) { - return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key))) - } - block, err := aes.NewCipher(key) - if err != nil { - return err - } - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return err - } - salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key - if err != nil { - return err - } - encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil) - msg.Raw = append(encrypted, salt...) - return nil -} - -// generateSecureRandomData generates random data where extra security is required. -// The purpose of this function is to prevent some bugs in software or in hardware -// from delivering not-very-random data. This is especially useful for AES nonce, -// where true randomness does not really matter, but it is very important to have -// a unique nonce for every message. -func generateSecureRandomData(length int) ([]byte, error) { - x := make([]byte, length) - y := make([]byte, length) - res := make([]byte, length) - - _, err := crand.Read(x) - if err != nil { - return nil, err - } else if !validateDataIntegrity(x, length) { - return nil, errors.New("crypto/rand failed to generate secure random data") - } - _, err = mrand.Read(y) - if err != nil { - return nil, err - } else if !validateDataIntegrity(y, length) { - return nil, errors.New("math/rand failed to generate secure random data") - } - for i := 0; i < length; i++ { - res[i] = x[i] ^ y[i] - } - if !validateDataIntegrity(res, length) { - return nil, errors.New("failed to generate secure random data") - } - return res, nil -} - -// Wrap bundles the message into an Envelope to transmit over the network. -func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) { - if options.TTL == 0 { - options.TTL = DefaultTTL - } - if options.Src != nil { - if err = msg.sign(options.Src); err != nil { - return nil, err - } - } - if options.Dst != nil { - err = msg.encryptAsymmetric(options.Dst) - } else if options.KeySym != nil { - err = msg.encryptSymmetric(options.KeySym) - } else { - err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") - } - if err != nil { - return nil, err - } - - envelope = NewEnvelope(options.TTL, options.Topic, msg) - if err = envelope.Seal(options); err != nil { - return nil, err - } - return envelope, nil -} - -// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256. -// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). -func (msg *ReceivedMessage) decryptSymmetric(key []byte) error { - // symmetric messages are expected to contain the 12-byte nonce at the end of the payload - if len(msg.Raw) < aesNonceLength { - return errors.New("missing salt or invalid payload in symmetric message") - } - salt := msg.Raw[len(msg.Raw)-aesNonceLength:] - - block, err := aes.NewCipher(key) - if err != nil { - return err - } - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return err - } - decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil) - if err != nil { - return err - } - msg.Raw = decrypted - msg.Salt = salt - return nil -} - -// decryptAsymmetric decrypts an encrypted payload with a private key. -func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error { - decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil) - if err == nil { - msg.Raw = decrypted - } - return err -} - -// ValidateAndParse checks the message validity and extracts the fields in case of success. -func (msg *ReceivedMessage) ValidateAndParse() bool { - end := len(msg.Raw) - if end < 1 { - return false - } - - if isMessageSigned(msg.Raw[0]) { - end -= signatureLength - if end <= 1 { - return false - } - msg.Signature = msg.Raw[end : end+signatureLength] - msg.Src = msg.SigToPubKey() - if msg.Src == nil { - return false - } - } - - beg := 1 - payloadSize := 0 - sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload - if sizeOfPayloadSizeField != 0 { - payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField])) - if payloadSize+1 > end { - return false - } - beg += sizeOfPayloadSizeField - msg.Payload = msg.Raw[beg : beg+payloadSize] - } - - beg += payloadSize - msg.Padding = msg.Raw[beg:end] - return true -} - -// SigToPubKey returns the public key associated to the message's -// signature. -func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { - defer func() { recover() }() // in case of invalid signature - - pub, err := crypto.SigToPub(msg.hash(), msg.Signature) - if err != nil { - log.Error("failed to recover public key from signature", "err", err) - return nil - } - return pub -} - -// hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding. -func (msg *ReceivedMessage) hash() []byte { - if isMessageSigned(msg.Raw[0]) { - sz := len(msg.Raw) - signatureLength - return crypto.Keccak256(msg.Raw[:sz]) - } - return crypto.Keccak256(msg.Raw) -} diff --git a/whisper/whisperv6/message_test.go b/whisper/whisperv6/message_test.go deleted file mode 100644 index ece6d732cc..0000000000 --- a/whisper/whisperv6/message_test.go +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - mrand "math/rand" - "testing" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" -) - -func generateMessageParams() (*MessageParams, error) { - // set all the parameters except p.Dst and p.Padding - - buf := make([]byte, 4) - mrand.Read(buf) - sz := mrand.Intn(400) - - var p MessageParams - p.PoW = 0.001 - p.WorkTime = 1 - p.TTL = uint32(mrand.Intn(1024)) - p.Payload = make([]byte, sz) - p.KeySym = make([]byte, aesKeyLength) - mrand.Read(p.Payload) - mrand.Read(p.KeySym) - p.Topic = BytesToTopic(buf) - - var err error - p.Src, err = crypto.GenerateKey() - if err != nil { - return nil, err - } - - return &p, nil -} - -func singleMessageTest(t *testing.T, symmetric bool) { - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - key, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - - if !symmetric { - params.KeySym = nil - params.Dst = &key.PublicKey - } - - text := make([]byte, 0, 512) - text = append(text, params.Payload...) - - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - var decrypted *ReceivedMessage - if symmetric { - decrypted, err = env.OpenSymmetric(params.KeySym) - } else { - decrypted, err = env.OpenAsymmetric(key) - } - - if err != nil { - t.Fatalf("failed to encrypt with seed %d: %s.", seed, err) - } - - if !decrypted.ValidateAndParse() { - t.Fatalf("failed to validate with seed %d, symmetric = %v.", seed, symmetric) - } - - if !bytes.Equal(text, decrypted.Payload) { - t.Fatalf("failed with seed %d: compare payload.", seed) - } - if !isMessageSigned(decrypted.Raw[0]) { - t.Fatalf("failed with seed %d: unsigned.", seed) - } - if len(decrypted.Signature) != signatureLength { - t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) - } - if !IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { - t.Fatalf("failed with seed %d: signature mismatch.", seed) - } -} - -func TestMessageEncryption(t *testing.T) { - InitSingleTest() - - var symmetric bool - for i := 0; i < 256; i++ { - singleMessageTest(t, symmetric) - symmetric = !symmetric - } -} - -func TestMessageWrap(t *testing.T) { - seed = int64(1777444222) - mrand.Seed(seed) - target := 128.0 - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - params.TTL = 1 - params.WorkTime = 12 - params.PoW = target - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - pow := env.PoW() - if pow < target { - t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target) - } - - // set PoW target too high, expect error - msg2, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - params.TTL = 1000000 - params.WorkTime = 1 - params.PoW = 10000000.0 - _, err = msg2.Wrap(params) - if err == nil { - t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed) - } -} - -func TestMessageSeal(t *testing.T) { - // this test depends on deterministic choice of seed (1976726903) - seed = int64(1976726903) - mrand.Seed(seed) - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - params.TTL = 1 - - env := NewEnvelope(params.TTL, params.Topic, msg) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - env.Expiry = uint32(seed) // make it deterministic - target := 32.0 - params.WorkTime = 4 - params.PoW = target - env.Seal(params) - - env.calculatePoW(0) - pow := env.PoW() - if pow < target { - t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target) - } - - params.WorkTime = 1 - params.PoW = 1000000000.0 - env.Seal(params) - env.calculatePoW(0) - pow = env.PoW() - if pow < 2*target { - t.Fatalf("failed Wrap with seed %d: pow too small %f.", seed, pow) - } -} - -func TestEnvelopeOpen(t *testing.T) { - InitSingleTest() - - var symmetric bool - for i := 0; i < 32; i++ { - singleEnvelopeOpenTest(t, symmetric) - symmetric = !symmetric - } -} - -func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - key, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err) - } - - if !symmetric { - params.KeySym = nil - params.Dst = &key.PublicKey - } - - text := make([]byte, 0, 512) - text = append(text, params.Payload...) - - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - var f Filter - if symmetric { - f = Filter{KeySym: params.KeySym} - } else { - f = Filter{KeyAsym: key} - } - decrypted := env.Open(&f) - if decrypted == nil { - t.Fatalf("failed to open with seed %d.", seed) - } - - if !bytes.Equal(text, decrypted.Payload) { - t.Fatalf("failed with seed %d: compare payload.", seed) - } - if !isMessageSigned(decrypted.Raw[0]) { - t.Fatalf("failed with seed %d: unsigned.", seed) - } - if len(decrypted.Signature) != signatureLength { - t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature)) - } - if !IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) { - t.Fatalf("failed with seed %d: signature mismatch.", seed) - } - if decrypted.isAsymmetricEncryption() == symmetric { - t.Fatalf("failed with seed %d: asymmetric %v vs. %v.", seed, decrypted.isAsymmetricEncryption(), symmetric) - } - if decrypted.isSymmetricEncryption() != symmetric { - t.Fatalf("failed with seed %d: symmetric %v vs. %v.", seed, decrypted.isSymmetricEncryption(), symmetric) - } - if !symmetric { - if decrypted.Dst == nil { - t.Fatalf("failed with seed %d: dst is nil.", seed) - } - if !IsPubKeyEqual(decrypted.Dst, &key.PublicKey) { - t.Fatalf("failed with seed %d: Dst.", seed) - } - } -} - -func TestEncryptWithZeroKey(t *testing.T) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - params.KeySym = make([]byte, aesKeyLength) - _, err = msg.Wrap(params) - if err == nil { - t.Fatalf("wrapped with zero key, seed: %d.", seed) - } - - params, err = generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, err = NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - params.KeySym = make([]byte, 0) - _, err = msg.Wrap(params) - if err == nil { - t.Fatalf("wrapped with empty key, seed: %d.", seed) - } - - params, err = generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, err = NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - params.KeySym = nil - _, err = msg.Wrap(params) - if err == nil { - t.Fatalf("wrapped with nil key, seed: %d.", seed) - } -} - -func TestRlpEncode(t *testing.T) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("wrapped with zero key, seed: %d.", seed) - } - - raw, err := rlp.EncodeToBytes(env) - if err != nil { - t.Fatalf("RLP encode failed: %s.", err) - } - - var decoded Envelope - rlp.DecodeBytes(raw, &decoded) - if err != nil { - t.Fatalf("RLP decode failed: %s.", err) - } - - he := env.Hash() - hd := decoded.Hash() - - if he != hd { - t.Fatalf("Hashes are not equal: %x vs. %x", he, hd) - } -} - -func singlePaddingTest(t *testing.T, padSize int) { - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err) - } - params.Padding = make([]byte, padSize) - params.PoW = 0.0000000001 - pad := make([]byte, padSize) - _, err = mrand.Read(pad) - if err != nil { - t.Fatalf("padding is not generated (seed %d): %s", seed, err) - } - n := copy(params.Padding, pad) - if n != padSize { - t.Fatalf("padding is not copied (seed %d): %s", seed, err) - } - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed to wrap, seed: %d and sz=%d.", seed, padSize) - } - f := Filter{KeySym: params.KeySym} - decrypted := env.Open(&f) - if decrypted == nil { - t.Fatalf("failed to open, seed and sz=%d: %d.", seed, padSize) - } - if !bytes.Equal(pad, decrypted.Padding) { - t.Fatalf("padding is not retireved as expected with seed %d and sz=%d:\n[%x]\n[%x].", seed, padSize, pad, decrypted.Padding) - } -} - -func TestPadding(t *testing.T) { - InitSingleTest() - - for i := 1; i < 260; i++ { - singlePaddingTest(t, i) - } - - lim := 256 * 256 - for i := lim - 5; i < lim+2; i++ { - singlePaddingTest(t, i) - } - - for i := 0; i < 256; i++ { - n := mrand.Intn(256*254) + 256 - singlePaddingTest(t, n) - } - - for i := 0; i < 256; i++ { - n := mrand.Intn(256*1024) + 256*256 - singlePaddingTest(t, n) - } -} - -func TestPaddingAppendedToSymMessagesWithSignature(t *testing.T) { - params := &MessageParams{ - Payload: make([]byte, 246), - KeySym: make([]byte, aesKeyLength), - } - - pSrc, err := crypto.GenerateKey() - - if err != nil { - t.Fatalf("Error creating the signature key %v", err) - return - } - params.Src = pSrc - - // Simulate a message with a payload just under 256 so that - // payload + flag + signature > 256. Check that the result - // is padded on the next 256 boundary. - msg := sentMessage{} - const payloadSizeFieldMinSize = 1 - msg.Raw = make([]byte, flagsLength+payloadSizeFieldMinSize+len(params.Payload)) - - err = msg.appendPadding(params) - - if err != nil { - t.Fatalf("Error appending padding to message %v", err) - return - } - - if len(msg.Raw) != 512-signatureLength { - t.Errorf("Invalid size %d != 512", len(msg.Raw)) - } -} - -func TestAesNonce(t *testing.T) { - key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31") - block, err := aes.NewCipher(key) - if err != nil { - t.Fatalf("NewCipher failed: %s", err) - } - aesgcm, err := cipher.NewGCM(block) - if err != nil { - t.Fatalf("NewGCM failed: %s", err) - } - // This is the most important single test in this package. - // If it fails, whisper will not be working. - if aesgcm.NonceSize() != aesNonceLength { - t.Fatalf("Nonce size is wrong. This is a critical error. Apparently AES nonce size have changed in the new version of AES GCM package. Whisper will not be working until this problem is resolved.") - } -} diff --git a/whisper/whisperv6/peer.go b/whisper/whisperv6/peer.go deleted file mode 100644 index 68fa7c8cba..0000000000 --- a/whisper/whisperv6/peer.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "fmt" - "math" - "sync" - "time" - - mapset "github.com/deckarep/golang-set" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" -) - -// Peer represents a whisper protocol peer connection. -type Peer struct { - host *Whisper - peer *p2p.Peer - ws p2p.MsgReadWriter - - trusted bool - powRequirement float64 - bloomMu sync.Mutex - bloomFilter []byte - fullNode bool - - known mapset.Set // Messages already known by the peer to avoid wasting bandwidth - - quit chan struct{} - - wg sync.WaitGroup -} - -// newPeer creates a new whisper peer object, but does not run the handshake itself. -func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer { - return &Peer{ - host: host, - peer: remote, - ws: rw, - trusted: false, - powRequirement: 0.0, - known: mapset.NewSet(), - quit: make(chan struct{}), - bloomFilter: MakeFullNodeBloom(), - fullNode: true, - } -} - -// start initiates the peer updater, periodically broadcasting the whisper packets -// into the network. -func (peer *Peer) start() { - peer.wg.Add(1) - go peer.update() - log.Trace("start", "peer", peer.ID()) -} - -// stop terminates the peer updater, stopping message forwarding to it. -func (peer *Peer) stop() { - close(peer.quit) - peer.wg.Wait() - log.Trace("stop", "peer", peer.ID()) -} - -// handshake sends the protocol initiation status message to the remote peer and -// verifies the remote status too. -func (peer *Peer) handshake() error { - // Send the handshake status message asynchronously - errc := make(chan error, 1) - isLightNode := peer.host.LightClientMode() - isRestrictedLightNodeConnection := peer.host.LightClientModeConnectionRestricted() - peer.wg.Add(1) - go func() { - defer peer.wg.Done() - pow := peer.host.MinPow() - powConverted := math.Float64bits(pow) - bloom := peer.host.BloomFilter() - - errc <- p2p.SendItems(peer.ws, statusCode, ProtocolVersion, powConverted, bloom, isLightNode) - }() - - // Fetch the remote status packet and verify protocol match - packet, err := peer.ws.ReadMsg() - if err != nil { - return err - } - if packet.Code != statusCode { - return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code) - } - s := rlp.NewStream(packet.Payload, uint64(packet.Size)) - _, err = s.List() - if err != nil { - return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err) - } - peerVersion, err := s.Uint() - if err != nil { - return fmt.Errorf("peer [%x] sent bad status message (unable to decode version): %v", peer.ID(), err) - } - if peerVersion != ProtocolVersion { - return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion) - } - - // only version is mandatory, subsequent parameters are optional - powRaw, err := s.Uint() - if err == nil { - pow := math.Float64frombits(powRaw) - if math.IsInf(pow, 0) || math.IsNaN(pow) || pow < 0.0 { - return fmt.Errorf("peer [%x] sent bad status message: invalid pow", peer.ID()) - } - peer.powRequirement = pow - - var bloom []byte - err = s.Decode(&bloom) - if err == nil { - sz := len(bloom) - if sz != BloomFilterSize && sz != 0 { - return fmt.Errorf("peer [%x] sent bad status message: wrong bloom filter size %d", peer.ID(), sz) - } - peer.setBloomFilter(bloom) - } - } - - isRemotePeerLightNode, _ := s.Bool() - if isRemotePeerLightNode && isLightNode && isRestrictedLightNodeConnection { - return fmt.Errorf("peer [%x] is useless: two light client communication restricted", peer.ID()) - } - - if err := <-errc; err != nil { - return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err) - } - return nil -} - -// update executes periodic operations on the peer, including message transmission -// and expiration. -func (peer *Peer) update() { - defer peer.wg.Done() - // Start the tickers for the updates - expire := time.NewTicker(expirationCycle) - defer expire.Stop() - transmit := time.NewTicker(transmissionCycle) - defer transmit.Stop() - - // Loop and transmit until termination is requested - for { - select { - case <-expire.C: - peer.expire() - - case <-transmit.C: - if err := peer.broadcast(); err != nil { - log.Trace("broadcast failed", "reason", err, "peer", peer.ID()) - return - } - - case <-peer.quit: - return - } - } -} - -// mark marks an envelope known to the peer so that it won't be sent back. -func (peer *Peer) mark(envelope *Envelope) { - peer.known.Add(envelope.Hash()) -} - -// marked checks if an envelope is already known to the remote peer. -func (peer *Peer) marked(envelope *Envelope) bool { - return peer.known.Contains(envelope.Hash()) -} - -// expire iterates over all the known envelopes in the host and removes all -// expired (unknown) ones from the known list. -func (peer *Peer) expire() { - unmark := make(map[common.Hash]struct{}) - peer.known.Each(func(v interface{}) bool { - if !peer.host.isEnvelopeCached(v.(common.Hash)) { - unmark[v.(common.Hash)] = struct{}{} - } - return true - }) - // Dump all known but no longer cached - for hash := range unmark { - peer.known.Remove(hash) - } -} - -// broadcast iterates over the collection of envelopes and transmits yet unknown -// ones over the network. -func (peer *Peer) broadcast() error { - envelopes := peer.host.Envelopes() - bundle := make([]*Envelope, 0, len(envelopes)) - for _, envelope := range envelopes { - if !peer.marked(envelope) && envelope.PoW() >= peer.powRequirement && peer.bloomMatch(envelope) { - bundle = append(bundle, envelope) - } - } - - if len(bundle) > 0 { - // transmit the batch of envelopes - if err := p2p.Send(peer.ws, messagesCode, bundle); err != nil { - return err - } - - // mark envelopes only if they were successfully sent - for _, e := range bundle { - peer.mark(e) - } - - log.Trace("broadcast", "num. messages", len(bundle)) - } - return nil -} - -// ID returns a peer's id -func (peer *Peer) ID() []byte { - id := peer.peer.ID() - return id[:] -} - -func (peer *Peer) notifyAboutPowRequirementChange(pow float64) error { - i := math.Float64bits(pow) - return p2p.Send(peer.ws, powRequirementCode, i) -} - -func (peer *Peer) notifyAboutBloomFilterChange(bloom []byte) error { - return p2p.Send(peer.ws, bloomFilterExCode, bloom) -} - -func (peer *Peer) bloomMatch(env *Envelope) bool { - peer.bloomMu.Lock() - defer peer.bloomMu.Unlock() - return peer.fullNode || BloomFilterMatch(peer.bloomFilter, env.Bloom()) -} - -func (peer *Peer) setBloomFilter(bloom []byte) { - peer.bloomMu.Lock() - defer peer.bloomMu.Unlock() - peer.bloomFilter = bloom - peer.fullNode = isFullNode(bloom) - if peer.fullNode && peer.bloomFilter == nil { - peer.bloomFilter = MakeFullNodeBloom() - } -} - -func MakeFullNodeBloom() []byte { - bloom := make([]byte, BloomFilterSize) - for i := 0; i < BloomFilterSize; i++ { - bloom[i] = 0xFF - } - return bloom -} diff --git a/whisper/whisperv6/topic.go b/whisper/whisperv6/topic.go deleted file mode 100644 index ee255f785d..0000000000 --- a/whisper/whisperv6/topic.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the Whisper protocol Topic element. - -package whisperv6 - -import ( - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// TopicType represents a cryptographically secure, probabilistic partial -// classifications of a message, determined as the first (left) 4 bytes of the -// SHA3 hash of some arbitrary data given by the original author of the message. -type TopicType [TopicLength]byte - -// BytesToTopic converts from the byte array representation of a topic -// into the TopicType type. -func BytesToTopic(b []byte) (t TopicType) { - sz := TopicLength - if x := len(b); x < TopicLength { - sz = x - } - for i := 0; i < sz; i++ { - t[i] = b[i] - } - return t -} - -// String converts a topic byte array to a string representation. -func (t *TopicType) String() string { - return hexutil.Encode(t[:]) -} - -// MarshalText returns the hex representation of t. -func (t TopicType) MarshalText() ([]byte, error) { - return hexutil.Bytes(t[:]).MarshalText() -} - -// UnmarshalText parses a hex representation to a topic. -func (t *TopicType) UnmarshalText(input []byte) error { - return hexutil.UnmarshalFixedText("Topic", input, t[:]) -} diff --git a/whisper/whisperv6/topic_test.go b/whisper/whisperv6/topic_test.go deleted file mode 100644 index 454afe0de1..0000000000 --- a/whisper/whisperv6/topic_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "encoding/json" - "testing" -) - -var topicStringTests = []struct { - topic TopicType - str string -}{ - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"}, - {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"}, - {topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"}, - {topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"}, -} - -func TestTopicString(t *testing.T) { - for i, tst := range topicStringTests { - s := tst.topic.String() - if s != tst.str { - t.Fatalf("failed test %d: have %s, want %s.", i, s, tst.str) - } - } -} - -var bytesToTopicTests = []struct { - data []byte - topic TopicType -}{ - {topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}}, - {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}}, - {topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}}, - {topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}}, - {topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}}, - {topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil}, -} - -var unmarshalTestsGood = []struct { - topic TopicType - data []byte -}{ - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x00000000"`)}, - {topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte(`"0x007f80ff"`)}, - {topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte(`"0xff807f00"`)}, - {topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte(`"0xf26e7779"`)}, -} - -var unmarshalTestsBad = []struct { - topic TopicType - data []byte -}{ - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000000"`)}, - {topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"abcdefg0"`)}, -} - -var unmarshalTestsUgly = []struct { - topic TopicType - data []byte -}{ - {topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte(`"0x00000001"`)}, -} - -func TestBytesToTopic(t *testing.T) { - for i, tst := range bytesToTopicTests { - top := BytesToTopic(tst.data) - if top != tst.topic { - t.Fatalf("failed test %d: have %v, want %v.", i, t, tst.topic) - } - } -} - -func TestUnmarshalTestsGood(t *testing.T) { - for i, tst := range unmarshalTestsGood { - var top TopicType - err := json.Unmarshal(tst.data, &top) - if err != nil { - t.Errorf("failed test %d. input: %v. err: %v", i, tst.data, err) - } else if top != tst.topic { - t.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic) - } - } -} - -func TestUnmarshalTestsBad(t *testing.T) { - // in this test UnmarshalJSON() is supposed to fail - for i, tst := range unmarshalTestsBad { - var top TopicType - err := json.Unmarshal(tst.data, &top) - if err == nil { - t.Fatalf("failed test %d. input: %v.", i, tst.data) - } - } -} - -func TestUnmarshalTestsUgly(t *testing.T) { - // in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong - for i, tst := range unmarshalTestsUgly { - var top TopicType - err := json.Unmarshal(tst.data, &top) - if err != nil { - t.Errorf("failed test %d. input: %v.", i, tst.data) - } else if top == tst.topic { - t.Errorf("failed test %d: have %v, want %v.", i, top, tst.topic) - } - } -} diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go deleted file mode 100644 index ac61036705..0000000000 --- a/whisper/whisperv6/whisper.go +++ /dev/null @@ -1,1140 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "bytes" - "crypto/ecdsa" - "crypto/sha256" - "fmt" - "math" - "runtime" - "sync" - "time" - - mapset "github.com/deckarep/golang-set" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/syndtr/goleveldb/leveldb/errors" - "golang.org/x/crypto/pbkdf2" - "golang.org/x/sync/syncmap" -) - -// Statistics holds several message-related counter for analytics -// purposes. -type Statistics struct { - messagesCleared int - memoryCleared int - memoryUsed int - cycles int - totalMessagesCleared int -} - -const ( - maxMsgSizeIdx = iota // Maximal message length allowed by the whisper node - overflowIdx // Indicator of message queue overflow - minPowIdx // Minimal PoW required by the whisper node - minPowToleranceIdx // Minimal PoW tolerated by the whisper node for a limited time - bloomFilterIdx // Bloom filter for topics of interest for this node - bloomFilterToleranceIdx // Bloom filter tolerated by the whisper node for a limited time - lightClientModeIdx // Light client mode. (does not forward any messages) - restrictConnectionBetweenLightClientsIdx // Restrict connection between two light clients -) - -// Whisper represents a dark communication interface through the Ethereum -// network, using its very own P2P communication layer. -type Whisper struct { - protocol p2p.Protocol // Protocol description and parameters - filters *Filters // Message filters installed with Subscribe function - - privateKeys map[string]*ecdsa.PrivateKey // Private key storage - symKeys map[string][]byte // Symmetric key storage - keyMu sync.RWMutex // Mutex associated with key storages - - poolMu sync.RWMutex // Mutex to sync the message and expiration pools - envelopes map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node - expirations map[uint32]mapset.Set // Message expiration pool - - peerMu sync.RWMutex // Mutex to sync the active peer set - peers map[*Peer]struct{} // Set of currently active peers - - messageQueue chan *Envelope // Message queue for normal whisper messages - p2pMsgQueue chan *Envelope // Message queue for peer-to-peer messages (not to be forwarded any further) - quit chan struct{} // Channel used for graceful exit - - settings syncmap.Map // holds configuration settings that can be dynamically changed - - syncAllowance int // maximum time in seconds allowed to process the whisper-related messages - - statsMu sync.Mutex // guard stats - stats Statistics // Statistics of whisper node - - mailServer MailServer // MailServer interface - - wg sync.WaitGroup -} - -// New creates a Whisper client ready to communicate through the Ethereum P2P network. -func New(stack *node.Node, cfg *Config) (*Whisper, error) { - if cfg == nil { - cfg = &DefaultConfig - } - - whisper := &Whisper{ - privateKeys: make(map[string]*ecdsa.PrivateKey), - symKeys: make(map[string][]byte), - envelopes: make(map[common.Hash]*Envelope), - expirations: make(map[uint32]mapset.Set), - peers: make(map[*Peer]struct{}), - messageQueue: make(chan *Envelope, messageQueueLimit), - p2pMsgQueue: make(chan *Envelope, messageQueueLimit), - quit: make(chan struct{}), - syncAllowance: DefaultSyncAllowance, - } - - whisper.filters = NewFilters(whisper) - - whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW) - whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize) - whisper.settings.Store(overflowIdx, false) - whisper.settings.Store(restrictConnectionBetweenLightClientsIdx, cfg.RestrictConnectionBetweenLightClients) - - // p2p whisper sub protocol handler - whisper.protocol = p2p.Protocol{ - Name: ProtocolName, - Version: uint(ProtocolVersion), - Length: NumberOfMessageCodes, - Run: whisper.HandlePeer, - NodeInfo: func() interface{} { - return map[string]interface{}{ - "version": ProtocolVersionStr, - "maxMessageSize": whisper.MaxMessageSize(), - "minimumPoW": whisper.MinPow(), - } - }, - } - - stack.RegisterAPIs(whisper.APIs()) - stack.RegisterProtocols(whisper.Protocols()) - stack.RegisterLifecycle(whisper) - return whisper, nil -} - -// MinPow returns the PoW value required by this node. -func (whisper *Whisper) MinPow() float64 { - val, exist := whisper.settings.Load(minPowIdx) - if !exist || val == nil { - return DefaultMinimumPoW - } - v, ok := val.(float64) - if !ok { - log.Error("Error loading minPowIdx, using default") - return DefaultMinimumPoW - } - return v -} - -// MinPowTolerance returns the value of minimum PoW which is tolerated for a limited -// time after PoW was changed. If sufficient time have elapsed or no change of PoW -// have ever occurred, the return value will be the same as return value of MinPow(). -func (whisper *Whisper) MinPowTolerance() float64 { - val, exist := whisper.settings.Load(minPowToleranceIdx) - if !exist || val == nil { - return DefaultMinimumPoW - } - return val.(float64) -} - -// BloomFilter returns the aggregated bloom filter for all the topics of interest. -// The nodes are required to send only messages that match the advertised bloom filter. -// If a message does not match the bloom, it will tantamount to spam, and the peer will -// be disconnected. -func (whisper *Whisper) BloomFilter() []byte { - val, exist := whisper.settings.Load(bloomFilterIdx) - if !exist || val == nil { - return nil - } - return val.([]byte) -} - -// BloomFilterTolerance returns the bloom filter which is tolerated for a limited -// time after new bloom was advertised to the peers. If sufficient time have elapsed -// or no change of bloom filter have ever occurred, the return value will be the same -// as return value of BloomFilter(). -func (whisper *Whisper) BloomFilterTolerance() []byte { - val, exist := whisper.settings.Load(bloomFilterToleranceIdx) - if !exist || val == nil { - return nil - } - return val.([]byte) -} - -// MaxMessageSize returns the maximum accepted message size. -func (whisper *Whisper) MaxMessageSize() uint32 { - val, _ := whisper.settings.Load(maxMsgSizeIdx) - return val.(uint32) -} - -// Overflow returns an indication if the message queue is full. -func (whisper *Whisper) Overflow() bool { - val, _ := whisper.settings.Load(overflowIdx) - return val.(bool) -} - -// APIs returns the RPC descriptors the Whisper implementation offers -func (whisper *Whisper) APIs() []rpc.API { - return []rpc.API{ - { - Namespace: ProtocolName, - Version: ProtocolVersionStr, - Service: NewPublicWhisperAPI(whisper), - Public: true, - }, - } -} - -// RegisterServer registers MailServer interface. -// MailServer will process all the incoming messages with p2pRequestCode. -func (whisper *Whisper) RegisterServer(server MailServer) { - whisper.mailServer = server -} - -// Protocols returns the whisper sub-protocols ran by this particular client. -func (whisper *Whisper) Protocols() []p2p.Protocol { - return []p2p.Protocol{whisper.protocol} -} - -// Version returns the whisper sub-protocols version number. -func (whisper *Whisper) Version() uint { - return whisper.protocol.Version -} - -// SetMaxMessageSize sets the maximal message size allowed by this node -func (whisper *Whisper) SetMaxMessageSize(size uint32) error { - if size > MaxMessageSize { - return fmt.Errorf("message size too large [%d>%d]", size, MaxMessageSize) - } - whisper.settings.Store(maxMsgSizeIdx, size) - return nil -} - -// SetBloomFilter sets the new bloom filter -func (whisper *Whisper) SetBloomFilter(bloom []byte) error { - if len(bloom) != BloomFilterSize { - return fmt.Errorf("invalid bloom filter size: %d", len(bloom)) - } - - b := make([]byte, BloomFilterSize) - copy(b, bloom) - - whisper.settings.Store(bloomFilterIdx, b) - whisper.notifyPeersAboutBloomFilterChange(b) - - whisper.wg.Add(1) - go func() { - // allow some time before all the peers have processed the notification - defer whisper.wg.Done() - ticker := time.NewTicker(time.Duration(whisper.syncAllowance) * time.Second) - defer ticker.Stop() - - <-ticker.C - whisper.settings.Store(bloomFilterToleranceIdx, b) - }() - - return nil -} - -// SetMinimumPoW sets the minimal PoW required by this node -func (whisper *Whisper) SetMinimumPoW(val float64) error { - if val < 0.0 { - return fmt.Errorf("invalid PoW: %f", val) - } - - whisper.settings.Store(minPowIdx, val) - whisper.notifyPeersAboutPowRequirementChange(val) - - whisper.wg.Add(1) - go func() { - defer whisper.wg.Done() - // allow some time before all the peers have processed the notification - ticker := time.NewTicker(time.Duration(whisper.syncAllowance) * time.Second) - defer ticker.Stop() - - <-ticker.C - whisper.settings.Store(minPowToleranceIdx, val) - }() - - return nil -} - -// SetMinimumPowTest sets the minimal PoW in test environment -func (whisper *Whisper) SetMinimumPowTest(val float64) { - whisper.settings.Store(minPowIdx, val) - whisper.notifyPeersAboutPowRequirementChange(val) - whisper.settings.Store(minPowToleranceIdx, val) -} - -//SetLightClientMode makes node light client (does not forward any messages) -func (whisper *Whisper) SetLightClientMode(v bool) { - whisper.settings.Store(lightClientModeIdx, v) -} - -//LightClientMode indicates is this node is light client (does not forward any messages) -func (whisper *Whisper) LightClientMode() bool { - val, exist := whisper.settings.Load(lightClientModeIdx) - if !exist || val == nil { - return false - } - v, ok := val.(bool) - return v && ok -} - -//LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed -func (whisper *Whisper) LightClientModeConnectionRestricted() bool { - val, exist := whisper.settings.Load(restrictConnectionBetweenLightClientsIdx) - if !exist || val == nil { - return false - } - v, ok := val.(bool) - return v && ok -} - -func (whisper *Whisper) notifyPeersAboutPowRequirementChange(pow float64) { - arr := whisper.getPeers() - for _, p := range arr { - err := p.notifyAboutPowRequirementChange(pow) - if err != nil { - // allow one retry - err = p.notifyAboutPowRequirementChange(pow) - } - if err != nil { - log.Warn("failed to notify peer about new pow requirement", "peer", p.ID(), "error", err) - } - } -} - -func (whisper *Whisper) notifyPeersAboutBloomFilterChange(bloom []byte) { - arr := whisper.getPeers() - for _, p := range arr { - err := p.notifyAboutBloomFilterChange(bloom) - if err != nil { - // allow one retry - err = p.notifyAboutBloomFilterChange(bloom) - } - if err != nil { - log.Warn("failed to notify peer about new bloom filter", "peer", p.ID(), "error", err) - } - } -} - -func (whisper *Whisper) getPeers() []*Peer { - arr := make([]*Peer, len(whisper.peers)) - i := 0 - whisper.peerMu.Lock() - defer whisper.peerMu.Unlock() - for p := range whisper.peers { - arr[i] = p - i++ - } - return arr -} - -// getPeer retrieves peer by ID -func (whisper *Whisper) getPeer(peerID []byte) (*Peer, error) { - whisper.peerMu.Lock() - defer whisper.peerMu.Unlock() - for p := range whisper.peers { - id := p.peer.ID() - if bytes.Equal(peerID, id[:]) { - return p, nil - } - } - return nil, fmt.Errorf("could not find peer with ID: %x", peerID) -} - -// AllowP2PMessagesFromPeer marks specific peer trusted, -// which will allow it to send historic (expired) messages. -func (whisper *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error { - p, err := whisper.getPeer(peerID) - if err != nil { - return err - } - p.trusted = true - return nil -} - -// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer, -// which is known to implement MailServer interface, and is supposed to process this -// request and respond with a number of peer-to-peer messages (possibly expired), -// which are not supposed to be forwarded any further. -// The whisper protocol is agnostic of the format and contents of envelope. -func (whisper *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error { - p, err := whisper.getPeer(peerID) - if err != nil { - return err - } - p.trusted = true - return p2p.Send(p.ws, p2pRequestCode, envelope) -} - -// SendP2PMessage sends a peer-to-peer message to a specific peer. -func (whisper *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error { - p, err := whisper.getPeer(peerID) - if err != nil { - return err - } - return whisper.SendP2PDirect(p, envelope) -} - -// SendP2PDirect sends a peer-to-peer message to a specific peer. -func (whisper *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error { - return p2p.Send(peer.ws, p2pMessageCode, envelope) -} - -// NewKeyPair generates a new cryptographic identity for the client, and injects -// it into the known identities for message decryption. Returns ID of the new key pair. -func (whisper *Whisper) NewKeyPair() (string, error) { - key, err := crypto.GenerateKey() - if err != nil || !validatePrivateKey(key) { - key, err = crypto.GenerateKey() // retry once - } - if err != nil { - return "", err - } - if !validatePrivateKey(key) { - return "", fmt.Errorf("failed to generate valid key") - } - - id, err := GenerateRandomID() - if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) - } - - whisper.keyMu.Lock() - defer whisper.keyMu.Unlock() - - if whisper.privateKeys[id] != nil { - return "", fmt.Errorf("failed to generate unique ID") - } - whisper.privateKeys[id] = key - return id, nil -} - -// DeleteKeyPair deletes the specified key if it exists. -func (whisper *Whisper) DeleteKeyPair(key string) bool { - whisper.keyMu.Lock() - defer whisper.keyMu.Unlock() - - if whisper.privateKeys[key] != nil { - delete(whisper.privateKeys, key) - return true - } - return false -} - -// AddKeyPair imports a asymmetric private key and returns it identifier. -func (whisper *Whisper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) { - id, err := GenerateRandomID() - if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) - } - - whisper.keyMu.Lock() - whisper.privateKeys[id] = key - whisper.keyMu.Unlock() - - return id, nil -} - -// HasKeyPair checks if the whisper node is configured with the private key -// of the specified public pair. -func (whisper *Whisper) HasKeyPair(id string) bool { - whisper.keyMu.RLock() - defer whisper.keyMu.RUnlock() - return whisper.privateKeys[id] != nil -} - -// GetPrivateKey retrieves the private key of the specified identity. -func (whisper *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { - whisper.keyMu.RLock() - defer whisper.keyMu.RUnlock() - key := whisper.privateKeys[id] - if key == nil { - return nil, fmt.Errorf("invalid id") - } - return key, nil -} - -// GenerateSymKey generates a random symmetric key and stores it under id, -// which is then returned. Will be used in the future for session key exchange. -func (whisper *Whisper) GenerateSymKey() (string, error) { - key, err := generateSecureRandomData(aesKeyLength) - if err != nil { - return "", err - } else if !validateDataIntegrity(key, aesKeyLength) { - return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data") - } - - id, err := GenerateRandomID() - if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) - } - - whisper.keyMu.Lock() - defer whisper.keyMu.Unlock() - - if whisper.symKeys[id] != nil { - return "", fmt.Errorf("failed to generate unique ID") - } - whisper.symKeys[id] = key - return id, nil -} - -// AddSymKeyDirect stores the key, and returns its id. -func (whisper *Whisper) AddSymKeyDirect(key []byte) (string, error) { - if len(key) != aesKeyLength { - return "", fmt.Errorf("wrong key size: %d", len(key)) - } - - id, err := GenerateRandomID() - if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) - } - - whisper.keyMu.Lock() - defer whisper.keyMu.Unlock() - - if whisper.symKeys[id] != nil { - return "", fmt.Errorf("failed to generate unique ID") - } - whisper.symKeys[id] = key - return id, nil -} - -// AddSymKeyFromPassword generates the key from password, stores it, and returns its id. -func (whisper *Whisper) AddSymKeyFromPassword(password string) (string, error) { - id, err := GenerateRandomID() - if err != nil { - return "", fmt.Errorf("failed to generate ID: %s", err) - } - if whisper.HasSymKey(id) { - return "", fmt.Errorf("failed to generate unique ID") - } - - // kdf should run no less than 0.1 seconds on an average computer, - // because it's an once in a session experience - derived := pbkdf2.Key([]byte(password), nil, 65356, aesKeyLength, sha256.New) - if err != nil { - return "", err - } - - whisper.keyMu.Lock() - defer whisper.keyMu.Unlock() - - // double check is necessary, because deriveKeyMaterial() is very slow - if whisper.symKeys[id] != nil { - return "", fmt.Errorf("critical error: failed to generate unique ID") - } - whisper.symKeys[id] = derived - return id, nil -} - -// HasSymKey returns true if there is a key associated with the given id. -// Otherwise returns false. -func (whisper *Whisper) HasSymKey(id string) bool { - whisper.keyMu.RLock() - defer whisper.keyMu.RUnlock() - return whisper.symKeys[id] != nil -} - -// DeleteSymKey deletes the key associated with the name string if it exists. -func (whisper *Whisper) DeleteSymKey(id string) bool { - whisper.keyMu.Lock() - defer whisper.keyMu.Unlock() - if whisper.symKeys[id] != nil { - delete(whisper.symKeys, id) - return true - } - return false -} - -// GetSymKey returns the symmetric key associated with the given id. -func (whisper *Whisper) GetSymKey(id string) ([]byte, error) { - whisper.keyMu.RLock() - defer whisper.keyMu.RUnlock() - if whisper.symKeys[id] != nil { - return whisper.symKeys[id], nil - } - return nil, fmt.Errorf("non-existent key ID") -} - -// Subscribe installs a new message handler used for filtering, decrypting -// and subsequent storing of incoming messages. -func (whisper *Whisper) Subscribe(f *Filter) (string, error) { - s, err := whisper.filters.Install(f) - if err == nil { - whisper.updateBloomFilter(f) - } - return s, err -} - -// updateBloomFilter recalculates the new value of bloom filter, -// and informs the peers if necessary. -func (whisper *Whisper) updateBloomFilter(f *Filter) { - aggregate := make([]byte, BloomFilterSize) - for _, t := range f.Topics { - top := BytesToTopic(t) - b := TopicToBloom(top) - aggregate = addBloom(aggregate, b) - } - - if !BloomFilterMatch(whisper.BloomFilter(), aggregate) { - // existing bloom filter must be updated - aggregate = addBloom(whisper.BloomFilter(), aggregate) - whisper.SetBloomFilter(aggregate) - } -} - -// GetFilter returns the filter by id. -func (whisper *Whisper) GetFilter(id string) *Filter { - return whisper.filters.Get(id) -} - -// Unsubscribe removes an installed message handler. -func (whisper *Whisper) Unsubscribe(id string) error { - ok := whisper.filters.Uninstall(id) - if !ok { - return fmt.Errorf("Unsubscribe: Invalid ID") - } - return nil -} - -// Send injects a message into the whisper send queue, to be distributed in the -// network in the coming cycles. -func (whisper *Whisper) Send(envelope *Envelope) error { - ok, err := whisper.add(envelope, false) - if err == nil && !ok { - return fmt.Errorf("failed to add envelope") - } - return err -} - -// Start implements node.Lifecycle, starting the background data propagation thread -// of the Whisper protocol. -func (whisper *Whisper) Start() error { - log.Info("started whisper v." + ProtocolVersionStr) - whisper.wg.Add(1) - go whisper.update() - - numCPU := runtime.NumCPU() - for i := 0; i < numCPU; i++ { - whisper.wg.Add(1) - go whisper.processQueue() - } - - return nil -} - -// Stop implements node.Lifecycle, stopping the background data propagation thread -// of the Whisper protocol. -func (whisper *Whisper) Stop() error { - close(whisper.quit) - whisper.wg.Wait() - log.Info("whisper stopped") - return nil -} - -// HandlePeer is called by the underlying P2P layer when the whisper sub-protocol -// connection is negotiated. -func (whisper *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - // Create the new peer and start tracking it - whisperPeer := newPeer(whisper, peer, rw) - - whisper.peerMu.Lock() - whisper.peers[whisperPeer] = struct{}{} - whisper.peerMu.Unlock() - - defer func() { - whisper.peerMu.Lock() - delete(whisper.peers, whisperPeer) - whisper.peerMu.Unlock() - }() - - // Run the peer handshake and state updates - if err := whisperPeer.handshake(); err != nil { - return err - } - whisperPeer.start() - defer whisperPeer.stop() - - return whisper.runMessageLoop(whisperPeer, rw) -} - -// runMessageLoop reads and processes inbound messages directly to merge into client-global state. -func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { - for { - // fetch the next packet - packet, err := rw.ReadMsg() - if err != nil { - log.Info("message loop", "peer", p.peer.ID(), "err", err) - return err - } - if packet.Size > whisper.MaxMessageSize() { - log.Warn("oversized message received", "peer", p.peer.ID()) - return errors.New("oversized message received") - } - - switch packet.Code { - case statusCode: - // this should not happen, but no need to panic; just ignore this message. - log.Warn("unxepected status message received", "peer", p.peer.ID()) - case messagesCode: - // decode the contained envelopes - var envelopes []*Envelope - if err := packet.Decode(&envelopes); err != nil { - log.Warn("failed to decode envelopes, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid envelopes") - } - - trouble := false - for _, env := range envelopes { - cached, err := whisper.add(env, whisper.LightClientMode()) - if err != nil { - trouble = true - log.Error("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) - } - if cached { - p.mark(env) - } - } - - if trouble { - return errors.New("invalid envelope") - } - case powRequirementCode: - s := rlp.NewStream(packet.Payload, uint64(packet.Size)) - i, err := s.Uint() - if err != nil { - log.Warn("failed to decode powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid powRequirementCode message") - } - f := math.Float64frombits(i) - if math.IsInf(f, 0) || math.IsNaN(f) || f < 0.0 { - log.Warn("invalid value in powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid value in powRequirementCode message") - } - p.powRequirement = f - case bloomFilterExCode: - var bloom []byte - err := packet.Decode(&bloom) - if err == nil && len(bloom) != BloomFilterSize { - err = fmt.Errorf("wrong bloom filter size %d", len(bloom)) - } - - if err != nil { - log.Warn("failed to decode bloom filter exchange message, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid bloom filter exchange message") - } - p.setBloomFilter(bloom) - case p2pMessageCode: - // peer-to-peer message, sent directly to peer bypassing PoW checks, etc. - // this message is not supposed to be forwarded to other peers, and - // therefore might not satisfy the PoW, expiry and other requirements. - // these messages are only accepted from the trusted peer. - if p.trusted { - var envelope Envelope - if err := packet.Decode(&envelope); err != nil { - log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid direct message") - } - whisper.postEvent(&envelope, true) - } - case p2pRequestCode: - // Must be processed if mail server is implemented. Otherwise ignore. - if whisper.mailServer != nil { - var request Envelope - if err := packet.Decode(&request); err != nil { - log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid p2p request") - } - whisper.mailServer.DeliverMail(p, &request) - } - default: - // New message types might be implemented in the future versions of Whisper. - // For forward compatibility, just ignore. - } - - packet.Discard() - } -} - -// add inserts a new envelope into the message pool to be distributed within the -// whisper network. It also inserts the envelope into the expiration pool at the -// appropriate time-stamp. In case of error, connection should be dropped. -// param isP2P indicates whether the message is peer-to-peer (should not be forwarded). -func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) { - now := uint32(time.Now().Unix()) - sent := envelope.Expiry - envelope.TTL - - if sent > now { - if sent-DefaultSyncAllowance > now { - return false, fmt.Errorf("envelope created in the future [%x]", envelope.Hash()) - } - // recalculate PoW, adjusted for the time difference, plus one second for latency - envelope.calculatePoW(sent - now + 1) - } - - if envelope.Expiry < now { - if envelope.Expiry+DefaultSyncAllowance*2 < now { - return false, fmt.Errorf("very old message") - } - log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) - return false, nil // drop envelope without error - } - - if uint32(envelope.size()) > whisper.MaxMessageSize() { - return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) - } - - if envelope.PoW() < whisper.MinPow() { - // maybe the value was recently changed, and the peers did not adjust yet. - // in this case the previous value is retrieved by MinPowTolerance() - // for a short period of peer synchronization. - if envelope.PoW() < whisper.MinPowTolerance() { - return false, fmt.Errorf("envelope with low PoW received: PoW=%f, hash=[%v]", envelope.PoW(), envelope.Hash().Hex()) - } - } - - if !BloomFilterMatch(whisper.BloomFilter(), envelope.Bloom()) { - // maybe the value was recently changed, and the peers did not adjust yet. - // in this case the previous value is retrieved by BloomFilterTolerance() - // for a short period of peer synchronization. - if !BloomFilterMatch(whisper.BloomFilterTolerance(), envelope.Bloom()) { - return false, fmt.Errorf("envelope does not match bloom filter, hash=[%v], bloom: \n%x \n%x \n%x", - envelope.Hash().Hex(), whisper.BloomFilter(), envelope.Bloom(), envelope.Topic) - } - } - - hash := envelope.Hash() - - whisper.poolMu.Lock() - _, alreadyCached := whisper.envelopes[hash] - if !alreadyCached { - whisper.envelopes[hash] = envelope - if whisper.expirations[envelope.Expiry] == nil { - whisper.expirations[envelope.Expiry] = mapset.NewThreadUnsafeSet() - } - if !whisper.expirations[envelope.Expiry].Contains(hash) { - whisper.expirations[envelope.Expiry].Add(hash) - } - } - whisper.poolMu.Unlock() - - if alreadyCached { - log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex()) - } else { - log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex()) - whisper.statsMu.Lock() - whisper.stats.memoryUsed += envelope.size() - whisper.statsMu.Unlock() - whisper.postEvent(envelope, isP2P) // notify the local node about the new message - if whisper.mailServer != nil { - whisper.mailServer.Archive(envelope) - } - } - return true, nil -} - -// postEvent queues the message for further processing. -func (whisper *Whisper) postEvent(envelope *Envelope, isP2P bool) { - if isP2P { - whisper.p2pMsgQueue <- envelope - } else { - whisper.checkOverflow() - whisper.messageQueue <- envelope - } -} - -// checkOverflow checks if message queue overflow occurs and reports it if necessary. -func (whisper *Whisper) checkOverflow() { - queueSize := len(whisper.messageQueue) - - if queueSize == messageQueueLimit { - if !whisper.Overflow() { - whisper.settings.Store(overflowIdx, true) - log.Warn("message queue overflow") - } - } else if queueSize <= messageQueueLimit/2 { - if whisper.Overflow() { - whisper.settings.Store(overflowIdx, false) - log.Warn("message queue overflow fixed (back to normal)") - } - } -} - -// processQueue delivers the messages to the watchers during the lifetime of the whisper node. -func (whisper *Whisper) processQueue() { - defer whisper.wg.Done() - var e *Envelope - for { - select { - case <-whisper.quit: - return - - case e = <-whisper.messageQueue: - whisper.filters.NotifyWatchers(e, false) - - case e = <-whisper.p2pMsgQueue: - whisper.filters.NotifyWatchers(e, true) - } - } -} - -// update loops until the lifetime of the whisper node, updating its internal -// state by expiring stale messages from the pool. -func (whisper *Whisper) update() { - defer whisper.wg.Done() - // Start a ticker to check for expirations - expire := time.NewTicker(expirationCycle) - defer expire.Stop() - - // Repeat updates until termination is requested - for { - select { - case <-expire.C: - whisper.expire() - - case <-whisper.quit: - return - } - } -} - -// expire iterates over all the expiration timestamps, removing all stale -// messages from the pools. -func (whisper *Whisper) expire() { - whisper.poolMu.Lock() - defer whisper.poolMu.Unlock() - - whisper.statsMu.Lock() - defer whisper.statsMu.Unlock() - whisper.stats.reset() - now := uint32(time.Now().Unix()) - for expiry, hashSet := range whisper.expirations { - if expiry < now { - // Dump all expired messages and remove timestamp - hashSet.Each(func(v interface{}) bool { - sz := whisper.envelopes[v.(common.Hash)].size() - delete(whisper.envelopes, v.(common.Hash)) - whisper.stats.messagesCleared++ - whisper.stats.memoryCleared += sz - whisper.stats.memoryUsed -= sz - return false - }) - whisper.expirations[expiry].Clear() - delete(whisper.expirations, expiry) - } - } -} - -// Stats returns the whisper node statistics. -func (whisper *Whisper) Stats() Statistics { - whisper.statsMu.Lock() - defer whisper.statsMu.Unlock() - - return whisper.stats -} - -// Envelopes retrieves all the messages currently pooled by the node. -func (whisper *Whisper) Envelopes() []*Envelope { - whisper.poolMu.RLock() - defer whisper.poolMu.RUnlock() - - all := make([]*Envelope, 0, len(whisper.envelopes)) - for _, envelope := range whisper.envelopes { - all = append(all, envelope) - } - return all -} - -// isEnvelopeCached checks if envelope with specific hash has already been received and cached. -func (whisper *Whisper) isEnvelopeCached(hash common.Hash) bool { - whisper.poolMu.Lock() - defer whisper.poolMu.Unlock() - - _, exist := whisper.envelopes[hash] - return exist -} - -// reset resets the node's statistics after each expiry cycle. -func (s *Statistics) reset() { - s.cycles++ - s.totalMessagesCleared += s.messagesCleared - - s.memoryCleared = 0 - s.messagesCleared = 0 -} - -// ValidatePublicKey checks the format of the given public key. -func ValidatePublicKey(k *ecdsa.PublicKey) bool { - return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0 -} - -// validatePrivateKey checks the format of the given private key. -func validatePrivateKey(k *ecdsa.PrivateKey) bool { - if k == nil || k.D == nil || k.D.Sign() == 0 { - return false - } - return ValidatePublicKey(&k.PublicKey) -} - -// validateDataIntegrity returns false if the data have the wrong or contains all zeros, -// which is the simplest and the most common bug. -func validateDataIntegrity(k []byte, expectedSize int) bool { - if len(k) != expectedSize { - return false - } - if expectedSize > 3 && containsOnlyZeros(k) { - return false - } - return true -} - -// containsOnlyZeros checks if the data contain only zeros. -func containsOnlyZeros(data []byte) bool { - for _, b := range data { - if b != 0 { - return false - } - } - return true -} - -// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer. -func bytesToUintLittleEndian(b []byte) (res uint64) { - mul := uint64(1) - for i := 0; i < len(b); i++ { - res += uint64(b[i]) * mul - mul *= 256 - } - return res -} - -// BytesToUintBigEndian converts the slice to 64-bit unsigned integer. -func BytesToUintBigEndian(b []byte) (res uint64) { - for i := 0; i < len(b); i++ { - res *= 256 - res += uint64(b[i]) - } - return res -} - -// GenerateRandomID generates a random string, which is then returned to be used as a key id -func GenerateRandomID() (id string, err error) { - buf, err := generateSecureRandomData(keyIDSize) - if err != nil { - return "", err - } - if !validateDataIntegrity(buf, keyIDSize) { - return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data") - } - id = common.Bytes2Hex(buf) - return id, err -} - -func isFullNode(bloom []byte) bool { - if bloom == nil { - return true - } - for _, b := range bloom { - if b != 255 { - return false - } - } - return true -} - -func BloomFilterMatch(filter, sample []byte) bool { - if filter == nil { - return true - } - - for i := 0; i < BloomFilterSize; i++ { - f := filter[i] - s := sample[i] - if (f | s) != f { - return false - } - } - - return true -} - -func addBloom(a, b []byte) []byte { - c := make([]byte, BloomFilterSize) - for i := 0; i < BloomFilterSize; i++ { - c[i] = a[i] | b[i] - } - return c -} - -func StandaloneWhisperService(cfg *Config) *Whisper { - if cfg == nil { - cfg = &DefaultConfig - } - - whisper := &Whisper{ - privateKeys: make(map[string]*ecdsa.PrivateKey), - symKeys: make(map[string][]byte), - envelopes: make(map[common.Hash]*Envelope), - expirations: make(map[uint32]mapset.Set), - peers: make(map[*Peer]struct{}), - messageQueue: make(chan *Envelope, messageQueueLimit), - p2pMsgQueue: make(chan *Envelope, messageQueueLimit), - quit: make(chan struct{}), - syncAllowance: DefaultSyncAllowance, - } - - whisper.filters = NewFilters(whisper) - - whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW) - whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize) - whisper.settings.Store(overflowIdx, false) - whisper.settings.Store(restrictConnectionBetweenLightClientsIdx, cfg.RestrictConnectionBetweenLightClients) - - // p2p whisper sub protocol handler - whisper.protocol = p2p.Protocol{ - Name: ProtocolName, - Version: uint(ProtocolVersion), - Length: NumberOfMessageCodes, - Run: whisper.HandlePeer, - NodeInfo: func() interface{} { - return map[string]interface{}{ - "version": ProtocolVersionStr, - "maxMessageSize": whisper.MaxMessageSize(), - "minimumPoW": whisper.MinPow(), - } - }, - } - - return whisper -} diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go deleted file mode 100644 index 7fb8f7c1cd..0000000000 --- a/whisper/whisperv6/whisper_test.go +++ /dev/null @@ -1,928 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "bytes" - "crypto/ecdsa" - "crypto/sha256" - mrand "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/node" - "golang.org/x/crypto/pbkdf2" -) - -func TestWhisperBasic(t *testing.T) { - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - shh := w.Protocols()[0] - if shh.Name != ProtocolName { - t.Fatalf("failed Protocol Name: %v.", shh.Name) - } - if uint64(shh.Version) != ProtocolVersion { - t.Fatalf("failed Protocol Version: %v.", shh.Version) - } - if shh.Length != NumberOfMessageCodes { - t.Fatalf("failed Protocol Length: %v.", shh.Length) - } - if shh.Run == nil { - t.Fatal("failed shh.Run.") - } - if uint64(w.Version()) != ProtocolVersion { - t.Fatalf("failed whisper Version: %v.", shh.Version) - } - if w.GetFilter("non-existent") != nil { - t.Fatal("failed GetFilter.") - } - - peerID := make([]byte, 64) - mrand.Read(peerID) - peer, _ := w.getPeer(peerID) - if peer != nil { - t.Fatal("found peer for random key.") - } - if err := w.AllowP2PMessagesFromPeer(peerID); err == nil { - t.Fatal("failed MarkPeerTrusted.") - } - exist := w.HasSymKey("non-existing") - if exist { - t.Fatal("failed HasSymKey.") - } - key, err := w.GetSymKey("non-existing") - if err == nil { - t.Fatalf("failed GetSymKey(non-existing): false positive. key=%v", key) - } - if key != nil { - t.Fatalf("failed GetSymKey: false positive. key=%v", key) - } - mail := w.Envelopes() - if len(mail) != 0 { - t.Fatalf("failed w.Envelopes(). length=%d", len(mail)) - } - - derived := pbkdf2.Key(peerID, nil, 65356, aesKeyLength, sha256.New) - if !validateDataIntegrity(derived, aesKeyLength) { - t.Fatalf("failed validateSymmetricKey with param = %v.", derived) - } - if containsOnlyZeros(derived) { - t.Fatalf("failed containsOnlyZeros with param = %v.", derived) - } - - buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0} - le := bytesToUintLittleEndian(buf) - be := BytesToUintBigEndian(buf) - if le != uint64(0x280e5ff) { - t.Fatalf("failed bytesToIntLittleEndian: %d.", le) - } - if be != uint64(0xffe5800200) { - t.Fatalf("failed BytesToIntBigEndian: %d.", be) - } - - id, err := w.NewKeyPair() - if err != nil { - t.Fatalf("failed to generate new key pair: %v.", err) - } - pk, err := w.GetPrivateKey(id) - if err != nil { - t.Fatalf("failed to retrieve new key pair: %v.", err) - } - if !validatePrivateKey(pk) { - t.Fatalf("failed validatePrivateKey: %v.", pk) - } - if !ValidatePublicKey(&pk.PublicKey) { - t.Fatalf("failed ValidatePublicKey: %v.", pk) - } -} - -func TestWhisperAsymmetricKeyImport(t *testing.T) { - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - var privateKeys []*ecdsa.PrivateKey - for i := 0; i < 50; i++ { - id, err := w.NewKeyPair() - if err != nil { - t.Fatalf("could not generate key: %v", err) - } - - pk, err := w.GetPrivateKey(id) - if err != nil { - t.Fatalf("could not export private key: %v", err) - } - - privateKeys = append(privateKeys, pk) - - if !w.DeleteKeyPair(id) { - t.Fatal("could not delete private key") - } - } - - for _, pk := range privateKeys { - if _, err := w.AddKeyPair(pk); err != nil { - t.Fatalf("could not import private key: %v", err) - } - } -} - -func TestWhisperIdentityManagement(t *testing.T) { - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - id1, err := w.NewKeyPair() - if err != nil { - t.Fatalf("failed to generate new key pair: %s.", err) - } - id2, err := w.NewKeyPair() - if err != nil { - t.Fatalf("failed to generate new key pair: %s.", err) - } - pk1, err := w.GetPrivateKey(id1) - if err != nil { - t.Fatalf("failed to retrieve the key pair: %s.", err) - } - pk2, err := w.GetPrivateKey(id2) - if err != nil { - t.Fatalf("failed to retrieve the key pair: %s.", err) - } - - if !w.HasKeyPair(id1) { - t.Fatal("failed HasIdentity(pk1).") - } - if !w.HasKeyPair(id2) { - t.Fatal("failed HasIdentity(pk2).") - } - if pk1 == nil { - t.Fatal("failed GetIdentity(pk1).") - } - if pk2 == nil { - t.Fatal("failed GetIdentity(pk2).") - } - - if !validatePrivateKey(pk1) { - t.Fatal("pk1 is invalid.") - } - if !validatePrivateKey(pk2) { - t.Fatal("pk2 is invalid.") - } - - // Delete one identity - done := w.DeleteKeyPair(id1) - if !done { - t.Fatal("failed to delete id1.") - } - pk1, err = w.GetPrivateKey(id1) - if err == nil { - t.Fatalf("retrieve the key pair: false positive. key=%v", pk1) - } - pk2, err = w.GetPrivateKey(id2) - if err != nil { - t.Fatalf("failed to retrieve the key pair: %s.", err) - } - if w.HasKeyPair(id1) { - t.Fatal("failed DeleteIdentity(pub1): still exist.") - } - if !w.HasKeyPair(id2) { - t.Fatal("failed DeleteIdentity(pub1): pub2 does not exist.") - } - if pk1 != nil { - t.Fatal("failed DeleteIdentity(pub1): first key still exist.") - } - if pk2 == nil { - t.Fatal("failed DeleteIdentity(pub1): second key does not exist.") - } - - // Delete again non-existing identity - done = w.DeleteKeyPair(id1) - if done { - t.Fatal("delete id1: false positive.") - } - pk1, err = w.GetPrivateKey(id1) - if err == nil { - t.Fatalf("retrieve the key pair: false positive. key=%v", pk1) - } - pk2, err = w.GetPrivateKey(id2) - if err != nil { - t.Fatalf("failed to retrieve the key pair: %s.", err) - } - if w.HasKeyPair(id1) { - t.Fatal("failed delete non-existing identity: exist.") - } - if !w.HasKeyPair(id2) { - t.Fatal("failed delete non-existing identity: pub2 does not exist.") - } - if pk1 != nil { - t.Fatalf("failed delete non-existing identity: first key exist. key=%v", pk1) - } - if pk2 == nil { - t.Fatal("failed delete non-existing identity: second key does not exist.") - } - - // Delete second identity - done = w.DeleteKeyPair(id2) - if !done { - t.Fatal("failed to delete id2.") - } - pk1, err = w.GetPrivateKey(id1) - if err == nil { - t.Fatalf("retrieve the key pair: false positive. key=%v", pk1) - } - pk2, err = w.GetPrivateKey(id2) - if err == nil { - t.Fatalf("retrieve the key pair: false positive. key=%v", pk2) - } - if w.HasKeyPair(id1) { - t.Fatal("failed delete second identity: first identity exist.") - } - if w.HasKeyPair(id2) { - t.Fatal("failed delete second identity: still exist.") - } - if pk1 != nil { - t.Fatalf("failed delete second identity: first key exist. key=%v", pk1) - } - if pk2 != nil { - t.Fatalf("failed delete second identity: second key exist. key=%v", pk2) - } -} - -func TestWhisperSymKeyManagement(t *testing.T) { - InitSingleTest() - var ( - k1, k2 []byte - id2 = string("arbitrary-string-2") - ) - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - id1, err := w.GenerateSymKey() - if err != nil { - t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err) - } - - k1, err = w.GetSymKey(id1) - if err != nil { - t.Fatalf("failed GetSymKey(id1). err=%v", err) - } - k2, err = w.GetSymKey(id2) - if err == nil { - t.Fatalf("failed GetSymKey(id2): false positive. key=%v", k2) - } - if !w.HasSymKey(id1) { - t.Fatal("failed HasSymKey(id1).") - } - if w.HasSymKey(id2) { - t.Fatal("failed HasSymKey(id2): false positive.") - } - if k1 == nil { - t.Fatal("first key does not exist.") - } - if k2 != nil { - t.Fatalf("second key still exist. key=%v", k2) - } - - // add existing id, nothing should change - randomKey := make([]byte, aesKeyLength) - mrand.Read(randomKey) - id1, err = w.AddSymKeyDirect(randomKey) - if err != nil { - t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err) - } - - k1, err = w.GetSymKey(id1) - if err != nil { - t.Fatalf("failed w.GetSymKey(id1). err=%v", err) - } - k2, err = w.GetSymKey(id2) - if err == nil { - t.Fatalf("failed w.GetSymKey(id2): false positive. key=%v", k2) - } - if !w.HasSymKey(id1) { - t.Fatal("failed w.HasSymKey(id1).") - } - if w.HasSymKey(id2) { - t.Fatal("failed w.HasSymKey(id2): false positive.") - } - if k1 == nil { - t.Fatal("first key does not exist.") - } - if !bytes.Equal(k1, randomKey) { - t.Fatal("k1 != randomKey.") - } - if k2 != nil { - t.Fatalf("second key already exist. key=%v", k2) - } - - id2, err = w.AddSymKeyDirect(randomKey) - if err != nil { - t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err) - } - k1, err = w.GetSymKey(id1) - if err != nil { - t.Fatalf("failed w.GetSymKey(id1). err=%v", err) - } - k2, err = w.GetSymKey(id2) - if err != nil { - t.Fatalf("failed w.GetSymKey(id2). err=%v", err) - } - if !w.HasSymKey(id1) { - t.Fatal("HasSymKey(id1) failed.") - } - if !w.HasSymKey(id2) { - t.Fatal("HasSymKey(id2) failed.") - } - if k1 == nil { - t.Fatal("k1 does not exist.") - } - if k2 == nil { - t.Fatal("k2 does not exist.") - } - if !bytes.Equal(k1, k2) { - t.Fatal("k1 != k2.") - } - if !bytes.Equal(k1, randomKey) { - t.Fatal("k1 != randomKey.") - } - if len(k1) != aesKeyLength { - t.Fatalf("wrong length of k1. length=%d", len(k1)) - } - if len(k2) != aesKeyLength { - t.Fatalf("wrong length of k2. length=%d", len(k2)) - } - - w.DeleteSymKey(id1) - k1, err = w.GetSymKey(id1) - if err == nil { - t.Fatal("failed w.GetSymKey(id1): false positive.") - } - if k1 != nil { - t.Fatalf("failed GetSymKey(id1): false positive. key=%v", k1) - } - k2, err = w.GetSymKey(id2) - if err != nil { - t.Fatalf("failed w.GetSymKey(id2). err=%v", err) - } - if w.HasSymKey(id1) { - t.Fatal("failed to delete first key: still exist.") - } - if !w.HasSymKey(id2) { - t.Fatal("failed to delete first key: second key does not exist.") - } - if k2 == nil { - t.Fatal("failed to delete first key: second key is nil.") - } - - w.DeleteSymKey(id1) - w.DeleteSymKey(id2) - k1, err = w.GetSymKey(id1) - if err == nil { - t.Fatalf("failed w.GetSymKey(id1): false positive. key=%v", k1) - } - k2, err = w.GetSymKey(id2) - if err == nil { - t.Fatalf("failed w.GetSymKey(id2): false positive. key=%v", k2) - } - if k1 != nil || k2 != nil { - t.Fatal("k1 or k2 is not nil") - } - if w.HasSymKey(id1) { - t.Fatal("failed to delete second key: first key exist.") - } - if w.HasSymKey(id2) { - t.Fatal("failed to delete second key: still exist.") - } - if k1 != nil { - t.Fatal("failed to delete second key: first key is not nil.") - } - if k2 != nil { - t.Fatal("failed to delete second key: second key is not nil.") - } - - randomKey = make([]byte, aesKeyLength+1) - mrand.Read(randomKey) - _, err = w.AddSymKeyDirect(randomKey) - if err == nil { - t.Fatalf("added the key with wrong size, seed %d.", seed) - } - - const password = "arbitrary data here" - id1, err = w.AddSymKeyFromPassword(password) - if err != nil { - t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err) - } - id2, err = w.AddSymKeyFromPassword(password) - if err != nil { - t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err) - } - k1, err = w.GetSymKey(id1) - if err != nil { - t.Fatalf("failed w.GetSymKey(id1). err=%v", err) - } - k2, err = w.GetSymKey(id2) - if err != nil { - t.Fatalf("failed w.GetSymKey(id2). err=%v", err) - } - if !w.HasSymKey(id1) { - t.Fatal("HasSymKey(id1) failed.") - } - if !w.HasSymKey(id2) { - t.Fatal("HasSymKey(id2) failed.") - } - if !validateDataIntegrity(k2, aesKeyLength) { - t.Fatal("key validation failed.") - } - if !bytes.Equal(k1, k2) { - t.Fatal("k1 != k2.") - } -} - -func TestExpiry(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - w.SetMinimumPowTest(0.0000001) - defer w.SetMinimumPowTest(DefaultMinimumPoW) - w.Start() - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - params.TTL = 1 - - messagesCount := 5 - - // Send a few messages one after another. Due to low PoW and expiration buckets - // with one second resolution, it covers a case when there are multiple items - // in a single expiration bucket. - for i := 0; i < messagesCount; i++ { - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - err = w.Send(env) - if err != nil { - t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) - } - } - - // wait till received or timeout - var received, expired bool - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - for j := 0; j < 20; j++ { - <-ticker.C - if len(w.Envelopes()) == messagesCount { - received = true - break - } - } - - if !received { - t.Fatalf("did not receive the sent envelope, seed: %d.", seed) - } - - // wait till expired or timeout - for j := 0; j < 20; j++ { - <-ticker.C - if len(w.Envelopes()) == 0 { - expired = true - break - } - } - - if !expired { - t.Fatalf("expire failed, seed: %d.", seed) - } -} - -func TestCustomization(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - defer w.SetMinimumPowTest(DefaultMinimumPoW) - defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start() - - const smallPoW = 0.00001 - - f, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - params.KeySym = f.KeySym - params.Topic = BytesToTopic(f.Topics[2]) - params.PoW = smallPoW - params.TTL = 3600 * 24 // one day - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - err = w.Send(env) - if err == nil { - t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed) - } - - w.SetMinimumPowTest(smallPoW / 2) - err = w.Send(env) - if err != nil { - t.Fatalf("failed to send envelope with seed %d: %s.", seed, err) - } - - params.TTL++ - msg, err = NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err = msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - w.SetMaxMessageSize(uint32(env.size() - 1)) - err = w.Send(env) - if err == nil { - t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed) - } - - w.SetMaxMessageSize(DefaultMaxMessageSize) - err = w.Send(env) - if err != nil { - t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err) - } - - // wait till received or timeout - var received bool - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - for j := 0; j < 20; j++ { - <-ticker.C - if len(w.Envelopes()) > 1 { - received = true - break - } - } - - if !received { - t.Fatalf("did not receive the sent envelope, seed: %d.", seed) - } - - // check w.messages() - _, err = w.Subscribe(f) - if err != nil { - t.Fatalf("failed subscribe with seed %d: %s.", seed, err) - } - <-ticker.C - mail := f.Retrieve() - if len(mail) > 0 { - t.Fatalf("received premature mail. mail=%v", mail) - } -} - -func TestSymmetricSendCycle(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - defer w.SetMinimumPowTest(DefaultMinimumPoW) - defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start() - - filter1, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - filter1.PoW = DefaultMinimumPoW - - // Copy the first filter since some of its fields - // are randomly gnerated. - filter2 := &Filter{ - KeySym: filter1.KeySym, - Topics: filter1.Topics, - PoW: filter1.PoW, - AllowP2P: filter1.AllowP2P, - Messages: make(map[common.Hash]*ReceivedMessage), - } - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - filter1.Src = ¶ms.Src.PublicKey - filter2.Src = ¶ms.Src.PublicKey - - params.KeySym = filter1.KeySym - params.Topic = BytesToTopic(filter1.Topics[2]) - params.PoW = filter1.PoW - params.WorkTime = 10 - params.TTL = 50 - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - _, err = w.Subscribe(filter1) - if err != nil { - t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err) - } - - _, err = w.Subscribe(filter2) - if err != nil { - t.Fatalf("failed subscribe 2 with seed %d: %s.", seed, err) - } - - err = w.Send(env) - if err != nil { - t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err) - } - - // wait till received or timeout - var received bool - ticker := time.NewTicker(10 * time.Millisecond) - defer ticker.Stop() - for j := 0; j < 200; j++ { - <-ticker.C - if len(w.Envelopes()) > 0 { - received = true - break - } - } - - if !received { - t.Fatalf("did not receive the sent envelope, seed: %d.", seed) - } - - // check w.messages() - <-ticker.C - mail1 := filter1.Retrieve() - mail2 := filter2.Retrieve() - if len(mail2) == 0 { - t.Fatal("did not receive any email for filter 2.") - } - if len(mail1) == 0 { - t.Fatal("did not receive any email for filter 1.") - } - -} - -func TestSymmetricSendWithoutAKey(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - defer w.SetMinimumPowTest(DefaultMinimumPoW) - defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start() - - filter, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - filter.PoW = DefaultMinimumPoW - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - filter.Src = nil - - params.KeySym = filter.KeySym - params.Topic = BytesToTopic(filter.Topics[2]) - params.PoW = filter.PoW - params.WorkTime = 10 - params.TTL = 50 - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - _, err = w.Subscribe(filter) - if err != nil { - t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err) - } - - err = w.Send(env) - if err != nil { - t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err) - } - - // wait till received or timeout - var received bool - ticker := time.NewTicker(10 * time.Millisecond) - defer ticker.Stop() - for j := 0; j < 200; j++ { - <-ticker.C - if len(w.Envelopes()) > 0 { - received = true - break - } - } - - if !received { - t.Fatalf("did not receive the sent envelope, seed: %d.", seed) - } - - // check w.messages() - <-ticker.C - mail := filter.Retrieve() - if len(mail) == 0 { - t.Fatal("did not receive message in spite of not setting a public key") - } -} - -func TestSymmetricSendKeyMismatch(t *testing.T) { - InitSingleTest() - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - defer w.SetMinimumPowTest(DefaultMinimumPoW) - defer w.SetMaxMessageSize(DefaultMaxMessageSize) - w.Start() - - filter, err := generateFilter(t, true) - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - filter.PoW = DefaultMinimumPoW - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) - } - - params.KeySym = filter.KeySym - params.Topic = BytesToTopic(filter.Topics[2]) - params.PoW = filter.PoW - params.WorkTime = 10 - params.TTL = 50 - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d: %s.", seed, err) - } - - _, err = w.Subscribe(filter) - if err != nil { - t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err) - } - - err = w.Send(env) - if err != nil { - t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err) - } - - // wait till received or timeout - var received bool - ticker := time.NewTicker(10 * time.Millisecond) - defer ticker.Stop() - for j := 0; j < 200; j++ { - <-ticker.C - if len(w.Envelopes()) > 0 { - received = true - break - } - } - - if !received { - t.Fatalf("did not receive the sent envelope, seed: %d.", seed) - } - - // check w.messages() - <-ticker.C - mail := filter.Retrieve() - if len(mail) > 0 { - t.Fatalf("received a message when keys weren't matching. message=%v", mail) - } -} - -func TestBloom(t *testing.T) { - topic := TopicType{0, 0, 255, 6} - b := TopicToBloom(topic) - x := make([]byte, BloomFilterSize) - x[0] = byte(1) - x[32] = byte(1) - x[BloomFilterSize-1] = byte(128) - if !BloomFilterMatch(x, b) || !BloomFilterMatch(b, x) { - t.Fatal("bloom filter does not match the mask") - } - - _, err := mrand.Read(b) - if err != nil { - t.Fatalf("math rand error. err=%v", err) - } - _, err = mrand.Read(x) - if err != nil { - t.Fatalf("math rand error. err=%v", err) - } - if !BloomFilterMatch(b, b) { - t.Fatal("bloom filter does not match self") - } - x = addBloom(x, b) - if !BloomFilterMatch(x, b) { - t.Fatal("bloom filter does not match combined bloom") - } - if !isFullNode(nil) { - t.Fatal("isFullNode did not recognize nil as full node") - } - x[17] = 254 - if isFullNode(x) { - t.Fatal("isFullNode false positive") - } - for i := 0; i < BloomFilterSize; i++ { - b[i] = byte(255) - } - if !isFullNode(b) { - t.Fatal("isFullNode false negative") - } - if BloomFilterMatch(x, b) { - t.Fatal("bloomFilterMatch false positive") - } - if !BloomFilterMatch(b, x) { - t.Fatal("bloomFilterMatch false negative") - } - - stack, w := newNodeWithWhisper(t) - defer stack.Close() - - f := w.BloomFilter() - if f != nil { - t.Fatal("wrong bloom on creation") - } - err = w.SetBloomFilter(x) - if err != nil { - t.Fatalf("failed to set bloom filter: %v", err) - } - f = w.BloomFilter() - if !BloomFilterMatch(f, x) || !BloomFilterMatch(x, f) { - t.Fatal("retireved wrong bloom filter") - } -} - -// newNodeWithWhisper creates a new node using a default config and -// creates and registers a new Whisper service on it. -func newNodeWithWhisper(t *testing.T) (*node.Node, *Whisper) { - stack, err := node.New(&node.DefaultConfig) - if err != nil { - t.Fatalf("could not create new node: %v", err) - } - w, err := New(stack, &DefaultConfig) - if err != nil { - t.Fatalf("could not create new whisper service: %v", err) - } - err = stack.Start() - if err != nil { - t.Fatalf("could not start node: %v", err) - } - return stack, w -} From 8327d1fdfc42b0d73520fe1af1e875b960f03ee1 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 8 Sep 2020 13:07:55 +0200 Subject: [PATCH 005/948] accounts/usbwallet, signer/core: show accounts from ledger legacy derivation paths (#21517) * accounts/usbwallet, signer/core: un-hide accounts from ledger legacy derivation paths * Update accounts/usbwallet/wallet.go * Update signer/core/api.go * Update signer/core/api.go --- accounts/usbwallet/wallet.go | 14 +++++++++----- signer/core/api.go | 33 +++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index ee539d9653..993c599346 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -368,18 +368,22 @@ func (w *wallet) selfDerive() { w.log.Warn("USB wallet nonce retrieval failed", "err", err) break } - // If the next account is empty, stop self-derivation, but add for the last base path + // We've just self-derived a new account, start tracking it locally + // unless the account was empty. + path := make(accounts.DerivationPath, len(nextPaths[i])) + copy(path[:], nextPaths[i][:]) if balance.Sign() == 0 && nonce == 0 { empty = true + // If it indeed was empty, make a log output for it anyway. In the case + // of legacy-ledger, the first account on the legacy-path will + // be shown to the user, even if we don't actively track it if i < len(nextAddrs)-1 { + w.log.Info("Skipping trakcking first account on legacy path, use personal.deriveAccount(,, false) to track", + "path", path, "address", nextAddrs[i]) break } } - // We've just self-derived a new account, start tracking it locally - path := make(accounts.DerivationPath, len(nextPaths[i])) - copy(path[:], nextPaths[i][:]) paths = append(paths, path) - account := accounts.Account{ Address: nextAddrs[i], URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, diff --git a/signer/core/api.go b/signer/core/api.go index 7e6ece997f..3817345c8f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -346,19 +346,28 @@ func (api *SignerAPI) startUSBListener() { case accounts.WalletOpened: status, _ := event.Wallet.Status() log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) - - // Derive first N accounts, hardcoded for now - var nextPath = make(accounts.DerivationPath, len(accounts.DefaultBaseDerivationPath)) - copy(nextPath[:], accounts.DefaultBaseDerivationPath[:]) - - for i := 0; i < numberOfAccountsToDerive; i++ { - acc, err := event.Wallet.Derive(nextPath, true) - if err != nil { - log.Warn("account derivation failed", "error", err) - } else { - log.Info("derived account", "address", acc.Address) + var derive = func(numToDerive int, base accounts.DerivationPath) { + // Derive first N accounts, hardcoded for now + var nextPath = make(accounts.DerivationPath, len(base)) + copy(nextPath[:], base[:]) + + for i := 0; i < numToDerive; i++ { + acc, err := event.Wallet.Derive(nextPath, true) + if err != nil { + log.Warn("Account derivation failed", "error", err) + } else { + log.Info("Derived account", "address", acc.Address, "path", nextPath) + } + nextPath[len(nextPath)-1]++ } - nextPath[len(nextPath)-1]++ + } + if event.Wallet.URL().Scheme == "ledger" { + log.Info("Deriving ledger default paths") + derive(numberOfAccountsToDerive/2, accounts.DefaultBaseDerivationPath) + log.Info("Deriving ledger legacy paths") + derive(numberOfAccountsToDerive/2, accounts.LegacyLedgerBaseDerivationPath) + } else { + derive(numberOfAccountsToDerive, accounts.DefaultBaseDerivationPath) } case accounts.WalletDropped: log.Info("Old wallet dropped", "url", event.Wallet.URL()) From 066c75531d5668366dc0cf9a8a2d9cb2addbc487 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 8 Sep 2020 14:13:48 +0000 Subject: [PATCH 006/948] build: remove wnode from the list of packages binaries (#21526) --- build/ci.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build/ci.go b/build/ci.go index ae5aaf5ac8..ea708d5e7b 100644 --- a/build/ci.go +++ b/build/ci.go @@ -79,7 +79,6 @@ var ( executablePath("geth"), executablePath("puppeth"), executablePath("rlpdump"), - executablePath("wnode"), executablePath("clef"), } @@ -109,10 +108,6 @@ var ( BinaryName: "rlpdump", Description: "Developer utility tool that prints RLP structures.", }, - { - BinaryName: "wnode", - Description: "Ethereum Whisper diagnostic tool", - }, { BinaryName: "clef", Description: "Ethereum account management tool.", From 86bcbb0d79859266d4ce69ee99408aaff6018830 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 8 Sep 2020 20:02:14 +0000 Subject: [PATCH 007/948] .github: remove whisper from CODEOWNERS (#21527) --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 59e73396a6..58c1a4a62e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,4 +20,3 @@ p2p/simulations @fjl p2p/protocols @fjl p2p/testing @fjl signer/ @holiman -whisper/ @gballet From dc681fc1f6a183a476446167695e5e6fa0e5d4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 9 Sep 2020 10:33:20 +0300 Subject: [PATCH 008/948] params: update CHTs for v1.9.21 release --- params/config.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/params/config.go b/params/config.go index c935405e7f..e5ec64b2bf 100644 --- a/params/config.go +++ b/params/config.go @@ -73,10 +73,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 326, - SectionHead: common.HexToHash("0xbdec9f7056159360d64d6488ee11a0db574a67757cddd6fffd6719121d5733a5"), - CHTRoot: common.HexToHash("0xf9d2617f8e038b824a256025f01af3b3da681987df29dbfe718ad4c6c8a0875d"), - BloomRoot: common.HexToHash("0x712016984cfb66c165fdaf05c6a4aa89f08e4bb66fa77b199f2878fff4232d78"), + SectionIndex: 329, + SectionHead: common.HexToHash("0x96bb6d286ded20a18480dd98d537ab503bd81110c6b9c3f8ad1f9338f3b9852d"), + CHTRoot: common.HexToHash("0x10627ff648077adeaab9dbd4e5bbed8671c86005b2aef5f5d4857acca19a49d8"), + BloomRoot: common.HexToHash("0xf499b0cfaf426a490b7b5ddca58d3031b008f0c15338f8f25c20f3df050bf785"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -112,10 +112,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 260, - SectionHead: common.HexToHash("0xdcf714d033b8be3f0786515649d76e526157f811e5ae89c59dbfd53029d0d165"), - CHTRoot: common.HexToHash("0x987759454d404cd393a6a7743da64610076f167e989ec2cf9e0c0be6578d1304"), - BloomRoot: common.HexToHash("0xb8ee6d34cc30d61410717e2dc1af3294bc056f4b32a5eed5f6f386a8c1daa2b1"), + SectionIndex: 262, + SectionHead: common.HexToHash("0x12b068f285789b966a983b632266484f1bc93803df6c78773538a5777f57a236"), + CHTRoot: common.HexToHash("0x14000a1407e866f174f3a20fe9f271acd704bcf929b5205d83b70a1bba8c82c2"), + BloomRoot: common.HexToHash("0x2f4f4a34a55e35d0691c79a79e39b6f661259345080fb880da5195c11c2413be"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -154,10 +154,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 214, - SectionHead: common.HexToHash("0x297b4daf21db636e76555c9d3e302d79a8efe3a3434143b9bcf61187ce8abcb1"), - CHTRoot: common.HexToHash("0x602044234a4ba8534286240200cde6e5797ae40151cbdd2dbf8eb8c0486a2c63"), - BloomRoot: common.HexToHash("0x9ccf6840ecc541b290c7b9f19edcba3e5f39206b05cd4ae5a7754040783d47d9"), + SectionIndex: 217, + SectionHead: common.HexToHash("0x9afa4900a60cb44b102eb2eb5e5ef1d7f4cc1911c1c0588518995fb778ffe894"), + CHTRoot: common.HexToHash("0xcc963e5085622c7cb6b3bf747fbfdfe71887e0d5bc9e4b3fb0474d44fc97942a"), + BloomRoot: common.HexToHash("0x1064ca3a36b6f129783cff51bb18fb038bade47d2b776d1cccb9c74925106703"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -194,10 +194,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 99, - SectionHead: common.HexToHash("0xc9f09369acd657d5f77e6a389a68f673bf909ad98c269800c08229d75c1a90e3"), - CHTRoot: common.HexToHash("0x523218630348e98fa9f4e7fc3054aff717982d79c700cbecf5730c1479f21c6e"), - BloomRoot: common.HexToHash("0x75219ad4a3ec4682b89dd248ee56b52ef26fe577a426f4813297550deb5c4cb2"), + SectionIndex: 101, + SectionHead: common.HexToHash("0x396f5dd8e526edfb550873bcfe0e93dc00d70be4b881ab256980833b97a18c3e"), + CHTRoot: common.HexToHash("0x0d145657a6595508ef878c9bbf8eca045631986f664bfab0d898fc64804a4e64"), + BloomRoot: common.HexToHash("0x12df34d07cf1268abe22d40ee6deb199b8918e3d57d52f9e70f9b2883f57d74f"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From 0287d54847d3297f3ced62cd83a4c95ccbe0045b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 9 Sep 2020 11:22:11 +0300 Subject: [PATCH 009/948] params: release Geth v1.9.21 --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index f827bcd62e..4a0e2f0c9b 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 21 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 21 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 8d35b1eb2b4b844b45cf504cc05704b1a3004e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 9 Sep 2020 11:23:37 +0300 Subject: [PATCH 010/948] params: begin v1.9.22 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 4a0e2f0c9b..521efe15f5 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 21 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 22 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 367f12f73495b30c17c7d358cf71575f34f79757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 9 Sep 2020 14:06:32 +0300 Subject: [PATCH 011/948] eth/downloader: only roll back light sync if not fully validating --- eth/downloader/downloader.go | 16 +++++++++------- eth/downloader/downloader_test.go | 7 +++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 9c19543a46..df3b6bdb68 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -1501,18 +1501,20 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er rollbackErr = err // If some headers were inserted, track them as uncertain - if n > 0 && rollback == 0 { + if (mode == FastSync || frequency > 1) && n > 0 && rollback == 0 { rollback = chunk[0].Number.Uint64() } - log.Debug("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "parent", chunk[n].ParentHash, "err", err) + log.Warn("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "parent", chunk[n].ParentHash, "err", err) return fmt.Errorf("%w: %v", errInvalidChain, err) } // All verifications passed, track all headers within the alloted limits - head := chunk[len(chunk)-1].Number.Uint64() - if head-rollback > uint64(fsHeaderSafetyNet) { - rollback = head - uint64(fsHeaderSafetyNet) - } else { - rollback = 1 + if mode == FastSync { + head := chunk[len(chunk)-1].Number.Uint64() + if head-rollback > uint64(fsHeaderSafetyNet) { + rollback = head - uint64(fsHeaderSafetyNet) + } else { + rollback = 1 + } } } // Unless we're doing light chains, schedule the headers for associated content retrieval diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 51d485761b..5400049cc2 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -1020,10 +1020,9 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { // Tests that upon detecting an invalid header, the recent ones are rolled back // for various failure scenarios. Afterwards a full sync is attempted to make // sure no state was corrupted. -func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) } -func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } -func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } -func TestInvalidHeaderRollback65Light(t *testing.T) { testInvalidHeaderRollback(t, 65, LightSync) } +func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) } +func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } +func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { t.Parallel() From d81c9d9b76f4255a258c1a6d93b3392ac0e8fd5f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 9 Sep 2020 15:21:20 +0200 Subject: [PATCH 012/948] accounts/abi/bind/backends: reverted some stylistic changes (#21535) --- accounts/abi/bind/backends/simulated.go | 44 ++++++++++---------- accounts/abi/bind/backends/simulated_test.go | 6 +-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index a43cb94678..c7efca440b 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -195,7 +195,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres } // TransactionReceipt returns the receipt of a transaction. -func (b *SimulatedBackend) TransactionReceipt(_ context.Context, txHash common.Hash) (*types.Receipt, error) { +func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { b.mu.Lock() defer b.mu.Unlock() @@ -207,7 +207,7 @@ func (b *SimulatedBackend) TransactionReceipt(_ context.Context, txHash common.H // blockchain. The isPending return value indicates whether the transaction has been // mined yet. Note that the transaction may not be part of the canonical chain even if // it's not pending. -func (b *SimulatedBackend) TransactionByHash(_ context.Context, txHash common.Hash) (*types.Transaction, bool, error) { +func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { b.mu.Lock() defer b.mu.Unlock() @@ -223,7 +223,7 @@ func (b *SimulatedBackend) TransactionByHash(_ context.Context, txHash common.Ha } // BlockByHash retrieves a block based on the block hash. -func (b *SimulatedBackend) BlockByHash(_ context.Context, hash common.Hash) (*types.Block, error) { +func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { b.mu.Lock() defer b.mu.Unlock() @@ -250,7 +250,7 @@ func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) ( // blockByNumberNoLock retrieves a block from the database by number, caching it // (associated with its hash) if found without Lock. -func (b *SimulatedBackend) blockByNumberNoLock(_ context.Context, number *big.Int) (*types.Block, error) { +func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big.Int) (*types.Block, error) { if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { return b.blockchain.CurrentBlock(), nil } @@ -264,7 +264,7 @@ func (b *SimulatedBackend) blockByNumberNoLock(_ context.Context, number *big.In } // HeaderByHash returns a block header from the current canonical chain. -func (b *SimulatedBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { +func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { b.mu.Lock() defer b.mu.Unlock() @@ -282,7 +282,7 @@ func (b *SimulatedBackend) HeaderByHash(_ context.Context, hash common.Hash) (*t // HeaderByNumber returns a block header from the current canonical chain. If number is // nil, the latest known header is returned. -func (b *SimulatedBackend) HeaderByNumber(_ context.Context, block *big.Int) (*types.Header, error) { +func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) { b.mu.Lock() defer b.mu.Unlock() @@ -294,7 +294,7 @@ func (b *SimulatedBackend) HeaderByNumber(_ context.Context, block *big.Int) (*t } // TransactionCount returns the number of transactions in a given block. -func (b *SimulatedBackend) TransactionCount(_ context.Context, blockHash common.Hash) (uint, error) { +func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { b.mu.Lock() defer b.mu.Unlock() @@ -311,7 +311,7 @@ func (b *SimulatedBackend) TransactionCount(_ context.Context, blockHash common. } // TransactionInBlock returns the transaction for a specific block at a specific index. -func (b *SimulatedBackend) TransactionInBlock(_ context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { +func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { b.mu.Lock() defer b.mu.Unlock() @@ -338,7 +338,7 @@ func (b *SimulatedBackend) TransactionInBlock(_ context.Context, blockHash commo } // PendingCodeAt returns the code associated with an account in the pending state. -func (b *SimulatedBackend) PendingCodeAt(_ context.Context, contract common.Address) ([]byte, error) { +func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { b.mu.Lock() defer b.mu.Unlock() @@ -417,7 +417,7 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving // the nonce currently pending for the account. -func (b *SimulatedBackend) PendingNonceAt(_ context.Context, account common.Address) (uint64, error) { +func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { b.mu.Lock() defer b.mu.Unlock() @@ -426,7 +426,7 @@ func (b *SimulatedBackend) PendingNonceAt(_ context.Context, account common.Addr // SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated // chain doesn't have miners, we just return a gas price of 1 for any call. -func (b *SimulatedBackend) SuggestGasPrice(_ context.Context) (*big.Int, error) { +func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { return big.NewInt(1), nil } @@ -525,7 +525,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs // callContract implements common code between normal and pending contract calls. // state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) { +func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) { // Ensure message is initialized properly. if call.GasPrice == nil { call.GasPrice = big.NewInt(1) @@ -553,7 +553,7 @@ func (b *SimulatedBackend) callContract(_ context.Context, call ethereum.CallMsg // SendTransaction updates the pending block to include the given transaction. // It panics if the transaction is invalid. -func (b *SimulatedBackend) SendTransaction(_ context.Context, tx *types.Transaction) error { +func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { b.mu.Lock() defer b.mu.Unlock() @@ -615,7 +615,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter // SubscribeFilterLogs creates a background log filtering operation, returning a // subscription immediately, which can be used to stream the found events. -func (b *SimulatedBackend) SubscribeFilterLogs(_ context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { +func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { // Subscribe to contract events sink := make(chan []*types.Log) @@ -648,7 +648,7 @@ func (b *SimulatedBackend) SubscribeFilterLogs(_ context.Context, query ethereum } // SubscribeNewHead returns an event subscription for a new header. -func (b *SimulatedBackend) SubscribeNewHead(_ context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { +func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { // subscribe to a new head sink := make(chan *types.Header) sub := b.events.SubscribeNewHeads(sink) @@ -724,18 +724,18 @@ type filterBackend struct { func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") } -func (fb *filterBackend) HeaderByNumber(_ context.Context, block rpc.BlockNumber) (*types.Header, error) { +func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) { if block == rpc.LatestBlockNumber { return fb.bc.CurrentHeader(), nil } return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil } -func (fb *filterBackend) HeaderByHash(_ context.Context, hash common.Hash) (*types.Header, error) { +func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { return fb.bc.GetHeaderByHash(hash), nil } -func (fb *filterBackend) GetReceipts(_ context.Context, hash common.Hash) (types.Receipts, error) { +func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { number := rawdb.ReadHeaderNumber(fb.db, hash) if number == nil { return nil, nil @@ -743,7 +743,7 @@ func (fb *filterBackend) GetReceipts(_ context.Context, hash common.Hash) (types return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil } -func (fb *filterBackend) GetLogs(_ context.Context, hash common.Hash) ([][]*types.Log, error) { +func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { number := rawdb.ReadHeaderNumber(fb.db, hash) if number == nil { return nil, nil @@ -759,7 +759,7 @@ func (fb *filterBackend) GetLogs(_ context.Context, hash common.Hash) ([][]*type return logs, nil } -func (fb *filterBackend) SubscribeNewTxsEvent(_ chan<- core.NewTxsEvent) event.Subscription { +func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { return nullSubscription() } @@ -775,13 +775,13 @@ func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscr return fb.bc.SubscribeLogsEvent(ch) } -func (fb *filterBackend) SubscribePendingLogsEvent(_ chan<- []*types.Log) event.Subscription { +func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { return nullSubscription() } func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } -func (fb *filterBackend) ServiceFilter(_ context.Context, _ *bloombits.MatcherSession) { +func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) { panic("not supported") } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 2dffe26f6e..e2597cca01 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -1086,12 +1086,12 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) { t.Errorf("result from %v was not nil: %v", key, res) } if val != nil { - rErr, ok := err.(*revertError) + rerr, ok := err.(*revertError) if !ok { t.Errorf("expect revert error") } - if rErr.Error() != "execution reverted: "+val.(string) { - t.Errorf("error was malformed: got %v want %v", rErr.Error(), val) + if rerr.Error() != "execution reverted: "+val.(string) { + t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) } } else { // revert(0x0,0x0) From 328901c24cdcbfe57ad983248b2fd481f495db3d Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 9 Sep 2020 23:38:47 +0800 Subject: [PATCH 013/948] cmd, eth: offer maxprice flag for overwritting price cap (#21531) * cmd, eth: offer maxprice flag for overwritting price cap * eth: rename default price cap --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 8 ++++++++ eth/config.go | 2 ++ eth/gasprice/gasprice.go | 18 +++++++++++++++--- 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ccc6358996..677a19eed2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -154,6 +154,7 @@ var ( utils.LegacyGpoBlocksFlag, utils.GpoPercentileFlag, utils.LegacyGpoPercentileFlag, + utils.GpoMaxGasPriceFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, configFileFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 5e004c42be..334a729c24 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -189,6 +189,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Flags: []cli.Flag{ utils.GpoBlocksFlag, utils.GpoPercentileFlag, + utils.GpoMaxGasPriceFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e6382c1634..2c57e533ed 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -631,6 +631,11 @@ var ( Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices", Value: eth.DefaultConfig.GPO.Percentile, } + GpoMaxGasPriceFlag = cli.Int64Flag{ + Name: "gpo.maxprice", + Usage: "Maximum gas price will be recommended by gpo", + Value: eth.DefaultConfig.GPO.MaxPrice.Int64(), + } WhisperEnabledFlag = cli.BoolFlag{ Name: "shh", Usage: "Enable Whisper", @@ -1291,6 +1296,9 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { if ctx.GlobalIsSet(GpoPercentileFlag.Name) { cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name) } + if ctx.GlobalIsSet(GpoMaxGasPriceFlag.Name) { + cfg.MaxPrice = big.NewInt(ctx.GlobalInt64(GpoMaxGasPriceFlag.Name)) + } } func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { diff --git a/eth/config.go b/eth/config.go index 1074e5a371..0d99c2a3f1 100644 --- a/eth/config.go +++ b/eth/config.go @@ -37,12 +37,14 @@ import ( var DefaultFullGPOConfig = gasprice.Config{ Blocks: 20, Percentile: 60, + MaxPrice: gasprice.DefaultMaxPrice, } // DefaultLightGPOConfig contains default gasprice oracle settings for light client. var DefaultLightGPOConfig = gasprice.Config{ Blocks: 2, Percentile: 60, + MaxPrice: gasprice.DefaultMaxPrice, } // DefaultConfig contains default settings for use on the Ethereum main net. diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index b9eff3d11a..5d8be08e0b 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -24,18 +24,20 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) const sampleNumber = 3 // Number of transactions sampled in a block -var maxPrice = big.NewInt(500 * params.GWei) +var DefaultMaxPrice = big.NewInt(500 * params.GWei) type Config struct { Blocks int Percentile int Default *big.Int `toml:",omitempty"` + MaxPrice *big.Int `toml:",omitempty"` } // OracleBackend includes all necessary background APIs for oracle. @@ -51,6 +53,7 @@ type Oracle struct { backend OracleBackend lastHead common.Hash lastPrice *big.Int + maxPrice *big.Int cacheLock sync.RWMutex fetchLock sync.Mutex @@ -64,17 +67,26 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { blocks := params.Blocks if blocks < 1 { blocks = 1 + log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks) } percent := params.Percentile if percent < 0 { percent = 0 + log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) } if percent > 100 { percent = 100 + log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent) + } + maxPrice := params.MaxPrice + if maxPrice == nil || maxPrice.Int64() <= 0 { + maxPrice = DefaultMaxPrice + log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) } return &Oracle{ backend: backend, lastPrice: params.Default, + maxPrice: maxPrice, checkBlocks: blocks, percentile: percent, } @@ -146,8 +158,8 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { sort.Sort(bigIntArray(txPrices)) price = txPrices[(len(txPrices)-1)*gpo.percentile/100] } - if price.Cmp(maxPrice) > 0 { - price = new(big.Int).Set(maxPrice) + if price.Cmp(gpo.maxPrice) > 0 { + price = new(big.Int).Set(gpo.maxPrice) } gpo.cacheLock.Lock() gpo.lastHead = headHash From a3cd8a040a4ffe04a7b455f11a6c049a9b29b1c5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 10 Sep 2020 09:19:30 +0200 Subject: [PATCH 014/948] core/vm: fix benchmark overflow + prep for precompile repricings (#21530) * core/vm/testdata: add gascost expectations to testcases * core/vm: verify expected gas in tests for precompiles * core/vm: fix overflow flaw in gas/s calculation --- core/vm/contracts_test.go | 12 +- core/vm/testdata/precompiles/blake2F.json | 5 + core/vm/testdata/precompiles/blsG1Add.json | 104 +++++++++++++++++ core/vm/testdata/precompiles/blsG1Mul.json | 106 +++++++++++++++++- .../testdata/precompiles/blsG1MultiExp.json | 103 +++++++++++++++++ core/vm/testdata/precompiles/blsG2Add.json | 104 +++++++++++++++++ core/vm/testdata/precompiles/blsG2Mul.json | 104 +++++++++++++++++ .../testdata/precompiles/blsG2MultiExp.json | 103 +++++++++++++++++ core/vm/testdata/precompiles/blsMapG1.json | 100 +++++++++++++++++ core/vm/testdata/precompiles/blsMapG2.json | 100 +++++++++++++++++ core/vm/testdata/precompiles/blsPairing.json | 100 +++++++++++++++++ core/vm/testdata/precompiles/bn256Add.json | 16 +++ .../vm/testdata/precompiles/bn256Pairing.json | 14 +++ .../testdata/precompiles/bn256ScalarMul.json | 18 +++ core/vm/testdata/precompiles/ecRecover.json | 5 + core/vm/testdata/precompiles/modexp.json | 17 +++ 16 files changed, 1007 insertions(+), 4 deletions(-) diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 5bc365949d..6320875e1a 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -30,6 +30,7 @@ import ( // precompiledTest defines the input/output pairs for precompiled contract tests. type precompiledTest struct { Input, Expected string + Gas uint64 Name string NoBenchmark bool // Benchmark primarily the worst-cases } @@ -78,6 +79,9 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) } + if expGas := test.Gas; expGas != gas { + t.Errorf("%v: gas wrong, expected %d, got %d", test.Name, expGas, gas) + } // Verify that the precompile did not touch the input buffer exp := common.Hex2Bytes(test.Input) if !bytes.Equal(in, exp) { @@ -137,20 +141,22 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) { bench.ReportAllocs() - start := time.Now().Nanosecond() + start := time.Now() bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) res, _, err = RunPrecompiledContract(p, data, reqGas) } bench.StopTimer() - elapsed := float64(time.Now().Nanosecond() - start) + elapsed := uint64(time.Since(start)) if elapsed < 1 { elapsed = 1 } gasUsed := reqGas * uint64(bench.N) bench.ReportMetric(float64(reqGas), "gas/op") - bench.ReportMetric(float64(gasUsed*1000)/elapsed, "mgas/s") + // Keep it as uint64, multiply 100 to get two digit float later + mgasps := (100 * 1000 * gasUsed) / elapsed + bench.ReportMetric(float64(mgasps)/100, "mgas/s") //Check if it is correct if err != nil { bench.Error(err) diff --git a/core/vm/testdata/precompiles/blake2F.json b/core/vm/testdata/precompiles/blake2F.json index 0fce477ba6..a25f9ae501 100644 --- a/core/vm/testdata/precompiles/blake2F.json +++ b/core/vm/testdata/precompiles/blake2F.json @@ -3,30 +3,35 @@ "Input": "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", "Expected": "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", "Name": "vector 4", + "Gas": 0, "NoBenchmark": false }, { "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", "Expected": "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", "Name": "vector 5", + "Gas": 12, "NoBenchmark": false }, { "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", "Expected": "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", "Name": "vector 6", + "Gas": 12, "NoBenchmark": false }, { "Input": "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", "Expected": "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", "Name": "vector 7", + "Gas": 1, "NoBenchmark": false }, { "Input": "007A120048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", "Expected": "6d2ce9e534d50e18ff866ae92d70cceba79bbcd14c63819fe48752c8aca87a4bb7dcc230d22a4047f0486cfcfb50a17b24b2899eb8fca370f22240adb5170189", "Name": "vector 8", + "Gas": 8000000, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG1Add.json b/core/vm/testdata/precompiles/blsG1Add.json index 95b78f3ecd..184d765aa1 100644 --- a/core/vm/testdata/precompiles/blsG1Add.json +++ b/core/vm/testdata/precompiles/blsG1Add.json @@ -3,624 +3,728 @@ "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28", "Name": "bls_g1add_(g1+g1=2*g1)", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d280000000000000000000000000000000009ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e522400000000000000000000000000000000032b80d3a6f5b09f8a84623389c5f80ca69a0cddabc3097f9d9c27310fd43be6e745256c634af45ca3473b0590ae30d1", "Expected": "0000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e2", "Name": "bls_g1add_(2*g1+3*g1=5*g1)", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1", "Name": "bls_g1add_(inf+g1=g1)", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g1add_(inf+inf=inf)", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7", "Expected": "000000000000000000000000000000001466e1373ae4a7e7ba885c5f0c3ccfa48cdb50661646ac6b779952f466ac9fc92730dcaed9be831cd1f8c4fefffd5209000000000000000000000000000000000c1fb750d2285d4ca0378e1e8cdbf6044151867c34a711b73ae818aee6dbe9e886f53d7928cc6ed9c851e0422f609b11", "Name": "matter_g1_add_0", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95", "Expected": "0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6", "Name": "matter_g1_add_1", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f00000000000000000000000000000000114c3f11ba0b47551fa28f09f148936d6b290dc9f2d0534a83c32b0b849ab921ce6bcaa4ff3c917707798d9c74f2084f00000000000000000000000000000000149dc028207fb04a7795d94ea65e21f9952e445000eb954531ee519efde6901675d3d2446614d243efb77a9cfe0ca3ae", "Expected": "0000000000000000000000000000000002ce7a08719448494857102da464bc65a47c95c77819af325055a23ac50b626df4732daf63feb9a663d71b7c9b8f2c510000000000000000000000000000000016117e87e9b55bd4bd5763d69d5240d30745e014b9aef87c498f9a9e3286ec4d5927df7cd5a2e54ac4179e78645acf27", "Name": "matter_g1_add_2", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000c3d564ac1fe12f18f528c3750583ab6af8973bff3eded7bb4778c32805d9b17846cc7c687af0f46bc87de7748ab72980000000000000000000000000000000002f164c131cbd5afc85692c246157d38dc4bbb2959d2edfa6daf0a8b17c7a898aad53b400e8bdc2b29bf6688ee863db7", "Expected": "0000000000000000000000000000000015510826f50b88fa369caf062ecdf8b03a67e660a35b219b44437a5583b5a9adf76991dce7bff9afc50257f847299504000000000000000000000000000000000a83e879895a1b47dbd6cd25ce8b719e7490cfe021614f7539e841fc2f9c09f071e386676de60b6579aa4bf6d37b13dd", "Name": "matter_g1_add_3", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d30000000000000000000000000000000019fe3a64361fea14936ff0b3e630471494d0c0b9423e6a004184a2965221c18849b5ed0eb2708a587323d8d6c6735a90000000000000000000000000000000000340823d314703e5efeb0a65c23069199d7dfff8793aaacb98cdcd6177fc8e61ab3294c57bf13b4406266715752ef3e6", "Expected": "00000000000000000000000000000000010b1c96d3910f56b0bf54da5ae8c7ab674a07f8143b61fed660e7309e626dc73eaa2b11886cdb82e2b6735e7802cc860000000000000000000000000000000002dabbbedd72872c2c012e7e893d2f3df1834c43873315488d814ddd6bfcca6758a18aa6bd02a0f3aed962cb51f0a222", "Name": "matter_g1_add_4", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90000000000000000000000000000000001461565b03a86df363d1854b4af74879115dffabeddfa879e2c8db9aa414fb291a076c3bdf0beee82d9c094ea8dc381a000000000000000000000000000000000e19d51ab619ee2daf25ea5bfa51eb217eabcfe0b5cb0358fd2fa105fd7cb0f5203816b990df6fda4e0e8d541be9bcf6", "Expected": "000000000000000000000000000000000cb40d0bf86a627d3973f1e7846484ffd0bc4943b42a54ff9527c285fed3c056b947a9b6115824cabafe13cd1af8181c00000000000000000000000000000000076255fc12f1a9dbd232025815238baaa6a3977fd87594e8d1606caec0d37b916e1e43ee2d2953d75a40a7ba416df237", "Name": "matter_g1_add_5", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b0000000000000000000000000000000019cabba3e09ad34cc3d125e0eb41b527aa48a4562c2b7637467b2dbc71c373897d50eed1bc75b2bde8904ece5626d6e400000000000000000000000000000000056b0746f820cff527358c86479dc924a10b9f7cae24cd495625a4159c8b71a8c3ad1a15ebf22d3561cd4b74e8a6e48b", "Expected": "000000000000000000000000000000000e115e0b61c1f1b25cc10a7b3bd21cf696b1433a0c366c2e1bca3c26b09482c6eced8c8ecfa69ce6b9b3b4419779262e00000000000000000000000000000000077b85daf61b9f947e81633e3bc64e697bc6c1d873f2c21e5c4c3a11302d4d5ef4c3ff5519564729aaf2a50a3c9f1196", "Name": "matter_g1_add_6", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a0000000000000000000000000000000011f649ee35ff8114060fc5e4df9ac828293f6212a9857ca31cb3e9ce49aa1212154a9808f1e763bc989b6d5ba7cf09390000000000000000000000000000000019af81eca7452f58c1a6e99fab50dc0d5eeebc7712153e717a14a31cffdfd0a923dbd585e652704a174905605a2e8b9d", "Expected": "000000000000000000000000000000000013e37a8950a659265b285c6fb56930fb77759d9d40298acac2714b97b83ec7692a7d1c4ccb83f074384db9eedd809c0000000000000000000000000000000003215d524d6419214568ba42a31502f2a58a97d0139c66908e9d71755f5a7666567aafe30ea84d89308f06768f28a648", "Name": "matter_g1_add_7", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000d713e148769fac2efd380886f8566c6d4662dd38317bb7e68744c4339efaedbab88435ce3dc289afaa7ecb37df37a5300000000000000000000000000000000129d9cd031b31c77a4e68093dcdbb585feba786207aa115d9cf120fe4f19ca31a0dca9c692bd0f53721d60a55c333129", "Expected": "00000000000000000000000000000000029405b9615e14bdac8b5666bbc5f3843d4bca17c97bed66d164f1b58d2a148f0f506d645d665a40e60d53fe29375ed400000000000000000000000000000000162761f1712814e474beb2289cc50519253d680699b530c2a6477f727ccc75a19681b82e490f441f91a3c611eeb0e9e2", "Name": "matter_g1_add_8", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000006d92bcb599edca426ff4ceeb154ebf133c2dea210c7db0441f74bd37c8d239149c8b5056ace0bfefb1db04b42664f530000000000000000000000000000000008522fc155eef6d5746283808091f91b427f2a96ac248850f9e3d7aadd14848101c965663fd4a63aea1153d71918435a", "Expected": "000000000000000000000000000000000cfaa8df9437c0b6f344a0c8dcbc7529a07aec0d7632ace89af6796b6b960b014f78dd10e987a993fb8a95cc909822ec0000000000000000000000000000000007475f115f6eb35f78ba9a2b71a44ccb6bbc1e980b8cd369c5c469565f3fb798bc907353cf47f524ba715deaedf379cb", "Name": "matter_g1_add_9", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac9430000000000000000000000000000000016380d03b7c5cc3301ffcb2cf7c28c9bde54fc22ba2b36ec293739d8eb674678c8e6461e34c1704747817c8f8341499a000000000000000000000000000000000ec6667aa5c6a769a64c180d277a341926376c39376480dc69fcad9a8d3b540238eb39d05aaa8e3ca15fc2c3ab696047", "Expected": "0000000000000000000000000000000011541d798b4b5069e2541fa5410dad03fd02784332e72658c7b0fa96c586142a967addc11a7a82bfcee33bd5d07066b900000000000000000000000000000000195b3fcb94ab7beb908208283b4e5d19c0af90fca4c76268f3c703859dea7d038aca976927f48839ebc7310869c724aa", "Name": "matter_g1_add_10", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e00000000000000000000000000000000065eb0770ab40199658bf87db6c6b52cd8c6c843a3e40dd60433d4d79971ff31296c9e00a5d553df7c81ade533379f4b0000000000000000000000000000000017a6f6137ddd90c15cf5e415f040260e15287d8d2254c6bfee88938caec9e5a048ff34f10607d1345ba1f09f30441ef4", "Expected": "0000000000000000000000000000000006b0853b3d41fc2d7b27da0bb2d6eb76be32530b59f8f537d227a6eb78364c7c0760447494a8bba69ef4b256dbef750200000000000000000000000000000000166e55ba2d20d94da474d4a085c14245147705e252e2a76ae696c7e37d75cde6a77fea738cef045182d5e628924dc0bb", "Name": "matter_g1_add_11", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000006a3f7eb0e42567210cc1ba5e6f8c42d02f1eef325b6483fef49ba186f59ab69ca2284715b736086d2a0a1f0ea224b40000000000000000000000000000000000bc08427fda31a6cfbe657a8c71c73894a33700e93e411d42f1471160c403b939b535070b68d60a4dc50e47493da63dc", "Expected": "000000000000000000000000000000000c35d4cd5d43e9cf52c15d46fef521666a1e1ab9f0b4a77b8e78882e9fab40f3f988597f202c5bd176c011a56a1887d4000000000000000000000000000000000ae2b5c24928a00c02daddf03fade45344f250dcf4c12eda06c39645b4d56147cb239d95b06fd719d4dc20fe332a6fce", "Name": "matter_g1_add_12", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a578768100000000000000000000000000000000068e79aea45b7199ec4b6f26e01e88ec76533743639ce76df66937fff9e7de3edf6700d227f10f43e073afcc63e2eddc00000000000000000000000000000000039c0b6d9e9681401aeb57a94cedc0709a0eff423ace9253eb00ae75e21cabeb626b52ef4368e6a4592aed9689c6fca4", "Expected": "0000000000000000000000000000000013bad27dafa20f03863454c30bd5ae6b202c9c7310875da302d4693fc1c2b78cca502b1ff851b183c4b2564c5d3eb4dc0000000000000000000000000000000000552b322b3d672704382b5d8b214c225b4f7868f9c5ae0766b7cdb181f97ed90a4892235915ffbc0daf3e14ec98a606", "Name": "matter_g1_add_13", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f7680000000000000000000000000000000000adac9bb98bb6f35a8f941dbff39dfd307b6a4d5756ccae103c814564e3d3993a8866ff91581ccdd7686c1dce0b19f700000000000000000000000000000000083d235e0579032ca47f65b6ae007ce8ffd2f1a890ce3bc45ebd0df6673ad530d2f42125d543cb0c51ba0c28345729d8", "Expected": "000000000000000000000000000000000b5513e42f5217490f395a8cb3673a4fc35142575f770af75ecf7a4fcd97eee215c4298fc4feab51915137cbdb814839000000000000000000000000000000000e9d4db04b233b0b12a7ff620faefef906aeb2b15481ce1609dad50eb6a7d0c09a850375599c501296219fb7b288e305", "Name": "matter_g1_add_14", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca", "Expected": "000000000000000000000000000000000c27ef4ebf37fd629370508f4cd062b74faa355b305d2ee60c7f4d67dd741363f18a7bbd368cdb17e848f372a5e33a6f0000000000000000000000000000000000ed833df28988944115502f554636e0b436cccf845341e21191e82d5b662482f32c24df492da4c605a0f9e0f8b00604", "Name": "matter_g1_add_15", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000091ee883cb9ea2c933f6645f0f4c535a826d95b6da6847b4fe2349342bd4bd496e0dd546df7a7a17a4b9fb8349e5064f000000000000000000000000000000000902d7e72242a5e6b068ca82d0cb71dc0f51335dbd302941045319f9a06777518b56a6e0b0b0c9fd8f1edf6b114ad331", "Expected": "00000000000000000000000000000000122cce99f623944dfebffcdf6b0a0a3696162f35053e5952dddc2537421c60da9fe931579d1c4fc2e31082b6c25f96b500000000000000000000000000000000011366ffa91dc0b7da8b7c1839ea84d49299310f5c1ca244012eed0dd363dbcf4ad5813b8e3fb49361ef05ea8cb18ffe", "Name": "matter_g1_add_16", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000000d3d4f11bc79b8425b77d25698b7e151d360ebb22c3a6afdb227de72fe432dcd6f0276b4fd3f1fcc2da5b59865053930000000000000000000000000000000015ac432071dc23148765f198ed7ea2234662745a96032c215cd9d7cf0ad8dafb8d52f209983fe98aaa2243ecc2073f1b", "Expected": "000000000000000000000000000000000113ccf11264ff04448f8c58b279a6a49acb386750c2051eab2c90fa8b8e03d7c5b9e87eccf36b4b3f79446b80be7b1d0000000000000000000000000000000004358a1fabfe803f4c787a671196b593981a837ee78587225fb21d5a883b98a15b912862763b94d18b971cb7e37dbcf0", "Name": "matter_g1_add_17", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb600000000000000000000000000000000034f725766897ed76394145da2f02c92c66794a51fd5ae07bd7cc60c013d7a48ebf1b07faf669dfed74d82d07e48d1150000000000000000000000000000000018f4926a3d0f740988da25379199ecb849250239ad7efcfef7ffaa43bc1373166c0448cc30dcdbd75ceb71f76f883ea7", "Expected": "00000000000000000000000000000000167336aeeb9e447348156936849d518faee314c291c84d732fa3c1bd3951559230d94230e37a08e28e689e9d1fef05770000000000000000000000000000000005366535f7a68996e066ab80c55bb372a15fb0ed6634585b88fe7cafbf818fbfebbf6f6ddd9ca0ff72137594a1e84b35", "Name": "matter_g1_add_18", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b00000000000000000000000000000000079e5a154cf84190b6c735bc8cd968559182166568649b813732e4fb4c5c428c8b38e8265d4ef04990c49aa1381f51c8000000000000000000000000000000000ae08e682ef92b4986a5ac5d4f094ad0919c826a97efe8d8120a96877766eae5828803804a0cae67df9822fd18622aae", "Expected": "000000000000000000000000000000000a3d66cf87b1ce8c5683d71a6de4bf829d094041240f56d9071aa84ff189a06940e8e1935127e23a970c78ca73c28bf6000000000000000000000000000000000b2adda87740873c0c59e3ebde44d33834773f0fe69e2f5e7ede99c4f928978a5caaede7262e45fd22136a394b3f7858", "Name": "matter_g1_add_19", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000008cefd0fd289d6964a962051c2c2ad98dab178612663548370dd5f007c5264fece368468d3ca8318a381b443c68c4cc7000000000000000000000000000000000708d118d44c1cb5609667fd51df9e58cacce8b65565ef20ad1649a3e1b9453e4fb37af67c95387de008d4c2114e5b95", "Expected": "0000000000000000000000000000000004b2311897264fe08972d62872d3679225d9880a16f2f3d7dd59412226e5e3f4f2aa8a69d283a2dc5b93e022293f0ee1000000000000000000000000000000000f03e18cef3f9a86e6b842272f2c7ee48d0ad23bfc7f1d5a9a796d88e5d5ac31326db5fe90de8f0690c70ae6e0155039", "Name": "matter_g1_add_20", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000008e5afc16d909eb9d8bdaaf229ad291f34f7baf5247bbd4cc938278f1349adb4b0f0aacd14799c01d0ca2ed38c937d600000000000000000000000000000000006cf972c64e20403c82fee901c90eaa5547460d57cce2565fd091ff9bc55e24584595c9182298f148882d6949c36c9d5", "Expected": "000000000000000000000000000000000caf46f480ae2ea8e700f7913c505d5150c4629c9137e917357d2a4ba8a7a1c63b8f6e2978293755952fbed7f0ad8d6d0000000000000000000000000000000002e62e715b72eebbc7c366a2390318f73e69203a9533e72340aab568f65105129ffc9889a8bc00a692494d93688c7ec0", "Name": "matter_g1_add_21", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0000000000000000000000000000000013a6439e0ec0fabe93f6c772e102b96b1f692971d7181c386f7f8a360daca6e5f99772e1a736f1e72a17148d90b08efe0000000000000000000000000000000010f27477f3171dcf74498e940fc324596ef5ec6792be590028c2963385d84ef8c4bbb12c6eb3f06b1afb6809a2cb0358", "Expected": "000000000000000000000000000000000dea57d1fc19f994e6bdda9478a400b0ada23aed167bfe7a16ef79b6aa020403a04d554303c0b2a9c5a38f85cf6f3800000000000000000000000000000000000b8d76ccd41ba81a835775185bbf1d6bf94b031d94d5c78b3b97beb24cf246b0c25c4c309e2c06ae9896ed800169eeee", "Name": "matter_g1_add_22", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff320000000000000000000000000000000005728a219d128bc0a1f851f228e2bf604a72400c393cfb0d3484456b6b28a2c5061198656f0e106bbe257d849be159040000000000000000000000000000000011f6d08baa91fb2c8b36191d5b2318e355f8964cc8112838394ba1ded84b075de58d90452601dcfc9aa8a275cfec695d", "Expected": "0000000000000000000000000000000012e6d6c518c15cfd3020181ff3f829e29140b3b507b99251cc7f31795128adec817750296bce413bac18b9a80f69ca5000000000000000000000000000000000131ee9b748f6f1eb790adeb9edd0e79d89a9908368f5a6bb82ee0c913061cdfffe75d9ba411a49aa3f9194ee6d4d08a9", "Name": "matter_g1_add_23", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f200000000000000000000000000000000171696781ba195f330241584e42fb112adf9b8437b54ad17d410892b45c7d334e8734e25862604d1b679097590b8ab0a000000000000000000000000000000001879328fdf0d1fb79afd920e0b0a386828be5b8e0e6024dfeea800ffcb5c65f9044061af26d639d4dcc27bcb5ba1481a", "Expected": "00000000000000000000000000000000111c416d5bd018a77f3317e3fbf4b03d8e19658f2b810dc9c17863310dfb09e1c4ffdbb7c98951d357f1c3d93c5d0745000000000000000000000000000000000af0a252bff336d5eb3a406778557ef67d91776a9c788be9a76cff7727f519a70fc7809f1a50a58d29185cb9722624fd", "Name": "matter_g1_add_24", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c2000000000000000000000000000000000231b0d6189a4faad082ce4a69398c1734fcf35d222b7bce22b14571033a1066b049ae3cd3bd6c8cec5bec743955cdd600000000000000000000000000000000037375237fb71536564ea693ab316ae11722aadd7cab12b17b926c8a31bd13c4565619e8c894bffb960e632896856bbe", "Expected": "000000000000000000000000000000000d2b9c677417f4e9b38af6393718f55a27dbd23c730796c50472bc476ebf52172559b10f6ceb81e644ec2d0a41b3bb01000000000000000000000000000000001697f241ff6eceb05d9ada4be7d7078ecbbffa64dd4fb43ead0692eef270cb7cc31513ee4bf38a1b1154fe008a8b836a", "Name": "matter_g1_add_25", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000015653d1c5184736cdc78838be953390d12b307d268b394136b917b0462d5e31b8f1b9d96cce8f7a1203c2cae93db6a4000000000000000000000000000000000060efeece033ac711d500c1156e4b6dce3243156170c94bc948fd7beae7b28a31463a44872ca22ca49dc5d4d4dd27d1c", "Expected": "0000000000000000000000000000000003996050756117eeab27a5e4fa9acdde2a1161d6fbfff2601a1c7329f900e93a29f55a8073f85be8f7c2a4d0323e95cc00000000000000000000000000000000010b195a132c1cba2f1a6a73f2507baa079e9b5cb8894ea78bebc16d4151ee56fe562b16e2741f3ab1e8640cdad83180", "Name": "matter_g1_add_26", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc0000000000000000000000000000000018adb42928304cbc310a229306a205e7c21cdb31b9e5daf0ff6bb9437acee80cd8cf02b35dab823155d60f8a83fde5cc0000000000000000000000000000000018b57460c81cab43235be79c8c90dcda40fafcaf69e4e767133aee56308a6df07eac71275597dd8ed6607ffb9151ed9a", "Expected": "0000000000000000000000000000000003c7a7ee3d1b73cf1f0213404363bf3c0de4425ab97d679ed51448e877b7537400f148f14eba588ed241fea34e56d465000000000000000000000000000000000c581b5070e6bb8582b7ee2cd312dfeb5aaf0b0da95cf5a22a505ffba21fc204e26a5e17311d1f47113653ff13349f57", "Name": "matter_g1_add_27", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da00000000000000000000000000000000001da65df8574a864ab454e5f2fa929405501bb73c3162a600979a1145586079361c89839cc0c5a07f1135c94bf059f9c0000000000000000000000000000000002560df402c0550662a2c4c463ad428ab6e60297fbc42a6484107e397ae016b58494d1c46ac4952027aa8c0896c50be3", "Expected": "000000000000000000000000000000000d7a539b679e5858271a6f9cf20108410eb5d5d2b1a905e09a8aa20318efbe9175450385d78389f08f836f5634f7a2f0000000000000000000000000000000000fb624e5f6c4c814b7d73eb63b70237c5de7d90d19ac81cac776d86171a8d307d3cc8c56da14f444fe8cf329ab7e63dd", "Name": "matter_g1_add_28", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e0530000000000000000000000000000000005311c11f4d0bb8542f3b60247c1441656608e5ac5c363f4d62127cecb88800a771767cf23a0e7c45f698ffa5015061f0000000000000000000000000000000018f7f1d23c8b0566a6a1fcb58d3a5c6fd422573840eb04660c3c6ba65762ed1becc756ac6300e9ce4f5bfb962e963419", "Expected": "0000000000000000000000000000000000849bbc7b0226b18abbcb4c9a9e78dca2f5f75a2cbb983bd95ff3a95b427b1a01fd909ce36384c49eb88ffb8ff77bb000000000000000000000000000000000087d8d28d92305b5313ca533a6b47f454ddce1c2d0fa3574b255128ef0b145fa4158beb07e4f0d50d6b7b90ea8a8ea8a", "Name": "matter_g1_add_29", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e000000000000000000000000000000000c8e293f730253128399e5c39ab18c3f040b6cd9df10d794a28d2a428a9256ea1a71cf53022bd1be11f501805e0ddda40000000000000000000000000000000003e60c2291be46900930f710969f79f27e76cf710efefc243236428db2fed93719edeeb64ada0edf6346a0411f2a4cb8", "Expected": "00000000000000000000000000000000191084201608f706ea1f7c51dd5b593dda87b15d2c594b52829db66ce3beab6b30899d1d285bdb9590335949ceda5f050000000000000000000000000000000000d3460622c7f1d849658a20a7ae7b05e5afae1f01e871cad52ef632cc831b0529a3066f7b81248a7728d231e51fc4ad", "Name": "matter_g1_add_30", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc80000000000000000000000000000000013267db8fdf8f488a2806fead5cffdcbb7b1b4b7681a2b67d322cd7f5985c65d088c70cdc2638e679ed678cae3cc63c80000000000000000000000000000000007757233ad6d38d488c3d9d8252b41e4ab7ee54e4ef4bbf171402df57c14f9977dd3583c6c8f9b5171b368d61f082447", "Expected": "000000000000000000000000000000000c06fef6639ab7dceb44dc648ca6a7d614739e40e6486ee9fc01ecc55af580d98abc026c630a95878da7b6d5701d755c0000000000000000000000000000000007c9a7f2bc7fa1f65c9e3a1e463eb4e3283e47bb5490938edb12abf6c8f5a9b56d8ce7a81a60df67db8c399a9a1df1d4", "Name": "matter_g1_add_31", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17000000000000000000000000000000001975bc52669187f27a86096ae6bf2d60178706105d15bce8fe782759f14e449bc97cb1570e87eec5f12214a9ae0e0170000000000000000000000000000000000ca6106d6e6487a3b6f00fc2af769d21cb3b83b5dc03db19e4824fc28fd9b3d9f7a986e79f05c02b3a914ff26c7a78d6", "Expected": "0000000000000000000000000000000002fbf4fba68ae416b42a99f3b26916dea464d662cebce55f4545481e5ab92d3c40f3e189504b54db4c9cd51ecdd60e8d0000000000000000000000000000000008e81e094c6d4ded718ef63c5edfacb2d258f48ccfa37562950c607299bb2dca18e680a620dff8c72dedc89b4e9d4759", "Name": "matter_g1_add_32", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f00000000000000000000000000000000109f6168a719add6ea1a14f9dc95345e325d6b0e56da2f4ecff8408536446894069fa61e81bdaebfc96b13b402fad865000000000000000000000000000000001806aa27c576f4c4fa8a6db49d577cd8f257a8450e89b061cbc7773c0b5434f06bacf12b479abf6847f537c4cbefcb46", "Expected": "0000000000000000000000000000000014e0bd4397b90a3f96240daf835d5fb05da28a64538f4bf42d9e7925a571f831c6e663910aa37dcc265ddd7938d83045000000000000000000000000000000001695d405d4f8ba385ebf4ad25fb3f34c65977217e90d6e5ed5085b3e5b0b143194f82e6c25766d28ad6c63114ca9dcdf", "Name": "matter_g1_add_33", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af0000000000000000000000000000000019d3623a7866933e2d73214ceb2e56097a1b047db5943c3ecb846890aa02250126e90fc76a729a952cef895bd154cc7d000000000000000000000000000000000e87c376bbd695a356ef72226ac7ef6a550d99e9693d8485770a686e568ae28c038ee201d3f2ea38362046236ade91cd", "Expected": "000000000000000000000000000000000ffeab47985bd9b3e10ce27c6636bbda336dcf540cd37eccc3faec2adff2d97dd126633bd83a7d3c8c73c3623bdf0ba2000000000000000000000000000000001992eca4b1e924b360d57ca98b543ab496a8b55bd288d23f03bcc1b22f6bc76d95b12f47c3e305812097253c73b876dd", "Name": "matter_g1_add_34", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c300000000000000000000000000000000163aaecf83d6c77a5d7417e73f5cf9d71a6aedfd194b2f3b53c608d06a228190f4f79ac57b029d77504c72744df4ecc0000000000000000000000000000000000416e6f9ca188d16daa2c28acd6a594f8fcb990eaa26e60ca2a34dfcad7ad76c425b241acedf674d48d298d0df0f824d", "Expected": "000000000000000000000000000000001812bcb26fa05e0ab5176e703699ab16f5ef8917a33a9626ae6ff20f2a6f4a9d5e2afe3a11f57061cbaa992e1f30477f000000000000000000000000000000000680acf0b632cb48017cb80baa93753d030aa4b49957178d8a10d1d1a27bbdc89ac6811a91868b2c181c5c0b9b6caf86", "Name": "matter_g1_add_35", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000000aba7362eee717d03ef2d4f0fef2763822115fcc8fb9e2e8243683b6c1cde799ebc78f23812e557de2cc38e2b4a2e56700000000000000000000000000000000170833db69b3f067cf5c4c4690857e6711c9e3fcad91ca7cd045e9d2f38c7b31236960e8718f5dd4c8bfb4de76c6c9b9", "Expected": "00000000000000000000000000000000196ffe76a4b726fa8dd720cc1cd04c040724cb18ec10915e312eaa90d124100b08f0ce3a7fc888f46914319a3d7581f4000000000000000000000000000000000e2612357059ca6dbb64efb98ef19370560c9e83e2aad7ab2d9015e2444fe4d8c796b5577584aac9f63258beb5ae863c", "Name": "matter_g1_add_36", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da8257721808000000000000000000000000000000000a98ae36c690f2e3be8100f43678be5a1064390e210328dd23f61f5a496b87398db2798580edeabc6273fb9537fa12880000000000000000000000000000000009aedf77bb969592c6552ae0121a1c74de78ba222b6cd08623c7a34708a12763b5ff7969cf761ccd25adc1b65da0f02d", "Expected": "00000000000000000000000000000000072334ec8349fc38b99d6dea0b4259c03cd96c1438c90ef0da6321df2495892de031a53c23838ca2b260774fa09b5461000000000000000000000000000000000e4535767c2477c4f87c087540c836eeffcd0c45960841f9c3561a8a5f8e61ab98b183b11192b8e7ea1c9c7717336243", "Name": "matter_g1_add_37", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e00000000000000000000000000000000015c3c056ec904ce865d073f8f70ef2d4b5adb5b9238deaa5e167d32f45cad4901aa6d87efa2338c633e7853ce4c19185000000000000000000000000000000000a15f1aa6e662f21d7127351a1655821c943c4cf590e3c9e60c9ab968b4a835f87fb8d87eee6331ee4e194e5f1ea91f4", "Expected": "000000000000000000000000000000000140fb6dcf872d0a3bff3e32a0cb4a7fb7e60ee4fb476bb120c4ce068e169d72e1c167d7fda321280d5855983d5a9af800000000000000000000000000000000108f54a4ec3ba26dd614f4d94c5c82652583906986158ad40ffea54c17703fa4b0bd7806633e1c0318d06e8dc7d41cde", "Name": "matter_g1_add_38", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb000000000000000000000000000000000307841cb33e0f188103a83334a828fa864cea09c264d5f4343246f64ab244add4610c9ccd64c001816e5074fe84013f000000000000000000000000000000000e15bbeb6fff7f1435097828f5d64c448bbc800f31a5b7428436dcffd68abc92682f2b01744d7c60540e0cd1b57ab5d4", "Expected": "000000000000000000000000000000000a1b50660ed9120fff1e5c4abb401e4691a09f41780ca188cea4b1c2d77002f08ce28eb1caa41ee3fe73169e3651bb7f00000000000000000000000000000000125439ac3b45c698a98063ab911364bd3c6dd2a69435d00d6edf89fc5566b33038e960a125e5e52141abb605587942fe", "Name": "matter_g1_add_39", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000013866438b089d39de5a3ca2a624d72c241a54cbdcf5b2a67ebdd2db8373b112a814e74662bd52e37748ffbfc21782a5000000000000000000000000000000000d55454a22d5c2ef82611ef9cb6533e2f08668577764afc5bb9b7dfe32abd5d333147774fb1001dd24889775de57d305", "Expected": "000000000000000000000000000000000037b4e8846b423335711ac12f91e2419de772216509d6b9deb9c27fd1c1ee5851b3e032bf3bcac3dd8e93f3dce8a91b00000000000000000000000000000000113a1bf4be1103e858c3be282effafd5e2384f4d1073350f7073b0a415ecf9e7a3bfb55c951c0b2c25c6bab35454ecf0", "Name": "matter_g1_add_40", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f180320000000000000000000000000000000017440fd557df23286da15f9a96bb88cfbc79589b1c157af13baf02c65227dc0a5bdec6f2f300083ff91dae395ed8cb75000000000000000000000000000000000ad09b4290842cc599d346110fdb39ededbb1d651568579564e274465f07b8f77eeaf00fece0c10db69c2125de8ab394", "Expected": "0000000000000000000000000000000007c158b4e21566742f7e4e39a672bd383e27864505acef4ef8c26f8b0a9db418f9c088b555b8e9eb25acf9859b1207b40000000000000000000000000000000016e06a1ace89f992d582af0de7662ef91c0a98f574306f6f6d0d8d5e80166638d2deef70105cce2e9b20faa9d6315510", "Name": "matter_g1_add_41", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0000000000000000000000000000000000d7ccc3a4efdfe1a92a88e453933b8216016091f1b9d575faf18a5b3abf90daf077813167a3f4acce7359472dee544bb00000000000000000000000000000000128008c075ab176100e755cbb8de5b9ff0e9a78114f862d26ed030d9c1d1dea1c21ec8ae4d82a84d3ff5ae4c1cd6f339", "Expected": "000000000000000000000000000000000b84f9de79c748e37797c629cb78b86b4b736b199f161b30147b5dacf6eabe0b54afce40d5dacfe9a8ee8da5ef5b49de0000000000000000000000000000000010277ad094bb9a3b96379b1366dd90125b51a21ebeb4f776a81d9d9c1f37ab58c32a884a26fa32c83783ed0eef42b820", "Name": "matter_g1_add_42", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e00000000000000000000000000000000008da4a93d5ffcdaa0adc736a59f0c187ae3bf11ecb5e9e6f6aedea976a47757739042200b4c4593c2dd5db555425531000000000000000000000000000000000a6fdb2d4160c6c35223daa6fa10d0b1073de07fe4f2eba28e65ed049ff8d8852ed0538b30759fe7a0d944009ddf9a6f", "Expected": "000000000000000000000000000000000d740bd1effd8674250618af0358ad0b83bbc787f0264af9c2ada72fa5431be909e82155da1de0211f46fb307e9949f0000000000000000000000000000000000ddf62c91d587a14b64feef07da52c081b40fbbf9a0f2eae8b66022e0850fc94de6a467e7e4f580c7f2c806f6c6ed8cf", "Name": "matter_g1_add_43", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d790000000000000000000000000000000003258d7931a1d72ab6344c7e96c0dbd435a7909fe68cc679c08ca9b62f7a6a04863082cbcfdbe9a736625d895e4f3bdb0000000000000000000000000000000009ee3e470e2b2cebc955ba3444b7e478f887138e36c13bd68490689122627269ea5e7ce22dd9c69792394a24187103d6", "Expected": "000000000000000000000000000000000af674691f5d87655f0066188fac5013f31b4169a0181d3feb7ac3beae0d9a3429d4125f099ee344f644a2de8b941f9f00000000000000000000000000000000042a9603b8e4a6c37d59ede3a1398f5f80c5298da66de575a204ee28811d9f7c7c0dd40cef3769bd72a2156b9eb620c8", "Name": "matter_g1_add_44", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820000000000000000000000000000000001833807f1ced52399305419450355499a63411837ee61ad681559d59561db18511eb1e8ad3161e7fe30016b560d18b8f00000000000000000000000000000000198b11b31586e17964a4a4ccdee85703163d2106481833e71f26327a589bafb43578d08d87f6cb19c7a04b4ca92392bf", "Expected": "000000000000000000000000000000001081c3359a0fadfe7850ce878182859e3dd77028772da7bcac9f6451ac6455739c22627889673db626bbea70aa3648d50000000000000000000000000000000000f4e8766f976fa49a0b05ef3f06f56d92fe6452ff05c3fac455f9c16efadf1b81a44d2921bed73511dda81d6fc7478e", "Name": "matter_g1_add_45", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8", "Expected": "000000000000000000000000000000000f60d66fd1ed5eb04f9619d6458c522cc49f5ace111aff2b61903b112559972f80ac615591463abf2b944c4f99d4c03e000000000000000000000000000000000001a1abfa869be2cda6bd7e05454a8735e1b638db7e1b3715708539c2d14ade53069c7e68b36d3b08cff80837028b7d", "Name": "matter_g1_add_46", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd0000000000000000000000000000000014b78c66c4acecdd913ba73cc4ab573c64b404a9494d29d4a2ba02393d9b8fdaba47bb7e76d32586df3a00e03ae2896700000000000000000000000000000000025c371cd8b72592a45dc521336a891202c5f96954812b1095ba2ea6bb11aad7b6941a44d68fe9b44e4e5fd06bd541d4", "Expected": "0000000000000000000000000000000015b164c854a2277658f5d08e04887d896a082c6c20895c8809ed4b349da8492d6fa0333ace6059a1f0d37e92ae9bad30000000000000000000000000000000001510d176ddba09ab60bb452188c2705ef154f449bed26abf0255897673a625637b5761355b17676748f67844a61d4e9f", "Name": "matter_g1_add_47", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe900000000000000000000000000000000104ee0990ba4194916f670f44e254200971b67a18ed45b25c17be49df66e4f9b934bac8c1552ecc25bdaa3af55952076000000000000000000000000000000000591094d9d89afe025ca1832d7f3e60444f83e72403a434b42216b6c4213980d29e4ef0c64ae497006de550c1faa9425", "Expected": "0000000000000000000000000000000006db0cc24ffec8aa11aecc43e9b76a418daac51d51f3de437090c1bcaabace19f7f8b5ceb6277d6b32b7f3b239a90c4700000000000000000000000000000000069e01f60ca7468c6b9a247c79d18cf3d88bf5d1d62c76abf9237408edeba05dea744205ac5b501920f519bb847bb711", "Name": "matter_g1_add_48", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a0000000000000000000000000000000004840d028d0c0f056aeb37b7a8505325081e9822ef26046f2da72f2155c20987dd51f4b5577c5395e24288b71d2ce5140000000000000000000000000000000015f231a233e997633c1d6492e0df358fb658ae29d0f53928c8a0578484c899a699178ca3223772210063aa08991c3fff", "Expected": "000000000000000000000000000000000fa72bf2d7d564cc4982b9f2cdca743d2ac14f0f1be4218dbafb8b93a9277e55273487a5d2857fd3f731ac4ee469a6a1000000000000000000000000000000000fce44f886453c6ca5ebde9af41d2be92d1126e9897d72978a179dd7eebeed6242b6e9718604ab0c9369529a0426a575", "Name": "matter_g1_add_49", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece1738220000000000000000000000000000000004877b97faa1d05d61ab65001110bf190d442cabcd6d4d1b9c1f0e513309aebd278f84a80354dfdef875769d00ec2c7500000000000000000000000000000000187066cccb5008bc2ffd0bcd1b227a5a0fe0cd4984316ba3cfd5113c4632a04c56cbda8d48993bd0dd50e9b7ce2b7ee9", "Expected": "0000000000000000000000000000000019ecd38afacc6b281b2515270157328e18039d51574bae0f7e0ef16c3f6da89f55ddee9e3bbb450ad51fe11edfd9f18d00000000000000000000000000000000088a5e292761bbf7a914a9f723de099035e91bd3c1fe9cd50728a4ceaa4fd3953683f30aa8e70ba0eb23919092aa9e22", "Name": "matter_g1_add_50", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000001881f5aba0603b0a256e03e5dc507598dd63682ce80a29e0fa141b2afdadf6168e98221e4ee45d378cee0416baaadc49000000000000000000000000000000000070d255101319dd3a0f8ca3a0856188428c09de15475d6b70d70a405e45ab379a5b1f2e55f84bd7fe5dd12aeedce670", "Expected": "0000000000000000000000000000000011ccd455d5e3eba94567a17bcd777559b4ff1afa66fd6f05f99c69937404290a2f1c83cfd6c2c25886ebff4934332c0e0000000000000000000000000000000010920aa3d5974df25530610ef466adce3d51fd6a508d4b1111739c586dfd7ba9040836e075fd812fe111d92f25b67f51", "Name": "matter_g1_add_51", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf000000000000000000000000000000000b53e5339f25bcd31afd091362874b5042c0b762ed7425341331630addbc4dccc299936e1acdf89823c36867d46c6f28000000000000000000000000000000000fc3c6b522268511dd52826dd1aee707413d925ee51aeb0e5d69c0e3eb697fabbc14783b5007e240cc0c53c299a40ada", "Expected": "00000000000000000000000000000000060773b9b8f3babdba3db27089b7be3e6e287a635dbae19576039d34ae18a0e6413278bfa280570f6329ae05cdb693fd00000000000000000000000000000000075fb9527f99a8c8db41e67baaf1deafffd2c134badb1b3478a26b5501b31dca858fad6f0d52f412d5631ecfa72eece4", "Name": "matter_g1_add_52", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000001693f4ebab3fed548784264196fb01cf55311399f47cdad74a9543bda5d1ca682a00ee04bb0b3954d5a0f00ceef97a750000000000000000000000000000000017f4019c23bd68e84d889857c417b17aa96c780fec3c1ed6ca75100cc70c97a8bb8272ad4c6de896d76dc2a1b09c7a61", "Expected": "000000000000000000000000000000000a3ea8afdc83794f18f9a9427bcd60a355196925d38fdf74ab09d4a08279647b2da6f1fbe30948a785497d6c6dddc2a9000000000000000000000000000000001263c88f1ca3e574cafac21641432d45ee01e1b05eba95716565922abe28c7f0fb004c255afcbfa10cf7959bbe6b00d7", "Name": "matter_g1_add_53", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000005d5602e05499a435effff3812744b582b0cd7c68f1c88faa3c268515c8b14f3c041b8ae322fe526b2406e7c25d84e61000000000000000000000000000000001038eaf49e74e19111e4456ebba01dc4d22c7e23a303d5dec821da832e90a1b07b1a6b8034137f1bfdcddeb58053a170", "Expected": "0000000000000000000000000000000019258ea5023ce73343dcd201ec9be68ec1ee1cb4e5b9964309d801c2bc523343c8ebc4f8393a403c7881e5928f29db14000000000000000000000000000000001423bf52daefb432162ce2bd9ef78b256ff3b24d0a84766b87119489fd56ecf6156b2884c8a7e1220e493469723cd7f8", "Name": "matter_g1_add_54", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000002626f28d421d9d1c28f5e1eb5a51ada9610dbdd62cd33c4078d2fdfc18dbd092e2847cf705ba5fcd8c1a60c1cc34a3b0000000000000000000000000000000001f7b8cfdb7e406c920f5fdecae45fb4be736f209480ccb455f972c6b1a1aebdd5ba116903c46ded72ce37cd8836e871", "Expected": "00000000000000000000000000000000081d674f5b9c7c64673c39fe33f4f3d77271e826dcb4dfd2591062e47c931237e8539ef9c886c9e112eccc50da4f63fd00000000000000000000000000000000141b700695839110ed4ced5f8a3f4fd64a8086805358ab4a5abd2705592e616cd95ff01271212ca9014dcb68d8157ba0", "Name": "matter_g1_add_55", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df91000000000000000000000000000000000259e307eacb1bc45a13811b02a7aeaaf4dc2bb405dcd88069bb6ec1c08a78905516169bd3440a36921764df0ef3a85b000000000000000000000000000000001263372b675124f6cc19ca16842ba069c5697dbf57730875fe72c864a81189d7d16fe126b5d24953a0524f96dbac5183", "Expected": "000000000000000000000000000000001908aa3a640817e31a4213156fbd4fd39ab39eb931091670a0e06399def71a689e67286f90d38ce9f97cb85f6488d9c8000000000000000000000000000000000764e46b6b82aa2f8862d28e9d543a751a9de855645377b9633cc098c2110ec6ed4fd30f0044ea5868c93f950f6cfd24", "Name": "matter_g1_add_56", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000a138203c916cb8425663db3bbff37f239a5745be885784b8e035a4f40c47954c48873f6d5aa06d579e213282fe789fa0000000000000000000000000000000016897b8adbc3a3a0dccd809f7311ba1f84f76e218c58af243c0aa29a1bb150ed719191d1ced802d4372e717c1c97570a", "Expected": "0000000000000000000000000000000004ad79769fd10081ebaaed9e2131de5d8738d9ef143b6d0fa6e106bd82cfd53bbc9fab08c422aa03d03896a0fb2460d0000000000000000000000000000000000bb79356c2d477dfbcb1b0e417df7cb79affbe151c1f03fa60b1372d7d82fd53b2160afdd88be1bf0e9dc99596366055", "Name": "matter_g1_add_57", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29460000000000000000000000000000000019f60f2cf585bdbc36947f760a15fa16c54cf46435cc5707def410202a3f4fa61b577ab2481e058b0345982d3e3d1666000000000000000000000000000000000a70b7bbc55e1f3e11e9eb7efd79d4e396742de48d911ddff8dd0a7cf10422423d5e68021948e1448e92c2e07c194776", "Expected": "000000000000000000000000000000000a87e7e115ccdf3c2c1a2716491d449c3f8329e73d264088f4af444d43cf05f8be0410da273ce7eeb32969830195b7e70000000000000000000000000000000010a973d6e4bd85105bf311eb0dcfdc0a5d38dba1c099206b60f2e2df4791fd58846bf19d83769506e1561212920b4895", "Name": "matter_g1_add_58", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000109bd6e0636a7f96ffe2ce8e109171efaacfcd60189c7050259ddedd15dd257e11f2585bbd84e4a3f4d8fc5fbc0289cf0000000000000000000000000000000019b420d778da53aed81b48f2c9b9eb399e771edd5e124a41577452b409ca2503e2798cd25d791f489352fc7b7268ae23", "Expected": "00000000000000000000000000000000162bd29f2de10002c1c446bd9583e89751fb91703ad564e7951d41673e28d214729aa9b4b9875c397989df197c912d5f0000000000000000000000000000000004d393181871c93714afab6c33c16f68ec391fbfcad606ac65cc1d070949c099e21f710e2fe0dd4e4f50f99ea2167a7e", "Name": "matter_g1_add_59", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000012bb529b45ad7875784b62a7281d025002f15e7f86cc33555e7472df60da2cb15d37c8bf628142818c0711ee9047fb4d000000000000000000000000000000000baa801623312d95e2b51ce86373fea516007e468f265d974c2327c1779830db180bed6dbe8a64f0959aad26eaafb8d9", "Expected": "0000000000000000000000000000000010c4b328d264893099d89ba81b0765d0642bf36b0ac043be090c7b4f7987d21a906228c3c208c4ec5123d577efb0771f0000000000000000000000000000000016d08ce3bf755da7d4bae5f4b06b37845c17a717329c547e941be93325a04e9a5095d3f6e6c6f9ec3b1a740f59d88919", "Name": "matter_g1_add_60", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000002c9e50f37ff0db2676637be8a6275fce7948ae700df1e9e6a0861a8af942b6032cca2c3be8b8d95d4b4b36171b4b0d400000000000000000000000000000000050f1a9b2416bbda35bac9c8fdd4a91c12e7ee8e035973f79bd35e418fd88fa603761e2b36736c13f1d7a582984bd15e", "Expected": "000000000000000000000000000000000f798f8d5c21cbce7e9cfcbb708c3800bf5c22773ec5b44590cdbb6f720ccddf05a9f5d5e6a51f704f7c295c291df29f000000000000000000000000000000001483903fde5a968dba6924dfac3933cd39f757e2f89120f4ca9d03aaaf9e18252bdb5c5d3939471666b8a42aeb31b4ed", "Name": "matter_g1_add_61", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec000000000000000000000000000000000332cdc97c1611c043dac5fd0014cfeaee4879fee3f1ad36cddf43d76162108e2dc71f181407171da0ceec4165bcd9760000000000000000000000000000000015b96a13732a726bad5860446a8f7e3f40458e865229bd924181aa671d16b2df2171669a3faa3977f0ee27920a2c5270", "Expected": "0000000000000000000000000000000001c762175f885a8d7cb0be11866bd370c97fb50d4277ab15b5531dacd08da0145e037d82be3a46a4ee4116305b807de6000000000000000000000000000000000bb6c4065723eaf84d432c9fde8ce05f80de7fe3baed26cf9d1662939baac9320da69c7fe956acdd085f725178fe1b97", "Name": "matter_g1_add_62", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c50000000000000000000000000000000003ebca978ea429eedad3a2c782816929724fc7529fbf78ea5738f2ca049aab56c1773f625df2698433d55db7f5fc8ca2000000000000000000000000000000000d2477f57b21ed471a40566f99b7c2d84ce6b82eaf83a6c87a7c21f3242959c8423d4113b7fd8449277b363303bb17b0", "Expected": "00000000000000000000000000000000071dc0f985703bd8335093779de651b524c02faca5fc967766abd3f6f59176d2046d7a14d18c0b757b8c9802e44ebcd300000000000000000000000000000000154e5cb66be8979ee276e8e0f240557e3f7dc074c497293af589256652da21d66a6e6b00ca5bfa6f89963fbd5bc6cf48", "Name": "matter_g1_add_63", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001461afe277bf0e1754c12a8aabbe60262758941281f23496c2eeb714f8c01fd3793faf15139ae173be6c3ff5d534d2bc00000000000000000000000000000000148ad14901be55baa302fa166e5d81cc741d67a98a7052618d77294c12aea56e2d04b7e497662debc714096c433e844e", "Expected": "0000000000000000000000000000000012c4dd169f55dfb5634bc4866f7cbd110648b5392ace6042b5f64aba3278f24085227521b7834864f00d01ec9998dd6800000000000000000000000000000000102d7a495850195424677853da01d70caeb6c0af5270bcfffbc2d4252c0f3680518cd8d2a0a6dbbbc7b52923a5b26562", "Name": "matter_g1_add_64", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002218b4498c91e0fe66417fe835e03c2896d858a10338e92a461c9d76bcecd66df209771ae02c7dcace119596018f83c000000000000000000000000000000001990233c0bae1c21ba9b0e18e09b03aeb3680539c2b2ef8c9a95a3e94cf6e7c344730bf7a499d0f9f1b77345926fef2d", "Expected": "0000000000000000000000000000000010c50bd0f5169ebd65ee1f9cd2341fa18dd5254b33d2f7da0c644327677fe99b5d655dd5bfdb705b50d4df9cfce33d1400000000000000000000000000000000088e47ffbbc80c69ec3c5f2abe644a483f62df3e7c17aa2ff025553d1aaf3c884a44506eff069f4c41d622df84bbafa1", "Name": "matter_g1_add_65", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7000000000000000000000000000000000160e0f540d64a3cedba9cf1e97b727be716bbfa97fbf980686c86e086833dc7a3028758be237de7be488e1c1c368fe100000000000000000000000000000000108250b265bd78f5e52f14ef11515d80af71e4d201389693a5c3ef202cf9d974628421d73666ead30481547582f7abaf", "Expected": "00000000000000000000000000000000168af33c85ae6e650375ed29b91218198edd9135683f6a1428211acdcbf16bdf86f0a95575e47ee0969587a10fa9f3c90000000000000000000000000000000012d9f5d692c870b3da951b6d07797c186a8ddc89b9f08a1c0b8f0f119f10ca0b155e8df5424cf48900ad3bf09ce6872a", "Name": "matter_g1_add_66", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d20000000000000000000000000000000002fa19b32a825608ab46b5c681c16ae23ebefd804bb06079059e3f2c7686fe1a74c9406f8581d29ff78f39221d995bfd000000000000000000000000000000000b41ea8a18c64de43301320eaf52d923a1f1d36812c92c6e8b34420eff031e05a037eed47b9fe701fd6a03eb045f2ca7", "Expected": "000000000000000000000000000000000b99587f721a490b503a973591b2bb76152919269d80347aeba85d2912b864a3f67b868c34aee834ecc8cd82ac1373db0000000000000000000000000000000007767bb0ca3047eee40b83bf14d444e63d98e9fc6c4121bdf04ea7148bcfaf3819b70dcebd9a941134e5c649da8f8d80", "Name": "matter_g1_add_67", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000002a540b681a6113a54249c0bbb47faf7c79e8da746260f71fbf83e60f18c17e5d6c8a7474badafee646fe74217a86ca4000000000000000000000000000000000fe2db7736129b35dc4958ffd0de7115359857fb9480b03a751c4fceb9ae1b2b05855398badffc517ae52c67f6394e2a", "Expected": "000000000000000000000000000000000bc719a8397a035fc3587d32d7ef4b4cfd63d4a5619ab78301d59659208f86df9e247e5d12650acc51a3bca3827063a900000000000000000000000000000000150d5519380a65b1909b0d84da374484675d99b00b254d03e423e634a012b286e3fe074e9b0a7bb24ff52d327249a01b", "Name": "matter_g1_add_68", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787900000000000000000000000000000000019d917eb431ce0c066f80742fe7b48f5e008cffa55ee5d02a2a585cc7a105a32bbf47bdff44f8a855ade38184a8279e0000000000000000000000000000000012ee762e29d91a4fc70bc7a2fb296a1dcdd05c90368286cca352b3d5fffc76e3b838e14ea005773c461075beddf414d8", "Expected": "0000000000000000000000000000000008197403ab10f32d873974c937ef4c27fbdb0f505c4df8ac96504705d4851cf951fb0263335e477063884527b21edf160000000000000000000000000000000005396f1affa20ca8530b519a4d5d400969f0c8c8731ecc0944e8086388e89a7ff7c16d9a2a90780972c4762b88a0f0af", "Name": "matter_g1_add_69", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000d280fe0b8297311751de20adf5e2d9e97f0c1bfe0cd430514cfddbafd5cdcb8c61bd8af4176cc3394f51f2de64b152400000000000000000000000000000000039f511e890187f28c7a0b2bd695ae665e89b0544c325a44b9109da52cc6908d81e1a27163a353ab275d683860c2e007", "Expected": "0000000000000000000000000000000002baea63055f72646189bdd133153dd83026f95afad5ce2cffbee3f74c8d47d5480094b2b58b0936c78aa33cd9a8f72f0000000000000000000000000000000013e600456a2d76f5a760059e0ba987b881c6bc10d6161f388d7a9d8b2031921054edfec46afbd80b1364d8e8f6a5a7a2", "Name": "matter_g1_add_70", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000015bad24d12b5d68558e961a17dbc3e1686e1b918e6192ebe6f3f71c925177e61d0162e018ac81126099effa0cadfa185000000000000000000000000000000000de73182569184b3d79dcfa8c27f46ec7a31fe8a3fd73fe26eec37a088461192bdbcf4d4b37b33b6177d6fde015d1631", "Expected": "000000000000000000000000000000000ced641c930387432d512861eefbf2d6131017154f99a0d3d24da880dfd2aaae91c2d9634053fab8b85fc11a7884d30600000000000000000000000000000000122071c0e87fae5031c850dccc4777c3ec9d8463bbc4ed84364d4261bc9d38f696a4320d53eea926a75ed9fcc9789a07", "Name": "matter_g1_add_71", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b256730000000000000000000000000000000015cdf7dafedce64aba34e1f18c57b28f297629c07ee96b732029b545cf5ea6afdf926daa6a48d1250c67aa2a8b797d370000000000000000000000000000000004867352f86267dbe8e32806e4ed02f1487e036051068f8e06d02e8dea6d3773b422e065d2db27c89ea69246d0185351", "Expected": "000000000000000000000000000000000e2c633351d627a075acd1e373bec96ba41b047f0307201f4b7c9978c1a72243d0b18113604cc421b8f66d76ec9b1360000000000000000000000000000000000844e258d602bf9aaa35ce46c4c91c80dd9337053d8ab22c1163a0571fcd1488a2ef57476e2b66dd9c26963b28284d11", "Name": "matter_g1_add_72", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000077eb801bcde78e9dd73b58d2429a907ea0f5600a8005093d471be373bba23ea70bf828c766ccced6a46db84b440053f00000000000000000000000000000000101af9df2939089d72e42fe2dc3de3e32be8f4526a2263ebd872d0080ed4a152107bb3d2f56176bf72d5ae8bd0c30a3f", "Expected": "0000000000000000000000000000000010205c6be10a5fc5390b0e5ae47a8a822c8e9a7a96f113d081cde477ec0de7bf0e8385e61780b2335e4297edb35bcc6d000000000000000000000000000000001796af180463ed70cf330791c8201ee3f0fe52993f64819291bda33017285fcc3a515669b3d48a411276c849fa021f6f", "Name": "matter_g1_add_73", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0000000000000000000000000000000019b09bb7dddd11c5d0e304dac120b920601dd3a3505e478c88850cc701c17eb02aa7bfb20e4017a62fc4fb544d4f9e8f00000000000000000000000000000000048ad536cf89576d4cce83ef065bc16c47f1a28ae27bd71d30d8f2177a9c6f8b2ed0cdf872ead71bc5a1252bccb4a7e0", "Expected": "000000000000000000000000000000000fb047098a1996a625cd19021f81ea79895e038756878d8772aaee9b6bbb66930e474dcc04579ad58f4877b742a890900000000000000000000000000000000017da74a4caefc55794a36eda7938371f42265cc1f2d87d41883152db82873daeb59642e8e663afddd4f24536a1f52b3f", "Name": "matter_g1_add_74", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa30000000000000000000000000000000005f84f9afa2a4a80ea1be03770cb26ac94bec65cf9cb3412a07683df41bb267c2b561b744b34779635218527484633e30000000000000000000000000000000013ce1d1764961d1b0dff236c1f64eabec2ce5a8526edf6b0bccb9ea412e5a91880db24510435cf297fcc1b774b318b65", "Expected": "000000000000000000000000000000000f4ca788dc52b7c8c0cb3419ab62c26db9fb434321fc6830837333c2bb53b9f31138eecccc3c33461297f99a810e24ad0000000000000000000000000000000006785d4f9cdf42264c00fdc4452883b9050eb56e2f6e46c7b8fc8d937dfe4d3ad5072d969a47c4811b36d3887256d0b9", "Name": "matter_g1_add_75", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000f0dd7a15dfc39dc2df47cf09761498b0b363157d8443356e768567f5a6d5913c2a67f12d93df2dcf50756bb686836b100000000000000000000000000000000055914dbda5b115222e738d94fbd430440c99bcc6d2c6cf7225c77756ffadf765b2d83447d395e876b5f6134563ed914", "Expected": "000000000000000000000000000000000ac0f0f62202d09cede55ca77b7344b46fd831b41015eb357cac07f0fa49c2564c2e9d5c591630226677446a9100757c000000000000000000000000000000000ca21d0128ef933fc1a48c1b4967f56912513e63a416d86ad40c0a4590b2edf88e4e8a286338b8b176d8b341ea480277", "Name": "matter_g1_add_76", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb211000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe", "Expected": "000000000000000000000000000000000b728d4ae4b45fae9a9e242524e95e44f175356726da50f46236f690eec17fdd5edce5df1253383378dc8f9c1fee98ae00000000000000000000000000000000131d28a5eab968c45ddc86b82f220dcdeab7c009c7c61986ee4e55045c024e1bcbe76a4e35000b5699ccec5858ba427e", "Name": "matter_g1_add_77", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a000000000000000000000000000000000b35fcf625cde78fba1b70904acb97d7eb449d968e8013855d44292e9c3b0df3cfbcace6f292ec3c7717e25490bb4c67000000000000000000000000000000000af57abd87df55034c32dbe68bd1c0b47139fc2c3a8887b7c151e57b57c9002070337c8dcb2ce2687f9f007d48dd68c1", "Expected": "00000000000000000000000000000000178a19966b5b0fa70c138be7f5ea51d5399c7b8dcc5171cbef82ecb1451aeccbd1ed29170a27f404ebf6daa2ec99bd69000000000000000000000000000000000b1b748494806175030f6b5e2977c58982bd6ec6662d69237f0521351653c772a40035f2504ac8949fb448a901379fd6", "Name": "matter_g1_add_78", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b00000000000000000000000000000000177a51fcc81580ccb7a8873fa93eaf860ca8fedde13cdf3eb53f11e66a1c1e934b82ee9251f711c5c479f33a22770c47000000000000000000000000000000000a0edc9a58f4bb414aa0aeec7bfa6076fb62bdbaee987192c18855adf4e813e7103b943e1dddc24754acfa90600a5750", "Expected": "0000000000000000000000000000000019195049a2d457709e284c84c72a211224efc4d7d46d25c9a537eea94149b06506df02a2a4e0a6428263e9605eaaacb500000000000000000000000000000000061139f9a70ce7cd87ed3a701163bde247382295f557b47a3a0a880d2780f015e8ac753eb3243f9ad138f92c3a2257c5", "Name": "matter_g1_add_79", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d000000000000000000000000000000001552982822e0b64a6204b27da0e192873bb5bd2997784ff0b6ed53801b402501a665c17f0a379fd946ab1adfae43c6af000000000000000000000000000000000938359655fe135dd2a390f83e27273feb68387ba94f2b6f7c15389f8272d64231ebe9c8271de90ff2358d935359ba85", "Expected": "00000000000000000000000000000000168f958a40e85341d90012e134976d1a5839e807948410cc0c81a50961552c052bb784c50da4c734f6aa583777c22b28000000000000000000000000000000000d26998bac6ec11bc5fcf6fe7262c984d6500cd5b21af979048b940e20054f8d759f8a011f3e09d01d10f9cf8ab150e1", "Name": "matter_g1_add_80", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d50000000000000000000000000000000000d94885dcc21b0b98821b6861a4d094e9eb5d5adcf7ca4275c5b759abbf9a9910f3b38073183d54a0569ecbbc1e9826400000000000000000000000000000000034a54b4bbb3f128608a866f5f5c554cf6ad7899f6650ca663a5bd5f1a3e4471e35a2440644c0e4e0a56080936b46d12", "Expected": "000000000000000000000000000000000d4734ab1bbcf9e30cf142a7aa9e8cde1b3c88d92397b8d7d48c7a7402561feee58a810abf67776e1890489efe7f8ec20000000000000000000000000000000005be9e4af0c0c183c43601339f162345f7c013f5941167cd925057e91c4641e19091a20123a36f2e803142833c0bc1ef", "Name": "matter_g1_add_81", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000014f16cbb17e7f63284d8a75968a4c8fc8ee7f37233ed656d696477c507c23e7c7eaf54001f44c93deb14c298aa6f94c00000000000000000000000000000000169bde83e861889c50b2138c76531a5866235d515a6fee4da7aaf8e8b903f2848a9fe7bbd55eac7f1c58ce3a88e7249d", "Expected": "000000000000000000000000000000001400f774b2d932c6b990da6e1b3493685e8f51d429e0c53e9af1b4a2d3876781b790bca4a1bc28ce0240ea21be24a2350000000000000000000000000000000004993fcf5723b7e02095d4ba73ff3194bbe36027bc9099b57084c91c7e7d50b76331bfb06d3c678d3e401bc3f7fcc577", "Name": "matter_g1_add_82", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000009acc4b4678b4b645fde47d1b75a5dda8caf6696ad2bf312dd5c12d7f3ab50b95152f5fe59842650c8a1a785f345c3ab000000000000000000000000000000000b672989004fe54f4d645e40cd29a21418151134fd2b90a68185040ceff141ced7f7ece1fdd9137c32589fa04b105a0e", "Expected": "000000000000000000000000000000000fcb0ab180a69b0a230d9dba98099fdce4969f82fc7e7ad93352a7c8dd448bb0ba9c7d62f53d5dc80506bc36190d9bc700000000000000000000000000000000047b7306f4a53c21d42993c50f2365486d02dac495f2dee4f8971a4af308396fce6c90f3cfde857bf7a2c6bf5d0d8aa7", "Name": "matter_g1_add_83", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6000000000000000000000000000000000198e12ade128447a240e03e024183c401d605cab1ed81f0f5bb7bc4c7cc9c889a2a01f59c0e37a0767a927719e5a95d000000000000000000000000000000001946e39fee9b76ce552108b339b9b24d11e43d3275ac19d2d4bc745c409bdc3f7c473a60c4d3a4d2cc3b598ae0d66880", "Expected": "00000000000000000000000000000000050b45f896fa40099cda8b1f20ab88644915c16f926589cd709e00149b12922347fa7122175424cd44e8875f217b9ad7000000000000000000000000000000001122b7e9b1509efe5616368b14085bdd36fb7adb85cd5a7f23e327548986f5298c045a602b6ee1265d53a4432a4a3c0e", "Name": "matter_g1_add_84", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac47388620000000000000000000000000000000009c48aa2681b3005b24075bb3a122ac100cbaca872f761f4398edaba9dd9da6d04d4a4925028297dfe5f77c2b0b5c821000000000000000000000000000000000ea95c646fb68aa458e69c267a6ca640a6a24d40bdca0161246e4521d13c46facfc1ac86dfc0a804cfa6665cebeec822", "Expected": "0000000000000000000000000000000005325a499aec678ada9eb673d366fe0475e885d5188e2fb687a96949e8f782852fba962197976b868ec083c512bfb66b000000000000000000000000000000000c4d6fcacc8d82401882bee355b37930d83e3cea2e4a7bc133e65a3e0af919b25fc3f30c333873da9406845ce42dbb87", "Name": "matter_g1_add_85", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae80000000000000000000000000000000008e8799a6cc0339e94e861692c81eee53e8a7b326523d5344b416bfbce04290585ef56018834cfd93d234bfa2943369f000000000000000000000000000000000fa1b01aab0878adad693ec769fb68640931c355b3802c51d4a3772300be5b16ceecdc8328a229b3b9f3639170db96f8", "Expected": "000000000000000000000000000000000685ec14da61c48bcb697966aca9e27601db43f0fb1f32e026fb33738eecfbb7012aa1ca3acf36a21fa846730245add70000000000000000000000000000000003fc52a1c3342b12271bbc178545bb20e96e8f1fde673e51f3d27ab5cb42e60aca49c6077e0f687be59b2d25cda9718e", "Name": "matter_g1_add_86", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000bb3a76287fb98fe668cb0a5de603c768340ee6b7f9f686a22da3a86926d8734d2c565c41f94f08fa3ef0e665f4ccb520000000000000000000000000000000016c02dbfb307c96d5b9c144672fe62f3e9cd78991844f246945ee484cbdef2a4c1b001a017cafb3acc57b35f7c08dc44", "Expected": "00000000000000000000000000000000021796fd6ef624eed7049b8a5c50415cc86104b2367f2966eb3a9f5b7c4833b9470ef558457426f87756d526d94d8dfe000000000000000000000000000000000f492dca3f0a89102b503d7a7d5b197946348e195954d23b8ab9ab7704b3bccecaa2123b8386662f95cd4cfdbbb7a64d", "Name": "matter_g1_add_87", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f00000000000000000000000000000000127420ff97df415e336cf3e24c39c161fad630c45c7ccef80f1831c4f5ed54da12f2c49a161e72bc70285fa0498e46d00000000000000000000000000000000013e605c21014f72364f8bff392ce64a10078ea537237fa282d5dd252ba1677b84b8c15d7925e54a4ab36f1feb13d3064", "Expected": "000000000000000000000000000000000ae916770455b0a63717e81802f5a7fcfbcc3e260b7adeca02a61a520c338d495eea29c4f070fd6efc1b8d23eb285e4c00000000000000000000000000000000134784e092744df573ba78f7d6f3cf1ed19491a0fc7ddfa02d3ca043bcf102fd40c33ac44b03a947308e3cc7af41c2df", "Name": "matter_g1_add_88", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab40000000000000000000000000000000016f41e8b098839944adc12481e5f965657a4faedd4f4cdea51a9597a6a0356989e791a686d3d2ee6232ab93683259c6b000000000000000000000000000000000d27b4a56b2cc2216e61eb41061f9a586a704652704906f7fe0eab869ba00d34205ea66f7a02d337d08b916598494e52", "Expected": "0000000000000000000000000000000012842c9d7f4309f6e40124a071d317f5597de419db0d5a8e5324a517f7b61dfdeea2fb4503ad7cdd8deb8aaa5c412554000000000000000000000000000000000ace4d9f98ee6e8a4416ef14d64f26dc49e102e69eced46ef829a352e58e8c1a7e1f083e3f4fc07f24ccd1685dedf215", "Name": "matter_g1_add_89", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000019e7c8d182e3b674dfa21539613f7de5d4872d4f4732307a5c6d95ada7e81a01bc25bda34e0b46634e0b0b32cd47e8ec0000000000000000000000000000000008149237de73ab46d5c20dfd85b07f593c0caf2e2e364335450e3ebb478a9f6b9ac0af89174dffd92eda2783a5271f01", "Expected": "000000000000000000000000000000000875289fdaead079a283aafe4de7035c88662642b6bba389b17583f8e3b5801dada6e46bd897af961997665e6ed4a55700000000000000000000000000000000050a6b9c1db35865df0a042d27a042ff4b8d3bec2fba6a3a28a71c5a574620dc05cda0e70932ce9b8966e4592220c147", "Name": "matter_g1_add_90", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6000000000000000000000000000000000c0f33f2d76366af661d6fa58a8b5aab207d35ce03899e495f7ddccedf201d9816f270468b207413a2ca70380c798fc60000000000000000000000000000000002a7dc7e2b163e65cadf93b5d682982288c8f36d08b1db8e0b1cb40cd3c7231f3f1672da42b4679f35db2076a8de5b42", "Expected": "0000000000000000000000000000000019ea92820dcd442358db359146797aa82beff6154946b1ea14dccae05e8252b776b817dc044a20764e3514cd22799c0b000000000000000000000000000000000ed929fef2cb11e8b6b9b5d52bfde82080eda747f0c82f33b9cb87019476f0c128e6b918a4486172dee2884ba538ae5d", "Name": "matter_g1_add_91", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000118fb45274a6b0ca9fe2654821e3b30caa46444f7c64b1921cf16dfd56a43916947d4fb6968d718a59a30ed38d65ce3000000000000000000000000000000000110e8e73e640bbea6927cd770baaf887c8e0e0c58260bca489c39b6dd7a24ab8c0c0a2495133d8ff8c7afb9790b37faa", "Expected": "0000000000000000000000000000000009452bd0a167683e30c673ffd4e750c66a81edf309a8d2d6dd915c358b30b0ffc001c4165b1b17bf157a0f966bfd91d00000000000000000000000000000000015df0b1ee359dd3e35a7b2c33edbb8e92b18804ae3359a369c6a529f5561298e6be9a3498c9477f33353124af7e91968", "Name": "matter_g1_add_92", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000005dcb54cdf9635db275540c16307fc9f07b4ca5cd91e3977e4b95b58e8103e40ed9fa74752b2a43d95b6acb6f5fcbf440000000000000000000000000000000007ef8457752a47864ef2698176a53990e4822421ecf83b2716251e3ce69151ab2767d4a6611a0a6e0e40a57164ffb94e", "Expected": "0000000000000000000000000000000011f1ac702a06699dd64b63ebdd8b5381578f63b603c63c3a47413fe764af239ab7024712320f3ea3daefa6bd3cd3dfe9000000000000000000000000000000000918bb83a22b4fc66247e007c17155c4c2ec6326131c10fe04a5f9b82ddeca3d21c7c397a70a3949fda4d766540c85ff", "Name": "matter_g1_add_93", + "Gas": 600, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35850000000000000000000000000000000006d3335e092616363e94436bb68be89667c706564ba687f4a3494fcf7da62fd9ad8ae68cb76524926c261983711a14ad000000000000000000000000000000000f085a3d013592c402a380e2e8d9019864a775e7b8e8b94603c8cc1eb1def1e91075fd5675f76534397e2a7d76c2331e", "Expected": "000000000000000000000000000000000344951ccb5e60d1838f7793fcf8b765f5f252b69e1cfdb4bd3c20692c8ffa01afbda6950974a65f6ac74afb9da5942e0000000000000000000000000000000014f5f0e6b99a04d1c5c2adf96c53dd41f8c01aab8db4f0e6d7fc5eab27f6c03c429632db4e1c21467c09d8a54066a4d3", "Name": "matter_g1_add_94", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b0000000000000000000000000000000019e2ed6e9757e2339d013078fac91c966045f7a1416a56135d75e603c2021a8bebf4acbf6c0d5ba911f66510e9a7ad1a0000000000000000000000000000000008b8585444ffb3bd4fb6ee23e8128142aa72fd574a506151a0eea8979cbd694e03897caba63771b0490d46063bc5bb57", "Expected": "000000000000000000000000000000000a449fb0da911c544887b24860bc5fcaaf054041cc80f16bbb44c796520bee454d0d06f84fd5aa179a44fd4fac9f144a000000000000000000000000000000000fca81401349089caaef9156a86c64271c77235c9efd136dcfad9894450b076cb3dd1a05bfa1e62ef904435eee5d2250", "Name": "matter_g1_add_95", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65000000000000000000000000000000000f4a256b4288386545957a3ba28278c0ce69a8a412febfed1f952ca13e673822bacb6b7751ea75893b680ea363aab66400000000000000000000000000000000152379d006e74798199f83b0c6c22a98440ef653d7f0a8c5e3026bcdabec8be59a3cc291ba05860bd0639c5c5f5bee26", "Expected": "000000000000000000000000000000000c427721953e139d4f12ad2a3f8f91a4caa49875a87001b619c8a6e909a7da8ddd9dd026bf56d5f85d49fd17527106a800000000000000000000000000000000018add2816914ef51a289e707ba0224fcf0b7bcfa4001487e90dbdce53f1b596e1f5872de32fcee6f63bce4484ccbef7", "Name": "matter_g1_add_96", + "Gas": 600, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d220000000000000000000000000000000012dae9aee13ed6ad52fe664bf7d2d0a1f134f0951d0d7ce5184e223bde164f6860967f9aaaa44fa6654d77d026c52d2a000000000000000000000000000000000f71889d64ec2f7da7319994883eb8bd1c753e6cdd3495036b630c35f07118a1bc10568c411ecbdf468a9cdaa9b4811b", "Expected": "000000000000000000000000000000000275b8efb3a3e43e2a24d0cda238154520f0a2b265f168bfc502b9cd4a07b930756961ae7e4fe3f01a5473d36ce3356200000000000000000000000000000000113403d5a968f01ba127dd8ef6c8d7b783a10d039a6b69c617032eba7122e9297f3ce2360c829ae64fdc9794695bf173", "Name": "matter_g1_add_97", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe54850000000000000000000000000000000004e9dd69012ab596b5d3f1f8e4593b448685fcec4ab3394008178b137b762ddf9150cbb8dbb74c8af45bd8baab9a6c4f000000000000000000000000000000001132b66a2127885774062732127951f051c9c3c9b5aba02406e3f3cd4ecfe2dbf6614ebaca3bfe9efbe4f6e5b15ba0f5", "Expected": "000000000000000000000000000000000594c808954bb930bd038806500c9e3fd6460a83554e945baeeec2354a3805f046c76aea62c249080f16ae8e70f8fa6b00000000000000000000000000000000046924a32fb3f2df9a52615e45eeea2fa3ac0e2ccd38458194ada6b4d993ecdc0f441e41d0ea37599254a06aef68b9ae", "Name": "matter_g1_add_98", + "Gas": 600, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b50000000000000000000000000000000017a81b957a12adf474a2913e8636f169ea9cd10be62c16b88f95f5caf661f158a032a9f7d249fdf2765caa1564bed0570000000000000000000000000000000017fbf2abc62dc2678b65d509e19c9c9c5d961c72565649a078da8dff98be6236ef314e9ff8022f639ff565353345c230", "Expected": "00000000000000000000000000000000002c8bc5f39b2c9fea01372429e92a9c945fad152da67174f4e478fdead734d50f6e2da867c235f1f2f11bdfee67d2a7000000000000000000000000000000000c1dd27aad9f5d48c4824da3071daedf0c7a0e2a0b0ed39c50c9d25e61334a9c96765e049542ccaa00e0eccb316eec08", "Name": "matter_g1_add_99", + "Gas": 600, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG1Mul.json b/core/vm/testdata/precompiles/blsG1Mul.json index bf61c59bb4..0e166a29ca 100644 --- a/core/vm/testdata/precompiles/blsG1Mul.json +++ b/core/vm/testdata/precompiles/blsG1Mul.json @@ -3,624 +3,728 @@ "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g1mul_(0*g1=inf)", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g1mul_(x*inf=inf)", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000", "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g1mul_(1*g1=g1)", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000011", "Expected": "000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436", "Name": "bls_g1mul_(17*g1)", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e", "Expected": "000000000000000000000000000000000f1f230329be03ac700ba718bc43c8ee59a4b2d1e20c7de95b22df14e7867eae4658ed2f2dfed4f775d4dcedb4235cf00000000000000000000000000000000012924104fdb82fb074cfc868bdd22012694b5bae2c0141851a5d6a97d8bc6f22ecb2f6ddec18cba6483f2e73faa5b942", "Name": "matter_g1_mul_0", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d", "Expected": "00000000000000000000000000000000195592b927f3f1783a0c7b5117702cb09fa4f95bb2d35aa2a70fe89ba84aa4f385bdb2bfd4e1aaffbb0bfa002ac0e51b000000000000000000000000000000000607f070f4ae567633d019a63d0411a07d767bd7b6fe258c3ba1e720279e94c31f23166b806eabdb830bb632b003ca8b", "Name": "matter_g1_mul_1", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1", "Expected": "0000000000000000000000000000000014f9bc24d65e3a2d046dbae935781596fb277359ba785808fd9ff7fd135ba8c1ddc27d97a16cc844427afbf4f8fc75a60000000000000000000000000000000017e3a485f84e2f2bdcf3255fe939945abe60dca5e0ae55eae9675dcc8d73e06d00b440a27ab4dc21c37f0bd492d70cf4", "Name": "matter_g1_mul_2", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a", "Expected": "000000000000000000000000000000000827517654873d535010e589eaf22f646cf7626144ca04738286de1f1d345342d5ae0eab9cd37ced9a3db90e569301720000000000000000000000000000000002a474c2443d71b0231d2b2b874a6aeac0452dd75da88e6f27949edafc7d094cb1577a79f4e643db42edcaecc17d66da", "Name": "matter_g1_mul_3", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89", "Expected": "000000000000000000000000000000000d7e5794c88c549970383454d98f9b7cebb7fdf8545256f1a5e42a61aa1d61193f02075dc6314b650da14f3776da6ead0000000000000000000000000000000002054faff236d38d2307aa6cbbc696d50f5b3ffead1be2df97a05ebbcbc9e02eaf153f311a1e141eb95d411c0ec6e981", "Name": "matter_g1_mul_4", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944", "Expected": "000000000000000000000000000000000ff16ff83b45eae09d858f8fe443c3f0e0b7418a87ac27bb00f7eea343d20a4a7f5c0fcc56da9b792fe12bd38d0d43c600000000000000000000000000000000042a815a4a5dca00bd1791889491c882a21f0fe0a53809d83740407455cf9c980c5547961f9ebe61871a4896dace7fbd", "Name": "matter_g1_mul_5", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1", "Expected": "0000000000000000000000000000000009e425f5bdc7df5c2a72303918e5a3c7d2fdeeb071179c533f83cdcf38dbbdb1ec5f4ebc85f3ed80757641ee3f8a8637000000000000000000000000000000000819a3e81e9ac2baacdc778225129e16344107517157ab2a7bc5e3480938585c55fd2dd7185f52251f5ab191f162cf5d", "Name": "matter_g1_mul_6", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c", "Expected": "0000000000000000000000000000000015e6bea7ecf15d91bde67231f794397502c087960fab36d905137ce2608172b5a5def065cf7ee567ca7fb08a22adecf80000000000000000000000000000000001eed472d6138fbc56e10edb62563c086fdeb9acf6de957f2367db7f1c80d2c23197c09039ed55e65cb56de9fb9be64d", "Name": "matter_g1_mul_7", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108", "Expected": "000000000000000000000000000000000220a71ad70fcf7e47df60381fbd1aba33c03a3f8537ba2029ad8e99b63c8677e0183f0b5bb2a5e1b23bc56693adb45c0000000000000000000000000000000017f26ac6ffc79ded7c08e08673336402f47ab48ef9ee2e46e3265e5cbb790cfc86f41bd1b578c5891eb052d11197c850", "Name": "matter_g1_mul_8", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672", "Expected": "0000000000000000000000000000000006b27724c4898b4f71be9727b773709a7905997d06a41ee618b7dcf864d7457bb3241046f0139c1d678b6ba6226f090f000000000000000000000000000000000b20cabf58f9c29897e20e91a9b482f5f867bef45ce0941cb8850aaa2022182298a1a24655a4b905f436520cc42a30cd", "Name": "matter_g1_mul_9", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea", "Expected": "0000000000000000000000000000000004745f9877b3a0851df5bb770a54c69d5355cdadddc9d961e2bfdb3d0531d3d0f780f462335289be29ad4c62cb1250a00000000000000000000000000000000011034a094f59212c29e3f91c48df670e7a4021e4586645d250ee74a90f4b7b51510a5048dba3b555511c327ed211f81f", "Name": "matter_g1_mul_10", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76", "Expected": "000000000000000000000000000000000841c1538c1a3b54418c1c5557a5815c9ed74f6e1c8ed70e1ad424220dc522c530e2e48affe6cb3190abb25af84b91a300000000000000000000000000000000167490a2aa6c8796736cbd364a4d18007ecfee403bde5dc13c611a214610e85af314ddddbf05ea129e027e0ae8d89b36", "Name": "matter_g1_mul_11", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c", "Expected": "000000000000000000000000000000000ea1f952d65dbb9a40209aa89e367d9d75e1b4c3a70a609efda5fbe7f5c5483163671da425545d3f1afb817c6d8c59a0000000000000000000000000000000000cd537dc11cc63dd15c8ff74d15961390eaee59b2d5697b18c1ea6d534d71551f5e195e8a0793140d821dde97dc77623", "Name": "matter_g1_mul_12", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a", "Expected": "0000000000000000000000000000000004c92b7cf9199f47008dd561e624c822a067c57fdea9d016f79e6c7956dda9df0e36b4e78715f3da1319af9f4f1fb160000000000000000000000000000000000d2851d68617567ad5308f69dc5dbbf37603c2ba48cb3759b70fc4301fdce3bdc9fca076e2ae09562396c1b8558ccdcc", "Name": "matter_g1_mul_13", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054", "Expected": "0000000000000000000000000000000006ed98add25d64f7488ed270e0899ee3633c84b73de26557c552017e7cda4cba1228c15e87efb5a740284dddb8cc80de000000000000000000000000000000000b363e14b0285fbd24eaacfe80b992d8df1abfe83991cc55b0484076385374bc87d9c7860177f06143c600503ac54577", "Name": "matter_g1_mul_14", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", "Expected": "00000000000000000000000000000000164671460621354cd352d93ca7de51828b3e6db0a37d2894a0ac475a5facdbc3ca5909d3bd7553271dadaa68b7474e2c00000000000000000000000000000000188827c6e2f4e9796c71703ba53ba2ded71bd6e8280e047fb6ea440b8dcafa7c4252d26bee1780ac67790e0d603c8ca7", "Name": "matter_g1_mul_15", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf", "Expected": "00000000000000000000000000000000023b2129ac67abc79966102ba223b982d40ca83e9b1ce33dff681c751b3f0c692f8bf19fa0394eae190767899829d1d10000000000000000000000000000000015449c6b5ee2c9f8b28e9732c9ebf6ffee5048263f7b5050a5ac9a76b034931a5c034f91d24b461636f5b116e37a26a5", "Name": "matter_g1_mul_16", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da", "Expected": "0000000000000000000000000000000004edac7b03b5861d178bb4aa34e795c776fd95e7c0980f19d111ef208ca4854f73a3ddc219bb6bca173dec67b0e863a00000000000000000000000000000000004dbff672368f86e048c3e33cbe90aba570484b4ca2221f7f6adaa1738c369f4c02c0a10118e84ea8e53cfbaa10fa48b", "Name": "matter_g1_mul_17", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833", "Expected": "00000000000000000000000000000000169d637c52c31e4c62c9563a508869f7bb5adc7defedb5f4ba9f3eabe517fa8c0be2e44d656e50903dcab67a6a44984d00000000000000000000000000000000192b39d5cddac36940d896a738e25c25217768e1d0ca712968718b8fd9ad492bae63063b3cb168368c3df196306b6a1e", "Name": "matter_g1_mul_18", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f", "Expected": "000000000000000000000000000000001608c3bfb131eae485545b7d19b8f42de18dcea6a0db3279eac2b7c008fbead54046bf13dd63835abe9c63110e12526c000000000000000000000000000000000abb41b2f17cfcc2292c5bf559b38af3b25db40121c6a5627997f65765eee1743c204f1161abe3f71ac1fe4de6aec1d7", "Name": "matter_g1_mul_19", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc", "Expected": "0000000000000000000000000000000016e3125ae97a2b1184e2c6dfe5d9459ac567c686e65674f3b0513df6de5e80d1efbff3c254e509eec3f951b0835b5829000000000000000000000000000000001889481258d3e898ed4e4a43e74c0eda5ba26c0b7525973ca86b896969240ac5928ba58bc86ec17a47f2469d023682dc", "Name": "matter_g1_mul_20", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52", "Expected": "0000000000000000000000000000000017d8c0aa81ca6a1e4de8d0b8b3a13b1d6350f79ee8439da97a5d564d435f4d40bde99138b67284beffbb176daee92352000000000000000000000000000000000a04e0bee6b9681db56604a6dd5e41c072e84f8ee9cb4054410eb610472b96c09802a1d70e325c40c7ab7e248eb2e3e4", "Name": "matter_g1_mul_21", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1", "Expected": "00000000000000000000000000000000089ae9fc5cdba1a24ca87fe4f1207d1a36c494d842eed330069f988d3bc8554af1deee3a5c59b5e74729097acc1185fb00000000000000000000000000000000002fd95001da3011b48067d351ec8667c2b2390b23fa0948896725292311dbae71b51d6d5d57e173970bc992d11fdd11", "Name": "matter_g1_mul_22", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9", "Expected": "000000000000000000000000000000000548e7564e09c2bad9859dd63dd1045878c9b257015558b18cf5911d1763325e411c1fb8af52e8766fa7adae83eea12700000000000000000000000000000000111235351d136905fd19fa726eb6626085875c33c98067a01fde9688a5b2c289cb8e3f5d6a85d0829200a355c82f423e", "Name": "matter_g1_mul_23", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b", "Expected": "00000000000000000000000000000000165504769c7ab0d28b39f38f3bd09cd47c63b74c57d39935d1c03e262f9da0e8b0b9264b0d8e2908423fe5c74288c208000000000000000000000000000000001680df1d577bbbb66ffa10258bca54b74cd90a7b3f3d50472e70e18ef54b7a4412e9eb93e39b9b312e3e8e00a52e4067", "Name": "matter_g1_mul_24", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6", "Expected": "00000000000000000000000000000000087a52e8eadd5461e202a640024fa17e201a9f0a2984be3fecfdeef86abed72d059e8879d0be8789f2a6db0d2cf55d3400000000000000000000000000000000196fe307db05207661a5a5f8f7fb24d8fea18ef91941ea7febbc18819f49f73aef9dd1bdf4fd605e031dc04f16fa92e3", "Name": "matter_g1_mul_25", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36", "Expected": "000000000000000000000000000000000301caf675cd5359bcc274b6141bb6ac53ab6a86a38ad4f8c3233cc9c1a77723eb0de4a2014e556185947dc1ef6624e3000000000000000000000000000000000136d286e623637f12c8b86cd9fad2bed8479ace5189e064a4e12e6e641447dfb0399757026126ad2d169c05011f5031", "Name": "matter_g1_mul_26", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc", "Expected": "0000000000000000000000000000000004ce73cde58c9af5d1f76e100849b0ba3d3cc6491e76b39cf4d7b681fed0686396440f6a721f73b31fb14b4c7624c176000000000000000000000000000000000e26b15c1051d7b049e82476a30545cfa4bf0a2075681d7028797c528712c7fba7a59145c9dd9ca9f5e9b1ac8a68b126", "Name": "matter_g1_mul_27", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792", "Expected": "00000000000000000000000000000000028a89c904f63eb8e68096bd2001458a4b9b32556c93fab5e52ab26ed73d62f0489d6bf1906a62c8148d50d30222a65f0000000000000000000000000000000007e54f21e2ac6d5287289ed9e2a15d457b5dac22ef36c19cb28a6cf9a0d11c981bf6549ddaf7ddc0a59b3d3a4698d975", "Name": "matter_g1_mul_28", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c", "Expected": "0000000000000000000000000000000008c39ee7c8d86a56ad1a9dbe005b4f0d44849d6fea6bbeb0732de725ad561befd49d465a134bd1a63a39eadbb6e0bce1000000000000000000000000000000000d5c892c92817fa24afb0a0fb319ad21e309edfb6300397a215e34eb3aadf91cb41b4ab1c5273bfea6eaf33982c75eba", "Name": "matter_g1_mul_29", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa", "Expected": "000000000000000000000000000000000ba1650840e24c0f99ddd10a6c3341661e5c96b2e95cb6bda3340e7a0167c906e2f0ccbac6f0be2d7dbb3f9370a5ec960000000000000000000000000000000011638a3d9a81c0fe2ebb547808db758c7cfa8648b4835fb8c4931fd622da3a001fbce9a21d61f98f35b1e907913ffd25", "Name": "matter_g1_mul_30", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", "Expected": "0000000000000000000000000000000000eccc25cfd8c5a58b330a74b92af0c2b932772eacfe898ff3d391fad5dfba52a3940e8edfc9bef5c4de670207c8585100000000000000000000000000000000095ae48a94c92c332915b0c07511bb0d54c316ff3a0dd2509a18a21320b506bbefa76a459260efdf4c045404f02e114d", "Name": "matter_g1_mul_31", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd", "Expected": "0000000000000000000000000000000017a7f3b439a98885994a6832b6394b0ec9968f665b5810da58e3ece3d8e8694c482a15d3129732b43d4b7008660f19c000000000000000000000000000000000195299086d3b9448b26fe830522d520d132ed59744e677e6eb114ba7d7045019a0d0386cf817701ca3afad2a0487a689", "Name": "matter_g1_mul_32", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45", "Expected": "00000000000000000000000000000000063c123a3cdb92469e7e57a18eaf3e7cab1d85d64cbcb52499d2e611e6ba71c717b0ebaf4cc9208b18c925a5ec167b78000000000000000000000000000000000fa5e78ae10ed8a4dee9440bfc7637d903404749681f85bcb62444d921c4fd809a646ffe3bb7c70dc906d07c62381415", "Name": "matter_g1_mul_33", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b", "Expected": "00000000000000000000000000000000192b1497c71eb894a7509bbdaf308428e4d5899edb15f9e6e45a88340f55e1b76ee0901a830b66114deccda63a913a6b0000000000000000000000000000000017d58bd474a61ca0ceb23ec392dc08abe5697b8394fd60440cf787f15cddab36aa99c2ec2341bcc06dc1771b5f0fa139", "Name": "matter_g1_mul_34", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca", "Expected": "0000000000000000000000000000000015f72ad769cbaa2bbce0aecef9559b825ba4ec17ec5be2d9f0dbc7184383eb3e201de5163e71f1e71655acd5ee1fb30000000000000000000000000000000000194d27d9045b9760e66b578af24b282d9aeb28eb51206d2e18dc04bcb6df90553a846736afd92b23aa004f8de90bbf9f", "Name": "matter_g1_mul_35", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc", "Expected": "00000000000000000000000000000000146f12001844bb0ec185e773175634f2e56bfa7190caa851ad16443b629b375ce3967b0c936d30dac2f126343722ce5e00000000000000000000000000000000080e8e90ed0d259ad803269711e511577769f7886b425f9b7857dc90ab36438cbd7435f6eecf2328f5fb6eb56f370163", "Name": "matter_g1_mul_36", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447", "Expected": "000000000000000000000000000000001344d2c2bc5ef45dc69597e948ed6021d84f7bf2c36119869a3f84288f3bdd6fc3a0de2b9e2564a930c2207c1ee36a0e000000000000000000000000000000000dc4d15ae09642ffa17d77510fb1ad4bf9e06084e9d352f4e234ea35f33458df4f23a209e29da42c41fb9a3cec3e8242", "Name": "matter_g1_mul_37", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828", "Expected": "00000000000000000000000000000000084f2ed8573d5d04e41909d5c8ed3feb88f572726fc86d17d466276342f01503f7c8552498f8a7e96c875c4928b808f2000000000000000000000000000000000b618ca81b6ee891690099459634e011b5f59fb5c96488b0205139a65c77f15af135b3528a5ca3b794e7b2991d2434d6", "Name": "matter_g1_mul_38", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a", "Expected": "0000000000000000000000000000000014733ee8425f42a30010366e4585cbbbdde6ed602a639bd299e63c113db3d797fa01075e24a042a060a043c9e1fa79f40000000000000000000000000000000013b44e1932681d238c52e959e1e3daa7a2e1ac67252ebea0cae90e8249f85b61812b9e09203d38d96f4916837b3693c8", "Name": "matter_g1_mul_39", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb", "Expected": "000000000000000000000000000000000ba15476a1346fbe9be2720721b592ce7c111b95f0b8738495e6c28487e12fcad60006314dfe68789e60f4df2db14eec000000000000000000000000000000000b44b9a9f695c94ad206717daa3128b672924d0db83ae0d47b62b3c79428f6fe151a65a39ae411e18b128d6796b67bbc", "Name": "matter_g1_mul_40", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425", "Expected": "00000000000000000000000000000000070dfc697f7068180a7a792604d7b8453dbd393c993be9829a263ad5864c3575d3fb235692ab12a4dfa4221bc6e0c6d600000000000000000000000000000000123a9d9b83e2ca7c95de9602116b1e14d48175073e1fe766458e3fd4b6676f120adfcc5c497febe2f7ff68b1e3508e3c", "Name": "matter_g1_mul_41", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96", "Expected": "000000000000000000000000000000000dcad6e29cda2332dff09377460c7a2b9d908ee53ab13f648cd892bf68a44ffcc8cd5d501f8b068f506b506d01d3f4430000000000000000000000000000000003aa625a60932474ca3f914a3e0aa8384533723f824b12c686a64863a734d96ba13670c8b355b52b0c01b49fbffb6149", "Name": "matter_g1_mul_42", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc", "Expected": "00000000000000000000000000000000023909bac6048bff0373d27a06dbbb8aba8ddbada93f4fea65c983598307f3c3a8cbe163462484ebb88165c6b6da41590000000000000000000000000000000002162d8a498670158c23daebb724168b5379d9124b064de871674a3ecd15e6b546366287563928a1e279fb1eb2ea0ba4", "Name": "matter_g1_mul_43", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca", "Expected": "000000000000000000000000000000000f79050036c4bb6c6b8e91abb300dc49a75b32faaaeb258661c905b4d936f4096d59de89b911de294603a0e3443fada5000000000000000000000000000000000985105497cd87d5ae2698479da55f6be9bc2cf5a2093b651d7305b67e36343debaf19c266ccb55c23f3de55bdae23a6", "Name": "matter_g1_mul_44", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a", "Expected": "0000000000000000000000000000000017901e77745a98c09d6740597c40f27df841cca6dd95653a1da6d8eb1c57d5ebffa6a7b894369b6b419c61462697080b0000000000000000000000000000000001732540a1bfa4a1a851106209ce4807d7c0a33816d3742ad5e2729229f3403940e03b93121b79bb94c24f7e60539ece", "Name": "matter_g1_mul_45", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4", "Expected": "000000000000000000000000000000000f990d646495fff77d090f4a69b8af0e1762982b53ef8ae9bb955ad8b894942b85c7726587c9fd956ad58eb9e3ca25630000000000000000000000000000000007b7315e1f93cfba8076cf539aae01fd3bbe1cf92daa168a6fd6a2e7c969d35c51fe7eba04f1e0dd3e2020635f2c4f09", "Name": "matter_g1_mul_46", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", "Expected": "000000000000000000000000000000000de9d7e58919ba6386f32af53ccf36cb0b834855ac8dcc19af3c3c9522c3db2985e51ba36067b61181cb0fe8b47d853a0000000000000000000000000000000010ff0800ed1b4067f8c920462f7abd7361dac2371716f7b8648d64a71cc7d53265db6d80b26b9efddd572a2273ab1b17", "Name": "matter_g1_mul_47", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7", "Expected": "0000000000000000000000000000000011a11cc098144fe9bd42ec8845be76b6cae4b3001a79f4bbbf9f20e8ac8bca5b37ef8006c958318c3894aac7d6bf77e8000000000000000000000000000000000d5c1e6b78c40a356a35bfabfd66a81924d2eae6d428b5caacf8f3992ab980640e857e756e649ca83f5aa4bda7cd00b7", "Name": "matter_g1_mul_48", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602", "Expected": "0000000000000000000000000000000002e72f4568780fb41858edc3f5796f7936a30ee9ddc7b5034d9341614d301c7906238bfde3bcb77f063fe652a43b88270000000000000000000000000000000006f971f4a8ac554df7ae7ecdfab724410f1948af994d760c5f5977961f891ba4f4e76b27c3f0e5a1471ad017e91a9af7", "Name": "matter_g1_mul_49", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013", "Expected": "0000000000000000000000000000000014b9ef8878af80f824748389d608bc9d0ffbca96230ed590d8e351586607a614f2658e348ac172f3184c1e5fde50f550000000000000000000000000000000000630f0556407c140d0a05b10ea65de48e4866e040455ebcd54fb6ed6996a6a3ac7a94a6818ba424936fa505c2c364124", "Name": "matter_g1_mul_50", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8", "Expected": "000000000000000000000000000000000e66c8be115a941ef7adf4490faea39149a3d812c29d4afb36febe3f813c7390a715f838dda90cd73556f89abf3949120000000000000000000000000000000015d85c185cb86af3ca1c526ffa6e9459a9c699c5a4d57278f33b14691e980e0f86b9239e626fc4064890cb610f10e496", "Name": "matter_g1_mul_51", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b", "Expected": "0000000000000000000000000000000009db6ac72cdcf1f69c6593bc183aaa2b3980ff78a4417e23243f81243987ec6f2636641c9e9c738c7af2a1e9f94149d0000000000000000000000000000000000ca7537c04c06607e42403e84e7d9e55b2a06c730ec342f16d03689bb684918e85f637e7a6279d95cb7774f106139d0f", "Name": "matter_g1_mul_52", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244", "Expected": "000000000000000000000000000000000620b092ea8cb718ae9669da4ff2faf639fb5e657b7759fdf292e6d841b51545afbabf95a98601847f64fc7367f872ff000000000000000000000000000000000a14bfc0e328310d62f116652b1de3a18282b122e0e3965619a099466986a546b73696274e12bd395224018a48b3d80d", "Name": "matter_g1_mul_53", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83", "Expected": "000000000000000000000000000000000a633928be3f3bb4c94cf4d8d7a8169779f8bd4bad31ede895937e8e8b0ddea956d255776141541ef5791aa3a0bc6d360000000000000000000000000000000003dc3b703753a7b8ccf7676b04cac8021aa311233a99e8d5290655d2f84555dedff62f9f81322307b538c3f3458f6313", "Name": "matter_g1_mul_54", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852", "Expected": "0000000000000000000000000000000014911a8b41cb65cb7ccb940a472cfa58861f1a506a4f719888eb35d48ed9774ea0a0dc3ba38760253bedb4a1acd0963a00000000000000000000000000000000031388c90440f22cc63a1e9450256e5cfcf2f7448641ac66b43d542c4b77e9c590b957efdb1c6d75846b3faccf033276", "Name": "matter_g1_mul_55", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c", "Expected": "000000000000000000000000000000001968070c01f0aeeb42ab71730f5b78ec122c10ca9dac1764ff5e916fc85a5eb5ed406c03263c57858fb03b15ac0035550000000000000000000000000000000012ecfee330e1cc8006c73e9d41ac1947b67f8704d12faf8c0c05c2519dca68be7bdf88a58eb4825b35a1d270554d6ce9", "Name": "matter_g1_mul_56", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0", "Expected": "000000000000000000000000000000001469e7ab4c3740701927da2b0e34508a73387aea671857b042dabbc65cb849f8c8ed0b7f8c8e37f80aeee98ba953f4e4000000000000000000000000000000000674212f9f8e1419608ccf1a0447533fbd6fda87a35cb9fb39c8a7daf5d12f450c12bfac9e9f872b2643b1f8f201439a", "Name": "matter_g1_mul_57", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45", "Expected": "0000000000000000000000000000000009c756aec59a68832728b1133a69f0794f6a082e2f0f161e488078bec7420a0da19e812def625df9b12aa36d94d8a38600000000000000000000000000000000014aa28b18771ca07b7627446eb60d53bf4837541da661a0e5cadcfeaf58f5a650a39ac304f48e45d9b714cead9ba5d2", "Name": "matter_g1_mul_58", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f", "Expected": "00000000000000000000000000000000153548fb1d7f1721c7fbdfeb167e1c060a90aab8f7b6572f4a2707de91b03a7b5e68f792a18d940167ae83d1380d6653000000000000000000000000000000000113bb747eab3987cd195e9eb755735698993332d517890f4e3285bf7274f8579ffcf84908a4758f0bb932021f2c76d6", "Name": "matter_g1_mul_59", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d", "Expected": "0000000000000000000000000000000014ca98181489c96227f8052a77730ab446615cb7b2b00a600cdd7defe8b3ee1cd53a6d98892ffccda5fd4916e0cf5886000000000000000000000000000000001567c3207cbd42c0445ea96b464dbd9099b85f5df1932d152436c936623d92fdeb009e69919368134501fa9363a0b1c4", "Name": "matter_g1_mul_60", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a", "Expected": "00000000000000000000000000000000139d093364c313d400603dba5a79479d566245a397f88aae748e110e09e7ab6dd271b8c37a90b86f6b48490ec1d0d8f3000000000000000000000000000000001099d4cb400f2d786dd2dd5d162580d2113c8405f51e8a619a6894d86a7f7ceb237289808acffa274069c24ee27c860c", "Name": "matter_g1_mul_61", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997", "Expected": "000000000000000000000000000000001247d4d3b1625ffccd350a9fc9759295637e91d9167d9bc72bbc1b60b1abb71dc29595b49ee1edc778f5219416bcd0cf000000000000000000000000000000000dfc69cdd0e4e126208b76a4e5fb8d032ae93031dde7da9bb1358507d4480881576c5d7cb7f0b3fa3032c0151650f2da", "Name": "matter_g1_mul_62", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", "Expected": "000000000000000000000000000000000150849c60273de83f9ce2016238c273359ecf486adeacc4450e1d1a6cb79fc0d0fb38974489375d5763da8a5f4e743e00000000000000000000000000000000157ec6c2dd68dc5fb3cef4e935fedb74e1f0e856f1d75890bf995a08ed6b53b52e2e0d412ae190365b139101e7fe040f", "Name": "matter_g1_mul_63", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d", "Expected": "00000000000000000000000000000000024b59fbec5240fbdf3fb4e565bbec20f26edbc2a1bf7ecaaeb5278ed9fe13d1e360fa298e2d3f9b2880b00aff827f620000000000000000000000000000000013ca56975d9fd667bab347ed67fb96a433d57836ca4069976e12459152e1369154bd095a15980880e21fd02b1d7e3156", "Name": "matter_g1_mul_64", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616", "Expected": "000000000000000000000000000000000aaff66eca5ddce81533afa27e2db1c25a2c6f0dc1dd7c2236d4c89cb9d2539e109cd1362dbfee86397156c3703d44e60000000000000000000000000000000013598d8ef4470998aec290e941576f5e94d696f7f0be40e3131b516a1679c5b0eba74dc9ae00ecb8f115e4613a50f3bb", "Name": "matter_g1_mul_65", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af", "Expected": "00000000000000000000000000000000163cf5475fae000c38e59754cd29f1290ab2d6550552e9186555d1ce2960b7dca5834e0347699d2869b8c9bc42f6f717000000000000000000000000000000000b21bd3bfe50e0536135a910359527f80c130a08029c24f990c82f02727def21973a20a2021c95aaa3a7c8a980b44f33", "Name": "matter_g1_mul_66", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145", "Expected": "000000000000000000000000000000000bc3667c38602e7e1c018cc62933c013a9e78c375b50ba06f0c3d34fead5ec8a9658702a0856625a712520ac99afde230000000000000000000000000000000015c6b5487a52b41ae1a4634c8675f7b847aa5d319ee9eec0c92fc06d8e92e1cacc90ee394f8c90ce3e2c00307f53dec6", "Name": "matter_g1_mul_67", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a", "Expected": "000000000000000000000000000000001358e1724cb3ec4028a63e4252eff164defaa41b21042037ea9a1e06bc1a0a1e838afc1965ee665de3da0163d22682420000000000000000000000000000000019828e11831e3e4216d843ed3446345edb357b2082b7947fe71932dfd894543928ddddd8649d32b4f1349f63f60bf095", "Name": "matter_g1_mul_68", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0", "Expected": "00000000000000000000000000000000130e09c096ce8ba86ae71a817426d929c7f9f8bfe00e76668b0041e935d1531d6f58e5eb743df3cf86fe88bdfda8c8a300000000000000000000000000000000187b25d8216fa3851bb6fbace998bf3f23dea80dd6e1cd94bb6a72d335702694804c6ef3d350519c5e781f941bb72f92", "Name": "matter_g1_mul_69", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258", "Expected": "0000000000000000000000000000000011e61e5158d9a7c59a5007732a76e27d14602e15159e8f62bd13be8b44c96736af5a77495c3da55c8244af6e60eb4f2c0000000000000000000000000000000008deda8447009898c89c6766e8add105892992585724d520c38d0d4f8c833f88d8c331e11b291b6def6847bfa9629d2b", "Name": "matter_g1_mul_70", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e", "Expected": "000000000000000000000000000000001182f2e45f06a729f82442ddb372f2eb8dbfccf12edd8df0764072c9f14cbe001893d932e89b948a643981ea8aa4fa41000000000000000000000000000000000910335dbdbef74b844a6f3b879d14c23c711ff2362213636ddab7eb1a44cd4b687659f8dd521c134b56bc4eed0ec5bc", "Name": "matter_g1_mul_71", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83", "Expected": "0000000000000000000000000000000019576d68ce66218d4c9e2e6fa9985451eea46ce60b11a74cf5ea9dbb9d0e8741d11436dfd77b0a8b490f4882cc5b416b00000000000000000000000000000000088ba5153e91738f7524034a2609848652a7e416fc68537ab2c16b6699f69695c62e5724dfda2f3b4f90277f5005bfa7", "Name": "matter_g1_mul_72", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07", "Expected": "0000000000000000000000000000000005720fd4bff4da704edb7e317e3d41f1d1f45e3c1f22c1b98ee0b6875af414f6f58793e8ffd5c89bcec2af711973ca1600000000000000000000000000000000051441e34eed472766186a44b2028d86eebadd597cb7e3fa4f935d30aa043f11fb18670b31f0a3b8aa23bc8f05361064", "Name": "matter_g1_mul_73", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6", "Expected": "00000000000000000000000000000000141a0eb238edd1cdb670737d94f658fef728691620f9c6d98e34ed8bd166b38ae6912b5bd90ea21b091766ad27d689480000000000000000000000000000000002d0e7d2584586ab2f08cbd419df3defab53a287ca467b6b081e474711a23608831c1507bac4f328750731b99a06c6da", "Name": "matter_g1_mul_74", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659", "Expected": "000000000000000000000000000000001227b7021e9d3dc8bcbf5b346fc503f7f8576965769c5e22bb70056eef03c84b8c80290ae9ce20345770290c55549bce00000000000000000000000000000000188ddbbfb4ad2d34a8d3dc0ec92b70b63caa73ad7dea0cc9740bac2309b4bb11107912bd086379746e9a9bcd26d4db58", "Name": "matter_g1_mul_75", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d", "Expected": "00000000000000000000000000000000187cb196679b6baf78a7908c37d7f31a9fcefa90b7cf165d0748a358e6dd86fc5c2d91ff1c4429a563b5962b821cbb01000000000000000000000000000000000d94711dc6efed34385579532f59964ab18b9debeac96044f3eec14cb36965f380d21d39c246e972aa2d5891ce417e9f", "Name": "matter_g1_mul_76", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712", "Expected": "000000000000000000000000000000001405c27eb28f58e7f66988a300df376f3536723e2ba5934d843ae629669485015c90a8da60ef5c00c63c0b08a00203a70000000000000000000000000000000000a62dc83ce27987849070a6022ab6a06186e2527f39ae94d5a23d2e4d234a465d50e03b0d7d175ed7f53ced0c3bbc8f", "Name": "matter_g1_mul_77", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72", "Expected": "00000000000000000000000000000000142fa228919f71f75df073927d03d9204b36a5177b4ab7bc995b59ff312034f7ff916635e27abbe775379aafc24a35c30000000000000000000000000000000014429fb137cf912995ca785902877e6675105b252a64282412798f883063824fc31cd79b356ea4e4822363b948ec27d1", "Name": "matter_g1_mul_78", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", "Expected": "000000000000000000000000000000000cf0aa7969ec44cc21bc8cca97fc8a581aecb63054c4fa3b7b69d28e0e2e901fa51c42a629145d9126e63aefe7978c8b00000000000000000000000000000000199d565f26b9c6496a4115eefc75f1066480f498a50314b396685a3ade8e50ab03c7f56316be2bcc02dff8b11ad5e4d9", "Name": "matter_g1_mul_79", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b", "Expected": "0000000000000000000000000000000000bf4256ce2a2a976e35a9eb266d11dc53d043f6fcafb47eee06e120457ea56decab47ef22b251c6cce17df9a7d91e3300000000000000000000000000000000152c438e11fe1d661eea7c631e04e02eb9204ebe52cbceca1ab6a9b4c889a1ebdda01d7505df29fe2204ef5787749a63", "Name": "matter_g1_mul_80", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035", "Expected": "0000000000000000000000000000000007754a49dcdde1354412d3fe2e108675fde8a1df069c86be54c4bec46338a0952aeed50842c2486ac652202c26a1861c00000000000000000000000000000000023fe3f5e6786e339002e14ac5c9fdaac3c012526b33da9ed314cdb145f9279a71e306f5d51243a0f0dcdf59bc5d55ed", "Name": "matter_g1_mul_81", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff", "Expected": "00000000000000000000000000000000141464b4326b0353aa99674bbd98853b926aa580c1e03673297bcbe9094eb1d795331d16d883e0583ed0551f064d7a0f0000000000000000000000000000000002dbbfb86c4d313bdbc8ebd266c190e38645016aca22261665dc850b0d7db8b240aacebec8af097724e5291ff43e6f90", "Name": "matter_g1_mul_82", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b", "Expected": "00000000000000000000000000000000118ab56a65ca63becc8aea3f11b370c705f32418d51fb1b1ab64bdb8f0125de2a760cf21e7ffd4d99e9d7cde1368791c00000000000000000000000000000000047674c8f3627527dbb41f51fa52c0fe3a921d07466cb2b5484e4c8094556cae247347a0a1a98499510d1ce5067480ac", "Name": "matter_g1_mul_83", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d", "Expected": "0000000000000000000000000000000000d76cf9fa103355e6f5cd4baa3420e694f252249aa6171569b70cb43c906eae9b60bb79b41af8dc714bd917638bf538000000000000000000000000000000000b9272015e64f292d7b76867714a55d7223bb026f354b20109e81122fa13fd0426bb3aec705b477e7b9560c5a99c9d60", "Name": "matter_g1_mul_84", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33", "Expected": "0000000000000000000000000000000007c87e6d92bd41b7fa6a6ca890bf0b58304875a79af7959d9226a5be2f4ac2b4531fd09712eb6299c23d7c1c5ba3997f00000000000000000000000000000000164fb86eafac39e06c2403e315bff96faecc57474bfc964736b1850696ecfedbaa0795e537b8f541159d479ac5b52560", "Name": "matter_g1_mul_85", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f", "Expected": "000000000000000000000000000000000fb01ce0567f09dc44fd473009d2467c8c16da5ea7b39a1f1dba7b3656cadd6bdf2bf68f96a43252d92e428c1d2785490000000000000000000000000000000008b4fa645f3c56459a17c912c82ca36165e730807282cabeadd9c6c4a12c8a592cbac265021ef62c60eb60df3ff61061", "Name": "matter_g1_mul_86", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c", "Expected": "000000000000000000000000000000000b9c328c8a18113e1d1f783432c857015eaefa724fa2c441d5ef76b158ee6fe0cd1775b0c6db7600754cbf25fea528fe0000000000000000000000000000000019d30c3557af1da2ca169e70625732d9a4396b51f3b4988a9aba1be62538fd51c167c83e921f4876224d361afc90eaf8", "Name": "matter_g1_mul_87", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1", "Expected": "00000000000000000000000000000000041054430741e889d4cd8e7efa41547eb624bd775fd9fb64cf9e3dc2c6df27c95ffb8d76933ac4fa1952a5820ff88512000000000000000000000000000000000e8a28f5c622482b296a43ddb607e0f25635664fa849f3d6840ed7118892106a787bc07806dfd83935754d2057f2eff8", "Name": "matter_g1_mul_88", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb", "Expected": "000000000000000000000000000000000da2286b44e7e90e19d51c3c41bef375c54688b07afffbd7c528589dbf7f012e1fd248b9067a3faae9f1c6b626a5c90b000000000000000000000000000000000bfa0a482b0fc445f7b99c52a48116383bb70d5f2ebec5b7715796fbd0da744d0467584bfc1c8a42ace833d57c167a24", "Name": "matter_g1_mul_89", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf", "Expected": "00000000000000000000000000000000148a7e9b0b4fde322f1177ced0bba34abec4a3e500afb86f9ae0a71bd75004e9c631d4cb26798bf963f7aa367f74630c00000000000000000000000000000000097f4c0893f9beadd66e4cfc6976dd277e527b1e31443e07554dacca52390066a4b37a7f0824cbaf51d3a555d696881b", "Name": "matter_g1_mul_90", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5", "Expected": "0000000000000000000000000000000003e1d921b5e0280f7370d55967e716bdacb7521547e22190e89862dbfcce02dfe7fa7927a70e7bc33448b9321de3d8ae000000000000000000000000000000001163f78de4af8494666c64d47d68a0feb0905c42ddfa024398401202d1fe0d6672bd1bd4222a8d106668ba4617683485", "Name": "matter_g1_mul_91", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e", "Expected": "000000000000000000000000000000000a94a186b96acbee87f9c1745dc301229ec750c6967262e629924227c6680b1d404e4b23d998611ad0e415610dc8edd900000000000000000000000000000000014da21c0f6930a79c8afbe42f73e048236b6d9f9ef8f270733fa1cb1012377eab37ddf2b9c742fea44020caeb95beb9", "Name": "matter_g1_mul_92", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5", "Expected": "0000000000000000000000000000000015cc6c31dfa9482c6341f816786562481bc3a4db4a4a00807a9c7c676eb32b9dc7e002ed4971f26c1dddea00d78721b5000000000000000000000000000000001303660b6bcac611b2d41a4f7ac9ecf3f0b4292f83f2fdeba300a060131322ee3c2da3ca3539114114ec8a76dee6a5ac", "Name": "matter_g1_mul_93", + "Gas": 12000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0", "Expected": "0000000000000000000000000000000009f1903e9a7d275487a503b9c968cd86823fe6667c09593b60ac2c88f306e20ccde32eebb5942a03fabde9195c5c500200000000000000000000000000000000179b41dbc2ede95ba7dad512329aeca9ca3bfd4da4b9620070d76d8fe8b49ad7fa92358070dd5098a2eaff490641edbb", "Name": "matter_g1_mul_94", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", "Expected": "000000000000000000000000000000000f9736431073987708757d61927a45cfec471c8366776e140f62d805afd948fd132c4a5f4049de3a1474d0cb52c3c25e000000000000000000000000000000001515b057952696810a90dce1ee8464fd6370e8af5434a99333eacd1fb2884f6e8c568f887030a4957ff6d24ca02f4657", "Name": "matter_g1_mul_95", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4", "Expected": "00000000000000000000000000000000195460b2d59df32f9f41eaef1139d45f0cb8f35a7982c38d356a8a8412f25e600580026d2d908b0493edba5dbea85f5c0000000000000000000000000000000004b339d62b3cd4cc966c6b4038adb302f997a16d8a6dfebd153295de08e57d1513cf0f16d82dc450e4d6f52621a42fb4", "Name": "matter_g1_mul_96", + "Gas": 12000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125", "Expected": "0000000000000000000000000000000012cf2bcb79668067b7a265672ca614405868cf189ee9789b9e1e3186d231176dab5fea86cc5865392db8c75fc5d124c900000000000000000000000000000000121bf40feea00e151b718157b8c024f126762d84cff20aac08e7f2a027ab88b33e134a410c2af279a39618f7d21482a0", "Name": "matter_g1_mul_97", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b", "Expected": "0000000000000000000000000000000013a530f94e7600820dbd8aabefde2acb8b3c74e833457102fbd297317eb532c0622636ef9e9376fac1637dc745fe895000000000000000000000000000000000139eb14d3b69be977413c832bfda234348186d46fe177154e34fe204f62ac79f4b0f59bbef39b0676d81ea42a0946fb3", "Name": "matter_g1_mul_98", + "Gas": 12000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf", "Expected": "000000000000000000000000000000000bf700422a382546a74376b0292f3a49ceff5597f0d2b726b1ff099bcda7ba92238a21db12eff5c314a29dd2387bec850000000000000000000000000000000005e22e3c772f3634b1ccf4e311241977eb20e7269540ef22d379de26ab80c58461dfa3b67848e0d584fb11de1917949a", "Name": "matter_g1_mul_99", + "Gas": 12000, "NoBenchmark": false } -] \ No newline at end of file +] diff --git a/core/vm/testdata/precompiles/blsG1MultiExp.json b/core/vm/testdata/precompiles/blsG1MultiExp.json index 114acc9b24..62b91f6f4d 100644 --- a/core/vm/testdata/precompiles/blsG1MultiExp.json +++ b/core/vm/testdata/precompiles/blsG1MultiExp.json @@ -3,618 +3,721 @@ "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000011", "Expected": "000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436", "Name": "bls_g1multiexp_single", + "Gas": 14400, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000e12039459c60491672b6a6282355d8765ba6272387fb91a3e9604fa2a81450cf16b870bb446fc3a3e0a187fff6f89450000000000000000000000000000000018b6c1ed9f45d3cbc0b01b9d038dcecacbd702eb26469a0eb3905bd421461712f67f782b4735849644c1772c93fe3d09000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000147b327c8a15b39634a426af70c062b50632a744eddd41b5a4686414ef4cd9746bb11d0a53c6c2ff21bbcf331e07ac9200000000000000000000000000000000078c2e9782fa5d9ab4e728684382717aa2b8fad61b5f5e7cf3baa0bc9465f57342bb7c6d7b232e70eebcdbf70f903a450000000000000000000000000000000000000000000000000000000000000034", "Expected": "000000000000000000000000000000001339b4f51923efe38905f590ba2031a2e7154f0adb34a498dfde8fb0f1ccf6862ae5e3070967056385055a666f1b6fc70000000000000000000000000000000009fb423f7e7850ef9c4c11a119bb7161fe1d11ac5527051b29fe8f73ad4262c84c37b0f1b9f0e163a9682c22c7f98c80", "Name": "bls_g1multiexp_multiple", + "Gas": 27504, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000000000000000000000000000000020590000000000000000000000000000000009ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e522400000000000000000000000000000000032b80d3a6f5b09f8a84623389c5f80ca69a0cddabc3097f9d9c27310fd43be6e745256c634af45ca3473b0590ae30d100000000000000000000000000000000000000000000000000000000000b7fa3000000000000000000000000000000000c9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b6000000000000000000000000000000000143be6d078c2b79a7d4f1d1b21486a030ec93f56aa54e1de880db5a66dd833a652a95bee27c824084006cb5644cbd43f0000000000000000000000000000000000000000000000000000000004165ef10000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e20000000000000000000000000000000000000000000000000000000173f3bfab0000000000000000000000000000000006e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb9090000000000000000000000000000000017d81038f7d60bee9110d9c0d6d1102fe2d998c957f28e31ec284cc04134df8e47e8f82ff3af2e60a6d9688a4563477c0000000000000000000000000000000000000000000000000000008437a521c9000000000000000000000000000000001928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb700000000000000000000000000000000108dadbaa4b636445639d5ae3089b3c43a8a1d47818edd1839d7383959a41c10fdc66849cfa1b08c5a11ec7e28981a1c00000000000000000000000000000000000000000000000000002effc7b3027300000000000000000000000000000000085ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf0000000000000000000000000000000019e7dfab8a794b6abb9f84e57739de172a63415273f460d1607fa6a74f0acd97d9671b801dd1fd4f18232dd1259359a10000000000000000000000000000000000000000000000000010b4ebfca1dee10000000000000000000000000000000019cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a577930000000000000000000000000000000007481b1f261aabacf45c6e4fc278055441bfaf99f604d1f835c0752ac9742b4522c9f5c77db40989e7da608505d4861600000000000000000000000000000000000000000000000005f04fe2cd8a39fb000000000000000000000000000000000f81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed0000000000000000000000000000000011af629591ec86916d6ce37877b743fe209a3af61147996c1df7fd1c47b03181cd806fd31c3071b739e4deb234bd9e190000000000000000000000000000000000000000000000021c6c659f10229c390000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a900000000000000000000000000000000000000000000000c01a881f8abc4d8843000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf10000000000000000000000000000000000000000000044496e633650ef8f6fd100000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000000000000018461a3d444ec527fcbf4b0000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb54117000000000000000000000000000000000000000008a0eb53c748001536d7ffa9000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a700000000000000000000000000000000000000031133a6c7d698078a7ec7e11300000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb300000000000000000000000000000000000001171d5c4909480aae3b110d01c1000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc75843600000000000000000000000000000000000063376fcdf64c9bcbeeff0f9f9f9b000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f535800000000000000000000000000000000002344b4be368d3b617df4aa8dbdbc19000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000c896c3f9d64341ba7c5f8a06271dce3000000000000000000000000000000000272e9d1d50a4aea7d8f0583948090d0888be5777f2846800b8281139cd4aa9eee05f89b069857a3e77ccfaae1615f9c0000000000000000000000000000000016ab25d6a997bcac8999d481633caa41606894aae9770cdb54aac65ac0a454dd0346b3428fefd837b1e3f654f8217f4a0000000000000000000000000000000474d97a9cf29e85d4a35f6102fe7984b1000000000000000000000000000000001780e853f8ce7eda772c6691d25e220ca1d2ab0db51a7824b700620f7ac94c06639e91c98bb6abd78128f0ec845df8ef00000000000000000000000000000000095bc13d5a05c686e20d7b904db4931272d84d051a516fbb23acf7981d39bffa3943d08a9be01fc48e5241cd8b775ddd00000000000000000000000000000195894e95ca3e59929612e77c1075322aeb000000000000000000000000000000000b48aa2cc6f4a0bb63b5d67be54ac3aed10326dda304c5aeb9e942b40d6e7610478377680ab90e092ef1895e62786008000000000000000000000000000000000f6fc00c0697119a34363c0294acf608eca3c680d80183a59c89b45a66dc750f818a27e3a6e136d69e7580a8afca001b00000000000000000000000000009027ceef3ee429d71b58b84919d9a8d54189000000000000000000000000000000000c8b694b04d98a749a0763c72fc020ef61b2bb3f63ebb182cb2e568f6a8b9ca3ae013ae78317599e7e7ba2a528ec754a000000000000000000000000000000000951b70c206350e1edc2aefdfaa95318368c151e01e468b9fb1cf7c3c6575e4f06c135715cc5e51e1b492d19adf9bee000000000000000000000000000333e268f0b5b1adf76b88981fc305f03ce4bb3000000000000000000000000000000001717182463fbe215168e6762abcbb55c5c65290f2b5a2af616f8a6f50d625b46164178a11622d21913efdfa4b800648d0000000000000000000000000000000008531aa42aa092a91e0894d84ff0bcec0d37cede43dec85cca80ffad335d6f69da18335869ba1174f73bb37501404d6f000000000000000000000000123717b4d909628d6f3398e134a531c65a54e8a1000000000000000000000000000000000cb58c81ae0cae2e9d4d446b730922239923c345744eee58efaadb36e9a0925545b18a987acf0bad469035b291e37269000000000000000000000000000000001678cefdd942f60480b5f69738a6a4cea5e1a9239d1bd5f701ad96c2dd1fd252f0aeea219bddcda4bc8f83983a282aff00000000000000000000000679956d49265608468757580db6b8b1821c2eb13b", "Expected": "0000000000000000000000000000000005548dad0613ef8804a347152e8267acdbbcab98a795fc0da2d9df5c8ec37e0eb32e82950fbe5f8ec330b8bffafe13e40000000000000000000000000000000014e94dbbf60d89b3f68a5a076fcbd7cc0b683eae228f5d5036ee61012996ae2d347cec19dbd4eab547fadecdb31c078a", "Name": "bls_g1multiexp_larger", + "Gas": 89400, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", "Expected": "000000000000000000000000000000000b370fc4ca67fb0c3c270b1b4c4816ef953cd9f7cf6ad20e88099c40aace9c4bb3f4cd215e5796f65080c69c9f4d2a0f0000000000000000000000000000000007203220935ddc0190e2d7a99ec3f9231da550768373f9a5933dffd366f48146f8ea5fe5dee6539d925288083bb5a8f1", "Name": "matter_g1_multiexp_0", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b10000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e90000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a600000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b2087920000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", "Expected": "0000000000000000000000000000000017479d99909c144a5a5fdfd71721f4a2ee90b2b9654e069a38b460945b9291fc74e6922a7dbab9bb12b4bff9e2d0175b0000000000000000000000000000000015cfff11afe08d76944c9f810017ecf78b8ed54096078195d65a5418f660cf9b2024646a8532e349eac5d32d59c829db", "Name": "matter_g1_multiexp_1", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa40000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", "Expected": "0000000000000000000000000000000001c143e5d7bba56a959b94955f8eaab82a92a2e2b355baac7da0b57281645c689486059fb590ef2576a7a03a7c57e85d00000000000000000000000000000000182b1e16004c7e6f55923dd0b1dfa7346d1243996070db78f45c4c0a2cef95e93c6373903b5e0dc63f171c8164c2fb5a", "Name": "matter_g1_multiexp_2", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac700000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c0130000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f8520000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad9970000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", "Expected": "000000000000000000000000000000000b2997ce4cb01abbb0ae6d28099d20e1f08c33351a6f0dce417a279789d6c581d4bc5a4a261e37e6df31a6928040d1f60000000000000000000000000000000003068e73dbbab6fddfd3c1e4fbf58bab58f15e1630c8c236faf3048be840abe316084aad7dd4ca6ee9d353ea8db536d6", "Name": "matter_g1_multiexp_3", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d1450000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf542580000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de0700000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", "Expected": "000000000000000000000000000000000ed96265e66875001ebbe888571ded16799d0bf5a6bad0abaca75b94bebf3023487a29fbe26a68f1cc90485df379845d0000000000000000000000000000000001be40cb29d8b722f91515f7e18372f7a0f77bc3ef2852c59e7533aeb67cc4cc4aab0b8e87f9a4982806124462ae94ec", "Name": "matter_g1_multiexp_4", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be50000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", "Expected": "0000000000000000000000000000000008644b6d6adf9d5b6b50d4759363901ea94218881fac2006ea391c41fed2a94645eeb3359df803d740710f0f7842b985000000000000000000000000000000001168ff1897eb699e475b8ca2930ae9ccff139d534c7cc606c7bafec0ed23a6e55c6ddb1efbb1b5f75044d0a7e122d204", "Name": "matter_g1_multiexp_5", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000107e1fea76b5f2be2d12e11082fe690f01bfe6cefe22ce67de968e410ef51a6192b5b28a89f222db7e5b5fd5b8bc7c4000000000000000000000000000000000014028a700cbde8bce295c564dfbd73294f9bb65db3db9d38312cdc31410ceaf7151ff5d9420de2a5bc8f0d609893c0612adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c6000000000000000000000000000000000a57058bc228914bbb3e3e8f6a73842533432e0cf226cc02990b9b99a74b0acbad498036d8fb72a163590c75b6041d060000000000000000000000000000000016d275fe8c7e37058f287e1646c28ad1b4a675c0eef9671cf95dfa25617e2f2d515b2fbc04cfdffd5d487b255dfca245d1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000016b6ecca57c78d6595e6b55b9360bd946b2f0061b98d931d82b03ed747998285e093c978015f0b775867ad0d8b4a1f82000000000000000000000000000000000b584f6f00bbcb2432b6cfbd4f6c88e228658887b5278e461ede804fc8a65dc6c997de30efc65b4f43e3d96717b938644c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d3000000000000000000000000000000000d1eac060ddc0a327396051c8c4dcccb77d11da05678d0720dec020d8aa29cb8ac959184417267cd7386feb1c81146a70000000000000000000000000000000003f8b5667ee4707958ecb93a1772849d5d8a4d42a2367ca058b160dbafa8ac0b98d5ea216fd18130237a1f17ce905feb3c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd00000000000000000000000000000000180152247144900b015c3db2d8b26d45a57930a5ca988c1fbf74b63b48afa149347a343f3fc6b1f31ddd6de079391efa000000000000000000000000000000000b6f3ae16d2a580ae06634455302db85fa94d71def14c84cbacc5ef98335d6d87faacff7a9bc14dea342a6a80d9bdfd661301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000001918c4f95a0d0931ac3f254cd61c10cadce5cb9e1ef352edc8e5944c8aa8ecd90c403ed764ef42f646c7ec5e3126a140000000000000000000000000000000000ed644cd065411c63c7d054a57344e7a909e1d0a6b414bccbd356f15d16fc1b42c681cb6b36b143e91b31866387fa94395cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c00000000000000000000000000000000070dfa1dda5ba02e94b29a63f8eb571ed7e8b0d037a0203af9a8350dacec092be1bfe33f4134b2afac77b9a36f95208000000000000000000000000000000000019e11a80ce3f9b3321cc6fd1ea2b314bf0c71d0dde80cd5b4de5f0d974597f57036613829dc777a6f6ecd6f9bef2f85fb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e000000000000000000000000000000000584b7ea99ce0398473167289d34314c60ba913338b0bb690cfbb013496d24854863237a4d716437dc6ae33326240bd800000000000000000000000000000000065964a064e4da56471c9aed383e6eb38b58b9110a2cbf991d6dee869d2f1307cf7273d203d941ead90ed67c923dfcd5bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b860000000000000000000000000000000007d6061bdf40745ef7573917e0e19f240b6e917f7cd4c47e01969b9afdc6af4e3c93e0f1dc2d15790bc2e6f182c01f680000000000000000000000000000000014625d3f2825121a907b570e9aeececcf81137f40ca6d0c00d709ba9931e403c0c2ed262a8f4c2b24305dcd3185b81b0e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d2500000000000000000000000000000000087b5d6595554184fee36be472a0ddb9ac7f9beb20817647fa9978b2e0c3549ece4f061b58054e9191ff3f120c12077b00000000000000000000000000000000168d8d995c1fd032ca7b0aef2ad5c37ef7c7cef8b61ab8fcb5ea2d449455bc75b1b85631fd2ff8f5ca4e5880f36905ded4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b048000000000000000000000000000000000f44cda026dc5e30eb06f12307bd582b271ee695fa68fbff48674c0499dcc875d617471830958e31bcd2c883e97a9e590000000000000000000000000000000002977682ca8ca450df2ac3c3880b1235e0ad8436a36364d319903fe2ca2664e05a70840aaf2d62531cd8c4ba4bfae9124c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d300000000000000000000000000000000107dd39f779801f608cceb4784134894d2d9aee37cf328bb764d8afcb6d1e0f1387b36bf5b7b335099849278eac44e8200000000000000000000000000000000045c985714b519061a9c8d8c9665b582abdc4116a48a70e0d3c4a7709568aaf011aa8ecb893ca483878404b3f8b22e41a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae", "Expected": "0000000000000000000000000000000005605f40a1116742ed53aaf7092715355ba8e41d5d49909f79ec9580955c4a9e696fa30df37e4044de4d23fa1c4121530000000000000000000000000000000015acbfdf914883d16918f980aedc2dfddc428ef8e9897776443ec3cb091f6cbeea31634ef5ed061792710d5d2c87b8b1", "Name": "matter_g1_multiexp_6", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001696e814de192623543f822a22344258c8ef9b92c178c4bf2404b38f39151828bf518a868cde3db5cffb745f1bf0023900000000000000000000000000000000125bd1df30f4799271a9db03842f98ccb5a5c3acbe813b8324fefd06e3c8ec4e2c0be8c2d24328a2d1169bf6ea0ce46fb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff00000000000000000000000000000000138dc5c034135105c8899eeb61ab54a84cf02919e5650f34518f941aea5bbb6f9df3ee6bb2056d0b9e060158140f990a000000000000000000000000000000000eec8442c8656ebc4696ee13273b12f3e362862acc3b8ec6f2b53f58f74ea23b33c79fbbd2058ad205f4932db2e87ae9064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb334610000000000000000000000000000000008f7f45310b5638221cfc9dece18010034c00b3cf170535008d60dc7ed6ff90bfe87e03d94e9a38201699f0742d8973500000000000000000000000000000000185b62a19864e21e1bef19fbbc21863c75f558bcbfa1b948bf939876188f4fbff5d5f4f756e0ec5348e194bb4f70706ca3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca0000000000000000000000000000000019269dcdf3772ae4969b68a0b4f87c5097aa8bcc9e6155638c3de94fc22b4965386be28e059bcea69f993cc388ea9a79000000000000000000000000000000000b95f44ad9f14cb5e3b9a338d0e4345153c4ad0d42aa00a4c12df117b89d9cf8bb556041d49f94b8f63108f03c56a449bd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000011c86d420b6d8820af8a3ef511d79aed7c82ee08df993a5ba479b29ef2f968919444a7c48a24ec33522e1206bb9ab784000000000000000000000000000000000f4a47f3f14a25108c2c9262466d14e3a8d1f21bd2d3d6f28f03f35bf23a4b5b494a7cafe6ed5f39195e07b1692bb6da562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000091d9fb6493f4441c6e57a5a58210a6b78e86f1a9d204094ba6fecf2c146522cf842219c900d7cb95366cf7e411ff4a00000000000000000000000000000000015254260fb67e88d0067ba7006a49814c74a5369837dc5279c0fd19c8826813c922793c96e0f708092158ac297a368ddaf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f00000000000000000000000000000000027910712cefec94f0fd4de6aa70ccc408e64d5de6b473086009c525fb6d058ea03bc99f7ab49cdbad3a42bc8ec0999d000000000000000000000000000000000c0b0bedbad83ebf6af4f5757035b8292fadae4bfbef9f3bfcadd21dd796d7e3ecdf9685ca6d4d649b2f0702a3280d40e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e88000000000000000000000000000000000b0ae8987464ea0b77201d468db7256b135a5cebea92dddb3aff10e451568e714f1c418b6d53903b89bc71109180b8c20000000000000000000000000000000003050becb4625f8e3ab2cf13dd1eb8f7eefc7e14c16934b87661adbf0139631108d241bcb1fb24c5b989f6d424cac883396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000dba43568347a96f26f2633d9fc0fb4610428a8d4992c2734b20928bf974bf642a5122995884cf11b76126ba66522c8c000000000000000000000000000000000b9bb25b0db32149736b671ceed44df71f36a33c15ed821f591098ecd873355cfb8a39fc7c7378a19d84a5b232227ab92087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000018d46d1a9ec91cc7983b29ac83fe9101c0ca36276d40743d2a6010d574fe1c16ebd9d7f0c83cad5ec2b2f652d3e6cfa500000000000000000000000000000000185f6367fcfa70e7a005c1739c0d0a19b5ec8de766037ec92840e66e2e9db18ba2356676899763183222f9957f48f300f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000016677511c781b2b97456c3059c19b3e12a865cc21ad71cf06979bee1a3128682a4a86f3e07cdbc9ff7b5aa7a9899653600000000000000000000000000000000006307c89ac36a88c6921c020d32530fb69338afbb33929e231fa704f0454d642c47a3b8d320b4266283a8571944d0558624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b600000000000000000000000000000000015a9b215eaed682e4704cd3b1265962ae0e24555a16612ac762040e1fb9b412eacec5130a6f6a31eb670806be7ed775f000000000000000000000000000000000f60035910c438c177a27e6141d0ddae706d3e852d61e37cf8bb2f03550feeefa7213545e3af5ea614c91b51bc2fb378b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb3832000000000000000000000000000000000077b7a4c4644b21ac3ef56db1163f7b2e07a817cfd9d4c6830a97d0ae0b620e0b235376d590162c378899ba12eadb5900000000000000000000000000000000022beafe4b4ab44434c9dabae45a395b5b8da15da2fc2e723c1b30b5efc95e880846844f27eb47dfae8657fa27ab64ef88ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb000000000000000000000000000000000324928100db98f5a1af039a8e1b63099214982f1729ba633b51166da97e861426bb91283b386ed4b465d791e63928ce00000000000000000000000000000000178823756c0facbd4b1cab22f346ea7d1dce0ab687263265350c9939d52abcb5a5000b3395f8268a38027410675e8baf951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f440000000000000000000000000000000008828eea92c3245eea4d60ee385734d3237e4e346e52c5de8b24c82325819be6984da4f0c1ecfc6ded5d0539a6f1f1490000000000000000000000000000000017169bab8970f47a303d2487e3af707eddaf7c4453e9d2d6bbaf953e74947b5fd40663173edd55c0d6aad7884f69a0967056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000787474664b2803e78489de6c5d5f1938e784e552bca4c32196cfe121380aad591c9fe4d9230dbe976b3ed3b3044b8630000000000000000000000000000000000c026547c94cea37793fee439c359cbeb2b985a33559ab25d1b55773c24faaf4fe511fbf7df085bf5c1715c79469cc28aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a", "Expected": "000000000000000000000000000000000a7153c6173dc27b6a241c046a2a9bc25b85c4621667d0a10550cf7a090a8fb914725201876a5bd2af09d4fefdede3890000000000000000000000000000000007aeec94a64ac629673f3be3cf660f471e3e928de0884300ca8271d42c92b965c86bfe76df61d0645e955f40cbe3751e", "Name": "matter_g1_multiexp_7", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015fec2d82f5286d2067b07d83cd1c131d3fe18628101c3e45caab07f3c775c97e1533836830959cd7e434fc3fc392203000000000000000000000000000000001050e1396a5053c902441cb33003d9c54e6b631a80e3c132dfd37805bfe87cc2ddc495200268fba0376c5fa071fad230a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c4000000000000000000000000000000000fbcd07180f265688329d72ca68cde644a580cc9d698e40f69380065110ff5a61149e4aa9f67056e0e1603bfb9b5b3ce0000000000000000000000000000000006f363a9addd63a59035cad90cd52213665069f540b6c6cb41cfff5711376885e3242b596d051a59f681941bafeca53eb54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000001164abfa75cb4d711ad811c4df430ecbd6329968ab003fa680d235ab34a9565e5c08add76cf412f132b54812671da7a900000000000000000000000000000000141c9858dd17dbb027dde22dd65f6a7cd38a1999eb7977cde87ad762425e364e1395851b1cdb41094551e530d891b0d15849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b95d221c628a77bb75ee5942c9df4b700268c90c4e6330ab5533d13d59826c81aeef7621ef6145f48bef9607d280ad2000000000000000000000000000000000b2ae1b6f916d77c31e4421f8d0241201bdab5339f95eae0e9491b4da5e226f8eb3f754d40be3b446ad6d18f28158b08aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000128989e92641f3c3a914c13e986aea1bad2c87a8c28cf156bbc68bcbb134b25cd672832f2a988f60d2ecaaa1b83159e50000000000000000000000000000000000106dc95373dfcc85d9de6b5b609554b67e8683f90ea13156c8318aa8de0a2355a721b3bd77a6329264ae671c05af4a52c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b2020000000000000000000000000000000009fd9fc9ecc0d1521696bfe7624360d11111523a4ee0e30432a468dbaf1c101691fa527aac5ab531be822ae914b0afad0000000000000000000000000000000016b317ad68ec471b0ad67be2c489c9f5bb0d8bb6b5ef909ea975cb17f5964564d5f1a61d32d60c457923e4680a218b9bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd9000000000000000000000000000000000b982f33980dea4d89b577c9f849f8b8d9cb0c7efec7e17284d45c855638fe9ab2e5bdc52ba79d06a9133f66bf0ea2b5000000000000000000000000000000000c252a2e2769d3250479091050133808a1b0fd20af2b41cdeebe7cfcf7e3a92b9ab17cdf4d370f9fc391981db76de39c915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000001769e8b5fda96ef205750826f34fdda3587efddc86f69d37001c62938a90efc23a3ae150d223ef4bf3766ab7d86d80df0000000000000000000000000000000009ee24ab483300764bccba33b55b8889b084288ffda23d157f650df34125fd803624d88f2bd0c3c3ca51bcb57b9f4dcb58fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000146001b68cd902fbb4548c3e7cfae9cf3c8916e462f1becb9918c8de42483ef65f418d6e93200e8ec95528928916bdb10000000000000000000000000000000008bef4996b8120613292dc76dcc77b07b24d4498d6bd35f5dfb80ad241ad97bd161cb2c5c96fb250b70f8aec1aee5b56c29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea00000000000000000000000000000000019d02e168efb5769416132b0457ee1ca74bd5737f9364623bb270e8218c96e71dc49403584aa0a7e6c15bf6948ddb956000000000000000000000000000000000510c0917796c7ef2e100c7656591d04c3c5968d688b36b93dd690b0a8ea55694157fead964b85a5eef1815cd5932819dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e9000000000000000000000000000000000dd05e53ee40f051037c88fd28364aba276c793047007a20f893d13222c35b24e14f6c74004c3d8070405621380553af00000000000000000000000000000000191d7f1863ab7bc4ad1ebab359499f4df75b8c7a58fae8fe7cca530c7a56e5ee1617b343765960ca4bdc0245ff997a9221c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea470000000000000000000000000000000013320367c29a4f1527e8c0f3047f776d7c892d08988c402c55e90e84b07ed7f0932c3b5fd19f8d133aa839ebd90f6428000000000000000000000000000000000f8396d819d7aabefda680c8ad51c7f907911dc4da7c5fbb7e599e7f3b758c5e7c9e9ab4de1700f72f109d7206c1be0ee1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c96000000000000000000000000000000001413f6a4ec8b21a459a4aa33ea9d92614857df629ec16990939fbb8ab11fcc919a25a10423ded219ca5b94f71377dc2c0000000000000000000000000000000014a3320275a64ede5e1221c78b421c1e4474bd499263aa21e97af103d7cb62335faf4b85b5983c5865599b709e95efc4a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000057c3c7e4cf799d716483f1e8bd4e6ec91ad9566379683c54204ce46a0e5635fd9852b0a83328386643b2017b9b551f90000000000000000000000000000000010e3d5725beabfa7e4843eeb5bcbf6e7a54b4b82fd1768a3c276bba8fb7dd25dcca7e20e74231e2f7cdf0ff50cb9cf7c2c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b60000000000000000000000000000000009d85ce8e918ddbcc47494c4b194649fdbc8de31f5f3299ea4bec7c68ff56c7f6ae916c85118553b6a6634ef9b8820f50000000000000000000000000000000000c9a680e6389d447a4884b4e134a3e025f8679edcba56bf8ea2061a00e34d38c325319a8a5efb556fc2536886e225912908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f000000000000000000000000000000000f34c8793a9ec6c34c704159d18e385dc9a127e0a9b5f95667f58e68f5ddaa272f68f5fb55e105010fb656954f25927c000000000000000000000000000000000fa1d9379fbd273b05aaa8ef5397eae24cc14f83118b2584085312986c192d2c5e3a0fd8fe5c2d82be2ee5b006413a2be8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399", "Expected": "000000000000000000000000000000001223d94bca6cb3225511b4e28797ddbf1b1e2d059c6f7c5e852331f381c578016671b5390dff3898f9048e5868b253de00000000000000000000000000000000093eb1c50064251cf043e7c7af7518b883a8f760deac99f6e10c3dc941fed718f2293ec2cecaba6d58060374bce11a8f", "Name": "matter_g1_multiexp_8", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001429e7011c17bff6df1b3237a06bae78d427720af641d2614f32cfef8c537d5ae9315c0b179af0a114a486e2eff7bc470000000000000000000000000000000003b9caa69b5495dd33139d14146919f9344efe2416b665dc262bd09ab91f3f07d1fb5eaa3c3a94606e74ee747114f347e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a80000000000000000000000000000000012b481abfcf8ecfcaed39a4277492641c420acb65ec809a7d55892091c7f76f82c02e7baf2a648cdd5cdac45113b11e90000000000000000000000000000000015d32649850a5c99a787ceb894a66b58066c9257dafc4a6cfad2887e7a19f8af69f8d1fa69258289e417954d064e63eb5307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af0000000000000000000000000000000006038134150b97e785f33b0accd0d1991c7b97aee1acf9bf671188f61a846a9603f2d3f56d2edc0564d1ea7967e112460000000000000000000000000000000019434ad4fe571da11e2de03c891d19ea2729f4bb7b7863ae0bb8f18b53852ad4dbbbe682da2c8568fbe96c6c9a7236dc7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e2260000000000000000000000000000000013786032ab493b5026cf23fdcc468ecc486cc8179c9510d99a503031d1fe39f9caedb2d42dcdfa17173e693e2344bd05000000000000000000000000000000000f1deaaefeedfac7f708092bbe3005be7c4b56499bdeb8fc742b72be7ffe4d8ca90e605502f1863d89a41ed794e06586eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df000000000000000000000000000000000aff14b235c3569586e67cf5113ac0ab32d442a1c07cd9e249149d719dbd64f8ec1b07c4241af135d3869eae05ddc0a40000000000000000000000000000000013d960e93447cf6df8bb48db45532d567dd2b0756dd674625657e5364f81b4bb94bf436b54bfe9afe8eb5f4bd1be90732c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000f85e9736fd9d3f9a839f701b6d8a6724af55ea74d28f101f97229f4b406016e50f54a0b3d2087117f352bcc28b53d5e000000000000000000000000000000000b2717e98f9fca574ad9202bd76ff6e53c74c342d1b6049fe66310040217563a4e5df460f264769418cfdc443dc31e008dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a02538000000000000000000000000000000000ed8e6113d657b2d3283e50e9d054e612793fcdebfc31c53ef4f417e63c76234900c627b7e8c433addbeb6a79bcc5d380000000000000000000000000000000012f0a3095ae16b5535192a932f188c62c3cf01d2184f8e299794bcba86d4573e423a0eda4e17b4b512c5e06367e470f6146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000002fa5630b261e07326fb51aa2bd897ab49e0b960f769e3207906a530fd759a53db8ae17fa79c8e8c889a923fb38888770000000000000000000000000000000013d49d032b888aeba7e652b200c91042f409a6a824d1aaa04bc402f94233385254a2d1f8605d15d04013ab0de9e40a94de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb9497368000000000000000000000000000000001495234b14a93a24881f3b4425dfd82b49aa1828746b06822097c8338da57db37ddc836a9abc46f7a0cd17ec08d36fef0000000000000000000000000000000013b868cdd5ed7bf90018873ae2ec84e4bc71d002483831ab7a4a19bf18feabaa210a729ebae606ea18ce16458e997497c2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf000000000000000000000000000000000f223490fde3ae0d7b94412b3aa86030e5d9dca805f6ab5b025ce8e9648aa02067fd29ab9a1915c2df7b2186f35a2c74000000000000000000000000000000000aa747ff7e24cf6d1dd2c4fe9db8c031b78830e98cab27cf765fd874fe6b7731c13af69559748c81f3915f9f3a6c63bac1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b00000000000000000000000000000000134f8ec87b5572c062f6f3b43ee896c2e019356214ad397f703a839d39215bec954f02d3f81e3442586ba9762bb9690e000000000000000000000000000000000218735ec0b5bf9b59dee7cfc70ec4c6f21aa129d604fffe824b7ed6b6346dc242757abbe98c19c02d5235da448e331d6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa2000000000000000000000000000000001510f39616d7f576980055d0547c603d882dbe85dd0b634577fae134f210736007345d035d815306db660de4a07fc24300000000000000000000000000000000064d356ad7bd2edcd3622b1fc225fe319f86b5f7da875cd57fe5adc5bdb6443c5b09d676950e2d069bd4303b8f9206928e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca10000000000000000000000000000000014d4184d69d34b8e509f3fc7e7033d76b10ba913d6109bdf842be4c49cc0c29576adae2f75e6fa054bd989e26bda58170000000000000000000000000000000019d0b70eb45a353166bfaabcb661b46eb1b7d8a59a903cbf9e43ceb6ece492e78d7f1765922e981903153072a08bde098efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c00000000000000000000000000000000087bc015b995ff8a840fbbf23db2cdaa8bb2dcbc38e12b588bdc4186a77409fa2a4cd74347f568c5b516879b70552df9000000000000000000000000000000000b15f04955dc27d19ad2a97a99e0890e6d3ad17d29f6b30f866f8cb3ee7789038abcc24c63d4525860e64593af02e39f47f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d530000000000000000000000000000000013eb2ed1d78059beb34c3fce731d42ba28c485dbc74916e373424917d60bc8c402e331e8aa2fdf70360049740e670da7000000000000000000000000000000000eaf5b5e47a2312410035d87aba7196f3f0b65abfaac28ac80accc9d87a1115b7f175e59ea2394198a2876568986fbebfb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b5580000000000000000000000000000000012d7a2e92adfff3d37ad21dd26299188e25b628a9e9d7b54d2eb8a886e80de812a32db9816964f2c0ad25d9f0aa6ae9e000000000000000000000000000000000c7084afff475bdc0a4ec265a3cb3f87d862270b6263a47d869786495abdd4316f6f154b997224d3a895010ce04151c34b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a", "Expected": "0000000000000000000000000000000019c9d9833332c6dd40c200d53b30e92f1595c22568b31882e24b8fb17be41f4d2f9692ca1e63106c59ba574f8b0764c9000000000000000000000000000000001414165b510abdf0d4a32236cdbe51fe2b5c9a31942710a07bb5664592a3f1b5c821edea44bd7fe185cb53b11d6407df", "Name": "matter_g1_multiexp_9", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000038e60d2dae22dee4dad0d9e0658741c13d165d3718c763270292602852625ac83c5ebc1a6d86c181686cd01a1891b520000000000000000000000000000000007299913b59e2d245fa489d92873b7d2bc8921191a34a0d7f6c5774757ea4eb3d667ff8f3e9293f0d2354ef03cb6592b68ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000b07454ff91e3f9707880c1713c69f8a44d70040b44d96ac74d196853c62f264ccbe6d9c8945905092d9bef665e45bf9000000000000000000000000000000000405c965e2e8cb5e85ef9e18927c7e86e63e7aeb49f45b3428089010f34eef9ff37eb005e6b86e20236dc870661dd68c61529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f81000000000000000000000000000000000557c7f55246759b901e4e8478aac7b80d37edd5d6be057e5aeafe3d8da008e48c96c17ab1093a6a4fb39cbe9364fdff00000000000000000000000000000000158908f112d7cdcf867f1a5b05062b92972c2947be213ede3a7fed7a477fd57e69e1de82164f7cbd53a3f4f4bad551d744d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c0000000000000000000000000000000001701edcc472ffbbf157b1f239968924bb91825754bd4fda9f13450162e82932b8f5f39e54ec5975dbe7dc744d6d676a0000000000000000000000000000000017d13c1f6d64af2a808c3ba20792af9ee9c626235ceb9ced3c7acb4bc864ba47e55e0945a430da47da1e87f015dc024724872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000011231262d0fcf5a4b92cc1ed62aa66a55be739eab1316219ed2bb8d3e939e25b840b75f914cdd3f07b3f57bbc07c23e0000000000000000000000000000000001eabe4a5782244ceaa57ea0b58ed1334dcb94e449b7fb905805cefb786e83af66ded006cadc651a7b2cb07c3e3fceb401a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc000000000000000000000000000000000045bb823151b691e26b0e706b8abb248ecd87107a88c728e7a627a962aca7f85d4c88df949b3c53e2d32ef18f60675350000000000000000000000000000000003342b2d1a75300ae9ffbae66326936b19c7e59fc6f597ff09f2e5d50c1942f161dcbcbba00e4a46d87ab51074320132c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000043d72d26ee669ae8e47eaa74199feb37d51f5c99151a8f854362469e5acb2c5f6d2c208e7d674efa189fb90275b835e0000000000000000000000000000000019e6f1b3137bdb49c534902abbf42893fd576a211b93c831dee90723c7daeecdceccd3eb981537d4fe729d6e48d70d6ae62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000837b6a981e486865dc4d6d0c123230ced707e2518277cbfd0be747a8c9c76be6aff8b06df76f7c801fa34d11141354900000000000000000000000000000000011d745300b20c5ff1e607ef3a42ac31cc55e8be979b091aac0396748e607f00f30ff579321f2e660e90e8e5f9efd4f77a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000740837b02d2923815914ee9cfad663eb7246ec8c56e632cdc2dce25b6e475dbb6a75ed2ca6790f5f83fd1a274832e8d00000000000000000000000000000000188034daa9801ea182b712da519f7524cbb9f641146bc0fbf77e72ecd066bd577672c1ccf28a2c4d3cb9854cb2b9e7c80f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce888000000000000000000000000000000000014ddfffbffd0317ba7e248f648cbc98fac2be9f0cc31d6476f41527c25fe8d078207965eb2382ee1e0f08a38fbff7c10000000000000000000000000000000003e492f3667da69d44b35899f425af2ba51130aa6341bcc0d4d9646cc96b090061acece81ed16c7e75fa452818748b119b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c0000000000000000000000000000000005107fd2b5b483173992b0f2f51dc24bdba94b5174c063b52c33a8cf84ce3adefe0efe08e6bf4de3e68189e495b39c6d000000000000000000000000000000000605e8540f1c7f5790c306643a68606581a16a60d33607064dad5572947c93f3846f66afae10a66cd33621c6a2dae30c6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000012eb811b231a07e27e997900be274f73720afe3b0626104a9d5aed39a3931595f2ad57cf6e8f12d5110cf38fc8e7f244000000000000000000000000000000000abf1b8abe848b91333b4bb226b81a33aff5b8f7af70108538a3c706da182476a42e0e5c2fcdf694c8a12f62a996c86c030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d4050000000000000000000000000000000009d2b5044a8fe22a957b6d1eb20454db2cff51e7ebb6357b3c6b95387b1fd810b94eab4aef4f0a0aec4e6a693903dab60000000000000000000000000000000012ccb794eb1174735b5f7700ef95ccb67691cd3673d601dbf6b2e2469521f1b2ed283f2f98a9cd601867de4640c9517988e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000003eb6e7ab6dbf66614ff5b55ed36243e1d9baa317f01aacbd7f3a015bddfd818c6764c0802e97a42063a18edd9dd091d0000000000000000000000000000000018571d50a947e56f63b26a4377678c838de7b315e655104eeee48b7d5e6f5ee5d876b3ebdebcbde4080e022cc88c995326989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000906d5a1691dcb7dfd5d78f0688e95de2e2f06cdc70f8760e43a562365939d3fa23ddaaddfd1ddfbd3bc9777783a7ab600000000000000000000000000000000168422a6171f5ae44b645b6b6e73011494dc75e98793db2424bab311990eb7730a9a45234afb78aeff7778503cf4e5a03d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b10000000000000000000000000000000011c20d0c6140e0e11d3ffb8c28c6bd80ec495d057775f6dc331c98b0b0aba17568e1ba773771c703068dcc6747187767000000000000000000000000000000000f88fde780460bd75f46f593cf6fd0aa25ad14cccc061d9ae2cd8c20398f24e76ef614008efc9ffe1d1884df1122111b5639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef", "Expected": "00000000000000000000000000000000018ed322f140a351069f18d039ebded8630fd019e8c6c401dc760ec5cc6536bc2f52a6cd5400dca0caae95e4b9282967000000000000000000000000000000000b9666fbbe91ec8bd670b2a64571a34d689eac44c5b63a43f60da08df51b003d85c03f0eab3e2042b1175af3501de8b5", "Name": "matter_g1_multiexp_10", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b42381a83d4472a3a7a18d2ba5266bcca254fade1170c6f55d442aa2a7674008fb35c58d5a638280e0ff7531617a768000000000000000000000000000000000eb5de05b5cdf9f95c5a3ad30ce068d5491006640be4c7f02b7498963b5769d516efb9a117c60c1c5fb71617d42c977142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000163ee62f1ea9219b921ae7ed0f121426fe9fb8fc0056916c81ea9e713f1a16e3f2bec6ed0e3e552a7173f8dffcde82bb000000000000000000000000000000000f5fa0e4980d3d2b92e98e76e5d67815ce55858852f03ec7b8809b02d4b1e9e1a6c8b06bd481d9d153acc68378e779fa253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b7000000000000000000000000000000000cfbefb41304008b0e7341451f13d65681f0726544f14fd1c0d02433d3c34a4769f1456960cfdb11b6bcf016b906228d0000000000000000000000000000000001adf387f4feeb3845b12449fd5294802ed30ae211d0837eff1b22c3fedf538ec7119c1fe69ed7d595f7c0fdcd54f684acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000d736d3b8b586e09d6ecb1ee2d7eb28bd68aec60234e90611da8f1e1aedebd9c74718d41a89186a4a5dcc3f7cc81e99e00000000000000000000000000000000000ec0e89da57affa4686494e8e0f5517f11532f6e294215bd060c370fc64c26e34ee1e2d77cf341226daf84791f5e3cd9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df0000000000000000000000000000000008fef795f8bfb6de5feee020a9363adb1c26fb521439e405570b4e997f55af5783968b24d2e95144bcb6b38e4ef9497c0000000000000000000000000000000004d4e31720644e8828faeaeff38985ffa4fa2f7bdaa476b5c4d7eee81c89491eedd3f4262effe118a4c204eb555abfbd05fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c3400000000000000000000000000000000195f8fc4b1ca0c7041810b02bbc38b8bcd0711dccfd80de2b2f357f4a732e65492d57f455e99fc810d6f86eeab0ac101000000000000000000000000000000000e3010ed298656b91b5aa342f6be7250cf5504fc3aa26a2c7f46f90e852fd7799d96a85b25e6066b7d24794648a81331d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee11909900000000000000000000000000000000192cc18dff89d9a94e6f0498419ceb9f21d70e42a1b9b64bea093d67075d499184d7b2106f74d31ccd1863beeb7be0a9000000000000000000000000000000000b80e940dce71be82106640d99c121dd21e99ba459f0dc8b1f11cdffaa0d8ab295b9711c23de1f4bc35120a89948b91a44c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f6000000000000000000000000000000000b53f55edb182dd08e2c9d0ee43aa3d734143b54686295410f80086d3aebf6fc681d1150e808d684f47b0eb23fcaf629000000000000000000000000000000000d73442636f4d5dd1374cfc7ab29b995420995bee9808aec29ef7d1aac08c0ee51a0390330a863295af6129b7e8171d82583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000002bd8316507e6eded2034cf268b2b4660211e6bea2e82b3e3a0902bcda0f9ae9980b401f36178f681691ee7c10dc4ecf000000000000000000000000000000000e9af98fdbd02ef62ae90f1e87c4e7a8eb2089204b1c58dc6e59fa32d001c97f22740d8a13ccab23b5a8842b693504a8506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000aef5a5d5b46d340fccdfad359b0499a5c62ff4e5d9b9d6f7a5fd6a97e96820b7fd226e7a2aabaea392869a40cd38e1d000000000000000000000000000000000865d32d825149d26b60969ca567ca85af5e280b835cf541b20b0a4db83309dd2b5700f802ed9106af73b912dcf9630b7f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca600000000000000000000000000000000153310de30b7a485753dd8443f8638c12b21083f6133a1c093648bcb566b33f73631c6fc558f32abeb0d6df8430e61a900000000000000000000000000000000005be397e9f77556ad952dba0540f46cbc7db910d5203cb976e168a7be3a3b8557c5f08d51cca9379552694a291d67fb4429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c000000000000000000000000000000000cc75210c78f2e7903b7c33379a6ab412e92f35de51a152cfd2f4a5d122f9e558b617d8a09670990b7f056e95eb058ab0000000000000000000000000000000000aee8eda7c1bedd39f97efc60af110e64662b9990257beff15ef5e7856e5ea388df058ed8aa6dd93cf5a81ba48cb88854a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a000000000000000000000000000000000f396976e55dc0c46fc4543a8dbf690b8da7b6010a03e04c9010f01abe1b3beab8870be0b6a2c6d6afdf85c6fd38d8b70000000000000000000000000000000006c60eeaa2d94b571df8a6291c2b12b2ce9f17f414264e4af2a006d6aef2d70436ef0978139751d4ccafce200f16f06113814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa04000000000000000000000000000000000543f8d9faa2b3cac2518f1462c297595ca10d8415143c8ff3feecfa58b648d0dd0c25156287b2f29f3b6f9a60f02701000000000000000000000000000000000be673141c496cdeab5ba8604e081ed3006828c7c877d8990efd29798c1ceae3093e052f1f928fac0c5cf84174283844aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000001342f79c96ba0a29de9a77cc2e10314bf2e15a7d192a90af9c025e2f23ff30fe49cf239b180cfb6f8c35f95c115777390000000000000000000000000000000011f0bfb11be253b3680817af2b929de9ccf06dc574d17cf6680643b87e5fadd06b54224f155c1393c870c2dd01d6bb07c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e00000000000000000000000000000000183eab3c2a127818862c6cb42bfbc9d59c51043dcc28c68d3fea08331323c9dd50cc34a4ef66a97f98684a5d9a982a1d000000000000000000000000000000000228f8f774bb68f966f3ffab5d0928a59707d6fb4f6ca84fed831a8212f71085cdc27b1d52909bdc005b3250f26cff3b9680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd", "Expected": "0000000000000000000000000000000017ddd94df17d52e842abacf3467f4461e345cbb680ae624f98c6885e40b17940bc240da17ed0a1a45f13f2ce4ab8dc100000000000000000000000000000000005ea5144aa5d5393b98db4d23477011638dba465a542dc28691ee2807ffc08413938ffb74e16c7afc507f85d76acbcd1", "Name": "matter_g1_multiexp_11", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000023977b65312306b1a746b94bebbe79ccef0342ce833684a273d8baf74e0ee71104d6c453acf02d0c4f3909144b1a3b700000000000000000000000000000000050494df74705eddbf97da56a21bd673e2b0d3a9cc157168b8b413a89359c9c48f09e756f8e6ecc67811d4bd8043bba91ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e0000000000000000000000000000000015862e2e3cb73ed2ba6b0b69dd9fc4c308c0a79e5cca2d2a42fe94e9b029b22b5b6aefe0503798d78d4599dd5c201cd0000000000000000000000000000000000c49723dfa37fb1592722b14e6c75110cf2252ad5170131bb72fa35bc359470bbda292fc2a459dab89900eb251e848e12943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b1000000000000000000000000000000000af2d03791884033b8293fb636b0c569d9b008b075c6c71ddd7b0c3f5e139a17e1fbb18144d1ecf491d2fc40b7369c0d000000000000000000000000000000000d680b707e32626219fba862cbb18e39e03a8b9ac78f7bde619049748f7f0e49cc0223f1111dfc1f5c851229e62a9cdc1551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b442117cecac25834a442ef457061634d863875c10e1809a3b9464eef6760f074e06c046a74bfb34f4d16255cd4f62a0000000000000000000000000000000000febea79eb8102b2632b6fe3151d9d972d5dded2893a117a6cd7e2bb662f042131cf06d04ca5c88c8535155910f9e008eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f000000000000000000000000000000000d355c97dcf055181b8c523bbdf7eabbf064159c15532bef1e1be56146d72c08eb5d6994a3be7d6f4a4ef204f0e6d8dd000000000000000000000000000000000cd6d4e6df1ef7cd5fcd360e8aac511a3aea1f3e29536c193f4c3a2ff0f3ca16ebec620cecddfa8f27732eacbea75500f72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000072ff95f5cd9416eac2cd83781acf856a0bfa567a079bd3cc909eeaf5a3fb31090e3e2ccc3acd44b6b04b47b5b8609a7000000000000000000000000000000000b7a39ab3ec7de26c86eee5d8737c7ae7e5969b03457b7b7b5720e3492ce254a63e031fc477361606a24821830d27271331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000886babc1acee93b5f96e4a0700805982657d15170c77468c77000f21978f0cc154a265de2f766d6f7f8600f378b219c0000000000000000000000000000000013cc47f0a1e5f7315e6ddb9003dbf901824e419854d234676e4a8593bc5ad4c15e8c59ee6985d0b729e7d095e9b7642416d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000567f08c96b8431a133cb284144f6ec8f7c68722f18ec257b4def0a18a754507eb477f405b8c256adb797f45ed2755050000000000000000000000000000000004945b59bc84df7b793dc759bc2a3352b3eecc5cd59bea7a9560c06ef25828ad2e9ccdc6b3beab7a71a702b829208b8556be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef4000000000000000000000000000000000584ae62e22e0c2fd733cf2093f7a1f3c763453cc34a7a7a4548d8fd43c95f13be06da4e41f257f6d38e6e6921ad0f6e000000000000000000000000000000000dc803ba6a45298075a8cf45939a61760de44d22407da6ac0d63939918daa6f78e8d0b7cd794256f992cc89b8622e737aea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef00000000000000000000000000000000090848e332eec39e026eac0e6416d1ecd5aee8b4d82712b6c113da1e7d38901470743af43bae951d4141592f6057caec00000000000000000000000000000000140f8aa557213d49097ef315a18ae7e62924a97c71139555baf08c70674031934b629a457f75bd801af579f9fe9395579ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea000000000000000000000000000000000e29d6fd73f56b4546358967d7f0080e6cad97531e3d672a91a6dd121f35cdf0f452dfee1ad98b7c832c2878b495f3c100000000000000000000000000000000050fe9818b36baa8ccef166247bc673baa8424e19a19b199ea5e9d0baf56fd68cb339fdf5d041b31545e28bb2b8fe32c773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000c13e5062ec580886d09c87c7cc72f7f19227eca99b0092a7e9759672ed1405d21fbdc8985847fa1b57129ac40bb036b0000000000000000000000000000000007d6407d32f846088759be5369c5ab66d2f512f00c93eefaca86a86bf7b1e3ef39ab85fb6c317c28c4e331a19b927650f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba6800000000000000000000000000000000112839aa4daa7b0d614dc6a555731cd4b595a0495f2a2f0f1a3b3fa1b603c36348e265145583e8bdfa8a2a26c1f822f1000000000000000000000000000000000383bcca42f2513ce42342f4bab5377ec276bf0f1910718c7203d450f15c5b6a3648a82e4cd1222109171030eaf05292d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a0000000000000000000000000000000010aa255df04dde054fc069473dbbcde9c68dbd71048b195df2b23e5471e5cd39eab5658ce689ca09db80c72e099907120000000000000000000000000000000013cfb46746c9bd13aa88a24ef3097b35ee2302e76b19ed001baee8cbe5b19c2620043efeaf81697ce48af0717a1066eec629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b000000000000000000000000000000001888735aecb7125b08f2a840957887fb5be0517788a8931fdb8d280579776c5ad70e6454303ba23908bc6fb864a4ea290000000000000000000000000000000019479631b9c711f700ff2353aac97cd0ddbf14669cc046e686ef19ff0bea0aa74b4bf771882f7226de0d4fe356301912c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d50000000000000000000000000000000003233c1edded239fd465f7f7833251b98ffed6180b56676bcbe2ed361438d26db671c03a6454a4fda34111e358eb2cb10000000000000000000000000000000003cc9768ad0576a34550b913a895e2687481c6adb3371bad5cc8f9792c61aec555a52bcb267c337649fa00293c9b4af3c685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7", "Expected": "00000000000000000000000000000000117879988edc3cc57fe19ab04eee6f9e94a30811b809afe922d63bc3d785a3a581b69512180beb89402410a4d8abf6620000000000000000000000000000000000beda376a5f1499882c560961f8b0cfc44c712d6691677ea24db18b138af8a21a5a4fcb9cf5814473b0ef7c00719700", "Name": "matter_g1_multiexp_12", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bc83829ec8e98081abac2fa8e0572e819b570b2499d4cd1e6748f48c350c392f5d52c672dd0bbcdf1469414d7ce929c00000000000000000000000000000000007d1574eb65b391475b49857766c808fa95ac2a78755d8d740d2df90bfa9aab3dd5c850d536c9794f6cfa2f004b4550c067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000022e4ed74f98d69a9bb1037a307eed57210d3ca92648ca9c54546c7b57102558ab12f5d2bb46502ba3c07529f64b72b30000000000000000000000000000000005ea660c44a9d36696a899ed1bbef1d951656f2eae553f4f124ac9cee3d7de13556a7884ffc07e20d5afb7bdb9c6f1638b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d000000000000000000000000000000000f6f9411caaf7bbed9b05368ed8bbc35a0439a5c1ae417215d10adaab203aa0a607642aa8b94f4846add8f5f8db755530000000000000000000000000000000012eba1de04ecff3405596452a4f5830bc6c8af2ab0e84115a8a04a2cf60400eb741e8eda78ef733338494fd4e7b16f812db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b0000000000000000000000000000000009898acf9cacee1f5750d54798a4c31796fc471a17c9d2ddbd00262f5a82e3ca968c3e02334c29aeae9b16d8916def1600000000000000000000000000000000017f5a3907bc14b6cf182af2778c88704fc6b02d2b47bbbd6e40a448a89ad1455f868dba330452112973ab69489534ece2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000e782486684a6c3fd7f5977fa40038e8a9ac0a8611e79c18ea5328248be9ad4d95c63ba9ce41d3b4d85701283369063f000000000000000000000000000000000a98e9f649d2431991dbad1cc7f4ea0c89a58bd7e75e4a5bf7d9a728943363777c1cf84bdb1853a976e4e66a6d3fa8cbaa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a000000000000000000000000000000001171bd468b4d40e77b8264e082cf7a168d88ec3c21adb6c33f215e82f5ff3d0d2314e0fb12d7ec93aca92532debde74500000000000000000000000000000000099bc823a44c54fd379798eed2559d95275b324481c248d452a02755e1b5a48a7b0694b637dce4c21ad7d73a63cef2a3e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa00000000000000000000000000000000190f9d82f079757ad752b17b419c63ca09e3c8a23d0f56b1e738dc8ff4d588a4a2360687679e51bd75615c18c49103c400000000000000000000000000000000191b91de53dc0807b537540e81d9219daee48ad27de9e5ab2980dcc09062b80dad2a0a9024c5b0465e04e6ea2b225d0249510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e4000000000000000000000000000000000c8f48d3dacefba0e1719f74867b539a65d640d2372ad38bcfc43548f7ad3d8a04337878529119b9175068b511efb04c0000000000000000000000000000000003c7b5c11985fd7ff7c75e2cdd8670f75de655aa81f6b99206ed8a344f86ae85d2fb14bce434a25a5ee25c903c238341713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000055e115a8a7edec3a443354b381f584ba13a5802520c54b51ade1bfc7c93c96c7cd66254738929aea2e88edf2895d82f0000000000000000000000000000000001bdf3f4b489cc22c6f57a1eba23d3348c5567d0dd1cc82924873813b92a0d0b2b90727589028b9844d351e13c6e3868c040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae400000000000000000000000000000000041fd1625afa48a446454d6613c17cc6a65b3ec8b8f2125c0eb7b8e5d07968397d43969a6579226f496d9b24dbb71b820000000000000000000000000000000006131c506f243b5ac40354f826ac1838839eee9f61301aabd88e499d40e57df3122edc8b36f0a8b16b72f9ac783efd3e17b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a000000000000000000000000000000000113a08cd0728cb3bab3886681d8cd4e5f14b3a4a7979f9929ed4d8dc77de6a65f7bbbf8a282818ea3f21e6ea59ab1f5100000000000000000000000000000000032e95b26193c9768cc9967c9710c7695f57fce8a4e089f290526842963504cc8c99981bed3cc7d827eedcf686c813c3bd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf25752000000000000000000000000000000000ac19ea5cb7169ffa2741bbef922e0ba307e2bff5eb67fbd2c1545bcfebb79948489605f3c6c072444093e996594c95700000000000000000000000000000000111c277e16440fc3f0cfe16bb81b927cf76553fad040c1825210fa145240abb0bfc8a40a016db15844b8830d4d725da3f221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000906df246466ac720b1db9445902aeba8ff5c747133b037f29b33880b3f511621a0241fcc46adb0532682feb4e8819bb00000000000000000000000000000000145b356e384183788358353a69c49332ca137e9faf30bbcd7a67434a980c27630c3f21781a36fe73e82459318b59331bba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e0000000000000000000000000000000003f2f02b7ab2d2165836349ef8f53e42d223f4f6a892e7b72db93362de3929fcbda5edc4606766fe26ddfda9d09b283b000000000000000000000000000000000feb10a6ba91dddb0829cd6b95a78958fd55cdb120a7237a2842df1a2007530775848c3976804824698a4370fb022bdc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001433eeb265f1d57027a80189806d071edb1f5ccb97da0b5e00dc75eb88304ef2eed287f5d74264245684a1677a23b3f5000000000000000000000000000000000be2d2b5fd307192ef8a0b2b4dc9970c112a236a71ee899a0a5147012a206a0274d34901594f54bdaae26f2552da481b87eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000011b50012e0d92c0f74e3b6e83d60bf77e710dc03baeedc949c1af218bcb87ca1528a745aa819a5b615ac355dec360eed0000000000000000000000000000000013cd46e3cbe008dcec36e64285173b7d545359c23fea32d3a1fa2918c5c5d671a87d90791b70a740564c0f731fbb32013bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded", "Expected": "000000000000000000000000000000000cd7a2b89d286a421808305db523aca962c3f253f6defcfee9b74bd7f00b3ca8521b520376659d584b03fc5dd736d3f800000000000000000000000000000000117b8b8a8e299cb0fe3eee45651069a21988a2421b1d349529cbaf7362425349148fa043581e5fd275cc94a4fce44732", "Name": "matter_g1_multiexp_13", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016b98dda34f703f90438f5c2624c1ccc870b18cf8eb964800ec97179f67f82c521b1cccb1b81ebd3484da1349e4c0cc3000000000000000000000000000000000c743850f15041ed9023ce296570036f96db4a510903a0e7971592348651b44afc0091c8f0d6e86bbed8bd3f6b28072af44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000007df1648d65d140c775f729e7739a807a7f430de0711671807a7154a8e5723a2b9137175d47bc319ca659faca10af23d00000000000000000000000000000000199ebb99b555fa438587b9badcf5d7858029e905b97229f1de4ecc1940ccac59503e0e1a99c9571d50ba39ac3619699bde25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea0000000000000000000000000000000000027b64caa979063b420cff77cd259e54bf86498f87e7297651f9bbad6087a8b4b704b27746db53f8869d080a22363c90000000000000000000000000000000003239455ad4ab885727a25b0cae5115d87ac9ccfd93120ffded5130ac683b3b2485fb358e3aca3b6cac4bb3da5b4210d2e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b210000000000000000000000000000000002f14454852a72159581b8a931d863c65170fa9280cb811c697fd067a505910d17fcb71b27963c2a6a02264aa0e1fa04000000000000000000000000000000000303f0857d990e90e19a076d2d331f5eb7fbcf102dbf8d4cb29f159fa2277eb413c0c10c3b501cefd9ca581ca62876c5e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a91300000000000000000000000000000000192b3fcf7dd2534f226ad51f40e7256064eb788e7c91b1155908fb752ed4e854fda44af13f0c681fcb818eb4202eb64100000000000000000000000000000000125b51b4cf8e9427db9baeee0417b02c2d296ec4adfd437667238ffe5137b85b40fca4fa705f81d0b4b6d788a8456f1fe6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c400000000000000000000000000000000130798c851758638c03f90f9181814eba97c5f93de85a71bbcc360bc53e4491e8fea38ff8c94061cd5008b0333ff26af0000000000000000000000000000000014758dbfcbbf0e1c78fb3ad4945bd300a74f2555338a009d807e2cf0e5fe582729556bd3ecb79db131ed9a72c3362c37e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f243500000000000000000000000000000000093318a1c189c8957c9736a56a4b3e8da13bc8a303303bbc106148a0a7f319e30f5dcd11787dcd3424255c7a02cd3e760000000000000000000000000000000015f0767a3a1e3c448ecbd4ac8c4c70db6daec95a1e4b3a69cb5dc10fb43f8ad030e360832f7726cb166e0fe5fad0c860c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000c7143093aea0143c58e2c459472f44b6b759a3f036aefced481eef6fb3a1b2af72ae4cc4de06af2a8a99e27cf9cae140000000000000000000000000000000019f44d1120d82e50f7da3c1e87a47d3433152b7141e9085eb54e04f30f5931d067f9ad559cf5d092dbaece723e6a724138cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d14740000000000000000000000000000000012a171d46d2bbfab83d02e023f5edb18e353ea82174d1a1653952bbba234c7de4fd5ed212c81f795e8c7a0b81e37087a0000000000000000000000000000000015dd85eecde306a845917187c404cee066038a764beaca9a58b859873b06652800291506b4c995581866a3c2bd7f19618a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f800000000000000000000000000000000176e512a4122ef10ca1fe6626cd2c839d4c573bede92092e5ca55b0bb936de9b62297b2a598a033e9a7e49ba9aabb9190000000000000000000000000000000013bf0f4c0dee3c9298192748497803a906e4192333b1ca61deff010a63eb8e4cbd63c7bd5b5546540e71bcac6000eb5380030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f600000000000000000000000000000000066bb65bbc3f8ed9cdd5cfcdb121274427ab7dff904551a60be48f8197c84400d54ec27ed25c2a09687f1067c10edae5000000000000000000000000000000000afe1e97e1dcee30959a6411328f0d69134bb4c3a0d5ac53b87f254593f7cecf3070eaa9e19de76ebc6e1052a41ccca00d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000001439b3031d7272f92c7072c6b44dd3a1c328251d34e1fcafc5f864b7072086168fa6f398d6334fe7fc56d6fc0e776eb600000000000000000000000000000000090885199f56df470628357ad224e19c29dc435ac54b8c17a7df5cdd24c3fdfb136952063dcb446ffe271ab5775bbc51969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000011a0c8f7d76a36e605f193efdb5f7899d7db5b89ab0603dd6184e69a7e51f0d7e12f466fbc917cc5b6dd6d4a0bac16c30000000000000000000000000000000015dfa17cdd22984bec570d2ca24a5ac373f6f174b66aed70a15ec892caaf92c73ad3d7ef11b2f4a0104df8ec5397f5e9957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000004bfe701f6645589925b34c1117cf62752b4e242e38bf056ef36515338a5c3698f561d65b237123677d926c1616618ec0000000000000000000000000000000011892535443daffffce0867dee36b7bc711006bc0963e6a061066b889adcde877a8dd3661250b6bc48064ed9dea304168e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d50000000000000000000000000000000017666cd38f1e7139fd032a79776301e4eef7fc22c144900c711f1568634d9712b2e3566bcfdd152faeef20b47cf6cf7100000000000000000000000000000000150c30df0eb5945ab96603b0f36120a4f697b6958a9929f6dd8d1b8a34a1d1d3f1a34bddf9ff7f1e105ca23ac34b6f7671a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000107f9378f695524614ba000d6fd1b72c5eafc4ee60c5ba36ddb72814936403fded547f8d15083186f7f5f5d94c1ce18300000000000000000000000000000000140bc17d86038d4fed0580582f55d90259b460ddaeb37a70063d09d83f5fb6c803f8b467927758cb7cc52a2a6f8a84ba2d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641", "Expected": "00000000000000000000000000000000179ba87e1596396fb5cf88f91f56e3fd6f01dda6f95363be3365bd42960ed620b7648863524c22663bad20d128d22f4c0000000000000000000000000000000001ad84c4e62b0494bab4f2ec2931d2331d88502674a1bf58b765eeb022f3d6fbe8f8546818d965a8be79a94be86292c8", "Name": "matter_g1_multiexp_14", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007eefedc0360b258ca2bc9add8e23b9d535f35332e7a35952fd832d7fe3d448aac08a01073876a21914a501dbca513850000000000000000000000000000000016188049abc44154b244c6af4e115caa14a977efcdd524ad78e5dce010f2f48259708d14454630eabf2318bb271315007740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000010a19a7cae27e432b77c77d26653c6f17507413a5037621bdb096fa4f33e68dd86d5aa3b52fa54655730fd88415c3eea00000000000000000000000000000000031925aae4540280dd6d08fc53478fbf05b0ec784d04abd04c3a8dadb04ad9adebe87101c6401ebb4a808104b3d7e88fd226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000000447e249cb49d64494fb1f1b18c94a44791fd8d4957bac13df1f992480f72a14c3aec517184700d87200092e866d60ee0000000000000000000000000000000018a12284086bf2f64297a65f6c8b55b4ff3b791372b88aed9085152e24b1214655a74a182e131d7023f949c8cd9602dbc133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000780d3f5c10ab7932e3e3b45c942d1ee2a12f28070674d9c666016d084613f3ffbcccfb576fb7779feb2d0e614106c990000000000000000000000000000000000ea320730367c89cf162305c69ad594d8730d71a910f53143770f50024bdbc40b7d2486b1eec63b1ac7dbaeb51ef9640fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f00000000000000000000000000000000049ff517593107482da6805fe4ab49cfe9cf71c9a95eba00091511719eb76db98f71f089a701c6c136b398a40dccfee700000000000000000000000000000000038d1566f1057bb2da7813c39374b79149e598e1651dc3541a445264693495dea35a6515dd2173f7de43964dd5e8257d70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d00000000000000000000000000000000016baae36e71ce87a6dd7136f7572788c256ef88cb73e550641f14a557828e06ad64f001fe78d69465fed92b67e8dec3000000000000000000000000000000000613a6b87249bfdfd01016ce920aaf902de85c066c2d64c866ca0a93950a1a971cc561560a4122d9a766e38f9dca9239ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d721000000000000000000000000000000001921d310700ff4e2868a28dd29ae6e0216bc27ee9463cc8dd2823a1b4670abe973859e86719142525ae5c76e2df0bae0000000000000000000000000000000000b4b4952e96be92ba6c78037e529c197c9404cfb67af04f39d24045c742b34a700057b2cedb3193dad70e64944642c01fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000001dae69033cf21e6e1618efba143426df1501250c82f214ecc9ccbf957e685d9831533cf7f747fc22309227aca1d1a2200000000000000000000000000000000114abe65155656679b89a11c7961435ea9f77fe2f957833dfb61b8538695e2569e509f0ee2c0bfff75f83d9399a3d49b5af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d700000000000000000000000000000000128e019ff92e7171d3c791bd4cf75b0f47c2a9d8722b4a8279f1178db6dddf8a4c00083a935168518a1c26a56b23624f0000000000000000000000000000000008d0c5f3300e73682f4756e6ff1d6722dde576beb587301ded34427d6935e59e76cc8a8cb0ea5f659db9ad5435851e53c7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f1000000000000000000000000000000001110168c2dc1c2f0df0dc645970c0feb03bd644fdbe1576d5e5a8090282bcb81ac9be738d18e72a31ceeb5ba826b40290000000000000000000000000000000013fccd2429da394be698812af6c3288e89a26f0244327cd38bc85d5c3bb934004bfe24449534b7d271add7a279bdc8512d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a000000000000000000000000000000000199b9c9772a8c1bb0c015c467098bd38b5f73e5d0b3f627c8279b8dc853fa2952faad01e7be353a2762b8144cc1614c000000000000000000000000000000000f781597005df947eaccca59939253b936d1ae84805ec27dde0dc707a4583af408672addb2eea607a14faec9dabe61ae3549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab710000000000000000000000000000000014bd5d22e4bd2f7b8df4add90446650fd83d72d531395fb35dfcff72eca0886ded935e7a0e3fc99a7dd07efa1ed60c3f00000000000000000000000000000000122cfac9ae5c98dd162576c92e9acb4582b9eb67117bfbf4074654fc8bc473793a7139995666447a7663f3af1446dc35c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000f67ef1eff6875abb96378e5a7b1602b5dc553554987589b9953c4401fefdcc5cd7b196a1a65cb3daaa13f9fdd703835000000000000000000000000000000000f58ef60be74af52c23662e6b405f1d5c359b2ce9d15b5e139460e10da0e31161fb52f529c7b406e52c6f600d5670f3c9ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000000b7dc680fbfff55bf0cf276a864f448d5a9feef303d2416e7d87d6d669456b951a8769026bbba545685e1f92277b182000000000000000000000000000000000c36a14d5693b0d9d91d831c0581d1f4ee801f86e5c32f10cc400f66b58f247594c30f0059b4ea79995d6f9d90b0009ebd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b0000000000000000000000000000000014dd947a01add8294f97a84850e6dd11ed4a513e7656daac5b725cff501446e95e3b966492e028ec23fe1238b53d99ea0000000000000000000000000000000003d9726342018f802df12fc867998b6016743739a2a4f47e1f6f50992e4fe23a6bacfea0e7ed5be570eb8242ec4101ec10bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000014182228dbd223cb5b601521608bd7f87659f86a7a01233d4158484024730925e3d841e05e07f2a330b9495fb028db6d0000000000000000000000000000000002e0ad163d40a56215a774751434d19ea17341f41701d41e521983ff753ed76c435c6e2b543510e47060edaaa06d29f665c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d", "Expected": "000000000000000000000000000000001364f0b671bbcf69b3a101dd758ce54434d59fd053591cb744841ba2061bbe2be91cc9f2cbe1ec058119ec8a5e909024000000000000000000000000000000000cf5be1c16fd10ff97a154826067ab7cfd7b78ca5ad0a2e2117155f1ee126286019f8a1049b86095887e79ba67216a33", "Name": "matter_g1_multiexp_15", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010040f531866c4e6fdc255e2a7ebcea89ffc36d44e265d5129f8be44b07f00646a7810662723546ed158b2cef146c7120000000000000000000000000000000016d6a5e46b2067c29e11d00b6b6ae9f0987afb4e9357c1d223fb2962589c3527f94d4e01f2ce6a7c57f971756163e48108e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b790000000000000000000000000000000002b6e3a234119f0f06a2b049d952230da40590a84d241ff76483169428e787093ae88c4040c64f2f1e3aa5be2c37db3b000000000000000000000000000000000732aea9a2ac5612ac350b474d9d267dd1ffa822cead992d3eb411efcb6992d196d66868a0e1f89dd47da584d075d4f55e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d0000000000000000000000000000000014c3187e04024d719560e36b5a63228a685f085aa080c82244a3a704aa2ed68b219d1c699e49dc1fd648e904ae638e3d000000000000000000000000000000001911df5a9f709b8434856c14fe4111935156a984a5e8cc27081059840167c3daf468a290461bf6cbd2ea4fa21255d7c11362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000216ba7fa8afa06136b054c11bbd978209017dc4d8c8a2b05fa717a97f4d88abd9efc1e9879de709b87d7de65c859b65000000000000000000000000000000001797c34bdde358ba5533d5bb531915545e3ba359ea1fd66d9dc2ce06f7cdb64684bf11e5bc02097f3b957957c986de1074d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de0000000000000000000000000000000013343f0b79485528b8a5ca5e0780e8925ea7277970843ce3699046673a41c977dd0cbbc97273ed47a1a105a0017853340000000000000000000000000000000010f3232b511b8d529f91f1ab613af1e2443947fb2e29c4f98d1dbab1aeb965079f64281d0b10e58e26a4bc0577943873389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000005aa017b9381423c9d00982fffb93a7cf9bceceaaf31895a17ce3a9bc42bc5b6f5c69679ebc91c9e5cdaf7651cf78621000000000000000000000000000000000c77e86d84377ceab757a0da9bcea401b3db29e8e577da793da0d5338eb471315315171ec4bab4e9dab36f4ec6d907a85f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000001971e5758027516443fb373a8ba8cb98b78fd5d16b42a83becd2a9b06e8ca7d255fd687cdf10de7dfc6bee5cfd199b1f0000000000000000000000000000000013465b45ed2469c2dc6ef4b4b8ac90b9b30c793425093898203d3b13d76cf4b8e0836c6fe57e637a6eb08bffa3bb55250dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b6000000000000000000000000000000000c14833dd82daba173eeb40c29912c0edacff741bc3ab03ae4911c334cf91d5832a8847d7e175934f61089f523b77fdc0000000000000000000000000000000013820819e27a27009ee44a5cf02e995bb317ee49b6068d2e9f4c5f072d233a6808d0feb61958e047f70b2bb1a5426319c579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b53600000000000000000000000000000000105a1323577a38bc9495090b4d023a9dfed8b510a9a6d755f7ad6af72eedf1c92e6a5172cf68608d8dac34242d1e0eb200000000000000000000000000000000147d889d919a58de8aad3b4735359201c47d8961a1dbd321061a81c67b1a05c6732782975445d9c1f2aed12b0b7306f469f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f1000000000000000000000000000000001335049a2ed3629ca83f041e4ccedede286445e4b79f3afe225bbee6273e0cc84b32b91c54991dd072c54ecf0d6c538e00000000000000000000000000000000098220fab5661a40cf34782efcd62ede159c82dba8c6e9f032f7216b888ad85fca1031c4622547a03f14185b3eb6d0d576618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000018799254b6fe847f53e2892343dc77efa3717bccb3589b776584fcc9e934deb3b8fa4c1ac0709ce505ca4d1504ed822c0000000000000000000000000000000017b98c35564c9d67b77bfec8ce23310c93167a5f75a4680420e8d71d8851f4061d897fd86b52d4a8cdde391c5b21a63afbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a0787617788000000000000000000000000000000000499468c8da336124bb89285a81eb76fb05e4ac2bde68d2f78f1de8926109631ee3e33eeebf686c7f6b7b4d68d13d2fc0000000000000000000000000000000001ac43e7c6d46e88d88a195180df6a3a91b3aabbe54f88c8b39168ead4b9847a031561828b0076b9b94c8fc7cc0c4636a0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000006690b59efd7c3e7f9477cc35fc5e13a5dc7f485100ecde7771e7bbd9f79f72719cd45cc9e0e791b7b5dee6f0252c53d0000000000000000000000000000000008b6f82c8514f7804a1d75f347f08334064b81ff95765355550c53098e19a4a5fe59c6a9611f4795981047754a6304792c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b0000000000000000000000000000000000ca51cd2fbe8d015a2e80bb4a24f52abfe6b99b1fbf1b656d4398f76e8e73e7a441dcacb43a4bd0a1dd45df2ed03a4e0000000000000000000000000000000006269d0e0f77f3ac5af8f70905ddb323362ec5de91a1eb90bf3773457a2bc2d018942e58c04013b83a7764b6639ea87c141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c59200000000000000000000000000000000122f6c35f7b1456952b56a5f90ef9066a191a4164d4b2f81965bf7318d485c725141576e5a1164c3c17a8bc387c9262800000000000000000000000000000000086bcc20a2f0f0afd4ce845243061e1c12eb238f2d3fd711000f259c31d826c2bb56617479139cd611d35b6548a438101d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c00000000000000000000000000000000008ee124fb457671b65c0f9f550ce1ef196c3bf13a5403a3a21a801cb1a335012b43cbdab33a1ace7f84a998a4322ae20000000000000000000000000000000005b0067f853d9dec4dee3b2834679b9145bba170f22b7e1dbbb6ca3dd98abe4f41673b283f9c43f2cc7ee2305b874a0e1b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4", "Expected": "0000000000000000000000000000000016dbe06af92533e96177581a7a349811e0a3d3826241c4ca047411d5b24c76dcb6849df7f4ca050286b2d84afd71ec9f0000000000000000000000000000000012dc4fc05526d6dd6da27b117294da4e9473a34699871b7bc9f02937a8308c57b5387a6fde0dd09e8a01702a8b97c4cd", "Name": "matter_g1_multiexp_16", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001394f8d94cccdaf982b1c6a8080be6bbb65c9352a961cd5daf2f817a17bd8d5e3e086c6f54f6068691f3edc4378215350000000000000000000000000000000013560d0482e6ef2fc19cf274f85bc3d14236273dd8af86107839882dc26dbe897a7de90ab5457ca440498265bb59e59358fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000bfc36885481f9ea9aa275c1b4a774fd01476c6f956fe75b5f6e73199928b1928108658e35dad50b298307598582443a00000000000000000000000000000000161f833b58de4db4de0af0fd17ddf81ba20e4b6ca21dd80852cb992afce9857e6cf99cc580664a970e9c6928d13dffba73b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad00000000000000000000000000000000042489a05aecc0fc139c0ef0c703860ed36f8bdf50e4c772487c0d27b46b395f6417ae34ee98290a40b3b765d5a41d430000000000000000000000000000000018fdc2c8ac7aa01ae6dbf84412de8a47c3c504f2abed060c63190265babf779384dd6e3330e91198f5bce5a103bdcd701ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000005a44b3af7b95c7869d74c7084d0e556a67b39090b7a62fe51fa833cee316044a26d4e383695ecd3bb1715d0693f2f1a00000000000000000000000000000000112fafd6d6f1da250d12817711bc999217d16d7a6a923b5e11cb91a333898fb27f7b89885567d33b39923d7a664960eca691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000197317f509ddb9d536845443d7966314eca15f20cfcbf3ff2f8701d94974e35cc0957855e0085b3f85c7da512ea882910000000000000000000000000000000018b1ddc196607122be575ebc923dee96823fb4f8ed05fd8639b1af06ddff25398e67709809b642d4d9c21dd8ab6e65470d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000000c7ed49a60aa90f074af9f7fb19f6e27ec4a83ce2ed77a44c70c8e0bec02318bbe44a212c505efed3550ab6a1ea2c6d50000000000000000000000000000000013c0a772ce2c97522607b1b05cd9a89e930b6371202b69eddd108237f1495eb1c6ca65549c5ab030cc4f7e3ff4492fe9c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000fd64797f2bdd429e6f5217858cb14d78b7054b178b74696b8bc8ec9f9ede70bd03c36c824a3f775ee2f8cd6be7e2ca2000000000000000000000000000000000f675a8a43da599a09ae2367240870636ed385eb280cc199fb7c4ee575f5e3c5fe0b302566cde70b956f3c2b20fdf09c6fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec500000000000000000000000000000000065856fe1dcbef934cef47b177ecb7df76cc8796624400d5c0518aa9438bcadf397234808d099bed89ab674560ffbb1800000000000000000000000000000000071b2ff64379ed3e20cda000602c3504616dd673aebbe7690e797d6428ecfbdb29f11138169f3462dffd319cad68b96ebffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf00000000000000000000000000000000094d6e0bae02b4e7541a27111092737e7b27fe742fd0400672953d8fd787482195a2cb59a91e8584be002976c3c3e9b8000000000000000000000000000000000c2146b68ef535ed9efbed7fd02ea5cf6ba8cc20ad8bce17c06e5d595282f6e7453e2cd267181e477f511cd4fd56e8b157f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f40000000000000000000000000000000003925e9f1e24531f9f26547108671a6a0fcf58aa6ef2bcf9f4f64b659782b93187bdf2988029de9f51e5d41cbbc4744d000000000000000000000000000000001975210e2c8bbd2431288a42f9cf5d6bd6c6afa2eb05caebe740c0a1f680b9cced0f32f8f84e368563183b97aeb6e7ef2d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba8400000000000000000000000000000000004a2ac3d53c193265889f6c3802d7c68b938ebb6298dbfa14d1a9f515647482c84ebbb3855686b544d4299554473f1d60000000000000000000000000000000003283688bec2b8ff2e34565f8e254d579f57f9c0fe0e8521129088099a5005dfa9d565d52a75a2b26148205dae83aa6a614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000001b82af64f984294882fef7e5ba880ed8b0a36a90a5e9680ddfc5d86e65aafc3899a7d63e2a420113ba29412a025a0970000000000000000000000000000000012b11a5bf0f7895e329c2c6bb3d1737aeb5fe9f32a96262d8268c74687a460c47a89e252e607032576e7b67f5ad655b87c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef72840000000000000000000000000000000003ad0dbf936f79659ccab765a61633ebb648503a774e92b24967aa8f8e45c5e26f03acbc7984a45e089ce68c5566664c0000000000000000000000000000000011686f58262dca9399d95cf2828b50b216e1df251b61c77f952c21374bcdacd99d26891fe5f335afb7ec76ce7d95b43f64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f000000000000000000000000000000000ddfea60c169079c0fb4b9c3ca539e43b7f184f31cfa2eeb942acd2a84b472597c83fb52544479f326bd1207b4e872f000000000000000000000000000000000102108e827cf4473ba1382a2fa8f3b904f20a40657784d54e3a91fcf2703dc6fbcfb7f4b0e04c3a53a24a6e14b5735f435bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf000000000000000000000000000000001286a578ce3829f289cb98aa41cb6bd7274aecbe15b5087d8c16d575fd991878b06c88f17fd4bd905c4576494ca9f8fe0000000000000000000000000000000018e3cffb0746cf70aa79053ac579c1adbb09ed5b6a8b5e7b84951460e551e9bb62f2c1968e37ba34f7633e60a5f1f2a97980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b28000000000000000000000000000000000ad648d5e0a45c8208fb9b6adcb3c47cf0e20ca906c4fdb31e5c2f0678fa3ddb6e27848a39e8035cfd9eb91aeea824200000000000000000000000000000000005ea40be38d82e2b256bd5e26e71dc642e06145d94c1ca4fcfd6e63e2bbbd7b7aa153b498793e94ed1d89691195b4aa3a256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca", "Expected": "0000000000000000000000000000000013a9e1e306a5cfd461a1df4f2097f0beb079a845a959ca3a6d3e229198c75d29daeb5e7b65603397353cf800e0a10d820000000000000000000000000000000016532afaf2b6d884a5b593cb8dbc38b4e2bbe522ac69b745fe9938834d66e2393653e31b052a803f3f901abdcb352eae", "Name": "matter_g1_multiexp_17", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a187eff5afd944ea8731afffb4aefde4046b812b47e7cd99687ce40a5af90d6a4a2c7e2c9ce515a229e6c99ce46933a000000000000000000000000000000000121183879453793d954c99cbb007ff428c721d0e0b9cef192dbb177696ab9d575d3ade2cd56964428adfbdfbafba7505805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c00000000000000000000000000000000196b029b6a808602b09dd4597db611f19bb911b3acb5dce08bad8676cae9910865355cca0a109bb8d7b60359da6d0544000000000000000000000000000000000cf045d01c1a6d6ae397b39833243ad3cc310be9220f71a248032e9325c7186ce3ea40fbcdae5258410e1a513b69713e502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000000c6578ed0ccdfea63fe924d0a30c4aa7d65d9f85ea832733013c0ac225f039bd6f94b4acf634a01ac67b7165a810db8000000000000000000000000000000000624981245bedf55b95217691d9dfbc16d0d83476f8c09a46f9541d77c29ff978ded7fb7fed7272701e385e016647463e7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a74000000000000000000000000000000000a406d8da1910d9ae8e52ac70f1fbb85954ff7590863ba9f6e00861160f83defd24e99be31ec63489a483fa77d84ffaf00000000000000000000000000000000170bac083f0f6f4ff5edbacc5cedbdfa314de364e86486cac0e656d27e6a4880ea3f76ebe0f69927299bbe4a734e0482e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba000000000000000000000000000000000b8a583c24eba7a27a05bcc606a10a306ec07401ddb8de8e9bf206250ab7cc720903bd81a2c17a9e075ecf0ef99ad65a0000000000000000000000000000000006d5c7e9faf830ebd0154dc1c366b84445a85f0ebfc13b5339294752f4d1dc352e0e4204d9d64abed83e8297325de2556e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc500000000000000000000000000000000122735cbd1927c40688662c740db5cb78425942985ea69c729061a6ba749c78d4fc3d894d07c285aea9ee104f59581690000000000000000000000000000000007c18425af769864f403c39ce3df4f07d4b7319342457d0dee30ce4bab013b754e2ab7492f2dbcd5bac2ec1ca2e0220f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed00000000000000000000000000000000039c8c0453627d13ca0e174f5a27525f8a0054ced2b9e7d92c0ba7bcf06c78c1e1632db35abe2a81f72b986934ade66300000000000000000000000000000000134876b42096d986e6004364e176e23f81637f8ffd3dd86097f480d25aca9ce3a96c9dc73b651106b4de307c002dad95586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000032e727809658a52f60a973d32bf47bff5fc45404e6652facc1801d52188dc7db79ac1bff415a6c3e49e417f205422c7000000000000000000000000000000000c83d3e5ed78c1304f42afcc0143f959ca24348510e241c3e79ed5eff8742a89b4ce131e63544b9497c2a1712999a18cefaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e4000000000000000000000000000000000c2bb8dd01510ffe473715d7714e47dc8fff0f24af49405e55a9537a617dbf425950ca3df681f1fb2a3721afdc5a4d730000000000000000000000000000000019fcf0bdc8cf0f14c4b8eff44ce2646feecb3ab0555f530f579cb2764badb6445166598824f7b0c46a857865ade1278239d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca80000000000000000000000000000000011a463b5221e4c3abd9652917379564ed2830907464235fb6f9038f1d3a4f0f8cf9f6ccbbf66c86e216975b2d372400d000000000000000000000000000000000f0e9d5050d539f9211ff7d3cf3f0e7108c5580b634b122810c78d8fe047ac709bbb06ab1501e08f0e58093ba8208e0d4c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a0000000000000000000000000000000010b293dd411de6a5cc464e919d290d6bdb51952a7a68cc27aee3ec80808bf05a50763fd4c17f25e94e655997bc948860000000000000000000000000000000000f18c7ab95bd74d9095ea9ea66b2b14987157162b8b8a313a781ce58b05d2307db4e853733a45344923488ae9dce1a459af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000013ca27fdf920f901634156567835601ac0b84efdc79d7d979c2156041bac04f3297c1799d3b0641df33da9647e604b87000000000000000000000000000000001527cf040f6c84496ceb57df9c9ebda89c394eef034e40f5e6b540e931775ab91a4aebbf6078922da479ff397cc5271ac72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b00000000000000000000000000000000197c0e4474e27fcaf203819563b86e725778409c7d6792fe41820c495e412382fefda97b7df560885082c70f9d522024000000000000000000000000000000000b14b9d40bf866d933a15e16f06ec16b502ea8e7084d68c74418414fd281a6da50bc443647fdba348b26b4a3490d0ac4b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000a254b07ca0f2c9219fc0dfb49bdd7901999cc85161f741500a3ae8be566e64f8a5fb3e59985444206a2cd02ed4ee99d000000000000000000000000000000001726739e92da7bf5a6d2dfbf96fee6028fc7022cb1be2f838ec1b9bd08ef141f4b24e093fcbd99080721063f8b7c98dc986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000001133389c12bf1d2e232cfef1a8303a733edb0dc4fa26acedbb288166fd232b79f67cbe76227ab2eb517f1c3126b929a30000000000000000000000000000000001ca6bf5c18255bb3c533ece833964320bee7c3da4af56d876058edd15f89b0ef147fba11e1d539d528e4bc452e17df8979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000000d0caaa05d3a01c89d6efad96f5957f1f9338136119e8530853a58c0698583d834fb0f045e212e6889d8baaa49815c790000000000000000000000000000000009e7fd124160f6ba3afa752b2557f1c4b5f4010a6d4a3c8a8bfe350c6b6e198b9e3d11f2ec7dc6a02dad4c07bcd4bb1d25ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990", "Expected": "00000000000000000000000000000000138cea47ce2ea638f06c26d24ce87073f98b023b800245b6fc74fc2851d79a402b49c54e5df4e1aa98e33801d3fbb965000000000000000000000000000000001558e37121ec3710ff5e6c2a4e418c803a5b83cdeec98c8216b8dac7890ce17bff08a95ca2aacb40eccc761c8a31e8c0", "Name": "matter_g1_multiexp_18", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001920ce210ffc78b2c053eb2106acf1e238ac5160b50187fe816010e8a95ec632a7fd29565aefa4bec90d87701c2610dd000000000000000000000000000000000322ce646a20e23a1a68361806cf072ae3d6310f4055f5289ace0036a90b5c7ada537e614780156f6a103ed726e15b4fbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee967000000000000000000000000000000000a1ee4319282f43ab9cecccf2c7f5e08f35a6c7e7bdc8dd2f4d642e8968aff377791a5d1e2b2152c59a8f36d9bbe04ed0000000000000000000000000000000012e60ad9f99f55859f2529ce02b8b41f8565705455cdfeef3cb315903ffbf29fabffc2546359007a36ba579b6dd06c2043cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000000c2aa9516360c840b7f88ce0cfaa0ebec502bc9cb9304c1a4d895089a2344bdb6623638e730cf30c66d977e077423a000000000000000000000000000000001163f60b32213940c9cfdeb2c86d5ccf61c0a714436b3d0923ec338ce7bd35542726a87a1311c8072fd589499c26521d733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b600000000000000000000000000000000116aa02028755dd5195ce0b2d3234d31b07b557a52330fdb50064a18015ae630f427a4512dff06f93ae67c4fd0c1e10f00000000000000000000000000000000117d4a68064b3f11d88ce976ed43ceeb742ba6f473645995a2773121b2b8edb8fa2715f51c8be109f8d91c44e8943e7c5112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea7100000000000000000000000000000000166cbdb131fadd6c4e7a94af82ce4fc4805dc34aacb0d6cd89e69cef0b9071b112ea4a7d9d03e3dd961b5d833b84195c0000000000000000000000000000000010736a73e2283849595569db9a5b0b9cabf2182c3d8c40a39fa32abe52dd6038edfb8176f64ec12671e3411dd69397585683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000ec2405173e541945011d09092cc3a71d9dd1ff54451127181bb2d5b50876a148e59f298ee30ec5473c520be0a53d61f000000000000000000000000000000001239198a5b1f6f57bce914583c3bac476a922e56d2bb30da4912acd31cbf307bc258f22fd9f6a0073ec48dfdaa4799bb5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000000232940188006769a382a4958383aa6702b2cbfb9c2907a989938ac618f23e241767b021e8ae11c23617ab393d4f85a0000000000000000000000000000000016a672061fe76ed943e36b2d6fa4aadf579db96eba5e4c60cda2884ddcbb0f37668638a3167d8858cd296917eaeff8e0f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da00000000000000000000000000000000066fe1f7cb3d67c20a1ba29a52c0c86d6a2aca176630ff20d45632398a39404619e55b8ade69e0cb0b7a6f363c3b2d4d000000000000000000000000000000000aa25dbff2a8c1f1d0982a709fbe88563813e918c8f25f0da9c5d0dcf3abc257f7e32def4efbf74035aee1ee350cd4fa57a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000001e54b0e8f34cbfbc20c9feffc555036857c31f453a1bbcffe67bb71d0d6b2b278b2ec5d6ab6648b397c9255a1139993000000000000000000000000000000000bb6d6c1a41675b3394f5b9cf14ddfe73c188592916f24240edcf0940fdab1d1fc04a11bea4af90d0d9f6734a743b38086ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb00000000000000000000000000000000099f521ecae704ed5a37ac90dd4beb4fa21ac197d467185c8329ad7b87c02943a228285b109178bbc2606e89699403ce000000000000000000000000000000000a95a85f84e76ebace78bbedbd13c6b79a6339dba246596e0695aac18d2b14b370c033e62a01caf8484dced0ebe8a76a03fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac377321360000000000000000000000000000000005b8ba4c7d3c83fbe9bcbcbf60b0b3ce42b52ca19a5a322fb18bc20f81c2fcac23e1f62b9fd6edde5ffa2e37f685e06a0000000000000000000000000000000008c03604012e4dff47923a2a43382edde86c76754a1073ba51fa3a2ec7011268ffcd1452d46786682ab2ee4848210cc635ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc62791000000000000000000000000000000000869a2ec19afbe70ad0a15532f776f56da5d7a7dd5b75194d0c65d0304c69a6d0363c0ff3b549e8d15171fae18ea13f8000000000000000000000000000000000389d0e6c9d73bd98202191b5b213fbe77bcf527faf98f4d25c9dd3ea2cec8f3b1e8f261d9fc8baf7b1c21dfd102f99104a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa0084527000000000000000000000000000000000138a4f53b8fcaea11869a6208e7498238dd80be79cde96885e6e5226315deedc98a17f8d75df733ab6f15dc24efb5c5b000000000000000000000000000000000d25d69d6d5a9c597fbec8aa7fbbe579dd86c5fd3747378e984c20b34e018b83f889bef3069c693a91ff552fff1fb8a403f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c98162100000000000000000000000000000000192e8e186cc9159d2207b0af2dca495e9d0c82fb376041360ea80562e470168b52a3326553902fd6f5a43ead32eb968e000000000000000000000000000000000fcac12d18fdfb661a12d112fc3414839bd34aa244ce0cb40be79718ec37a014b43856e5e4b003f4816e04ce612e63ca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c942342000000000000000000000000000000000b860984aed11a63656e3390f5e94695d8cd9367ad7961c65d714637c68ad88a3602699ed3f627f0fbc5782ff18775af000000000000000000000000000000000ed00636e74e8163645c43b8b31f05228da7c42aa332ca250270e5f14b3660fbadb8e8957f52592d942b1cc1bd2eb0a50033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000b162c0897755fa47053e45ee1b298404818ca282a7b5818364c292a6052703502656e536f2dfb470730e9bef0d7cbf6000000000000000000000000000000001924ea42eddcddda067126534e8b862f0e16dc0cc296ea892115a9ca9734fa03d019e90263be2c909528129a12a68d874c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87", "Expected": "00000000000000000000000000000000056604e75c1069b4a061ea72cae2cfcba90130f6db6d9c268649089ce4ae1cbd80120a086e3f066f2584f5b2233b468c0000000000000000000000000000000018c5e93590b89119ad7d44f80cce5ecd97d4a4d3010a19fd02a58342752b166a1b82dbbad30d6f3404b330dba6a57454", "Name": "matter_g1_multiexp_19", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015f9de55b3b45c16d59adda55d9f5059e765ddc06d22d6e68c099358d8df0229c6fe368384a0486af1cc9e532f78817a0000000000000000000000000000000018b992d73dd4c602afd82ad0845ee2c6662c860c5b7be197c62a8a20e91764004b5293ea40602574e91c313e8103e7a1dbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc8000000000000000000000000000000000157a9795cf9a45d2ea5e0312783829cddce176c63eb16195e7994b0688f9f30a4f2b2113e955bc66dcf05b5441521889000000000000000000000000000000000dd9365359ce805327b8f627f02ef5458cdc806bba246dbb21065c89e7ac6093004d214145cf3dec605195f14f1a49d357df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec0000000000000000000000000000000000066d9a54dbb5fe64835523e8ae05bb70b1934e389db0ee7547da60e4af965c7eee14a148f2e3269f01e8a545480db610000000000000000000000000000000017d6a22dffc3eac4366d0d35bfdd053d73d7b3392e7f52fe04e7e481783db3232f85687d2341358d2148fb3af7e9315de2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e0000000000000000000000000000000008be91d2752203afba19d8f3660118f83dbf851a6d2c54af389ef979121c55426d0761812de72a79d46c66dfcd00d5cb000000000000000000000000000000000269b050e36718ef4ebbc89bd88106a4043b267d974439855b6027f7fc3441518c39af6d3fee46e87d399d3ef03c63c82897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a94000000000000000000000000000000000dc976bbec5c5791688499da28c1d120e8a68eb5511ddf54525c047378016f68e8590b95f05cfeffba56c3daeb0729dd000000000000000000000000000000000af6e02afcbb707fd4d8bdcb5e73e1db56d7a2eb02258b91ec4a5c46c4627525220c11e6e379077677e1b733e2df60e02f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000119b86eced2222d203b6428907269b950bcbc1519859c013349b1c7acf486d3da5c4b35319e6b1ba8ae815e4ea14a6900000000000000000000000000000000015c342be097ba679319b83a68164f6820e2ceece3a90d1ec296514f0ccab6e454a0fc444d599a812bb4d78e656e8897fce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a610000000000000000000000000000000002da8de95ee2ee1be2f3ba8afd8f52a4fd0e352c295e92aa8fe9a08a03b6170222f5d6cabc9b9d9bf2835128c6ece3e9000000000000000000000000000000000fddd2b5faaff49cec261eaa8d093b410e024e1620863b6b9bd882088b59afdd4445a4971f31738e2afeafb36900b2d47b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000012060c8cab190beadf40a2e3d927d7cff21c475dad04d64c718d02ead9e351a27be81a3c5a71c6c95aa7d7e287070356000000000000000000000000000000000233ee868716db87f46d546aa1a7e4d3e70b2592efa0104d9f4fab1680c627484a33346406f61499e3971157a6dfbf972b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000cb9c4b59eb8bbbfb8aa2e9ed72eab69735a0154645d68428f0bda762d3b061b0659b31a907f531a55c0906532c539e6000000000000000000000000000000001806c7e8a8d95a34403ec78b43dbfe0bb09014fbe0e019f8c3b6ffd91a75d5e361a6794996e975309fa716b6c6a933784f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000131a492451e5c0ff787a233f72766339d7dae09f2e17c6bec9faeb08e4e48d6407b12adf2dffa3911395d5f25980c9650000000000000000000000000000000001f14d5268c422f94657a20ca02be7d007ea88e1a352753b2fdcceca5275a7ac101c0ecfc075735eec82b8fa6bd61c980ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000d988d419d559b1b487297cec19386f28659fbc5f121750b6bbe941794954e82e67c15a9a00334527d85e9be706bc2960000000000000000000000000000000004c222c037fedce38f42da2b08f06614ec9b166cc6428e3c4cad8ffa440af3d8fca7b9e4aff727eb0890effbc2b88060002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000fc9786ef5291943cfd885238090be47632c10cc46df48f6bb5250a7a85690f1c90f5f5bae03a71d7c52634cd0deff340000000000000000000000000000000019b4ec13ad67e058906a3559cc683511715b25e52f39a591b22177e2dd235e042832f740269544de112d9100c1ae49d9aa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000570b5e7b74c04db066d0aa751c9f763f59c6121e4e2ca4eec222277049143fb2e5fa39ac0fb41cd85310e4504f662ef000000000000000000000000000000000b522af535ca2b9db0cff08bf8ba19862e8f964b6210ee19f0cfccae8972150ae41ae1b8ddce4b1d2733c7dd47bc4c87df9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d860000000000000000000000000000000000892cc60eeaa0ab6584ef2731538a84c6a1e8dcc2efa9591ef1321442684ca9fd953553268ac4ed44bf50004683793550000000000000000000000000000000010234542eb7231f4356c34e11e7b4f08b4cb405a31aa87f961d4eaddbdaf5ba6227b2764e7c7c9ba76bac7da3b19f6014005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000005f3ff7ed08cfc6bfc8f5b55e2b368cd7e9f4a508ab46c7a383b2123b0346b81c39ba1304d628448c65d8c86bec682760000000000000000000000000000000001cbd3457f6925d5b8db7a785587d0dc6e2ad2ff5a6683dd11c8946e953dee72bd52760cc977987cd06a2679c74f9b64893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd15157000000000000000000000000000000000aa17bed794d72f8ac77989ce1b78550da54b4920ef6ac4ee0e83bb3cac5431cc7fb5c300c021045d4d391c67963feab000000000000000000000000000000001300e87daa3c36d87138628ad9aac5ec7d62e979c83c5ee4ce9a375fdabc745fc5874578945395ae128022eb98c6d8e4f685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b", "Expected": "0000000000000000000000000000000010a773006edb1a84341f2971f9226841142b26bcc4af52bc8b000f165f6c59d094aa2eab1b83b3623c6c09145b5bf6120000000000000000000000000000000000130a0242c95fb2b858256de2fe27df7f5736765f880673300e3506a5e756b3b563e52b481c51a003bac76d04f56c5a", "Name": "matter_g1_multiexp_20", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000090ef8b0781c66698848215b3aa84f7be47f86a9d95bf5a1ebe9c3dd6615d4fb4c6425f9e0029fa3d7b94052ef8bb252000000000000000000000000000000000cd1927ed1bfac35325d69fc924f4045c5af9fa5b0a18fbf6c658a3a6d405ac1159d1c82934aa116a98cceb382dde2ee94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000dfb10a6b4e5980400bc275ba5cd8211b8a6bb6cce026546b9459805ba48f46a429ba683ad3f96ace4a4ffd6cfdecafd0000000000000000000000000000000001f643a6d83f235edd9dea19f0f2ecb98a82ba295d8ad45f75be5c0d5b1a1522c5d9f5ed812d47da6e5fe8d7924648fc6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed7000000000000000000000000000000001824ddc80e263475b6ae3b73ef5613c7334b2f71c95d64cbb84dd489851580e767be29e7c7b47d53668a0ee3e6bcb03e00000000000000000000000000000000073f6ee13c3b05c466d35ac49c33e5ffebe5e8325f8f06b893042734bcaa4a1bc76da272602664c2aff48e731cea0304e5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000000abe490a12162aa01307e814684261566302501f589c655b3cb840876259112a1924b1ee723e0c81d6cc6b19535d52f20000000000000000000000000000000006a2205d02f58dff40715070cfd635aa5e68553eea8718090e5f6a96dfb0a2f5a23e11ba92d38a7cee16ce67aaf5de194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000180b5eb4201b4f10f605b4a7f5f5e25783bbd7c9e354238dacbd29563cdf119c832b4ca5c908329d5087d5c8c6786d68000000000000000000000000000000000ac5f56013acf364ce736c455a88a4b2615ca40fc67251039eb99df3cf6423fb85695cc035b6a9b47ef15db7406880bcde4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef1000000000000000000000000000000001963e29f92f6f72be2afa4635221b0d2f6afe9ada4582bd7ca4b77eb77fc4503578f38fb49aa1838751db8cf1ca0b0cd0000000000000000000000000000000009856a48f12966554afbcde1971499ee3ae40c9c5c3aef13bc415fddb97545ed84d5f50d2a26b9c16c4403a487dca614bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006cb805ab137fc56763f73867a7ee5635448a8a66bbeaa9ff07554db3d07aa38542884006744f6719f4cfab1392039820000000000000000000000000000000005e6f6f14f7aedc757cc458ba363fb5d97ee0dc092cf6866083722d4535e1b852c1d99d0c7c57e96a644de4b431c7f9bc3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced4329000000000000000000000000000000001045bd19d4fba8380467df25a777b1ed2850b7f5c5ff5501c048339c2f71278b2c97e4815973303e9eef283378cd8f470000000000000000000000000000000003278c7c8aa02c15275cbbdfc49f6286d6e7fb208a71a4da390c0c853684d7b4d8a6ab24953075a6a45f79fe0c9b910b70fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000007af4aafeee0372e88786c6025a710fad46252a8df870b56bc1d8a39497c2422bc01aebfb567b5b68273ac59b5cc8d6f000000000000000000000000000000000dfe4a8471e42dceabb609b983b59dfd9869f29fdde01a168c07247252a9be6555a823a61487778597e0ae305da4205fe538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000005f847129487acc07fffe21e2d0aa6275a586f051c06e2575f3bf8549ad9f6c2678c541d0dc7bdf909b7cff683ecc5bc00000000000000000000000000000000163451ea5122e16ee62d58d6ccaf8cd981a29aa820d77967e69478127a76092e9bd0dc9f24a27ddca5b40b1fe8ce18b130b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000faa1804b1f65a6ca75d032186b5dda63799a5fff3ffcf1f53eeb04bb5ce08be40fac13295937f34666e0f0be3bdfd9c0000000000000000000000000000000016a9086134daa2a1374fd8eb74ea65858ebe8b2990bb92972121ac68bd6bd77916203a1033ac4b163d863d9120bea0a33a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb4605000000000000000000000000000000000a2ad31568d9778b306525e275bc4f525d86c04dbb98f48e72adae813ce9d02dc6d826a813ffa5b9f9d014e92de42c520000000000000000000000000000000014e928d48c4ca7640a5f5c55c8ae756fb6f03bc1a8e4e907ba89865ce610fbd919a024e86969c52a4216d84b37673cb5c7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d5600000000000000000000000000000000041fc07f8759995530350fdb8712304083da882a5e4df8188cdad48a3df91a5f1bcc1b2a25fb3c9b59e2c935d579a9d1000000000000000000000000000000001925153fa12217d98007963237a665e56570cc666651c29729445adab3963d599a4eab996b192be1d49c7429d9f0cfe43563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe00000000000000000000000000000000006c45218eaa27435aff594c2601276950bb99fb3c1756dbec76e609d163b2593933b5ecd5fd8544d4bd2d145821831c000000000000000000000000000000000a43ab2ea73a8e1131e184fbe9004aaea198a3dab575d3516b422c275f20c7a6e5d41bca0aa3dfe7ec761dca0ba6687d833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b5000000000000000000000000000000000d17ec8ed30bbca5766def9fa375219503bf2f7322d2cc36a38fcc8471fd9d11d2a30ef004e39cac4d1ed2d33a66f7d200000000000000000000000000000000108e6c9ef3a5a41662fa16488243af3419e2d8e78c0311446186c96f20d9c15a60b5470eb95e0e58143a3c71a7565b05d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000017f498e192905962fdaf41120027d49267523bee9de8e412161cec69c62d2586752d1da3d15e89446b5941a2f321beb60000000000000000000000000000000015e9e4eb30296ca3355ba9c5eee343fe7edcbf5bd110ca5be12f55191d0f07b563881f52e65588a8f4b3e03dfce6566e3ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0", "Expected": "0000000000000000000000000000000008b5f4f55def15b4590e978384fa0aa87e088916de20ff0fbd688ab3a13138f32d1e89cddc72acdf69fd86aaed6cbc4200000000000000000000000000000000022a02016f38156fcff83fceed881f96fe14e5d3410b4fc50e607d8a23ca986351ce48d6af970590f68aa2ad7181c9e8", "Name": "matter_g1_multiexp_21", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001155a7d2cf81ee4f8d65c835ef422075a9453bb85b3566ec0545c1198b93749beffffbad14ededaa5bc6443736f77bb800000000000000000000000000000000073e4df0ea06345dba9fe772710ab71153e57152c74bd05d8cd4229c5ba1301f7e654f3fbb2a45526f1bc3b09c72366f16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f51000000000000000000000000000000001238e5a46f24e0f00d2b45bfad87f96140ce10d774f4a17c3df224b58693afa7cd0655e5ab202998f4f8b4b5e22cb82d0000000000000000000000000000000012628d85d982086640b09f046c5bf07b1cf718b5b4b20bd99d64382bbd8bd0112230609d78ecdc742cf1ebd24f1750ef8c02014d5392d30863a12102d1c9315839b5611dccfdb489207f918662513850000000000000000000000000000000001363b85a95432193800bdf353de1a5764cc2333b0369ca7dd539f230bffe81dce11288a289e0842f2db62a89e6f6af1a0000000000000000000000000000000003dc043b958167a900cbca116b097724e64d49897f8fb6a31df99e100be837e873328f5113a28c9fb510017d28d90d30d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea24900000000000000000000000000000000175aef023d9375ae90e9f562f88e0a4affdd399c3755c1b22494445d4e7d96899aa4d5f77ab9392051de4cb7e400ca830000000000000000000000000000000018e3eab56eae429c09f9eed67492181279704d947cff0f1c9a4919dff5e6fe07fedcaf5dae854dba6719194f9fccde1704753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000018d7001b1d4a67d22399c5f9b3262183a47b6fc81786f8f7b78e80fdafb3c0c175756e602c92855e8ff9d99d4116e3a40000000000000000000000000000000018451928599da4a14442910a5bf125d97f0b67af4194797b3f54ecc9ef0be840a1e0ede13e1415391f57044d71fae2efd1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000013ca19bea2e93c748cd2adf682a123416823a2473148e59d87da33cabba8e0ff2516e5b2bc9a8fcea9dc4240b20133ad000000000000000000000000000000000433fa5475709a7b70044f88a5949064e32014f1d64826abbf60789380db6d5ccfa750a868d9902e4646bae766e241acab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da000000000000000000000000000000001236e6ebf0b704a18f85281b09a9552e8a478c66e59c9f5d53eb6ff1f606fd667a6f0bfe239970892c9c295a378fe389000000000000000000000000000000000cc5c1039850f3333981b1cd6457a466dde93e2355c2052cc325e18604f59cb22588b6d892685fd7843938fc1b5b8d8a1d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a00000000000000000000000000000000003f86a5cabfe7792de25b9d8c58a283c5cef56e23dbf713851b42fc0d66481ce1946d1c632e38b9de1a55ffa0bd7f5a000000000000000000000000000000000f548b05782ebe160d487c622f8378786712cb5b68545ede95b34b08698f600e02e918fa2253a8be2c1b773cc74c41042cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d40000000000000000000000000000000015a4bfb53e57dcf53483fca1b4dad7f788e48fedf8bbd7ac40b1707c35a57011a0c7f77ce6626821221e59d8185b9ca40000000000000000000000000000000005618adc16eb9771bfe731dea180e7e2b3b0c9537806349e653a586dea4633aaff7fa7e7ff165fa16ae0013c9672a783214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e000000000000000000000000000000000039895bd3ef87c094c9cb1ec77229d615e76dbf0f3bbd399948a70714d6835b570e54f46f94197657dc94d36c4a49093000000000000000000000000000000000f1c6f8b06ea4378234e99d16fcc439a64cad45a7f8ec567755febdeeeaea4f4b133af18a4c00b3778090c5857739b66c1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e20000000000000000000000000000000003007275e93f828b96d060e775f2b92d191d6da44b1441bd0aaeccc5abcfc7d2b5e9cfaf7b8497016ec992b13455af2c0000000000000000000000000000000015c1320efcddd0709a12a75049633dd871747e51f099e40908542a3e426d7a29b6633f5e69a4c0b5c32ad0269a969bbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f7400000000000000000000000000000000068dbddbfea897bc2b20b6f967aeafb0ef759082f55a180b3eda87174d0e036761f1be1c682d1a4c33f5113a6ff4e2240000000000000000000000000000000004ad9da407bd80ef365df2eb763ee35ae06074dae0eec7e2a36e57df4b3e5ac333e373cc60c1986543c0c23f3124253561ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a0000000000000000000000000000000007f506a54adb1f763d55278419d4c18ca581b28ee369f33b848be495dbcce72c76533b809d70e26dda71316cfc3a1c73000000000000000000000000000000000a6c574799ba920ac58d6cea6d0f8ae249ef5310609904965bf86fbf88269530badbeededfcaa03892f1ad6b76818ec4681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da000000000000000000000000000000001424ab1e7a30035c4ee7d5bdcd8ef87a0aac284a36259742b68a5997e7dd3f2e5065e2238f2e29a23ac5ae9bce3bedc1000000000000000000000000000000001530257b63872851431a0bf5397dff45d6c201da58d7b779318beb70a5ee2a93142e4c5c43c3d65ddc65fe2df1af18906f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000bdc58489ffec3668363be0a3e45ca2115bd5cd1745f86f1842ab82ae31b08a1f285e88dd4e0c7b94778f42d495b1f9c0000000000000000000000000000000006f4d2a07ebc588a8f9993ec6048092b6dad82c25275c922b2842253a8fe24e191cad4fab51621198147c6d1bfabeb0ba8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe6000000000000000000000000000000000096e94b43a1dae483b49c1a616c010c25b660ec3566fb7d9c295d3b43c60ba4967b3f0abcc0634de5cf3fba14169fea00000000000000000000000000000000026146a58d55ba4cef1cfbc1db6efd46400b78f508ecc0b2eede8834eeb741b68ade43ef2300fdfae18c02b86e3386768823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b", "Expected": "00000000000000000000000000000000143fd63e2576a606ec59d017e6582711718a542dd0a4c84566fa4312b2d2bbb2d71c1e0ab8e4e214ef2376706a20e3130000000000000000000000000000000001e97699fd2e0badc3a97f6cc55bcf729142aaa13c51f9b155e5904382ed0d94fbe1d2553d602a71ac7ff39189848a52", "Name": "matter_g1_multiexp_22", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000023a2a3e6e1c1cc57b2295c664ac26abd0f5bbecc0ed8e9850f90b04484c0cf048a76477ddde84e90cc452429e28b78e00000000000000000000000000000000194aa1d8332fd8120ed518f27fd827e3c955c2cbb2cae8d5e677f55963565dfdd232c83a38826621e8e66565f8e200b39f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c540700000000000000000000000000000000124adb0352af8f18a631cb0078ec7daf00c2186e04d3ee47882d557b0e9e7fda0e0d258393ded20288789085583a97dd00000000000000000000000000000000053f94d0889a5122b6dfb1da2d7f13a836b9be039f127a011991c360c941e5dab8cb3c7ff3d7e128e52dfeb776aeedafc8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000009dee1a168c00632903b93fcf330b28ec7dcb8d6fba65f369237ef873ecaddd60a2d1af6e5b087f07a103f096aeb5e600000000000000000000000000000000006f90048b72dc28cf4cb40585925e62275d44df95fcbf1206e2bc762a455dea5fc6b830420d49b2415d259f8d5ed3ab7e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c000000000000000000000000000000001750fc7241cee9d71d95f0023dbc4b1f41ce794e9e7822a29a84c93b9374ccf0f11f931795fb824bb5c9fdb4f9e7bd9c000000000000000000000000000000000a0e6e6c76088200a345531f589ed883203e35c8ad8413575bf961b1e8d6716829f632e72fe90947dfa46745c9ffdefdf7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe2200000000000000000000000000000000141d878adfaa6a3982cd0de93b4d64ba840a07c026ca443d6d4c2b6c36cf882e109d80df63b1626c112f9a89809788080000000000000000000000000000000005a5888d22a2f654a58d9a03c68d59cde9ab5e5356b2288033ba58fe2dbacf533e59344bdf30eed07698261d6269fc70557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000afb5e198ea80997e7cace2d5b271e3907525b6383e9d45d8a7717317655a79bec3a48800149d6bbb11a838b1338079200000000000000000000000000000000060dee81112b7e0bde192c9d382b1eb695f3a1b0b9ef7ae33b1c5ef8ad9134c23b4f473103df15a97bd6de007b828fe63313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb75000000000000000000000000000000000a9327207fa94bdffaac0a8741955968ee2278dc0fd17e99c6f4717e8b0db2ce7915b1b028c81d48380cdef05ecd5a7e0000000000000000000000000000000006c24bd6aa5f9c41bd4551afaa6baf5bab1729b7012951fd0ddaf2c6dd03ddc2030d49dc92073540503718a44260fb028faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000195d0a7f5a351dff02a805fa08b2a793d9e0c74ae95fbf2f42bfefae8aeb0deccadeb9a2dbad7285c015ce14724879ba000000000000000000000000000000000e177a86f6aebee8bad62d77703d1d34a1b708e84216437c02e0694fe722414f2ef2577c1d39a45b4cfe6c73f411b1b413994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000bc7fbda14f76ed98e78eb84033b65f286527ef76ba56dae43a094a23067e10798065674daa14f912ee13dece4f36b17000000000000000000000000000000000f69104995530de05660aa048993c4e08576488deaa177520676c9cd53034ef101fa3911e40933975aa958efbb1b931f81eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000001c3bfedaa15025440c6cd32115555fbbec439a9a2fbf706ef21e06a534af3f43baf46897158e211ea8821a5e32f932e000000000000000000000000000000000fe08cc9ff0fc601e5609ca139ae0ebe58faf8d2e2f4f3d0a1231382a15ebdc8f67271b556cc24fc5408daf3c7f74f875bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d00000000000000000000000000000000032c376b26551a064cace577ef53077cde48c284af5633152c89ee109e880b511c0b90db1b30d6d9700037489f6984af00000000000000000000000000000000059c013cde62f10f39175335b76adc5cf7330ffa75d770d908ac7e0fba6faa7b9453e8d0215f0589af872b2e648ec1d0a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d20000000000000000000000000000000009cb943167f21d9399b184f0bc0c2aca58dcf8e702614ffaf5407644ffa9eda85efa12dd23e756c5ccb5bbb25abe57e9000000000000000000000000000000000d4f59115321181962452c6f3c1e086cbfbc155f2c3019e51e73fd193e9b11ec891b2dfbd95198b318e4513c62cd51bc2c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b2000000000000000000000000000000000637e1dae04d31282c2278e087eac9ba8506d3c1349c6b98485cf32805bcad002e37d55667f1cc8e5e11f35b4d228cba000000000000000000000000000000000778c3a40e79d6288d3a93580c8f8bef7591acfac2c734018d61aea5dac020360ad4c69b4422f7320b87ff22e30d9a6a5e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf04227500000000000000000000000000000000069a54448ac1c9ee754fc28c9b671e84a67e884492f8e84e09e49cbcbcaf07fffed42820b1de61cdd0bf6314a2f4a1e20000000000000000000000000000000008f5512a1a70d3a61ee7fd6750813a29c47410b7ddd62db0426b3caf9cd7c31029638499c2e27e5922810cb9bb130723606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000078356cf80bc64c0e03da2198da5971b01341024a620ef4a455291b7a694ac3d91fe6f19299d725cdf7506e0485485da0000000000000000000000000000000015af5f875422c1e3ec6bfc5e57ed793f368799c2e068669656294be0de25eb772aebbc61358b410fa9ef79c72f309c84c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b8510000000000000000000000000000000019699fb3c6af71eae16b8ee123870888d646ac71dd31d0bb3ca365f728a6687540851c8539dee5c34f16871ca244ac6b000000000000000000000000000000000e68a278bee81ea53d4a52e84c8f534a0fb8c065bbcad9f3727917402746b4d1f611ba5064f0c3cea6f4d7fe84948dfd47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455", "Expected": "0000000000000000000000000000000009057b093eae9c7ab2455b447a681857d588819c94b1cdffc0e315987b095edba1ca727043667749c56591429f9173b900000000000000000000000000000000157bac2835d2f972fd1269039a7b6159b7a81a1bf4327cfbd3be8b7c779631e8beea634ffefd9771c910c612d6925384", "Name": "matter_g1_multiexp_23", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015b6687a34084292423eb600bacc585b4e686251892b16a52d0783b1490a82f68f4eba5eefd36d147c4ec442d2eddf8b00000000000000000000000000000000151f59108d7383351b426ba8bebcf2a04976550aa2d10d5f89d5ed7c3bbd3473ebfa29c1706560866c8596f7549085cc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a00000000000000000000000000000000064e3333f828b1e54d201c043bb0f327d8c9af2cb96fbf587dcfbd55547d76784de0981a0ac86b65f4b8e45b19abc66f00000000000000000000000000000000172b76a242fb2bd9070ad26497a5c190d08472d3fbffa83dafc53d2bf612bf805691bc8f850da8c230ca0b8bd4fab818a59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000158e81d92b789696efcdbd6e3e7c16386d6e5259a247991118dfbb3674643fb97a82fe404832cdbcbb58156c9548e59000000000000000000000000000000000fa0d18e57d64db246ee52980218c3eda5fb7b1029e1c76c9894548df52f69725fb7ff090417ae05957a652029d0a37019ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c500000000000000000000000000000000027a07cd6b7cf0219b57110edf07d758ea40b1cca42270b341b2bc33c78fb9cf52acc31676811032d3f618898a0d13330000000000000000000000000000000000e1212938244e425860646cd0258b65556360e832d4f2262984f4e307023896714731a2db10004e5509a1dc25f49ab7b8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc1000000000000000000000000000000000bd589682a8510471ab1be8c348ed0d242548f0a5b85ee9eaab5af164367be21684ce2329a64a6afdc6a30ecc5bbb51b0000000000000000000000000000000008c8af9dd0e06a08f2da0ab7cdfc20100b94c04c7e6773a0351bc0e0ea503a69e5f25f250f0bbc5c7685795b279ae151edf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d0000000000000000000000000000000015865d51ca8131cd5d2b0cb11c2f06e39b7e167ddf504d5772d478d48463668c4f7dabed00cbaca414b6ba96224c95cc00000000000000000000000000000000042fee2fb44ab45d310ab00896170a638940edb2df9a0f06c077bd00d203966d49694c82cd59c378445ae0577471221c0dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd0000000000000000000000000000000017257c7d5c733cb6e9ea1bc93bda4f36b98375147a119c376996beb6f0bd030c997ac52b1556d01152991738dc640788000000000000000000000000000000001155b29f473d9abd15514a0ae1cbd0b6a4ef394aa65f4fadfd3e9551c1d8420fac28acd5337fc5d114c092bd45e9e30d109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d370000000000000000000000000000000009802bef3feb5688df77c86c74214451e4613d0260fdc5ed6e763226d3eea8a583c7dcf29eaf4c0bf16c907ceda76db9000000000000000000000000000000001447b1f7ac05cf8dce7e81de516d7303b310316f49ed5ef3f40f03db17926ff5f6656d859367805c889e07919224a6436326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a0000000000000000000000000000000000057b59f849f0237ad511a75b66a77e79ae062025e5019eb71b7b7ad94a96c2905e25afe4357506b2472f99bc71a8ca000000000000000000000000000000000f10b6ad9fdb4f346c5b4a499722e377c7649a800bb95306dd7e2ab7542e59455ea5541f2d75e7cfb1da5dd03bf037a1e005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d000000000000000000000000000000000e0725ff4149698aa757e794590ce446a1589d9a574587575ef64d6a3c935fbd78fb60c7c840d7ef42eee8d72a5ce341000000000000000000000000000000000f0478a776be354e29bf8bd2710a8529cd01da31853d04ea722225bde560f2d9da302ce4f2634c9385ffeae379324b743917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000003103b0553facf8f3cd18967a758b73111a4a9987b0ceca3a20d6657a7e365be3925f63bd09990e33e1162bbffb63278000000000000000000000000000000000998a34ba445dbefe6023e737f3e35cc6416289185a26611301721db3a24f80dd784b001a2f2a745ffc3d0da5a9e6204f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000041e13fb55bc9ed069c6d625ee08122efb0212f525b319b88197450ed1a60fc7283f61083ff263e4df10499b689498670000000000000000000000000000000010d931f006adaf737afd1ed2d1a631f519e6d1e9e22166c24830e92e3571e9f138ba901f5ac2f03192c9701067e7906b3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb442000000000000000000000000000000000bc0d401197ce816b692c5ac3ea539cc9658de56e48b4c3ac78631f3c529d4fa2a656f66098a702b4307fc56e147f962000000000000000000000000000000000d89fa2bbf3ad409a9ee7b7097662113b94fab95c98bd47a70fc2707a6aff23bf39944aad5509aba34930d7343762f6e5ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e9918800000000000000000000000000000000103cc442deeb800c14c9b3071c13d354d8c36d187e580073d150f4936ff178817dce67ee276d1633e003e66985c038cd00000000000000000000000000000000188b34fb0a4fc2408d8c70eab6df4c6c42d92ac5e43827044db526d4208acad4561c1310115448bc00feb9ee7cfdc40a877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000145220a2f8fc61b2973d219042580a0edfcbd73a6bb6feea3655dd33bde8a25e0fb841a3b038049e554315100e6724c50000000000000000000000000000000018bf41cf4ce164819a8b00e630401f0332f5caa08b03bda27c205e8fcc5ea7a3374b591a4adc581f492cb07445c8995f145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f607000000000000000000000000000000001416a39ffccdb10f65e5f06c8d7af68fbe894a0778e7270ab167ae2a5e917fb0eef1ef1b9fd45c991a45dc92a223ceaa000000000000000000000000000000000755c58a0692f8ff860430c5f75fa35366391f7e5313936e04230a1fcf1142c81b01e68fb3c888effddc0a498f264da9de4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88", "Expected": "000000000000000000000000000000000d73a7edcbb7163795dbb5a5b4daca733e07f6498d336a5dea1a61c9edee346f74676afe0d6d39c39caa1fa7660ab311000000000000000000000000000000000f3d573970077a17967ecc0fc5e2e7dd4b6ce910f1891f444e36761e2ee3a72fce399993405761de29f9563f74d8b1c7", "Name": "matter_g1_multiexp_24", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008c2f928feb8b65e521b7218b029a4f54022a28a18845614b3b2de93035228c282c73ce172997e6af93a402e35158ce3000000000000000000000000000000000ca2dd2c06221058a4a7a06438f035ddbd96f6b39fe80c0029f41246a2c8a4410961555e43d9b3d5d87dceb8d0be1ef42576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e00448390000000000000000000000000000000010d919a48f588429918f1b2f05ba6e897c45b12d905615e045c1969ee8a7d9ae262551f546b7de764266d3ab656c3137000000000000000000000000000000000a40d6f247315e0440b0b8195fe5f7a7dfdb2e1be9e593f7933691fd22789ae94bcb6bfebf3b84afaef7cae9fd539b5379f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000005eaa990ca9d57885e6ee3eee10b6e2dde6e1652a743c62ebce4871ebd2d3c8e4915418aea4f4285ba375ad1923b70a200000000000000000000000000000000159919c720eefd062ba8d72fd3befd953e1272695471315ff500830c9b5b60ce5f94bd6e966828d69f7f268bb423dfd7300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f65715000000000000000000000000000000000b7244995b7819857f716288dc59eee9ba5ac7bfe010937ea0b67ee71388a3792e5b7feb6890a436db4f1b26df18b38c0000000000000000000000000000000009a0b73360bc0ca3b632c0116f21ffdaecf37e4d6c904c98d6225a08d7caadf5024ad6b457cf31b924118ea147ff10fb0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000005c2bd45375084cbc4bfebf41709a87c2a8d52256a5e4bc162501bc119394186fd624c5d3d6749708be2811da2c84c15000000000000000000000000000000001626cfa6e87e41c2f0960d6d2b8e303ff8de00c78d1e788f32cdf548a5ca00db1f3a3c082f051b4bca93788243d9b0973685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae8206000000000000000000000000000000000f736c8cab0794e3751a9e13027a8e4ded1308c23be3d75b373780eb69f130654121435c53b62a929cad39c605637ce10000000000000000000000000000000015b1edb73501789811fc09fe0156344a7a4eab1f04d1fabc24f36e2ddef7c2ccf9643699cfc654b7169d8e371c14e8c660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc2540000000000000000000000000000000018dbc414f9e1c66af803b0c228a3fe77c94c29239e529cee652099d80795c460a507538eea6c94e99b78779fc0f3f33400000000000000000000000000000000151bf39a8e3e85b9361a9472e95cafc3ae11f7d0b952714d2836b903910a8c701e0c3832b8c88592bb8507694d9109b5addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001241319f49e1bcc2d3f3eaca51d2e4c395241e2c5d8f32749a168e4af17570793fe086610432db1f93fcbbb95ced8b49000000000000000000000000000000000d90602dfcefc3860a78a8f51432a7608a7c483fcd86c0ee6a70f8ac723537825c14736240cbcf903c94d04e24e8ecc928416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae800000000000000000000000000000000024f26ba0c3295002418f7839b774cd305cecc3c2cfe20974343dafbfa6677c2fa6be5c546a1fe81458678c3548d8d6a000000000000000000000000000000000fc8ac2bf4585e8ac8454e3e424e858e1d67cb6b9a7181e26af803d8895717796f20abdfce0dfb390bbc0c7b16c70ffb78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000005f24bd878cf5832ebcf008835f12f9dfbc78b2f6e46ee384b419928aae0e754d86809d360b0afc01bd8f2f8d79a685d0000000000000000000000000000000004aafc9a20f52d1c78a17e7824062a1e7165362ff265dddd4c3458c7810a8e59104d36035c93284988eb708ba196d6a2871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce000000000000000000000000000000000375313e7ab999d174735b5290bf9ea333a62387996bf4df3dc33d9a5212ac0645789ef4153223d488aa2fbbcfe808f00000000000000000000000000000000014b792fb5bc39dbfe409356bd75b195d7023bf6f715a4102cf36ef05b52fb2284cc0739fe5ad628a760049c3624a3f2876ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000006ae2c85b2b267c86320c4cdc56b1a09e25f0f68dd208e898ac5b1c0645aca3dd8000eb544eb666f4256806123480800000000000000000000000000000000006670390bd47829d3c31cf2da8fdbbb64b92b47c78d3ab638727ea834ea6203e45a9a023060056c69c1fb567c35b671795ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c40000000000000000000000000000000011c78f1b6d0ecc5523dc089852d95dee641222c743dfd09ff2e56d008ce523762bbd9c7bec6c18e9885b7022131ad30b00000000000000000000000000000000066a1aa8af751eac5dbaf2d3ae285e0cc7a975c1787178f550a42e8ba89fa74a1b18f27716eb7ccc4f21b7957cffd8e806d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db000000000000000000000000000000000cbc0fe6b4956c0f7b9fdd36ea14a4d8284468c280605a31536636114759ece1339f06e050260bbf936b560586e7d12c000000000000000000000000000000001213bfe642bf78554d91820c362b73b7059cf20a0aefa5855f9e61a0490d165f6f61416e135473e2de54bf97cc14b8f6257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f0980000000000000000000000000000000007cac206b2d123cbe9375f5c913939b25886a51c857271a59cc2fae2e9d669af0ada833c72366f78be265ff9db049d0e0000000000000000000000000000000002db3f65b6fe7c6688f8d3741e448ac6ff322b8769277572f0198dd6ee8a99397aaeb9addd0892286a9ec6028bf9678863d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc3000000000000000000000000000000000a60331f8e8b26e97366c0e4cfea158e78ac72d63f219e1abbb670675bea008609f7154752438d9c7758b2a2e076da7b000000000000000000000000000000000d40d90f498a2855ba35f1c4bb3c5409b87062d7857bd97dd37d6e5fa53c94c78319c6b16bdcbf2610ba379d50d131e47a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c", "Expected": "000000000000000000000000000000000ddd3c7964bf51207485b0575afb6430cf801bae388ff78a69b8173c27431e0593584f9e755b99a5b2ed3113b3fc0082000000000000000000000000000000001735fb40978d364be3521ada17c3ae74b2a738b412906fdf425bdf13ec09e5acdf29013b03fbabe889fa261302a7ca42", "Name": "matter_g1_multiexp_25", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c52993730e412fec923e33f3da42adadb5d87290ac4448d7df9b401e28b3c7fe7f49c7b7e4bad5412c815931416303e000000000000000000000000000000000db71c91975e41b3f12e303bd8ad15f7c9836b146073946129ba3815bc3217b6116a2a03137608cdab8807d5834eb12026a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c0000000000000000000000000000000009657686875d82eaf4f93f3e710c467ced1348b60aa47658992771195660c4b96798cfec584ace3bc64040666de71f8f000000000000000000000000000000001375f7e985d987df508321c3d0aa7e7a06cdb78117248e19c3344dc443da319f49c00ff605c057d1ecd942e8b04a5e4ef1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b6000000000000000000000000000000000da69e098b5e2c8be2ba699f20fa38cd27b9c78025e071ecb2d9fba3bc84b1e673eed79f1887fcad9bfd5b0516236a1f0000000000000000000000000000000016c4ca4d9f15716b7efe6f9e61aaad880423243b2d5ffc96804fc70f29b633dc16474f7194b5e3ca12ab5a1627da580f97fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e0000000000000000000000000000000005370f5c60fb3bc36ee208e8c185613390748452cf6191bfad06c9bcb52501873bff63892066e0afcb01a0204cbc951b0000000000000000000000000000000003c7a2a97cf7be433864541082bd04467bbb42b2ab708866c8520a6582cce5225af13acb887b6b6a8d627c90e43f6e7b640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d67000000000000000000000000000000001820666eb1abd6144df2f21f2d46096410274e346ba862aca0e62d293fc64a6fd213dca4ddc1a4e414796f59db4d6104000000000000000000000000000000000a2521c021f2fb7beb76a2ff4c7ce96cf1d05823ad8edd9b2021eb39c08e0c7caff505ea76bcff8f6afb6e8c2e81d2f68bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af4000000000000000000000000000000000e756ad1ccf0404e110a778f66ade3d10464bf8902f646f7d7ff38d15ef890bbc6d61d48122ba6edb799630a62ae084a0000000000000000000000000000000005b322f44f07d3db292c43f9ddf9ac9e44e8d16c07537bf563c98e02c2705eefc1013e627567ac2a03698268707cd84e8da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000000420b819a63b7ff7ce541661c5fa8cb107cf00ae678981b3fc1b568174ae3864a8241f1e9b656cadeeba232156e66feb00000000000000000000000000000000136fe878b886bc14fed061cd8ff1fa2d85f05bab922bf18a1f09b55c331e7cc9bf0f9860e9112c2f6242b6d1124851dbd6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000b73d3549a6b2f76741aa39ee9bc2bda8cd55759bbedaa9ecc5802310b054b01670dc803938aaea547389d7b0ceda469000000000000000000000000000000000227fc49bdf53bc4f916714ea9789b526aa53efa1eb032c4030519608c62434443847cac82a13e2dd2eb48f73473d8e1e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b100000000000000000000000000000000167cdb86301937bff18287eb0b00f5224e674953d70258065e5e8370016cac8194ec8c2f44330adaea44426aaefac7d70000000000000000000000000000000007e9128bb015f01aa725796d7b7851f9c2819a8a578bc7d3af02f7328c922c26335ae9f87756f52409c446852bc710ada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000018bd46832b101d12f95b21332b7259719c1f94c056118d877324656d285f73a4fe2cf637cc62a45647db92ba9d6c7d18000000000000000000000000000000000fe58fe2c19ee903d82da6da8713863423f10edb954606b6c56326eb8eea6c66cab63b0c816479f8107612391072c634b929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf00000000000000000000000000000000012db7fda36505d19a2c6ba5072044154f444eaaf3e12cce81ea74f28e691e4b7a730095667a71308db5e8322e80fc66a000000000000000000000000000000000fd0f22b05bf82688ac72e9ede526bf806695ff430ff3c750c2946d58ef90c778e4c5693d152e39fb1837bb10cf5f3be36999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f00000000000000000000000000000000116ba7b6faedd465fd4d1e5f42ae80c133a1d158614894ba663f87137f6108ae03b8e80bf32852ccce78b776dc224c760000000000000000000000000000000004c3702ff7fd9c74169ea76c00efb7b475d45efb12e1b5b700d47a970ed9f95f46e4c0ac66cd12fe79d62898b24b54a0fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000016254d89b0e2a8315253434d5444000d9b56b8f43d3c20d17fd26da4c8e7432d6e463b71a5b2a1a7f559a908d73abf6a000000000000000000000000000000000170c490fe3962fbfaaea1707bd28ecdd46ba29b5d8a0a35baf7fea4eaa47694e680e47e8a9f07d25078274074e232dcc36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000007a7fd283d64efef7094fbd6162da2fd56399765b559674c18d1cf6df51036007ad6c9af62bee534388ea093d3cdc3c90000000000000000000000000000000012fcf920eeec2c1728f3e620fdab1f8a0b99c6219f44b0fd19d0f7f4a15d1636fce7b4701f9c3963cff9b030c3759fb20f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e40000000000000000000000000000000009bcf0b2d49ce38914ea877832eaa3f1034cec429cd9fe0d06ef36691ac8ac6b69a712792e31afc700872d08c2e0fa48000000000000000000000000000000000f5fd9d2d4710d1cc6c13c88ae602f584a7b671df91cd544697070eff3342d80d750e15e09358125d15fbf8a1ae8df93c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba00000000000000000000000000000000053fc59a0b84028cbb3a97dc3124927d6a0eab1c58d4c6d143462bf73c0c847712bf22557a1181750146fe63e9c9668b000000000000000000000000000000000c1fa8c1539ae702bc9441085a89790a5dcac9b18925cdb1e21b95c9f7286795e8f36e7a8b4c3f4dcfa12454624911675398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc", "Expected": "00000000000000000000000000000000085e4232f0daeddb9e1ec8731855cf855d7dbc05d4b82d10b77a53306ee7a38ebf45bdeef1981325a61ecd754944c84d00000000000000000000000000000000061e32056ac411c3917684356a6ab3c7068f55d30ebcf8cfe446c68267923e4fb98596aded9740dc7944847a2e617fea", "Name": "matter_g1_multiexp_26", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000070bcf49d6d066afa9b008fa22fd52f63b68a648bfbb5cb3eefd6feae666f3fd0b9a8447f427d5a9db52ba49854db7cc000000000000000000000000000000000947d708a02cd0a18342bc04639e8d126fc4c97acb497aa507e1c4c3912b04bdca886b75b9b9e1c5ca745acd090433119f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000001023942a16150e6497289627dbb0205a7c34afc704232ef214a6609125e90260d68b7c60600cd6f4859ddcad46c015580000000000000000000000000000000002da96265b7460ea6a8d51122bbd2442c6784d4f5bcf6d8b0b6eee6ec82e4d03c9265887f88c106792795837c02ed76e4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000d5be6f99bb9a2379d1e542ece048164fa5d14e0c6c459180717b3da46e8446e9def576635ac1124e1390196fe97f39e000000000000000000000000000000001482d8339b402e3bffe61aaa298c8bae4286f1fbfc877a66e21cfe239bbee383d701d95a6c2b8193d67df5a551bb7aba7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000001969dbab76e6a158506b9dd38c647d4a670a21458a9552d903ac686855fe021a7dcabc91e712aa252de369c9234fdb59000000000000000000000000000000000b60179a6fa6146aa6e57b097f20944c123916c6722fd7e606aa34b8da579f6c126dcbb251da7917076a83e2e4b02d32a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000e5fa0feaca8dca2a6b4a42e4a291383ec867f12b85593360f8caec45d31109373dc16d985a4702e3b5684774699e6b5000000000000000000000000000000000ae96f4a4ac0d0a6fe6aabcf902eb0765aee9ac81ad09e7e097d649b0c0165de6ad7e5ffd4ae7d8a272034f28c85ad6f97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d00000000000000000000000000000000006391a93eb14641ff145f690c626ca412af266d50b903f7465d9a9b678025a35a68bf1962bb5ffe76ea07989a7d807920000000000000000000000000000000001a90846cba7c708bf8b4bcdb3415e17e80ffc9b48820d3307362327b29eca0d1bb7fcac9c09d09fa309829679080b36efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000000c9cc9547fd49cb22986f7a1dc1da89b05f5e7c0d3cf2179f22002df9fa2c586bb3f1496c0c60f8ba36b631fe74c8fcf0000000000000000000000000000000014f8e4e8c5a12b61caf4325d1e4a8505409d722e4eb16d51be5f01f863e5dc1ca68df1b83f546d22fc116f1654a3b30e9a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d45200000000000000000000000000000000123292cef01012c3723b4713a345ea7648bdd8b8edaf76f149f1afb993f196f57b3315d86a374fb78a34486ea10e0c26000000000000000000000000000000000ee2389f669431df6697d79ba16d3e4d9bb4264c9ac146a772de6a9a8ac94760cdde7f613a4ae6592509b04b1f8233cce9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000001284787a11e0164bb197f69702d0d746975bd96a3b9221841c7193676861e97e11077b74e69f744c521ddb40689f9685000000000000000000000000000000000eb6c4c25fa1322f7c829691d938f87ba6bcce850404bab57cc3be8c3d0abcf123be8922af9967b83789fe64e2cb35f40a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d400000000000000000000000000000000069f8970964efa22facc786291d6ffe860929121595fa713f4a12f9e99d8508d7d20f7d19c51514538d1ce89d2adb78500000000000000000000000000000000122bc9405ccae4e409c1aa22b36db314a19ef6e67a572f7ea67c247085205302ad12ef7f83d3616279892ccd3c456980cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb0000000000000000000000000000000017b92fbdb00429846fb30633a2c3f383d32d0bd433d5a46e27d3c7bd6880948f89bf70b3f1639a18d308ba80b7209df00000000000000000000000000000000012374e8e7c1fdaa4ad4a2d8607afb62ce939bed23ea42a51fbac995e2c3026c2daaa338be160dbec2602a0fdaa1e9897e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac576800000000000000000000000000000000004850419631e3de2617bb6b51ef19bf14dcc9f4c7b24ae817cb239342081947f1799080cafaf51ed687b9dabb2f3581000000000000000000000000000000001108a0463d38d617d0a778bf9478ab44050ec290e442ac41e23b526089ab5aabd5819a8f08f903343e93177ce4042c82f26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac57946000000000000000000000000000000000c9ef168fadf7a056e6cceef0430f65a57b0f2c3372a5d3c533871c91cf81c40d3459cfdb5f1f66f53b2d8d50124ed15000000000000000000000000000000000483ebcdc219c4c361735aa0ea96c00c4908b9db62ed8cb565d25a7fa664829bcacc37a5608a3c3ea3a42ecf74708ee9ccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f94000000000000000000000000000000000f7232dfd8367af413dd078f9d5f47b8c76c38b3ccc4110fd59764265e6a368fd4609b52c21f8e6db2c73908d4ac0b3d0000000000000000000000000000000018433b00ede4de21cd6a1c78c5d280af98b814f0a60c625f0a8f355be43d8d99346282b6d9911c4d4074fe827b55d726fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb3400000000000000000000000000000000015b0ef81908ae275b2d5c3cbc563b8424ee0be0e1f2fb77f67749a79b7730d33028a136d133825da14448b05bda1409d000000000000000000000000000000000461c575bf65c6c5754a214c2e72d6d24df2cc228ae1c9f99d75eebf9cf48f20945a6483185337aa7c0096543dc0a527d94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000006e7385d061bafef2c731ffedd01758f153e2635c7f2bc42ea2efe29931697a1c50e4a13ac420572afc523b7316190cf0000000000000000000000000000000018ad5dac1577c9cc1e9ed30ab277dd381a6babc17e86538570abac44573a8c2439d97cbc370cd2b5d2c6509a18dbc96f122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2", "Expected": "00000000000000000000000000000000020da97236c2405d3f1bf4e937d8285014a190bbc59a17b7163a292a2b825f086db5d371776988d1aa2d7529a64d2a4e0000000000000000000000000000000016cf6d7b831a81d0c487bfc3380a1dc8a1bdada61426a457993f7d6c9c8fee9ee4959324bf7a2425b070aeace3cdaff6", "Name": "matter_g1_multiexp_27", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017df783852d1f1f9c6dcf1975ed2dfacf3dc0cf942cbd7243a0cea7907ddb289f378ae59b30661d06d0702792ea9e9e2000000000000000000000000000000001717bc4192402e587400b4e7243db7e79fead2f878079c3af998b3a683a0539aad5d6c1e5da6e0a00ffbd10a2d891ff2b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb0000000000000000000000000000000018c9cc123fd18d50a7c878b31622a3727864fa61d784285b990fd116567c69dbc7ed872866db2166c7af1812157af9040000000000000000000000000000000000f38e55466a6d1cc2512c1282f74f5c0c19777365819e48606c0a86d2c6aab8938475d15a74f24db868802fe935f6107e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d310000000000000000000000000000000000816580c761a2f54c386cf60b1417d51a310bb7569a50b475f8d45f13ed6c1f11640079b5d6119270d616e77a489069d000000000000000000000000000000000d9af7b25803b611351f00daa88464e49b277de8d8fe22284a9001a13ed63ff931937d27ee19ba4000ebc212fe03a0390a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000032fbde9d988ef200df573dc99b087a8ffbec95349256989774194dabea55d970ba303657837bdcdce3b59eb54669c86000000000000000000000000000000000d65e89d8df2a189761e04d35c9f4d3a5292d1dc0d083bc9a982a131b07df6250cc969a3534808959b583923bf02125cb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a000000000000000000000000000000000ed3f47ea234f8fdc16e97eec7f4521941c37acccdfc422fefc6df9c1127ed293998945fb1bdce89ea18b9ec2b6e5175000000000000000000000000000000000a066fb6f1d69b88495bcb0f0eeaad2a41d5c6764e2dcac2ddb4ac340cda72d7b51b7901c758df15ea16e4e46c7053298902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf20628000000000000000000000000000000000ca7faa768ce5ddb6d668436e2e1692893d07afdf7466c00bc8c963b80cf0d44f6eb9a2070a7bd889ef692a81f9d76d8000000000000000000000000000000000f86fb53e3f061cbe777c7aeb63402616c428216a0c65d5d5a13cce1dc31567a4051420d54b4fc93c6bf263601046712426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000011113946d8ed7e5e545ecd0ef30de293206f3ac50e6010fa7a1cb0371f47aab2d8775c51172c4dbacb05414e65fdae10000000000000000000000000000000000022a7b8af616e4076f625f8151d748f4f49e6dbe439ec695b854544f8a498c7e261c366a4c81be5b9cad85a4eb07c36de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad000000000000000000000000000000000d863702db9f9e43ea311fdd7e0d87495ed0bbbddaedd3333108704417521b3da4b8ff0bf904710b0200453ecb2948620000000000000000000000000000000016a520d1162c7070030fea7702420de2a6e0f255c28a89bbcaf663c0d6761d201f07d86adf5ea6589e27bf844abf85a57431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb6000000000000000000000000000000001357cc987a4ee7c7bc063ec8cbaecbea0ace4b80e3af01f74d23801d5d37326ab5732222f60ad864cdc8c5dfd3edb37f000000000000000000000000000000000094fbbc2936e1730a1abeb42e58818ffe6dd97bed27a1e4fc090388d943763b055301852222503a2d2a9dedf69b3da26745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e000000000000000000000000000000000672e9a4eb4e8be8efab0595bcb7a6fdf269db71dcb585c12f9d7c1a8414b6e11d91373959d47a4c64a8890766f68671000000000000000000000000000000000203f3804abe330bca60b7bf9925a626eeae79d58ce7c71658b2fceb8cc93da9d455b6d59bb58bdc23b58238d4f01948ed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000c4e95c27fd983c31fcacb578a688c2fe055516735b5f1ea1415c5cd29592e7720eb2f548071fa3ac642b70e339757dd00000000000000000000000000000000067ab19ad1c97a773164e812771aac69fd5d199e4f60eb28c7aa5f09dd9b3adea959ab4ad47683d27394714eab4a40d281633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000003da3fcadcafc5eff08a736e4cacb1d6617c3f0850ffe33ff1648f783a4467163d1ddda082ba0b54e678b171b1f79618000000000000000000000000000000000a273fbd5fe99df4f724fb20ae0fee994823d374979ec7ff23dbe148f6977145de9a1f20eda777cbfe0fa4cf8c2a8949c6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000015b74087be4a98f4c5cb442e4e893d4d92602b1ad36d0f038f232ce25b53e19816f44122e8f5c821b40a0cb36897fef0000000000000000000000000000000017e50b1e84c7e767171edbddc397653c35b34141bd69ca7123792d6f20532f6daa5ed18615bb364b72744f96d4a730be6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000131517851372c44894bf433d5162d0da394b87a9554e9d4f6174d5712dbf69f756c5da1534eed80f8596f906f36799a100000000000000000000000000000000130a4583c7529129831ad621cd1e04a8fcfeed67ea96db4932809ac140a089e6252bcd101f17d4653555b1bdd9ea3a9b5906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000002b8d72148ed7076656128040e7dec82ecfc2d5ed05050b27361a85d0fae6d90de6dc32dbeaebac039187e3883ab238d0000000000000000000000000000000004021fbb748bdffca854bfc5de8f69a9bec478181477d3c6e41a7da2fab3100f7e2737ce958c046d6447370d47e373ad94ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000015d1c5bda34c6fafa52dd3801d94a04c53a3acbe43cdd128de3a346739df5afc6dba58d63c7cc09d18589c41d9679cff0000000000000000000000000000000014367ab7f03febf90be2279a87890527935725880ae3d418ec055004f312fa0c42c8f6fbc9c319117f6ce600d86910f16feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438", "Expected": "00000000000000000000000000000000130db02ba2d24a3d70439503b089e6da4cde7b5c51b1d69774b38ae0f265aeb8996e50ef077ec12199ffa3d000adbf38000000000000000000000000000000000de25ad8eb2142051fb2c97175cb4cb2984ddcab65dcfacb76cfe60f6a47083a22dac4f6e06e357a194249b7363210be", "Name": "matter_g1_multiexp_28", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016785db77cadde48a4ed0d2f8aa9f91bed9387a4766c3566217afec80b180461c8e1017297888e9c5896e509a26137b000000000000000000000000000000000025b26ffb3fa42b1a9e974eb23ada4b9329d670e38970e7abc937463e522887d777934895be0cfbf13d213b3b737a5f6cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000d90bd38049f2a8de869d8a748c9ff3120542f38fca6e8d5fbbff86baaabf0f19dbf449cf23c043dfea322d99837f7110000000000000000000000000000000000ede89c8bb8299726ec685765f10167c5b844e427d3c15da6ec2c1d97de174819d52caa96d5cc938e93dd09bbd1e0d813a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b0000000000000000000000000000000019e1e2706e878e60bf6fada47a4d4028750cb27749bcf8fff531ec75d1ff9b3a1b5e0bf19e2758899c3d8bc96a18a0540000000000000000000000000000000004b5f00109eb4832ffc9108740f0728ac059c613654a771beaaa028fef06b6cadb9dd182cc573d7ada1dcaf307a8bca4ace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b77400000000000000000000000000000000013844937de287b98db2b9631d8e36bc36ded8bbb3ebb2005ea5ab39a4844fa354b62feb7433b8fd3e72aa89ac8e4ff50000000000000000000000000000000005603183a5fb09ffcf6faabcb5042328496f8b0f83e8fe9031f9dddfefef43ee4525d1afe859177d4b9f966599005bdb8d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f5360000000000000000000000000000000008ad6b2bb88897a2e53d4fb9910b6244faaa045ef32a2fd223adbe6e0b1a5c1683dca69c0e9515dccf7e4589f1e69bff0000000000000000000000000000000013564245d53366d8468b51f88becc288b695879a70c3c753933092904b9fa5e64e39be30edf1f5e9de7eb29c4b3cdfebabeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000003b587bba9173011da620ff930befccb7b43093052636d6632fb6e9b59b8d127ffa0b7829b59873ae347eccf0e6c86c5000000000000000000000000000000000363be6dee6dd9a1271b24ff84c6557adc62738805b31714c9f7208c320aff220c02b222b96c62af96f1eb42b5299a63adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000009b403c5fe094f6ec4e4b9b7d098c3ca6fcd838e46a885506ebe8cb3d8b29849a8f3d8f9550f6d33315e69f6c1a6654a000000000000000000000000000000000714a7aee8bd6d754b9bf0292be50836e13ae886f7952c61afb1b45a02a2c378d6d22eb3eb882206a3141e43658a068c06e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f00000000000000000000000000000000115f7928ee8b8e47af2739dd70bbccbbd8c4c4f9b92868b981e407887b448745514b67164df86126a7aa53af9ea7a0ab000000000000000000000000000000000772b21e2bdc688f0b883a2ec5accd48a13ff3917d1c5ca8896faffca7e4097021ae3c348bfc2e8174db93e079979967b3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c60000000000000000000000000000000011bbf90f59d646617a6d074f5938f64232550e189c6d8105bcb67a3607e13b4668701f64933de602e5daf7b0f4f50c8300000000000000000000000000000000153ff6cb6a6dc6b6ec086e2ea8122d23e2c6abb8d59c7535fcbdfa721ba505d7e9113cfac69e1d81611c72e872071bdd29b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000001897421ca9a740a1f03d67ed31b3922d7f6067287b4addef6689303571b49bae574c343e967dc0f270aa4f91381609520000000000000000000000000000000007ab14771a4e256ec4009aa03af8caedbec4b3ab21d6499041ec58afe17175a656a7600c4bdac42c92efc9d2d21b48bb6b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e0000000000000000000000000000000005e4061b14fa76d4c02d77adc7e07881dbcb023dca9dbfd1301cb3252410d54db87816a6403d18c2ea8c18027674133600000000000000000000000000000000079d3ca06d0878a569a3984858cac6daf967bacb3fd540187e47dc2c0790d6cfffd1ae1f377c75910f0b9a17d2cde2bb3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f7897000000000000000000000000000000000e2814ce8e1011c37f6f7c38ee9543c65d0d40282793dec81b195b2d4f4b55f2d2b68416eedc6aba6e31b2234c3f08b90000000000000000000000000000000006ddeccda49ae15e5574bce201589758d7ab8baaf1348c30111e997154b6ba413c03e939e288fd95d808017387f1882947944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd000000000000000000000000000000000b78dc15a4f413ea9c8b347cd82c278cec530a28d239694d051812c4af08b5be888064f54d2fa2278ca4734549cdd41b000000000000000000000000000000000a8c5ecc1541fd79771037e247357599146fc46b852536529b841bf4b21978a85dd09c01baf8878bc2b6bd8e36bb93c030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac00000000000000000000000000000000172140620e46db480b2a9f1b7f9d0b374c0fa19145e3349906aba351686e0b75305db408fca3465fd263d06157ea471d000000000000000000000000000000000c20ddfb4502ad34e0934812913e222fd9aa201b9e10b4af688031d2202663e9c044cf3374ede037ef0c7aaa82428ccc4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd97860000000000000000000000000000000009d75caf6ffb593ff15d5635502abd9ef88675210aaf98a73bfea25888c90b63de14501459a038f07ca502b2b0eb98ea00000000000000000000000000000000091c4826870da1d2d7da43fabda1311384f24bc6d7693ab92f59cb76a06ea129911abdc22addd72181c3ecaa15dffc884d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e0000000000000000000000000000000011e977de99564d61c5e0d1654ceca0d0d63dc09a6dadf6baac980bbb97f38513459b391e40c09329d22be015fcdafa6700000000000000000000000000000000119164ddb3240c59428f11ef8c7e0469d219a591b926296f394048dd59a62a21ee2dbcca55f79df5cac6b784a2e06bc5e140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b", "Expected": "00000000000000000000000000000000073edf80ee80c7d1675d05f8bed28da759098f44730bcde3ca1a9a8e286ff1791fbf22bc36de06d88b20f7f1422dbe38000000000000000000000000000000000d52fe400f41b902f8801063c0f3e793bf643c027676e0a1ad3860e5455bdde58d988b929582823e5d7ee0af8987c551", "Name": "matter_g1_multiexp_29", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004663e332c105837eebfb9ecaf524a8f7f4d651f3eeae6909824eaaa6250c9f7fc212f98c6b3d4c08c5198477f240a8300000000000000000000000000000000057144a8578437c9a10a7801fb179e417e9bbe1b85e9dd8e2208943978cdd77a8345d682ba83950e174c6cd39c9eb936a57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b1530000000000000000000000000000000017c44ab586ecd185de616da02f99ee799487b32baf2470871865baa2b2e3ca20f61e6c82d741853b71c5578199d46afb000000000000000000000000000000000c77154ab5f0ba817b30672367bf1e19f9e53a95d7fcc4565f82f604a07d5eedba2182cf1bcca2371af4d1bd09146cb98fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000002df334ee40a5aa144d3727ec6c19d8dac476c01935e7ddbfc164112e35cca9180ffdae5e56f1fb31741c327b5733d6b0000000000000000000000000000000006c1721530a765ce427eacc4e5679c42591d5d1443f0a1bca8a87dd19d6a33b731db6561c50a35511735324c5f402858b061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d6100000000000000000000000000000000016682e225b46618ff794f2da02a82e40193289c9df4ed6985b4daca3e9ce9ac6e8ce84a3fd6776119ae1a2e84f62e73000000000000000000000000000000000e383f55e44fa8528e80fdf391f2804f7b7f3367e0db07b78647e9ceeba5fb151a5b867bafb2d9c07a6a572ee71c2714355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a00000000000000000000000000000000176de8a3ee21e803ec6fd42f7f297daeaf1541c08c5c359e286ba65b78d7c31a0a630a2c73d2e886cfcb289783f30cf20000000000000000000000000000000010645db8d7d42e004c4f76bb2fe8b99a3177624ce0c1f465e67f3767bb57ca80ebadb12fba65bd021106e17adcd8553430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a77200000000000000000000000000000000006c151767d1066f9567ed86f7759a6f425a9a130a4530a2dec0913e4efe2485dd4b0105f453e90bf27cbeee5d0482af40000000000000000000000000000000019a081fb1fe2893f1919628cb8a3b332ef072971fe6ea7fbaf79d327440274a589045db5d3f06d6dc32d6bc7038c528b89a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000d19d55d1fa04f886078bba50e09ece3a394f3413745785c16d17c5936941345e42e4ac50cba055d79f2d813c69e0b20000000000000000000000000000000000ba513864132f44be3056d3d3d1fe8d10b8be954e785e3d07f816875a3454fb6d44c1a6da8c9644648b46dc7d8a0b67120b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d5620000000000000000000000000000000019cb4ac7844effff88b242db9908bd8773d91cbd8e076127493c548350bb9f8230d57a3e9c4e4b212e5686bee925d80a00000000000000000000000000000000021e94fbe9881b2f5ce2e8d777a33336fa21c24818cc1b6b699f0bf5cf1f22d7b9fe85be05d09509b88391f78eadf14e3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe000000000000000000000000000000000c6429ad7548acf43bd9e7fd9ccbb09b5b9b4474937bcca985a2d00c62cc8b72e07e725a5d447e2a92a6bb9fff0c50c100000000000000000000000000000000135ae562ac2225bdfcbed36817c8deadf892da1f8982f4bf53271320bb4e702022128dfbf9e48fc6623648878020c1a67fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000004a813c60a1988f7983f6ac644a66369153319e3bceda90fcef6fdf3e53ceb04b2c5d240cc65aaeb2530e8931f1a962b00000000000000000000000000000000141411938210cef5576dacba6d521bc46b13ce9c1f2a9aa41a0e9b56639995b69b6198f2a406ca5e471cb0a48233985ff0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000041855bc5957b8649451b7d91ef58fe8e0770b113ea3009815e60cb36c9b7ab797b4448d3747fa9b64b7fb50af906b6d00000000000000000000000000000000048f78b763a88fb7122e117ea4946a631be83b5ae456f0c77a16f3f2b546802bea7117eb27e23a5db65d616966bf2630f8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000003ca70d52cbfe2c097c17bd300f4baba1d03951c6dae613bfbbd53f68598a71d80a285af1a16365b5b82991599ae8fd0000000000000000000000000000000000ff454d717d8518415f23ced167ad7ad1ec76c437e29fef81b5604e8bc628b320fa39c192f32aa6201c2b5b4035cfddc285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d3258200000000000000000000000000000000098363ac967c6800b28c28afe92c1379574ec11e0585a0319273aaa6b92322563ad56144437569f3b9cd70ba9e7f9e030000000000000000000000000000000006e4aa226ef031c07150bb231046f36b8ced6b795b3e3f25f707435abc214f14e0c420c699f9c880e8d647ba85d467ef35bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e440000000000000000000000000000000001ced5366374fd923b3196d8f6e35900b80d01eeaa6ac41bf7d05d1fb7d47810eb8cd2d1ab793126edbe863be4c1224200000000000000000000000000000000010b27a94ae8413494e0560a10ac71554ff502be7e86cd9760b0d4ea7d1df926cf7ff1661b7902fb93ebcfd1542619caa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c800000000000000000000000000000000120ffc413256888669dce253043ace9a8c924f2996d73ef3a64d76d88dab415c870071a22b97da222361dc02d91cb25e000000000000000000000000000000000940f2259f4fadc3bfbed20ed2b80bdd86f30a846d6167661339e15548f6e57030fcd0be99496fa406a2d025077a4a4e1155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a640000000000000000000000000000000003ea26434b5bc703c242cc5e84e17be5c7777758f0b232feccef6d200db9a03f10df46cf0eead48064f8dbbccccc3369000000000000000000000000000000000649df5d665a64565079201123e954e78f07177739d082c2bd0aabddcc13f9fec6ef082a1348a369e446b82181e52aadc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045", "Expected": "00000000000000000000000000000000110fac33d46271daf3924995a4798b3f62c79562d3b44f736b91add9f2af779a614d4b12a9c0d7c60bcb1f104b35474c000000000000000000000000000000001592121fbb147085613d1b647cb0e4a7b895bfd4e5391b45bcb287975bbf0e5218078d3e88f8383a506550ae07c9d167", "Name": "matter_g1_multiexp_30", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000033f3c31337bc48622d27a9a3224a2acdb5c538a59b497a4a85840c81cff667ed0a0e4e3f4bb23a9ae53c1e79ea54cbb000000000000000000000000000000000cf0dc22af4530260cde26aa0eedc83a0ec3ae87d024e6907f3d22070e1054b3d4f24d5ace7218ed44763af6ec3f25ee32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000935dce5baf85335575af5a6801b36647727c3e28f224cf25227bfaa52fd646d6fdf0f24466631a93506a58b5f2df9b70000000000000000000000000000000007e032c51e2d9aa53a3120e5777a14963af8a9fc65dadf5da779c5ade6aa043ff496cf4f33e2672dc5e10c4a06dad86a6583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e86300000000000000000000000000000000178e7b4d05c4b7762b474649b38a5ce999c67ea677fee77115ce7e55207d87a82b6d05516ab41c2bac294fc382c0e12400000000000000000000000000000000126e5aef1a9729c73278b805cf102934239d1f706bb3fc3a81f3726feb4b3d2fd8de69fff2f20d5e5217edabb645e8df5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000014760b82d3b4949c67d38c6d9172e12bacd52ed49f442d781aeccb7c0444407629e3b7d5d5e1be996940966785940e46000000000000000000000000000000000aa2d6391e40e50ab9ece25786a42e8dc657e9112683279b143be5665bca43746244c27352d3600dc62c2c1c7776924339c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b0000000000000000000000000000000003f7c65aeca3fe6e67c91e1f284be35149276a9d9c0c1907010d8ce26d5c88f2a68b632530a31e41388cfc97529485f40000000000000000000000000000000012b9322902ed50ae50e3bb3e07eddec3245df27f193fa88a7685795990a5fecfa4be4b5bf8b0702897cfa369d614eb942196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c5650000000000000000000000000000000013995f89bc17b99384e389c9a768fa4bc37526606966a74a370c9f964cd9d3a7dff9d6be2319d2c8c9d5ac1b6f5140b20000000000000000000000000000000017b32d8800e21a4553a1a15ddbee029788f58023164e65b25086e0dbe2ee0c16e519dcc4753c322b50c24edc305cc26d8df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000017601971d5328ca817108dc9899c9c3b88aeca2ac5c03f70662c9bf6bf3e06d25fa4b7150e0838c21c9b089c7102a17700000000000000000000000000000000198db85ed42c61e1137fa50c8b2a3ad2eca4e9dfde3553b8ff7ee3aa6389d73c80d500c883e52be5cb9fe8f828bba84f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef0000000000000000000000000000000000c830262d029435b1b857e7e3cd118e8a6825e3e413f5a5f67b37da686f442577c0beca3e86c13ef6924472305ab54b10000000000000000000000000000000003d35dcd36ea7352d453041e821dea655422ae01a50731698af020234e3ddd38140c24ba2af296a964f4f5896bc0af8c8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000009830bb211c58fdb25fb97a4ba226ab03516911e7b7d98f25b94c827774592b5d5c56edfe3c3040454def1429f81c4fb0000000000000000000000000000000003f34873ad16852f435cec18f977db00f786b7860c580ae0dcff8f03a8a1edbb417f01e0dbeaf035b6f60b733f38a564cd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e50667000000000000000000000000000000001718cef19fe02a179385ba031f23d28e20e7f57ee82db31e632cc3530d17291e54e8a01564963835c724056c53f9853b0000000000000000000000000000000016c44ed6c85628341789e80e1d95a10399b6ac126319bba3c66bdfe6a40f2b06b721a0867c30be1356656cd36e6370aa2a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000011267a6e9adc4b547ea0f42ff6cc9b35a40c3cdfd7ea3c4169fe1efdf533341969cc591f26fe9a48a44e544c515339310000000000000000000000000000000013d878f761efaacf28677577c93d825336698772044266d469b934332412bde9ad5deeee4c1f534a9fd89e799584d3394a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee280000000000000000000000000000000004ca71357762ac2e9bc1f53919ee2c19d071fbd3918f5948f32ecc78be1e65672d12afb4d4a8df41a038bd5448bb0a04000000000000000000000000000000000b80b54ce782afbdad1cfbd57a852f629c0452346d5b898062a8abf12c73bf79296564d3fdb867ddd81156697a00f03ba62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000009cc641fda19b0e33065a35e74a7ac28ca1bd3bb8a7fd350244ad0cd5dc89d91e7b2865e78ba24e112589e298e6c5cb40000000000000000000000000000000009c3ce4324dacb1e2ca82f4ce6a7ed1292f204f4f7b2c5e0086843546c5c00d16be4e7bd9c979ecd3af590b40b0d70a4a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a60000000000000000000000000000000016ed972bad2d24d80332c4aeb1dc012ae4fc30a11597df1ca73114945c20e337d1c424e636d403141c737103a4dc02470000000000000000000000000000000009ab2d22c0161247a3c4eee341027a97009ea95bfd45fd186e15feaaabcfc09fd39dfeddb2d3631b943958620555fed81f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b40000000000000000000000000000000001721e0fe2ebc0be63df10f4b9db3faa5c5fc3ada0bfea176c4fcd1cbb696779c03602cbcc1da3917dfc09af72fa3cee200000000000000000000000000000000192e3e3b5b9b087aba72b852319c200451a4976a4e7cd817eec04c007c8a2f800fe0bf7834d22a21c1989ad8c6ef73973bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000159c5a01e76ee666e8e22aafc77e27705a633bd3d1dbaca92117e4b80f917a3bfe80b36d3fc7721ed2fb8434558c780000000000000000000000000000000000c8c356e19c759e1eaacab45b4fd2e0b42dadf6aa2ee8c051b8ef4de0c4e583fadfd86ff6bbfca1eed42a29afa470c8c1b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d", "Expected": "0000000000000000000000000000000010f2b9ae629ef12f213e6408c94601482c4c3cd0ee33e3418c86f0b8092d1a1ab7d2600140f6e1231297c3bee4a48a9400000000000000000000000000000000018446e6fc72ffb3c6c25d6aee2d9a8bfafec7b4f63dd3f98fde09c088876c7f4d30cc0ee31985526712228766ad91d9", "Name": "matter_g1_multiexp_31", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000de77471af6d857548f26f2ccea9c33f50db361c59b097fa481887b5a5deb4fcbaa25ec1008b131fedd3711d4d3ba029000000000000000000000000000000001037ee7b2005032974767d672e14be86177621db0ad5d7df5faa966b0e7db6319ead334358142feb370f60cec698f3d1bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc0000000000000000000000000000000000ae57db1c0d1575c49f8b049667e1c8ba0ca863fa56ee58a34ac1ae780c92418ec50294b666a0f99e0efcb2686a4d27000000000000000000000000000000000aa08900fcc4f9b551229b7a8a59aa9b337100c68703ef60597f6acaaa7c1ce910e643549dd0c328a7fa17e44b68de1cf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda994380000000000000000000000000000000016cf186f3a0ee77c7e990ec0784d99510320114793fd7a672d5f739e9b0f1186faaa9d5914860d66173696c603173b3000000000000000000000000000000000124f5c20e988b460c261d274251841cadf5c99a12c9ae8b4b3baa7fea8b592192dac3506860b15289df704cdba1dfdbfe45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a67000000000000000000000000000000000166434c1551befa708de9201c02cfe18020d18ed881ac4d154f5e560995f302b57b1694740f76232307ec0ff729b2709000000000000000000000000000000000a961fa3c19068590b4c252c0429414ef393ee071b02a4ef15f6a5c722a73d145c8e058ebe1997058b38ce7961860da954868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001618c78e4962162f253729c4cbe326e7ea7dfd6d5cdac1b17353135485d434fe7c4d857df673793e9d12ee65dcad4bb50000000000000000000000000000000016921790d30423d878255c44966b316f9c29dde6695d66a97139fbc6fba9c4df9e291c308effc424e5e2134680846fc37068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb00000000000000000000000000000000128a8a8584726a4aa2cab71853f843f49efa79071a8ed0a6ed2c7913fbb85e254184d457163fe647d0ad719d04e6857100000000000000000000000000000000158d36271e87ac2879fdd3f1fe8ff306126adb340ed93406951e372a7f7f3deb1c347ccf598f2e007d92f502038bd4960042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e00000000000000000000000000000000047e63ded02c49b7126a1023f1ed4a0af20c2d5e95718f474e4171c0fa888d7fb53b6a2bfcd47893aef6657f31071167000000000000000000000000000000001404e16f51ea45098d5bfa00ece3df841a3a6630bff2b02a8063ff9af5c3f149e504f04e1fc9d9bf35324569e8b2e1730a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da000000000000000000000000000000001866eb045ddc4e29fa612a31a34355ecaaa8482cd0885bbfbc5cc0b3870a86a2b4c3f15da23638dc03619cae6b721f1800000000000000000000000000000000086aeb6a413db889a86bb3fe036486b4e26dd614aabf575f8d63614a300df8a528c9f6d47d59daad59d840f591063b22ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000001765c386f85f7282251b6054f03a3941d44f9a8ea2814a49f75519f9fc985133937e2c9e06b59441a6d9a95c806d6b10000000000000000000000000000000011db74b6bd144f9a0d48185a3e9f4adbc79131764b6e82f11823f1bec92245a55d82e6d949f3378ea6605ec84f0613285f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000000bc9a01aee9eb527491f7334959b0f4275492afa38044f0e6dd222a3704f440b5ae2120e8e2798179634c65f3d674413000000000000000000000000000000000ff19f94b6802a4788c4fd84f66b9be03fb1417544d56d0e473caae0f9b9124c622e6298624fa1d53886fb5ba8b470fdcc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed2165000000000000000000000000000000000df5ceaa6ca501d1869b51f035c19c0f3f9db39c739f882a380930cbde7737790b25a2c01e65ed477755c2beb16e97f300000000000000000000000000000000148458f4ff4fcf8559b9f8a2ee4e486febff21d91fe4bc3c77988007cf700186894f1c1fa18ee3c4595a462712750d3097b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba591672700000000000000000000000000000000029b16c9578701febf6662da833091deee23e647a15f16895fc057a37c153fa738efb1742c4bfcf27eda953a07aa01c3000000000000000000000000000000000196d74cfb1e6472b7ab67a664a7c46ad0377c2b465e12d94b035b4b79c7e358475339e09690557e4b280cc84391eb84752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000e005ebdde060ed0233d1b1d6344b8d21f8cc1ceb6d4fcca389303e1c44c5964a4521dac8ce225e2e4909c4b2a47f622000000000000000000000000000000000fb3185aca9683a81d41a17b3a6048e75549d589354d4652756a4663cb25b9fbca1bcb9158e2ed73765d03be4e2b570f2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee300000000000000000000000000000000004df18eeff223e3a255e6652c3d14a6dad17c76e0597b43a6679a85f78d4bbaac1e2fc0ccf6a89149dc18045169345860000000000000000000000000000000019d60ee8b23308fdcfbb26ed30fda1dda5c6841b46fcd902e6c34dd268fdb1426e215d21bf650a340b284d5c7516efd3f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db44580000000000000000000000000000000006b084e91066f299e44a0c37cf65c30009006ddda34d4151b0c18a5545d67f2bc76df0bf9a78fd2b771795c8d041655d000000000000000000000000000000000262ba1d9dbb009f779e2a584ed313d78e4ac69a811e071c10e21027138234a32deceab16a33767fdc4a78062cd23ec71c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc0000000000000000000000000000000009aafc73979c000236c08e089828880f54645b5ff4c1dcfea0ff41ffe8e3fce8ba0dbcebf0d4205bb6616a737b6d3542000000000000000000000000000000001399a2072604d50f92ee186924ce32c4e887803dc258b7495aa2f3d2187571045db7f360d2614b198f83bc8024b06559b0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4", "Expected": "000000000000000000000000000000000365ffdbc48aabd8f0e786634b9a853cb8312bf295543bd280c1a0a9f7d0f8ba95b3aebe31987ffab1f69a504edeac2400000000000000000000000000000000150af5ab7e9b1bc60cda3ceeada36abf9bb43f1182659d8d72281c1f1cdba73fe7d6e52abaa7506b89ef43f092f25bba", "Name": "matter_g1_multiexp_32", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000012a651f467e9a1c1cc99c82e16cab2cef53b77268d968dcc73c5008e103d2e4d19aef4cdffa24b9474fcb393a48d6a70000000000000000000000000000000005d202cf9bc8c0124c0f817465eee7d4b1219071cfde50ce2cf8951efcc21fa19c762a1a8630eb7b8dd90cd03b8bbb0484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd00000000000000000000000000000000060650b71c97950ce5cd6b6bfdad46d66df454c5aae1ea313a70e7fc841e06f64a31edaaced17d8de56f1ee75f5263540000000000000000000000000000000018a211f44acc52e92ab5eb1ce304d80532fd4dacce60370dc62d9ffdebbf749689620798429b5ad1d8293c1967a43c12bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd10000000000000000000000000000000000f51ac340d512becf5d7a515111f63123e9bc940242ba42be9f464b89847a8cca9d93360851e3d047de4ee667a6baf0000000000000000000000000000000000dd7e71b516b3752c5be5ee5f3908c17e3e019b46422f24659596a42e569ba9e8711b1e8f8329cfbb990942f258cce103717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000015d542246cc0b46bbf5571c3173abfcf10ba447e5ec962b5f712ea7de3974c2873df1979c9d6432bc88d02588a3730f00000000000000000000000000000000005e1611597c12a4c7aaa25bd9ab1b6d30c58bd1fce3d87d66a03f25d6ed110c84c3e902ff5475795b5159126debf6cb522788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d9400000000000000000000000000000000018f565b38ce775e6b40581f757935efca255311b872fea3bfafa0662620ad5a02a7e8ce48c17daf45668c95ab0487c4e0000000000000000000000000000000010686971b402783c1e7d60126cf484fd01b871944179adc4b28de5d72e5b8823b48d382a8b69f6b4681c74961ca2a3843c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd3840000000000000000000000000000000008d42e31cb4c514e450f56488208444481db0beb5807c6f1c2d82ee09c9413cd6726dccd72e0b8ab6f6ce6492921b14f0000000000000000000000000000000012143ca6dcc3bc9edb5b10c3a47a5130e393986dc5e83d1eb61d9b193ca28193101eadf00916a3cdcf7b6c1369b17038ccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a00000000000000000000000000000000101eb8b48df43c3e01c1508aa9d3dbfe168e7458cef2ff61c15d5b4e8dd11be6b9a76966c01682fb07368f22362f355a0000000000000000000000000000000000babbb820a5a8e0bbbae1e2455d54b97f6771ff914fe33a007734d5072a993df31c6a2726c8b03a8c2dcf48a73959a8ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f0000000000000000000000000000000002fe8c461de25f5e6c5a082fbc4ecab5a37dbba9255ebaa0b5d245735edd27550968c2558ed24f7bee99092228e37c8a0000000000000000000000000000000012513b2fb62725aaf948403c13f11a6d7461c70cce3e4f912c8d2cc9f2a8676d9bb37face3770e7c0121bad6af6302d121cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c91000000000000000000000000000000000175c93838001f4c67a3e0e5dd7eded26a8818b2e492eab2e0e6f8b421e3d3611561c8b933010a3c5ff96128631f4e88700000000000000000000000000000000136292092a366a73a5609cb1e7fa403c59825e99c8c91a37b289ed779c4a3db71370a4bda2cf8509cc9d4b4731b4f52d2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a05000000000000000000000000000000000d03e1d6dc4bf59262fe3bc3e163565110b751c534e57c621b4be59bac28d6e8bb379cd4afa3740797dadf32194fde310000000000000000000000000000000014ee46a0cf13e795c8a46399ae63e1b812f237eea725539265e13d3ad1a663374dd566df450fc1191512ba978736e5b779cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000003cefffd8fa01842c36dd9fe1c57efef3278eebe5d1020582c3d13ced75d24177127da37eb59e9b46b4a0a19421a5aef0000000000000000000000000000000016c258ffb2edb299fcc04ad309ee5d8a8f186db5f3af8011d42b22b23687c2e814e2a8d366f3cc61d7c89bd9619523b31973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e400000000000000000000000000000000097b6535843436f879ce659b6ac9563d81ac0262b9a861bbb367bf8244a35a5de51f3060d05cb2174cb41c8c3dbd8dfb0000000000000000000000000000000012dc9607e0ebf73e3577ba1ab39437b03215e366cf1ecffeae4ad4c7919a63f62e45103db65de4c9e3281d7604b07f24a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000004479ec5d5ba2f1c661df8e4f85320d0e754372e0c463098b0ad7477f7373f309c674dfd31c7f08cccbbf4bbd17c23d7000000000000000000000000000000000470cabd9f5c4bb8b1a370888d8f0f486387a89efb92912072fb0907a1e64f3327e9beaddeaff44c502414632243d6fb99ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b00000000000000000000000000000000038101da3c35dff20a878300bcf69e393b77873a971838581daa9d096b00bd6fec3dceca882a02d397a90c816fb415a4000000000000000000000000000000001184246344c03be6103acd745b3ed37d8f67ebf0caecb00cb2528e0da9aa3f352a4677dd6b832c042d6e1235da7521fbfdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e90000000000000000000000000000000000cf99121ecf9b02cbd006348b16f9d80f64ae3c946c4802ec6bc056bf6e95e01b80cf3fd10ab1d30260a402b7c46f880000000000000000000000000000000015f35fe1ec8c258095394ab2b021d63ce54ed4bfe14cc5666f5ea4d5a0461d535b8bce3263913c1b4e6db6996cdc037d786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e83f4b1d3eb8d45ec0fd9a4ef001e5bfdcfb9c99a6d1dd4b4e8043b4d11f5c6fd65296a33c7fd26a4e30dbbe1869090000000000000000000000000000000001197b11d6747280b37769946549ad9d4a1ff1006ac726d7cd322cdb4e3cf86906c7ed371e770fd95ab4fbaa1b7b514d985d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6", "Expected": "0000000000000000000000000000000012f496f031f5c1b594256e272520ab98f3733fc9c481e7ec8de8ba70f493065eb25b681a3959994d37aec979c22c6c3b00000000000000000000000000000000015dbaf471eeef9307d8dccceaee179d8c9072b052af66fbf049ad1d346e08bb555238a763e903541fc72d9edc30ec30", "Name": "matter_g1_multiexp_33", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b632afb8deb955e64fb4ff5aac396152e23b11a3f326df0d77b3ec078934cfd5e486244aebb44cbd1599f594991a26d000000000000000000000000000000000519f9de5a5b1623e4524be68b5ba0f997addb4da78adcc9c3d5910009a261fdf8b0efbb6e2a085e74112ac4e2106ef319582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d370000000000000000000000000000000005456d9312825dcfe5501b2c38aa610a767bd38f46cdc8acd92f0c8206a9c2f9b8f65c8baedffdec5e69f03fd3adc4c40000000000000000000000000000000009b2dab21ba4e4b4c284a623994b92ed5fff0fc198bd154fcfac9abe5f05b830066b44894ac6f92bb2f61bc88a7867a8ac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d10000000000000000000000000000000009f55a987011dcfc796df284c7bd758c3024d4f09edb3884dc087de26fc1df0f71067d44fde07fab9334971b4a0bace000000000000000000000000000000000003a4ee3e9ac2632cc81cbd4ba397d44f738ee390a4af6ecd65079f412bdd8c4a37d5413d0d9a7dbeda8a1267d6d843b069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000173c7db310b54a4a720074dee01dc0e5f84b606c9c3ea0962bd4610b569f478d7a5221feaa944054cf7395e578d730d8000000000000000000000000000000001697f0e16c49b223dec9e0fa429e68dbaf96b004a561aa3e37158064ceb9232c1cd21156c053fb89ddb230deaa7f8336be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c000000000000000000000000000000000ab7f085b711171f999d0c4a46cc7c8cd8a429f6bd90d1b860c01066bd0d193f1c1441ae5aa97d690569807749ed69e1000000000000000000000000000000000824841eab90d56a1810c129b8f27d0068fbb7e3536d6e56cdfdd9eb553e283c5d0ab1c418869e886fafce53697520859b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000bbc8c5b5e4373e76457fa45acfd3f1151735457b0fae06e1d3e6e5dfeb35815aed44bbe6395039481ce02d2aa2c502900000000000000000000000000000000089ac22ebc582bb71a60c88638747e2243096e8d193fa1863089698fbf6805128f9e32636d6f954ff03bfb6c5bcb0060d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f00000000000000000000000000000000001fea43c3029447965718c8e76100875acc8fb4da66f7a4f7fc5260de3844aa9e9a89ae4d9baa11c118b9f851fd63de000000000000000000000000000000000844aecf4a3ebfd8b711dfa9efaf1a57d635f46fb980903e362d4ad55d48c4289a3fb1f439e6b7d8f88cc51867d6b462cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c4000000000000000000000000000000001384e33086ebe795cde3c951de9b48f3f0fa2f627524cf0c4e3691599b62d4611c6a84897298c287d162825c3f153a75000000000000000000000000000000000a04af7cc41c2d3663444c8aaabeaf70dd146dec114458b3d1dbc95cee99ba89a4c5a38f2974622292e3236fe2aede6d17f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b400000000000000000000000000000000158820954aaf8e6387cc0e8e528723e0875f5f719a46ae5cd9d967674815a2d9679aea9b5736f882d37e2dd26b7db17f00000000000000000000000000000000058cb933f8dbac61a22477cdb3f52c9e3de6f060dd51aada35b6f8480a53e8eec8f82800e89ccaa2d2eb1dfb4352f16561ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb50000000000000000000000000000000000a7c9c1bf574503a884ecde5e921da80b299c4efe674a2d5c841e6036adaf7c1156393116c2c0b9827978d43f1e3e440000000000000000000000000000000005cf22e56bf4a46504ecedb072fe5e18096f9da550065612a1d00cf79c65384dea1bf59cb7c52de905a04f1886f36c8a0fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be0000000000000000000000000000000018e344838e2efd9363911898f27882f67454dc3b1bbc71f1d99e787bbd6a1ec9744876156ed8db2ccd826f2b4fa784050000000000000000000000000000000005528854a8568ec6491c79aae1df15d965cde683c9ea400b470105117f2bf3b41d2f958a8dea5f866a55e60fd06c1f07d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc400000000000000000000000000000000047dfb6a6125ff02e12c4a9d88ebcdf8a4375367e1473f5a0d99152bf0a4055138aa9a83d98d7f74d9fb8888f643cac00000000000000000000000000000000019d0bf5162ca55d8113a97cc3255d090c6924362e6e05083fc323dafc3b12e898cd600d2730acf8cf5cdfd4420962881c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e132de353cb09b69ab369c616718b9cf492cdb9d3002593319a6e7b61c7d90f94808b75d8c7e3b9d7a811d01baa47a1000000000000000000000000000000000d636abffa063379e2084cfc09da5ee04d40d8e74ba0247a01be414cce820024766195520f1d2eaa90fe254e12a4d86026cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f0000000000000000000000000000000013f2367ff71430cb541557f79c5ae8a0d9053d82341d83037c1f73a52585255b205706227de4e87d6ea2ca602483d2170000000000000000000000000000000011f3f4e882de30b40bc160e69fc2bf4f7c588cc83bb9dce3467accec7c47714e2b326be001a36c42ba39c7f56b72d6fc699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000014bcf3f26683234584d79b436cc608462f1e2c20b5ecc5019988d8e30137859a4b6d0e1135dd5bbea0781b8ed3f0653700000000000000000000000000000000090ef29bf63ca97ae8388588227e1d1a0653c43b16a35a63f2ab4f0b11fd8005d9a85d30a7406491d983f347e4dfb9f140e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf9523000000000000000000000000000000001817fac61301ea6a43d7968b22616b836ecd1f20e5883e9b475c18353b066f93bd68a8274d0b6ea4480d8e314766dff7000000000000000000000000000000000c52fc676604061338bf0712fc1606dd09783a1f9a5250e3417056e3c39e59a28c7707d5225808414279ab61e49b6081062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d", "Expected": "0000000000000000000000000000000018410462829b3a72024468ddcbc42d59a99a70296024654f99b591ce016304537c525513defb655417ba3c0f5e614aa8000000000000000000000000000000001416a19f73407c262f5e464021eeae1d1f10c3ae5e45f132a2f402a75cfbe409651d3795e482b15d29037e2f7105255b", "Name": "matter_g1_multiexp_34", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018e6f25220e4b4011a0291424b4062930f5df45eaf1581d9591560fd77e630411e0abd57f9973d4542741de5cf3132e7000000000000000000000000000000000b31f903e7fc36327e404973b90efe5a5d2249770170ea1e58839e19d8aee99743be012b6e8a3fa73efc6bdc08be372f764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff20000000000000000000000000000000005dc07fa620d476d8f64358c920a401f8b08abf739befe1c266fb307b959f37542140e398c33b082d09f9f53cedf6f810000000000000000000000000000000019d8e51a28c936b5037424a7ffa8cae75496131eeb2b2d5034e4e882c1c91f6bbabc9ce4fb2fe4be3da4eba46326a3603280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000006f5f80dcfe8be87d057e2162788f7599e55b69ee8c6bb6a47c505aa324ddb5ffddacfcff35cef3dee6264ef73d6a353000000000000000000000000000000001056081108195d4d27af7332215c0b444c9f63c7574eefa81046e1d064825492e2dfc5bf2ab5847a37e6b253d9dda9fdd4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb111770000000000000000000000000000000008db431907692896f9e6e254a6eac1a0ba5f9cb84563da69c3601aff1370b7a5a98edf5a5fbab06abfb4496c777bd83f0000000000000000000000000000000018a3bc407fc42236c4429f241fa760c6513614653e8b02835480dbe1152763bc6a1a7fe076e8bb44ddc04322cc906e1ac66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000cf32da94af97001664607c7840631a8df02a008fe262c6dc649a3eff34a42dcb98884212bf3e979629c98cbe5fc457f0000000000000000000000000000000019b3b4d82326ec1aaa3de3b2f8e329ac0243d3f6bf9356886be4033aadd0398a5c58c68510de29f92a7ca910d851da244b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b845000000000000000000000000000000001504d90c52af16b5f88357c87d4be7c329855ccad6f6633af0fcf4341fae54aa4b1ddc1aa22fe1ac12e9d850a05a9ffa0000000000000000000000000000000012ea642b96304316451dcece5a6bb324d197e31f56ef3f1a17c973742322d08f443b7cd156787f8291b52c0a6f78b4b1708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a000000000000000000000000000000000fc61e9589a2dd7f6dfd613225d80a70ceb977bdb518b5a16e415f887eb73fe9fa5c9130d5fc6deb4ad153c5de0907d6000000000000000000000000000000000a0fd7de87139581e9b1ab707e25c186640db92875a7822d61d8c476c40ea07bff000cbfe6975076434d0b703695740685ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000188901f19a776ebd2ddad60209f4545ca9b0a038b0b3c67b6f5e35d61f8cc2a297d51450663c4af182079d3ab6b01d2000000000000000000000000000000000151b9eaaa281acd803abd71ee4098b4ff6535e5081a33cc68ecca54eb9f1a8f94f3b1b21440f33b8648ec456dc1cf7f3fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee314780000000000000000000000000000000008ce8bd24052a8e1472bb64cc215974e20bb16d502b3a8113cd6e3e9a2bb7c3fccd45ff711518e8430221f40859374ba000000000000000000000000000000000aac2e8db9123be3e82905a0fe780daf4a841f6f961428b9b431c3ba2ac31e8c06118402bfc7fd15fbe3ada0ec8bbb2af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf2000000000000000000000000000000000017c580f501a1c4823483ae718371432a8a69e16e42dc0b15bb8e01729b6707ec20b898e3835bba40d7e8802d9438281000000000000000000000000000000000bcc167264fb9d6c27272c2280d8e89f9655ac7e6408694a3a4ca6fd0b46d1d7e3cf608bc2ac343806c5de42ae7a99e80ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000017ae89082d6f531bb7905068a9c00017ba8ac8867c6e467fcd3e88e9229ba5b21ff4d0a5ce937b75b3d5dfbbe35f2e7000000000000000000000000000000000005bda8d641b782ed51c416d0ebb1cc7c8f623d49b741a7cb93b3514e71d5b9102ba2e6c768661686c2af2acedf466e4dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d099539000000000000000000000000000000000150ed2c2b2d1b0b87badd0dda44325000a6fe98d335e03f0d4d147b20d4738e1e0f0ae0ddb2783bef283684e631ff45000000000000000000000000000000000ec1fa174f3f42cdb0fb67a520da161d9a9d1e53a5b0735738580fa3e80550c95cc3a1cf67fed67dc2eee1597e469fe0e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f900000000000000000000000000000000111f184636052719c6df1541c100d5a21d573370fa7afd18f5ddd1d86842169eeb02c494b33f2bb2f54278530729bfbe0000000000000000000000000000000016be03c9764aa34c898dcaacabd1493610f55efd36ca0b35eb48e89c7968e7a720d545b18fdb95954e01596856d42975cc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000114fa84ccbe9552a2ce2368f1778a1fd3c67303d8036fe4ba171ba9f2f6039aec1a59fea1b8efae88c01bb50e53950440000000000000000000000000000000017a51bf70c41571f36d003c0715238b6c8fd64185f616cd9076b730ad16caf364a75fe68de246249a42cfe013606874709a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce000000000000000000000000000000001450fe1500a6fa9d966a0c905167a414d59a3f8a064089f09db047241e9abc31d9e41ef73558eed741541414731f838a0000000000000000000000000000000017e61d4092537ec48683f86b72123637df25a5fd926e5703f993678a798dbe635ea29303f8b4d9ac76231a71cf515a70d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000000e72f0c855fce66335533c05ae30031cbde78ef07571eb1b645fa3ac5f3a7d76a4d60cf078145617c5a7ccb16266bbee0000000000000000000000000000000005b3981900432b193985f28a88a72ca9958b4628e5ff9d2cf8b0b23184e2bd433d495636de3d56711f207719fdd3fd2f9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12", "Expected": "00000000000000000000000000000000110feb31a1c40d570d7281ed9f0c0ac8418f4a7aeb6be3221b130945becc15bb353ea63623ec7dba2844d3f527c167e6000000000000000000000000000000000d76c7aed58945a7fe52f37eec3be7cbd4438645a649a04859a487e9e2d4c82bfc76f7ba990f825302861d82a748c8f2", "Name": "matter_g1_multiexp_35", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000030c6580a3dc73be106748d070b24d9231c382df143fb4bb8ad45e4723b40f90724b7e54510da1b2bee523a29aeb58100000000000000000000000000000000010cb3562fa1b0a3778393412994e46028367ed52dd62a1d446fa02b50acd48a784ab49141778bee5036b7d3a95c9ec217b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d000000000000000000000000000000001972db503f6d70a0b247eeac7fef277098604e54465309967b68d24ec1cece802d8c4b699eabb72e03736902d41fd5b60000000000000000000000000000000007f30233f9043927a629b11e7da48f895fce86b31911ff5c511c7b50642c296d37a3078e2e12f1adfe668731d0e6810ec6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b4800000000000000000000000000000000011ab9fd98e42539382c85bf76b563478fae8cca90ba1beb0be56b405da8326e6f1348b94eba61fa29c78645f8eb96f8b000000000000000000000000000000000f30617240632d129ceb69de1d69a23c9bdf950819608deac0600d1d1fd730a3a6d22dcfd635b25154b5ac7e22b20c70410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d0000000000000000000000000000000007556b86cbfa9f186f38fb1a8adce4c08f93f874bcb36ba61df5750c7927cec8896bf831c0150c249067ddada2e914bf0000000000000000000000000000000016ecf045f13c78de8aa18c2ddd1714bfc532ba8ff5b7851b58240cfede20f032067e943486df628995b8f3845289eb02d97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b30000000000000000000000000000000011379452e627dbed2ef1c74eb917b95b3933b8fad8295235cdbd6a4394d9b75cd3598c930d48c2d4abbf1558c65e97490000000000000000000000000000000005e7044829ae3f9b073e4a2237de96b0a1bbec3a30dc39c839573eff77321b1e0a49d555f0e31b8aa096f83f5945026bbdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb360000000000000000000000000000000015f8f0f22c1553ca663ce7e9ac00514eb53443f6c4869f985dceb118ee60a88a4826e9dc7fdbf61e77cbc93768fbfde0000000000000000000000000000000001646ecc89754ac57d7d6fe9b871692d65057f23d397a410bcb07ef3df0a3c3fad9eca515f0d0dcf0610edbdaf4cdb5d743da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000003a18dcef4939e154aa790b0ce8265f27cfff48d5fec149d91307759eaddf601c788da6ed8124764bad940f117751b0e000000000000000000000000000000001813f4650490f3839fdc9f96ef744ea93a9fd86f8a43d767259c2e0abafe308fec2bc6b9d62c1dd7b5ab1aebc19586e93c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b68058930000000000000000000000000000000006490f351e78a40c0cdb827aed3869db293c7d654b43d69ad1c9b3b536b1fbac67d50a835878171974669a30ae9ad1bd00000000000000000000000000000000041816bf846528e23eb129689a87c2325f1b8edf237c530eaf578a908fa0a2604baa19d6e0b4a5801280c27285896d5a51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000064be79c5d382c6dab72bbf28defddf14cc7cdbb23eced6bd93abed078175668d4dd66d0b3abc6384165d26bd86680f9000000000000000000000000000000000fa4c8be5d20d16bee7bd5bacc0b0086875a14a119b4888bc408850c0a099603fe3f79d334e45bdc9130132ea15a180f6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b000000000000000000000000000000000182f23242108b022ecc1d156a97f1a5fea2cc2e059dcc82273212f37c312ab77886c1adc370bdcc6ee05cfec957db970000000000000000000000000000000014ceefb3ca54bfde172e0455d34f1f462208df69328782b7961ade821ab91e7b3ed5426b4065fad10cc8fc88c90d8e87f4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb10000000000000000000000000000000009339b95b043903f2a3b5926a27e57cd0c45e7955946718e7dfebb01f18e9d7a2002c670769c4674773a835311f2e58e000000000000000000000000000000000ba94f6b625c507934f633d5420654056a939c68899c41e3f337f7b927fe82191d39905b349870ba0c41c8bfc97d64a9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000007604b5eb3218140b94732a601da577da3cfebe04dc7dcd94396c1a6704a0ef5a5bbd0c31c196f2876e1a4bb7490629700000000000000000000000000000000193098ff839d38c9bbda43944d7b0a3ec9d0d6732519d4cfbec506d29801780813b2faab46658c4383b2f26c477580af283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e500000000000000000000000000000000022ca1a16df42ba543a118212a75eca13707ee01eb3ce27d3659b1fedd99b9fae859f4eaa51e9be9107704276b578a0c00000000000000000000000000000000012d60cf33701caf11be6c9e3ebbddb9c7066dec3821a2e0f9e5b94e029dfea4063bebd4b2fe18c2442311c2bddc7c08d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b00000000000000000000000000000000011d4918642919c801fff0962062a387a4dffe693ec09cd3d0286a18e3a22c84fc09e8396ca82e6054d8535cd888179230000000000000000000000000000000016a1f0c7fec5647dcce688d3e4e526749bbf23c1fcd9e9168ace47399f9198c9b3a6b8aeca68febde1b7beeea0641aa2310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a990000000000000000000000000000000017203c37b21375a524bcc906843a0045229c5531ca23177dc88026e83723db21d9a8b5e52cc0be1d232818ed9abd496800000000000000000000000000000000097b4d7fdfa442dcdb64e405965439ebe70e4e71cc8e13e299fcc0b5dd88c67d6d0dfd254ab9b545e66295e2f3df14dd91b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab9850000000000000000000000000000000000fc4198a87e789015a1e44935321677e84356aa9e06592f9cdbd149d13ac312980f3048dcb9bd02779a3b10fd24ec98b0000000000000000000000000000000011425345ae1139647f93fc13eea0e920c491a49998430a339cd9d4260479a427515109753e70811be4cfb3b96db5c78b3e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8", "Expected": "0000000000000000000000000000000011cd4c4507778871fd7b28aaf79274178df83f3e53c256dbe7a52df884d28df6a0d87d20238a15f489546a459195ace0000000000000000000000000000000000439a672492225fc09e46bb178a5d38956ae237d9f7d187d4cee14597facf2c06d7e70be5ce20e1f1389e4da6321e455", "Name": "matter_g1_multiexp_36", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003790fe37a3aa78cdeafa76bdbebfebb22ab5f1e09e4e488418568fa307a5db18f9d93126b0d3cdd6a28abe3a4648f6e00000000000000000000000000000000043244b9c78fa56c611bf72bd6a17148abe76fd0efbd25085d7b46c90318ed591c5975f79653b98440f5f7c04cae4d7ea2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000008e8fcaf6d2056c6e144295d437f7f1422f6af7a1b62e0b8073141b2992b6ba865822aa2d9fe439aa1d896b2a6d231c4000000000000000000000000000000000bc693fcd2021972914747e48c600c444bf69ca8e1386655bb5d987608d648965c754668ae0a72c2439ba0ed98e5e581fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af0000000000000000000000000000000004e877b9032e168650ec3502ba65118aa0a8013b995a647210c1c36a6e6c702a93caef674d03d82da1f7c5d7ddfa0d0200000000000000000000000000000000063dd22dcd667c8288ca5b172e34b4eb783403105523c0467139b814e048fa21245879a5e9188a1a87d26fba52a9f601da609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000018cb2fffa3181bb665dedf1d60de6096e8c5ce43287cbd86c2df5a5d42d0129c73cd281c085fc562b7afdf52f0a680c80000000000000000000000000000000007f9884780460ea018351b4ccb5a120d44312056b96c5ba77cc38789627d20500d6b7e69dbf6ab49d6bee998a6aded67bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000bccad9f23b4c1231eb07df139548b66714a064dbec4ac6ac43ce18671144f2bf7ed99f16442b9f6600e1122c58f52e50000000000000000000000000000000013646b3c310a4b3f279e17f45fc8104d2c9d00f698b869479a5a0e1c2131e3f3a9dce86115ccd539bbd4346261c5a75f3222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb4000000000000000000000000000000000d02250115596126e858a63a7082a8c8f8ebe055653f5a60c855ddbbe3ed05792d08e5cc348094b8dfa4584037be597c000000000000000000000000000000000f68ec7da947cd0a57177fb91d12a820ef8574f4c524fe54b9420f9ba4944759c92d5919d6dc8030fc663c34519b64c37431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da1000000000000000000000000000000001960580ae965c37c2ec219dd0753749bd70ac2f0c4a3837418023c5142caf7b4dbf592554a6dd95872e018e912e3a20b000000000000000000000000000000001210b4093a07616543ac2034faa9c4a93b5f4cc3daffef2d8450b1a1770948de56c5bdbfdc9f1dc9af5e20778c1e8e6cd474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c73000000000000000000000000000000001955d93fc0f3ce0563ca4f4ffae0257297002001a3eb941cb9d3bf82b8d7f97657ad7168bd386636aaf45398745d5158000000000000000000000000000000000cc7a0babdf499322e060f2c83897fa7b6c3e7b4f56de3a18c823e0ffd87545a3dd68947df8cd8d3de5795ef7cb05391976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd1000000000000000000000000000000000d451eb31b21eff2c18b52b882e1eac68a524e3db43f233a9d08139667cd0173e3c716f29085c599a09f19019fcf447f0000000000000000000000000000000015852c483c8545fbf0932c99b1944ac58b37228d15284c7be5f5259bb8002abd57b26c244846652a862d46016221eab19db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000044e70861dec38d2b5ac7fec042c6b931d4e0a072073333f03ec4382fe40919b29378cac920836b1641e5e2db053c5c2000000000000000000000000000000000c422a91c81a99caa32666511c0ae4decc67cd94e85260b49760ac9e97894b0eb434d39c3884aa4614360b79681403f94ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce1997000000000000000000000000000000000af002ec82c5ac0dc87e1ac27f4cd052eab67bda318557c70fcc2edbdc071ac4a3fcae90f73ee514cdf8a543ef59050d00000000000000000000000000000000109f720464ff2eb2978d66370041206abd9ef0c6ce79d51f7d233c49b72da520612e59c39f3a775e288ba2220fac1563ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000f62e7d0aa954742a2018d42dd9cd76f041d9ac46ce659f4e192053a1d0c9b23fad78a06f61d2c90eb7b4d1bfe6d951a000000000000000000000000000000000ad5a5ce7b66928d8e6e3806a25425bbf2bc63f8ec87002a913c28ab702b83b6ba590b41a0691daa5b921a12375ef47b275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000008b836a23836624b39e3b3388027093125749a5edd5df50ee0cadf1d485c9dac9c2569a82484269fe7af02334369a29b0000000000000000000000000000000015232caa0c064d8d1bb7fdcd23c0eba21685fc4671e9f04cd1dbaa0382aa4e9d87aea42a99cca22205367d7b2261defaec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000875311ab0cde9a925383dc84e4ee8e1610b2f5af0e1f530aed4155cb8ef0b5050d907277f55d8dd542a89e4e0990bc30000000000000000000000000000000002c7a0d315bedb602f8ec558648ffa69831b9fdb6c14fdd44e636ff00777f2f8ae4aa23aca1b261460e6dfd87e7e501131e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f7000000000000000000000000000000000f9531c4998aafabc26e1ab588a97a78c236a854c3fc92424320a37a236d5181d34f8e5533aaaab2a6ea3385acc85f6300000000000000000000000000000000130350be432fd7d68940fd5f54649820ff5b3d015448d48d1f4db3a05ab0405a73ccfc8eea1966abce35833b5d03bf79f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e0000000000000000000000000000000010fefde43b2cbdab52ba664e12c7a6ff29f647942e16ba5a0d41701754ec63bf199ac8e710ae8dc6a033abbcaed3e05c0000000000000000000000000000000002189172e607876a6e1664fddb990009dd5c7a8412d60f7dcb235ed1825c756598bc67f8d5d383c2570a880492d4ee1967b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f", "Expected": "000000000000000000000000000000000765877938c1a8170e2c2fda55241e3c64f7485bbca218f4a2026d00ef4708d014fe4194048da8e994cae1088694d1b4000000000000000000000000000000000b32833dc9a39e1e73578b21f75136be6c6aa2b4128b0e6ff4fe099f7b7a8ba8f2b769f68d32ab4d1f37793aca8ecfc9", "Name": "matter_g1_multiexp_37", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ab94114b3ecf9535261a0726a9bc0e0907385d56206b61b7a42f643d46296c4022bedae90d761d3c002dceaa9167fed0000000000000000000000000000000008e67942ab2b9aaf2f6f865b7e957a25dd7ab8d8a0cba02fb1648e4c7f15ce00f4f5d09199a583f38425bc62d32ddde69f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e000000000000000000000000000000000e53abd9ff27231fbb09155f794e5d126c490314016e31c0b12bd1d2af97a705bc267f92e20b64c91d9af1bbf5e45b92000000000000000000000000000000000ce7d0cc6656108aa7005a56d15a497009c90871f01eb38f1bdc82edcbe4945a2f2b67c9b812aee42cc9a9bf9ee84bc08fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f30000000000000000000000000000000018c2688f573d4849b6d19e711ef4d14659c2c580eb938434a3b2afb8c20c522423db4c7fffa42eee9ee907a6492b77ad0000000000000000000000000000000016a7e69d5539263fd6b7eb893d476a00efb8cf09f21a54e9ff0d1c11e9f3651eac8a5db31b40598af6c943f864ff60ba128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000002380f3260c7289ab2005f7b1d7f572565ec938bd894bbb0047ced0b652fa2e74aef19c9fe6bc1fd469b2a4640245777000000000000000000000000000000000f32ca31e6bbb72a02f4b0da0e1627dab9cf1195fd7f48613c89b06c702e662478b24d8b3730321f803ae3a307fd498bd45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c2060000000000000000000000000000000001892b0d219ebabc3be00f45b00be55ae486eb79b1e41aa7dc8457aa0812e7276c21024c79646128fcb2b3c517aa41c3000000000000000000000000000000000793bed9530c814fa0d0ed1684614c1e6968dec931868a64372dc1b648b1f99ccce20fffec7d485a226033601b92a7f228f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c132200000000000000000000000000000000173f49cbbe6304aa41513d3742b89c6b07a91be50264350d71bc03fb9efe4faac4a19e2591795ff4a7e67fef7a85ed430000000000000000000000000000000019bb5dcc59ddf055f099a1c3949bb50972c4cfd035d4d829dde4ae94ff9669983e9b1a7edccfc2436648dc942862676fe6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000e3daf60e4929b4a237caeab203f86e6eed0ac630a8b955a03460a7e609398d076c660401f8d2bd9601e5bb5e315e1e400000000000000000000000000000000058b20160ca2232cb8b6cc63c5a8e11613afb9776e22d93f687e7ba005b099531f9693f65f153db01f20c8e9bdd7839ea83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000c89f1ebd19fb920b6748b15192829d58820ee4995cab9035ad6bfd8dedadbc6352058806a7d45fecefce40133261f360000000000000000000000000000000019151260431a35d124fe44116d86ea99e3f3aa14e2eb09be8193dbaa8f26fb0ae2451ca1c70610233d3f0af9d2e33fca4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd3700000000000000000000000000000000162b48d56f439ff56197fad444dc460cc6432722b9b86c7abbbfa383ae1546e160716d94e442183196816084da90bf77000000000000000000000000000000001278d0796c26110f66930ea9248078c222a0590a031df30c62fe6beeefa70deb0c8287b0d204a911c147cb6344632bf329d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e6000000000000000000000000000000000f4fdfa45aa3b5d1838b4dc8a2dc6250c069806ec3c551ac961da5b44eb58d962d843a1c17ebf89bd653e9e44d16300200000000000000000000000000000000052ad9ce994c837596339dcfb73ee25bf8326657633fb5861039f197249d425e35c238dcebb287b77f41bfe7f4db5c9b971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e400000000000000000000000000000000185c62a080df61ddc97ab56d2286ceec655172b6c863b509a1a92eeb0719060528ad3a3365ad5e7c0858167ac2c6d22100000000000000000000000000000000126b489e107dfdf4a4638069944d1b1297db734e5da1964086114f9f62081527d7d3f6032c2f29e75b4e1ccf5b3776d4659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e000000000000000000000000000000000f884244e098975b837a58ae0218e7e2606821c95f51d114a483ed5d31a59c9b9cb3b1db029a0286eb95686e0457afd8000000000000000000000000000000000caab7f67feea4752d3822979a770a28c879f5e8f916b72dc71a3b14820ce170fd229fdb61596d9e89b4be8f515c470e12ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c600000000000000000000000000000000155d3e886cce6f257513529e40c21b5657ef1ff1f4e71bc32b968db3e05652b1ac780da573fe1a1b94b7fef86e7c260f000000000000000000000000000000001184cf09544ec2826d0101d2b79095da6e5f77d453203c52ea17b6476360ccf166ef092eccf86dbe3a260f7fd25a2794666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000001523e919446b532593b8e70cff1206e8910444c01399c0dbad932b596cd0b9c2e40983ddb38eeff4fbd5e8d2b15bdc780000000000000000000000000000000004be8fdc3a3296e543701ce8c1184a983a2932f33913d6d733f5baa3a783382739b697fab4a3d6f9ac5b85ffbbc78a3540a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e401390000000000000000000000000000000018d20e7846239f472ef42c78454b6c335979ec563ecbbc3a93176a7be9dde603e6f21afbb68058035958ef7392dff3f20000000000000000000000000000000011ae4de8a7e1a958a1186bda4890d282773788f7d5fc5432393ac9deaba8bccb5db952547f6aae49b8a90c813c5a93a4662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000010641c99a359d16dc3e3f68547288c944d44c7c3e6177fe94428ddcf3c86937a3fe1f41a31eeab551e11cffac012e1fc000000000000000000000000000000000f407b01737dca388d0793521b667757d70e626ea0ba3b051f522639e752280b5657b1b97beae3105489161ae95a470059401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6", "Expected": "0000000000000000000000000000000010084535f50807f287eabff2fdb83d34ca30454e4cd747cc3818a9dfd80c30fb3bf2f9f771d435b79a2d36105266f0c1000000000000000000000000000000001663a611323246a963856a92d52947e72dc123dfbeaeb9a3ede6147246814630e5304b50a6585894843790f5d4c818c3", "Name": "matter_g1_multiexp_38", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005315310b8412d62f5d63fd996e8c6b14aaad5a6c83eb3505a28fa6bbe469f7a7cfcf10b49382aad4d6764859ef4910e0000000000000000000000000000000012fbfd9ee8bc712354fa3b73e57fcbb07231aeac980e99d5843fdabc081a159bfc6507911212adafc162dfc21a5afb739c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c0000000000000000000000000000000014e14689a5ef5b9ee89369c5c0de07fbb7980f37294a0e7570191b73f4406ec4bd9bf4ca2521f8d90157e9c3c7d4211900000000000000000000000000000000040d06da8127e64a71532afc8846bd7eb6fd5e845ca0f1d96effe0b12a2f8afb121d7fbe89f632262ba0e382e8204701ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000c5ece364affb6af365a4c7506389694b9a10f3ad6798c326852fe85a892014b6901d097aa8910256f47ca1d4667b5a20000000000000000000000000000000003f300682da34e22416f1ca2bc3430e3b153c95773c8c76660603a0ecddc20ba570545d9307a6b0910eb406aa14d196bdfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000ad0508c3b4fcc1cc608d002b66bc703cc16182a6e83794e4f3739238c3e02fbb6387ceb445791d54321ea52f779a35d0000000000000000000000000000000009a442ba572cdd9e658080fdf1753670c27e88fa894c307eaeded6ead17799365d1cefd1fd13f0dc321c0e881a4965d3f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c30000000000000000000000000000000018825670284d3dcaa90a678ff37f23e8ba36307f3c1146a8f6c782f7b43ce16f281dd346962904684c22c1980a772ffc000000000000000000000000000000000d65166eaa6b4ed79b5ddcd7b44f06ca1bf8b960211bcb17d5a26a8595a1ae1aecee9945a674b92384ad05f2f0f64fb6c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000098da5d9289f26b61486e3ea52b0145a47847ff2b9f1d2756e363e5ea0bab27a98fd01d633a46ab48aa1d2f1d2886f9100000000000000000000000000000000191412a43858276e4d7e69542f9e6ba4fa9bc0a8784df590aeb1e0d65ffb56cce0031916af640dc3e57662f5e5203436458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013a279c27bf2234542f4ac0e4c2676b41b3cdfa1b55d5c0eca1c686589c37ac63139a7f532910fefe275a08ce2d37fe50000000000000000000000000000000002f56719390112560fda45943509729fef3eed60215190ca1f90143a4d2ae6b41aeaff7edf027f27857d56bae1900ecc804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000016489ce6e2b8298e2fe0836556875156502d36aaac621e45514ef03db87631cfcd308285fdcf8ca7ae8bf65bf53a37b3000000000000000000000000000000000b6c8fe0db4492a309148c54465ca06c59c7b71e4418d8fc1874cc338df40fc1355a523387187402b04f5d01b5e5b82b6f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000065358f885a974a1f64ffd526e5ced18ae5ebab2ed6c9719c9f879adc940292ad124fe5b6c8278c82a33d1ab2a1916130000000000000000000000000000000010d019536f727f8ae098dd9ccb6344417042855fc6722443218d83127cd2b07a6816698dc1a48776d2cbbc937f83163dfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000004248c5eb514980da698bc5146fd3743f5b1a458dbb17edd38f65c294e48bbd55e0d9afb3b39df2e82085fbc03e5655c000000000000000000000000000000001830c1d21ff8cd1ad8467ae0a8d2a34367e7c44829f7530263ef3d7d5bd9eef76b756f475448c308f4c03453f54b43cc1aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000000dbb72f9afde915110f2483c09291595c369f0b4ce2c91779da9266c9f74764da4976a221c4997cb940302ce0e59ac080000000000000000000000000000000012de4b2ca14004be2c64ada45e9a0ba7989ea0e22d0407088a092cad87b4e26b33d5d8f96fe6831e085c6fd27901af61c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa289000000000000000000000000000000001387a1edcc34afa05541e15e2355d3cdefbfe22ab7481e1f194e461521894b97b2e18c9fbab1eb5d8e508a0bdae08b5a0000000000000000000000000000000016c4ed675f20aaf2c825de5bc4c11ce1e85a0b91b08577080108ab7b52bb674f78943a5f619f557b96a72206cc1bd447e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf000000000000000000000000000000000498481301a55b2d1dc95f8115534b1baade13c2cc4d5bdce1fe8cb1734004600a2359e5dd1c61c7338275e2f4fdf455000000000000000000000000000000000a3d2ee413b7e6c0e32e51dcb7d124be92990b7e4307b9b459da1db20f85f4a35964b7987933634fb62a07f797b00b27fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf000000000000000000000000000000000a885ed8c3ab46b60a7d2e198b6e8d069ca8f7e0692f2b8ce99df2f44979b6045fc17991bfc27867be79e2055cc8aeac000000000000000000000000000000001728864f0fda8476fda4df08fb6aa9e40a01dbf19a4d22c4fa0c319d8496d405f0a5f9c79ffbdd5a4c1b617326f3d774dc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e0000000000000000000000000000000016edd94f91c43f15818752660e4737071d44edcec5d5de426141966a9880bb894f3566e98a05232b9717bf85d66a57c6000000000000000000000000000000000a789ee6ecb80e2ab9c6e7a945ae4839c620f9a7bf430ce09b57a64479d5a10a1ec0a721678b5bece737f0dce97a3a56e4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a0000000000000000000000000000000008affb2247059dd4bd1498c8e229dcba313b156e2f420fa55331e7eac93d44af55a6c02bf2101d90955b95ff6fcb411d0000000000000000000000000000000004759596f12f17d7bad24723ccd6f86c646a39beb2aad35ae5a219ef57e1ce6eb310b2098130489421709bc20b4a53d703f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c", "Expected": "000000000000000000000000000000000f841cf3d8897108b4a57a7802a3cf8a43ae31e711a6258991b6d5b3851e9e0d759fb90899e462828ff9cf996bbe9ec70000000000000000000000000000000016fa655a67f441e967d3137f6ea8f6cf636fc1a7bb662b1e22f87397e0c77f34e015e6bc124291647727102a12561dd8", "Name": "matter_g1_multiexp_39", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c98e02c9f7784d0dbcb4a49c97a9365cd069817d57cea3042cb4198180b95d141c5ba4d383de188f06faf8f845f78110000000000000000000000000000000014be6f602cd67fc2d147925cd6c90457dd253db766c4b8f737cfca02ae15b47d5798c621091c4be71fec75e0b8b1c00feb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea00000000000000000000000000000000077bb03ccd915742dcf3c2640ec61f05bbd70987d2dbe9641e0e34ebed3600703e8f9c382e77f99b70c47f54496bb6840000000000000000000000000000000015ad452396c23e820d1e8a8a9cd7557062ca9c627cc7439d43c528e0170e2760e7761c9cd872141543834c89c75537d72b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a0000000000000000000000000000000008eeb6c2c00a9f95c5b238290b06a67c1cbe0e96da246537c29c0efa36b53230c3c5d91e3fa9d129743e5a9d87e81d0e000000000000000000000000000000000ede1011370a956f240419cdb8a0c8ae869c3d583d938ec32e29c5ece68ec8be0e69296ff0c97aacba59991d65a25563babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db00000000000000000000000000000000179776b08cf2da01a94bfe7be4b89b3308330cf797906f85889b63487115b386c68c8518158342747377fbda82a6d2240000000000000000000000000000000003e51d69bfdb73a2abb469b379e2b4825423d2a2cf2cec62e2313a76d260be1b0f2892bf82e5435e88205ecc9424275d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000000af478b121104742d0cd13473d1b7f647437d980999cbe7aa8d2246148d970136f6194df1785027ce944cf9ba00aa4f500000000000000000000000000000000170e9f798184188cc21b0950e0f3a570398a97405dc87a2e077af96799960a938f363d216474422d8f4762fe5893ece61a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000005817e3812f73d3d236e45664af8a4abd2d4a44f741c3c1866588c2bdd88b11741b1c272b68e20800abf3adad7125a400000000000000000000000000000000008dc859c2323f0d2dcab76bd8454209c86685a971d531a32b00985eb822d33691c2524fe25d14ca386047a4976b9e7159b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa453000000000000000000000000000000000ee06092a2ba4c33f5c9dc6062d50e3b133c7fde5c81056f74a2d869e8f92310f07629db9cc2b755f12016cb7894aac10000000000000000000000000000000011714a54e236d1e13f9b649a0aaa80cff9e093342c71a8dc9ff1e2d4e95b0f6b4219ed847ba6620d23feded7d95944183b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f6460000000000000000000000000000000017f6061908e62edbb8fc5498eec23a51c861815bc1b437b7383dabf303e6a45d52e73f8363addac61974043afacb02ef000000000000000000000000000000000f3fc04d17d801741f3583e072110b327a3488135659fab2e8b1d2aecf4694f6d168bdd60624713a7c2c3314f8309079f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b000000000000000000000000000000000db7d958b44ac5ff3bdb4991dbcdcbeab36bc6d21d9e0c8fbb1eb66601df227a6367ccc783a92c534a30b17be462b95d000000000000000000000000000000000424eb0d9da831c658ff048d3e9ee43a900bd1ac98bee97be073ea55be1dfd07d425e0906779f0e3459fc69d316599e56440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af000000000000000000000000000000000c9cf785be01b7f4bfb0140004873d0db4c8b1387dac0fec42c6ae1a72123ea5cdd2b8c98c69b78d617b16c48ebfff2b0000000000000000000000000000000015c4856f183d26d13196739d9b9c971af111b4905b669f3e46bbc8d8c4281cad1be05e9ac28de0a98031923fcd1f5aae43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f0000000000000000000000000000000001802675ef47f9660d5969dbfce973c8bb3e6b2a2717fac9a509fb3c7ddb272db86f283992eb3167145f2e496002fb1f0000000000000000000000000000000014a5b5d966ff72e036c51686dc6a9f39a487ab8adab6fa4a906f28acc67d64576fbb3a00cefb7720f42ffcd62fc8adefa40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e00000000000000000000000000000000076ed600ed860f16ec5dbae3f09471302bf85fde7702b3376b0d670f93560e77699bed969e7001570f44dc5e37aaa830000000000000000000000000000000000c993a8b08d2eb00bcee05e1c09e8a37834fac53643643402f60fbfe2cc7d795f5c68f3d6a32c8604c37211585830426e03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000eaeaec30bd8d8dd9ad4d38ff97e08706ffbe51388a93967cf16155b10d218e5b1213c29c8054cb778a0d3ad22d32eb200000000000000000000000000000000079e5f2bf405cf2dc79984ddb3f813a07225729d4cae8ddf7536e9240fbd0480f6b66321749a6a9286cb07758482e7f865cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c10000000000000000000000000000000009a0933829c2a3f2c3e93f58551e7572ecf6eaa7857aa899a7ff0eeb15ccd601559b9ff844a177568632bc0ddd6e80a5000000000000000000000000000000000b69f23cc1556385897bb7457a706cdd8539a3ed3e7fa504ffbd95abba1e824dc77911efd1ad0a9c37e1a41a76ad38d13ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000d3c4cfdc03ef5fa066be3c26744032e5a2045746cd303b6df542a6133c671f4d25dfbd889840fd624125b63839a1aaf000000000000000000000000000000000102fd619ac946e99c765010a4ac392ab907c37b31f628d6d58c0ade093ef394a7547de36ca0630820f4b5d857dce449e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a3000000000000000000000000000000001589cebd579c2cd31226245f1dd3e428a76c7d0012f8dfac4dd3428a716d05a0a79763f0061d3b5846dc29a8a006a37c000000000000000000000000000000000bdf3425e6cbe628f9223930cb74ace4358e12e5d367a3604edb05cf0f0cbde84346ef45597bd61592500583827524144c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d", "Expected": "000000000000000000000000000000000555fd5a7818bbaa7e786f11eaf6f8620b9686b76c6293fd91690a4d181c0b470295313294589daaac429607b0020c9d0000000000000000000000000000000009c3a53113a657a5f7e30ec28056455f700cc7c3d40cbe4219dac00980675023bfb7462e634c8a131493f12725a27d5a", "Name": "matter_g1_multiexp_40", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002d49464783e5ff91aa0dbf6827315dd308e778b3da5833cfca3b6431ae784193d915a566142ef347b6ca024b6f1695e00000000000000000000000000000000029051d39ea4369a837d4cc8cec1eb8f9e7f9c3a247dcf99dc75eeae43378b4b9c4175aaa5eb3f7abdb1afc15bc2076d5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000001363b8347ef6754f61520942fa8cdd07e6dc2b72cd40ae41a23622be239ee25834482533ea7edb9cfd5a4e21e4f33f020000000000000000000000000000000004495e8d41b145ca7f5268e66c03528c8d976cd650d815257906e46c1f9a0827e0e79f5a8c2906ec96718538e1da3b1d2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000007735147af3bbef7cf0c4a7c8f1dea302a5e4edf01d42c1e484f7fb1f4b8fa23b8a7a16fbece9270d8786016836bc979000000000000000000000000000000000053406bb3d2a4cf37924643a186a56844a4e77ea4c9e9e2c707b5f947ef956369f400e448930aef7135449f8cc51ae1530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000133b92eb9f9a3c6cba655d5f26f396dac467b6444657eb0a811dc6a58ba1898f24b336f4fe9b11c1e0795891b00b6c150000000000000000000000000000000018952f3a7f8aa78a8c5e5bd96ecd5d2b2f237916d8e2982c40cb7498423f12c6ddd3cf1afee75a3e2cd773bad7ac3bf6d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec04991300000000000000000000000000000000051453a8b81b0b0a1566540b3026e40676ea48e3c5aff89ec4fe3b36c61aea27ebe01fe8a811fd3ff73eae0a67027cfc00000000000000000000000000000000090b399b1e5af056b428a4c270eb204df4999e53807d34ca750f30b292cd38030491c3d1b0e08600f40a16f707b4903242a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d600000000000000000000000000000000142bcc3458437416506631c4dda54572b5d66093ff23f152957350a3aaa462000ab000cb8e9c9b23a17149b5d012adb0000000000000000000000000000000000734c0fe1df24449ef498fbb60558010093cbc8a14ae068aba2f70bd7718e30450411a81499a895e3d84079a9dbb19557a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad0000000000000000000000000000000010a7a3380a6d8b2bbf212da72eefb57d2fc2305ce222e8d908bb572600bef7ff55b1df6a9af717e1345967cc18e779ac000000000000000000000000000000000c5a3aa84b489c879eddd3c20df6d510edb5e9ac5c1a2e42b770571ceec315d560235b27468299e2e60af3ac1283be12bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b000000000000000000000000000000000035471ee35c187e24cf0d113c0ca1ab6322528153d0687b15953c39290ec295c0dd4197b72448f2a692537064ede8fb0000000000000000000000000000000002717020e3369b288314a42fd8ab6c6ddf7007480ebc4fa094ff7c4c4b750f477917caf071d2f1897a826fe870c2b7dabef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d000000000000000000000000000000000b501cef8ea57ae253de63d81998768e115d58b353ac1ed6e90d24f8c39a31bac1a5be1b535a1dfe05e72d80d1db8b0a000000000000000000000000000000000a3b62c001c4b725f7cc861fa042c31fde4e77b3b0610df63dcbb7e89d3fd746919c2bd8ee4d623838a05d42b6932383392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000011cda0c937d8fb2b21174ff3a5b88aa5e1c9a8ce6eaf26cac9fb3ee7f3ad20e74ebbe2d1bd9f4faa3acc43b6e6d0d70b00000000000000000000000000000000195257a442c8e39ee6b72cedaefab0034f48bb988a3355ad07b3e3e314800b2ce30267dad6ef3fd9dccd7d2318dbce0a20a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000001630ea3c7f910ee8574f29d652e86fe3125c306218a894df0b4688ba582ea7d597d7e62cc2e7c78dc2db289f587f10ce000000000000000000000000000000000d2ecfe74480518ad4f5ded701afa68040246a08df1b8dcfe6fdffe77e33c6bbd37192c6c41c6ab5af506ba58d8b3fe4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c4725880000000000000000000000000000000017a910c111d7a0f7e7a3d48b1cd358e2a1213edc077034b06d1e96beedef80473ec17d1c10bc2d33d4fd2a8c052d926900000000000000000000000000000000040167897293a68c980bc34b3f79802b95186200b40b4763fee9cdce8afc681ee916042d619cd51361e6e02688b4915ac8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab000000000000000000000000000000000dbca3203ee6c7fe8d6504ad2041aad2681b889996bbe28ff1282cd20da563dcd5c9fea5fd03072134019f579e4ef7af0000000000000000000000000000000001317a861403866494eef2bf59519f2d324586e93a0037d07312dd8df4ab844525afdf4b70f9e21a6e0230bcde35db4d8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef200000000000000000000000000000000069ea1da08dce1c1239d49411861d3e8ee7e6082d9bf8ff0aad1cbebdea6dbf82fb0d6332ae436327440b71ce6535ed500000000000000000000000000000000079904ab7b16de5812ea3eae39d790aad32db02c9cbf7b8a3a8d4222d3baf710ba1cc5bcdcf4fc9e2c4567992fa911edd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d00000000000000000000000000000000173ca2615b65e574bd77c8cf55bb116462a7ab9ad4a3879f0eefe03f1a6c0d30feed076e0fb21fc60ee9f270af180cda00000000000000000000000000000000179351092d68e7e0d428811cd4503a57bab9a4072f1bd27b5e8445ec0058eb46af58c4752601b53714b816a4bd386048b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000016c9d1fc1790a15985028a38e57c87cf010c87bdeb2a288a055b4b08497abd1d616fa8b28d6da8cc23047e9f8bbe6bec00000000000000000000000000000000089601933b759bb565d849c3837570feb39d442461d764a22f993a695fe1c55283b8c7db02694aa66032512d44dc88867b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f", "Expected": "0000000000000000000000000000000018bda18912ce64106fd3d54ec2024a1d3e4a807d7bb8aaff7b515d75c9934d4729c14a4a72ca7654ca811a69f09d170b0000000000000000000000000000000011478fbc5c03470d9cfbf3decf9416e1dbea8a696636b94244c5c637e43f81eaed0210b1cbcdd291094e8581dba3548e", "Name": "matter_g1_multiexp_41", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000446af4281a01e0a20b7428d06b63b89573912955971be4a5cddca514708419640f8a7f95b50ef8714a04e1fd81bec64000000000000000000000000000000000087b94d8493239047a5cef74dc20d7708d7e3365018df80624cc5511483c3a5d9b14ac3d4aa391da60980397e4fb1e96f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d74100000000000000000000000000000000033e5eb4bae80d55f512a48b44054d0efb8af1f9870fddd99df00f31dd437025381df3f4023ca217ba924a961864223e000000000000000000000000000000000f6d7a7371eddf7283890d59bea3c61fc3bff19eb7fa333ae713fb9a73c4971354474986ef5a9a81ca8c5b38bb67f58d5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000013d4e9518d398fc0add8233fe58c198d65966844fe286fe657891245fba8f37665e2bc40e4e70886667c9e2c0a1c245300000000000000000000000000000000089562c10b287d4d66b2b694d29fbac936f700de78525e9be59a83543593b42c5c577910e7ba1b67d840d88e7a3e53fdbbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce50000000000000000000000000000000005762727639503eb63854e5fd3de33bcdd80227e16de19cd7cfaa10b7863915e490087dbb980b6dae5114df7d56716d300000000000000000000000000000000104306b38970a94b5c8839ff282883b7c88c7ef45a7ed49a02b322a16521faf2b881e2dfe22da3f4472e2bea9fc40d7e607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000056e61b51113719c1829d4ae4361f79c543961de801b1a62ebbc3cff04b0722be241236d4e1b2dcf7c309ab9735334a700000000000000000000000000000000031ddb45e491ba2d719b1f72f54640c63e281dbf6ff84eba2eaa2b781d87e243e7bf84d7151f27556156970dc8a2407f41c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000029eff96206ff45ea9bd0be2b83cdb660d6bb2d236971517b962faa54535f01097327a00154bf35dbe47841eb36417020000000000000000000000000000000013734f1218c3c34d2780920806c5ad211128352d8a41c2a1035594f470ae347e372914827775094164a5db9d0b2a1ef7c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c000000000000000000000000000000000f3aa9f069b07cc935a974ad4eeb47e8b0083397928e8102651ee54f53005625c359d82fc8b5dbe1c76f650cdccc2ee2000000000000000000000000000000000e2bf6a8c4234d118676a29f12daf244ad9aa562faa970d2d63feb074946ca70da039e2de104f1524b1a8f3897f053f4c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e959000000000000000000000000000000000795a2b6b27209b48c00cc8d37864f14c6be66d6a41038122a28186d7bbcc4b02f531aaabd000fc93c685ceeb67bc3c500000000000000000000000000000000143926b42a6654e439fd01883f1ceb524cd8b5b1f2e3eed3e905f6e948736790cc1325d1b04e30247e4971b75939a766e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd200000000000000000000000000000000074096150c9e04c082a1aea20c785b3a7396568e43707c42c512575a97db8127c8c1e0548d640dff8821d7d235f268340000000000000000000000000000000012dde2f1d15c04292bb5da4c467cd674ddb43e401799257524cf3097d0dda1f3c9f2f0637cfee914a4c66d737f9e3278651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f269000000000000000000000000000000000109196dc59d6ec06fc4c774f665612c11bc3e826ca4ba528a15c6290f733f3aa1fc441bd896021471e1e85943fc9ec2000000000000000000000000000000000aa0d17d44bf354e48275ee3e4f06291e242402469be6f4cd4a62ad3871d878c1d27a8d06974c5c1138281802368edb01a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca000000000000000000000000000000000067458ca402c19488e2515037abf9323ab8288e0e11f7cdee18b3da50cfa377435cfde1f63dcdc451ce65a05641cae370000000000000000000000000000000010ed9c895629bdafae66ea176388be4e4ce45cb13ecbe0869ce57f0f48852b6b8c47bcc4a14fc5327f1df372ad9f5d4a7db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf7000000000000000000000000000000000f393c5fc8e5f1cbc7b59742e5b6236c9d1d262d0b736c1bc188ebf58f954bf2835cc70617062a01459c139f328c912d0000000000000000000000000000000015501635aa7565045ef59067e0ae91a5ec4871485ba411425987d540bcd7b5782aa7164dd631e4c7896b3949cb115f9a14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e600000000000000000000000000000000174b0620cb49d8b1a5798c3746046c2888c8e96664dc7bda5b4e90336517448eef534469a40086703d9a835d2a94930500000000000000000000000000000000033db9968fd6322e7bbb9de572e8c92b5e3717a9496803e3f6ef8dd796dc6487909ff318ad6d4d91297ae6f2daf07bcbed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000000449ee22d2c23ec02fdf1751bb59feafef9291d6d56f7120612948875afdea56453e081c5c5086205ea83f0b8cd541ca0000000000000000000000000000000006114d6d8ef1e4c6d79b23a2b91e5577323107d90523001cf7d6d18a0ecf3b414d4fe1a3eb831a6d907fce9d22030bcc30fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000000c2082409ec14f6121de6ebdc06656a28dfc5e439a0278593dc6aa845e8091d8caaef45ea1ad05aa12e3c1533275a663000000000000000000000000000000000a2ad9980247640d44d3b37c7b7b2c1b57592ac12cfe9aabca4f88ba90c8b3221a2b9f5e4ce19ffcdbbaf99ffc584219980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000108ea3fbf78237f0e90d4addb69f25eadb0f21c89d92774b4fdcbc97632f1622ab4ab408fee95e735281ea5da5c2c8130000000000000000000000000000000012338527c7932a737daab3f8de98b9f2aab59aa1b12e84d3674a8ddbc1f86a8a9e7eb0ba854e9564407aedd489b6016c619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1", "Expected": "000000000000000000000000000000000aaf02063d6b5b395b75faae4039cf2eebb69053f1f161242b47854cf07786788930f3be2598520c178301ae0bd13ab80000000000000000000000000000000019574e1de9161a11e804d8077973c5ca70ff7925c847d870cd2bc985a8724d41331fec6c1cb341f7509a37371db9e4be", "Name": "matter_g1_multiexp_42", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000048708595ff4f08cfa2b1c101ec7b3538a2e6044157bf39a63255b5540211105f680464be5b03256f9153a90a4e62d44000000000000000000000000000000000f2fad0353cd8fbcf0ba75a403209094d88d8c8d068eb0c7077b8263fd9f7bff8d6234d75ac4da232667b5c566604706119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc000000000000000000000000000000000866fb774b231d82a4508ff9b017ab836936299954b2b404affea65f315b62da34c76019192f5c9a447dba8cc1b9075d0000000000000000000000000000000004e050fb7a17bc738a55f1ceba48920c62648a27cf438b770a66166522fb0929069fa6f2b2b742ed689f554e9023ec14f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001710b342a52b0781d1ea18a9f07d54fb18e9c90e44815cc7509aca3a5c9ca3cca6bc32ff6ff726cfa353faca4f097e9f0000000000000000000000000000000017fd38b122a7ac39533af597b462224b86370f6e6814ca1ea71d961b9c7cf94b952fd75502031cde0851773b2c6b0108e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001fd243a3c69dd5e7ef19cfbd9b7cecd475e88d7be85dd3a8f48eb46d5dca39d05aa4b43c0c700b6632ebc0b4cb3baeb0000000000000000000000000000000008ebf24e9d2de0fd82c69e0ddd1625da0367c2e9f975118dd2ba5606d77de377be10515d9eb921be5136ed25fa6b27abf7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000021f919adb62791296db3a0b81f03b87c01d94ca312f55cd94364eaa654bc47684d7b0336a3afe813ef1aefc7dd0ced2000000000000000000000000000000000b40dd6bc2fbfa2ed277d88f77aded330c54c1c46a781ccd039b270ee9b799a70855ddb1201dae29a1b124dde1e6acaab1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef00000000000000000000000000000000054f284874c53bc914040e6751ddd444604d34a38314d8057fa0f77978150fce0add250a6bd8693ede79c9f6b2e025de00000000000000000000000000000000045f6579793d166198d73ccd03da2e907efdb31b54b0b0fe3e2f1e02edd7d9cc0c08af089330d53aedb60aa7cafb0e0ca41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000189aa0df86ba479009d4bfb8608c31d3d49f52f1bf758e5c05ee9e5a673bfa15e1c6c37a978c4c431ea035cb7948297500000000000000000000000000000000120c90261fe77d6f41a42a170b28df1c9e6e0cc4bae247303f399d3be7c6ce8319a43e7d551fe554783ec5ccaeba3bb363cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de0000000000000000000000000000000005e990869491ce375477b586b63641ec71adf226c631a14ebfae3514718ce546987c17c9ef41f9005c10eb04909a74ee00000000000000000000000000000000141b8edf812a2918dc9a2242301a7e7f6433a83298be9312cb48f0d3f0c819a4368ca961a0b6f09f9e077cca6111657e6a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f0000000000000000000000000000000010f3f93de5573e42ced8278a7a12b58086c04f8b862e11f256f26731560e606ab81d61a1090857eada5f8eb3afc363c400000000000000000000000000000000111915ab2711479677489dad7695cb02626a0525ae9ca51b5271d5fb6ff438d99730369654240b05b5d47fe00847c6327d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec0000000000000000000000000000000000ca6c2f2191cf86c596b439de0e0df79b441de41c7661d4b80723f14337a379bed9b97958d225700f06f8be5401399e10000000000000000000000000000000015904391fc3cb879147c2b5192641c4ddde11ca8129c3a03b82f5f824b2ae60b3a33c925112d2de94ba3eee10761da528bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000fefa6dadbcd8edf2861c6ff4f5eb501a76507b1fdc1b8cc992226a7e5ee17ea343cff89426c409bc36c2aa3a8f5793600000000000000000000000000000000166706cd1ae090a41ea211d1333d360a1e34dde717979295a0d6a870932f31158e43ca041d1978815aadbf761275953163b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000008f02061fbfe82eacd770520b46ab49bc29bf358468adcf904854e39b30ec4e363e80f18eeec8064947bd8612c37493a00000000000000000000000000000000138888a1fd168e9c94959cf026605691b4100a828c3a75ce95f3dbeba2a21d8a44dfaaad834dbafe28c12154f41f652e70de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f531500000000000000000000000000000000002a07974c00de6936c31202e2b0c76c30ad15b6c42393d5c5d2b1e0d5eaba8b5680d3837a8029283f572d43d2944e4b10000000000000000000000000000000013fa3f905a5618b7aa3ee5ed37055f0472fa361fbe07733f9c500657338c62bd4cc3b0b89e8223894f365a58100ee35416732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000000775861019fd75c201b3a23141c8e962948ce38fb0f15cf9d08d56ce0dc574300e0a6ed90a7c50b8c71a1a9c466d16200000000000000000000000000000000066ea30b3a1bd410e3c70b1173b91d3eb9fd0be55b2d583c4be627c3aa9cab1b2a5fe13ccb37d781965b1b121079916c4a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc24000000000000000000000000000000000ec3ae37e6e5b0c623534f5c02d998bad139394daa28aced4b9f781a5ca671a02f1638cddd3bfb5124f9c5c830cdd9e20000000000000000000000000000000002688ab0be331d6f8246a54749c54fc111d2f7414ddcb1f3b42724e5bf14cb8ff3546a3b9be6115d91f62af8c3eed35efa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b890000000000000000000000000000000005d5e2230d538b05b690e878c03d793fc70c391e853b0ae3609f81a7f24aa6d5a67f3138308328783888645d1d84a15c000000000000000000000000000000000d625eed47e245ee74aeb91fbd72981c4f2afd53deff7ab478f32e2a8635431d9ab9848f7912dfa4bdf8ee7201ff418bc4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515", "Expected": "0000000000000000000000000000000007cff832bedad3caa9c91ac59292828e79811f91c51c4799e2752ac0c12e2c47a2a07ad0521097af7e4a939f3fd230b70000000000000000000000000000000015037ed0ec4981737fa94f276aa9a4325a2267b90f34844f14a92d677060a23e23e3ff0b3df77e7e67e85c23b28cd44e", "Name": "matter_g1_multiexp_43", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006984b92b5b868004f39ebf04f41084d03704732363e65c823e5ad4a0a3fb4c983ff9375249bdcc2f46650921031bc1d0000000000000000000000000000000019b9d69589cd29a9909af5a303586aed5e33650331b9866a6d959b8580ca8312ad0e96c7214ad50db7502f50ecdcdafb0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e1500000000000000000000000000000000173433f7025400852ffdfec020a44b545b365158ba8f919f434fcd995c0d84509c77d8a05405c79953b8cb667047690e0000000000000000000000000000000017d73ee336ea56efa64038b31d5abb6650c4c6f7efe67add40d09faf93fdd9fae44732bb69dbfb0dc8267c4d01d8aaae6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a2700000000000000000000000000000000184ef5b6e309fee5030e2cd8c6c3ec49b1cfb09cc9cfb349ed47e17409d9c478e8e54f285a3b3a4025464162b172d33f0000000000000000000000000000000009b78ea5d2fd2113a4bbcbbe6d0108bcf27b60ff435b5b587e91155eb0ac6ea35c27f276b7e11fe5fb59508454fd8bd24e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000d0b8aca446806ab51b4a49049cede15587aae742ce7d80c2a05d255429c945d1337b4fa7ecb8f2c3b7c0b0299a41ad8000000000000000000000000000000000bce866df7061aa4319336ba1f876254a8e0faf3faf2f9ffdafd0ebd7d7d0c854c61b476583207818f484ccf7faf90fddc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed70000000000000000000000000000000007018908a64fe5795ad178b8bb1c8540ccc5c78ddccf4e6cbae72bfb84e794d23172998d29e568b186cacfd025962a010000000000000000000000000000000004751f7d225407a8d68b4a196e32cb4c0bc6a9ed9f2093e4242b268d6c5df978b8595d8940f59be860b66310bf8a5460f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba74000000000000000000000000000000000f61d66b0539c7ce56da9308d0ccfa9245158541b2d1b14c381ba53471ae9944ef3ec9f4eaf52c95d5d0bda92d6b9a47000000000000000000000000000000000612e57aaddc6eedd9b8a08b991bebe6f5cdf7805c2cd4de5853856f11eaee94c4c2e0799254f98348cef63236cbae3980eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd5107000000000000000000000000000000000b8a8cf0fa6ea9f3154eb35994cfe2f7af4252adb8f26d718163f2bbee3cf1bfca400f4d3582fd5fd407083e0bb48ccc000000000000000000000000000000000c3251d0d9e8520b3e7b43acdef58c75348786654103fc770c7ffef8593b169bba3eaa2686791f919fc70f40a171bda8067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b10000000000000000000000000000000008138d173e3e8f5e63f6aef89cf2437690dd0c848435f6032f943ef6cbca87bd2a622f9aca825b7caefb497450dee4c200000000000000000000000000000000183379ed3c9a6a6904e169c68d627bb828a05a93e38ea3b7886db2fe6d1015319d3887136180ab7dbddaa26b1fb3335f34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a00000000000000000000000000000000083f8fe152f7edcde2c81107eacee9c58ce22b5aeb10eac15e7df1657a813c98b182433655380c9e8ac18efff2188b5900000000000000000000000000000000100b06f6129bd9063d2841f4c244adf2dfead83e23f3b1586126623ec35674ecd6422efa0e86ed0502a83549551afebd8d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e1900000000000000000000000000000000072c644635936a91dcaee40e3b4794e634c315a39a9cb5cb99ef6784b332fdcfaafdc80e228cd19d0104d5796f584c350000000000000000000000000000000002318bea9077484e9c1937dfa63774b5ecf6fc63ff06e5cb653553d5111a981c09c907069ffe11b5704ea60a9987328329ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c698693000000000000000000000000000000000aa7249112c7897c9b1f95a7d8299790a25d155dc9ef7b1ad6dd7b186bcddfacd4c77ee95e634b5f283c8caebc00b9c30000000000000000000000000000000012e31211b2bc88c568e08157da9c3e3220dcd563cebe44653ff4d62f8c306ee9136832704272317342f634e66e8e66a240bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000003a5576b3663114b410276a8c537a93f790276754913df727ec6c0a684ab3c705ec04b8bac882bb9c5223702860885520000000000000000000000000000000002221eb21003c6512428cccf8a9c775df9b72ed8810dada5c92463e6cfa3d619f22a22e314b9b8882c9e2f609b73353a1574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d700000000000000000000000000000000111829f79d4ec1a80533f76f32503cae2842981e29ddf9a376d16ecd7037d3e0dd1f8cc84d512fbb39d58564c019a559000000000000000000000000000000001808e65ee7f31a1fc15d187eebd76c63a3158469099bd6acddb0cc96354072f636651137d060efd850fb599a6965044e6dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001274417dae37cd33b2a3e086f327df292b6f997e5c93e71add346d6e5f6ded135c8d6047978c10c5c38752006b7f76910000000000000000000000000000000014f867c58d3be7b09891f087f47c1bcdf82c16f899ba960d8a0db4a5eb66efde12dbee75e77816cf9afd4877d9d08f32d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f423082000000000000000000000000000000000d98fa6b2371f65f6f0b62133d1a294a7faa9949c7df16818657a9757fbd8381222cbea98a72a951e4b2b69b216f705b0000000000000000000000000000000016331e8f0661228b1e5f4df59a09de5133d16e06e1628afaf8b2a1160961ed9738400078bd79cb5bada5f99748ba220b19d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb0000000000000000000000000000000018f129d1799d9b46dcea6d239679eb64f144adbe1a9561044355cf66b4b1158513406ef4423468b5ae446c4128dc03d8000000000000000000000000000000001669ead3f97913fe5448bda1bb0be354fff223e51bda5eba9743526e964247211e9cccf75e6f99c6abb5b8912af94f5d3f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6", "Expected": "0000000000000000000000000000000013426d2d18267fa615975295d2029d2e5730fc845556d501c8c6ff8442cf0f3c7facfc329f6703043bb2d45acc1639130000000000000000000000000000000012fea8316f8eb7cd655aaf9cff8e395592360eb6d62bd42f6e1d1e27b9b54bfb7be5b56791d5ba55a798f073f9b5634d", "Name": "matter_g1_multiexp_44", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018eef275d602fb152cee956b04313bdbc8f0079da7bd8d6841fbff1f63a9626f17ea3f7a8332023fd9793ed2eff3990f000000000000000000000000000000000c41214c40c5c65e79f561b59d7ae103cf8c60f78c2b0a16d96a2e714eb30eeb0cf748044bdca077c4be5f4ed155ec50cdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c90000000000000000000000000000000001e5db25f5964e3a5030005152fbe9c00252e37dba6febdb7441046f734d4b86d60334d91960b09bd32d258b7ca2662b000000000000000000000000000000000949bfe49b0256a01da76f5c2270cd0b6ae70fdbeb55f932895d0e72d94eb6db236a8ea40d419ec6d9354c611b8010a918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd00000000000000000000000000000000158addae39a79638dbd1a7cc280e0a24d18a4914ce4e290f8f79c147c4e817d95a01bf6b074ef8e819a124cf4e3bf088000000000000000000000000000000000bd2f13538d08742b3bc7b1cca9cb9e059b4bcff76b49a189f506b4bde56d8a58fe0bec2f8425ba5d574dcbc5efe0e93650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000f8615d47e4327d862fa64ff4b9be14df2cad729b816ac7bdcddcb32500b6523af3303fe36c0e93b638992c671958d5c0000000000000000000000000000000011aa78c5d0073fb9b34235555bb2e3f27e55a1d576ad4e3f63974cfcb2646c6ebfd6e595d46613987c0c8e86846845dc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000000fe66db078653da2fcd1490a36db9371039f3630bfa4d574cb700b19697c7194e8e44453e16ae71db6c9001e18392a76000000000000000000000000000000000cc69605c26212c6a088b9a5c2cf6024e46f035e4c64da67383f294d6186bedc18922ac891f742165e3f09fb1720d476283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000a280b29948ccda96a2a05ceb9fca703dd63c65ebe18a0002cf1c63b8f64282cf9d3d4d73ba3a13426f253d09f83ebbe00000000000000000000000000000000146f604d1e90c4a14aa6599ff5c6389e426232a2dff39334f3390006f021f83500300b7b0f1585ad591acb1e0baadcd7145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f4720000000000000000000000000000000000be58255d1f227af95dc9a818204d687064d62166c16f1de052aca69a37ae98c2a73a9a9cc6cf187128e5b86969e2810000000000000000000000000000000003f1155d7e91220bf0b80943a16a9f41e4def1d5f8ce44d95dc2f9099019a1d5e770158338ec248eeda7c5af412890cdd9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af00000000000000000000000000000000077c3ebd0169da81bf07ab1bfb8770732e4182a30504cbdc8fb1abc49f31d698c17f68de1a6d8bada62e98e09bcb22130000000000000000000000000000000000d677a33c1590cb55c9c78afa455fe2b349c465e90537a73906343aef577afbfacc8e157ea6f834ff959f3dea5941bcf262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e000000000000000000000000000000000aa0a3898520c5bc19d7f3a8e0710585dd08419b39d9bdcfe12f7baa6b4cecb50bc0d6e877ccc2518e4d0254934669ec000000000000000000000000000000001376af22bb714adbd16d8d41ab503066fbe78f799aa8c1d8958eda9e4c8c6fbe119e592f655e0c3f93455e8acd8a2bc14f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a80000000000000000000000000000000011b1300312d0ad0352ea153746f051816693008f2d0b980974bc354996ebb664e910350e28770192f59c053f3f2bf00500000000000000000000000000000000125d87c441a1dd88f489514b1d550387aaba857d5a6bef20acfdc0afdbba3e98c2e0aee0528cb78970395a9da853ffba25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d0000000000000000000000000000000006a05ac512adc0dccb74c7b4c2187763a6ba8db9e290cb0efd1325b7a463e0e14a3e7463b5cedd732527dbd131246c6a0000000000000000000000000000000001c1b41b6d5c823c05a5d6db55d7068409f5fec25986db6e2689dc6ec3e0d85749db6deb737445c5feacd69925c5dfc44ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a616300000000000000000000000000000000143d22823412da99f7b87a794662bded7b7ebad9742e4d6fffd471b1bdc748c6f1b5bb395cd0a79c7291b9e8081825ba000000000000000000000000000000000f2b98d54e293befed0a97667791ae35494084229b2a25494fbd7295a04f03173a52efb8ff9033c4615ad1185d4e9032eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a00000000000000000000000000000000172919c33fd97de83b30740356c2bb2a9c97c3616d9f80a8d8266e07a1de21ad974ea796d3cf56660fc4e0df263a27c80000000000000000000000000000000019afdfd10bb736e8a6596db59f4f9a8244e585fa81ae315a768c8d91716de32d42fb75a57da238dc597885f083049a769d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b046000000000000000000000000000000000a944d2667a10dc5892760cd3e13289785f0a5a461068d70960e6546a0543474f92d68ecfa96efd19619d976af2ee491000000000000000000000000000000000a88a16dba3fa6cb5ef21015b18a14956ec9ec29650929fbd0308fa59ac4aa389aa2e306a3a68fc04e062367a72b3f861e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f96220000000000000000000000000000000014118a990f2649838954ab911e795c453ecd0d700077a5fffd1a4f303087074d595caf1b20399578ff1e23a2cada7e5200000000000000000000000000000000145bf8164b82ca5f8f93d89ca65a894c6d15e38da2cda296a94aa1a1efddc4d2663b8f09efc3b2d78510c4dceef8558fe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000012db33b91d99f44cdc785470e67a781b4a72ae2dcfe4555efe5b7527b9a76de8e492c4dc7793ad948cb46070eb1cc5be000000000000000000000000000000000ecf06e454ea574dbb9ba6209577425a58267d01f29f8706d06018a9caac994d2dbc9c6ca9fe3ec01aed9aa2ab886c60dcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c", "Expected": "00000000000000000000000000000000003747597e0f9bc39306319018fd08bc936c4d37cc6f84ef296df5a36cebf0aa46ed35ed215ba89a247174fd72fc7c1c00000000000000000000000000000000150f973b61f26aca79a3f7d1705999308a96136b08673322b4729f16b471e38f27e66996e2921cfad0cf889878c2ce27", "Name": "matter_g1_multiexp_45", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000046e955a4631d1a490f92cd40ee0a31c096210ead2b307a7aac60e84efc04898da5d4d9767f1303ad5652a0e377f0738000000000000000000000000000000000afd054be493fb26c7826c9c1f62365ebb28ed853bd3a45d266f4c690a24e179b2eea5261adb0bc50dd184c165231d2eaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b6000000000000000000000000000000000ec3c20a24a5f9fa7c5754007407d1aaddaeccf3f7956914ed3b06dbcff7f15c6d487a3b71fa9aeb61352698a93ed14f00000000000000000000000000000000086f3cdb1e21cf60a7a57e7ea7e00b4698a837916eb1f6ac1c6cf97ef2abd48292ecfa471ba7d9b8688b6f0dcfb6af62c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c000000000000000000000000000000000769b870411b65a1a86dfdbbd7dbb65feb708f9f90ee73153e42f7141cc660c50f41835ee44f58c7ffa136b944e84dcf0000000000000000000000000000000005f0480b4a35dacd304d8feca77f8580f66396a6434af09b98d57fd4f9f781012f3900407a49f4e0aca8d3ebddd2a7bea1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000000208f1b01599c969333ddf9accadb24f1c8239f82f5beca72d0d6d823b59a3b8c450e25a2da32b5a8cf8c0f47137e04000000000000000000000000000000000054051408658f025572a45c731e81f3fb88d741a632f1e2acadc48a1f257a69481c9c11e655c226d8e0623d34fc9fc158810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea0000000000000000000000000000000001738659b582e3667cee963fbea8cf695daa6b811dd808e724ae77db2060f248accf645db52f9838802c5322d993488e000000000000000000000000000000000a36fe571929153dd774fdcbaff2b924cd3f0ab4aced47d22a2662ac6f415b89372406c4ea5a0a466d4a4c5cfb02ad7d93e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a000000000000000000000000000000000c70dbc5f707fb949a2e0cd57e0ba6a5d28a2d85affcb55bdc9fd24a3fe395bd78b7431175a629475c0932b769b55d6e000000000000000000000000000000000a49fcd19bde4473bb98384bd63e96508b539fb80e1e0cd9fc9aedaacba0c36d705ad16a47f345c083401c6640675823eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000011236c10b9622f4e3d468d91ba9c6c072be74aea66f5bd77411193bf2358a03fd47d029dc7b50343ef72fe9bc08c7ea3000000000000000000000000000000000b923cf7f612e800c2c52b51203e12a72d6f106c0d047d1317711954cb33d44678f509da27f03dcfa1d4482a9cc2eceeabe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000883a868a58809bbe3ec9df32f8b963030d71a3ae97250ee9aa8446a8b1a4428324f22fddbe77b338ca58de26b1ad73f000000000000000000000000000000000a49fcca1f052e82fef8913b64268a33ef1d2ee213ce96e60a3a1842aa304c63cce711bba8f523302d9252e3def20e3fc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000d1ed017ef4702bcd3bfbbcff36000af6a1d26ab363e68ea5629027e0b90352bf1d8e03c13a7955da6c15507cc1c9f47000000000000000000000000000000000e09830e54fe9eddd416479a1740f6f1b7693f2d153d322f27779b16bb6451d7657df85a55da75a4aee0a2e33b3a46e637d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b0000000000000000000000000000000001f902e2947de38842c207b9029743da51ad0dffb61615b22c73d88739d80c926c07f97507ca3bb830c66661b397dc1f000000000000000000000000000000000d8a1d29f87b3335287142baf613fceebe9d4765d29e46bbc9e459af5450256295538b49081d849f3253f07357451b6e4904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000001846aeb64ead3a9b6da3b6f5de234fdc98442bbdc402af2d016c9dd25de8f9ca09269a3f01a812187ab7427b2bf31603000000000000000000000000000000001775e3fa3bd35f96faaaf9c3ce1d2391f89340f8d533e41a1d637fde7a2cd7ad997e50a6e9437468a1d5940e4004bc9068911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb490000000000000000000000000000000008aff7ad8d3e83ecbf5c3fa2cc9a5328531b1dd6e30b2aa618aa087281202de8f4d356586d64082fb039db4c9ce6c3e40000000000000000000000000000000014196e8ec67e5f0093da2b1233331bf1e90a8fe1db52b2629c0d25e3c181d595c03bbab3b399c87236d2353f1ea6bfe9481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef00000000000000000000000000000000018ad28e8d8c1d9dfd8f8cf4e60214446a988285005d92e38d46ba32f619e982cf96ab10b605b1e378d7b46b54282ff300000000000000000000000000000000029807f431a2101ac341241af021ee35c47e0ffa1975c982f75c10ebf3ab9081d294578288a5c308abb074b3e3c756c672780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b0000000000000000000000000000000008cf05c3d3bbcc63ee761f7cab1494299a3e2274ebaebedcbae5b35ff33bca129d79f73ea77152f19cc67fc66ff774040000000000000000000000000000000009ab576dbf0e8cead9450eea0a506c83f12d09fd2267715a76eb46602756859146e96920174dde3a361636986a3d38e084ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c6567000000000000000000000000000000000006f62bad30339a1a912280ba5d982bdf0d3c04ad9051555eabc32eef501e80d996f183a990ebd17301ede13db85f6b000000000000000000000000000000000b0c4bb1a10f8a281b83384ee05be2d65d6dfcec36253b9101cec7f1193f8fe3d29333034de96dc62d18a97153ce1d153256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd0000000000000000000000000000000010afb2bdbea9f6eb0c75ddb0a4404116498920557a5d416c6d855978e47aa90da70f29519ab244079762fbf965edcd070000000000000000000000000000000000b8b62a1e52eb3805056576810721cfcdb5b0d94759a11862cd7b0a88e3ddadc0efaeccfb89662860e187f8af2039f8575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b", "Expected": "000000000000000000000000000000001422eeff2bf06ecd67e0b310e7201773577a899fab8ee7c5b6ef5ce1021c9371e106c49a6b607cb3d58852f0e671000e0000000000000000000000000000000017ff4ceafb7c286f2036e6bf47a15e6695beacc7b988dc35f8521459203f69975d297160dc67fb88c4ed2fd7b61ccc0f", "Name": "matter_g1_multiexp_46", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008fae47827bf8786df7e9f8cb38a8e01354ed4417a05332e45a94f93a5ec61f11d517f5554d5444001ef2991f2e7eed60000000000000000000000000000000005cd17cc813442f45e7c2fc542a6359b16db4de7749677b1575f12ed694514b3569b722ab257f7678a230ca3ccb6e0ed1129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b070000000000000000000000000000000005aaeaf87735d9e9895e8703177faf8b11bea34aaa045852c57e9b86f6283332ab633f3e6947b84784733f6f73b289580000000000000000000000000000000004957220d5264c0ff61dbeeb0d0d51278386227a9386756a042df89fff5ff9a4d3e3e52293cc94ed729d00ed3e70b1a32dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c900000000000000000000000000000000049bec519df011ae5f19c85afb3301f41f71119ea6cd9eaffa9a00f9cb901681eec5f3f694ef9b4fe768a25a55afec560000000000000000000000000000000011414953ff3fec28aabaf3d62236d6a972da12c42102911a3ee8e88e188970a11487df719a739201b31fcda4e52d7c515a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b010000000000000000000000000000000003e5345484f59b269fa25b659e9a43573d4191c3c02f5f94534bfcd63d9abd57b2f3ab92f9fc746a852b185a6ae2c778000000000000000000000000000000000b7d7648096606b0c3fb93627e484eca017b95b27a8098e5dd332bb45171793570c69fdc16caf5b16e65f68c817de3bb579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000c6afd550c55cc41cea88e670443d97c6419a295918dfde1d5490718f18ccaf8fa0cb68c42fa2cf583284cc70bfb0a11000000000000000000000000000000000f88ec67e9ba0e169ebf93fffed1fb14dd1aa3e1a2fa614a140c1a2147fcf051457cba68043efdb1b851bace84078fd64be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d1600000000000000000000000000000000143a1884ecb4121e2c1c0cf2998b690d7f01aa3deec1a2ae5542647a3721f7be47c21ca071f92d74d9c3d9027b56d9c300000000000000000000000000000000113b01f060d10d95776b35c2b294216f768a323aececb308d3de24299dc12e55fac82c3134519456660a3465abeeb5950983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa000000000000000000000000000000000019a5620f3241d03d63ccaffdfabf7e99e784399929cfc3218d6b828d7ce137c9c6cf3ae830630fcef3cfdff705490e000000000000000000000000000000000114347768e5c8109c1bd47623eb51764d4b3f63f333677bfc28b143fcc1142f4d9094b2355408cd8c412a37a4579e0706615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c00000000000000000000000000000000043c0f4b09396d4b14deb7c5027ef6cd2d426fa4f93d4ba9c3647031d557a759e3426c113fa3949cadb8b98a64bd69880000000000000000000000000000000017efb6ab8b2eaa0768bb740cc8a4e5ecbad81087cec2a307e5f53b5f431d19e3467dee84df6c6453ad4566ffa2380c9ad77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba18122727363900000000000000000000000000000000140f0efabdc88a109da948494a9fca5ff790ccd6c629a088cac62e043e00e38c4281e49173ea0e423152c5b944d80ac10000000000000000000000000000000006d3d01cd44e56a4cd62d88a22c701b42c116082e92abb629e64040f57a240d71718927aedbd8ddef910198e1bb09c6841f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000000f20ace44f4b981adbb3035e450a656ce3d8464fbe4c45b9f7035c00aef11e389cccef660dddc025786d4f9216ef60c1000000000000000000000000000000000d5fb0a9e9ab03893a9ec61675af29e88bb30f3b61e05d7c5a3d823159bf8e641ad894ebedba4bd681df789e0c3d2547c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a16000000000000000000000000000000000dd38f99872751b4571253940ca588424190bae80434a3126a7ab5ad1383c55ad769e09179d148d151506e5cf5007b3f00000000000000000000000000000000032b2b9a8b13acb6589fea9e8b8d2535285bb32ab0e519cf8c63ea3e25d58cea7f9fb27481adcb9475abadd6f1384f4f8db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c88000000000000000000000000000000000c696064b7c9653cce986e119686b2e01216faf8098d494bdf6d302c4d176b24b05bfbd70b9ea3ecc16312f899f887180000000000000000000000000000000001b5b8d333dbf1d84feaab7737d3af13d3995d3ea976d9ea1cf1d005090a809fa6c210a6363495c2b22902442fc5080b70751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc000000000000000000000000000000000082bc6c7ff7924b88b4a6cda58295d050bbe8087670bc6036b5bad53247b803306ea596ee0689d805e7b4de65a634eeb0000000000000000000000000000000010a79825c716dce1572e6e8886f1c698d730327f195871db7a9b6690e9ba1dc38e8d92b34ee32b33705edc021f42349184bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000cbd4ac75eb0928f366d3b99e05799bf3d9dbf187e557f211af5ae514101961ba750e81ede07cb5a14c49884a9b55b980000000000000000000000000000000004fdb80f44f89e6cb44b950735703653152466f30a410109a24b555c4e6907b2c1d4f54c9c0d2b7954002a74f1b65e23d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000019a41f73feae98fd65e365912f5bc6c86142380b2633feaba440a6c635ce2bcf7f871f1f033f93f9f8668360da3898090000000000000000000000000000000005bd1afda6a52adb550fd9bb59826bcf492cdaae8e9600e517d77832a8f3ae8777756421fe7640aae0bf07518ff695a66940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000ce2a96c1ac3e2cd01ee4a20258436b62dfc2efb96a7148cf887c25d635aded48d18d38da7347abeaf72d73d613fafcd000000000000000000000000000000001773ef3bc5044059bdb5100430d4936f328cf876a48bd30784c8d3767a119bdbd5f1f97d78d52afadc42ebc85f912f0f7b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e", "Expected": "0000000000000000000000000000000013fcc5da42975bad80f3447a1ba05d9c6a51383921164ea3e93e983e24055f6398fe773b0e7a50d98568d49de36e295c00000000000000000000000000000000188455bd9ca4a0d3174cc8f0794d8c35356f697e62265d9e3d8e72bb2d1450caf5bf79dc5ba78a363a23d2ad58891165", "Name": "matter_g1_multiexp_47", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000466047055d438bbdade1bbb00a7bca3ec0ce30b042e56afb9a25de1407d5937038e01e3c07595f46bd00cc8202d2200000000000000000000000000000000016bcc696716c21293b68d4f29d9cf675d447b726d579628417cc457043186d54f27c28b47d2e430041f9417ba323109dfacfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe2433513000000000000000000000000000000000e8eb8fa4c0c2c86d0e571cd4708361e606c9fe789b60e099278d35d169424721bc789a6048774d739a5ceff56adc668000000000000000000000000000000000ddb7d2e6094f1940dc0f41509bd430163b220aeed1b8c0a2b90e37f791410a35d682b75223b32febc95500c7006f6626546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000001080ce47aa1c38db9c71d1834c0b5d59676b0d938ba55a62daaf50911d23e286b3b813c7261bfc19e95f3bc8ea3b91fc000000000000000000000000000000000bebf539c3c03dd260d579aa853c28ae582b9c904ba2c56bb1239aebbfae10c05d9e33c8e1c2bf90553025d3279572fba9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc000000000000000000000000000000000ba078b44f92e90fca4981c66e89c5490b34f92e4026d826c2076a995269e4d4fcab419a508b530793c465531a631ead0000000000000000000000000000000007c19bb972c27c00b5b1a8731ed7dc9af8270187cd26b1b9d65cbc96688fe2f0ae86ffe753a50b4500a46c01a75a93032c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000009b70de2dbfe9af8ae771ad5bf0ff962c9f906a3637f992b08946c864b3d1dc996a2ff918ecb3c9648ef9188b15b624c00000000000000000000000000000000186a9f4c06ce9d5a969b959e4b17d4428393d02d0e7259fcbfab8898481bc97582ccd0e1d87d1735e28dde10a99b683e9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000005b7dcbe86bb6e6b328325141c1da77f8af531bf1463bf3c8c94812784314fb13e457fa461c1c51aef0721c5d6ceb5e9000000000000000000000000000000000d9d1ac39a5ecd61670c1b0d061d93a198eca1d294d2e64c3f9e0a872e7c93212ce7835ae0a7fc2a42ab5c02192d70715e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee2000000000000000000000000000000000741769993f2dcf5869b8153bbbff2e6e5d429fd2d862bdd590fc50a8f186bfb105f5d57f736b07d919bf0dff0cf4094000000000000000000000000000000001917c91f954f68c6406d6dc716dacf729a8c4a0de73e04cf0ce554eac40d750fd25b289127023af299c6f63372c01b7d4a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a2000000000000000000000000000000000bb29ce10d6e571e62611364143e08a60eee5ccb13dcb77f17fde5829ae5fc025b309c98f892aec1fdedb7d1920e658c000000000000000000000000000000000ab6fe2dd5eb1b90f15a3632749c351ec871038f0550dc54cf1bf2575f80ecb8a3c0d3c1a333bdd803e22fb6bd3e64bc5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000004fe17772d4205d7b1d0cce0db3404119707893e20f6b27138918d2cb0e4de49cd5df1258103c1fac903c1a443cb62530000000000000000000000000000000014d8246911dc40ecea823f02c0e17e690a5f66848223218dd1735cadca1a0ae89d7afbdc727158257d2cb248323c55316e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000784a1b282846404f71227064ff1a97766781900136d4b7ac73bab19cf8e03b449ddd35360fdb6dcdac80e335ac5cd1600000000000000000000000000000000074fc137d93decad1cbd4b753fe9ef3b8b3445c12e358450ff494a1fbd6e192ad7a4812358d85f6e3cefedea3aadaac6428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f000000000000000000000000000000001661e6d386aa6516f08decbbac9c1c3411ae9cae62b05037dd626a2e2273eece64615c54a4d73e09814d497067f9e6e30000000000000000000000000000000007543030f8995237f65cec9b69b0356a29133d8be27b5f79aea580955042242c2bc1c6a01539b6b55ec9af96db60b394ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d9700000000000000000000000000000000148597902b3ffe4ba8a5f9012e699a3cf189f58275557d98d132b72d3c34e5faa0953ec8cb10b0228a23803b70836e200000000000000000000000000000000008741bbe372a1e5a697e7059a9e80de8a012b0cc7b12c14bea098c16cfea154204d4e27753f1a8fae0e618223da14fdd88a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000000cfbbd4632e8998ba59721686310ec115b98ef470c3c4bbe427495d6d95d06ec6180e64b509c4c06e32862e17939a2cf00000000000000000000000000000000060042078794f4539a9b3e3127632c3c8b46322a669605d1774e995c5d82287d3d9be51690b4b5df6de8d55b20941dc630eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000007e67da2f320e1ef0d3afbd50634aff753a2e2104ddc03244a0c79eeb117ed1beb7316f7c5e116bbde47c53d47e725b3000000000000000000000000000000000b5399ef864331db729724870b431d8dcd8d3279cd00a59de2fdc15bbdff2035794025edafa21fce97836e93b41aae067e8dcbf708682225fe3f71b7a687da23de5ed188e40585be05533580121325770000000000000000000000000000000014bd7f0effe81cb626f92422ae7900bafa7f4c2d51d4ee6926eff68b60c7f41e667a57bb0506f7c36d3549cf154f6cf300000000000000000000000000000000050aecd688a63075feacfd29d1ab6430176dbc5ba6d406636a6650427a9e0b0d51df51d8dca27665b0b6c60e08d5b087532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000009252c5f7f7f3b36c5dd32991641c9f8244579960fd2d07a8641b82c5cb1768a36f4e5ad623319ef3f7d0c670fee58430000000000000000000000000000000018e432d33e506ce42bf3d873e36ed6ede0c9de44203cdd453cf91c42fc2ddaadaadd2e3870c5f5c171cfe76862ce44dc3ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5", "Expected": "0000000000000000000000000000000008b38b298fe2dfaed042b35ce73c76ece7537fe5181ce647de756349a8dc48d3296e243fc7613abb10e254e2b0197d7a0000000000000000000000000000000018d59a69b976b1bacdffbea68d523da3fd0d2910db0a509760bce56bcba36a55fbfe11cdc14cad50e6951ffdabf97a64", "Name": "matter_g1_multiexp_48", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005d929298c9361736ef5f7c83b6a851c344d72b7bb92a8201d629bf9bc1e66e4db6dc9df64ffb41a11eeeba10be52ec40000000000000000000000000000000007962e1b1b823b770b44eab51b3b84fd7e0e57a2a3f7eb1ad9c3ab02677376cee08b0a2977552a0f9399584b576f17f148da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c0000000000000000000000000000000013283d9b3cb5ca4c3a39517adf466d2b7fc90f4895a24effca7ebaee4df8735c69993c7cf2483c3480cd2df4be04366f000000000000000000000000000000000fc94dee82225161feb78f2a7c951c41f43ff3c1109a824b56c01854688feb86e240c9fa48534809354e74ca8360cda4def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000000fb727cd02c5f69af676f9cfa68cc4363cbfe5343e304ff5180ed1f57e6928fb808539276feeb1e492ae2455f65de0b00000000000000000000000000000000082d09bb2e1f1585933e1b9076711803e71c2236ff78e83f5dae6ad492c1d723120ef64eb25c8e91486d102c2297c9e5b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000000882f34897651c59970934848ba13e815710b4952dc0ee1abd0e04ed82ab399ccfb16ec966d010eab51e5fe514af91ae0000000000000000000000000000000017a32754dbdae7a2541eddba29cb8ca85a0c6d189f9bbbfa24d477e9f1ec2ab8f7dd2a5aa7a596d3a2af916ecfbdb2c2a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000db9ec112ddb4a9c6e371440d0c79bf043c5a3c6c6bd613dc031ce9b81b49a32b006a165ef29a8e05f019b76b3cf520c0000000000000000000000000000000002485dbc3c3e2aeafcf18dfecd842ec48b2e79d3bf7936917df759a9ca2e25fc3f137eb88a701f5fee1ccbb06d5cb08c0b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000100d1fb78f53423c8cd60de5d39a004ee1c99b2fdf6847a62c73c33bb3d317ec06afd6424359481f8ff2d0730cfc9095000000000000000000000000000000000211cda7659f1e848c931ba1f65ca9c6021067ca01cdc8e87f5c742006f6dae39645553b69a4ae00ab6eca16afd0bda7571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf00000000000000000000000000000000004b07c2cb575e2499e333140e48446fdaa00368a74b87e607e285781b42eec39d1578d2e34701ed28488f160e9e50680000000000000000000000000000000001c2d66d28031aa91f6aacfdd80d222b4a0bc699a9b58b7f5d68bb9ed0a297ffbec3a6ba968f225732879f2f9907ca3954c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b000000000000000000000000000000000bfa7f8b7783780a2b0f5b9f1b10da77cb5904618b8c8a1d062fc94aedb0fce090d8c4e65515c0d05a471f2261d0063c000000000000000000000000000000000f45747e4b0bffddaa13c7e03b6930ec474735b6a0e779d3722330828ca26a07bb731a5d4884ed3eecc710356a00a897000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b000000000000000000000000000000001245cf167d097de0753d29ce6018b7777b1befe43b5709e8217b9f380d958e3e9298347673dce432e57338b313e84950000000000000000000000000000000000d697bf8ec405e252588e3ef6d979bfa60ba174da03266c3a2efdac176c1ec1341d737b16d53bda6ddf8be6e1f433ff6909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000000152d7f1e704619bbac7e594be6e105120b76d9bbc711ea40beb1063c2996fad70bc8f77a915411f3601e75af2f2059b000000000000000000000000000000001622a6467c13c534ff1fabaee8b29452d689e7f9e118e050cb91328b8078ef97fc82321b80d28d0c02f2b0a7b66f04a36d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000001c118fddc8df59e2d4ef9865d69cc044fcf870f296b009a2a471b1f74692f99e392b455b8b03d079b1f39b09e5fb720000000000000000000000000000000000032c05dc9eef5b55857956919f7a51b5f5225a45ca12d80208231304e66c77b24707a934cb9814108b44427e658d143dcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb280000000000000000000000000000000012da4a2c89951f85757c59a2630bde25c30af955448c972d256f1a6a259793c7b2bdc3f8734f4e312897cb6a3550800d00000000000000000000000000000000199939ffbde7b14b5f23eef23d4a660bf3f561aed38205e68d091ddef9679df9230a59e8cb03212df2e99788fa2595bc23d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000017a61df7581a341f21da2d1768fb41bb89847c88b2a0d7b61aa3275e376a46672dcb919eebf20b242ce83493c83335680000000000000000000000000000000013edc932b7755115f530d1d044c4afe71807a6b9810f555432910b54b0fef441b4618652fc4bc2ac5b789a75d2d276aa2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000000c73683f328a0aa252c10bc3fae9e786ccf183f1b606a4596094fbe10630d4418a527509c93d23e62dba263d86f88951000000000000000000000000000000000260c9dd70a1ddb422491a20293c18e4749427cbe9841aaa3370533b6e5d6fcf882f8bd68b7161434bcd5060716fdb97e3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e000000000000000000000000000000000bad0e395f46f714ac9d40865d588c06adb54b12439bb408a9d546b0a8ba5b3098c242cf5c17d1e40dcf7b384e81b444000000000000000000000000000000000e595304cd73c8c2a0bd1dff70e89edfab22be69bafa16877ecf669ab1e1160c9719952bb6103f31f2ff028cae0f0ea45d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000011f0037c9bc2bf953a3eb7d8a0a3c8d991e6eaa5f13dc1978a31f0eddb550432c70aad096cc0b904ee540e5d2d1ee4730000000000000000000000000000000004f8616cc7476fd0b95f7bbb7fbcda389aab60a88ffba3c819868f7ed6cf08e7c0c7da0958bcd957e0429b9a7fe120bafa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc", "Expected": "000000000000000000000000000000000e87aa419d686c55b1ed8ebf6e48d8f218d7198edcbc7db0dc3bb9581bb8dbf891dc383f27717536dc5fb7265ce1ffd8000000000000000000000000000000000a00646bc197307a7416aa9e35db9ce7eb81d762a065cf8d2e07f6470e424d7d59021be049b36eba2e44750a902f3124", "Name": "matter_g1_multiexp_49", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005d5e69a8876b82b1de0b2d2a0d808c739b361d1cadf3ebc9c6096afbc19169f237774be6882caeffe47e86e3b8a33710000000000000000000000000000000017bf0fa8c247af0078d486e1961577d7977d0b4258ada3e158822d995188ee374d900c4d8b1ef4887fb03d8f6a4bf1776e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000e6ee0b0c7bb7c3f62284efda6bdbaf38bb5a72b4435b76928c5640fedbf9d4144358a20629403359fef5bcb99a795eb000000000000000000000000000000000e72324fb2decb0b0c7fa18061a41bddd6e2c55f901554de9be8ac7b2263631fee8bc77773318f6b13b2db7eb1ad0f3cd51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000102df0d54108666e7aa611fec5c09b72d269c72e6fcee7787ece5f33153a3999ba5f22adfafa461aeda64e113b795dbc000000000000000000000000000000000b77ab3de0a2d91b8c24a47a27fbc5b2281cea40d87872010b94e895d9589880385f82ff53fad55af4f4e462df1c9ef6fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000018e8fa3c5bd83b51b1af197f0dee78e5c912c742df0cae1b59ac44fb2b903ad5ee1fe9750a034d18141f09a2b8298f850000000000000000000000000000000001526a80337eb938420cf2e825e5bcf3152e90e448ae3b40ee61929117d35f694eb5ce9133b2cc664c520fa9da8ed65a7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca0581299490000000000000000000000000000000007f11b03e06ca74a35cf702f19fe29facac855d7f5adac59bbb8c058b1eff7d4748c886eb08600e0484aa976269e5d0c0000000000000000000000000000000010a5b0f723371690f6ccc5fb346874e58071167947d45e54f9d5edd035f2d68b4ef9e301f26ef671634121ae6145e44e41b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d65000000000000000000000000000000000db2e2ef96ea61075e063629eb031235543e8f39f012fd006e143eb137524976c5a81eb26996a4ec3619a7fda051df6d0000000000000000000000000000000015d39e93da2b392dec64c58e73740376552e69caf87ce9162801466e75dd1e25b7d5762099112b21411e8d8bc18806fe5615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc000000000000000000000000000000000e048ef3ee3bf3c41cc10b89b7d0f8b3f27c89fa0ab25542653155dfb7f8a7e8488a737bf2f6dab558910c9ae98aea33000000000000000000000000000000001357eb0945e2c4933b358970184a21b3369dd7a43a88841e94c3a388681f338770fdc3a32862c3a52eb251721b2979e9bcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000c6321bbc74b6b3a9f0c9470461c80b1713a5092871dc54dd022d3ade73845852315b3e85b53b74ce2b31d1780939d13000000000000000000000000000000000cdef7351c2923faedb211e79a44e0e02ebceb8103cec2ed7541a54bfafe3967791edbeb6d4b0da1ee37b9a5d77ac8f194c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c4000000000000000000000000000000000b925015af89d42f155eb1f5104db1128faa23101fb9bc1a9757266a2717d50e908c64c502a8d19bb1e8c01dad554e41000000000000000000000000000000000fc8c5cbacca685c24188e8f936637c7c8010f6126e9b9b49e7d38191af1246c2a3cf7ca45bce6f1e11c404919da61c3dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f20000000000000000000000000000000013fa1374d37396bc60386d07a441a7d21fb808e3b2ea0c39ca78a6dd70c473a8feb972e2981e50cab6288dd80c40c06a000000000000000000000000000000000f35c2a2897b35cd7417aac29ade18f86d56ba24848aed78a31513d5115bd964ac6711c5f71736490195bd97d2d5b507d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c1000000000000000000000000000000000799d71ab5145a8a4726cc5567d99b344971eb8bd6248e41aae02bacc358f967475f64169e1828a66905e4373cf5c9670000000000000000000000000000000017c680c55af98789584e073c3caf32373f58bea6ef7f839f1d5c39e512058360efe80a884ef5822bd5fee34869d028d5aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000000b4be422e3d3e96f6a6821c55bd2a37ba57de1bb59c8f4855b1f4b6906259de6be1c1be40523d5370ccc426b89478a350000000000000000000000000000000019212f598150b576c17c32a8f374db52c19431d7a60b99379f570189b3fa15edc75b807adabbed712268087cd9b89a8a106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a0000000000000000000000000000000009df46395e64ce38bc79acee751484ce1bac53c5e5233d3545df2ec776440e3f5b04239d6de10bdb086aa3c462fc6e820000000000000000000000000000000009a5c816b2abdcca7a916b1eb015b3d1c01f766e01264b5139e5a34a82a874c1efa8ef097d23b9e9441916a2f5bb17b4c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000023537e0238470f4d513d56d4ef8e244e3d853b3b10a893928547675c6b2d409ef6bbfaa299a726eb472067c48f056c000000000000000000000000000000000b48f21e01e72bb6ec384a1e8ab35db6ca032e4476f37a3282214efe483b672c34989e6d5c99f69473eb19e472d984bea5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed490000000000000000000000000000000013f1ad33a2016874de5265565049722929528a1c66b84c1876f4e4396f22fb2583d025c481d4d9aa2877e0062e842d7c0000000000000000000000000000000008a11522b3e6982a4b46ab6f1f6b07d33443780c914d4bcd50ef7ebcbec6ad944ab88b82640971e890a363dd92c71531083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a460000000000000000000000000000000019dfc9ca394e105c6ad51b130aab8a043ee58f26a0d8efa5beee59eb1543c2c3d33abb5cf2b23b0882a409d32f845b1400000000000000000000000000000000143e219edb6fad7dbd64e6aa82fafd05ed92bb46e526468cc3bc0d60c89319d3fa2032b5a617691ca2f136c9f7904225d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d", "Expected": "00000000000000000000000000000000027cfeeac9c1606a0942a95068faed1405a5cc9b5e631c25a0010ea14cae5b7a51b406fd0b8a9d9ea7be9a8c0f76c86c00000000000000000000000000000000106c07dd0d4e6f44fb1c3451942bf10060d16980f50b63005936e35b5add71e1352c48d1048e5d77cda79a09f86ff213", "Name": "matter_g1_multiexp_50", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000065d5c6ad252823540ff4a4639cd42443a3cccd808d40d8bd71379ef939b47c3027ba5593167b4dae93b62b2bd498f910000000000000000000000000000000012623162c0f025b16dfc1c7e5fa02f8af7b7fb0f2d42d6fa0fb01af45621f00faa4ed6da6f33c609448bc027cd6a4fd367c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000008b3c274f83f49cada0a1bbf0f56f6fe0f8a0873cf13efa42ff65dd6fda913102c2034a31a1a92cd154210d27b0120450000000000000000000000000000000001521dda1b2c9b42d7dc9822c64bec62e71c629d61e796165d9a18f8ab44056914fe5c8809f21663bfc70e310ddf5d952d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba9400000000000000000000000000000000109f72caac5abd41a228bd82b6649fab639e4d22cb3a9a060ff7577de61f33d32217a73014f5cc2c2a76582a6b751ae200000000000000000000000000000000059d0e9e64b10cefe03daa146c00c5040381ce6ac63886b5fcf19a0555a22a395a0cbe8b49c510c9bb7a308813fb482958c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e70000000000000000000000000000000001c5e132707520c525045a08626e014a84d8da23dc27b6320d5915e328c3bb0df3618cbd7ace26834920d4a8757368050000000000000000000000000000000008f5127405631bed295596639ec6091e97f16ce5a3062831102be951aec98c9ad34721489f65e731026029ae3eb13aaa50a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c3200000000000000000000000000000000146124bfbb9a3d253670be419f80998382895ad6237138044c55764f0d6fc07da5b70cbe17af3ad0c4b0dbe33f869e490000000000000000000000000000000011cadf640e78298347115e6110d3ed63dcbd251c48d3e21cfba4bd6859b0310041e67d212b54e63be6d68d2e7fccd83b3b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000012ab7e51b87512007e1baf2f3c3473cebb553bc2ea3d3146358688ea3167817a449ab9a7e0b090e00f47846da7f46340000000000000000000000000000000000702c1e0df68bee2666abb90bd593a17a6f9dad02a7d66102add9f3a525a1b4f1fefa3abe262852fd5ca357d2e1f02fd1d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e39210000000000000000000000000000000014635c8b9cacbe976733bcb1245eea410008082f240cc8d8246200abc0eeb6b7444f38da3ad93b1e029b06cbb12d42f7000000000000000000000000000000000d9aa00397e1799a82d73040122515b98be82052b784a4b385417f6e260e555c7c0c48a32ca1fb28224f75f887fa4bf86fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000009a0efefb9daaaba4b2beabf6c381c27df7c32d4021a4d722118886405414837cde5c55933de23ff6769a0a42933bdd700000000000000000000000000000000101c9941d98dc8a146a75f2fa48a8650b25ae8f6d943323b1c10360cfdcbebe220494660f4d6f7921fea006942e122ac06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e118862000000000000000000000000000000000e5b403702a229f36c9b83bab9335cbb4e39fe8f5e9a5aa4bace70361dd05c87ae356a40720c4a8214765d028cd161ec0000000000000000000000000000000006e447c61bce31b4843530e504fa1324657eba731a272ddae680c202a7d017ffdf0ad0656dc0984a1fa297f5e32c2740fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab00000000000000000000000000000000135fb22eca115779ad1295f8c7f149a6eb4fe046df664ddaee976a15e11a7a59db5e2c44b4a82c8ca1d17c0043f41ee0000000000000000000000000000000000fd9c1dceb20e85ef80bc9ee44e483cd0e2714882734a561ebbd0982d6d08e9c41484ee99790c20e83d051dad0a1b1e04a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf00000000000000000000000000000000040f65cac81c01f04db3e331659d6bbaac8fa01581b1bbfa62891c1bc95a67182d254650019dfa3171e16ce37deef29a000000000000000000000000000000000afd5e22abd5d5cf78764262a91aadcb8b807b2aafecb2aa3d3ba5a187304208e212e5df46a4dc48d6150a733075bbaacd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf00000000000000000000000000000000051046cbe6862c5e37cd2f3c14dfc2825d5c32de69b40f29140fd31405615edf6c116d384bdf1552a33fb00c6c65cd97000000000000000000000000000000000a61a19fdfc994105f03aa3e1b907f5177409664b2e50243cf7e0e6e7e74c7bfce582929e5670a351b3d7b4034f101ffaba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa0000000000000000000000000000000013b76183fa2e01d10a3ecea5be65ffbcb04724ed30e4655e26a7ac94d5861f0f308b7d4577789d2f4892eb89202d84100000000000000000000000000000000012c3fbed77d9c37c47c838899aaea0fb6585eec54801c3ff2b486086e33040aca6baf6192c33af59f7db1d489ddf7d086ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c00000000000000000000000000000000195c1bf8dc0114a472cb4daa31be44f22a162d22f2968b7909374fbc4d0883614d2911475cc3ba242844ef1c046885e70000000000000000000000000000000000d03e5bc3acdd01d174e1d2308e3f1ff3f103db8e2804210da44c47229bd983ac127295558dc5560c0fb2ea34def196f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000001261cc24d5e69fe8a7747fce45086499ad54f7c138fe76fa665517c58e475683c5a219df303810745dc554fa3c096f300000000000000000000000000000000122fc4c068c079827635d29e944366516c1d7cdb1ff62968d847f4882da8a4919b59e57690f6e0f6aaf083af0a04b2ca32b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a00000000000000000000000000000000050b5493fdadda15e15b2ad6104274da831753b1cd247f1dacffb6f896b9db7190bfae2ca202907d36b979b668540ea400000000000000000000000000000000141245d4556c7f1032d0ccd606e3a2d3338ad753fd7d0a3c1b8ab38e94d8618e85c22a269428537abe003f8de89f2c1171a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6", "Expected": "0000000000000000000000000000000008bfa9c347d937e2ff2d77ce7127b1af4e7adad5f1aa6d71c6226b64c62c955fb0dd0e36a5e87472d6979b656c8a740e00000000000000000000000000000000032de435000391e441ecb7e777741fc72f4676f64cfaca6fadf030e12ea6374a7fa988c443a38595def0b434b185c75a", "Name": "matter_g1_multiexp_51", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d0296528c7b2516ea73cf14c5625a4296c311fc2e09722f3b381279da52ba9482e43d3fdc1694b96c3f62b7d98d6951000000000000000000000000000000000da2aaba37d0955c5fcf31152926f2fb345deba744241bf66511da5f4ad9fab8a1cffa270c4e838c39b34bc28fbc08b02e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b200000000000000000000000000000000175c976baf0205ee7326c84c49cbd2d7c3db91d1ec92d87cca896ea09a7cfe4ef8ca45873f86e28afc4b525356a68cba000000000000000000000000000000000c442d3edb8b614407e0d138417f8a6c028b29dc1beb5825c928dff3a08820c5a8ed5de643068bf4d239bbcc2dcd0b7612a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea7778355947000000000000000000000000000000000f45bf893109177d3c336915c5e28c338ea28468cbe215ee6fc6f6e3c9aa9e0b7120586e42c5c087b55fb5789a4a9eb2000000000000000000000000000000000b6ad0cffaf555f081ec7a6fb354d6b20950fb6fee059f2f571430f86a7cb9996b5f655bc7cddd14f3f8ed37c7fd278889a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000001944f2fa08357307df2271f4bb57cd07a998df56425f7b8563902aaa0330070ce260b6d86fc38a5c6a284788d9cc0ed700000000000000000000000000000000165d8134931f7a4cbeb5114a10e44172aa6a0c250989dbd88282f92fc238a8e1e21221b04b239cfc597e2b74700c626d27dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c7a02cbcc758fa7b1ea5fd30b3b88cdda7c8661b3712ba5bf924b441e056fb9bea804bbfa1850c21cad891ee253ff7100000000000000000000000000000000012202a151fcb86875b4dd2dbeafe5ca484b63408ba01440007164fa2a2b7ebbe9d7f738f382a010508408d26a57c566ca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f840000000000000000000000000000000017d02a3ec6d45e9b49ddc8d1bdee168f71c32ba26d4de8c1bdb328cb4c46286328387aac8785eb5a7c71d0ed59810f4e000000000000000000000000000000000d23ee9c9fc914404ff46d0f6ee86984862e97a777ab516c2b84f5b5a7c1807d64e93fe57db53c7b95257fe46a7a15495aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000040ca6ea6cae1be17996106cacbc5d9f1962203fd25917dec2c053816f3200b9853b218a07db690d8261ae3cc85679bc00000000000000000000000000000000097e8f4b5a24b010382888ebd7ab7cb71f471bca00c1499486cfbf1bc5ba6af169ac27e1ed8cf31b5d9600361ad13663fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000016f95e3e24941c2745c009437c1b2f5ebf690c9c76e269f877bbf73ddc6b15c6132d424c26a3c7bdd9c5302dcbab171f000000000000000000000000000000000cfca2fd001c0da52f231a60288b22a134c7e16aac8745129c351dd96fa37b72a9ef3d93d5e8e45cb5fab9e73ff188e128d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b6224000000000000000000000000000000000e2bdbc906c10b04c5fc1e867af43bea7ca43cdbc43cc3574a47b2b0670716a92fd863d4f423f3392ec8849e74850eb9000000000000000000000000000000000ae76847a2524be3a04bf85e096a1ca4cd3674459698fe326db2d71799c8906022e15bcadfbc9ddcd43dbee3443842a81208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a2900000000000000000000000000000000035d66b8b8b64bb0d3d1ba6bc1bd34c326ce6abac3a97188f82be38d1756f14a63bfedd531d5e19813b668012f77763300000000000000000000000000000000060851234e4cfa8c168db199bea8cbc337e685b565a6faf67e07c463632a6a163a2d22acf9fc6bc6a1f7ead5d288fcccd3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000010e398f6c9ded2fef3cd95cbef681c5335a1e9d08c05dc05b6391f65941cb3a79df9e1cc4ebd3fce82d36cc628b7f65c0000000000000000000000000000000016dede30728c57650952e9425b6da1ec8ee5702e783c69936eaf6857f199bd9ffae569db3cbd61483d48188633fef7ed7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000005eb3a914a78b4bb3041a32397bdba3edf6943ed474ac8efbf9c84a6cdae5d65a8f55ce4ad141b846f1bcb5df1206417000000000000000000000000000000000c20828a5d8abc2c8f72809348e770649bdf4bc0991f45979501f31d9f31e028731a8ccf07f0cc51bf8b59632897c540c5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f1300000000000000000000000000000000122cf863d9ddfdc627a0993dc7ca5810e84ab254ff8147a220d436043c0a695b0cceaa374842c335c14b6ebb273472d800000000000000000000000000000000150fc0b14e30ee797e3b9202533c681ca9e6b1b43347cfa11da59ceab439c9e5cbc038a50917cd9167a0fd591d8175e484ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000002ec5aa74588f6a7fd8076b9a846ff3542543dc7a3c798c423326eb06ef92edb8c35583785cfff21f903f08f692d6293000000000000000000000000000000000df140c1539cd3d94b5f9d0aafc38294d1738c5b3c1880d8864e83909b152de0a469742cd31e5e8f5838ad793ea32649a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b5000000000000000000000000000000000a440f227be209dd1bb816a4dd8c1abbdbd03d97c243ac6e48c4efcabef4d7a4b5bf65ea7bea6f4a1da985bbb9fac626000000000000000000000000000000001431a99e1243e57054d2b43217286b35bbf37afff72b163ad40dd4ca92439f4b513284551b0fb137f968f9f59a540cac650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000b99ae325f1fcf4f3c83f251183871d1b6048a43d15da80650e0b5c1b671031cc9af63a478b5939210356c4c2dcc7aa1000000000000000000000000000000001382d6f0550aad61dccb47a66d004ab3801445d55dd320a6ccf03577b1c1c915022a955e7f3fccbbdd20e4175bd0ae38282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57", "Expected": "000000000000000000000000000000000c62c70aac1893222d967bde4fdffc565cc65fe29387825224b8571367ae8fa857b63c32033faa564f6e12d409b5cc060000000000000000000000000000000015cb57fcbc876f5aeca01d90d55ea777aa1421547e8aff0de69fe5527c9a575f9cecd1235a37648c7509d9bebb4e6800", "Name": "matter_g1_multiexp_52", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000123fa54665de1ad1eb74d400f93b70f8502bd9386a164ef9ac7549b3693525e3fd077b2b2d8b15ab0c6cd5da30f8317800000000000000000000000000000000185921a0fb38ec1eb6804002b3bcbd4d4bc759885e9c1fafe275d51840434382df783518ce768ae40e736ad2ca8fc8803d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb9000000000000000000000000000000000eb30c8da4c7eb16d797f24b5d8e210dfaa68684939cad598518298c84214ad769f6a2634fc290c2c267c8f3a2872f020000000000000000000000000000000006452f211931b8d7ccd8777b2407e5cb073097ae9b309f1e95633f39d1a5a7f5843a6e87473b4b9c1bbfc17971108e3de71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000162c0325ae75a81c92a8885f14e2f7b9b8bfb249fb9a352d0007cd8bdfce2d8024f1e4674614cd0afbded99472d547000000000000000000000000000000000010d8497a5f31cda80af22bfa6695b4e2c8fb5557ee74581a33fbd0cf8cb2e0b4ca3ecc42487cf957ea81a5388d9871fcbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a0000000000000000000000000000000013270ce7a1b4abe3026d245df9b93061a435ed00d0464d8de14675247c7f2f1cbb6e21c8282e71d2fa28eca1e3f5863c000000000000000000000000000000000b87656d14cfe98c2d3f34b03de0b9f08207b00aaf6c5a4a6b9b4989744581772a2d6d1923c3d07b784853f7b2d789b9ef0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab700000000000000000000000000000000067296630285ec7da7cfdeedd387d52d905ec39e183b87479c8f0fba967e840f8394cb518dba4f4b7d4e2cdc00ca62c3000000000000000000000000000000000ed41fe0f04e0c63f3fd7ec7560d24974fd06a1566e8f129f580251227cb9b7e10ed6e60c2e7449721d5332709f465973ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc00000000000000000000000000000000006f7bbdc3c8fe2f7da9533a3f8a3c48c630d6cf567c75dcf89e13852f7a8691e2625ca24517ad3b59ed3513f7d3b4fb20000000000000000000000000000000000a2e63715ec49b06a78e014b98effbb03f99ce61b464c66108cf18ea49def3e1f035a8b88f37b453b31357d2a2a48f4e5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a9e51eaf24d2d0fcb7f1dc7ad985ecd4da3ecd19fb75591467edb0f7fc7bcef67c1c272f39c31ef36bbc73d7ea6034d000000000000000000000000000000000332dedca239f4d1272db77dc388e07005d90f44311aa889b42e931d08c2669c3f4aeecd9052d3f2585b2a4e41c8abbf97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb5926000000000000000000000000000000000c6b189ddc86e2d6722ebabc445190cf94bb4c54135aae2601c957e062d351d0c9fff19cbeb45cfc5dd05eb3543a660000000000000000000000000000000000133794839bae14fa041004f173506fff511526313da5a8f4e32c895751a22ecf01cfba564006037326187b899aed596ac54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff67352000000000000000000000000000000000a15343698b916965009f1894c8b74a790d59bc39b7f0de01095275ec002c97c66e7a6a970b4b9091cdc54abdff1cdb800000000000000000000000000000000045f084e0a7c0014e58c9988e72e1861bdb4f962ff9869d444d5ba4094178d52f9c2aa511feb6e8717098cc1f09d49eb47ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001656928ad3ee67675951e2d2ddd6a7d9c629a3148face6d1269f79c3d0699f95350e83a6ec20aa3be78a2794c3f250160000000000000000000000000000000001b8c9e4c818774dbd2416193e795a429a22881abc94ebd9a8b42bc4d7069a9778e4bdf7270180784d914bc6be99b41c14ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b0000000000000000000000000000000013d779138ab03fafee1e4bfa2a290c4f20d2b57854a5133cf5ad7817bd32bbf2945a02b4fd5c8489e704e60ee937f962000000000000000000000000000000000aa058528a4f9bb583295ace843feac4dbce24a22ea6bf412be019f590c621bdfc7562e8dd49afcc337cab474d9abd0129b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e2040000000000000000000000000000000014f30fdaf2f81f9d941af33d53e2d9e3162f62f47c60164e9b5ea3a5cf3a681a80b66ebfea391331c231abc4341cb94b000000000000000000000000000000001854addff23c2f53a21a6d39c72f91ef0e8d9a6d6468f319200466f78854c41be3e914bf7f966f00e185b44108af30f092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f00000000000000000000000000000000188a1934a28c7571ee94f1aa5c161be611939e52156bff158170d5e12a6480e3b9d1528082cc2e537ae1734b1847f8f8000000000000000000000000000000001728b57eca86cc8fcd9dfc65a8f5f055d51d300d8781839d744a1b81a0233221cd353f642b3507703880eb0a33afa05c8f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000006c99e6c8b554d748a3526da79e8a867efde15ec50ff62e43f691748996dc087dbc538cf65820ca065f3adb5884e2f0c000000000000000000000000000000000c577c42243b95b4a613c485026306513685cce294333b72388d6968019d04214ed4bbbd5b64bce78fc380115a4b067ca9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000d7b48e69a9807c6fc867f59c894d5bbfeeeacff500a3ad4528ed4848f5ce501baf8959f822c259b712236529dff0b0a000000000000000000000000000000000e7d7932084a0416a4bafe237c923d1390dc6662e7842829ab6747024378f284af07ccde9cf80042bec56e7429ab3acd80b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000a1f74acb627d1814ef90b2d756bf76383075134c1b34dc126094238eadebd780c1ab8a3d1f4d9566dbef1c706d931920000000000000000000000000000000009bf8c2fc78b1f7af25941bf429059e9f86b34a36ff865b33e918c8435a766d897df83005c54871ad0d3e82308e368501b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61", "Expected": "000000000000000000000000000000000a912d7d352bdd182a8b431672b496ecbf18276da61d6a8eb066c41783b7cf3df287796b34b165db186e815c8847b3ea0000000000000000000000000000000002881de241ed8109f544f3a6262eac1aae69de7a7e9953812eede9e63a970225646f5c441b7de80106d53cb6dbb43f10", "Name": "matter_g1_multiexp_53", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018896a4d84c1ec1a20e1b0e33f159de4d82b55b6d27d863ec7cbefc2d9c180beed2285aadb34d29ceea681689dab06ce0000000000000000000000000000000013398b5f6f2c0c9095af94796572d603de02d41c599e09d3e254b326fa1575e0c6a2b7263a196c5150440daffb0d60e810e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f6631850000000000000000000000000000000015b31d591dedde69dcfe9c23df11782c090c443e505d2edfa217121a1d51b6883d782917b2a082a41ce698ecd95ba95b00000000000000000000000000000000164b18eaf53165842e50112c4a8490b8246376b58bb6c188fc929160f49cb0b68ad2f13dbeac8466fca75e6f72a398b8cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000012876e247618c76af5221a50780803ab64970fea8bdeefcdc1ef4c9a160718fbaed9dc6691502433295d54d4030ee157000000000000000000000000000000000cfd8dbbeecfd176cce05ca1663930be8cf3b300a287ed053e36f64618a14850a3e813582da1f54ac7e96ff61ae57c86290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e800000000000000000000000000000000085c5db53c4abe188f44f084bf17084d3ae409b753089636d3c528162c2816b9b9ef3c0c8c05e88189407d7ca95d40f9000000000000000000000000000000000015d9ab325a8ae365f173821829aa395db9211015903c08491375f82853d9084d8aa2e35c2634a296ba14b50e34c1feb71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff570000000000000000000000000000000014bc3ba096662dc560e88ea6b7b4363c427d038fe85a49ab8d9d63524940f26106447ad6e3d7495ca562c98b64d445880000000000000000000000000000000018bf745fde497914d81d1e3ab96630f24f6ed27ebf1208f7a46ad9fb893a3f183982c0acfc001984de34f617841524f9250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000019b15da994067a017c3040830b5e5f7eb77ce0cf0674e96b209b80c54f1307cb04799624647fd1fb990c61092682ef730000000000000000000000000000000002248d31211c2a37df59a0a4ddb0cc7880dea316519ab7baf1c614b26e2673f03b00e387fd537aee442cfc94f734aad8ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000003a499813ed2a3878ffc11d27dba4d55837d1114049a72444b6db0c8a7d23a53af765d66b5017695efa39bcf7d1c97ba00000000000000000000000000000000011fb1a989afe2b093fa2ae3c0405483bb1a52c21226acbdf2a52e2e5fd5f7404776551c2deee87f431ff39dfb031d716ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000014540330ba54d2f16a9bdec93a0b7ccd58ecb44361c67f209d36d2a42b5d5a4f9b9dce0701ad0677d6d6ca83a256e8460000000000000000000000000000000001a64d5b128c07848ec579df1d26755e5d2f70cf123013ac249a4d188b0eb56cd74cb12f7de2db69b3a0f9f4ece2c4201271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000017783e019baea183ee5d9e1f671a23108e403a22580f5c203dd6ff72dc0adaf802d031a236e72463e0fa2c5f7c6e68b300000000000000000000000000000000132d32bae3b92b7212dd7db16c87360274a409f46199f66e572bdb21c4af24af62758978e6d01af60f5fb87481d9f4f23ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd00000000000000000000000000000000000a625f252a8185bea7f1b73d1c7c9b1fc7f4ea5cdd017afbe9e56e7c12d58d893ddc387b7c2870f4a975b613bef0129000000000000000000000000000000000aff6dcf60f78bc908fc4c2466270065766792a05d8629fc7f5d2b61ce4882644947fcc3600d63bd5f49fea5574616bcc6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000008ee6e9521f32feaa034b533c0b7c749f60d84adb53d6943d3974fb4b92ce3cb3f67fbf52fff27802c893cb97e587b930000000000000000000000000000000012000b50d1c9628f822c41d56b29e21f3f496f00bcf05edb234ffda56767bb33dfae736aa9fb9a84ccb6a0e21131c5887275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c6150000000000000000000000000000000014d9d3051d073d24701f01631408b7dd1d37f0855baa64a13c493c15f7acf36da116595fb3d69dc386cc611c998f9ea9000000000000000000000000000000000b33438dc1f84da6ae50b1aa76fc52f5ba0e547fb15e8f655db9e0e26d6aed15c5cc4e48412d089d1ca6fb7a550f8eecdbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000008935c4cfe2a1620a0c895feecd91ea7fdcca3bb06fa514bafee38ea5819b7372e75a106904b9c9e8af268c9f5e5a45700000000000000000000000000000000114e9944fbfc05ee1ed75603bb9b79301a1f90d3b5209ea14989fdd16f5deeb01e3474da2b4692a3e0b9625d3bf9b4b2ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf00000000000000000000000000000000105268fff23696890182b5ec307b38ee1cf28336e1c3fa28b9b697998567035323ccc91e974f63c55c928f64fabc2ca0000000000000000000000000000000000ac2f8c91fa31e2d950385509b86d512c80f0d1c73d223f71b26040d58822e4269a85e82ae390441853f8169177943aa461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000d5a5c94375029e5511a6c6ca40108377db43e4e0b03cceaf9fb77fac7906f71019c1a85591719bfa5d9349f1089ba0d00000000000000000000000000000000163bdfc6d40c96bd24a3b83f89037ec9e4191b533e36dc699a32c854291b0823b3f071464654eed00f08a691aa68636bcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d0000000000000000000000000000000011654611997b772db3111d2d4edf92b83689451b1e7594a7a4bd40d85820df6a1ab090f6a1959acb322323eef27fbd86000000000000000000000000000000000b905fec9e379cfba09fd502197305ae39b48facdb01f52afbcdf159c5674234ac9723643830ab8e2639e7a0d6bd979267de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda", "Expected": "0000000000000000000000000000000004de1528d78645a4055ea348ef2618b85f8214c1dbd21ee08ad164abc02cbb2927202302dcd142c65e12934dec409e18000000000000000000000000000000000de34a6fbb73c7152f1636e5c02c75dbbc5910c763bb790d78bb56e48cbc6380bcc2ca14cc11ae139fe009135c81b243", "Name": "matter_g1_multiexp_54", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000184094ad33f83f5b229643d9808f5b3b7f3e50306788f8485472405c79e57e489549c0901c3d1694b5f61f74d87afe9600000000000000000000000000000000007ec616b56868e00563d8e8bdd36de3b5d1e314be0d81c4ee97fabab1641c89cc21e70153a4d3d4e788b04ffaf07bad624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000017e7c08cfedb74bf88c1a80762be0e0754a86e5482c27b41240f4ffa9d014e9e8560e172519031eecccd897b869c365f00000000000000000000000000000000115ff96d404829597f16b9b97f8ff71a8eaca1a76bbcb72d53803d35335d8a8c1cea58559136f9b254c28262aa907414edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c235000000000000000000000000000000000b5450038d49c91e4e5a40a31f1f75923c7e1599695b829d9975ba7d845ab20ed5a62a7238d6a6479b2c6f9249068aed0000000000000000000000000000000013066cb8ef171bdfa11e70ddf83eb2447c4169fe38e008be5787c38b1b8a946fc474e07795765ca17fd5bcf64150fb04feb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b000000000000000000000000000000000f19fc0ba8a0ec5a2cdb9844002601f580e0eb9b2265c86f6efc4b633079d43461d6bf241ccbf422eb9d7c00ecca88570000000000000000000000000000000012e744ae937ff9e8e4f611fbd1c9896bd31bb1ca36b948d9be89960fee6c0cbab3264aacb916ae3596f110cc1b26bfedcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000006ca09ce8c07e89e9e51e208b5d32b5ab61f0de60484d9185a26911b56a728a7473b70313fd18c893ed3453719b074450000000000000000000000000000000003d372a5477fe7fd84a58f6f2eda8f5c61aa0c357c7fc1708f7616b8cdff249e7d2910d753c2e531a278f5853fc065970b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000152ea2fd1934c32c3c8e27a6ffb278741b899c5e296549380d019307875629d57ae44580a944babeecef73753e30c92600000000000000000000000000000000161a77844c90a6e83ed2c40c937de21fbd714a5cde60015a71bd4c960e894d3cb54a8d1e4bb4cb0a1985d4469814a991730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000a959b12e3af03cd4629f5f6f412b7084eec6aa55369e2dd2f355c93ea984ea6f2a7a01e6a10146849503d230fb08f7300000000000000000000000000000000161340908a38e4ff5373df643e3cfdc459d872b5cfd41ab34fd3297b10c37dbf3088fc23fb71f2a1751a121bcf51ee36ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000013e31a4f0cc29a5ff7f4df39db999c95eac789656bc9c6b91d0209b8a5ec2dbab698048fefb75a3dfa48066ed5743215000000000000000000000000000000001851e72741707cf96f887d13e01981f1e3db5834185eedaafdea99eeb11dcd3e90a9985f40886b60ee2a779b141bb62082b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000001501f155cf6f053631ebac7d2c57cbb101a750f98b6e11df79dbb24ec8804535b1b24942022aa64713fc60adb2017bff0000000000000000000000000000000012a08f9b1ab90531a26221b70751efa598b4046a5482c01d72f506ffbb3430d35016848755674d01e16bb78a44f8b6882396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc0000000000000000000000000000000008fe1ea18cd8e1d2c620356430ca43782f844a2efa6a285a7c9c086e972b12735faf6237447759bd93d98b6dc7c42344000000000000000000000000000000001731f36e811c640f44adce6bb68fd71065f440eeada278ebcabfb9bf0291e551ed302c592aa4ba7e3a502cf58e3eede69a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000018e3a4176b543f2152bd7f72ca358af6226f77b5e10f3f9006c8bbe4283776ac31e6d10e838e89e8090215a133e2cc510000000000000000000000000000000000f88c3eab9ab32fc165083ba1650736e04b4e8740591f6e3ffbf684fb359fc8d82513c25a9ecf4d46faaa14d9f13a3ff20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb0000000000000000000000000000000016fb47b4497cdcc75c0547f4234ce94f45d160e7bbe199902b2af5a5896e7d46cdc866d0fd730f568449032fc3a2df4b0000000000000000000000000000000016c2da30ef51e6728c09c3b29a7abdbb104f1a4fcc8960248b9773d2ea7f1bd161bf17203a271edfb235e8b0be437957f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000001992edcbf32707e92506e5cd12662e730bc96b5f33bb88c5569fe6b266aecf63548be20b03fefaa078231b17424ac98d000000000000000000000000000000000f6179cb8878214222c2353a60e0ee210c86e306e335e929050543f084ce7c7ef56ca8444eee59856f4107e0d8cf997b52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e000000000000000000000000000000000d3ff341e9b3821ac23ff7a87cc9dec3fba38ab8f2bc0f58e4c0135a9d66c6d6731ad8bb97468ca44538ca7f26fdfeea00000000000000000000000000000000053240b8429fb290453de18000ac58df56b5bf3c279e35d9cae8b350b932b0545b6c19ec7ff186c2123731d971146df1d32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f0000000000000000000000000000000004b424dab429bb3d22d18b52c4f9412a65eb7e8ec40b5e308f65fe6c0da1a1ab55a629ef8ed57adf108d146b46e6261e00000000000000000000000000000000057b7d5285194693a7ec1ed9ee3dfbe8598d9acb670baf03bf77c7799227ea788052de690e229b0d28c0a6cd79d22b0c1f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad2694000000000000000000000000000000000ffc202d826607947dd8f63b227a06d8c6b04848dd102da57723fe20e9b06b7c125f0ab2d2f53e14cbe95f1031624f99000000000000000000000000000000000880400b425ffe1b63214509f9acb0255d089e9de8e4eb643fa3b0383aed760f4c00babadd32f48af724a2c80a8223b383052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692", "Expected": "0000000000000000000000000000000013c0b89e259f71ae41cc73ffa3c900ccea45a8a655353db6eb17a87582b00bfb971ba91d48526d934b95e9bb6a0fb5a200000000000000000000000000000000042a78ec26bc1ac4165c36d84588ca132b7366a3fb548801810da041213ee84c7e6aaf5ba02ac051cc1c5be5dfce0ea5", "Name": "matter_g1_multiexp_55", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000083ddce067e21b219535e477f77ba100fb86744b1b82b4ccd0c72aac69025038e719ed173e70805c025b19bf7ba5908a000000000000000000000000000000000a9eb816ed60bbe55d4833c0e91ee73669aad116ff793d941223c17c86fea3ea434172c3214a4620d4090915cfb15d11c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c705200000000000000000000000000000000050bdd7d98b9df55ec0ab87e757de009c804880f06be3ce13c5e051c3080df45bedad4f074812a698f50d6774cd5921b000000000000000000000000000000000a8dde7b81feef753cf16f0818f29256391276847cb832bc2940bddb329b249af4970684e95fc02e702f09a84e7737dfccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c0000000000000000000000000000000000d989e383d1c6e48d14332a72a8efd89260fed65a47c4baabeb0c0cd8322e26ade95b8be9f532b4813153cc39e7a9402000000000000000000000000000000000f6f7ba41c95beccbf59ca1ebb1dd43348c51de617a09ab8a2d67d3f7065d3f4699b1fc31197275e5b895f92dd106d667ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c396310000000000000000000000000000000013f3c3eec6fd2d4c830458cf58d5e18f0367675c47d38fd5ddce1e8be3d6ab04f71d09852b987d2db64652b3255e874d0000000000000000000000000000000009c0000761e1fe517eb32bc3da4f7a933e77db6f960f5405b64d9088776b6ee8e23743cb4a1779e8d0d93787ca029d7c6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf018403978000000000000000000000000000000000c7a5bc0249717c1e39a4eea37de1b423960b409f5e0b3877e90d5278cabee197948383936739ee3f25b4bbf7f32e18900000000000000000000000000000000113d6fdda1f4b2a20d98e1d458920658c762303ee69fd7273a8830728f79be00358b3f3000927bc4d26352e5b9e6652b7fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f00000000000000000000000000000000108fb7a97ce429fc3ba1ca54ae841309e2ccce748dca953cb7dd9dee3ad9d919e3f8ab635b294b94b939cd80d3435b5e0000000000000000000000000000000003af838ba4ec485ec2a17e6f592fd832d05133952f273d1b472800b210c96cc503caadc17b38d3d1e978606786d9ffcddd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b0000000000000000000000000000000004cef28329ccf221ad7ab2b851e869bd433116753e0d8bf38d22ca46fbdc71fd9d96aeb9c0df69c47905a99c96fef0aa0000000000000000000000000000000012ef5c40d8b6469d9f3921eaa99446fe494a55994551fd1996c453a4e5cb4a2cbabe20671ff51639710a5e45a57271aab4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000c81313e8b5689935fc01b5f999de2fbe9852bdccf484edd0771e8427f2a194e29d0af09db1152fcd91c8f7b665f6929000000000000000000000000000000000f37dc7f87b8de48441861ce0c88b1a24f22aef2c321ddbf385cedec7810c20c7fee3d2c5a04b5390a5fc24612e4b3e9457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd10000000000000000000000000000000004ebf9f75e92ec4fb7168bf71215c9ea8ec17dd9ab392c9810316a30a33b4ace8d93ab75356baaeb51a7f47b4370915d000000000000000000000000000000001307c68414b73db43bcd9062580f7c814c3c34545ad5d943685ed8df26acd457823ed628e4b215875a9008a406fadb5619f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000cb5860f081e314d4fa3bf70a5eb18d6fb7f5257a708f1b1726b539115050754724ffd6a34d3b5c95359f40f41f2390e000000000000000000000000000000000c392d8603c2ef93d2765d98c695dbda8e4b64ed90c4771a4e69fa00a77d788981132336f870a3a93765902fd8fe8763f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000011c802516f42e267c0f9db096fdfff77d676eb301ef1ad440b6c2129c5b5722c420f6e479443cbf43d48803f7e32d8470000000000000000000000000000000004a5ef232d3582724c3eda67cf2e69b26ce44bd927555359820efc3fc67912df560edfc4d119c5595e1ab1fd7e2a262f50aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000eef1e6400dbda287910c117ba17eee1137377e262f7f5cf13710b521bd26eca2aa9731b0a1cf182a0d57a329369125400000000000000000000000000000000188e925365fe7cb96875e85f711d8ce233cadbcdd4c892eac52d9c77f98082662410db4cb6b24889b21f162eecd10f42d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad434000000000000000000000000000000001307849ed4d685815c670477ac54826e94465aed0b70df9683d09ddc62597e7a0a7a4b2839fbec735eeba08bbd3e821c0000000000000000000000000000000005dd74ee1018ff2280c3dd8faec3c97bbd00bbb7cfbcb849bb003b590a999b6bb3a973ec96bd9d825206eb353086283485c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae000000000000000000000000000000000458181a1019a65c34835eeca4898b88b0351da7422bb5982616c90740e8773b5a03272646f26c3a5801c6c16be33ec900000000000000000000000000000000101c2091a08179eb0be41e20a545f5b53b8ee39365dc9b57f12d75b2beebdad488d63e857ba5187c8f92af447f72896ebacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000cb8c637a9b8f053d5104b582ca03ecba768425c639fef23c4b624f31523e0ac669183639991728135474ca19e0335160000000000000000000000000000000009e0798589417cff12eef14f00e415c51c30fc26461e92c4e3fb4a5ab1a653ae791f05f4cde0cfe2132c377175cec1c2473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae0000000000000000000000000000000008400ba7dce60413ff085c0904066b8e9e9ae290781132e739a5a8c7bcbda322fe1c8d0fdb0e9b0abe44ff99d4ca22ee0000000000000000000000000000000008b54feb64f59541ba3b7c6f86d24b69fa30ba057db890cc6d958e3a7de8bd379257c90a413050f7789ded9ee7b28bbd2774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a", "Expected": "0000000000000000000000000000000017377baed9953cc7fe1868baa9e2f6c87edfab1700bd1f60e2644bb97cbe2dd1fe031a986fde6555930d4c464399f1f6000000000000000000000000000000000ff69a282658b49c270e829b326502d83a82dade820de50764a7df957e94e7c0a1f0e3d9084b45d9a875134bedc4a0bf", "Name": "matter_g1_multiexp_56", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000d1de82c29aaa76b17079b2e1000005bf37df08de2c5ba7a0f9a14870e0ac327f46f59a116c72db57cf5110aeed6c76000000000000000000000000000000000a8ff0afd1cd7f541775567134a889d82727e893e4f57d1b5981fabd4bbff59dd3d3995a181efc9b5fc078eb3d4cd0e7d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f026672140000000000000000000000000000000018120f0d0dc908dce4adbe50b24b66ce12e710fd35e5a8a8c357dd80c078d6854f20b12d40279b9d6a895460d8989cda00000000000000000000000000000000064f4e282ec5cac74e1a12f678391730663c83afcc0b415fd21475875762de2224e389d607cba84788a16d622d2ef5c13e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000dae339b418871e2f31ed380824412acbe44e6c73ede9b4c52c054924297aaee1f7da749374d7ca44b138acb85dc182f00000000000000000000000000000000155cfb670ac94e7d5a095d2797cbbb5b8ad3e037fd246246f8e8c2278f5d4e53a773e6518ebc3ea5aeec6383d6fbb62c145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f000000000000000000000000000000000c1f7aeb05294c1b496de11f743c0c7aa4255211e1e36389bc93dc8d0e73fdb9af7bfbcef2c196a95d1d449b9983b2020000000000000000000000000000000011251668e9edb38ad147f22cbab7d280d436d11039d9fb823a19dffedf2c6a484f112560623cde7e5525c85b4f5d06accf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed0000000000000000000000000000000000ad488f5b9934adcdc834558c8db1d62574e1ffcd03da30eed865042abae4dc03d69010e7e591d9f0a8e421d22cab23a0000000000000000000000000000000002cb0a8e0713dd3c4833af74767ce46aca6c1efdfe75d09a50fce4df2eee3fbc031357691e23ceee810d30004d03f6b9d9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000465d95750a3c688f560ab9ca6fd1f77457592a0d5f54c17904a222010444d048df2be3dd402f046b1375d75de446d2500000000000000000000000000000000166289d948aa518167e72591a011b3f5ce209bd32ce091543bbdee1e8776269347ed711e1e9f1193f818e3045761a75141733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced157586700000000000000000000000000000000012abc4f1c56f9ac3760acec3d79b77e9ac71bbfe4d2a90cf43da3607c99035e550a4d0fda734bcfcb16ad08f773535d400000000000000000000000000000000030953a6099532f7ac352eff43569914c3f8d736b8aca89f778b4a67c754ada78e121dff664feb751532a41c8081380eb21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000138cb559d92b392e94cdd8666605cb5b05e585dccfc023bb6f1abe82fad35c108fca7a41afa49a801700dd8ef89eb3b0000000000000000000000000000000018cf89ad3e05492ac8699ba0723d5ce43e81b0166fc33653c967da921faef37f3ee2e8e3f71f983774966ca183e05f9eeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000129c48a05e3d6bfab6e6f5200fbb90fbb743b045509b129e3622929712939c5d15126a09f1a650489c8afde7ace8baea000000000000000000000000000000000abff3803d605dbd63bb8453e304335a943bebd224d2d8067d76f5591cc6a2b954b9156a243b0c23d08424fb9edb52383d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000194e6f217b863339824d95c77253ddef4ab97d9744d10392d399b1f165170bb8c13ef1b7cbd995c1c1dc2a9d1b87f0da0000000000000000000000000000000019fbdffa8df167a5e891d09aa1e79049d377014e58523c0eb453f5f072a468809dca8ce0aa22b45bad4f8853d985be1df6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda40000000000000000000000000000000006f0b72c2a934e430e4b773a61317007f1ef02c5f978b3565d623b6590b6cfec22f98b49f9d7f7efcc6913c139fb27a60000000000000000000000000000000018ea7df5f807d4c4981a9159d73d83ea84359d6aa00a5ef019b0dc307d096676c0d16c6b167fc55e14329a858c044c5c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e108700000000000000000000000000000000130fae66f6b4e1a9b0b39906fac847f1285a7d37bdb0d3ddc2c2bfcc6320ccbad2ef1f119f2663e3a45dbff005a469a10000000000000000000000000000000019ba2ae0c371256e4c3dd6f9ae2568386d3a8bd90a57ff982294eae9194494add18958dd516ca9dda6a0b334391cc211f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f000000000000000000000000000000001152651000a16809ec599f2fe9f330b0782685f6302254450884f0ee61ee2dc2cc9211f69d5d9dbcd7fe3345542a0159000000000000000000000000000000000b5c017e7ef71eb089188ed85331815b40c37abb6ff73d76f40fd8dcc6d2120c6a52df0da042b2b63dfd0da7db2bbca9ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a3130000000000000000000000000000000003a6f178d8c63765b2c8df834ebf7e96a4f451c6e05692f96b71c8be2a6e9af17a5cfd8b263eaa254592ea9a898488bf00000000000000000000000000000000185537df1a10c4c12fbcff08de45b349a90b0cc8cd17827df87abe160e84b661d58a1fd03c669015b991225ba08e171e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000085475c2fd70cb7caaaa7c5c1fb17e2346903a962fa68536240d041f2f8cd3a7b83aa79a77f713bc31f7becd347d18d7000000000000000000000000000000000c98414bc318b350113186db9e965a238f1f181b00a2265638d914d263e4a71ff643907ed8dca814e5b8d5713baa8dc9d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd7100200000000000000000000000000000000001064b94e868fa82c892dd244c6247063a276cc651e22d09695ac6e73d20bb801a189e8fcef8a711ed471fa3b2c7d19000000000000000000000000000000001561503962d7314fe41f7b2d34eadcc985fa748cc98479a06749692a00a46fb2fe5b5a68f7001a0f89f20f7f42f4463c38f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0", "Expected": "0000000000000000000000000000000003e62892118f11065ebc66c58c86e2f0d3c79541aca8db33bd0e12f034f0e106a76f0aecd537539cf2d793cf823ebbbe000000000000000000000000000000000067e42ecf23e1b0590c30106b0720c806ca71fca0601b499025b58f137ff22aabdc6cc5eeef232fc9a20fb9c2bdee16", "Name": "matter_g1_multiexp_57", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000184a661b34e18b637bca53ba60c891da69fe743d5336d92e811649094c15ecf2445736d0c1577bba4eb729aa7204b44f00000000000000000000000000000000129a348f7fa726585badc23f5dabf49ae095d300056b219bce0ce15f1f6a9fc5c8ebaae56362c3501af3f3de19515143cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a000000000000000000000000000000000badd515b1e0959e77e0f00c7420b46bda5fcb6db59cbd431a1b0ca68c291c6dfe89ece299434f83a980613fe73ab7d3000000000000000000000000000000001266343ad330fcb2cc8242e30a8085cf6995ebd810780115ef881516d4227c6051564d7343e4a5d6bfd210e2e40b91069a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000085e7c22d51db0a45d8db7d5365de9541eb87b81c237fc47cd25c297da4435b4c9b8212c76c929b7c8f32e8d9b11374c000000000000000000000000000000000a4b0f905b48145f1831e453d0372b7861f7be6e413182153cf77d737450a58f378652255cb4516a482d166233dc88c574e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e00000000000000000000000000000000086b97f87625356425a79db717f940debc7a7e932370ea315d1f94b1ead853e3ab6edea6302b6b5b0eb4e4bb3c7fd14e000000000000000000000000000000000fde70203ac7a82901250e9798ef1c671f8d5f878fa3bc83556437b9b98e77f7fe7d3a0f31b8cf05ff6332df0424136fc23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000e629b2d9a57bf96cdc6871ee7dd7675257cf62dd10028201448d8e5b1c0abe777190a868fee83ff5d067252312e82dc0000000000000000000000000000000002102d461c9522542acc185349ea93810c3e2412ebb427f8556b947efe198d616fe00818bedc22765f697507d7678dad972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a32640000000000000000000000000000000015727f52d46099c0ba041be660ca312204afb0f927fdcf0f1afa4cd3448cf3e9fb76bce7ce0da8b4c0048f76f0e7b1410000000000000000000000000000000009dc4e213faf0a8216061b59dd35a135b364431e2be37e42d065a42fc8e42eb8669d32a5f5ecdfd9234487479543471bdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000afb691289f877e1de6fbeb38cee0e36fabf3daf904256d5d6db6e96ce555a9304219bad41400ab6278727e5fe2faffa00000000000000000000000000000000165a54d6db7332b12224d59d8b677517190744c039d9bb401c2e3c4437dbf230b67308fa2d5ae2bf5de282c9ae38a3fa4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd3300000000000000000000000000000000032dbd300fa383541e5c40c849addae3def5a4f5392c44b9e96981dbcedd02252f9bfe4100de9954ab34fae9b2ec21ce00000000000000000000000000000000185e62adc2a44462019c86028c617ddf59a6b1c16071624de5ca755f936e73c47cba00f552d2d79baf60a1796dee009edc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b0000000000000000000000000000000008a574ccaa24ef76112a25b990b5d3b462ff9c43589c9efbb617b45a87bc26eca6dfc6c9e58a12650c202a06d3c86fe60000000000000000000000000000000011f41e39dc0f0bdde1b9e1879741824b20d9237dd7b462272115e8ed44a1e6b7bf82e8ae481204dd8662418fadc63bbf8af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b000000000000000000000000000000000efa7f183cdfcb25cc5516bdb45c409581b6f2a5bd8ce8092dbf9050a20b2ff57c6add39e96a6f1c8d2134a5a37778c7000000000000000000000000000000000a8213977e8512648b6aeafff2cefcd17a14a052791d20236a78e0b462dcac81db74f1625e787540d7dc279846983f647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b8000000000000000000000000000000000287e0add9dcf33f37a10a5ee89cef5240313af0bf0dc183d0c3d6b919c88b979c932c7f141ec5faf012a7f33fe56fa4000000000000000000000000000000001313f591d1da8f6baff044857d2c04f01935b493f5b951cd3538054756d33a52f71be92ef908f016c133aafeb9b9ad2470cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f85200000000000000000000000000000000192d02ab0a323e85e9fa6f553eaafe0d8ca2de63f0fec8139e24805f0785cc85b39908756ab4eb39354ecd8d9440d5260000000000000000000000000000000013997cf706bc8d40b019c2dacf6a7d269e0ffdf8bbc1b4b39e75b48ca5e5e6eba0007b8c55b59530b34b7ebb4c657c57244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f000000000000000000000000000000000a61b3cc7913e45c132cfb06a26fdb1882bd700b32361572fc79a3d2c432644392f341cc70905b86cad2ce52c30e2ace0000000000000000000000000000000011bb3d958600993ec04d9f98ea3f29df0dacbfe6557b36bed865c564595a64132e4036b6240c97cdb38a60533d5a08baad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf30000000000000000000000000000000003d2bc11fa699b284b37d1b45c8dd6b41436a7b2fa09cef316821516801afaa4e1282d717d4eb3d46e54c0208548dd9100000000000000000000000000000000123f8cdf2bcd7d6eab31975ddd610afa79c3c95fed2a6348fa6872b74a6e2816509c71f11d1f272dddb59bafc0f48fc454b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c6000000000000000000000000000000000982c1cbcc39867c7c8c4512392af1489a5e6aa01ecf56abf4cd9050a33536feeb1866421958b929096d2c3f6923891700000000000000000000000000000000104ba4defb74b35d15db80df1f4029650f00b306d702b5934c1705d226886d4bd22b6c88e71b862109f8dceacde3c6d2ae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e490000000000000000000000000000000006e5fc17bdc786eef8cf2140bd8002ea859619d319126fcc5053be9c28526e14e0bc8eb924fa242305069226d766f71c0000000000000000000000000000000017ee60b0dc932806dfefdff2cdf00efc4d5c81a1e84ce48a25db1d49ca26232d4e4cc1f37b34c80375597587dc183b4259b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4", "Expected": "000000000000000000000000000000000c3dbdef90052dd5bdb70f15963091c8fccb5b8720a16140ec96dda18eb6cf003b6f5f7c244d25cf6da34f2a1703a9d800000000000000000000000000000000187456c5af42c3de3d5e08936e8a3f411fd6458d017ec508e48f2b634319155428733634e22883492d7612b2bc38158c", "Name": "matter_g1_multiexp_58", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005a912b6b2b00c2b2c90ab1aef8c9240831ea9ff2ac3a92753054f159f5ee4eadab8ef57eb5972e3169ff9649b886daa000000000000000000000000000000000981b901734dfb3b5f63bcff802536492664ac13dc695960ad89342ea865ac67d00da7130833126a33573d55a9baf128a53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b46000000000000000000000000000000000b052881b3e27d232ec980dd99bd0ece4e861cecff2496472caffd741f2954718d605de98d9c27dd3ff473ff12b238400000000000000000000000000000000004de4bb9e5a4cef93662cee72259b88f7ccf8a495b733e868d76cc25e04c53a65a83c853c98a25f7a551641d54ecd9534d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000016e4d257db25c08a68943e6e0065b375422fc817539d2874279e2b41428da449627e6e04087fd448f651a23fb01816ba000000000000000000000000000000000e80d041b65789b3289a94848ca4b1109028c9fb314e652486e650221945ef4224ca03a693e062b06036898eb664fc211b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c74000000000000000000000000000000000542fcc8d668a827daf3726bd71d7ddeabc440a6fd0c08a4730803be6e76613cc0265252c41123146a5d7aeae93f485f00000000000000000000000000000000109a61920ccf34a0a71f51f4fe7c882b3d6fe449a8c67711dda64f9eb684b4a28cce6e8bfcd6f3cb599adbd0771a132dee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a50000000000000000000000000000000000a65c746a1206b1250598823b9b6fe5df3dfbee21cd31000e50140893875d1ad9fcd4fe12bce0758544ad8cb4cf5ba700000000000000000000000000000000038c25d3c35fb34151428d2f6bb8a459f834902334d195da214ee9fae4bc6099d225588a001f8fddacadeff0d3d215463e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000158f2288a667f475c736dce9e404ed37b18d677f984b6d9dafb051b87901e6fc60289161bfcfa46a3fdbea7b4cc3f795000000000000000000000000000000000d7faf96c636ee38347b0046d333e72d601e20b018d554d50ed63e30c62db7fa20e29b3ea57b1f31e0d7544ad711c96aab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001933815ab2d8b6cef8790f87dd9750bc2b7a75a9d6c425a3e84cd109f5f5ea866e171dfc212f6f8641252e9421fe3aaa000000000000000000000000000000000f8ba799ca5dd885046a4ffce1d26688d0bc6936f3a5a943dd54f89d991500908c81ec4f9b116e73f74d46b67731421bc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000d4ba404254175cdf5c47c08ec158ad83b6ff5b8dd32b8cb9753fa157726271f343cc0cf5231e7e31583877d2591930000000000000000000000000000000000191f45fc4b8c94519d13ab28e5f84e22dae2f82550b44be737728a695865973ff5060a639e3f03904d74717963dcd764ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba0000000000000000000000000000000014d348b7dbace24bfcb258c853b19fcc1637d7ed9b0ec00d4124cdf6d608c6849e8d2f9858afa83ff356380afa1376fe0000000000000000000000000000000008c509beae3cc22f0da64bccd2e0387c05d7613460942d25182605b3eae6ce052540142d5975733cb6554e6da9f473b6e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ef9aac66681015bdd9bf287caff9aee89225e30a7976e9f503a1712fa863c8d6d46a80952a1d94d96a5e0496f64ce5b0000000000000000000000000000000016c66018f43bf585195b256ca106f47077f977701d97f42564223817ade0a520aa3d7f06d868f1e91705232b1d2440d9f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000042d1d00a946085dc6329e852342573db7dda7385e6a50a2660a924ed6202968e787559fc58a162a775bcb115bf1fcf800000000000000000000000000000000162b52027b08b7d91fe0814c7be69414121cfd452f4d0407a2300bdfe9ba81a4561af74d8067e929b71a92947eac4fce71927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000001598949030cc21d76a9c69305f023bad3cc761d5f857bbccec4de6b0f7557395efb2d126382731aca994a5020039acf5000000000000000000000000000000000dbea8852edc6bef41dd317e7d70eb2a5416d5087ec5207af3f5b3fec39a416dd9ccf4cfb5400cca152f173e66df05f75ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000081e898b02838558c1c9d7ef9f86fefe512e2e7364ad824506c886b4cbe947657c5480353e4f72e237da013d81e5eeb10000000000000000000000000000000005353bf2dafb1b9b4f2cf58e16645aa3fb759eef6eb8f516db068d2768851e7724fda5cb85241aee62b4404de2862dfbd37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000028453aa48ad0302804f9cac568467668b1dc0dce2cbbaf280810ead2c0a94e156420f4fd2566ee7f629e57c3741b8960000000000000000000000000000000001cfc5ed80924f7088ce6a5414372d13fd8f6eb3dd83c66d8b8e4dd1d4db2bbbbbc6ffac00e3a880d8a8fb5dc07fb23f06d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b4000000000000000000000000000000000a236833fafc3da813b95f4562804361aaabcd8166780a4646734e4b65e3a1924c075d402404b52adda4902bac7a2cbe000000000000000000000000000000000def6beaad6a180998c4c70f9a8dd0d948a79524b31fa44874908058e9e58caec2e23d5a0787f1ca05a359ca276c840ff56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af00000000000000000000000000000000188bb3e69bdf0a5f31ad16751a12c767c86df80f53f6688ad74cb2fb32b81bbf9d60be1182ea1b6c0d6fd12ef73e253e00000000000000000000000000000000139ce5ffa569548f1bb877c3d573136a8eb12e7c69cd21a70526f8724bc67e0b37cf7149dac3f78377ae7d5bf4882a6771e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c", "Expected": "0000000000000000000000000000000006e5af407ada013abf1451bc9d5c08e5ba9cddebff0cb77175b152fc19bbdc48e1498673ae4698dc74d039a671ecdcd9000000000000000000000000000000000c8783b3ce25445209b9f1d8bd3ba539c01d230c07c4fdff38ec902467d5f7e9e8e660d8997939684e119fdfcc963836", "Name": "matter_g1_multiexp_59", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001731f73d2ef1f87fe1752c9d6428de241ba71506c76f31aa9697d1c436af51de363405f60110e8e69ab268280c20f92d0000000000000000000000000000000001ec6ede05f60685e39acc7e105f60602f0fa3c4a6da7342da755eb34aeaa5adbbac4c13197a2c93314ec79f5da8b90177f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da00000000000000000000000000000000158d295d41540fb1a27d8200ddf51fbb9d31a70fcb639c42b7fafae4a95b90ab1ca777125092aefe20f856e3291e528d0000000000000000000000000000000019670ff04a77cfd367c5f0c14218b5d95ea2eae8577da10f27d96e58039b7dd7e9f7f75c32f99dae0920509733ff9c96630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb00000000000000000000000000000000052f8e8098f9e83eaaec1c2638aad30b043c2359f2551a02b2b95816e1c55d37bbfa6e284f280f15dc174d5f03a7698400000000000000000000000000000000034bc698f07544952274c21f416d8f1281ccdcf6bf53ad352afb15a3412879a10b37e6b8b9fc5f46ad715f9ce7b46e3d0969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a5162000000000000000000000000000000000c2e014d5068adce3049cc326d36ef92f294700ab64bfe170260727117f098727cef2e28dc10fe473a46c98867c618400000000000000000000000000000000005b3ea9c12179a47f7e69690f3303ccae614e06878189b40264f02e9bb26284dde846d704121340723bfd1fe5696410dddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000119e9fe8723883d9ae8c61efdd3ae961795d79409750dd39aa6f0f8727ca2429856f977697c4f81894061da278a0f9a9000000000000000000000000000000001438a4dca0c786062aa9cb21e26b87e92f90dcc0bfa014f654b1734cf7cdb8a2e62fd3836a802a9917539dd068c6b4b1191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000012872f4dca3a9f3fcb07d67c76836c23eba3f7957bb77950a4b43ad9c7ee54f53187a742b13e026f8234df9e91659c400000000000000000000000000000000078b9d597bd9b5ed2f7e0d5f8e4a518012591b855c5352fa1450704a33c3cbd5695a0f8da235411aa99aada88086f643aa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f8480000000000000000000000000000000006c414c6611e00c6e98b370bacf2ffbd7ebeae890278a0e951d6aff7dd3e5fb90f82b4e65dd007a3289f97a9600786a9000000000000000000000000000000000cae4750f99ba13f03d3e0769cccc879a4832210d6a2f25b2696099c0cb184398b7d432e801d23200166a4c53a3e70f3049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf100000000000000000000000000000000122f404ddd6b34938d8e57d9d6ee78c3fdc1b771dd7392944ae88c625f81df63915a87ac63dbb69adf8fdf856a92bffd00000000000000000000000000000000197c20bf1392d4d68efc6ac3bd5d8b53b360e305a501dcfc2e350e3738503ebd44a574e478757240236762db2f23d4310502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f0000000000000000000000000000000019195a36dfc449c19b172ec061b4825e4de85fd5b9c633c953ba7a5617973e61abd0de3d59d441f49264a0dd2e781b20000000000000000000000000000000001430f743ee98a2b2f37d9ecf2a7d4dd4963707fd4cd6ccfdff55c3eb189aba2fb295877bc2d3db9032af26eff6485e459787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef52371375000000000000000000000000000000000be5d90e5fa172a2034667160f635ffc190fa495aa9af51b648125c29bcf9b4b31fea7a7e4b49d91b4a8d081c9aa2d3d000000000000000000000000000000001721ebb02265f698528ae1bdc5bd4500d7612bcab9ea939f552ffd8e9dec1d267dfd25ad4d3531676e2ecde3d2170c4810b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000012b7607bd9f1701ed002b6f72b2e832dad7c9b2bb6eb6368fbe78c48bdfa17b2546574d7876425cca7986fa6839b6da2000000000000000000000000000000001975f41ed7cf252a658e80634872ac495e4b518349487930610906bd396f7fe4af3c97acd0ed3b3f265917560b13e6ef072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000014ddf2cedfda66e12e999d0b280883c546e00dddc0bd17817d6df90b7a614c472cb2840b133eabdc7be39b63e50cd9ae000000000000000000000000000000000b86e0559e27a6061aafc091f93b744a8273032f0e8b1c8b7071baf3ac7008a8173b71f51b27efccba27cb018b25257ff3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d60000000000000000000000000000000010f691744e7094b801c180810b24f6a29c21a13514bcaa6303ae49067bdd001213f13c6f980c51b050a684b525c2dabe000000000000000000000000000000000e4e4cc3769cd3e0e458ded43b5c7c481c17efd3283972919212b877c21aa7abd31cf86ee2bdfd3cf0ef6d730c0907db393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca177000000000000000000000000000000000b5e86c2be33255bf6f2f2aa8b17109467543168c0bb92a9ce19bb64c5f84188b2e9f93ac85d948c76989d9d4dc9eafc000000000000000000000000000000000c5244fe670dcb16d7994b7db8f933ff98744e5c6dc124e057c05d2697881115a99f983be480e30ae3e0ce75081b261edb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000df7f56643536b20f65cae1ce4c67c6bb6def8c9b514d6edc92673ae743a2f4e4906aaf7e3b048f88f08a4f5c9f85c8000000000000000000000000000000000176cd183f547a3f38a86d604f8e76261755f72e7222f3734a456a3bf7029590848970e8836b3570e9a4f3500e54fa3008e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf240000000000000000000000000000000003737e58505d0f4c6890c7e03d5f252aa682c110f5bf5dfe8bcee9393104393f4a6a22c34c773e1dcb78881a31b33a71000000000000000000000000000000001988ab3430de7a463dcc2156db572c43b68e58ac2ee26f1ee1bf8e9889f6cd3250e5d7f9464a8eabb127306af39c13140940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c", "Expected": "000000000000000000000000000000000d541103aa046ef53761e929d50c88796b90b3284668a2a75ecd461ade103f086fc443a3681b6440e6b8a8b9558870f20000000000000000000000000000000014120e26b160a37f8e18faf541bb863ebb425fbd0eb072268a12657b6e5ff62d4c0e504691b3d09b710741820f747b85", "Name": "matter_g1_multiexp_60", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015c91ab58aad72af3364a3d05e2893c756a273b2c731ef421c0552dffcb32fdc4296bb79afcae2d3c8aec6e0dcd27c17000000000000000000000000000000001901b4fec7a1324a34fe403dcc51656145fcbeb4eac94f955f4fcc5ad6a016eaa436878e85dcebd8992e1a177c5bdbf80f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000016df7578f74b1ccdfd537a074d71f2dbdd581f1a2f78875a7d4e1c3cb772aad0d02bf4935f7b08aa5163e82e5a747bdb00000000000000000000000000000000053931dd0624377808705d3fc6e12c4894414c8f6a5662ffd71251bc7725e6d23b7781286b8be1e35eb615bb1efeee9c34680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba200000000000000000000000000000000056f0c5d78c5d4e97fcc7d6c3132dc4cd802eaa1bf18921d039274104b56e8a701c25de6ad33e57997b2e8491d7cedee000000000000000000000000000000000c87632eb73c464f53c15ec127cc5c72fe6a413e74313e80395b55e122108e2984eee6f53742ce4445f455108002398fefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad02200000000000000000000000000000000040f1ed7a9f7c70a546088822088c476f8954681f3741cffb7e6614dcefe2963253599acbd263b988af3764331a273030000000000000000000000000000000007f9d150a4b34b9a6f872f9bbec4d2e0795d02c5411d6b3a844ab95ea87f9330662c8b0789e12a8f6dafa2f7cf2f13a12c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000002890e211b1969c72a15c0f24b21dbf672b2cd33ba9ab79790c07f0734709bf13bbef4f54bf17db9629cd7abfcf1fc2f000000000000000000000000000000000010f13eb17ab7ccaa0bf32b8d4d38760b72fa0fbbabe04017d9d8283f6dcc5500a336339400bdfca06749f7c1e08f748b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc93000000000000000000000000000000000f7e328026c07b116dcb8950273579e0c4af027bd3aa442a41d279b1b7d87d672154d2513669428e8f401db490404e6d0000000000000000000000000000000004208901e02756c5a2430200d562c0ddec0224446b3fca62cc98e9efcfb3508f50794301b026d47eb99aee210dd2f898202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000001309e8c1cd6ca596ab2c9605ed0e356cfb97c4079518b0241d40a3e0e4769a8e58c0ec6a7bda173fc427aaedaa275ff7000000000000000000000000000000000143b1d1bb451cd56d800d71a747173e56b75cbd6fd28ff4abacbc1dd87653abcae715882af29c29a1631850694c5aff8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a0000000000000000000000000000000013bf8880a6c95a8791b8ec37c2188e4c0c2cf188e2fba01a9e7e4b81116b10da49415a0588385156e4bbd45b168467e3000000000000000000000000000000000be052be3f3278259b6e01d9d81afb4d4215b0b738378e56719403e2ed31bb6e15e47c9986aac19f79001a76f35e4162ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a730000000000000000000000000000000013b27128d2e8bde36f11503986c226a1613ba0779de9b25686284d12bd995c83e0db9eb0b2ea759ee81bce0ed2c0c2ad00000000000000000000000000000000128d6ea67c8cc9ce6eb93111780989b4b33afff45a5075691026ebcc607e61b7a48e2549ce8286cfe4a72b182073f373b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f600000000000000000000000000000000137033427de6a6d23e0a2fc17d396114f8f4ca3e56e42936c96029c5b829b3b8b7ea46fa47fa39f6e5dbcd804873d3ab000000000000000000000000000000001986563cad41be453d14ea3f166c2ef2d89ada32a345554ea7c7141f6b1306af815579d7399c73039d1696fb62edcf80b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e472000000000000000000000000000000000f0878d6eda3d119eafa0e5cd0260cd5c9bed5fd3251f0eda5a6aab6b475ad8982b55a0c8c07b6921de77c4e23478f2f00000000000000000000000000000000181d4cc9e77cc1e21145457948923cee50db145dde59520e6ddc2da13c3380188856c220cbace98f7ac4bcd7dcbfb1812810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b8936389000000000000000000000000000000001267b7c2a91132c46ec835a5c2ea1f1c1021449d4ab3c14355777f1b7771787ca8b72b61563dc7587db6318c2661551f000000000000000000000000000000000d9f7257977b3f207e889678b72b584b84bf736bc23081d1267145a886e2dd6b669bcfd8b58414def71c27cae868f39a175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b6460000000000000000000000000000000017c223749282ef77696136edd0b30041b7743e40c2cadf8b491c2dee0730554e39ecdce41e45d647340e73bfe77407d900000000000000000000000000000000025924e40885fe566166bd4c5de6e5bdb3ab993c154ce908afeded5614cbb0c00e6ddd648263f17ebb3d81bd6a4f79afe7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000730c41758d12795c7e5540e4204e43c75a01dc6263833f8db435117429ddff6cf4fbffd6cc27f553b8524710aee9ab000000000000000000000000000000000154c3ac230c725594a3c985b7ad71d98c172de8764926e74f6932f5a5d40543b5060c5d604877e3a8df093927b0b171c6aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea50000000000000000000000000000000005c6852bd3eb4db383e9aa8c74f4c158888ada1c9ba07ab8c7b4abe9c05bca51f0065a29a814892303a42a6f2736043800000000000000000000000000000000086d733e758dd4f0f911df6cae3d678dee3500a53d8a364986d88c50576ca6bdcd10fd31f3cebc7a35f43de1d90ee4bc985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000008445e5c464c4e10fb0a10c97023c5a9b169d042971597eff4380821e44430e3790683c7c66afb89921f06199c72c87f0000000000000000000000000000000017e55467ed664833131b82a2875e22fc5b29a3808639e90741b731d4efc0420b4934fc75ebc2048e8196be55a600f9bca3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010", "Expected": "0000000000000000000000000000000004f03dd7334a0dfbc7cd3b74a8e06ccf23fad0802e44c95712e920a2b227f09ac3d62429f40bef8241abe41575cc541c000000000000000000000000000000001092b20e8618beabaee18b4752a5b320307d22fea2125b3d23c6ad178f0cf2af7a1a1c0af3cfc669929087235e2f8960", "Name": "matter_g1_multiexp_61", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000121d2cecb2c9892d69e6a15561688edb5020dc39fba96eac835c0577ef017191572f8bba780a608c41d53544d24a306100000000000000000000000000000000080c59704a5ef9251654458bebe25d949bd5c7793c438a50019a9a7cf26036f014fa3f024edb767d233dc09710d53daa709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000012be549d6b4efbe6e8c17393390f3cf190abe4621a16e951203747dc7faf6d6ac831582fefaff20c952502fe43e2020000000000000000000000000000000003112e26ed614405376dc1af80b9f1984439c0b67863f5cee6d3c44f74f320e66574aa1501376cf8f924efd83655a72b9ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e0000000000000000000000000000000013b6249bc071ab2f9f048531e6bd27a1b8a45d34c66623268402bb37f6be2d71bb5127461221089ffead4a9f6c708f0200000000000000000000000000000000016a321e986c6301240b1e9258423bb8f38012ad533b42cb487384d9af63713d4b84c383ebd4512145b3e518e0c935b1391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000069ae7a90e9402d4f9f1b4a8a799fd5bec30002683692a700ac3a25f8f0a8ef9fa9e6f34844a6c320877f4b4883f36e7000000000000000000000000000000001214fee37b448c79b5c3097dfb65f8b181f16f0daf54861d4e5e7297db7981f2ea20622d12acfac04c066fcd23169f0294f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000006e8cf07f48627571ab5fd1a6f988723465ea3f741b71b9aa9156c50e13d5481d66f7fe4006a54cb283c6d43eecb4ff9000000000000000000000000000000000ade4e4a949e6dcb45cfedf2eeb91abe406cccbbc7b4c7804b77d04fd7cbd91fd44f0053196bb344fb8ac1ffa37c83d470f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000006b130d66b74b99a2048127c24899ea6ccb0a53c4404f36371f30fc1ea99d02853d4555385a9fa022a552b85422daa71000000000000000000000000000000001824d4d0eebb0178947adf316258d297698ef4575d8ebc2bd300558df914fea04f0269fe67205db1e3dbbae74c0db22bbd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c0000000000000000000000000000000012ba5b9c8a86cb99337a7c4955b1a1b459c8a1a7eb6ea908bf27d5f7e41d5f3423c1ff44b4615c689df14709c703e9ae0000000000000000000000000000000008627851a30e33fecf67dff807bfc5430a77d0a85f1e4f8b790b2b072fb7b86d5e81b934ce197fdac6aea60414a616541a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001051ddad286eaf9c9ae5b3757c53e324acfcb6a1a7d5b490eb9479e337c9824bf619167bf8f2aa5c7f175da534e91a10000000000000000000000000000000000754b16cc6cc813c5c4d44eb4488b04abb659d89cf0dae5fd5f59f257cb396e139443a99b71079c5aa10f8f48465fc398ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd00000000000000000000000000000000035732a9fc03435f3dc3e31af693b1d1ae79110cd46d07541a35b956b928cb4a2de2a16cb8295aa8e8d0c74556b8189a000000000000000000000000000000000d4e762f40fcf43635151631fd6238ab3e1dcf578dcc84d462dbfadcdb621be918f1f0a7015377b5ff9c182494ae149c26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000018f1eb31d3d4e915cd1e0cec33b4838da1401c6667d8ea25209e4c5683dce96b1d7adb4feae7fdb80144c30145d7f35c00000000000000000000000000000000050693e8b9c90d12af4ded25e05df86a3e233425e2f77c7ca9e99b0868eb8d9337186113b078f8083a4273c9411ac1dfc470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb38000000000000000000000000000000000e1ca58d3eb507f977257ed8bdff474a05dee19a00818754e3a85f1cec882b8e3e0296d5c3788b101da669a716772936000000000000000000000000000000000532526ecf42eb00da76db02ab6236dc51a346f0a1271f1e9d721a40a4569d46fdb63e0211f7986b98475d81998dbf8be53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c92690000000000000000000000000000000017bcd6bf54d51fa12356f3428f02ad8ca31131a77951459d32c554e2dc2487be1bb9f10450e5d1f38af3cc7de1096a9a000000000000000000000000000000000b7b5ffa4d08175916fcc542660c85063e8420987b2e16ed2ef9464adf928a4c0b8e6d5dc870b4f00de8bbec6f0dbae3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f6200000000000000000000000000000000151e2e32203b03a054459fe391ff4a4e962ba5e10ff93a1592043ad968c9f968a6e50b5943e50815268a4abe055a1a4a0000000000000000000000000000000004bd116c6857c2f4efa087272df160b765dfdbb842a342f9cd3e5cff006030f32e5a8b60acd8a376378096743000b2fe95f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000ab6c3210ca0b70b2b3bb916f31e17b8632513b15a99c7cc61cd21181152bfc6ba6ebaf8e96a05d0d2d42a9dd3b61a53000000000000000000000000000000001308a33fccdd6cc8990c21fe7ed03bca42e3ae24bf07aebfe6878c2c8316a7a52477c929fc7c67a3a13ed811a2adda7b740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b00000000000000000000000000000000010db7de8485e5504211088ada8924386b36b7dee37170f73469bc77212d56c3dce9802c7599c83c5cc5b18883cca5845000000000000000000000000000000000ae8d817daba71325b57f81301c17f401a6870a13506de2a443602ed44b6b0824e6cb763ef556908f9b3f30010f86394f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000000fc19f1ad220ef5bd76cdd7d3ca08539a97514bb21429af5b1774d4c58a7e4ae137505fc240dd0ec01d1a9eb06a157c0000000000000000000000000000000017ce712d74d68568a945fbe2e0b21c180c58e9297f1f4dbfb0775a133832d4d8aa0688f031385190324f1e8ed65bd5378390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9", "Expected": "0000000000000000000000000000000016cd97de498b8809f4de87bcd5a66c41c231653fdec9bc1ebae0ab3b8c56258430bb13bf400f5f971a1ec558ee66ef02000000000000000000000000000000000cf57701a5038ec26e4027c3cc5a8cc298c2d86e9425ae027dacea92d4d475130487b2743c64459990e88768d8b7a4e0", "Name": "matter_g1_multiexp_62", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016fb67277c28b5665f1b7aaeb1bd70f679b507a6b30f956a1fdc0d522e430cb4a9c089093cfd14714f25cc9498f89b610000000000000000000000000000000018ddf06c643bd77c953a0bde77e80e77334410d76910dfb587922e6dff23e821ebbee2dd546e65591726f9743defaf9a414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d0000000000000000000000000000000011d630f01000c6e1279f330893a18b903b7246031d7d05d80d4172b08e1da182594cd42934de3d1418445a76bc9c8189000000000000000000000000000000000c3e335aba4402bd3c711569e466293c15d89f4893ba91d8690e4eaf4c7962da458471e8c7f22c417abec313c2fd223399eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef942000000000000000000000000000000000c38e3a1c95f0faa10980976f83d85954813faea27c120fc3102de51096f6c3ce89fc4155c6fc878fbd18ffb32092d7800000000000000000000000000000000178d0c64b3b7da5b6f57c69bccbf73e329b18e29e9187a7af31b9b8e480b210dd36589540d77b3041472d9612b05693a49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee00000000000000000000000000000000146c5b46bb4194ec04b5b63f09e8066f24e350cc62fce016b8a25ae57877614162f2733a5df8909eeed2df30374004ba000000000000000000000000000000000cbb312823ea25bdbfc4afd00cb65748401b47ab7dbd5a40905162c1ea676268745af11a2770509eb74aad45663f7f5b6e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d000000000000000000000000000000000ef489c4443175873e33111e9ebb3140ca0796f13ce8d34b30d8fcb7b9130ea0574754e800fa0ad15d71c35a3584e11e0000000000000000000000000000000018bd8ba66d5b67537a03030f5ae56c01e640021ec2524a2cb4b2005ba267e737d27916dde1e94d1f15b6d3e1d480ad82d3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000182acbf5e02a0b1344779f7ced01961f418fa8ce94f939025110823e5d5116d771328362498324e1067a3419062341aa00000000000000000000000000000000174d3a7754b18715722a07ecff5ee3b7f30606c3c573770c88703b6e0abd9ff4aa4bd2879c4c0512f879af95554f47316de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000178c3a28f9333be85ff364329fe897660261092d9bddb36687cdbf5a7a450f27060a3aceaf45fb8acfd123116f195d8c0000000000000000000000000000000015e0a930af79ad263b115dc733560752cbc4453f111550fe3e9448b6818a75babbd0044b9b4f133bcbf16f8fb7586055a28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f0000000000000000000000000000000007fbd9b191af6a797c68ca85df2100b898e3a4d9569c717e3d02c259eb4dff3a1ea948e56001f33a3ee1c74eb966b6260000000000000000000000000000000003b892510d5073bc3597f8f513908077814a7efda2df6051c08f7347433703496e522d70ad4093f76a3e5288044ba5dca3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c0000000000000000000000000000000015d26d3ee6fc5f98584c206466d2c1a4323f597e0ad665b289e76184770e81856482c9f45ff8c891622d8de353b172e80000000000000000000000000000000017fe0582d363a30677bca1feb6d7f16be6b07d6e5d6b2a2080d07ca306d5cf733103f20403ceb486ec703277804e7971b90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d00000000000000000000000000000000108de390a69c6001124820072eb5d9ed9eb5b5a6199c33db1ab0239c447e009df4296f5324660e7ea1133df0c8e6a9de00000000000000000000000000000000040e7b3392a116c7289644f393bfb24d84b76d8378c042d86cb4af861af42374b709cb0ff5341e3ae9d21271c32c0a5914f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb5180000000000000000000000000000000001ed57bfdd0542efe8734b0af448c025eba4d60053b7b45baf682cd310f4c2ea07e708bccaed390c2b061c89c2855c9e000000000000000000000000000000001496190ccfc4bf428706ac344ed691fbcc7b9d6a456f2653f0da421a44653d4b1e9e967954b847a4e6014df15ef48719e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a100000000000000000000000000000000147e5056444c7ea97a319bc71a3ee4188f68b517b92c64f556d22382389c5bab95110728cbb7d525499cc3b2d70541b1000000000000000000000000000000000f05b91c8d05b31ef6497595ecee6a6766f03a006b4c2da408f4d7b7601915cef64be69735c269007fa23e5f91fb07148c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e038930000000000000000000000000000000011b1b7c28754f3dc8b21dc823fe02d617374bdb9b96dbca572eaf8897f98ce9409ce8a63eafcf5308d8236bc3c18b4960000000000000000000000000000000012360ef03ee4dbf0bad68232b8454a26b666d827bebac03da314b2631a45cd365248316f72e991004d0158f89ba5811839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc36520000000000000000000000000000000008b563f6f97fee7e2852b44d8e39ca314963b517116733924d2f57d9c4f202b47fb3fdb85fbca42ffedcee290050ef0f0000000000000000000000000000000016112f264c2b3c838b02b78822d27f6351860d10da3ccb763c1650420bf22755938cb45c7566a2df0e4aea4f0281262ac395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000a8397b009cac789cfd496f4f1237e92ae570f67b4bfe7e8c80171bb9d9cb53201c2ce112473b74646a4948d7c10c338000000000000000000000000000000000092b7425031fc7c328e3be114916a06305b62ffec8e7e93a591fc5f4f9022333cc664057ff6983677cfb998defe249553ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e1000000000000000000000000000000000df6739202d9f1f13145b697d5b78ccb84845710923a0f3bfd5a3f337e200b3ce5390aa185ddbbe8088462926a7f4b40000000000000000000000000000000000d00ec3648b2e5790ca7b05ff32c6bd3249296bd693f520f6d8385f15dbaa9f808d770f9ba28efdc4aa6bcf862c17c4abadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa", "Expected": "0000000000000000000000000000000002665808cfac4b9befb1f285c590d639132adf9d36a4fd460de0b3347303aa056a14780deaaa02072fbb08e1dea06b940000000000000000000000000000000001ef22acce32662085c53b2080df9354903f151089faffa43c5f6a1a306d2254fad240bb1ba3dba4927ea5640347bac4", "Name": "matter_g1_multiexp_63", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000194b906ed067bab0e26b9ff4c0ecd909c6aa23b5cab3a90d1761840b784bd2af6e9f9ca570ba6643d4781885553f3e4b000000000000000000000000000000000e8a480cf75e20cceb6e1d9db5594d19849aee6d45bb3ca7c0311bfbff8263420e0278b7e814088abb69e73bab6368a92c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000019ab570a48bf15ce6f007b528d7113cf423e1c04d9af9497dac47a69deaff52dc9fc4d202649fced07378b84fa1b0054000000000000000000000000000000000e3c2971aefe89a629600a243c7967ef001ee17f9ac452a8131ea44815ecb6596f4fca4f47a316f62234851dc485fc50b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c0000000000000000000000000000000001250315bb81e9ef7de73e709f18003018fc1c55f694c0e28152fdb244b07dd2d7812c3ecc4ba362fdda0707d02d697b00000000000000000000000000000000188a852c5850f471d4ed207d5782518f189cd08d63279c4cf19c76122df0e4663217f1cc8374c7a02d99bf6d59a80457ccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000000df12b5c659c17c808d8e875a1b9c125396cdc3d8a2bd6f99def15d9fdd1fc7fcbb309333cce1b778612d6114bba63b500000000000000000000000000000000019f11577152bcb0229e168a8e97804e8e00a58fc236c8ae59c575c07d6a3c1864b7c8132f245aeec55d999d54745cab99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000004b007a33b0ddefa5ca9379614f698dbdbbfc6bf8bedaa485dc360cc759ffa4ace304fc64071e8f228a8882d5bbdce22000000000000000000000000000000000927e9f018b8cbc2f21b72f0f19994705197d4b6ab3f03e231e51b9cb3d899fd8f8b71feadf3c9be61994936535c61e8338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d0000000000000000000000000000000005decc41dadef7dc4ebd8911af09974686531907e41dfa16c857fc3a2451b96069d06ce1159d47e6f1c97cdd932486d4000000000000000000000000000000000151d369a147cae5d78eaf7ed99623675491f20aa2cec9700053f853551208fa21e085962342072c96d79233bacc7adc1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a000000000000000000000000000000000fd41ed8d5b7e5ca6a6feae98592217dbe676accaf6e73062d9de9eced8af59563f7f441a50ffbb591b8a987c47988f80000000000000000000000000000000001199e002504726f2ce429cdb3da304f9b54a933c1937e8dc39a3a416d068cf46f411b207d9c6862a50962516b2867ea5202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000017cc9741662834dcee7af988d3e4de2c30d4f9e90f2b3f7ad07f756acc793c58acb2a04c2726129d0f0c959f1d3154650000000000000000000000000000000008052061afea4c307df56a72530effa73b34beea4d731b1562de1e985ef455d39b0d6c57008ec092241262dd611ec598a2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f97220000000000000000000000000000000019adb959f4807d3bf7e0616a8a3c02e9babc94b8ee9f8898f2ddbc8fed7a5bd88e83c70c5a98afa823a0f46560e32198000000000000000000000000000000001189adca458e0ef67fc686b5a94986be37c414cffcea5b4fd44430c8d5902512d84200007a93104048160ca3f5bbb9a8892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf0000000000000000000000000000000013d55a4b466ddafa04c5690628dc29deb0ae9115a4549767b2aa22b8aa02a13f1db82dc86fa3df85a6a15463fb0e7903000000000000000000000000000000001488a03340fadc9e8f7552273699870ad444ea513cc7bb91259ffa7cdd5e7377d8fb5510adc2502fb8124d7914af85d5fdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000a847c98ccbccdec67192529c3da593f1d6de5d7dc0bf4452e4f09e93c2c406d6eaea30431ba95568c92938150a00a05000000000000000000000000000000001201397edaef2f9b89dba7f67b22088cb954f95b9db3d1c11bd77aa0dc94def6283af2866a64f0028fdd87b587669f31660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000017f76412c8e679676eb464204348d591221ba17a1c90a22b2482991deee6b61edd7520ed10b0105426a15fa3282cbff000000000000000000000000000000000c65a821d170a9726e947868d861717e8cbcd2438e4d4b8ffcee38eaf033f8f3a57af68ab6314a52952a305db54ecb361ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af84090000000000000000000000000000000000b14fc1d34bd7d85fa96a4d12ee99a6d327347dc63608f94bd750e2096dcf11066e384ba3c68610c70dabae795e668c0000000000000000000000000000000004f3ac3e885cadfaa565b1ec15cb81e3fd4d561b2a8d92a9287bd0de893563676118d34a9ef3bb3112aa534605219feb2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000000979231339f20ffaa38ed21cfcef923fc9a4ff77f7d6fb4df212a530ff456a32f50a77d2e7f6d87c4a58270c006e68070000000000000000000000000000000011ff95871a91385ffeafd8a609a0c562bbeba71a110081e5db6c8035d8176067a528f4d1c6d7dad43b3bb8d090077e1357f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d000000000000000000000000000000000e2c7c67fd50bd2cc8ab18808a69d62bc2d3f110ef49a02259163f8fb152da6ca9cc771d1221d7719f9bc349e68594120000000000000000000000000000000008393769453eec7639d66525d6e875bbde7a4a28c434c82571468d496c4313e12414f929139c482569c003a6c0dccadf3017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f0000000000000000000000000000000012cfa8448935a292911ae6fc175f3049eae5e30d714b3439f55be9970ca959f218157097bf9837125bc8f772968b0d52000000000000000000000000000000001747193c5402daffffe4b1ba9034231321d01966befa174f526014d6c27fe3683eedefea8690b95c8f71fef1152929bd08bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd", "Expected": "000000000000000000000000000000000bedee9e836b3e046bba7fca7632d09a5a25fe1f0fd25cc6ae1d1e07d98ec52742a60bf346285488dc84b2360e0db58900000000000000000000000000000000071ef77988eea20a38fe33564689a39a7113b1715dddc1b212c6edab6bdea8de54089eb7b49b63296792bb2f4aa68733", "Name": "matter_g1_multiexp_64", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000b7db363585b0061a4707a2ee67d6d7220e9209b4eb9a59c02aa6e177c948057826780f292dbdd824d67ca9f78864cb000000000000000000000000000000000a31f49bfddb5c48730e1cd429f128a540ff44b6a5031e7975ec0c6661f9f3f2b79ccd2d13cc1b50d50ef9c7f658d412cc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d000000000000000000000000000000000d4fb266e9fb18590037394b18971cad5840bf89047dc11e52c90284642be7e27007c62a1e331a2f90ae67313efcbc0000000000000000000000000000000000047b518cd6a7d7c4d262d1f9f5f11480e30c626d45fee2d6caa274aa1353035a3c42ba96b5875de36442aa5d4b92d6d257b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000000cdc9bca5cc807710948d5189dfadca2cdfa6fca5496234f13024efd84a37070a2fd51a609c4ed6aab54f8687ac9700200000000000000000000000000000000011bc450e4222090603ccfaf7c1dee67bbd59aadafc3810d3aaa8362fe43f48952320e25bebef482c5d21a541400df5a03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000000015d20abf274edf0c9d45c2675e4af7987e98005b2a0d128ba7df6b16b88784a7134d37d0da2da02557f88d26de33f00000000000000000000000000000000190adb20cb0f5902f7e92f79dd6e7d214eb892834611ef222e9a80ade4c7cf96e0b5f9382b61715e1701c7e9cc4f4ba5976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f0000000000000000000000000000000017dcf175327086e058e4696d689f2e8a167aca5616f2317b7673850a2272fd5742b70eb362b37874d573cfefa25ce3ce000000000000000000000000000000000e5e1af08f6174641aaf4f1584ac40d53c393314dcb1c405263e8689558445196371e2858a4f44d605550fe0f15962223aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c830000000000000000000000000000000008456d980ecc64b04a203d61bdb78bad67b4568b2dd9a123634cefbd7f7077cd9a4c038c0aa3654915c12242dc594b37000000000000000000000000000000000adbd582b0a8ac28ab21961476e163255089c2d362bfe9daa7007a2c9d8d261593eab22a6bdaa9740da81efaa24cc3d5f9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000b02d326ecb5c04ccea4cc3d29f82117f3d97f788b8e70cbb346d43d27e674540c7a94d242d290e55d992eebac546c9b0000000000000000000000000000000013901f8dd68285d73093c30b37419ef8e4b28371474a040a2ea293f7274ec4d6ced0f32686405205324740884306e3a693be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000181bf2fe4bc67a1d10335a0ca9427f603610646de485a7cf039f0706c0a0858ea694db3b3e5ca85317c98b5cd75865420000000000000000000000000000000014b1b652e2ec7d05956705f692860b83713c5cc98c6532b3df50259f27f92d485e8df846883a4af4e46020ae54038d955d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd3000000000000000000000000000000001084f77ef23ac990b43363db38d652f0e6dc04a4bc395c8018083fae6fa6e42f463af7748d71f65b14f94632ca0eaaae0000000000000000000000000000000004ebfd75ecc9cea5e49082e1adacf6b50e4f14600d9343f6459900605c5f36ee51e95408a3005c0c1093e41794c282a0854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b2400000000000000000000000000000000062a062d2ccf5c131e1278a63e713ebcf8a221e429b52b3a7688f7e68a12558fd0f584e03835daa3988233d6a84010310000000000000000000000000000000013e9330d29635892fbe0742d1a8c96ef527b78ae389385a366b6dcc6a04b8cd1d5b8bbb79ea649179e78fc061d23cafd6f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000b131e0623b7f30bad70145cc4964257053f2ead992d28aa5b24c04bc316d479d077af0ff409cd395a86b808bd3e4f02000000000000000000000000000000000380fe6e79e5e0a399365d73244f2962facda8b7b381c111508819309ec5b1d3d8783067245dca26641a966969dcd0ab191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e20000000000000000000000000000000014971f46efae601309f3d16c15ab5c46ac48d2199431fd959cbf4efb768ebcc4f410fd66de04d3280659004a6b54e64700000000000000000000000000000000113e6438dd8088e73eed84d24ec286a45ca51f0fda88c7ae3f1e6a2195f6b11877e606773bb9a8db19dc92c3b0729754b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000006fceb59d8baea4a10aa9f1e825631e28bdd379189eb464a3c6d2482319a09337a78173f9207a58ce15bb1c518b39328000000000000000000000000000000001609e1ff34ad2e4bea4cfc4a993d8d52a1a8676679c91544ded432adfd7fdb5c016f8d825af1c6b8207170d05c10e04a7d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000069e48b113b822cdfc02f2f0efa02724193a5f032dea902b189290db91c6e4550fb33e2915eaa8e56ef329d6c61a0d95000000000000000000000000000000001426fa2fe7c160e8e32c3252383a7c7967b3515c3f76eeefaa5c76f02b3308d86ab95f9a3a0dfacfa6dc12eed2f3a5e8a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000000c173c6c949a7f21df4431025ce16c18b1008c75b8b1b23d03122c7c6ef714b5741804ec7aa5ac40f6b72a1a74ca5c340000000000000000000000000000000001b32d54f8f9839dc39e08bc6a5f0efc5db9bdf487a60004ee135c30efda577d187d9b9e68bdcdad558f2028d66e935cc0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc00000000000000000000000000000000037d1cbe4534b82ee79b2c957a6eb19d18dd3f3f6faf3313b0ce12a98953190aeb55f9d494bbac4f56ca6986c65f7668000000000000000000000000000000000734f505be94516149bcd6302a2c9f2f9b952c9e614c8e90b5466073a7e734ca203fcca242cb97abe1c532d7f59a783aaf833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94", "Expected": "0000000000000000000000000000000012de1cacd791ab81eb49d2e259f36abfd90e8c7ed4f9f23a4c95cb48cc7bf9df233951291342d1266d016a7fcc6b9d53000000000000000000000000000000001938d574d02b8802a45ddf8488340f2397e93125678400cfbd8e64cc3805704bd5f85f8fb2f64a96ec1089b03d9d1c24", "Name": "matter_g1_multiexp_65", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017387ec261c6dea7bbcaf4537182de1620adaa5842cf52c8b5b6cd851ca3c27abafa584547db7366455281d82d3f83ea000000000000000000000000000000000246dc1cc9773db7151e05d131398146b28850e97f6b13694d696be374095fb153b206723afcafddd4b3b56bb15bf778b16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000a909dad5029834df0202c298a577f897a376b205812d79e0bb58b91ca11262a766dc396f69fd2b199dbfa52670515ea0000000000000000000000000000000003737873dec25f011b24543071a61590646e4319a2128eed87d40193a22c47b1a6c0f807ba3115a7e45823e5a4bb433dcf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000002b959df6a1badcd306209c1f3c4c496cbfe4f00995cb4403b04ffa6b9f2c8dd9875a2747354a653a74fbb605eea50b00000000000000000000000000000000004d6b15939c8e282a5995c8c0b67fcba3171b35ecc039fcf32d1e96671698d8a9fd2cbcaa7019cfd01e56d68cac64fe51cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f421290000000000000000000000000000000006be65e97560a40394d9295fac0029a0c889bf803f09926359a1ac40deb7777cea7dc5d2c4a9600328605fb994f87b5600000000000000000000000000000000128249d2137f7ab1c5622a8eb1c59ee8ed792fe6b09e4d868c9d9ba900a8d28bde5b783ca591f79e1d729c99e10d5cf6d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf00000000000000000000000000000000002244c1e55324a4aeaa07c414cd3f9872290e729c1cf1c05a5b1de3443e12b2335cd36f0e84f11f04b62af37005ce0ad00000000000000000000000000000000151684aed084d38aba7127434ea73e63219c4f5b4b92017142d19d0330417fb2806e31440e0bb7c9fca2bc8dec73072f02e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000009f0c959d995af6cd0d45750cb35a28461d0f791e59b2975ba4edbd7db015858b41b3b7c5c2da0a4c6a5d7b4e855329d0000000000000000000000000000000012d495ae3096c2399149afd00f640f8840c3f8e5dda5835b62ef0dd8bb7303f522692efa72c37190bf6808ed3d4fe8e89b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e000000000000000000000000000000000334cef31670360b5ac7550b55cb03b770660ee79816a2742c059b2ed6cd9d5c53c5ca54793a9912ddd7603d975c3f58000000000000000000000000000000000144f221db562b0daefb20238a527a10ff1ccc279eda86723668f8ada40b41a2825f82f5ee5d619fb193b9c2b4180d932f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa0400000000000000000000000000000000037f14fb2d51b25cc04768d50fe26c1e156a3478b80e32da980f7e8d5692a4cf282f75e5d8be325ccb4223c7ec2c04af0000000000000000000000000000000004eaf2c069c96dcf18051a2c1d7ea876af67bf344070415894c07b3dd69330d8ca18e1313ff57d83b70e5cda3c9ea8582d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000104c1f5bdf874c91020d410d8fe74834cf15f341b86e66ac693003766484cffaef2c57fab5888f02f5ebfe1b9ef2fffd0000000000000000000000000000000014a2f6d185c2989ecbb766179c0b0d0713ea9714da2ac555bebf0522ff00766ea7e39c8237f8515224fd096d2b1ede34efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d80000000000000000000000000000000003ebae6a494d46ade2dc7d4630a420b519df7086b57a33da178616d4242fc20e4d02d38b5d00675d2cfdb51adc1921f6000000000000000000000000000000000edc56e6eb4aa8556225d928408702042d49cf3e1410e1c78d8ed5832ecae449d17c9d8f2a89ffbfaf01bfcc85ebc1669848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000000f96864832e7a9602196f0abba78f456300796d5afc18b0ff0c5c23b61865256fe5cfb960bcc8f73231c21b1084cf04000000000000000000000000000000000c59dcca2249b5b01c1b54be0e4114ae8228bc150e5ac7593bdf96136cd7cdd7562eb936ddc5c9e42bd93abe91bac5b0cbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000422c390e56fa27e3d7d5da1b2ef00a29d5340026becefc095d4cfe830208d3b94cbf5ae6f4506ae45d04764acc8044c000000000000000000000000000000000d1cc7c147cbedefa854fb9764352a9689fd157cb2540fe070ad7f6f3eaf761b4670ab9334de4002fa811aa7a01aaad479eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d6300000000000000000000000000000000018000e31f0ca43417865a1cc128f33383106f5bea71015e9e77cc5320cc3e5704e437ae8d84d96f2c4530c41bfad29b0000000000000000000000000000000011a74c3779c8f351d39db6745210972f4f299009afff643e944f30dbc4367e17271c688e1858e6f79b6636787fa56e6b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000002a6c7367526da989ae093350b7c1ee9013f977d6e75563f996e1f15cd4279932a3e4060a26262f27403966a7e0111f200000000000000000000000000000000038a85281b09e5e68d7e31bfc323c9c250b42248cbae47f9c018d72f3e69ec572779d7f8fc6ed3f027499741565274e5eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000b788a0d47da0daa1f0d802d340e68f9bdb5ddf91875732b4ae82f1a89ebb5787ec1c9f539b82e3c94c36a5df4ddb4ad0000000000000000000000000000000016f46ff55e9f1e19a332ba4ba43d66d2a11a2728a484a719ddfc9e223b54224db55af162e73a8f5c3355f0127a6b7cb652b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000379d868d91304b24e19694937402bb685f064ec5a89b49e243e2ab7eff5ca0a2023af9828c4ce9f768a1d6488c10e110000000000000000000000000000000011a9b9432ab253d47e8dff776c8b5810ecf7f7aae2ff36ce06b87436b4e20c22596c7713def3886549a36bb535a96fd1e83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384", "Expected": "0000000000000000000000000000000001a50ddc4d4542aae264dce69f5f5e93353a9eba30b177b33c2f74ef61149a301599beecc4d0f78078dbb9638eea526200000000000000000000000000000000160a2fd67df885232b06d3dead7ffca93cf0434a3e9a5412ed3b4c342819b44aad5385bf5ab60bc2b7191722d1372183", "Name": "matter_g1_multiexp_66", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008788a699276abcc2d8e4a35a9d0ddcbd8006a809799374ffd56ee8afa1a89461602d92fae6eba7fdd4045ba34d917e5000000000000000000000000000000000c8e03ca0da00c6829e2d7c49360e67e46ce12e0c99cb3d957119bd9c8bcac8e03cf32ec71db2a18568157f4b44cd4dca4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b0000000000000000000000000000000001373b4a0653f48c205b36bc50541a43abfcf35974a584953bbc40f5cabdc3ac2047bb86267cdad1e8f00766682d2e6f000000000000000000000000000000000faa8c977b4db7a3c9e65d9cd5af4ffd2d7d67fb038d92c1096124312a98d94e6dc3f3b8de73eeb057cdeec4bc0e0482bd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000012dddf5b96d0dfd2fd619b634b086ba5d5f25a53e93938559a7adef7b988749ca27d14f2ddbf5a9e7e6c1914403a45b900000000000000000000000000000000044b5c8041fa805cf2ec5a243814308369e5af534729cc9608fd17583a48132809f507cdb5b76fd6597fcababa865ddaefbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000017c5807458fbb875593ebfe83c49ac2493ddaa15671a59032528e0464360c64bf564f9727959108940ccbdb8d01f329e00000000000000000000000000000000121dcb798111976daed483f4efc95f968f5212cdfaaf0497eab0419a1b55c7ee4e2ea26716d0c1a8aded4804228b8ad860d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000717296a20594f940a05ed3ce4bf2b7779c428b33a297087e08b2283b33228a7d4d5b9c49a71ce036d6f2a078d8344540000000000000000000000000000000000fc78f64a461fb66ca081ff4d67369058e57e5ae0e284562161fc3244bae0b9c70ea6abb2d0da6cea4942530c64ea0e386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee8000000000000000000000000000000000584bbc0c537e7f37ee64604a134d5fc21d838c51a89c608ff9e3684357ed7f931fbd4fa4a5a56d20304d6f6f072316600000000000000000000000000000000191ea3bf1016b6402dca2856845017dc49c74d06bc3c5f10de379e04302c469015f205cfc97fa142727ba7e2439c15575ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d000000000000000000000000000000000f1ff007860ac58bb04d992d639a5f882c3c647e76e2d6d96888a55648f81ad8b7edb3dc2b0e56b6f2dadca73db7cbda000000000000000000000000000000000fbb952eff64505e02e0ab34875d7a79c72ab724cea7cd8f28df2578b50f78601b9a9eb4170e1b7e8d94d9db252e23c592837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c00000000000000000000000000000000011dad65f38b4c24527ce87f8893c8331a32a3d058cddcdec9f8708a3bd1e31871cbdcf944ec14d5f101b8d138b2a46c0000000000000000000000000000000012a6981c5100177e643dc421c5917896455107c5995b1e969bb18b4b2752700a18281f732530af9684db180290dcb138127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000bb4dccab7abf3f5393a338a3a07fc20d337ba2ec3b33227e8c9a832900f347d582d88cab123dab489daf471191538b20000000000000000000000000000000008589985e2952db000968a793cc0fb5bd1764ab1ecdc6f278a11dd4a1de87823016e14e9fdd682e6c489192b154cb997ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac6148900000000000000000000000000000000056fd39f2a5356870a3ebfedf35769710c16b2f2eb4a061c936f6de4f9001990769795b1c756d7c67623ce3931ea1b5a000000000000000000000000000000000b7fcba295d34fc38739c4b36689653731fa46e6029bf8e38ccb6af5ae08ffc09c86abe0de62230844a66cbde876f52663da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f000000000000000000000000000000000039b08e7110b0d17c41709378f75844379c662f7f3dc480bead6bd4996de2d8889f458aabca142d50ba0e34c0c327970000000000000000000000000000000013363b0da7c7dd343ffcf6cc5e9ddb5b51480b04a472c38f90ee08cc97507f5dd665e15a160860c6df4dfec154c1504bef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e00000000000000000000000000000000034edf693e1b201be14c496860d508d12d9180b62cf3bd2407b8ff95b93da67dc0c4c43344614dfed516d7828ffda4b20000000000000000000000000000000015246f388664b1d817fd17831f85d84cdaf31212f093820835f201c3fe6ac99d67cdcfdda3c2d74d75d5114e32c65cd7bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f000000000000000000000000000000000982b8886abbfe18cfaf4c0e16c2e7045973f5efa27e5cdb56443a22f5434e2456cad041bba3e6deafb072e5fc40f10f0000000000000000000000000000000016a45f684caf0eec143cf8f31ed5111750d8c4f1092651a471cb88cf534e81df117e3b0e8238270d3b03aeedf04d7a9f70363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a0000000000000000000000000000000016d13da2900e2b2ef8f6ae295bf16d100d451ac4709455c55323988c71ea6aef694de0fa5a33cdd7fa2512d3548e39a70000000000000000000000000000000005795677001cab950d1a7b802bb14f9203036f15fb335daa5f0b0ece4bcfcd3b31b581b439da46452e4e688f16685e37997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e88000000000000000000000000000000000b7d422ac85798cb5ef5548805bd6d3de20ada4994fc38355e92cbf0d0c9da356a5e9e1674a50a017643f652f71226e8000000000000000000000000000000001715616f53a501acbaaec470121caac29827b6b7bfd7e689d8e48822d2c464ae50158662e69c1c232ecd09f5ec946a7a5ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000010e7530ba600fa531878ad0f798a0ede2d025f149ca980bcdbb0e4316e8d2e7d2b248619369e36d21dfd766aba5918070000000000000000000000000000000000ecfa746f1cadbed34fc1ee3483307de400ded69af4a7dbb598802b7908495519b0cd4c1fa98c9cd8e82daf8b3e836e03c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26", "Expected": "0000000000000000000000000000000016bbc2dfb25ff6cb9cd03b0cc91f53c6ed9be933746d59e78c7251080856cf47c12bbecda442840675b03a812aaa5d98000000000000000000000000000000000555b0683640ef1795c918f5d10e8b3e07a09030fbf26eb34bebfbeac34e13c93ecf53d54dec5c8654cdedab2fd60570", "Name": "matter_g1_multiexp_67", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f08b9765910d97ac42bc31d4b4c8bad5f3db3fe5374f11ae1c08af41ee226bbb4b0869b039fa81a935025de11b1d1fe000000000000000000000000000000000ea29999ba91652e2e6dfdf77595b44da8e5cddf2e3ae6c782dbf1f972717833d03478bb8651bc0cc7946d813371aaba2849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd000000000000000000000000000000000997b39892bfe0c67c296135573975801ddb99d06de02d96853f44336fdaa25dcfe253708583f415d882115ec68dbaed000000000000000000000000000000000a88e2f75817ce91c7dbe365d67aca52186b5e94c735e5893bef6aabc61f015f854f9bd110d3201be6f35147f9f9b8fce6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba6990000000000000000000000000000000012a730eaf214a874448e654a06604c4b9218f163b979bd3700b7a7fa3856b814c380532afce59b6253344da5bffd684600000000000000000000000000000000182fb293f9a63c705501aa0ec7ca72698d7d4d50af3a0f68ee849cd3f82ac24aca2e2ee813f68e708991a97e58f2d03d8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b0000000000000000000000000000000015bbe08935721cc6199f9255379589a4512c178bbedf69c82a0d9cba22b285730d4f27a3629d92574b2c24dbe09300de0000000000000000000000000000000007aba01238f2c4ef0192fea78fbccf2e669f802a2822baf067632daadbc1d07e70095c14bae959a0f706092b0be10335c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e40000000000000000000000000000000011727d6d6cff667f5bdec92a3b502f9d9fbcded2ef12ac058ac51ccb4064443b7a2671e9ffa2fefd9b121d89bb4ded1e000000000000000000000000000000000960f8ac1e52246529fcc6f8f7cbaf42677297c00022d312e0deb5fc45d3685bb33fd68c193758258439864ba4a073e5650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd50000000000000000000000000000000014b3a9296c87b54f8f51b935a8d9ec0af44d711e3109e75fe34f07d0705e9ebf0ba5e81dd8b7e3c4b4f862570637a7f80000000000000000000000000000000005b834857b8629cdbf514e5ac2e0e2a45e4374c287bab5e4c163d669e7b1a36c72cec1ab7d857e28f2633a6e5f298f55c067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ad8c626ba39823a33d17a4f06cf17d29e9e0ae3f28db0b369fa0bb4b7343115fb3ded39862381822c3b2d74ab7f70e800000000000000000000000000000000117230d8da035f40c181b50c12370f159748955f63ee1eb61e8242e476575e9aaf16bd43b7e79a35ab4e2da20f43fd92b448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd00000000000000000000000000000000027764a17af5328811b305c21b0fecc54a3f225eaaedbf453ea4c0724fdbd481873d84b1a7ffbdc7f1cb07c2d1efaf5c000000000000000000000000000000001090ec8d750ceecf682de76d4794f9a8bbbf3a3f4ab591fe882613c1b6db0912696974a1f2ce349bd8c79acb4891719d441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee350000000000000000000000000000000015e707430eae84b75946f21e1fb0b6ede203b843671911923efd9674421a92ff13cd900bee1b27d70b8e8cbeccb165930000000000000000000000000000000001263ed28f531d8197606a038d7d7c3e1d732690cd69f52533470f6fbef193be5e63d5af0dee3aa8a73a23253533f8223020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c0000000000000000000000000000000007fe70102db7df6529f732b5cc2b1caef0fe03af9824a5097922dc0b07e5ff32bc195fbdfd7b5e4b2bbcd75b1badc6ef0000000000000000000000000000000011b40afd78bb5e835227e5a08f94f7c70b06dc010f5a710a025f589521543eaff27d789d4de10fd4020879b45bc0a9dc82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000e4df86963d375710c681c5b3910fe79446e73e00613bd554ee20f47fa9e2b0cfb6c14a29ed6dab0a56c49708fc624d80000000000000000000000000000000010029bbd62162cbca140c56354ea070ae3f1028e438c70dce31e7bc8691541e59e9168e9b689c19d177d4fd68f8b1081338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b00000000000000000000000000000000097f3f61b164193da313d88429a4f34b0ef2f864ad8fdf7183c3e1da02dcbf0ddeba9bc04a7594516e6255ed59527e110000000000000000000000000000000008133f297b8da5dac5e1ac3db3073587b92a5d821949968c125e5c9c79a19b5945ab47fb0ce5d6f4269231596b157826d29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee7502100000000000000000000000000000000028cacc78001b805c3e43e92fb8c4477778ce81fca9068240e0088e344cc8201ed5bba52e7ee09d5ea6f982f30d6ea2e0000000000000000000000000000000012c5db0995324657574a27c48313674d2ad3aa931cee78ade96408c5e04e6f5f8eae88018511ff156bcc787970ec40ab675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb25000000000000000000000000000000001768f68b0ec15fdd37c3ad9445e53a582ab5546f9eeec590b84e11f5a72585eada71129d1b93a72b334bec4df57ea4c40000000000000000000000000000000004d6e137e66243b56bbaaac98717061b36545c1c3e24801e6e054bdaaa6d28d641821a51233175f5e5823b7d2b7b42cc544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff70000000000000000000000000000000008caba9658e420fa17950c995efd00447bd5074af9b57122240d4e709229d382e371d7de867005745a35a2a7d68fee8200000000000000000000000000000000072e0c25435616f157284b48fc8da4a3fdaefc4f6d484e071cbe648fedf30b5da4457852d7715741615317e21110d4c2bb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a0000000000000000000000000000000016c418a3b3f054188d6891ddadb19c00ec629a3ae0f49cb1b6801a9db0afb1b5e473c75cc8e9f352adf7ce8ac738ae0100000000000000000000000000000000110b8099a39e40541dab01e10314a0cc10fd2277c8766c7c73d32d7d0c6edd3ed3984c8bce249de4776920dfa28ee86994a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc", "Expected": "0000000000000000000000000000000011104fff0cde20e65e73185321d26defcbce9c9d3c27923a2366730c1b560da80066734c61e0c42bac1da45787f39e2500000000000000000000000000000000066efa830182669715da4dbafc6434be27a2e95140cb39738fa75cfba2b5786d27fd727df4e9b0195b49be2dcb9c2c1c", "Name": "matter_g1_multiexp_68", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000188c13fde41e3c7d84ef3b5d1fa869dff4bf02cc8448ae49c6b72cc005bd06916a5d0a74fd770bbdd3d2c58839840095000000000000000000000000000000001637ed432b4ac6b5021aac0c9d5f084e1f6c541c101a3d650861f7d860572795f04e986c4a890ea0ec049da7c6025fa3f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c27700000000000000000000000000000000063a1afe2f64f1d04f7a5aa727cbd0e9dd9b66234120118db1f8fc3b90ae50cf493c3c4a48949441cc1e46488972d39e00000000000000000000000000000000049261c42dea531a6e8fd82f77605ad0cc9addb23e429f03f1aaf2fb8d9dddaa89101bd5b5b169dce793de9bcafc3b5ddcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec0880000000000000000000000000000000012ecb0f3bb6fbb4802479611a25781ab09c81ff7175170805ebadbc5f25d2c40bcaca855ece57f481160d49af008d2b3000000000000000000000000000000000bc4bccd65e010b69676d3c226057528dbe08271d65f83a918b06969c1d5303cb7383645fc19548eadb83649ecc54a551d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c67000000000000000000000000000000000e3dfb86c2eefe0b25f117484a9d693702496124fd0dda80830a4e917bc418a793519dc269fd4932236f73506ecc949300000000000000000000000000000000140faa4b38ace6e80e5d3fdd57079c215792672ce651563eb013a90e66665dccf6bfc9f9df145d34894e3972eb524f86310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a160000000000000000000000000000000016e70554f8580b8e9c5e421c6a6495df7df846ad67d5d4334e9aa89f7e3fae505a2d335d21624e66aa542dccf38081e0000000000000000000000000000000001090383d5f42c056c291a4c4c6127315849c647783a556aba3dc41c52545549d67560bdd697fd1f47dace750483ec9b72fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000e8ba15ec58e5de08935384a3674418942311beff3887d7b5b81da0d03348791e4b17a06397e33e988ac6719f4d6f5c300000000000000000000000000000000159841665c915844ed85abdec0c1e78f178df2511da4d3be989f27063a8e572fe746b20e3aff056a63f4832d82a7cc75aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef00000000000000000000000000000000167848a43b68c8f4c205613e1440f940735d7d44eb1b046e63ce50fe8d7acc5b2c020fa936d6e07347a7858be57870e5000000000000000000000000000000000aed7f9b7108aa4e7445be41bba256667ce7587a867b9b8ca70d3c42155521ea3bebbfe01bab038969721364eb758be10227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa8000000000000000000000000000000000699a81c47bcab8342b11a207af072cededbadd374aa79f6b401e4bd5d429a0443234522a8955b3a62a21ef6697410270000000000000000000000000000000008ec25a0e0dc6a3c8906a1b3413f522440d56f67fb780545fa022026c6faae016108cb6eb23d6d6d519a4aa790327ae6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc0000000000000000000000000000000009c2158ea44c3b590df30e15f97ebda263670c1bba0d97ceda7ea674af0e61f0b5928fe0bdcd8f18efe5340525259b4c0000000000000000000000000000000019a5534906413fdacde78ffb03e6564d8beaa155f86e4f19be2188854a8709e82d2ade21621934c1aef8be723ea91a141a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec000000000000000000000000000000000165b15830a84e786d563cc3c5117a3e7dbe9dda178bafd225503467ea4c9aa894294c4fda58734eba9864796974a016000000000000000000000000000000001285a2be50f38fa6a068b75386d468d8fc1c11405291e794d5aa5157cc81d7d66c1095f2fd9289f1306f74596e9b5c21b206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000019d5819c1c4f10c83ca6f1596e6cf9901611c1407d6d7abab989333b37a8c21cc3deb039722a51e2dec161c38f3ce74200000000000000000000000000000000136d05ff33253260cbbfea0390e78cf66845afb4ddd0b684b928da017fbdf6b0e840431064e6e6d5bc8e417a74c811ab3a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e000000000000000000000000000000000593e66a323cf3efa13fe19cde7a3c254c90b23bc836e1f437f4a4b85790f325f0746147aeb1d0447022bb138178bff50000000000000000000000000000000011a4b1222d0b49a27e66cd34a12f252296ecd1aeda435035f06c059aa3e6ba69acd1ae6d7da394f32ab78538f4e50a351231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa000000000000000000000000000000000613bef17f6b6b39f9f6bde785a82d2e4c368ef231d8cb89940059ac2c16bdd707170b660c0faef9e927ff7a72f6712e000000000000000000000000000000000fc85913ebe30f0af146df556c6984ab442b286fa70ee00d39a802f4c76c3e41cee68802982ea42fb25d4bb04593c0b5393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000003da5997b7b3677f6cb03fe969e328549b1c0b083a6df457a70f1276d10e01d65feaa5a36cfad19dbe41cad9eba2fe73000000000000000000000000000000000345176bf6a03a49ae0b6d89d07548ed47dd67dd620e5e29066d09a00a7e3bd4b7fcb79b114a046dcc0c705068f71b50d412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000105ed7acf8c7c116842dc159553499aac7b8beb36dfd7eb717c571ad4ee1f86b82b736b72c2936925afdc3c739e0ad56000000000000000000000000000000000618b8fbf8a2aa2d1030c6304655b1df3cf8e8260b7b2d97639bd857d58606d0eceff7ff0fc1a811396552719407daee5b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f1000000000000000000000000000000000eabffb8ece92d4b22ee47560984b3efc33913953dcdf5e22771bb8db2cd8eceea21a2b14d70b1d467d692371ff499a300000000000000000000000000000000143282a2cc502f477be295d5fb2ec847cc988e43f72be848464eb4c1dcd0b1ab66a6cc30dd4b465050f6c37e8b8e08a7ab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5", "Expected": "000000000000000000000000000000000378217eb65cf5ff48e40ebbfbf03c307aabb63955f755a93bfbea0c176e90dc986a8202c6ea4e9ecea176068cfc62440000000000000000000000000000000012ac5e1a0c75ac5b96af3959a65eed134dac6b35559cd923facd2062ee06cb3ae2018db7d27dad2927b3e9c2e3dada33", "Name": "matter_g1_multiexp_69", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d6ab2022d950cd2ad2f0087a079e435634a1e24008d12a349836cb7297defe857cadf3adf061e8b55ece662dd36ca280000000000000000000000000000000007682f1ced1ac2aca6ee9de682c7a6743fd32264eb0a087eb1df7c520c5748cd598be45213b398b073dccbb6bd67b44c3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e000000000000000000000000000000000927b5590892a4b897ff2d6ef6d5abe32bec8233bc5f35ea9ace2ec516037a8f3d162b0161c91d4e06d80d73528a6ba400000000000000000000000000000000064d3d8340eea43bb2d54dd6f5d9d49fc2275ca1ae7212329a11ed9a94c70c80584cb6ccc1eb653f001a1c1c4306e702b3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000322791d0e53364128288e40b621e6c47324dafcf86e9a8590a79eddc8d3e6c9d74cf9721115550e7e33868ced39cc4700000000000000000000000000000000112a246f82756d88f30e74b3f5df21e18ffc9cccd713e6509572338ccb4f52cbc0c3a6d5b5c112e304f90ffb9179238521953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b8250000000000000000000000000000000010bdece8fbaa604439e942e2c78aa5904cc1a0532d5bbf624794d3f10f4b64df30838799e374982feaa7346c039c08ad000000000000000000000000000000001085372e79e1046c870b1d49a2a8ea83bcddd6bb8718c7cb340dd3032739319c54eb947d518c7e17d6e603dd3539f269505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000001551cb2abe299a01cfba81bb306457b662ad57858a30d55e0ae0c0f5851483123c388ba06ead8ec4fad0b1e4f69ddd6b00000000000000000000000000000000159e5ffc459d38a6b1e49b30647939f37c0d4fc02b83f9dbac123d64535752977005e0cb1232ebaa7cf0bfdc203ccaeac4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f0000000000000000000000000000000004111e81afa9fd09e39df891cbb99d9b62205777bebee33b2914e24570db46f75db5dbe2e9831c50f9717dc317f05ceb000000000000000000000000000000000a999eb350750cd505ea9de43945cfb0c9c4ea412cb0f0e769e62e47d08f8d50392d3a5e821f1e9c947990e6398b5ec699762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb000000000000000000000000000000001496d3b0062e9e7166d777d90553545ee7dfdbdacb355fa7ecfecd65bcb96321aec0fd835b32c8bce462c87a2b52a58f000000000000000000000000000000000ef77e6ddce1e0eae50a1c663374c31a0c5846d6c2d777bb2f4831ecc806ac28591c3ab0222a6cc7821a45ddde1ce23e298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000194bd82f02047bc08871e431ebde41327a60e838d3a1ce6eb5470ba21a9b863025c8663f7d509a73847ed41515fdd3ac0000000000000000000000000000000006c9303814ddedc68b0047b5b2f0333cf226908dcb14ccc0aae4e14456a0c83eb4f498d559a649bb64bc78900a788a4b177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e573000000000000000000000000000000000736fc761eca44cd197ec6fc680de349f96e5294e42648825ce9262fef91766a8d7a084e5b598b5b47d947548e0c61860000000000000000000000000000000018eedf050da521b9af0ce2007cd664e2760320056e14ddb162db5cae78ed7ec859bad03fc60caa06081f0c24bb130ea4cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a990000000000000000000000000000000018a8e4887f0c08dfb7a741858580a1e0ba7e7ee1959284ad0955beb186e84a5d503ffe4000d5a8641575540b6b7a3885000000000000000000000000000000001946ae0b124fb60fb4dd32181783564dfb8ed0616a220d5650fcc1f6968ff70dc74535c71b0cf1019eb038c19cef0caae03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f5030000000000000000000000000000000003cc2093935fcacc3fbe4429868c7b31fe8c8b12c1184e2181dc8da4d56b9b3ace85ad8d6b850deccd047eb002acc8fe0000000000000000000000000000000008cebb95902576d96a3a257ccfe76bc727174e08d70492dbc2132b9d5f534de3b6a7baac2d90338278064565aa67b22c804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f10000000000000000000000000000000003a03e6c183afe6aae9bee030f46086032e9d81fe337e7e1c77ac6c903fb33154bebdc15e81422f057ba1853c1f7cf110000000000000000000000000000000011f5e4fff35ad1d6e2d2d4e30ddeac28432eaf13fc7c35f5a90f7f8a17de0f61bee21529b3db3633c178006f5c5fc403ed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db000000000000000000000000000000001986f950d86f35d45dfeba6c3e484a6da296ccda2314d03adc37bdaaab374aa9011e07e6c8fe056e66b9204c5e16fc990000000000000000000000000000000003220ebcac8189b30f6efe6051a2be1001b85a7f94d9ce289bf6e04edfdf2ff17b17702a1ce116445d763ed1c0dee645e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd50640000000000000000000000000000000001000e0934c04c36c621d9b308565cc75ff58f6c1c778b8e0926b4d22d58025edf8a853139667ab3d3616c33d8a98afd0000000000000000000000000000000008776b843fa3b1449a0879616b3a37bd5eff5c809c077fb0274fccd67d645439a79a410fe2c2db44f52887ea7f20c6062f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000e7b037fccbb3fed299960355ff2c6a51562814ac797ed6b4b770ec565bae5ac998eeba19819cf2b3d4e91591e7f051f000000000000000000000000000000000143dd07288b59a279de228ea59aecfba3275a87fd8307252e6b5d567bde87088a8a8f52da57cba4c0fa0e2aed423241e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d730537000000000000000000000000000000000b41b673bab477cdb21ae5f1c04922f2b8216d7a1423a6f6b86d4c33f0b4def9c553faca2798cba20a31ee7d71422b21000000000000000000000000000000000b64686b90964104f8e79bf9527f452d25c3c8e9d53e715d884e795d26e391dbf510d72fb2850fe66e35d31444814e650cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5", "Expected": "000000000000000000000000000000001673148063c4f68c59d371410a8ef20cc4516c579523e2d7d2ba04b03fc5a481a30fdc6b9ebaee1138b28a9463011227000000000000000000000000000000000067e9b23610ac41d41e0bfcabc455342360d4ae4c75fa0303631e97f702b9cb660b84b5d422e8e9a74890b803c944ea", "Name": "matter_g1_multiexp_70", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014cf7c57711c1708096cd33a9efd4f907112a3d4e5bad1767ddc6fb408cb7ac3f866143000154d1270c07b4294480543000000000000000000000000000000000a20191e6786d94721067d6942731110df277047541383ef9847fed9e4b8599723fd7cd7e2ca2186d56986feb8dd24d72063b046a71c2674e35466657a85d8e02253b42517b033619e31a53665917212000000000000000000000000000000000cdb0c20ac2c22a458d2370662d665005cdd8c662e318bb8652a2123f2d65d21c8e150daf51d7874c69bc039bb6163710000000000000000000000000000000008480687d726eefe93d5484ca375557e109fc64f60666e1b8aaf440100aa15e76aab6f821fde170046d2714d8986a1fe92fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000011f20086905f64c21bec021e5726c05158f892658cd69536945a3337a8075994caf4fa16fe66b85e3e0ec71ae5b4c09c0000000000000000000000000000000006d71057aeaf26fc685bfc0ca071126a81224692b3eb90e37a1941782b8f65d45b6a31567c6e3d2935d38e9e02ba08654484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df80000000000000000000000000000000005a186d0ceb2535037b22a6455c49b6227e54c6e6dcdd98f46d996f23301b208a87c4bcd0608972961b67c523f01c99100000000000000000000000000000000142367fb02fc6b2cf52a78e4cb1157d273e9fe13ca721e0fa725f2a6dd0b4897ffe7affa25925da47fe851362700c31bfae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000a758981a1524501c48ffc9990b738d51ebe38a0ba07b2b049110c7aa439253bfb0491a66cc42eb241a47d5e963db75500000000000000000000000000000000082adfa66bb46b97f14dec70b970469478d73d30216201e7467a927ae4ab9d93747b07ea69c406dfef789226afc4240a3168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a2000000000000000000000000000000000de8dddf04e0c2d9ef1887ea598030f2bf3bc7bd98b8b218d19f661ec4c9a47cb087639f72fbe97afe9617acb162bd1a00000000000000000000000000000000127e78f1f41df717e5f76692b9ecf21ec0fbaf9b1d56e51b37cea02143f3b91eb1f16a65046527339dc65d29435a2874f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda0000000000000000000000000000000009c37c64ffe9bbf264c475076ccbe6638653574ea84b30f4eb2601f1990f73fb5708af6007f21e4dd52f23ef5041cb3600000000000000000000000000000000170177e891c421ac91eac0dfff8bb397d7fc531e0fbd275c17cb4d894d18278a40a6c3093b92fc537244798f24eea4e92d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d000000000000000000000000000000000178d45abe2415895e0a550005c76522962c0ef0193cc7475a52f4d9cec9d4789406b7afa2872485722ec034df4446d90000000000000000000000000000000005e4253dde4284944b2083e07b04940cc72cb24d9866c953564bc0e847b72da59888e7a08cde7aa7c0753cda94a6e97c53bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000e48f11dee27507acd407ce1b810cfa8d0ff4414380fe26aba6c608784ef756d605c8c3ba92592ce342baef8aa927bd90000000000000000000000000000000000e604525ab4ed10f3a9a688774c6b27e679fe456190e67689959da296b650dbfb75610dcf54b30ab891c40784a9b90ff944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e43100000000000000000000000000000000199b1367bc3aec710e82f98d3564debe9e01ef2beb878935df4ea98e3725391e873d2661e2a27d778bd29ce6f66a9b24000000000000000000000000000000000e77a3ca6bc4584cc1c3df35b18402b75936f68f0f70193708da21649b6def59f1baec4d6d1a2733c369cb5d9a6b39347de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e10000000000000000000000000000000016ee4a1a3f99134ef55398e96b86a21708388c3ddbd86746745e24bafb062a6283c5bdd771f15eb501df6a19920162d4000000000000000000000000000000001001936f457d8241a4929aec1d3769bd1955433b340481936f9443c63a6c6ddb3be4f4e1ffbf62a5c4b154fa9f8acba3b0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000019aad23ac037d496eeedaeac9248842b0dec15478f62ab61d000a402cbdcc240186248ed931fe3eaae5a1d7153d3e135000000000000000000000000000000000fc1c74c4d8488edd92b42ca7c27e22a4776761829b06efb0d1b2cfa37738efb276cc5121d926665b99497841afcbd394aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f88990000000000000000000000000000000014c557c44a90fa9d958d2e701cb2aac1c0204246fae4ba7b060e74e5d4ff50630fdca918c47323f5d0eff118c7595a040000000000000000000000000000000015821312dfed1e0bc2cfb23536baceb7ceb45c6c5a5f15ce0d4d67ef261a30ab8154b873513e2c44f652b93989cb6f1badde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca00000000000000000000000000000000001ce81da6511eae9d2e155efb4f999a5d75faa99eed8fe784c7a398bf4b0e135bd0e8be8d9dfa2aa8ce9c63e091cb44b000000000000000000000000000000000695ff4e598b9e469bc62dffa214418536a6f49fa5f05680e09783b2f29bbfec5d43d42c969ad3b62c25c6192e328419b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000002f155e83bcd838ee8840996a3d8b0bef77334b0e8e75c8e4278411ae1012bae06959e8394dc4d1fd4ed5f07804b41870000000000000000000000000000000004daf1423e319b18dc57753d39777bb127b651f5294fe03a15dc4974eef8cffe337704c7f867fcb4c2fbac382e444a2b0cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e00000000000000000000000000000000000162f530647fc6290626d74753efe315e64dca2d73571dbd4416dbb41b07e8ddba40b3dbe170922c64fabbd937c961b1400000000000000000000000000000000021ac62abe15b0f1318063428d89f22d2090050b913973de571871125a391affb1cd595f9c596c9dbeb6025fc8392e48e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7", "Expected": "00000000000000000000000000000000127c2a1365d966520de7aef12e66e55d807b1a3e2305ccd5a78547fad6e6112d60a206412bf56866ca1b0af1b422c7230000000000000000000000000000000003a613a2e2abca1f42d3ed594a3b99a6cc51207b249aee2b5829aafb1327cc7bbf99604473b4048c4b2033e1befbf14a", "Name": "matter_g1_multiexp_71", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fd31933662cde0814cea424436ddeb6a668e20b69259424a652bab722aac70b3582cb641d53bff963ead87ef5dfe1090000000000000000000000000000000007d17925b0309fd8c92e52c1ad67937efffa7ae3c783177a82f1133c8e3aee2b8fe71095b6b88b01576c5203d7dc8c3f75888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001782f625bc3b25168b1f5b388b7963b9d158c09abbc0bc4c0bf5332e1817fc083d3d55124532fee2665c536b6923fe3b00000000000000000000000000000000118650bcb2d32f4e83257cfebbe8209c2c9062ab0eb805ae2977f79ef48af6fd78e7512b331933edd087054273eab52c18ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c4950430000000000000000000000000000000014a69e56a173ed13a9e2568a8af49d74c74dd67609ca58744f96f9213197b45de6468d69ed084ed8b1b29104322ac517000000000000000000000000000000000739671cdbdf98251ed4bf13d23c675500cb66344731ea6aa66ffe401dd6daa8157676fc46b413378b8325ed4cfe804a24a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005c95d722f8e50603951c21421e8532eae710929e976d76f28c050fb2b093618489c5f892198ca822d3f287fea6eb83200000000000000000000000000000000077a07fe1348e4b6b2a46f444137eb86bf7c58e320afda3d75769a9839fefd9142cfcb75da1d1aa0e7ce84b880ff1b3fe4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a135000000000000000000000000000000000efd66388da0825c846b6437b13ce5014b94b20cd3a713bdbb41a80892820ea7b12b6f6720fc7aa6e6756d496ef5ffdc0000000000000000000000000000000000adeb6281219c324d14ab4dc29841d52f3f21b512ef0a784454a01358747684afe22b34d4ff1ed29ea013d47d9059c838573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000010367597f1deb2ca9338b59ddcd8d02440ce8cc34c71a6ff93205375077c00f3f1c22e00ebc9fb60de7475400976e1860000000000000000000000000000000017d148179e9671959bf03fa1c95ab608fe2fb8b9b1a650f524a070d7857dbb8b14a67a813ba1b22e4b71df52e46c42c002257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc163000000000000000000000000000000000797e0eff7ff579b0c5161c8ee06a2b99ab44e515045e83438952226046bbb4adf3c8d0538a0bcfe27a533444e2bfc9f000000000000000000000000000000000c556867cb0238505da3b55321df66611e6a018be4e181a1ec121dd55c509d501558af880a2bcc71fcc641edcffdb13076b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c8000000000000000000000000000000001357812e6d93272645cacde880754514ee42aea3690d9d5d67e3bb5ee4444b7a3473ea2af0fc563d246b4c3e8ab5525200000000000000000000000000000000176c413594ca45019a174848f765f69e138e70dde1e184515c6f3012df4c5fa39a28a7e202c6c563db7681b0c4f8b3a9c9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000c5f9145b11f6af0895eca18ba6338408ce40ae1b25f8c04b40c0410a6c69b0144541e2ca1d4303c4c55fc407ca11b1a0000000000000000000000000000000010f2a09fd8b6cffae5a06bf50597a9c0d496bf5529c8925c1141cdb25ffd3afc6b51cb5d21d97c99a8d27281c657bd842467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000000c16911df03f532313d162bae1eb57c947059fb5d776ce3bfa661bad92ebacb51154697593e2321bbf85d43ae7ea567000000000000000000000000000000000564ac0f20388ca3bd483033994bf76b1ba135e229487e0c8aa10dfdec1887c62651f4cc0c05622de6356edbfd9abfef2f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae0000000000000000000000000000000001f6de29a7cf8a89e3cb5befc926eeef59270b929edb68e9b0cd96feb5286e130f1f7c0e0d46cf2a411e499be21d47a00000000000000000000000000000000002b4c8ff1040a843a0e1d691adead4fe3d5306f89f83724a891abffec3c742a3416fe54c27c97bd131730ad364373ed0474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000005d07fdc2e2afd92d5f0f1ab6541313b5a58868d1707ff0cc9e4ccdea0c105cf9cf1f6e52d0dfd22c70aee1f7835ee90000000000000000000000000000000001229bfa1d5c5e4aa5ed0f6753dcb40952fc5446b0c5d0d90b22a7b2abc388cc18e8ef74bb2370b6ccf036f09040f62dceaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec0000000000000000000000000000000019f4a0cb264a617986898fbfb53d1bde9cd82c092ad86e608750ffa990d6926644c717f6a63279f8061b066f0c4e86fd00000000000000000000000000000000082f1b79a9ccf56b743e14caf0cf18b94f1978d164d9a95fbf87ce15c3a9b414b098fb09654c23ed2981249233e8baae5158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000011c516cfd059a1b8ff75df3b9b6b135c2a52371f1a0dad631e96d8673f1b26daff9e776e9dfb225e9881635a28dd34c5000000000000000000000000000000000bb0dfd476dab29ccc80781a92f5a998b8ba2464d76df001440240957eb1237d9d210be62c9187d7f17891e837d52635d66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000928c4d78abffa6517742e617ff8efcf59b48efe0b55eaca1d93a434b84c42f29683952dd08546dc1b88bb63a35b49c7000000000000000000000000000000000d63b1f625ca9d33aaf51f8251a088642211a474deac9931c3ff8ad45f80782f62f71f014505606cc4a96f91c79a25709acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df00000000000000000000000000000000131c7e90e794b09da6c4936747e6509f94a467f38ac7f4bfd0c5da88d1733d1b6871a9df498b265c65695ab3ca889f9e00000000000000000000000000000000190e566597ec19df03c473b8ff4ec0cf24168f47c89525b31b1f3592bc7f87540caa8f91e2eb2f415c05502f72673dbd0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8", "Expected": "0000000000000000000000000000000015610fcdfe0bc92be1f3ea82eb0b0512ed5f448e0000951fdcb0e637ecca602f715674aab1b7555c8f8bdf886d3aa62b000000000000000000000000000000000103c3ceee1b45d9d8912887c40ca16dcaabab3dabf56b8f77cb3186b267a74a5193048ce4165c419cf727d6e9925ac3", "Name": "matter_g1_multiexp_72", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cda9f382fd65f5ab92cc560477f1e3b69d0efe355e40ad3bcaf258509b6ca5e179deed8348836b0e723d5f7ca4c43ea00000000000000000000000000000000037011fda0d188f8d17436d21b9bce522cc9f8e4f473965803b242694f403ecee29d2abccbf56ab0a1f2fe5831c14380b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000f591bf508a5076b26dd8ea3b0f7a92889131142b34cba3f35a9b861cc6deeca7378d5728c0af9503441144bfc82038b00000000000000000000000000000000156067cc00e82414150bc05ca2d0c0ce1c19e5276e00434754616c9021120bbf9d1c00df6a42b76c3ffeb6e32f8fc3eefee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b2350000000000000000000000000000000002a8128978ebfb99e20ac99ff5b3088e8eb95a7b6b354d46e775dd1662a27d5adc9513467690f377d4a13766276bf87d0000000000000000000000000000000012ba903800e9641de498d8e286c7ee48b48f7d36255823b88a24cfb67f8d2b7b6411ba3304819f588fff0d730cf130e428728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb96000000000000000000000000000000000b107e62453c7181b26a3accaa624a612b7498ccc50eaf0d47bbf350b3c8c54e940266cde786c608e42f59d793e45eb000000000000000000000000000000000194c2c3717a8284051a29586e540bd9e456c0169eab0412699865c12226521796a55d598f60280cdcf37b54a24c931040fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000013228e1a6346683320d8acad4a5cb1c23cfebdb9d9c451ab81335d27e8b82297b38e1fe2fd02651a8dce3838144cf650000000000000000000000000000000000c6d54add7bdaf9ff8158680f35be7f51dcd5c26a698750c7eab857140b6329157bb7aca8d7c68f107ed9f68b3a076aac14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d050000000000000000000000000000000006e1e7c15fd14ff3bab1e9b8f8b7d6244c707744708db629ef4146b8cefe68c505ea034c180fcff95a452f7e1e5433e1000000000000000000000000000000000735faa57e1c4349be51395bac55a331a04851b41d2ec98072c5ac38eb7bb03e00ed64bcf32c3eac8b34cc6e26769c3ad81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c60000000000000000000000000000000018008132dcbd9455c3932155a0b0c58066bec4803eafb0a2cc30a93b0a335738b52e6cff60b379fb04b5aad342baf11800000000000000000000000000000000149ea542cf34141fface44046aee2f6c436218374d095bdd46638ebc804bb0c9a7e1e3b01c0470bb6efc7749b8f70eb73a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c0000000000000000000000000000000003f12b0eb97856f3ead3d46a8321481351471e558add0ac4e1f285e7ee8a1f2ca88ffedbc8ed21df31d599e80b8f0e94000000000000000000000000000000001315ca27c955f3826da43745809fb1759f0f5d5674e4d94118bf2f2ea0411c7d9cbc65f054c41ffbdf196ef24eb9afc55af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000cca2b061959fb70d383f7e247c131f51920e048dc136036cc301f1ae6ce13809551d0a8074cc05409d124e2df6536d0000000000000000000000000000000000a9692e0263b563cda35f8497d182fc05e78e7bf88267aaebea1f5f41bd1cadb39c61431bfcaef208adcc9118d4dfb546541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c600000000000000000000000000000000005bfb5a43e3643846f92310e9d5439deeb4fdd6b5dfd3de2ab3a40b9b8b3461136b03c5601add616dd87b9a72e81856a000000000000000000000000000000000212c6c42e24a3f11c30b7751f37c0101b8a071a3d56f2d10b6c9f4f84ae12079d8c4f2d216cdc7ee93abf8b9d6973394b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000008adf951da1f0b64c17f84031985bd1f3561ab44c80c339c4c753a7c2080e0f57c41b79b6cccb75662e8642ae0a94451000000000000000000000000000000000d9082079fa53008a03f58b87fe0aceb121c6c004493f3da7ab37f3236942c8ac01fc28db26b87bd2546f93b12577ee57e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000000f352ce042cbbf1adcc42030ba8e0dfc76b4ca313e82a5c5105ec56266977dc83626c9a9b3b5c25ef459a6feb2722140000000000000000000000000000000009443440da963a7e64d90e4642861f3f5399835fc2fdefa7e87708c033848170eb02407a6a9edadad27cb02793055140fde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000001699cd7355b0a0be2946f8f49648bb04a90c6bc8ee7fa258a357455864022db999793771a2e66adf3cea5a54ada82d6e000000000000000000000000000000000a3ebfef4ba72cbccab5e93155429a14fd61c106ed6d2c0db0694c4733b6f1730cc9f34a5e9598c60e189b8e4943efb56f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000be5ac701c69b81cd75fddb8da92066cfc9d0d2aa7f01495afd87e44076f9f022179b7d4b4781d0b5c6c52b498b63dd80000000000000000000000000000000006f2fd1ca9a34fb09d922a76943b43505f2aad16489a138668f08b9f388c67e46a4d5df7387a1c3aa23c76954913abfae2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000019489b41d8b1f2e8ac09cf3f0930e092afd74405e213454c458cfe44e5f393a88713b62715097a1aaf01a188e8ab07c00000000000000000000000000000000018471d616eb66f1dcfaf84b7d49f632e0a5306888e44c70710bb61d4afd440e5f692eefad842b5d37762cab649fbef34762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001088372334c452709f81b57f5e5c148e0f88dc29dc9a118abd6911c46ee83d0c6b58ec9b854c15f519d33d281ac9e21d000000000000000000000000000000000394a7e49f32e4f7d27f276892002ad034dccc8263591b5d941eb2a5e60097e757ea67dcdc5242b755fce30c3b3b64cdffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843", "Expected": "0000000000000000000000000000000009e68140307d9309c32763e4de857372a61733060ac582922e3822e46c28f67bea87a75bd98248466e8938abdc4ef54b00000000000000000000000000000000187dccf66e6d0593ac8caf6723033db545c43cb90a5f5486418325860b692ffdf8dcf23da9103dc65336d3cec2577a4d", "Name": "matter_g1_multiexp_73", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000083dad213737f1789595316285a77c859c469b9bd0cf08c61884456e4fc5ef0947847186bd420af252d822419b1de3ef000000000000000000000000000000000795a6ced1d34d91bf5ddbe77fee452699a1b32daac270b4e8661259dcacbb9c8c3776043f2e149773427fe109818c87be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000005db83053f9824116b9d14ce0173c2243a4a8506e161db7f97408dd6fa77b65d0e0a32e95062699f7aa85cc9be448dcb000000000000000000000000000000000f20953295dde557a078c981f0b988cd9da8c7469fb7fa3361f2386c7dd609bf80ccf91cefd797eb3a4f849b2cec4370bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c726000000000000000000000000000000000000bc3e40ec1b6e863f75e4adbcb8b504026d0634d1d3769f7795ed2956bd450e68aebb1a9d11a71fbe5b51bc79d97aa000000000000000000000000000000001703e1fde7f2c740ca3224c1994282e633292f86095be38dde3673b78729db84bca33ee820532aa92bfd32728d9756404f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d72840000000000000000000000000000000009622c13e8924441b0043770faaead6db793ab818532c7323d9ee9a8d118cfd2a578e1c13723c8bbdd049b1d8aaad9ed0000000000000000000000000000000009da68565c05aa28648c0d0a0e185335b4e58903982fd361fb57f544c1f253a55e8a233b341537d78c4f229ec5f935a85d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd0000000000000000000000000000000005818b813993d7c346cd70190e1e6410974e64e08fb0a70721a0ee430dcb0d92d302943836343e274b26c69030226c0d000000000000000000000000000000000ee84b6b251c9d4f7e7abf843c73f0456968e23e79c54d8742cd5967737b9cf9ae8c6030722134c376c7c9433b749563feeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000006870d696789986991a222b988c3623ffb51ce96ee35140e817887ea37068ec77d8131a97579f2ea29a5b45ab55ec5d90000000000000000000000000000000016b203c189343e67e10928c2a45259593cedb1a016491e94435a0823522010469729bd69af9c3bd6f4e71e96c7d8ca72edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a600000000000000000000000000000000191a7f7469739ef4da1fcfed877b875c4b0af45df7aa9055b7d5f0c1360e4c4b7b67958d03125fade281c663923670040000000000000000000000000000000014d5256c242839e0951390f00affb226ee6c906214d8d7dca7e4fba7eaa8b1944fe4f1f93bf6ebb21b4a8585e000a76b7a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000001968eb742dc0e128c94c1f0dab2ff3b0d300966537293ea16856e5f3ce5e12164d9c52fa59e08481bce84f3f87dae8f100000000000000000000000000000000098ec0e7bc53314fc8729f4688b99c3d87e7e2770877a30898c37c68a5e0a4459851b8fa390cab18e7cf0d325d906ce4f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000064ef5e6fe9de3e86ccc7a8b809cbdd945eef98e8e6cfa82dc64ba94070cc107090427c13ddd3bf25d542696d5de44500000000000000000000000000000000116b4babfc4b1a7a36405f597d4afb478c024805495e1a412a3ad5e9ec5f01dc47411ee6e81a9477677b89291e91c2b68031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000014d07ad766b50a6150a50decabc56f04559d1b196b713be88b5543a673ee3f4499e42b58c532e38dca0101f639aaa9fe0000000000000000000000000000000001678e7e66f44cff05163ce249df65063c4ea2d2517a31f42dfe76f67041d7927ad4b0efa4b30c33156b14f5127af190cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000745f042a917dca8e35c8f0301612ce198f75144e145a3c3041f4ecf893360eb0b7fdfaeefe78733bb88010d6a7b9bb3000000000000000000000000000000000e8879142826593a2f1214eee206ba69b7962e9a10ba014af5daccc1e4a2d3c893fa47eb533cd0c0a9fc1c09d389db19d983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000009da4fdf5b86facd674ffe6d91d03674ebfa3aeff5ca2a659777be20109946b1bbd759d4dc2d9e859d587ce50ec3bf01000000000000000000000000000000000924985f655b00fec0bdacfc6914eedab676a962e21ffedd83be646dc17f5cdcdd3f43a9ad7ff9d976e4828b4dd219b7a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a700000000000000000000000000000000187414507425106691a2dac49fea1eaa14783b2a5b79a945fee44957619793be1a68aa110867ea405a076d30568ecf3800000000000000000000000000000000034e932247b81bda0a54568f2887824028d69767b9131c106a4d204c0b2bfb929b9ed7b3fce1e354e405aeca8a28d92e7ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b800000000000000000000000000000000027361b6341bf8985d79b6dde029a9ee54ef441894f34d60a3324edb502bdc78ef60789e5ce342c240db0fa91bbbfd00000000000000000000000000000000000bea3c850bc9d0860241fc6de65c203d5a11e6425faa503c37641522fba6fcd31643209329e6ad75a3dc5e4a4790db4a297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000000fc7095889f943697577c8867b411ac925ea7182e47a7cd19387dcdd48fad5e558de3d80e3036992ba5fb8dd7925774700000000000000000000000000000000160f1fbb346c48a6cab0105d343c55b3714899e931e7b4e0abe68c4fc7067189181afb9c040d41e4c1f7c4e2f1b8a63b1b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b64000000000000000000000000000000000bf1740d01ece251c0f0ee4f798872eda7f5a4ad3152d86db12844ffa88ca52835799f0b2601ed1bae6d4850cc889940000000000000000000000000000000000557f274109f745af6cd965d6e706b9ea1fa3c295cbbdb203ebf049c1070595ab820efad6652b1f1ba4e2d331b5bc6da5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6", "Expected": "000000000000000000000000000000000c57ca082c662618951201a98d88170a9aa076fd1fc48c8ababdcbb09b63c793725261acd370d03d28ea47c0ef176f1500000000000000000000000000000000110234e4c6a99c6d1ef8b43fa897990d605af9b469045dcd0ead52b03f9f90dc441f7fe5e624349788746802e4f74015", "Name": "matter_g1_multiexp_74", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ca64fb3ced1d15f94e9b234e6f6fe59d805eb0b50ae29c9b31514ea5c6e79542688e871de6ace893868fa0eafdf46890000000000000000000000000000000019c60ebb5ca4e605e3b0eabdec53f566c9b96a143631be93250260560e47a2ff6b073e432cb1f9104ff913616e7d81c834aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a60000000000000000000000000000000010c867a070e161939458694cd4015b76bc4c76eea884d9dd309d6642436a82bc76ab57b2c0e2d3ca61f34645db65f2460000000000000000000000000000000014d9df8b34369bb23fbeac29aa8c35b346992d847fc2b9e3b96345f4a2245fa8eed505daf17edb4090726052be75662308ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de000000000000000000000000000000000017ffdfa10cc8e1a8b3751312e5bcd09772462618b8bbdca59a60701a96dab651fee0dc755969e1c3a1d2aa4c11e48d6d0000000000000000000000000000000005c2aadea5a4b11077a2a1641eef2d3bc40c2d8001e9853e44bcead87cd968ce41ca50644ea0fe1d0ec4c2d7eda9dcd058c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb4150000000000000000000000000000000012bb9b8a1537c2856d4b2bbcc6fdec6d69eb6196d795bb0f1f49d8a886076e7fb424f63400134622941b2b88ea61b8e30000000000000000000000000000000017206fbf293f1ca1f2a0971b920e702ea39996058111ac2c041c12f58f67037a3840955e1185b413859a6f845b333b58ae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c0000000000000000000000000000000005eadaee4c48dca28f9469e882ca8ccb71f82bf1f2cb5b7f50b2e63a05e78415b3c5d0767a27f19a0b1c88400116e5310000000000000000000000000000000017e95e480a145b5e897c7a1ecc1b21c5a000248f87e74bfecc21a3cf8a06c04fd075612a62145ac089f208e567e4e12072022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c0128710000000000000000000000000000000007b834503ed3e1cb74738db29c91f415beeb3ac5b75bb2cbf11f4a9cd1608ea6080dd1bd50c195dbf5ab6808fe9d6594000000000000000000000000000000000eb32afb90ecf9923ec22a483ffeca3a15d358013e64e521aa42d3db1ed0397e07a85321492e0693f8f041f4f8346c6c800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc0000000000000000000000000000000018f208e26fd7c03313df686e27bb6ea09d9a998764e805fe6182ee221cb9ff1552e4db5feb91b3b2fa595bc32f81898e00000000000000000000000000000000137c06c3f9eb27f1c0546b3c7ce879218a309dc37c0590fc3e151d9f7fd5963f0fda201faab489dce0043c3180abf753a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c0130000000000000000000000000000000016fa49bb488a35ecbfa9e714235790cf6e7c3ea46e6a9a424f59c63d018206740e9467b0575077e86091ad6e0f9f56b6000000000000000000000000000000000197185b7c82ab9e6dc8e2a71c94dde328c923eedc6e305d8f36f4b636e7662e501917b89b33877cb2094b523c969dfeebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec7304050000000000000000000000000000000006b797e2bb8c0c2a5a6ef8d9f08241d42299efc8af049245c254a2e4bfd122a01954bc596750942bf7ee467b22bcc528000000000000000000000000000000000a655491c6381e81473c23565082544d9f223042c82e241b1cb8ba48e847d98a373fc68b762a600489cbbca612defc61fa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a7000000000000000000000000000000000d7b7ba451334d1391a51142c4b7cecf0032fa6d28fa7f36d2d43ba39c6418946244da3cedeb2bdfadd453eb4d54d05b00000000000000000000000000000000127655a7acb4e3271a188cfd287cc1af890756e340eb4648bf3ea3e469644e6d21f63e64f81ccb55b9b1e0a62ddf58b5f0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf211000000000000000000000000000000000129442dedea08bee8661b558bdf8c22dd391900a501f1841c77359b20c1a1ff8838829baafd2a6ab5eff31e3f9ee884c000000000000000000000000000000000ed7c27bfcfbf9b41c833fc0d8573d7b28a6d788ea3cff4d96900559cc63969ac1d5fd366fa705357626eacf402c2ec560d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f90000000000000000000000000000000013a11df012f8a55c263c5c55df0fb682e685a5feef160d77d26db7125ed08e6605f3d67878ec78fd064487f30228f4cf0000000000000000000000000000000019292997c874c72ce7c432f20da1a338e9dc433f9257b7353f99b5b531a9997bc3a3405b0aba89ab5a2f1cda98dd8199006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d600000000000000000000000000000000006a56b816898a1fc9954495b711c493ace881e3989207b2f862dc41c5fe346fc2eee18adfbb9db67e774055561af00600000000000000000000000000000000013971cff1e9a6ce35a7ae40118a007518bbdc5df5939a90fb263a9c345a70f4eef2f94ec671ac6964390d0478cfbf728f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be40000000000000000000000000000000004962ef115a4288177df2f0e4665e5d1976fd027f7f87a24ccdd0584e265e2f5cf0a7490dc7824f5eb26c9569bde9d6e000000000000000000000000000000001544f43d961320d59c65563d5f04341a8ec3e6e64fc2dba7e953652232d615c90eef2c859525fed99ae6ede2c39f510a861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000004b250ff8bea739fd73b3c3463617eaaf3b6bb9db11c2b915f7435996bb4cff3561fc268d2cf0db1705711de522382200000000000000000000000000000000001c428a889955fbb5fcba993f2defa5906ac7b6a3fee6c07f52de8d54b0665cbea84e89a0af3523213fd19f7d37944012dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be5500000000000000000000000000000000106fab277085c88a7d664587f67aac8de95aae908177dc513fa24c8115fa23db44eafa7075b036242306002ee6918da80000000000000000000000000000000009e832e0d01bb5e89460e2cab772c308da07414ff8b880288c7b55d6390360924b806c71c9f9762d84d8d3cb3c2f6a6199893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23", "Expected": "0000000000000000000000000000000010224cb0e43534025f8ba7a7c426355a2091473ab16a752a9819d8e5f3eb5b1b5c0891b1c0cc0017655dd8aa7102cea80000000000000000000000000000000004313278c1bbc33ae2c3010c50be0120bb3ec794d9ff77fe97154438606e5f6f04c1dbf8dc01b827643a31899342e1ed", "Name": "matter_g1_multiexp_75", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001812b7bdac748d2c0f05f10edaccd351e35347a4a582671762c0567f55e411839ec0a776c18cd71cc6de0c3a3b8bba820000000000000000000000000000000011afad9a48c42d8c3bf74dde15d7b744c6c141ea57e133c9dde7fd762636115e0296a647fc3fbca8144048721902973fd8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000f4900ffdc92661bb33e7561d08ce7757ae71a2b5ebdf6427922454044c6c6695e249069e83f3053e8a8a0adb5d3d3d2000000000000000000000000000000000be84ebce32bce4d58557422c7a8c4020d1bc643a99b00231a4d4a06d5dcb56bba61ead26fbf07079e9457dd4364ab6d33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f0000000000000000000000000000000017399488c58e24c6e1f5e9a04291930595389536480ee6dc493cafa7f0e85410bccbe5c5841a1a0e495830be7e32c0da000000000000000000000000000000001055ca833e53172ac1d2d3d7c6fd625dcc72556e8d49bb487a83e56deabee4fb672b6cf7787d1231c760c2b7e9d4e55e7894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde48000000000000000000000000000000000c57fc0c785d6b81d4831ba71bf27f9af318a730a9502917a68397678c7ba22f21335ca2fff5bd495676faa418fe21a9000000000000000000000000000000001012cef9cbc88b838492b6a0074e0e5d24635d36d669288acebfe446157a202443fbaa5241b288fe418e1fa50eb3e65cfb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000000a2a4bd7c7a79c2336b05bd5e0558736697c435477d4d0dc790033366ffcdecac3bb9cf48d1341835f7a42e17af833c9000000000000000000000000000000000ba384bfc6aaa8402ff869d78973c68ccc36c20a839da8d570b6890614f692f3a3316f0eb45e4afee0cca078cded752e83c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000143e594b8762b4f821a6cd294251a114e248974494bd16a66f27192d3c2dc56c19d886b6305d420f8b81b22a2ce4faf10000000000000000000000000000000012fff0d7edf98633e1b10ba09b3c70fa0ea8674120160933689115275da6f95a8cae1ec665f89ef3c5454dd91d291ba4d2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b100000000000000000000000000000000159734584d9cceceb9a27808a5bbc1be9acc15c6d2edad81759312898be4efaf85420cbd004102f7b051c83b27bc3fba000000000000000000000000000000000eaaf5b8e35ea5d52bbba19087520a96348b418159e043d3b39c451fb77d5b98aeaa43cacacadf3e6ebb503f49c5ad4c120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f000000000000000000000000000000000c62de2a514ba6a74f66312553218cfcf49828b6f01ed05561b54d5f2a87806694ada45b80429e60fb985d9cc39e9c4600000000000000000000000000000000146b134c46ef783488e0f2d6d9b7039971e8ab7f3c29fbb2635bed84b44013159f483df0e7f0afd038b64f9e5cd105726af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a00000000000000000000000000000000189f02eda06f2d39974098d874325e4711a3f4dddf78c1b9ffb025425c8abe6dbcf5a01de0ebc802816fd67b0a9882fb000000000000000000000000000000000b378df4be4566190679691561aabd7182e68dba4ba05cc67ae19cef483fae99f4cc54540b5a5180c3854f5a82b6fdd0f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b0000000000000000000000000000000011c0ed482c1a1f030fff7395db725633a60875028e2a7763a1ac801f00a8f4aff5e19e556516df899cf5e798197f6880000000000000000000000000000000000fa7faf03f2f636ab340a9d27d9b5a66fb8daa9c083a32904a4407d408cd3a14c17734d7a14abe3655979230e1a93e4d23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f500000000000000000000000000000000101f93857688bc4e4da2c5407d8bc68b9304d27c89a44daf7cebeef81ab96d89c83ac34ccd0dcd87297929551810e47f000000000000000000000000000000000457eef8e4d47638f83aa2165c0f2581e6a0886595f03fc41319d6ba71da0193a4cf9f52c39c79327a69037b11a382f696c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f00000000000000000000000000000000110957948a78ad9c04b7abea4d1caff1de20b5615909c2f5b8ab7a1dbd02b9cf2ebfaaf3b21908aeeae55e47b9a21b7500000000000000000000000000000000168f08d45ec66fd4c9a94d82d9533aeaa251186478851a421f097d00506fe6dc0392114115e3e66d8874e0aa4b15cca281c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c0000000000000000000000000000000015fe358a596150d9eabe6f18e06d562f9e6c42e9df7ad9ef57be8c47c5764e408efbedf136059d0e04f81d4838713a83000000000000000000000000000000000ff7a343274892ba23daff40f5f8c56db9a4788483c16a4a0495a1f696d3304c6276ab5a6d7b3cbdce14e9711b033582e44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d30000000000000000000000000000000010d66f27b2da2ffe49b7540da57c25f0d36de0c43d04da9b123c153ba3eb63f3d26d28d4cc4cfef2c0652010be2f9eb10000000000000000000000000000000004d4cf53935c01bca14c75d1be55e7473d17de6c5a2d69813df90c7612aa4815ca6ea982222793ce66bd1c69f6e456feae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d980000000000000000000000000000000008ece81bc19694eb40ac3ed089d8fb0cbed88371c7e314ece92547151165a017b0a5db4eac06bb2679a8d82b296f522b0000000000000000000000000000000017732041d736996351f132c92fa7249483612bcd79532156694314834c04d3b99579d44628c52eda270ec7c3ca7c3e576a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e00000000000000000000000000000000026b3730efe162d58adc8d4845706f9bfe8ff54116b518d6c3b2bc6418997a44e98071e83566a905973a2d512878cf1d000000000000000000000000000000001449b0e28d1c43ced7cd687a550ff7669df47e80d3f2ee621b791848f1f7d6cf6272e39c66e8a69c81aeb67b06c630b2ed3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5", "Expected": "000000000000000000000000000000000154edd700b8cda3a0532b2d6e81cded6a9662547b7d638f229ac44975d3b4b2f19816eb664e6208f33bf6f8d070fa58000000000000000000000000000000000a41ce7605d7ec592ec3a030307391ac64be5df9a7f8ff9b7ba15f7f6931353fb824ae8aa76b31466b7e06cb89fbc1e6", "Name": "matter_g1_multiexp_76", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001227a5d20faf2f8d9242e1a7bea89b5d7c41c3e0d8f2629b4004269f9babd2521a97cc23075e13a53f4c66a82970ee76000000000000000000000000000000001726ad8abed312a369001f53270b5e7ad8f3f2a031804ac055ed4ddb2f40eadf9142416efbc90e84f499e07a307994db8c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd1000000000000000000000000000000000a07b5276098f9b3767908192f91473c554eaed23b810d3b464a3677089c45e2263600cc8d84766c7c67d9b5e6a057cb00000000000000000000000000000000175af857d5b53d195a17ae246208b55f35f4ff193545ea5a725a70f11fdd34ad2fe22431cec7835d4fe3c401c82a93fd8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000039e109e0c2ccb5e6cb4c5451125047bbb854488ddd74fc4360430fd80f16db3498a8be9514099d3ad50ed4376bb5e50000000000000000000000000000000003dec8af7f6805ff9df65c39262959c3c80f271d2f0e53e7e719fbb16080d7d90a1211a6b4d0513c771ddad7d3dc009063016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc10000000000000000000000000000000015a00f549c3a050a5ffa8427bd0c8b90a788c6f9150728b037232ce1148c02bce908f60ee367b70d0c9642114d6e657d0000000000000000000000000000000016831ffba7d7d0bc239563e9e62990af4f740e57ca56d0d8826a9738338e9a1d2e8dc2b8869d62090b06f5a3f68bbcd36c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000165e447cc890b383b46f251531cb6d29cee835fe2a0fbe14c65f0998b2911ba86337ba79decd2701a4db1916e01ff4bb00000000000000000000000000000000007bfb52f3d4a281238eb65565af329b3e043e412588ae00342144d168d903cdc9131775ddcb5217ff692b0f922504ddaaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d71000000000000000000000000000000000ef8ba702c88495b63ac012fd9ce54b4a7ed67b5f7d25bcbedf951455fcfa95a8c7775c5ccc875ca5bafb9bfa1af738e000000000000000000000000000000000e53e18a3e7d294b508ec4084cf57557dd1a96ece8eac9873d35e4f1ee812a1380bf56569e5e797ef54202b1ea69291df6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd4900000000000000000000000000000000021ef16de941ce6394ebd484f6b9de12787aef9e7921292106e6c1b18b8de5c640e448f53abd536953b07dc41db21ec0000000000000000000000000000000000a5d482a1c20571e03501b89d2bb4c6d3251bf0b015f23ecfec87dd7cfde705f946c311483ffc84381609c394c83513a0cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c6470000000000000000000000000000000019c277726fc9c53de1ef3aa2ae6e15b360a98b4a2b27f9057f91eae5b2a308b2f5d618d8e458839d1d60105e4888e7920000000000000000000000000000000012ea8dedac124f05ff58ac72fc967e325e00e83aeedf956adee447720f491ba1bcee564f52e4f0e53faa106ed8088d4cea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000000483da14288400f7b27d712ad849fd7c068db47709f78b297c746ab3e15f17f20130b415c9a1b024bd5b24f74428f0e0000000000000000000000000000000006746bb7d3a38fd833187a16d5500d394303e2edf7d5341d787257a9f811411a5cd586b300b7b4398f9d266bcc27d9cecaf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea3000000000000000000000000000000001700795ca26c2cf7dbdb64034e45362295b7e9c60753d728bf689239b0ad7073b29fb872aff047605509ecd10cbd4fd2000000000000000000000000000000000266a09604de2ccb74c5d97dfe4e9a74cf89d3612de9b2d2d39dfa3362b500be127b83566a61df49e639d548a0ecfea7ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc00000000000000000000000000000000146b2b839ff63d376db418a51890c46b0e3df6848a5a39a26a02673e93ea8dec5079e89a333c85785eb0cd1d67b1e101000000000000000000000000000000000f57e8e4cdf2670dc35a12072923d334523e7ccaca66795e3a762bdda8efe5424f88ef7e4c48b0d6760234ddaad4d7370d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001989144efb1979a42399f93fa80bdf256316f6365bd82b89e0e2371de79ce9de2435a6cfe9704ed710bdfcbc8cc2bcb000000000000000000000000000000000084230cca1eb5defbf2f2ee29fb2c47b417919f220c25bdd2a017b514840466a45b2c00047e9628852d48a057d6335ad7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b8200000000000000000000000000000000128df806a651c43c7e0a3b2c5833bf158ea40953fb0efb02620cc4ecfc4c32a409a8bd9e98e82812b54d027b6346afc70000000000000000000000000000000005e28760f1e574aff9664e373622147c08538ed45cdad72a546e4b5840758f5ed442f8cf24cb0ba35902e64d084406f32a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab719000000000000000000000000000000000289520e710e7ce4a8a671cb00a015dcf40ee2a69309cb89b514f6fb2c6e8fc92a49905893e3e0e9567956fcc86dd89c000000000000000000000000000000000d1329a4174f802680dfe8410fb45e23f96eef4649579ca8e29b3040de33cd6bc485d1339afac9593097c70a0312f5162bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da96000000000000000000000000000000000175f1ed2dcd584f9c59c9c747ea1841792bfd9a64747f84dfe32e256ab5a48eb2dcaa337990089c86b3dd589d276e2ce0000000000000000000000000000000014d8bb6e278ae9bd9df2609690286be593eeb668f5e2adfe880e1d34276ec3bf4ab5514c7898a6504da63e0ecfa49d020f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed0000000000000000000000000000000018adcc61d9162790bd8c19be058afcce08104a952b15efc276af8a8807a4d2edcf8557aa03a297ca01d6a3869160148b0000000000000000000000000000000004338e5f7a12f2ffdc8158a51b14dd36934f01d7fbfe45e18276f2432b1b8210ba6bc5f246a52646bdbf99ed91f2f48f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e", "Expected": "000000000000000000000000000000000c1f9b78641053cdbdd6545691d1a5238389614524365bcddb07f9b9c6c654e58a40047084532b8473c7d541ebb187ee00000000000000000000000000000000028eb1aeec5e4672c41eccb45b356529e5331bb5fb0ca8f9e10b20a2ef1ea911f03af896ecf7575613bce5eb8a0b0837", "Name": "matter_g1_multiexp_77", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001242be79cbeb2176ecadb07d205d532bdaaaa26bf9103371f2c4d86ed1df72ba8b6d5c76b7aef25c743ec4f43e5237fa000000000000000000000000000000000d2de7792d0655ebcbdc123ed6093ba68948b8ea156a31b9f23d1abd948f4b2ef2f27a3cbf72b9e5b3e966576e9ffbd5f3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d8000000000000000000000000000000000fa50f78e45b1b7b61f8508bb5842bf59d0f41f2a8192cccec6e56125ff94b402dc47d3bc7762f3196a163fb148105820000000000000000000000000000000002933cca4d82c6f89ff8db5f9239ef8fee2efdfdfa22e0b4d0fbe223910b08060a77eb4328a05ddd31d205861db090ae4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000c15db9d1dcf646bb4c169490256050ad5e408d1f45221a9b4bf02f7651fe93ffb892c98d19d730bdf3971281c9e2e3e00000000000000000000000000000000150a6d1978ec63013ef3dd3b258ea3a716c1e564469d2aba343f3d15c30cf287b706b9eef8363351cccb79ecdf5aa189518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af07000000000000000000000000000000000f7e810001b9e3a11a535f6744a0dd357cffa585baabf065f1e72c9bab5484829a94159c72ff2221406c8b15de465f8c0000000000000000000000000000000009d48808fbf21370420cad4df7a269e1eeac98d2aa5ad5890ff362d91cca5ab1b57fb079caaba3a135c15515e98c6b175561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c38655100000000000000000000000000000000191dcaf13a62fd6de0bdd16151b3c27f54b40ad82da1299164da87d0cb7b4c769f941c39fb4b68a8915fa95a5ddc0e900000000000000000000000000000000008b0ad7fa07edefa61ad026d42df18273b6628b65a4e655a98b705f588494d06c37153ecdadff83d94739bc254d6d8f837c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e260000000000000000000000000000000005e3001f37e840a9edba48b3b436dce520203b0b36c3871933464be1c41178f7a8af9b14000b713ee8fc0faf5cc1a870000000000000000000000000000000001732dba0dbadbe7db31ea6af17520d791feced0a7bca298b932f51f3dbcb355699db533cfc8b61d35d1a346ea5de8032981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de400000000000000000000000000000000072e4d38aa0e168255f1d69ef129642b4b1b57289e630455b147574b03d17e3cf0f32326afb7c45da468e0d8c2276da9000000000000000000000000000000000b60685ad05be8453d5d272c73365d645dab6c50c820c1fb7fb50d82eebf9b03ad3c8f711140ddaafb2bb128b7be2e6c9913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d3000000000000000000000000000000001401e023aac71de3398f89893102efa8760cedf47938a655983d73ca8d394a239f37959e629cd908b4e4f5e55955b153000000000000000000000000000000001458e304efcf48594d7094d30a804742b08ec94ae479cf5d4e0575828ad92cfe8e11847d6078f5eeea4308a8f0644172188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe000000000000000000000000000000000d446202ebd7a7995a4e8aa7fcbaf6c4c4591c4bc40b374720752a150b452b461f59b775e3088733ca967854413a9f0a000000000000000000000000000000000d5fcb5510c0f7ee77c7584631149cd494a5fc496b325ba93ac5f801e34c815fe562be4758212f32ab0978930d142adf5525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a0000000000000000000000000000000000e034e4027e846a8608680995860b2673854d8fdf0e61e2663d7e0d904b6725ff28bb4593e7bf5e2c252d9c9710e39c0000000000000000000000000000000010bbf60b95669468e5dbdfe912dfeae9945f44454df62ec116b097b867b14c402349af692490269797a30639177151945ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc5700000000000000000000000000000000095e1315b3568e8a069dee00c3676d5d6ad94a2164795ca5f1418cff4a25052e741530c0df6d50c5cbcdd55a084227f3000000000000000000000000000000001993b036a3225289827691296b51ea4e42735af0506b317932b6719a381a59c89871a2a394f4a9de0aba3bb9a2b881f86ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb94280000000000000000000000000000000010aad99bc8570d83847a2a2688fa61d5d0ecc978ae842715a084d99392db343f581290478bc1bfeb8bb692e0d6fd58ec0000000000000000000000000000000004f82c0527d3e9329a6b460f1d781f881073b87711771699e9cc8c4229d5112d91d4357380c12c120313d2c9eb7bb427854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000008ec7244587110fd3fa0e1888427fbb3942d0885e002e4f846fb749bfc4a82bd7edd15cf81af454354006a2ea85234f6000000000000000000000000000000000fc7a19df5adfb5a154f32b9022e54b1560237f4319160c9c945b7bf4b55e45fc86616d3ec3cecc177c9f6bc54dd2cdb7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd00000000000000000000000000000000037ab89247516909dceeb59abb90d6968ddc3ef3abffac93c68757f3c9309d145cf9350e4d8f85db810cc5f156f8f126000000000000000000000000000000000289168c6dfdc25ea10e1839e10ddffbb25522be7ff80ef321241c6cc887fc7a42586dd9c1686c6c5c2e4caff0278155923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f5476000000000000000000000000000000000523020b4c34e867e75cdc668e541cfa25f2afc35573b2db083987fc585a487f1eafbac1c4267d2fdfdc5d2f94c51a84000000000000000000000000000000001581bf2744d78d680c9bb38a3f0fee76b6f0231f011b3f7ab3fd59c1ec6c99fac518857dafd410bce2e8610c6e5efbb1e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf9000000000000000000000000000000000615745e737980a923e87c3ef72330f55e38434b3974c1cc997a9d1136527de9bc21dfa73ea0d33d27324a53f12bf6f9000000000000000000000000000000001164b6ac376ef24ce3cba8e2ae74eb58437bbbedf68b4d0b6e8b7e213a789c8c3b7f173bbe52150faed93fa83bce0a9db6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa", "Expected": "0000000000000000000000000000000016e6cb1f899ee8f3db4b934c1facb3928b08fabdce74f569a06ae6eeab201925f6acb1a47ffef3c608fed32c949786a7000000000000000000000000000000001796fe817d2e4a69b4e8c539f6f2179a802cb02caaeedcdb316b3ec6571c13c349e115a7a05b296d6b182c8a618ed918", "Name": "matter_g1_multiexp_78", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cd7cca90c8742e7f541981a13b177a4e639195af5f15cee2ce37b01d50fb8478a3f0d0abe4312a4d92a201b4dbb030e0000000000000000000000000000000018e2a69bd1cd9bb7ea75ceedf28ac9c9514e8d28223af69dc991e46a03d8d272d267842f30552583a1f08a07188058ce13ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff5660000000000000000000000000000000011cbbf1ee7e9cf8deae286ba67ab0eeddabd2471d2ea15e86c77c4f2f23ce38e17ac3f3e3c2a40a1640bb3485be4e59600000000000000000000000000000000108e9f887f86f03dcbd515501f69a5983b4a6d707c26b69cb9ea7a387c5a914612ef645cbe81bf29ba91d209e839c72abcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a0000000000000000000000000000000015c1856c661396f8e3a477932e1eea7e124b2e9ae0dfb1df67c4b3928c462cfbb3220c4c2fbd755fb6435e144a2b937e0000000000000000000000000000000011b114659fa71c3ac2412d5c2cc1e184f05a45871e5ab08fbe5eff68ef9e457c4f3e2bb4f16d10e91f7ee2231bc3266359f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000b693c93d4f06be5bc8a84157c6f407c3db14175c56310e7d041118ec869f3992f75809b209f6dd01085991deaae2a96000000000000000000000000000000000ee21d90cc3825b401e6d452e27814672d849386eccec7be992581b1fb9f4ff4f3892d63e124bd669603e6269f099452995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c00000000000000000000000000000000013b40cfe91492dc53089325be73b5d404288e8056e30cfe4bf3feb6b854eb7d0efa3ac4afa822162ac16608555ccc92000000000000000000000000000000000576146711dfa2ee08bf08121c30fe63ef0ca4448b28076eaba9298ab925c615a56d497044be803f73e9586763aad52497b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a50000000000000000000000000000000009d66d52069b0d23faa33818a8c9bfc812ae6938dd02604e98a422f50c085a5641a46272dc9c8801a9c76cdfc2020a0c0000000000000000000000000000000004dba0f971336c813933bc6386e55044f5e3d3e5cf38ede5811b4e775fb41cd09d7f136d9de6fc36f2f435b8cdfdc26198115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd17810000000000000000000000000000000005ae8fd5c52fff0b80a2c5c4fca4bccad28f580c94edb7e28ca2ce2390cc2fe476a2b11f63c3c8759847e647d5fe5d1f000000000000000000000000000000000edbff5012f6efde3a9bcad65c805b1c4ac0899fbba5fd760513c673ce8ad18d3baf28acb3344f511fd4d9785afea33c27370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf50000000000000000000000000000000009d003d4213a46812ea1565bd9a6f0f3da1e69e289f026e619911354cd7444dfbfff1d842e3d9c61c305b2154851b29500000000000000000000000000000000070a1387dd16f9d8b4306ecfe0e9ba7aaa5959ec917e06da4ddf90c992fc569a56c61f6372bd26e21f5cbe7d720b68c66bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f7100000000000000000000000000000000008fccb033a3e10a0015b11ffe2ed5f4c96ea2262d06ca4b0eabbc15c9b299a5220444345c65e7092501b56599980bd0d00000000000000000000000000000000127583566286e52f2f2c7809cea1170a49993f171c1c217b82c17983e02b7e69cb8c948725c7a613c41f96e80c3f1aa96b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d000000000000000000000000000000000084501b09915fa13908466d6bd50a7e0d8b39893bfcec9c6876b7ed8effd100b8f0a459d754efb6b110af2becd882cfd000000000000000000000000000000000373669b2a03d3da4e907da24c61f5e7928c5fcef4e6c9ad4303fc4cc2cb641212680f7c33605212de8914caa58732f44f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce110700000000000000000000000000000000010608a9f87f46e528d782ef81493625f9a47134832eecca6471d2113060703750b679e64179e7a1c1c81311c38c493400000000000000000000000000000000032a0c82e42be6203415638e6cca4dc1621f87f030a9d742bc77862f4f10ceb44f1ecd377acec6587be0fdc33d8c17c98b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa300000000000000000000000000000000126f62cc3033b7235be5778289fc568a1c474b70cba2d35a0b9fdab5cf239a2d4fb03f0bedfa84425b142c04284da058000000000000000000000000000000000dc1f91754d582f57b413fde9b837cbfe3430582b0964620b02bf854c6f666914157d44a165f16ca1d7204f35caa7b0630e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b20000000000000000000000000000000016d277ee7864b3af3102190cc99db1cff9fd1b1d6e7fc039040149c5944e7837895532ae41b4db50e29a5d6bad7ceb630000000000000000000000000000000016c3f6e29114782c84734cb927d1a89b7755c3a8fbc99076ce3ae17f7f1d088e5fb9757237773fd4e14c2855ec12b93723a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe000000000000000000000000000000000dc8f59e410ef7145d636d2c7d43fc4b1c903d6c8c0efc3ae162293c7c65c48182f9a25c4e5f111635881533cc558cf7000000000000000000000000000000000082dcb0872d815465131953c69e260e3a9ae44d16975f361b5effe13ab1d61c18f050108e73f50871221faf28fd79771ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000002536653a945e03329279f382937d72bddd71ff8f19053e1fb19ef83d9751eaf101676249ac65fc61a0cbacbfca3cfac000000000000000000000000000000000806ebe4d62e62904ead05f814dfa6e8a392b887bab4aee61552c6f93ea5ffec6593e9078a33f4cefc96393a667c934c212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000018fe7f7093e0313737b8e0c6ba2fb0c93afe1e8241bc769f14cebbfdb4c73aa578fe3d37ce1221f21aca8af9ab99201c000000000000000000000000000000000ea0f2ff4c8ed0a51fc8fedaa056a369c5e97e347c6883b215d0f7e019960c0178a7962415c220766c16f4596d4b9d8ce9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37", "Expected": "00000000000000000000000000000000184197d1ebcdaa25c041e95424bb891fc9eb38160cb0d91e702ac283280c6df697ae85674eccbd0fb130b6f2e1c193b00000000000000000000000000000000015593ed27279ca601616dfcdc493b8c7bd68260f97f8a9f10c03cf871b17cf8f492518d2f8569d60056721723a0172dc", "Name": "matter_g1_multiexp_79", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018c31653abd67897b3bf8f046712b12d35ada1799d1c18071182fb61273b7bc506779ff2d774576a725f2f1035431c82000000000000000000000000000000000011b2fab972f183c75df3bfb7968dcdfeed0755f71ec118e56c61203e97064355200c5f016b9ed66040fc886062dc58f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000030d7e368c99113318a6657deb3c89424b9acbc5e3568e03dbf629333ed3a5cb45ce6988a3e5ef79e5ee91aa6b990b1d0000000000000000000000000000000002700af33eedebc8a4847d6772cf615413149e6d98ba3b36c96e43c8d97619cb01117570f263bb2f7579c7da67f40a25c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f0000000000000000000000000000000008cbea0d07e870d679cd20b4ad088bf3c5c23e83266b20e816f69bb918824c9bb4d0b3216f8da5a5cdc6f43359e02d06000000000000000000000000000000000d1c9949921e37e73f95b0e4c444e390bb71fef0d893d1b341b9338321bff4a23d1da4ffdd5d7148fa9fe9cc52ebbfa8f2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a958530000000000000000000000000000000003111c080876670db10abfc439b17b32f9e96758b057d3344c7823af1b0320037906b1a9d8fc42cab9e9e0e8449aa997000000000000000000000000000000000e0c7d19a0362a173b70b6fee3d3feb541c7d2ccca71f1f01f8bd105a18024fab05e0a6d448153139f2777b189ba0fe41022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b0000000000000000000000000000000018bcaa4869a5c6ae46e6f5fd5fcf835965d21d48871010245e722bead79d844e96e10558d71e425377f4adacb3f74074000000000000000000000000000000000414d616a4207e7cf79352dbf7f319bf554f043710cbeb48aa502235db7d30f4983b5381269f34ad6ad4fd5ff56d9586b80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000c86dc8b8f38d1e4281269ca252adde9f0fe933d4cc051c7aad55f96252d1e6f9eb6f4f876e153c11b61714d985d318c0000000000000000000000000000000014113f8e2c3ac4919de334eb5c04c909b88df39998e58883a5393a4d760cb6d07c65eae053a7b2100ff3028a786782bff397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000009b4437230d9dae44852d88dba2655070162501702998ea5a035cd88eecb64ad7c9ccaf696545dff98d778cd7400943f000000000000000000000000000000000706b196155640680b257a537c836507d95e6d5cb7f163ca340dc0f8b80859721b7b2a2ba51dd4d72ccc4c3cb91030c928e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c50000000000000000000000000000000017bf848757da8e7ce5e5e69574a9b31d35eb628102897922d4c996443fbc970374ebd601b96b3ca9412c13f50943c7590000000000000000000000000000000014741c0b49e4f02630a6cc1a723cae1a6a9862158bdcf996b46a9614dd34527a859db0b5718788eaf2caa059671f3c683cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000001269325967fc68b78cee64d0386e1fa6ecaca1f85d672f8b63831a1adfcbdbb40461a77ee0e59b1fcccb7c1d543f08a100000000000000000000000000000000053a22e8c4219e4d68a961c2127201a23443d8fddb02e3756cfdf74e616dd4abe73c4ac498ff5f6a68d730c0050b79e18e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c500000000000000000000000000000000018248505148876ab5a5ec3be7e3a6cbac30798d52f437bea7e966921723e6a4a30a0e53518e109d1683f3a4b3432136e000000000000000000000000000000000120602fd461206973e62ec8a3f1cfedddc1e9f9e1769ac06e2a1024a9af19d402f40ffe30f9cf77b8704497d3cba4a3674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000009cf2460e5121b15d177b8ad803c045529933d1abf62205d04726b67d64fee85e2008b5098ceddc42d5c8d95d39147600000000000000000000000000000000012749abe2d8b47bd9c899b6726ccc749bab2786e9568d32299f0e659664ba1efe764944c4087c549e2bb717c87c6b876288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b340000000000000000000000000000000008d764f80994fd37a21f6923d7fef255145ea875c892888d45efb7a37310182b04d2c16d4d91a2e7c41164706afdb617000000000000000000000000000000001156c016a289989510f1c8b39bd6a8c358a1c5611bd2286e9f15983f984e89e061e60717f1b700abaed57076e148a8a956e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000734cd0d73ef7d79fa501b98b7211d551127abf68c473c1c72c591180b605c938ef71f66c422bf2a8bcf16c6c8946c050000000000000000000000000000000008ded96a9fce61040c1acc71d6496cf72590c63c3514c4f1f77d4582635af9eccdfab2e60749ed24fd3b6e30e3576c58ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd000000000000000000000000000000000df9ecaab534bbe9c8531f813a95a7733df6a4c8785575c5ee89647941a6984cdb5a33d2eced340c683339c18f5da32b0000000000000000000000000000000003632b2377ab368bc9f735609452e0ec9fadd6f261cd5352e0a5ed6a37b25ff7a49fe57452e79e7330661b81d7d80a64b96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000006bc4871c0271394c9d6099667ff68e1dbfa9980976075bf81fc18f1875fc91b50a0e3be622882c90b1594419da7dbcd00000000000000000000000000000000168e1dfde47d19280dc213bba9fbb61fdce41f81d4b25b2a7abae0404bbd7a413cdd89611966a7f9bc32617dca51f369d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb000000000000000000000000000000000132a2a6832653eac18e2fcb2c336292dc7990fa1a004404973029a227c9871181ffdd88a74adc3edc7a8308dee803fa000000000000000000000000000000000b230c171d5739fed98d32a3b27584bb0128434401e9e05ae09a4dcd7a017d1cefe7a46dad2db5addfb389feb9c846181ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5", "Expected": "000000000000000000000000000000000cc6517e655697449988bef516e60c8202fa48c3573967491ea2ff2db9fa0de3f542f656228a5b90d7fc1e5eaa8b06d7000000000000000000000000000000001191ca6ef2791726a31f77b0431ebe170d8fb595cf6e6b9b33b7fb0d4acbecf2d171884c392832d2a91832e982c5c0f4", "Name": "matter_g1_multiexp_80", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000111de2b65f5f94851aee2861910898b74dacf013591772902239ff7f71a9cf84919bc4a84d6936f9552e97314eb52e7d000000000000000000000000000000000db96af045180bd4d88dc8c40f8cd918d2195c2f3651c176c1ee3ccb583a7363e2c2c900f2a54f26a881938cba98565f7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd0000000000000000000000000000000000e48144181d956ebb37d72c38c062958f73de8944995c7e7568997b04ec19949b348fd80e810632462ce43c7c6571ae000000000000000000000000000000000b4a19556d8c21206c4198059adf5ac2b8a0e08c948a8a4d7465bd31c5ce5887a069df5f80b1df89ab868ca53e16c730c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb10000000000000000000000000000000010547f218e33dd9f9425c8e7be4136e65ee3dc23e0cdfd5f1caa8986162cc13b77d30259b6b9c359ab0faac9ba29bda00000000000000000000000000000000006729e532ba87a77d1e458663690110cf63eea96f8e41a5a338493ff71b68e78e78b9c929006c0410c3739b15ff2810069700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000000d3630086b7e0c068c60192be8724ab4d18409fa6ddcbed02b52fa776e84e2115457c40cac7e903047fc435114150d5c000000000000000000000000000000001066ce26d2e940899e80e9c0e515ce9d5810a4048925a7ddfe0cbb24b3d8d654c6835c6872fff5a988f525c648661cbc19e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b000000000000000000000000000000000ae73f595bc9d22c8c959eedec4d1301a13c9b8c643f4335160bab4a99886694d112ed6fbfbf082629b76d1e2509ed280000000000000000000000000000000013dc07950689ba36736838714eeb28ff3be77ef8ba181718ea7b5229e01d4e036c98eb9ff7a867c017857c029f7f13e3199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e0000000000000000000000000000000016e11b169dc405035037a10180fb368988498b6e209ad62260c7ef45e9bffedbb0587fe282d193bbf88311f3d2880cf500000000000000000000000000000000090a277517ea7a1a7cbd68598aa1e16977cc57c8d095f66a7cd3f67814c2b8f35e17e20d7a26fa67274dc5aecbe778648159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b0000000000000000000000000000000002ea8cba4bcbaeed7feaac63caf21645ddc97daf9250ae29994fd04e798f94dab33bac6e08eef8e6c20f122bc5f88996000000000000000000000000000000000f7a0f6ac02bc9821a883393c8265ba748f9d7c3ea763037bde3bb0178067e93aea4dc70d25e5bcda642d06f41a7f18bef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a000000000000000000000000000000001618ee9c413dcf713699b7910989c20bffc5ba1ca03e973005f49084aba558797e7f9ec20cb86f308d737b97c08f42a6000000000000000000000000000000000db1daa5ed21250c696ca4da3e82f6623c54d643d773286811e21c09e9ef7c9ecb9d84d90b9c76ea9f65e04a29f82750d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c50000000000000000000000000000000012aece7d9e7384ae79e047ca4b4fe72fe541a825530d6c38b9a8fbbf8b801883ccbc3cae7c33e4d811198a7b7876c92d0000000000000000000000000000000013fb42fb1b4e7785c1b66364de150d1e38fd9fe3d8f209b7c168beacf4b26c35fe0fbb4a41f30adabe4314b20b16319561d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000033fce20f9202b89411dbeea59a5b1c632435eaf29e2739163b0837ef9278ee3903ae569931e70f79a9af5a2abd29749000000000000000000000000000000000a50360c73c3f735f97d7d71b21b2831f7d7fb59c594e85b604dbb79ccc884349cba8eab9ce613ed60416994322916db03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd0000000000000000000000000000000014d9a7dbc453effa7a76c774a289957b0ccd72994e568c0de345b482ed2b6db9a3a3e56e0fda159c25acb43b4a6765d5000000000000000000000000000000000b916f28e3fdc62d296e421b1684efd4e9a4b523f79dfaecc00872a1d17724e1e07e2386b4bc6d76b157ae94559d0bcde4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b0000000000000000000000000000000012a4670c5c2847bb188464dafe41360f00621ceb3b5da0a3dcc16732f4baeb0491664ed8c2f95ff9b44e2b77e698eb3800000000000000000000000000000000077b561ed2fe5c91b30a12a2df71e76cc4ac882301d1975c3cb176e22874e28868655db9d0c91003442b0277eff52669c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000570e1a0fe7f82c0d3cf38d90f77634f8dc2bf9b58ac473d9bcbe7242a4bb76d11f36083c90588a680004c077e957a9e00000000000000000000000000000000038ac2b58a16af0a3a0070faabe3969025440d9781e3ebc22ff873dab532d6ca1b0bbf21f32eb9728a322c158f5390fa7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af40000000000000000000000000000000004a3a287fe4401c48d7dc804363941b5836cfad6490b00dcb0ee830e876fa05a42d6e2b036a4e213bbf5b6ae5a4e31ee000000000000000000000000000000001877a91254211b2af54ea910d9efdf4b4e829fda5bf6b0c2dc849903c357bfc6f55b45c7437ba538ab6cc795b71e95796f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000159bf4b0dc89cfc9d1687d8552489b5c3e2ed059164197028bc67c51ad18b341d04e4b8be660880a76a44ef11e785ab5000000000000000000000000000000001643a41fe4104ab0bb96200472ca67064635bb728e6d909fc0026216a90083eb612f11bd5983cf4d7fe664f1c527b96a12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd60000000000000000000000000000000003dc904709f1da618b6a623888015a875b11e5baa5c10eb6d750354c09359b180858bf29d24bae18e7c78c81465659aa000000000000000000000000000000000c61dabb7085a1937782433ec46b0a063a34e102ae9a6b6bae7d82c94e93c3cd05afe19f0673f729761462bcd0d9ca5e5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256", "Expected": "00000000000000000000000000000000109dbdd05f92274f3edb0232b827a4abbe115bd4d51f8a1e7b8ee609511125ecf35ca60991e75a0b7973c4085b7d4bca000000000000000000000000000000000e14a168decb6893e9c447b4366247d997701471a33bf8f762bde44473d516d857d1825255d8b4cee8d04736cb369758", "Name": "matter_g1_multiexp_81", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009b85ef81b184c6383ff4e2695a8c261ab252ebd81bdb518001f110b2ba72fbf5014214f816c9453319934d8a010aa0d0000000000000000000000000000000013ce486b15a77cede98a46f66ae51d17713bef6dafbb2ff34c8f441271d52f4fa27fb88c5695f4af6d43e32333e68130acc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd0000000000000000000000000000000006a10f5973fd2aa312ce8f30ba5caad0ae6028bca5c186e4fd55ff4e3f5ce00220b94683e440b09a9fcee238af140699000000000000000000000000000000000ae8e9db6953ce2461bac3be78bebf6c4df8bc57bc7de375aa652d793bdb0899477464097514f0fe2d0badc9027baf3898c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf000000000000000000000000000000000c0c430ee1e9112d901b82e43a25ce4e5b61c81ed7ac7220d88bd10d44d28c1bd20fc8e1ad85f9b6eb43fc232594b4f1000000000000000000000000000000001233dee860032e2f9a67d7b3d61cea99f18b91620b76f8bd178295ac4fc3b8d0db4c4ff602085c7a897435a283e2a4eda0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f000000000000000000000000000000000061e9d3a919bdbdc42500b7daec837506bf0841caf35aaac34a3670517a59bf52343b47b46e8212208cd6fdca6b7140c000000000000000000000000000000000b87f7efb446cdba6e619d5fc04ca8dce8e57f6a76faa4a773c03ddc0666ce2d83682f24d8463d9331ae58e8afcc5641e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe000000000000000000000000000000001606311f79e836a03da5cacc4e1c3930695372f8f679c8f910627f86af15d1612d653c76d88b9d33f848f94bb63fa1ce000000000000000000000000000000000075b5d9626107a486079315a85991f3d77461b45e5c8aca6876287f624694c8ef1a4f5f0a5b65eefa8d6a4746fd2e5fa32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000012bc152cb7df01fd9ca35142806664fdbacb881adcf443051abac7c979d09a1c887fcfb8cad281f376ea3f6693812914000000000000000000000000000000000e32d4d6aa1f5046382c1d5e6e2f97319e8c6887b850b3cee498c482e35319a4f062be80f7f48ff3d1160ea6b18cf67824301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000001599c2c489535375270f0d1f370c6416c83c4043dbdb4999256f187e29c198b1f6c5bd1a52c997f01ebd3622c40feb63000000000000000000000000000000000b60ea3ee221eeac4a8a364eb52ee08579cf5a907aa5642971bd5523dee5dc6d6584ab993d33d9b8ad9de4a1a4f0cbb117a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff82569570674000000000000000000000000000000000b85c776ed6c9c78001ec7bf3412be495f40b0978d0582ad4f86ed54464fe562f9e699f727f36b2fc753f4328f0b2c6b0000000000000000000000000000000006e11a826fb4a8f0ac32f5c52a531508ad1363bf9b09919ccdb61ef25baa7718a4829fdd10fb6b680321cb7ef12d0c01d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a3180000000000000000000000000000000013539f96257faa2ae642c15f9c04e8fa7b2d6d095f7ca285e0dd90f022ec4a8fd74cf48557afdb57bace088b017b8ec20000000000000000000000000000000006cbc3e4291f373ee280eaface275e0334e46e54f65efc4e18b4ebb8ed1e61941d9c859903b56ed0d4aa3f4f3152b5b4cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a000000000000000000000000000000000675b4dab12db428a14afd8e696a64c0bb352bbcbecdcf2b064428b489194112f1cea4a383788e0bb0e97b7f88b817700000000000000000000000000000000013273075195b02abac630211c5870727a42e11bd96a2e2c6057d0c96bb60b73db72dec3135122865cd520c525588664a6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000000cb4629e659d5c2d91c5f909bbeb3381271ebde4f8486f76c1903e86efa78da06af752404ebddb3fc5d1a09ed28b3aa0000000000000000000000000000000019202a57e95d8d2623851973c324d1ed64b48b15388e052761493b1cdd6f3b54c6f47d2b312edec23e9da4c815f02e172e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b000000000000000000000000000000000a80bc4a39d62ca891044795e2b78f4eb82a3bf38c4ccb2e6d24ced4526db7c57ebf8b1951af0707af5ae5929f727c290000000000000000000000000000000001cbe991b082e840d8bd505a2eeeadf034f8f8c2bb530c742d7953089da1447e090d82399bc332127f14f1521c95f0042eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f18400000000000000000000000000000000112e7894d90a5cba2a8bdd0fa750d6e57c0a9938ca30526eb5289b4a59f92bddb33f59ca22a51d1bae03b850999180fa0000000000000000000000000000000016cf6b093a188ccbf1a000aa860fc794546ab0cf261784e7b7bc5750848f685d629ba55f71f2266edcf24d27667d2720caf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000005e2b8ac9124e8ccb6665842d77a2e9398e5b3519fa4fddfc4b10acb5eefceceb1cd6cc733e300ff95ea80d09e3bbeba000000000000000000000000000000001273d1990fa922276859d3921bbd49a452c821a9746c747734692d12c6f7d45533c0a7692d1a2d95e2d2be6dbfb3f6ad106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000dd2c7410b5f5ee63ad2a9ff3a96df2bad103caabe00a9892cc9b2ed2cc3bbbb53724b2ab63cabc44da7097b619f34c3000000000000000000000000000000000f695edd4b67f81f09fa89104c81717577cdd16db30901f4f04ac97e2e0749a80d34422bdfa85b5cdb65c042d90515742b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd300000000000000000000000000000000113c8f77a2409e0c7ad34186119833605f924545821895a283ec83bb6cc38c549a356b205c24f65be66fa627a378eae30000000000000000000000000000000013038ad87e3b3eb6545a0b5f7eec060895deafaf509ff6687024ada75f700d466df86ae5f95463c05f19750c0ce6cf56dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5", "Expected": "0000000000000000000000000000000015a7b2803cd9b078d457d3b7c62a2418f19c0cfa006739cf3878844c9e1ea115fa982a02fa6fa0cef74404bcf145952f0000000000000000000000000000000018ea40f019b9226cb0428356483f842ad73140a8da065889d81e35a564e99aacc5d5d833d35fd15713ec76c65f9d3307", "Name": "matter_g1_multiexp_82", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000163b380ea90b97146aa11c64b34de710e41b2ad54036a1a98659046f0e051e5961f30ea5ad78d8052f4a5d2a8388c28d0000000000000000000000000000000012afed5aa2e8c75e437fd796067e0c610a8a4c2f3368752413e6f179bbd4db25b18d5b3f8502186259a6368dd4321148ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000001bd4abe425f0418c86716516075a3ad09812650908cf383ec1396cbb6929bbc791f5cf65dbd95b51690b58ae3cab3f20000000000000000000000000000000008264362c7fa8021dec396c8355197ce4ef70e7b8894fe23d881d34b9a1b883cba1eba0e54d928b4eaa27aabde0df9b3c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a77300000000000000000000000000000000032112872b64559a03629b7ec8b32344b7d5f044670f6099d8e8b1a1d47223f9a42a072975c821d03b30d0994d782d830000000000000000000000000000000016042f6baa48d7c571e1f6c7cf3c7a0887bc4e2b2de51bae133d266dcad23c579e03d3284c09c83a54eff7f2151ce5b3dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b00000000000000000000000000000000028ea1499ad8761d908d863849ab4bbc155edeb03a7ef4bb93e96e25ab11c6dd0c21a6f06537a688189f08a00aa33171000000000000000000000000000000000ca3ee57dbe627ae681b12e0de4ed602bc3c09558444f38b0dee27320708549491a4482f7f101e8a722ef85e3fd742a5da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce4780000000000000000000000000000000018f397d7c84b8125844e874ea31d18b8705a75027d5324390e2eb7c9962d9de07add34a436db21a34fa7fc7898ef04aa000000000000000000000000000000001591f2cbc58c0841e5eeb8d9c75d8dfa0f2dc5e479d136905abb772a6170d131c0f2c9e8e55ffa215a4bd732c2fd85556aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab7700000000000000000000000000000000135452f0f8d4559ba041dbd2ac45f15416070b1674c9d8094556a289716814d2a4efe14857aaccb82c5ada5d6f0d15ca000000000000000000000000000000000f1c47592319db60db724c9d0649d0d713320be7dcc28e7318517ef80a3fda71fd1f4b722633ed7ab7df06218ee593e940273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a00000000000000000000000000000000003c75767678539abf7a62dcad5f90a3b4a54354fa70206e789a1f9b5daeb5fb6d9aa222476c68cf9db8a0789d7ad43d00000000000000000000000000000000139bcede61bcead99ef0d9554ee1c19db1869fe041671c199246824a923f5fd94e1da04fa17ec921bf6e82b14f126702b77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb5400000000000000000000000000000000020aadb198678aab5a71cd6dc33bd64c47be6d080d24f2f1bab7239808c10867ddcec65e27977b9eabef64455cac25e800000000000000000000000000000000141e58a9f8c9bd92d2de58bf3bbe77a48fae9290815915d7980f4835d805486d678ceee9676ab4fdca51d0fff411ab1b0be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000017abf5f132e8e466d2cae445d75978645c3b24284e1b7df7773c256ffc342d1484976ea1046aeb5307f735a69e2fd20a00000000000000000000000000000000087ce2fc44b9ed797f29c352393a8ea109281514490fbc7dc489acb55753fd5c577c4af0ca6c267c83408cd95b355e26c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000000b106b2b94858155849ec36741c7fef4d97ac704baa6752e8230e172da7208b7e9f187ef0a6cf054d00f2cac99235b8000000000000000000000000000000000d94c6e2349941a20884b9c2d702237c5b5ca2ed277bfc79e53452f1cd6f9f49360215d20fa06df238a7ad4ea253c93ec6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b30000000000000000000000000000000019b33665a81d0ceecd43f003eb34e1292945da1361adf118f36aa5acb71bd821a6732758a4aa6988e29d4cb70004df45000000000000000000000000000000000f3a244e578c66a9263f020e2f6ce49dd655c7e40a992c44cee40e1c874588e464f6254ba644e46adf348a26025d6d3ef3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a2100000000000000000000000000000000015618db18e00670281adb20c975f4774aaf169a653d5f583ff6966113fa773075db78507847586fcae82d6a468302706000000000000000000000000000000000301b18d0fe7d0db7793c62b3da072f4cc2fc3425583537110306e31cf63b228cb8c285029044c7b9439c1227d4c7ace2809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c00000000000000000000000000000000053001a82260b26e34e05a203c8233095da1da58c5f804da9cd6cffce07170e39044394f379173e1340da055066d320f000000000000000000000000000000000bfa2bc7fa0476eeffae4df98bd814db751eeac1dc67205c7629c9921928b55c70c2abe242728bc078bc2685690a38503521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac680000000000000000000000000000000019a7f78102671f6d84ece4a5bdc54e59cbeab60a8c6c15a708e0169f42a52e98bbc1f8ff52f34959befc859d308fea250000000000000000000000000000000016b5d76caac944612d1dc687c6dbaf10ba60a12b491b17b6c1c876a5dff933c4bd9c6f923e2ca4cd1dab38fb06dfab6a9c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000007741d8f72a5ddeea2fe82fbce4b3d0aae61e1ab9243ae6a3200711051ac74f30a4dadb597130fd8389353c230b6b7d3000000000000000000000000000000001809f1cc2fc23be0f05b3d12e6891a6aacea121e6db77400638031065d75c7b3fd9a02ded481eb3893b2449aadcf53d6dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd98966400000000000000000000000000000000013d9fdf041ecc7f2c728fefbd6e9da3169d872406b6fa77a52e342fa8852358b02bb2ae7ac77f83e2b25f0120603d0e7000000000000000000000000000000000101ae8e945d31a98c4dc3ba0e01592285c0c92721372bee6b138d9148883970708ad5e585a1b81d82ab0656a3b03a2c00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0", "Expected": "00000000000000000000000000000000193115466c33711b07826d2a021792b7c238ae67e3bcba00b24026503d818f912c5635f85e85402e3a6b2a8152027afc00000000000000000000000000000000157fcd63d3a0e48e25ca0196b7ade453fcefea33f09123434f418cd1d65bba65a789e4c7d6ddc92d4fe8aaf6bffb1ef8", "Name": "matter_g1_multiexp_83", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019f625f232faeac09266c2c4881f92c980db551ea236dc1250189c1e71dbeb151cf74e43b4d5f465c6ad92d75457d10500000000000000000000000000000000175ceb7cef0f8144fd4dd82428bade99833023125d34fb296f198673f4848bbbee343f2f2137b55b5f8c5f74032c1ccaa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000001353a419548d05e568f36adf72d40ba8b30be9a78732660331a5196b0f81b52330ed70e5c635acfa9ffbf083e46c8ea40000000000000000000000000000000013ca17c0dba35a747bcd314d87d1c6558e9f569955aba3d958cc5736db78d16132c9dc8f93d5eaea749a0452c13139da92073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000019a1bdc1f5a43fe746df46a7559bfa0bc5292f574fc424b134fb8b2d971e191b3c5d222d39515dd145819d56d5379d12000000000000000000000000000000000a08d0b7c7f5d71222e984bf574cdb7de76a7b3c61ab5a3ec202b295c62366dd958ffd5bb5a5c6c84584342bc76199c62117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f50000000000000000000000000000000000f2c85f34994643712207fc431219b925f4e701732fce95bfb387ac26ff95c9b10408d24aae5005e437bbae924816b2000000000000000000000000000000000d4377368df00dcde448d8399ceb7508a8fa1c17e9d9a5e09c4fd7c09c253529c07068e4484c7e7c6d3ed6fd3ca777fd9087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000d1483ef230a2ce75a59e07f83091291d2524b5d043db8d5583914a6775ce2c80368d9441aa2dd53061a8d9121a025ac0000000000000000000000000000000019100e75a72e07391db9574b3fc4aa1c669436fa802a1a5d71146c5f4b7fe118a5ee71a9df50ff67633f161fd151b947255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000003a88ed50b36d92aa4411afd0a340497962c7740d629edabd505d6023ecb8f9daf0e5bd8ab9dca26ed2ae3ecdfd98b680000000000000000000000000000000013d9d64ab16ce9401988db4855b26b994da09481a339c2a2597401adb72c80718a4df242776f09ed208a8f34ef7f67e6e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c40000000000000000000000000000000013b16751ff7f6af64c06f9ae6f59e1eb6c3ac76355e6192e6eb44bd1a9f866705eadf0d2907e2458462ad731523bd340000000000000000000000000000000000ae691a4fbf3d0fc72c0e14d4b31fc19c52ca07a81db0ba93949c56a9b75433257d784f7bf0611259dba8af77403f536fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000008ea61aba918d691a0d04582e1f48d671df39bc7de29a6ecc17b31a32d485fb1dbf499e01a9aae5ea21be5d6ff9808de000000000000000000000000000000000f7e8863a541be553b36b8424ba6ad057986a9f78454aea770449a23de70fea8eee6bf8aa30e96e90df9a373917452f70e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000010d41968ddccbb34b3faee226750e99301ac068d8e6f13e72962b53fa2d019da108af82bdadb3cfeecfb85f53607400b000000000000000000000000000000000a90e50ac4e0c39f579a19d49e6f64de6bdd5d6a3f9a91ab654f5be01b258af8709ce1c5a994501177d1c70b25e474a9a49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f000000000000000000000000000000000e85db21593e8d3d86df87ceeea7d7853758d69e15edd53fd7da52f0328805db785aa9aa5db25417d76d796200a37d1d0000000000000000000000000000000015d76c5317e1c8cc5a58a0cf0700ff73d92e7f60f4094030716bb8c657d5c75262825fc0683a88278018b4899a1c1ffeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a70000000000000000000000000000000014c9d6aa24bb34080b9a99d31e1bb431e911b2ccda3c8dae9c2c2114abca597b3849c5b3dca756d0f9ff97616c0b724600000000000000000000000000000000050224129c08fbb2f2d16596f83e2d09a09526851c4d52e8d5f0afdae7001af0006edce648efe7d94b6712d012817ff753961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000da4574f20849e04bafbc41bd361e8f4411815b9e7c2fdaa9a3ee70d4f608f89166dbe9e1cf4ff0fc9ae98f27e115c24000000000000000000000000000000001463727b23e6afc17101cca45de7d08b78358605c7b1ca089fc52f6a3c46f590210083103e51a122ed0768be2adeddefa04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001363808474ae9481f54d40fd35ed90c23d4349403d43af0dd603f1db6f5fd5ad8b77d21426977b78f1f5397df17f0bfd000000000000000000000000000000000118560d0cb0eb2fcd3b2d51fb2aa379112b3075e1d4c20757ec241a4877af271700d3412a8fd6f3f5a3dbdf4dc8cdc9b688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e2252700000000000000000000000000000000040c13a6f53ca485a578c6f3f49d917b774f7b2d1b15ed3e748a47b0bc0be8a7809f0ccf509f09121fdebcf8af46023b0000000000000000000000000000000014fc7869df366473b2c4adc2c0b12acfffeffaf22b4856bed6ec6d15f0f080596b81f3aceab9360e99f35ee7c43f1e2fcf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b600000000000000000000000000000000177b54249c613f044b40a11047778c86f09b20ab387ecb8165c83b36a1af046936623fb00764740a90aa232b7f7ae6bc00000000000000000000000000000000040a52fc58007717d6e1dd8486cfccb1f75827c2feb2b7d59b927c4bd23e5ea80d120875f611bed4b7c12b8a5c929475528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f365000000000000000000000000000000001918e41c557305934aa72aaa361d15843ca77c747ac16cb4c251a2f0d7c218b60a5588b0e5fb3573e8186a48d725e50f000000000000000000000000000000000cc4fa5302c177f9ef018445ab722e568347f4f970dd893e3227756dde9dc8cce3eb2bbbb4c3cd98af0ed4a45c022cf1c32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1", "Expected": "0000000000000000000000000000000016da14ee1ec80ebf06c0622a500a8eb5a560dfa3439a8e53a19b51c6c4576c31a5486c4c49a8050cc1dc30287f33b5b40000000000000000000000000000000003b04355b2d78266675927706874bb7fa67d441886972a8190a45398942622f09ece67ea74d49bd97827fee82d4a7a37", "Name": "matter_g1_multiexp_84", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e0ae8df03e4d6e36e1c156a35425a3b8189b56e8ce90045d16cfebf7fdd973d207db6391dcd007c311af34f495cfe0c00000000000000000000000000000000198e58d5278b2a82606af16a9af3f023b7182b6b5b2d685fb667714e9fb5c7a3fd5c98dbcc84ee31fcbeaa8f832d7c854f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe0000000000000000000000000000000007a130c85d67f97fd0dc2159d35be8984bfbe94c28d9d96bca8bab844dffd9a6eb3052c619646a4e564c0d47864b31cb000000000000000000000000000000000e2b8362ef5fa5be398a3589413ea69e98b15cdccd203119b79d96405c2c9ae9ca8eecc7533512a25421e1748ec3a1b74b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000acec379756a1fe9fa72f03da4dfa18de1fad19281f262ff39fec77684f0798b6d8aa895db93dab58165b67a875572cf000000000000000000000000000000000a246df19a23260961ea578a68ab4ae8811f9f391f673eab2b6fdd56dae8ff3b059e5b69052c9216529603e7eaf4ff306765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f00000000000000000000000000000000001bf749b61d7081f1e6141380deb6a5517d64e8c290363306fa23d6ba3b4e72ef53933f15ae77060758287a5a5c2bd4000000000000000000000000000000001661c564a5bc4dd852f35660d1e7c8193d76a48d1f0f3dff25adf312e28ebe9ce8972366ab224a95a7c1f6146b9f22412eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa000000000000000000000000000000000462a37cc68530a1c45001cda667e1ec10283b826b52986adec03db59a266cafc18ff76a666c9de9fc2384c5e336404b0000000000000000000000000000000010736bad21840f49466d9db82f01a922f4d6ab71f8d8ae246765300531b2f806663da2a8c16c644cf871a877b210b9e3f8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000013ee85b0c8f999c9d0682bf3f18a553b64aed8addf87e4baba55c6ad88de9c9955b82155caa83b8b6b7961d88c16c7dd0000000000000000000000000000000011bbe00b5ddab0b579375e2014021e3bfb1e11b7ccfd774b8679896c0ee34d1d19890fe5cf10e33e3332283b3a3dceaa28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fc3750c957b3eb656ad552c3997755bf28a54fe4aefafde15619133ae04a47f7c65122c86ef36fedac0c8e0d93c3836000000000000000000000000000000000f7f21014b7a9f07c2212af1b85395ef3072b84ee5e59ae675f6fdb9cac858b6213a264a202e29b45a57c69be5259470dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000d1fdcb6768654b6bc1b4d885039f1649066db8037f212b2d699c02606257388000b0543d25aace7cd1426462ec25c6b000000000000000000000000000000001386eb9bb7d8be5cb9e74a37759458091c44eb814dc3afbdf017a891359831ffcaad85d00d8e100886cb5624562ea0390adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000f4d6551f5587cdb4d92e13e3749f977f5bd35b5b71667edd79b5006d4b0943331a0b417f669c6125edc42099bea22be00000000000000000000000000000000041b8ec8547b710bf2c15ff41ea779f996db7996911a5b4ae9f23073e02b2c252592229af738f684e9cdf48aaba0512a345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000000c4a3756f2affd338f688ee90501f4bf4be43a4549ad8ea6aea69e5a4be015c97ef088da1a39d1103f866f1675f401900000000000000000000000000000000023e5d0bc92794536d59425c4bdf18dc5a208841953e5d45ae91f25d3c61bf66e704a8ca62a574ffefaea854fd23b8d65ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c0000000000000000000000000000000010129a00ea1c30e98c40a6c86090327d0a9b6c25b488cb0e369bc5a0e0658ec9ac9305e5d1469dd43395f72ef8a0e7e80000000000000000000000000000000006d2f5d4f3f8169f722427dbdee62f45f9791e55988910fefe188d6535fa15e2aab8de5130e81183e6ca25a8009be66f853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef3800000000000000000000000000000000005364313c0d2220ed57bf22cee05b77a53c24c97addae502c7b3275a19522b8ae8167194929770191b96b957b19e5550000000000000000000000000000000016ca50cc1aef3890dd338c8a89b906812ce26e0ef9035d1a026f686b0eecab718f6b0ba401556423ddc99d96dd812d566dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d00000000000000000000000000000000007dc52982caf2f5efa3e1a21e22cb8fc53cd0355f2777272806710a96a22f8e896d001bec053acac6241c7637df158a30000000000000000000000000000000017e9f4fb0adb96150095ad5f0d464549d1489d04c4556576865ed3045e0c477beea3115a6ce63910f797fef29f75bad521d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000000695feaefc8fa22f81bd48a41e6c85acf38fa542e96a7562b8d65834c2f64cf5770ab6731ca85b0c5a80a73622acb83a0000000000000000000000000000000003df65226205511218c263af6fe33a09fa3db22e636da54dd967741657e9da6367fefc5e33a370947f2003dc139765083d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000001588a4aaee74856a9d41305023b7eee367648085516c8135fca8c0a6c9cbdecdb2d7b44317286f3a06f92b9eee2470170000000000000000000000000000000005aa06c47bdbcaea82e910b8a2c43c13c23bdfe1897efb2a57d622f5251f0db6293ad21d988c3ee30e33f3a40865fadf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000140d9a251d355cc6a8ff9fdf2223df59747eed11ad140297b6189a8d49a711ec748447ddcc45733a3c36a48da8cd46880000000000000000000000000000000008ce7046871c0b7f781c667958ff22da6ef5447bd319b2df36c9fae9f5597c020c12c7fbc733cb75ca8f9d9dfd942954cc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3", "Expected": "000000000000000000000000000000000f1abe4dabd68ac4443ff50c0ecc5425ad60973dfbd2b29462254ad618b303dda061d80b28117803159ba503214b9ccd000000000000000000000000000000000d3da8a424576cdfc147e18fab66de0d1f93c05c0dcb08d9d5b2a9f5dcda76600b771991bf2d67061ea5b96d20a45c14", "Name": "matter_g1_multiexp_85", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004d4ad5e9acfcabc0b93eb9ea59a778a37d7beca03e285382d10d97803ad63e11aa2e3cd1eabf72383d93528e309c28b000000000000000000000000000000000855cbcccda0476699ad3de8d58b4502f9e61bce8d7db37e9fd26ac4649a4cb831cbb74ecf044ae6014c21148382cca3864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000156e86fc66a8b684327a4de77c31abaebbaf2ee5f0c4d5f9238c7d4683f08dc78d59fcdc25928d180a6f292bee23a523000000000000000000000000000000000f64634ec7de1fc93959639991df42e7dc621380f4433fd7efeff72ce75f6ac7738396a912f78ecfe70bfc4d0ac4239093064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f539000000000000000000000000000000000ae2c40a49f6539bb257fd759c2fcc9f7b09d00059c7a7fd41422ce39aa0792413894bc716d66dc79092223b63de6ad80000000000000000000000000000000017a82c6a853fe29f98129998708f0d4d2b09fb22b07474395050d87cfe4d3bbf94967e05861c20680dabf3f4367135a75e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000013b950aa9b7675322d7b39e81b13b14f2480155f74bdc5793425a02f7de41dc1ebefe4f07accd3719feecfe366e93c440000000000000000000000000000000003378e83277e4b02c3b517d3a8cfbf2d2a6585d028c723b2a263e6ba17faf14bb9aea301cbfdfb73f84709e2af99867693f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000004d87c87f8f05a0999c712756bcaa0572b70264166b16eea7fc4785a59cfca18d5b819f0e65e193dd7ec38d0756b84f20000000000000000000000000000000012f64e2dfa3f00ad8f7f68e08b24aae83a049390fbdbaf570a7973d8516dc90e9c5c9211130d5c6c09f5b29183e24201145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000851a636dfc668d1c5d5467774deaa78283a6f56cc514420fb2b6c58ec831add57b5203e31377a57adcfd9097a1cde2e0000000000000000000000000000000008828c34d4e712bdd5133e220167f3424491b9f47dfd95406bc833b3b030037c0ac0d2c84b06b4a2891c8181359af350ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000011021119ccb1cedf88be6f72d3999df899efc4dc28f828831be911582b61894aa37302f84ae9269b97b03a2e30d66c93000000000000000000000000000000000c373df4c0cc1d8a75cf2b9a99b5889811d3ed42850f55480d891b2f44769a371fa4894cb5bf78b7e995b4912cf47dad87bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000005bbd2831bb4eb8ace45ed719056b95dcf5bda8831bc1495f763ff5e82be9708a004a00ecd102d4fd084579d892e5da40000000000000000000000000000000004de171bf5fab4c89783ad1d0cc9fe697b827f023ea1660b0fa2cab108fbcdc80837d46f292b6062761dd865bd1f905f68cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000018b36452aa579eab36db9b0417c999fa334292bc7174bb88e4bb14025a20c86437d5cace5369b90640c81edbf2d60f2b0000000000000000000000000000000014278d1cc3fd07e947419a6a0d7f7bd5f9e13fbd63779ffadc150e3d5efdd1a3f6f6e5ba8516066b75e1925282d0e644a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c5000000000000000000000000000000001365922301de7c81b839e970775854881955f35ef7f718643a97e54746b9d9867ced3fb7525caf5b5bd0d382de02fedd00000000000000000000000000000000000d37c4e106e51c4cb65fef8460846eab04fae7e5ae1d1dbaa1e0bfb2eab7f2e27a9cd5c3cc942e38b021ef71827a0224c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b0000000000000000000000000000000003b9de0464ac24606ae840185d2ca6cc78773b674688a028161341b88907213e275d7dbcb8d8bca15b483922a09297170000000000000000000000000000000012ee2a578c09b7563508d0d94ce6ed75d277ebd89a7f1d6095f8992c0794b4de12e33ee24547c271e17b7a045eb3bf5b17ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a0000000000000000000000000000000005b7aa071b76f93c765f946b96a972c1d11a2c44244355e90cd77ff069b930b2e8171f7cb1ba29f7ca6e62d88cb83c1b0000000000000000000000000000000012cabb25e52f00f89f2758790f9a81d0e336ccd7bdff06a79552a346d1966f54a5157130e5aa8db175aa64a431e19e494615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000dadebc30ac3e033f433d8d012ffc70adc146f4d9574e5431360fb4a8ff0891c8a9f38a8754984a385d704086c320ca90000000000000000000000000000000000238439bc4e8c7dabe260c7b40d317014463c4728d79f521e7e321346747e9aa65bc6b32ee5920969c34421bb99bee9d38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f700000000000000000000000000000000029df69b4ad5cae9fd974da7f58e4c55e83c61eaf011b5f22e1308b56e2c31530c170b304d39eb3e8a3009b67b308c6700000000000000000000000000000000140451659b4d6eaf05db63be5a7b0341612747eea7536b958b0620bdfd7b9918e8bb76c05eb2a528bf4727e38605f99a364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c745900000000000000000000000000000000062493361a1a862e63eb8f20b0610a78d30ac8595e4c6c3487cf3add7cc38613870c2ecd0cb5a869110a99b76fb9055b000000000000000000000000000000000d8918e018ac5490c91cf2574e6a6962b69c17883caf2caa473de172b14961780fb237236b56a236ce8c674dc9001547031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f00000000000000000000000000000000016470ccd107b2afb9ca03a0efb958bbc165304871e683fd606d2e78f65e34885668c6ccb655d4fa98f5776280e63cb3000000000000000000000000000000000982eaaa34f9301fe0ba1915cc5632329715c506528860701f5e52d1d77b8fabc89706af2c4ab3b729251b9472cde96f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f", "Expected": "00000000000000000000000000000000112fdd661f948495ae2b9200d959ddc873c53c96ee4ec175f6748e62d76d61d6b15970d3a3a52ae9bda30a822ada25b8000000000000000000000000000000000f5b38208d69b5b842bc22ec9d91eb6b653acea5cb16569c61bfe0921f2d8ad613239e87d48c6a9b29ed6a5f43578987", "Name": "matter_g1_multiexp_86", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a9494f10e6187fa12d88e350ab84ab5bbf999554924e781d6470e700c3da78e411b8627459b3359d7363b088bbeb0da0000000000000000000000000000000017edbf1108591996f28ae17beadfd6b52340236c2741bf8474dd7471c19c1f62a0f28e8d8692cf3e700ddd86a931dcab4aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b900000000000000000000000000000000052f9c6ecc29239c614936bf9ecdfec677afe80de019230180d0fe529a2e82b9e15d6e081b02475e2bf812cea3ba6c640000000000000000000000000000000003dd0afc91516b50d9027c0b132453fab92b165c08428fd5c2cb994646b6b1cd5b82b7c3f7924e4a5cf8b45575e8dfdc22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000da6e13230b2236b2bdf671bd5f3f8bb47bfc637d6e3f1796b555a95e51b86d04fd310f3d3198dee604baf48f69ce0950000000000000000000000000000000018d209b03f61056147d6734003daa776011b70a57e1ab17d3b92e2565b31a846d8fb7c3fc6fa1fff04552800b73affab895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f683200000000000000000000000000000000116c72b5bd9d30182463c592adb8f73c16d22bb4a22832b8d47b683da5f4b8179d4c80d361ce69f92a393027ab29c18900000000000000000000000000000000026dab8d729338903d46a219004fada41eb666a9a90d8ba115f53da9e89a7bc5d824d7f4071c8859df52b3ede7b7dfaf4c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000004e0627475a0d4da458475dbbebd6c36f4ce771bc2b2a8c6adfe9d372ffed05afbea207476af26974476c0cf51a9267900000000000000000000000000000000199ebe83e44a269752d92629810d0c5402f53a1bee03ccafe0b3299a9968ec45abdb5a74a6d90cb026cd9b28cfd2b89f6d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa000000000000000000000000000000000b4918f4bfad81349edcb45439e148af7af6664094412c9a51b887271cc3c46e34147c8a306a19f08922bda9c7146c61000000000000000000000000000000000afc3d1a7c4b6d899149801cb74a7e64a126631b3e758a73feda92a2867c53fd3efd9adf025ca6f6c762029c57706b0b4cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b1384000000000000000000000000000000000a093eeb354ddfc5ea3090b20312788923c5db9d78905dd31d5bf15cd83521f2f186fd284de0858270eea05d21801aae0000000000000000000000000000000011d047410dbf6df20f81971327b38996484e0862a9f71879ff63462e189471c1ba391496753456f0b5379a3b36380e1296111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c0000000000000000000000000000000003d8e8e3a442f911e23b353e9efe396b746360254c14216c752fad17d96d440988d5a25f044afd37f12d74c89c8cb2d700000000000000000000000000000000179ba95a3d3b5ddd3d181e2312385f4ad7232d9af0c28f375e2036157e4603c1a01aa6c9c91496bb28508e5885bc2e599d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d000000000000000000000000000000000f56dfaafea0ce3152458b7252fac14ea64483e1d4a00a44f95bf3932eda2f2c51f0239e6a7a503cfdbbdd88aef2f4880000000000000000000000000000000010e02e9be7c1b795ebaa84f83bd27eba4f12dd49b146db0d788e37835338d352445e82060dd595f616b4f6d2d03cf4c911ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d300000000000000000000000000000000010ccd262b0cda9ad39177d31be0725b83e935c690fa8e07bc7f24e26f8b03122173f4ba43fe8ac933a7fed79f4496c8000000000000000000000000000000000318da543dfb04005a3cf6d93d6bc4058b4b93c4cd84ef978e6a30dd85d60e5e359b4f518842e73d182567ec4fb236b8b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf4400000000000000000000000000000000003dbf6c0676cec0202e328bf408a8fcc38758db1adba3e8184cb3904ed204b7e18db2183f5a1833737ad8eb089afcafc0000000000000000000000000000000014d9add10a0c739dec7fd09c57b3e959f3b7551eab8423ec5bcab4b14e63b7a27f128758d63f8e43a22eeec7bcaddd41a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf2411691190000000000000000000000000000000014d0230f7d5c51e6fff6490c61972e2564bc31fea4a6d1f293424934f75629cb96f189c80ab32a79b2e988582d0283960000000000000000000000000000000011813cbbc0cae4cf6a8d5d58859f1c3b75ac53819129f92abe0ba9123a1a277b55231e1a24745d0d2ba6242ee758113c87fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c5894500000000000000000000000000000000006adb1e972755f04cc57170d19414e6930d0e6d42c09f587e490593a5c01ce6e827a6dd1e21570ba11c7e4277d532e0000000000000000000000000000000000ef599058025f40c9f77ef858aaf314faaf8d72277cd319a84a9d7038d81b76aa260df0516dd38633b22f9d3996e4761c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000008190fa5e3d23c0186ba502a5892b76cf8faf2c15c91ee39d51b269b6bf4bd3e7ea395787d989c1a14ad88f3702cd6d00000000000000000000000000000000118d2d1b28f9180155277b80f1a7937dc7fe6be3b00cbf6a7ddfd08cf653ed11a4ddaa44576e70b27cacb7646a100d03f29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b49899117000000000000000000000000000000000d28e7ef8433f8d5399ce3cb847f2633392bf44ae9fb2d402ed8e7e6a22de35c39e4f09ea0fe673ae3cb652f75ec80bb000000000000000000000000000000000ebf2ed9df06e2d5688d0ea812b7f9de78fe292584476b20bd62066977f5e221dbbd8f552547f06a3e821a53aeab83c1125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000e162f9ba960f452c269bd2f9f06e8bf1ffe737788d6364b1f75ea2788fda7e265dcaa907e45bc6ef7a31c4791b470e0000000000000000000000000000000008a778bcedb58f562c7b69ef3073c81866a395d6408829816be3172e1e825ca6b88f156ed2b2ac5a8784fac62b893896bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6", "Expected": "0000000000000000000000000000000003f6acb4e1f7856413fe9317bc7cffa45f2053ae62e3b5e2b82ad57b48cbeb4110f53dfcace08bbb1955c21b50fc636f00000000000000000000000000000000172cf1d257572562f9fc390f96f2c39dc5809765b0b94240a554f8bbcc44e3658f22e81d1e6c43742ef24882934cbbed", "Name": "matter_g1_multiexp_87", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000013d3d80910d9f43a707ba389b03bda49b65081f65096bdef3942f0bde2122ea575abf810f400d47ced92c45dc73837000000000000000000000000000000000755b4f5a055c718f268cf3a74533fe8e8ebf37aff3045b58927ee6ee7a862c8c1cd61f00dfdaf6cccabf981fff16c7908c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000001530565bb621f7cd530c0eeb4cc41c2587ef8123c552aed339f80711c157e1595baa140434385d0977e9aed2629ea76b000000000000000000000000000000001806c5a90120fe65450e84ee0a56e0176e944a3fffdd2c83bf15d7dca875790d2f842eb31f640456a1221e44035ad33ca0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000143723a10965da7b47bed0d0b5bdb6bfef5b748f6e185ff2efb73c5756d41d77b8c217a6d92245ae36e0add4d743e7e9000000000000000000000000000000001274e8842cc812435a576b2ac19edb84f72d08cffa129d7f4e44be5cc88b3449ecaa719b4d76aaecf08ddd30f7b184ddc252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000379e08cc1f47014f7eede433abbe881818c0c3a9cb02bad8cc86242aeb9f9542aedb67313f494fd19971a0a15d4ee1a0000000000000000000000000000000004e83a0e52981faf6a787d0600ccc457ddf3bb81c76117265c1bd011e5b4f3237383e97dad3b019623521b3c94d67df36d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b0000000000000000000000000000000009ec14585b72733f621a58f35ab30580f131c93db491d4d704c8da2a7a0a1146e144575083bd963238434e2af48d3d57000000000000000000000000000000000ebd1a1c160ba7c8e3c20745bbde05f08d7f3189ecaa831d05c6a34562d6d3ccaa92472c67bdebeac8494658abf2c0405c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b197300000000000000000000000000000000084b7f967b141c94df69804a723169f69e05629c97a7a8c60b140787f3361ac87458372c91e04c08da2d01fa96056ef8000000000000000000000000000000000d731a1a900551ca569b8066af85176b934b94332679aa59924eb7d9a5fd776a55b4d7e5ef2413c53c244c848694b06411009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a0000000000000000000000000000000001847861de1064a4226435ca43c1cfbc5d4660fcac177654cf5d497ba9aa5a6322f1156adafba852633e111576698bd00000000000000000000000000000000005ba738972bf139d91f0a426c96fcbb3b77a01af0f2316f2427a20882b5f355772fd6d6016ed77c31c13f88b26c628763e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf000000000000000000000000000000000148907d2335e046c50fe213b717fedac86eb3920099526a62b4466749d435f5ce11a45032b60bd5d7b26799adc63f830000000000000000000000000000000004bdc2bab60cf6df6dfd25c16f04edd96d5021b97ef38cad02cc1fc7f12494098eb793d99d15b327185718f81ec0ea620444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000145ea0ffc3b24a623d74c27b84a390be062542795eb93a2f71f9358b44b76b93dfc0a2ae507f07a8a07edeed2410e5c10000000000000000000000000000000000d407c6c245316b5cc6b62efcd082829354d7e9e69ad739ae0ee55e6096ea08a48c59ded4595032093c32634576aa132035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc736000000000000000000000000000000000123f333f3554eac47c8daa1d4b362e42de1834ba9f55e4fee138eaf1a057036aa6ff9f50cddc78dabd3d5557b05b8bd1000000000000000000000000000000000116d786097bcac320327d7d56aa734d76d48a677e9c02ecc0bce550d75082c319f568d94b41e1c57c6075ee994e33304bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e826000000000000000000000000000000001012b1790e287a6328cbbcf80eaceb2c518a70e80cfe17143a41c4045e8c6c5317aafcb34f4f56494b401a8a9f21b5fa000000000000000000000000000000000613a88e513248538c1b767ba4d3667bca7aeee7974f691b7e4f012ea9b2b32603eddab0943229f53324c51838d18fe3369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c31aa6f315a1102ea973d13e858d079221087edf178d98fb05701ed0a159309fed05942626b29ade066f8cef465535000000000000000000000000000000000177a3468b7de9612a93b9f2bb3f07acf505f56c63f798b4dfc38a25d0fc133c862e90ec8b40dc94004cfdcc9da197ee7ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e12000000000000000000000000000000000092095e7a431ff3a8e51e26c24dd4a5fed6d4a4a169b5ef79e8822611da8aca5d7c27139a911d5473442db9ee1529bd000000000000000000000000000000000c59f5a649682e864a792ad50fad57b7cd14cbb19d1feadc3536515f01053fab26950f56bb78d5a51f4368e73c19062f2cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca3610000000000000000000000000000000003930511780f28217a125f524ddef656581a4ba2d461730f0837d1846d63258a02e659b25b882a3c3d077c880a64e3cd0000000000000000000000000000000019c682245c941c76605502785b1f79d37f65cf9ec61a4558092973bb2514de4e5852fc757c2fc7eac1b01d414248acdd60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff7900000000000000000000000000000000000edf518026cbf2dcca1d46340c24fa947261bcef36e3c8d026a09068a10a5afdb0964b54b70bb3b27e27c4d2e0bf9b0000000000000000000000000000000005cf718694ca47202be8c0afd56c88742e2b467d01e7b2330de778c434a57610fe7b8bd6071836a58f5d6b2876cff05a652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000013db13bf10b6d8b1ce5dccec98745dab635b8bc81d03601785185cccddfe2dfb3f3f9f6ed16d2c1a7a6bd63264b094d60000000000000000000000000000000001080522766b6cb5c90e6e0ae11ab4ded3db3ea3c7e69d00f29155283f7b25f762eb35bfeedf00caa83dcf04f22ee72976a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20", "Expected": "000000000000000000000000000000001975e01d64d38cb8f852b4177104388aba62c167da2a8530bc59f510a990ed57be0c6ddfc92622234a121ca407853dbb000000000000000000000000000000000de737b9097065462dda93321d5881430f50631f6f3deabca4c41cd491f1e441139bf8ceb598393ab2a3424b0acf290e", "Name": "matter_g1_multiexp_88", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001624f6ef9638cdc5f0b16b47ac8c5465cb479333a4ee4caaf6d2b656464d8f84387f01bc1811924312e6cc1e29a590c300000000000000000000000000000000012b3bcce18f60c4b2159df93a2d536bdcadd675439371acce011ac5b542fe1bcf89161fc3b3644679a395aed31dab569f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f0000000000000000000000000000000005d49b4ea69c41ffa7727b98db08ab3fd3ca0c0261ef04b426ef29e724bd6158b3f3242cb915cf0992f2a631fd9b4421000000000000000000000000000000000f635c26698cf5dffbe25ff496f80c5de6b181f94a907204b79b548c1fee8c7dd426b49e9eb9eb0b17e34a26628c38e71deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f1080000000000000000000000000000000003a80767130cd3c3fff0610f215337bc1b4a88886778fc0dcb6bd3cf7bee48f4c23c974c8883e2cf32fb01a84f9e148f00000000000000000000000000000000173f518f3349c1f704fd200747158940ecc395b04b4c476f406cc27836df182c3f1b707aa05767ff1bc75de42dba2a824b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000011874da4371ee8bddb34bc92fee6bf51226878e4550aa33313a434b75243c1f2296c1d62d9f31f6ffe2735f4f26a8082000000000000000000000000000000000f82551ba2b803e35c7118f4294626c151c7137eb4b97aa5265ce383f7ebc5ff5fe381776eee724aebb963d2bcb3d9f6124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010501308d1a05e69700111431a0ca99aa41a991555b9a53df9c38413c67fa1b1836853bda93bbd8679e7724b3141a8d0000000000000000000000000000000000b033cfca384e480f73a4f8f79ceea706d7390e5b702305b79e30890e158ede03814d1a0dffcc3608fcb9926c5c65eb65a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000c7bf31a1f30f8e0de1a4a77b8b6c115d1a5d825b51875cba3db857a9cd2c589696ce2abe5a87acf8d6604c1f1f89ab70000000000000000000000000000000019ad7a6190a69fe1df07d55f8c792fc72cf2be11bbdd83c06325682bdfb5c31efef11fcb819d39f25bb1978570a250218c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab870000000000000000000000000000000012befada1cdf63d34ee2334ba2e42d7e69ffed71a39714e7ed89a86fd5cc1c65a01340c986abc37e7e3ac5a22a2bcc860000000000000000000000000000000006e5b16316867dc33a9770aa2283691f379581ff2b0b7986003174d4862d8b73bcc3f325c9a90097328f881b15f877c7f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff0000000000000000000000000000000008e763f110c9415b63baf27236f1c0975e7bebc04bdaf47ea0d3a2709a455ea48ffefb7551a73c9d599bc5c9fbbca78f000000000000000000000000000000001492e70f2831c87222f7d7a9d00842870b77aa68e87b8cdc9d8ba61f86adce6ea514bf5b8f9d66937b1b640c43b02fac22d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000001685898af1ad3bfd350980872e6438048f6cb37398ceab33d7bae1d621b5b2859e6a07b4e4db891af37e29881cf573ad000000000000000000000000000000001084663fadcf81b9818c999c26a84c6f9a3a1f71a0a2982b5c6d01c56c2974656c08e4ba7833d1ef8bcf9af53d2f0732e816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000013b077eb9130821bcecfe9b366c7a14f4487121095d325e74de44ea206078a6b1ac7d29a4e80f75c7714b6053cf2995a000000000000000000000000000000000b825b95b52382195416477f0bce73f06167db02bbcb91944e9e7534f804973bb363adca8b5ad80e77b70f4f1b9654d004f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea00000000000000000000000000000000014c48b3b2fb994920957b046643bfff19533dbe533df980dc60d9c852a3d07b8cf67454820a89ec9c7ea73a209f911ef0000000000000000000000000000000019b19e64d977d40b95050e4af365541b6c815534dc4abba7ea0af4b0a7e6bff0495fbb347250f5b5a48020ac20ea61cb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d20000000000000000000000000000000017fe39b732e6b815bde4078cba9f926e117349e3e49fcfb6308a0a09296fa27da4580d8fd18b0ecfd0ca68312cc0e5c10000000000000000000000000000000018a4eda1862c5c296de2eea0e720ba13f8a60defc65870f0112ab394e8160d6e1a0beff5db8c450d8770792b7efcccba05edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000e65750f3b9690f25b5bf80de0d76da21752a0daa8ce01b2bd8d172577f6c7d46c119ed20e73617ea163575705343c4c0000000000000000000000000000000019d0f934decb53a477b37d894d6e651a8a4f25b9375bac6b6d3483ee8d85f56b8374bacf74bb8550bd26b3d326962666f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b000000000000000000000000000000000907fe95f32e22ed75f94d96c191bcb19f88355bb84f91a8a535441da04dc211376435ccc60ad2089835b51e79f24b5900000000000000000000000000000000071e35d64ffa38024f4ccf7c4a713e22d8fb4b8450ba7b05ec5e759c2f8ea30e7d9e71ec2c90b8c667370131de785116059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b419000000000000000000000000000000000ccc90617f386ee2a76da43a745972066955c8e346d3de214834ea79423e7d95a008a6c119d640491d515b801034452f0000000000000000000000000000000002588711ccd23b65cf2f63b2d602b1d7dbf97cdbdb159e02e3bdf84fa65685e14d4832cde3662950a7fcfd11e68ad40a47b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface0000000000000000000000000000000003b5acf5f4e39fcb32a267034c5e905eb3df32f2f6f7150d94cd17bf16e3a9fff9dfdf75a966040a6af5a623787a40170000000000000000000000000000000018e4b8d163e5176bc9a45da14fabbac696ae6870717bf5f6c00b5c73dadefbe329d86a761935b18e81d65ab6c48e241567d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c", "Expected": "000000000000000000000000000000000905fd0b04e8db7657431b5f69f1d896e79ecee300cd67ea8fbedcf51b0389506da0669c28ac8158d5357a61fbc3976a0000000000000000000000000000000003235ff6d1acbceb35cd29c5fe524a452064a6e51d1480ce1e57c538b2ab6ec4f98c3bac676817e25e2d92e701ba881b", "Name": "matter_g1_multiexp_89", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000688d6eaa2964e33cebae16623e228256937ce9a7721c4fbc85233ffb3edad5d6349d9c8a00c16faa0efd9c54827f46a0000000000000000000000000000000019fa249ce7be07208cdac9f9927163bb1b79b40b320623fc1a08a299d5500cacdc55386ce451173f683a9ce3f006c1e4aaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c00000000000000000000000000000000078eef7d7951f257b17c579fec05f3efe332534b2f56a953a701a8b92664b9a0b37959f7c3dbd77ac18a5e72d174b9f20000000000000000000000000000000017cb59169aee6caa1dbc3c47c29f977a44a81d33f1cd298d5df3e9469c8543d919b985e1b588a96a9268cef03876effbdb48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f200000000000000000000000000000000025bae0252e5d83a3b76f2a861ebb1312bd344e3eaaed5e7169de248137a929ab94156be11e9b16ff312180d856d93900000000000000000000000000000000013c207c57a4876f6bd6e8e87eed0021d5e6b2aa3b2a323572fc2ad521e807c366fe31ec285c8412f89328cdf09dcbc99ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f4000000000000000000000000000000000ea93e5fe055ed1ce77de5d298fafdc4201418489b64d10c447de3973c1b98c184c0cae1d95831742f3d50613c5cd8c40000000000000000000000000000000004f2f3d0a5caac826632ee95dd1aafe181976552abdcc7db737f5693f3d08d3c4a85365e05e369365a37ef1b3df5cbca36d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f460000000000000000000000000000000004c8b69c09f67ad17e8fb9fea4b7532c7c5bf3edb7669e26eea4f9c8f0bc10b0b1895acdee731da5999318d83095ef5900000000000000000000000000000000054f950a1ae65dfcd40eca15e5fbae984e7672a23ec030eea0cbc0424cc8073186b8442e0d71d6a4a77cee37c1108f941a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000b69b6b7b6cb1569ccbcea029dc71560d54b8bb88bd33af1c12a09d867fbeada2e58585385f1fe508a0dcdf8d2143f71000000000000000000000000000000000277561e6ac810ddf4c46288a065e5441ae0fe2d7ee79ebd6cea8712281a36f812c0bf49c21beb63a1f5cb670dd37d03ad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e9000000000000000000000000000000000c0f4206d4cd564be1efcbdf57f99ce43b97d3e170017fe352ed3ec60862f87730d4d9d9d56ea0aac4f586d2f1786df900000000000000000000000000000000073202e8c73d14469d15a392589db79f3897b72bdb2b788da9012c7aaa167a157f85f3431161d35f45bdfe0f2255b6378fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000191cae6012ca07ddf511ed586ef19e9f0d913d081cd752f033c9f74c334c6f5d075b4f6ec85467caea7836f51d0159af0000000000000000000000000000000016e65314e34e1c7ad577a36eff992abe6f26fc5349d12db12394bac648cbc1452cc366aff69e8cc4e2e5bc85db237a863023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e0000000000000000000000000000000000bf583ae5e3a7827610d91c0d2433c8d358fbc12c016c59be8454c039197971f90191737993bfd08aa96d7838b7ce6dc00000000000000000000000000000000046fc386c5b456bafe03fc84b4f98939f9c736ac74cac507ea036d2443066090118138547766f637537425f64be9691b8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000000f08b3868ea056ff8e82fb7e22a6522985e92df1df9db77f787bcb3ed701bf8c90badcfd94e9d3e3b3b68ec497b9fcc700000000000000000000000000000000002e6f5e9eb44fcc7aa96a43856a707f5a82cb4c14c99b21df09e666d4802d15fb50d535184b63ae246d4ad77b6c4851415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c000000000000000000000000000000000167e13cc54e9e9866bddff0c37e942ef8393a588ed3c2e90da12d0a8360edd6c3980bde808ff16588a57100d1a8898fd0000000000000000000000000000000014b21a7a106640b55cfeb19d3c23aabcf1c0be78fa554613e68404978b78e5d34b6b6378c2e87d0b8bf1cf3444d0db31ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a9740000000000000000000000000000000002e8053215ae6894e8df09394353fe98b38fe4b17b9f20c7b48c4baad91519587f63b863e4de79be71672e1fb00d337a000000000000000000000000000000000c2ef9251a148f1ba8cd75a60ee18ba6328e1c3a6780c790cba3bc91a2145f44cb8bda5257c03890d5c5674e4d09296d039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff3000000000000000000000000000000000aa7fdd550eabb1b734db00400304be9663c008d322d67fc771a85991bca6413ec07ab3adc3cb40d390fd41021434b97000000000000000000000000000000001994d9be11443f0a95a2ba4f7240a9dbaaffbc70256aebc0f10c322fc5b120feb2cd8492d02c60578f8becd7a8e589c92c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd05550000000000000000000000000000000012b3574c35288c63930be8024afcc91194b30d2b486edae832dcb34778886af5816f7478df166f0a7e4752d8c12423e30000000000000000000000000000000012cd382d17ea10ad3fbfb40fdf4f3814a19384e302542a0f5731920443e4498a1f8f4d89086764beff079583a672b93bab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000117294ca9961249be6570ea760bb1e562cbd587f78be482263e4228171d9ee3d970b234455912299933689096f4afbd000000000000000000000000000000000029f88a99c750a388eca5dc6939082280ddefbf7d23997cca3653aaaa03a3ee4677fa8291641ad1f46fee0f8f1268140297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000001006f64c279f074bf036897ded9deaf9b4ca380a9a7542490be675355c3979b2925be09ac4613fd6b7a4a8bb9e357f70000000000000000000000000000000001537e170e8dd88a92a6bfedcef69bb370f7bc1f32c36d203f5b6859be9b60fcb4d1e3948687ac7791d867e7c200967eea87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99", "Expected": "000000000000000000000000000000000603f6b2d8b806c5e399c686878eba299131204084e2c31c38640d757e8a6e5b318da785d54ec8335398610e9e3956280000000000000000000000000000000002abafc5839180e8aff2bbac4db043e8839ea25d8fcb7f6faba2a1c0a567863f256f820e846e49b3296a754650ca9b4e", "Name": "matter_g1_multiexp_90", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a2eba2e26da82458a494738fcc816405760f4991616d729415ee502d13951c319be796cf35d88a8e00e17fa3c58126900000000000000000000000000000000117f6b75a6e25a786e860df05505f8e107b23c6f4338b2f87ac8740554304046f7cbb43f2193da35350e5fb39077ff3f9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000000f6e3064df312fc97c4f30d3cab398f7921453b933d428a4162a37af5ea27c79d5b21d1d305a9609c994e61e56db226a0000000000000000000000000000000011edcb47b9d5339d08f24be87e52eabbdf701ab15f7799a5ae26cfca9d49e0e9107d9d1f09c711039d096a5745b89c9164be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a6769000000000000000000000000000000000d6bf9e905e907ed86f5d3a4cdf61c527ef43ea0befcf6bb7eb1bb790b3dbdb83e0b958836669827251da94db1d07c420000000000000000000000000000000007f85bbbc54af3eb9e1c7e4c4700b4c784b8d2e6b2ff6a981a534317766790942898b4eabbb8d6c893180a436faf88870c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da000000000000000000000000000000000b17bd374136dc1717cff915f7c898e049e892ced4ba57a16752a6dd875cf1cf9a2005dec3e3bc6f87b7a257d5ce7ca6000000000000000000000000000000000874999db06d15bd4b2f60e9b61d195747d12f38b75b74f3089d5b47735e9dcf79ebce22505399e16492c4a6e0f83abba2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a00000000000000000000000000000000179108aa8a7d8443f69b7c906f9a4869ff4c724aaac4fccb5f52cddec86e32180b3ab2f66ba76d57f69416b70334a0f80000000000000000000000000000000007f83a847f4c7e7b35fd091249120bc59719ede5b6db083b33f5ea6249f9e13457511db006f416e0fb9614b8d22d51e1e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c589000000000000000000000000000000000154b40b3bcd0ef04a5e1a550215c238adf07f92757c227e4d32e42893ee8e7e4fa9d7169005220d89b11253cffbdbd10000000000000000000000000000000018daff3cf04f648e59d00df4b86d8ea5dc74adbbc6fe4f080ea7a84dc6443d8923517a11f264f700e209af9bc52f759c76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000163cb54e83a9935be82161939360356f7f0cd0219f446fd243d05f6333c68a1aca8f5d2dfa2b54dbc07f81f756ed6bd7000000000000000000000000000000001667e7a040817e83896d62adfc4a9f3d329e87f7d598217c7d2195c5b0c3eb58047d4b9bb640e3959f7ad1242e10783f7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d000000000000000000000000000000000222ed79e925d64fb58bf0cf105a2087c538c9538070bd742f7acf5e00ab371766d286fbccb3e708bda2d227523a40cc00000000000000000000000000000000126a9569e9ba97e5c41cf11af3a601560d037f1594f2e352ac86c744542618e9d2b6def0c7d3bb6a3707b80cdcb60f15d35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f0000000000000000000000000000000003786245c244c9508ba94e994dd510a7485f4aed711c75a2f509cf01b784eb12ce2f3907156aa15675e36b4b2587e9770000000000000000000000000000000018de0e75256cfcfa2df959f1491d87dd5414a1b51b6ff02ed5034394ea636fd0bc5d3b3a3b84fa7156ca7f97aa65feea3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e86800000000000000000000000000000000026828a6409635184cb929a5b3fbb881ef013e8342cc9b5123ac82e7ce24fe7aa6a507ec3c017bba10126ad9bab5e63800000000000000000000000000000000132cf4a23eac460fb1a3db9aa43b542ae55d19f6bb2f408c399a570c1e479c4dd0462f9573c95c953bee07a51c543c4e10325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c1138200000000000000000000000000000000035220c800af6a330df6b6b6cbde47abef2e5fafedbb7a0feb84a317ca3cdb79eed934847694e85e2873ef97b31b6ba10000000000000000000000000000000011edd4c17352914beccd8c062aa7b95b913f35892c7cc5dd8f736a31a33d33a98d8f9b4be97ffe608531eb7c9643f32109545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb30000000000000000000000000000000012148b58f805c38bb862dd9847f12aad21d1ed760a022d2f619a0a077a0bd79fbbd6c066f0f6c58517ee9e912c60a37d0000000000000000000000000000000018dd847881616f7410f29d4e68854ded4e97b31d5112fd46437739ed62e6d78fab89b078581d052266b7c2ce403d3a79eef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c81000000000000000000000000000000000e36ce625adc496ac94b53552effd651a73ed0c69abedda36e88d408ca7bee73777fd87b4f55e2e8b567c2fddbcff3d50000000000000000000000000000000008a209510caa720f20cecdfc9b0bd71d3fd4015627d0227a027aeb9992ec8030056a5046feadaf149d2392fc98fd60bfc8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e8000000000000000000000000000000001373edf053517ee79eccbf02cce4b4b67d6efc53917b7cd548379c3f78b447ae5dc331285a28bc2aa5863befe2d26f4b000000000000000000000000000000000fce7f982bb8e937802fef7b3fac517054e6c9b288b03ad6497734d78d4b9074e22b1acef45938a08440948dd8b88683a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000001ac8ab3b3918836a5ba14e3d7c44eb8a0d909dbfaa2772cb9d7f8f517963662b5d4209e9a5d44ca0ed897412792792800000000000000000000000000000000169f8127198935f06d26ad8e4ca3ae5b95ad967aac69f7958fe9fb9c5b1f0e98e596fb73a0d8bf90174ca21a02a3e2c2dcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a000000000000000000000000000000000c1cfb4660400ad5d7ba2f394cefa878c6a8fc214823dab539c0aa6d08f36ff1bd706be273f25ec5f1abfb06bb57e8160000000000000000000000000000000012ff9bad1a1d71fc49e96950c74d388229d4e4c68f7fcfafa42329ae06d4dd3091b5b1c95f6498743393b6e3ee794e4ea6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce", "Expected": "000000000000000000000000000000000630b9d9da596e60b15251aa74de850ee74c7804414e3e4d7113cb3d5ad6b985e436aa67bed66c36f135050d66f91f75000000000000000000000000000000000ab084fa126c573ed007f983e01737453b3dcc99ead0a74cc0e8d7cdad89ce81489384b311b7ec4c34736e9520b37e1e", "Name": "matter_g1_multiexp_91", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015fec44912af2bcd34f1ad42ed24b6ce430f6d07b311d65ffd8b6d726ca23f5bc4b7437d158a36bd1790e806fd5ab448000000000000000000000000000000000c4a4de9940c7c26999773a396a8f9a6ff4b86f0525189426529d9cca037d504385dcbf1c89eefb5ae2cbbb394be42fc92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b7000000000000000000000000000000000fa1258cb0d8a37009e8c56228bbd11aa854a4695bfe96ce205efc1c9f32bff8afb64df0fb7863512ff8db6b091f146200000000000000000000000000000000188f128e662e8d28be612c8a17cfbf28b965340487df40bd3f0312187d027cd23b50e713e21f8595bc790ab8011919cfc193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000000c7b39bc2477597e37910b1888ba0afe5ed03e809618bca0e543add93519909b6cdd6281e2afa65a9b45627dc1c6334a000000000000000000000000000000001335cbe866b3139dbe22266c4ed5f9fdbc15a1b338a290a590c03811b6448244027c12d118e6f829dcd352a419bdd8283dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae600000000000000000000000000000000178ea2552d03f645fc3060a61b35af6e3e12095ec65b2e9972a5e346ac1019593298925a887e59a94af2adfac7a8361d0000000000000000000000000000000013996dc427ba51c4ec1f67b30c95659f35c8e71a225bf357f636fbfb428140f9b9e5602eda78bb38e87e3ab77495e505e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000013555f26c2e10b79f8f2a4c397dfda0d8839a35a7cc15673ee5da34578f3fc4d38bd0331a5c42665bf40fb2cf693f31e000000000000000000000000000000000bb16b5b1dacac465a751a68b99def392a69a293377a22194fa4d4d6662b912d3ad804cbe51a4ec4792229de57923ea14dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d7200000000000000000000000000000000120183e73d23355da316783eb47ca687ecd34d85e800aa65d2c95aa5f8eb730a33d3273307cc05d81fdafcee5138a080000000000000000000000000000000000171f5e63fd3c71200720cba782ab863ace945cf405a2f961baf39ffab2d3283c26347ba297d16c3f2567814c6f9914e795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a956800000000000000000000000000000000095ae1795306c8a8c48730987a842a05fcb263d1f9ea49d3f3c0ae70c7ff636fa4e7fa33a35637059c0b11b1b1adc6e000000000000000000000000000000000185e08447394763607d6efd8660118429469a1f6e7edd03a7a3e12ef99c2a15670d1f7ca664a8a14f52814db9810ea2b25b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f08380000000000000000000000000000000012b0afa7f55ff9131a9399cdf0fbf2da69dae7cd504a0160665f0cd74a02163b8ad7ab05cebf3195495a1637134cee450000000000000000000000000000000002a130747763c25b9b6c0436390da91f02c9d5b24178318717024390a841baadae6a9f933e7f87f7965fc96bb498ade5df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e0000000000000000000000000000000018911ed6adc5f48db7221656c622c6cb981b1ac1bffd64e30662035c0daf4bc5accbd53cdb1fe8eb60628262584de15a000000000000000000000000000000000b753d21d823d1050f109683c7c153514dd06663ed0ce118e388d18d36686e94588159e5afbeaa492d021a700caf2dfa959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000001107292ce4d57209e9c1e2c396688ccbe005699de4e77b1a221f9004585ae6cf8f901da6811ad85a88cd85cb819d040a0000000000000000000000000000000012cbe9c273a8a9c1404abe51af4a647f6c89e7e177efc04233586d70df6dad3aacc9ce2a9fbdcf2ee5c73396fe4e498d26ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d7000000000000000000000000000000000297e52ddc42a7da1025d43f46df11009ee035a9ac45e09a0902ba86fcfc5a4bb4c35ae8b0e0c9b86a8ed7e5ab751947000000000000000000000000000000000319c082c39ce4e59b952941dd7d14f3fec39a9eaccdf7bb41a2b935f876ebbb6778c90e1919c1e5804df91abd3bd9d5f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c2000000000000000000000000000000000ea5a9d96509cc5675e165e3a7c9f99a8c6b7be9c33fe5fba895a2d96a68e922271c90badf3c41b3ff52f359f5c6dae300000000000000000000000000000000106614bf5ae42409881f4889a82c6a3bc8000bcdec23b093ebf29b24cad128aaa7aa17566c4293f67af010e9b5950028b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb300000000000000000000000000000000145447f37207ac8d58c706af0b900dfc1f2638f840a0b44fa65245b5e671ffc6c008951ee17217e010ea6cd5e8477d4900000000000000000000000000000000187c607539f8d2b6afd15efa353e2fd1580cee48c469992785f02b3ea3396db5359e0d6743ff8d41648fd8680a4a8c2bad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c10000000000000000000000000000000012a022fc2dd9c201e9d86a0983fed4a71abd086068b8ab8c9586cf51230acafb084d559239d86a3713aef4b87a04c09b0000000000000000000000000000000017e02d69776c705bdeb9fe06d412a67601c6763a19c840f15f96de0fecf782e3a44118def54286cd52227361f0db3bf93c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e09d94291ce5e8310871aad89e0744e6b319b4fb1089048b0181cb9e885aec881fb7577fe0e80222793068deed473560000000000000000000000000000000017c8676e4b8216a98d9e9a05891ccb74e64d72a5ae76dba1b5ab2d1c4eb8291cdefe7753abc5fa59efc4a4834f815488ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff70000000000000000000000000000000008902b3f9b3ed6f0dba21e5d6bfc13fac8f003b3e11de4b883024c3eca0d2c4614604d598d31d9e328c7ee4a9d9be6100000000000000000000000000000000017a918bcd38986300bbc7a401e09b9ae20ccd382280b4e79294b6c8ae7bb1dbe2f72a582e0125381ef2b4fe24998e72f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af", "Expected": "0000000000000000000000000000000015e9fb1d1a586288f07f399b87c37a085df405bcf88505a7d2b0ae6609d4baef7ec358f70edf838d3bb7291c6e5a413c000000000000000000000000000000000cc7d7e2d372183766a842f5c14c1f2a528d502f1bc5dbf5dfc9d812c56503a0b7cf1e6f052e998aaf45cfe24a261551", "Name": "matter_g1_multiexp_92", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002cdb1466c13290ff0c55c38ca6afe33efdcd09ddbdf7461d6bdb3e36fb5d8be851458620a0bf54932c4ddc1778c97bd0000000000000000000000000000000012755c81c142c5051ec64de7c89719cb59d9003fd8785ed5b36993123418e49cd3afab18b599deb72c969936633a956114d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d9800000000000000000000000000000000067b3a33aaf3fc4b885035e60ba7f3afc7ccfff469cde1a67f48fd8cdf4b15b7beb9e2fbb13daa9283598aaeaff5014d000000000000000000000000000000000bf43cb79d63db544b2db14ec18c11bb9114db93662e8e6e7858d3e4a99cc332890ce90775b6c190d5ed418571fb907d82cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa86260000000000000000000000000000000019329a66132ba7ceaf5c030fb4ae9a599895aab7df2a27fd92b55e3a52b99ac51107e798175f2af83991eb63147901d30000000000000000000000000000000005c71bf6552c314dda4bf9f2b4fd8aa368c9e88c0cbf4b1c2bef9137d608738636f40579a360bcaee1a3f12274687063ea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f9824900000000000000000000000000000000048a258134ed95f91070684d04b83634c2d4c16601ad259d41e7d27292897a4d4ac76eb73425583ab1718b91f151019e0000000000000000000000000000000013a0b600765fb760919bf273a7b88bba568350ef82fc382babafd40a7e006e6808a03160f3747878368d8f6b31c619b1e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d00000000000000000000000000000000069d94fe286a9d39b64756e79669add0f66db69ead7db5b5c2fa1a9e5338aaa9051457a3a744c3b08d3afec8b87d2e9b00000000000000000000000000000000105028835bbeff46cb7d9be4b21f07670dc5589603d0d695355591ef5f7ba28c04c8e6dc40f0bdda031bb54a5710b4c0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc0000000000000000000000000000000015e6aab154e33627f92560e3def26d936a8876c52490732c807749cc28e34cb98fe8f86addb30e129f8149c504d1dcaa0000000000000000000000000000000005f6040a129df2340f3c3fd0935c02cfe162fe1afb58dba7699e7e08851b3a3c3fba36745bbc769aaf01a4f9a401d038eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000db5640083674fc75c0b0d1b2d6eb2b03cafa2e63d7a65c894d9a76b196d92916ce85c708c6c451aad65e0b439033d9b000000000000000000000000000000000ac8d6b508ff6797668ded6ceba4680443516d601a155cff48a51297e321417bbffa6eee042255e9ec054d837bffe628acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a90000000000000000000000000000000011daaf23ab5fc0ad7abbe7d5f1dc26c8ce388491cc049f01f287eb9b133e52f33d40f8693921d330ae57853539ee30c20000000000000000000000000000000017594ae7ac7f6e4f02df862b6d4ff946ac1a47085b554ebaa720ad3291f576ba720dd455829600f930e3964a44e5c7f30cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d200000000000000000000000000000000174b5b9d4ef01fc9d0f05a03612210690d7d57ccb772aa53175f11b9623388de8019ff2ae1d564e7b30ee06bafc37a84000000000000000000000000000000000e4c03b8dc45b0567e9ddaa0a085d169799d2a595c03f2ac679fd858cd59341393e6a0f62dfac0e53598af4758843673b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a054400000000000000000000000000000000197f0ad6576bdddb48c58adb1c9b2115cd9b38368dacbea9220d6a86bb621dba93325b676071e38aed2338273c98c4100000000000000000000000000000000011514f08bb28c37f078a47b6a0d53b311d5975c8a3c8e2c24a25f34bfdcbea53bcfa14b7f23adeb20bf440c87a251a66825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a3000000000000000000000000000000000aa5f3a29c47fed2e4a87bb4c2a46a5a17102535aba9426235d42f00007e35d1c902b43c1068af279cc9a1b689a0dadb00000000000000000000000000000000056d9729f8faa8e12027b993e8dc41a340d61c64e4388c3166482ddecbef8d04085d6ae3764f0d9cfe76288929749235307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd00000000000000000000000000000000070e376dd57cc8e2146d49ff08c6c6ada6302c36c4eefc3003f0cc3d75040d73599c7e0c2fb9f7e24484c37262f0eb330000000000000000000000000000000016a272b79edcb7e7fa92400bd55fc937d6389f1f0d3d2168656815845d92ab1e7b555fd4ea311802a62cb6c94bdc5d58bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d000000000000000000000000000000000bba6375b28ead3d49197ec9d3662e34c70735ed0f987f05f439da164afcbe98f25d2ce7a5e1e32515eaa4cb7f5a1f98000000000000000000000000000000000b1ec74ff999ac5a7a3ff2c91e93e5f0edf5f296b063d80bca22fa64198a798fa6b6385d25cde65b789454bc2674231058aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000012fadbd9c50f2e8518dc15d95a59ccec0c9886488ed4601b3fddb2bddd77a4bc861f2862c9c4666622e42a5dda7138ad000000000000000000000000000000000b2aa31218a13b4ab0b00d1b76a9ac7bb3d7e6473a29f2f0d137ca63bf7f152954e52182d32d3de31df0e6ef0d102c9e6810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff000000000000000000000000000000000076846443079520c5b1600d5faa5a6d500998ae355c84b9393c79f83f1a2485b1809058bc53cf5f8a1a46bde6cf2e300000000000000000000000000000000012027dd1a4fbf6078b70c507fc2cdc0fefc9a0166694c796eb26e9838195e68fc76297e66e2a0e9e069274d110efb095c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d4700000000000000000000000000000000183c019c306c08401b4f2c1d852b29dc47b56bce8cddfdb66d4e3d5385e4bc75bb9806da1eab476ee02e25ca2b4d41c900000000000000000000000000000000066d56711b80dc8725e112e4e2af6c939977aa66c931c6febb21735d78f5afca4bbaddd77387e52dd5bc9c29cf26923613a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc", "Expected": "000000000000000000000000000000000f75ea9863e14f6151c452f7a4268b099f97d076b249d02d72caf5d52635bca3c686070d4a4bf997b753c283c82cec600000000000000000000000000000000014402b3e738bee3bda4e62c077e9d355ad8a71b0830ec0e67a4fe6dc59b1f3f7809ca7d07184f53c5afed32db327598d", "Name": "matter_g1_multiexp_93", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000107072809eaa84dfeba5a95f94aecc2c985c9e5dbc49a741811fe4b2393ba7f6597ac99d8e80c0fbf715a164099e9d5100000000000000000000000000000000124d1694bad88200cde42f1e7721f3390df8dbe4745715a2f0b6f11cfc78996c6f342693acefe88b3d83736cac6e3e05dedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d200000000000000000000000000000000188a853e19d512149800bb0aabcec450561e5ad08b5159e0879422cca1f957ee15bad2b881979d7c8551eb19693bddf3000000000000000000000000000000000dc097932535d21656842615f08e7016f55752556da3be69027d0dea621ef46cc65e335873e041a3dee6c7e5b7589dd5db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d3a53b9865082b23226042f69ca71b99978fd6dc3c8553e33ddb12542d05b026345a23c2b24dbd934be2ba3cd585162000000000000000000000000000000000b0832950405431722c23cc78bf0b9f33c6e2dfecf10e6d503c8c96ca9732c7e7a29251fa5b5b161d14b7155a50846886e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca40000000000000000000000000000000014e19a8a203bd2e9e9601cf6feeac5699a3b2d2129b6e756b9b5a7af0cd9228083de8c9a2a0ebacd636ab1b662c8c0c7000000000000000000000000000000000faf049bd6532cdad26403b269d7dbdcab6c147ce0ddd6285ad9ae0e8ddab4b6706bbf038fddd7f63e6dc9a766928ec327a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000007e2e69d6c96b1841340c48e8ab070c67054b574bd5778a8e38a9873241baf8b85deb73695873fdd9e3387fb1fec3b6b000000000000000000000000000000000fd151202c399636a360cc014c90caabaf3b01d5a6114e078eb2473bc2fff94f1c24597e39a3d2298a2e9210726bb48e446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000000ad0e842dd19673bfb8534ee20591a9076268eb203940212f702131fc6a3e7b226a84324954eb4bcfa8a007669d2317a000000000000000000000000000000000693801615c5282a327ae034c3a1480de0e1c471a412f194178a59582509ac6fe8ea22c8ec8e98034348ac465527f4b35f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f000000000000000000000000000000000ac9f4f22670b52e0e85a37bcdd729b40c45fcbd6e8aa78626752d736771ede9c570991e347134f95385bd77e404e4700000000000000000000000000000000005964a351f406083b14726ced542fc6d95dcb8bccbd41aa3ca9cf0395d8d29143b897c66c78e2fe56eedf17d4d6f6c1f94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000018e270b6208be13c23cabf52e31a156209abcd7bab03694fcb7035b453bce8464fa1e090d59a1139fe451d8c699669c800000000000000000000000000000000158dcfe7736f4fc63071a70923d81db9f7d2a03512724dc41ca47a873132da66eb0eda58134312fdaa63ecba7ab529acb677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000000a614d7a53b7a06e71aea4014f9b951bc19747cd8822da50f7993c0821e05100dc5fc8d043b2cbe7cc4dcae9837679d0000000000000000000000000000000004e0495281282aeeea480fa47f53f8b521a7df4c5619d4e58f730fe346a6deb3d501ec8b55b581489f28b4d991ebd90cf5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000000edd664ad8b77d86bda4ba772f677d34c9341ce2b4d2af4b2680383bce0fd4468e936841dd57753d06c50a3357a47eea00000000000000000000000000000000063eacafb540655984104f60569720625e4499f048ec7849577caf240634ffc42612ca7ca92c17e3e50aa627059cddf2fa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000a9d9bea7d8a058cf254d2b7e10f6d2e8244cf131c6f87c4e25b5febcac352d02b1b45ba347e0b891c8b08e7b5dec82d0000000000000000000000000000000001d256cedcde615d01e15cf526c4a8bc8b565055567aa1de1847b524fa49b4b9f654f5b66cda0a78f414848aab42b05c93d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000001519b05b59250c72c9db7f425954694b29b36af21d9293a36d7bcd1ffb53d0ec55a3ceb7980580ce6f9fb6a0faa7bf3f0000000000000000000000000000000009e7d045b69e2dccad22dac427f5938974a6394c9fef84633fb5f90a0d09d437219f1b7ef7e7bb03eed106948eeb560d3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d060000000000000000000000000000000017cac7707469b98c6b4d24fecf6d818dce6c8b9eb44bb08d6e475e385c30fafc81551e74ee98cc854d38d77d15459e750000000000000000000000000000000019d5bea3e48fa7bd273233bd6325bbe38267e4950dca4fd9ad051f487e7933a366469107258d69f0603b2f9a8dea2e4f5143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000b993d9303ecc19122654d5cb10d488af5411c451b39b1e19e7a104477da50324472076c55c4557576a9e5d7755a381900000000000000000000000000000000172b34e576f0539e32c5025b3a8f25b5bf407f3f3dda863b194a9fd97d3a6facc00902c95fe076b91713bec162f61cbf0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000170d799ffc4c0abf6c582b41732308665d790900ef07a74183826e48c9f0fc500b09109b2b13b2b33cc17e6e639d2969000000000000000000000000000000001943fe62329fcb67a45b5155da7f950ee12fcfe0e8e9ee15868409ae44adaa5f03c330206d7d97fa733c9e93957755a0b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b00000000000000000000000000000000078681739039a022499219b298799027a341be64204a34a97a8115e5e10486420c18664825b764fd7bb931343c2558a60000000000000000000000000000000003313d3482f952c6f9cd4ec2f2b61f28ecf7d8cc7e60f17e9aac8e63ab25dd6bf2da2d67805debce0dad8fe37a36625298df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af", "Expected": "0000000000000000000000000000000010aaf24dce0b05179769031ab81db339cda85cf68963640b888d3aca310a6de690150166c0943d658e51524981a1f391000000000000000000000000000000000d1af37c2bdca5886d73567cb00d5a9859755267800d7239cf39c291ba83b1c67e6a3532a2d8e8590c1bf2d845001038", "Name": "matter_g1_multiexp_94", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019401d9118a5c2b0c6ae40507cae6180083258eb6c45cb8bf2fd5d2703c95fb07c031c82d0568a395e18015fe0a48a2b000000000000000000000000000000000511b992882f75fe98131fd35276b7a1de527b94718549bb4f5c9980917b6301a86e45fb7c7e3ea99e54158e49c7e60ee49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c89000000000000000000000000000000000d886eedf2a2b33a50dae5ca6f41237c9425b0a4daf08bf4789a3ea8c7f598d53257916d9c03df0d63f12a1a804fe0990000000000000000000000000000000012cb777812e76378f04fdaf2cea12456aa9e11b4c3ab0f12e63fe7ab11c716562b07b3864cb9dabb7970c81bc1da324c79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f39530000000000000000000000000000000009f09107ccfc5c4ac9b7e0058d6a0c4d7dc4309134d5fb972de3156a554211d4a2fbe639bb8a93d86091137671ab8447000000000000000000000000000000000b7f9955092221c8a2f09c6a9ffe6483ec0f8a0f6c555ec1772c260fb62c4ada6dc7beb92e29620afd15466b5f025cbed2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b0000000000000000000000000000000009a22492a1b78342b919f7b5c8fcdddac408cdd3e8af4d6de5a4b1e510fa3b7e0e6887bcbe074fa839f2d0dc892db631000000000000000000000000000000000e5eac3a77c7a3e89e9324abcc0203046948f3d62e40156a5e1b1d9a274d408d6cb49e06b8cfcd21b596923f86c02c6be9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d30000000000000000000000000000000019914b405a24e72896b3d231582f0075fa7e59b0d0bc796d790754902238943ba634dce66131260efbb5dfc3925a1d54000000000000000000000000000000000352a5a986c500e41d2fa4f65e5a917061b3f9449c1e720caac187c6bfd4ce14f1b49ad414864a1510894530cfb4a768c818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000019567f8de70c4cbbb25335e69154ce48d4106c8c9d0027e17c67777dedf758203b0a8fa3863d4e7812311f6cde36a6640000000000000000000000000000000009947f7401d03fa8b0801b130b43f729d6a71c04edfaf7b9d3265f82b039131fa09f20f9b4565d21939ab7dc7dd3477e9ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c496450000000000000000000000000000000003c42ae9653d1d1f00d79f8b1a0c53d0f2d7f3ca52ca1960a621fc1bead7ab31cf6e5bf30c5cf7877c83b33b6b5b54d6000000000000000000000000000000001221117f45dea3fa1f832bb8280512841ad1798b76f1dd16dc320ea7c86473f6f8c98ce007ebc3ebc39e7a860be987fe96acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000004340b7dbe7c27014add4ecbdf310de758ea5dd1100508a96501ae3caf9955c877113971a61f66e3691d09f0a259d4ac0000000000000000000000000000000001d5f83065f6d178b4dbbe0f00f0a88edf0a90021601bddc2cc27fb0ccccce7e48c6283a1e641408a20de15219b5553e60bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf0000000000000000000000000000000017679efa923688425fa9cff1f8e89ae681245371017f574f4a655aa780bd11009579d7daa47249f503592bf0ab79e67b0000000000000000000000000000000018f57a1ee533981c8df24895ad174228330ea361448ed63e522637df44cc1b888e969ee94d7b44bd532b655123f8f5d8537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa9830215000000000000000000000000000000000bf47631b34b2694ff7fc5d1e25de2195e606daafec34fc2c8ec86c0a325214d874002422810a81cff654eda187076eb000000000000000000000000000000000931c54d05eb43195c3ff6b396e324b5878c3fd507637c316c62b3b6e2d3d84cff9f33cd1046f1939187979330d3fc431a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a200000000000000000000000000000000138adb70a3dce09176914deb0be17821cd0212c6ab554f7e200804dcade06c6cb5f7b084a1d6ac0ef8eeabc7cabe7717000000000000000000000000000000000a4422c569aced58938abb7bdbdefdb27cb06677c1066d17f98a59f847928d1bf2343acf8b5d1717aa38cf81959ac1acc7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000fd9ff095adf9e3f666d3141717ac4a96deb5b4f92dcee35be1d305031d06d51ecabf863a41cfd8dcda0fc94ecf79982000000000000000000000000000000000fb55855aab9e557046ed53421cd3627b519859e26338328d7da249fdfa6a07fa533f748eb5dd564f9922ad911121b2784f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d000000000000000000000000000000001054ff028d2e2875330e3d0ffc52e2a83ee2ad2adf024ee294f695113d9d645f0be2a3d3c70f758f43f2deeb542aae810000000000000000000000000000000009a5e96cd08d3ee4e740e2f7b94a4e390ab5f6f572c4a1b2d927a7ef2365557ab9be65b8e2388fb571a3765892a96445c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea000000000000000000000000000000000bb1de70113edd86e5304248fa2f857f1620dc8a6bb28680f537e04029aee158e2ead4e0eaa373b812f6ca988dc40e7f0000000000000000000000000000000012118b670c9df77af087ad01e3b766d4a2b7c2b2a319cd733ed6c02ec36d9002036964fc442db992bf730c57a7d0a407a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000001968aed17e572c0d99e4e9262f239771976dcd9d7df19c20bfa94aefe1d4f3a3117bbfa4a6e329bc6b9552731446dd10000000000000000000000000000000004e64ce59b928e8cac2f744bef119018de8395b712013b0c69855fbf2bdc6a750a947b1a81c9df959c78367ed0e1575d95a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000148cd980512e0aa153adbdef262f098b1ece801ee4024b5561e261d39b495165851781d519d75f83dc5f298d40b4e9e0000000000000000000000000000000001dd43f37950976e50071226b6aa47c229085807ce9634e6583f5a2d47eb8547d4de0669b16a2771791c9ccdb4289cd9319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40", "Expected": "00000000000000000000000000000000059c7ca50efe2d0a64b55a560a08976b437c350860f9599fd30370d1cbbeacae147144c216cb3e63afb2ddcf34282a5f0000000000000000000000000000000018f42ef2fb8eb6cc8c31600a3be37ece06ee567ce0c8b8c7a54c910041b8c25b2d94a94722904de079737f06e817f380", "Name": "matter_g1_multiexp_95", + "Gas": 64128, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000172bbfff135f33357b0dd0e8545da99c4ee74d6414724c2aa66ffc85f3a9d0e35ac80850436745a12ca6f1c4ae5c0ecb00000000000000000000000000000000152dac882023cce1a3e1fd4d8d5aedcdf6acb2ca9628a94ce92a4a551b1b7268589b52b2c90af6e4be9631eebc2ef8a62a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000000c40d04b3002c21b041ea8b8ce967056435fadb479fe1fe20c373b2e2c5b568b7a38d031424bc835bdbd85af8ed1d0380000000000000000000000000000000005e7357194364947c8dc32fd74757a3f3014e914dc25f42b2dd86230ca4f86981476e6f10b1559694bc17d014cd243d7f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b000000000000000000000000000000001474002c92db026ff5bce69eddf1d8ff8e6d2ab9427bb82377911597fafa4d60256836c094cd513a52a3a09797afbf5300000000000000000000000000000000176a7b311a333c2d4f6eec66e8c889ecd7becca75fb35a38bcccae52f10ff69630393fb7d87c3b6d97cd648be099c56507f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e40000000000000000000000000000000009ecbdc4836c6c0cb4ccd014f9e582112bce0d0ab047115f38ed5dd51c54de5a43321e85c9b3e9af5fae0caaf2493fcf00000000000000000000000000000000034425e05f0adb1577f7b1bf9b9b50a76bc894f5ff0e9a8d190412eeeaf80d0bb96f21478fe8adea327f69c9137f57094e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb6050000000000000000000000000000000000ec3d4fe1b5e1c26de1558d7dc51eba3b6c37ec037de184e8a6f481ae20b830c92221593e1bbe4ee76a85cb10b33e18b000000000000000000000000000000000e51f811e16f00626d934e69024b55dc29fa4ea363916cd8f44f928fda6e3ca4947eb15de24ce1952c39e9ac52d2739d27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f2289000000000000000000000000000000001205dfd803ff3688c2023913aecb10c138be4d03756e2f05a63627973f511c2635571469d4f630758627f7977b418729000000000000000000000000000000001186b9c0d2b2073b495ef9c233c275922bdbf4691e8be085051c09e245242526b13b7051782a80726b381a72b5ef9d5ec2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc000000000000000000000000000000000413f6ee9bb25469af4298dde67f0a4a26d2f528848ac6646764703922c78d65e046204f891ac94b0b4c425110fe986e0000000000000000000000000000000011860881aa871fa3a6693b23fd7b1da0bcaaf044058ea0700b786f12f1074c615577e572e33faf8b3562bc285632696d911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000000010e8fe8fd7863c2807a4bd717fc4646a0e4f99598b9c6c2cf0547d039d58290a367e4ad851c7a67e8dd546d5e328200000000000000000000000000000000063ea10e84e4f5824ad7b9b68398c9154ab25ddc4043a4990d80e09dd94a890dbabf9c3d93b13c4f40bd7b1ff32b14b2fd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b0000000000000000000000000000000011cbc725705b809ad69c5ebb55ade0039989728e7103b684feb35c8142b100175235c2b395e37a20aa40845ebe2dabcf00000000000000000000000000000000057b5b5a5cf5f5bce985295f8a50252967aa54e934e87855097eb083a59863aba19ffcec4354a5a831b747175ba10e878118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c00000000000000000000000000000000041043cea626d6ab553b95c6e09de597454a3a3d1b8a75fc9ecb3afe15bdd8b5e73b8012ead8777df8957701fc9c9022000000000000000000000000000000000185da96dd1d54bb7ca5d7dc2fbe4cbd8ac95f06fe85a7a26e5e0e6353f6a6daf73b74117ee62be4f3fb268fb4c86275c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce773000000000000000000000000000000000a06b91559964aa8e8628946bfb720047915ddf08d24fa34f7b241e16bb163ef67f1e84fd205485d17725a8386a7016a000000000000000000000000000000000ec787cf5134bbd832d2a7dc1ed87b8c824552d92fdb30a790e1c73b22c753540a9747eecaf14dbf867d9667b7b852c7982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b00000000000000000000000000000000053bfa3bd311c1780afa1862de6ae8a475b8eb9c61fcee2b63dbb6556022d703bc7eb204fb038056c654dfb940e7039400000000000000000000000000000000074ab5797d3c39804dfd5359b69a4bdd2b738670d13662eb2c112eefbc0f90da85dd1a4b6e0613785fc66b100d129202000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000cd50ddae4f053bf5b7b3237701bdee2f5167e09d824d260e89ea498fb3b593e5053b781c159302b0433ead35f072c850000000000000000000000000000000001abe8539a4215a3b7b78c79c306dcef7334c83f571f4d6836e1c1839a65c8cfa9a0811395e3c4bea26b22ac2175757e773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb20000000000000000000000000000000001e6e0709869922c36e073fdf1404a973e0467cab3a04a806361e743d67468f0d66de28f6c0c7b8cf92954330485db0500000000000000000000000000000000084e96298cca174344b7b86052426f9316a15b4031b9e42677253fd9355b1c99ed9ca3eb3949005078ba228d4167f8b0759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e0000000000000000000000000000000002c5e399eab947a52660807752ca662212cf3a201c1127dab3586cae88f8ab6dd23deb0312387178e0e9526bc8fc7b8d000000000000000000000000000000000ad86b21dbf58098fc4f758d7ec9204bb16cbbe680b58fa42821456d4fa508e42b53c8988dc0d9a4d6f6a782a5fb90b6cce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f052530293800000000000000000000000000000000181f41dfee6effe70a28e4c53bb6cec52f232caee076f680fd63d73cae24b44709fc63ee3782a36278edcceeb7b32415000000000000000000000000000000000088d9011a9db9294bb4451e9981e84efa595462e26e5dbe14e9c84a8c5ddeca94f49857cf3b8a70e6a4047ad76d234585e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f", "Expected": "000000000000000000000000000000001170d9938910ce68e56f9e84be6f6ad284a244adbf26daf8d42f6411c9794b707454ec9300442fcb7a0f08eb91edf86800000000000000000000000000000000043679c7a917148d141a785e65b0a067e35a016c24bf7639ba374e21a817819ad67d317ee70852bd13a1fc3a373a38d2", "Name": "matter_g1_multiexp_96", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008d9cb39df5b28781d33d996039da8c94cd810bb85aa5868008b4267ad2a8670924d4b3ad7898b33689aab2211bb9bdc00000000000000000000000000000000007a8a6f888722e4717acbfc42ef1907206db31603c403e0a8c1ac0af9b37e63124d4645a506265487e5f9eda09c8baf85431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c000000000000000000000000000000000db6af04eccb3ceedc11378406a26613aebbbc2201a9ea2089848c7af3b34e46a3421d5704242c4b333f72180f6baa0200000000000000000000000000000000105f40c8b702f0989a9e20f72ff6a4f7310d81787e87638c33a61985f02116e106218d64976d50bcc61cf5bcbbff7c9eb6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa000000000000000000000000000000000a77e39abdc9d64d72ea4b321e3310a145feaa5d342bc1a5b16c0143dd01caeda4f18909acccb3cb5b43ad999a94f91b0000000000000000000000000000000016fc4a4f6b488fd1f45a158d941d7aeb5d431821589ee845c64eb198ff10931d586f8a0678237be2a394f5976d895bc854dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000000d0ddfc05bc9f89eed488752d64698bf00633c83cc37931d95a599d6be6e4c5d611a4151839133e86f74bb91aed1703b000000000000000000000000000000000be3dbea501c822730ab0176f64903931aa46b0179c59556ee7e1ba54605ed8da2eafed7eb2254a7ddc34e553a9b6d59eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a0000000000000000000000000000000004d6f288744016f15b21da736283af2ed1f45df12067a3a70391f66fff3ce3953a51169eba6288cabd84ffe7f597c9fc000000000000000000000000000000000f6556a63def531a940269b073ea98be79558d832123dd681bb4446d4c11e2fed59a2f97904797abb07ba53e0d48e923b273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab850000000000000000000000000000000013e692a13e79c734f3758780fbdabff86fe5936bf6c60f2f155ec4d1c49cdefb97dc02c1f1e4280c5ebb055914d93f9d00000000000000000000000000000000060898a9365ae49697e5ac23e320261eda04d818c5f1153f647844b1910bb3430d3c06df9a64af8ff9dd25c18cbfa79d1342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f000000000000000000000000000000000a2a4d92ad63dade4d666ea949dd64d5886eaa3c7ce466677356ce9f65520591c1aab590b48e9fe1eaa0f0f3e306cefc0000000000000000000000000000000002a2bfc836409b33bbe078a5f89c5142411bde621e9117ddf9f81f37bd546c3e2ba94975ab4652fa0858d5a2361592715b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee8239812500000000000000000000000000000000075d3ebb18437feb21f94ad5e2ce96cbaea2f6d68885483ed54ee67f2dbcf8cfa39f405afb46e45d08cb804a7aee3b8e000000000000000000000000000000000d42851366ed4694730b7c58450c3f9ebd365f15fa4dfa3fd226d180aaa921a0d897278506ede76b85decddc9580a365249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d17000000000000000000000000000000001722e05d33728260ebf5e4b48104cb2c89b4bc3073767e56fda373bc0e29261c9a5c53e5768b453b116494c1109cba2000000000000000000000000000000000030e4da8620007236b89103b215e54751ba2f2dce19b0304997f450791880ad34f3e43cc4e6852aa599fd65ef72dd9a5c014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e895000000000000000000000000000000000c4e7e44e8e0387bd99311343d2ff3a080ddad557c8639aad64c4f6e47d64f48b91f9de2e33b4b9c182a87efce5d4e0e000000000000000000000000000000000e7cb49fd7aca3daef3c0329c950c832e1d007f21a4f950f367eb37b5d7433f5d6f1ab1c206232b2ee32137b56b53967ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000014026b8dae20a1913ecb45359e9ceb317137244e16a036ac760b47363f2d389ef6cb12cd5f5fb9e8e31ccd39bf114f8b000000000000000000000000000000000f07f9e76789dd937b85e02a9c346f81e87637bd03bd5f98a9b18ad6d109100b540aaadf1fec048530bcfa35dbb5b8ae8b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000a0249c354052094cae5a3d77313360a8956839af614184696b5b7fbd2af6555c6ae14a150220f01d624484b9096eaa700000000000000000000000000000000043098df38ab37f42175cc9f9fa9ecbde75bb344776ed078632b3d8bbfbf04103adde27ef0d361177bb3814cbb8bc54994ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000011e90effb7ae193b47afffe6fcaa0a28c358222cbb087ce479b7fe88d25386c5a9c9527899d7633eaaed9d982d3ed4e100000000000000000000000000000000174877f80e5e9daf2cc219545ce67b904319f75c0284e41552662512727c1e05b364364c4c8835c1c9c6fe028ae45895b1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000170e754e54f64090c4c7520bfed82665b44728904092fe3a4fb2fd2d3667ccd4ecb796e5ed9fc4dafd315c0b6dc22b86000000000000000000000000000000001081e62ee7c502159f7a8e28c5ee45fb7fc5b301f3a081899bce10096c74d1bf7834d12cb7fb1301b986e9c6f7501d53e670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af85000000000000000000000000000000001434584d8d1cb34eb29fd1c95871f218f4dc46f8b2ddabafdc7049e88f54fa4b80c88960a76411e365aa65cbf77f01ce000000000000000000000000000000000e4e2e1318c5907a07a7ff154b07e959d681a69c066585ba046b8889d417d01c503b32a924500944d43e68d7da8da35d54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000945a9d0603a3bd0278fce30f0cf97274319a760291fea5aee143c364cc0bc60e59dcd1093aca1a3ef64696ec47845e1000000000000000000000000000000000a77cc690d55763a94aa48c210610833427ed3176b6dca184598755f539359bc7302f8dc2cc941d447d9b5b68fa716b70563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d", "Expected": "000000000000000000000000000000000ac708f97c9e9fef4482af7b95c59c2ce6b8826f6b046b5a29ee2224ad66648a8d1f9959ff434c77394199fbe75780db000000000000000000000000000000000983fb55837062305d7fbcfe8c76cce1c431067a97392923720a6c4f963839d7d2661e2a8dacad1f151f505748239798", "Name": "matter_g1_multiexp_97", + "Gas": 64128, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ae003001e3173dbd17f4d6598fcdaba9966f1e22a06ce747f7d2a06b2bd37579d093242a4940bd816ced07ec1917365000000000000000000000000000000000b27db470845f285c792da64e870b818a7598fb820313e075ec72e78f59f3903cb0860b749bfc67540a8bc80e844a8de5b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e9900000000000000000000000000000000018a33b2c2f1ea187672612b51c8dfdd9e86674df58ff4f77ff3f71628e7aafbb80ad22f34ab4203c42bd39a4f73c3d6d0000000000000000000000000000000017c3a68d8782a479ba9aa829e3f261a3e1b832595fe3922d800349bdc2bf58e0c1b523eb0924bf0996e38aa83267f570a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000015347adf6539116167ee71557b78d8fe13373512ca7d8d365179e25ae8ed2c6a65e1f643cb0ed677a2f44eab809d5b640000000000000000000000000000000002360dbbe0b7f8e97f6aec4b20a7e6525d83056975a4228901b4f19259c9ff2d2ee00da9bb9085232fdf843e5d305561677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb6071910000000000000000000000000000000005b62380515d49aa1427800077a11a8f01ff00fe7df53a13a9266910e4038167ab747bbd0705fc25ae2cb0e2451c893c0000000000000000000000000000000008de7bcad1c67d7f1fb5cfb9d20ac2134006618ce0d22f4120f5396bf8164c0effb0e3ebba7959e9dde757973080a9cc53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac00000000000000000000000000000000185bee837e3212323dc40fd471ed9a1a58f2aebfcf7f07ab761d40bc1ed77b385a134c99385d07e75c5f8c51d6496482000000000000000000000000000000000d7d42e4e18040da671799f981d404085fed490182d397685498e80967cb9c080a766d5c8822152d78920fb388b979f534c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d50000000000000000000000000000000012961da3be1ecc774fc9df2dcd87c337ee50a99df7c4821fe08da7327276a24d754be95b6e916d5c63926b6e44b74310000000000000000000000000000000000e44d11949fe33bc3a0ddfcc74c5b0fa79cebfc0d4a00a574ad7659c7a5e72c728ae4ee031af57e9135a3eabd93686edbebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000f03c013d5554584c2030ea02cb451ae508fe6dcba72bf7c49cb47a25d3d65eabb2fe043b9ea90e03571aa7b64be8b11000000000000000000000000000000001479789662864eabf677d2a541e48e5ce70f35a2cd6c0a476d4179d02955a51123e75c650888e514aecc85d67781c8c18b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c959600000000000000000000000000000000139bf8fb623dd156a3fcc46eca51e61155cf58e2dfe8edfe717effdd4418c833db7fde2031ef27edb4a70f9d60d67440000000000000000000000000000000000c352a16159eeca4dc9a86601973c02e39f2a11c8a0955ad52236d7e46dbc405147258ea8558505bef0f09ba92527c76d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca0000000000000000000000000000000005b8c4c2782a2a2a3abe4f99e60db6ff4179399aef4b9e305fe037e1a14a4c03ff59be1e91f55e5bf316356bbaf876af000000000000000000000000000000000eae605cef3beee4a176a0589f2676b3e212edcd7ac5834ece3066bbbb587bdb6bbe46663acfd9d8aba2251a238004106330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b8000000000000000000000000000000001038258f67b0097ec51adee244cc15d63c4d3bf1b3b3e64ef8ae6ac15a7c4195fe97bfe8c5a42981a2463ed1b39032de000000000000000000000000000000000a6f27fc1f2dca48f6e26456de5d9fb840e4ed3fd9ff12372e51130d7c439f4ceb4fa929da2dfa3ca271d34e9aa0985ec2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe374000000000000000000000000000000000373fbfebce5c599172ab017e8f4f9813b0e6aef3031faf61c336aa7d6b64c8986827a27605b476bfc1057a0388f864d00000000000000000000000000000000079ec2c41547d98277c60dc46a61ddda51c9df65a8ad2d0a64d483eb245986de36eea2509cf7949c5fb05a77f9cf3bacc9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000d50821953bbbdb494e48c59c940c5f2ac2b902f4c2ba2b2ad50960a51ed7eb1a9d592bb903a03b0b90d8817d10848ba000000000000000000000000000000000bf0898bd20e08205aa218e529db578d5118ae411159ed372eb8968cd773ebb1619f92107d2948020bb3c721ea63159dc2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a0000000000000000000000000000000006bab55b7648be3eaec947694311289f17258876d74a7d92f22b7807d007fe142a71210684593b1aabf74579eb1b1c17000000000000000000000000000000001016b28dadfe9b65d86a1f843f7ff4b774eab74431b68b079527c2387ee6cac69e95ca564346fc54237edd3d2d31f6ed9fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000c0d5ae44a0863ef3d6d32f1d8f32f2c5b89112652e2e3d6ce620479882fafd73cd3627f9f11315020c8fc9341c7fb4800000000000000000000000000000000197067de9d61733dc0367d91f55a57ae268d5e7babe7882c1fbcf03cc38de7a2dc41acfa16bac0ae63418fc349b9471cd4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000c3964c79741fe8093ccf2f3d118b33897a18d920ca242ae153118bc17bf0102fd19a9e4000698b256930a2f415305180000000000000000000000000000000003ce4a6877879ee56299ed27f634571126d9f8ca8ccb1e67100064e7efb435cacb1ada74d7c7529b495957ce7a5dfe709644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000018e6260c0cd6cf806ee82a047c51a85e0d7023883cfb05993ee81220e0871b122c12e65bb99b20787322d93b82089e98000000000000000000000000000000000d5b66fc46b7fb60fe8efb6659bbe948c6776d7780633f007123c5c49f5fbe7e3defc0f3d896333d0ca01244f2b6effe0df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d", "Expected": "000000000000000000000000000000000c7aa8aaa848c068104b88d0757551612285327ba5fbe35ccb4eacb1bc96a341786e4548f5f86f6ded789b712a4be74b0000000000000000000000000000000015c9aefc358834f21ec85092cd69df37e0036ea6ddf23a3ca052a669446b183e5340ec7ce894ff6575f5e6531947c84a", "Name": "matter_g1_multiexp_98", + "Gas": 64128, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004acd4cb6bcfed3219c3aee9368feeb58d77a7ec81d19bea11402015f4bd0ee2d7afd86fa7ae9dd320910ca28eb6d98f0000000000000000000000000000000009fe1b0094c0c2ae80a3c5accfed5d212ce39f867aa2150b781c193a0053aecb04d06e005fbfa0a24595e5968d024be18a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a8330000000000000000000000000000000018cf9bf39549c35e94211b4e2d0a0157d73e1ce8a17cd724eb33c38281dac07e12eec61b27b440b220c4f21915a73a52000000000000000000000000000000000fca6d956989db84dcfe58b0310fc21b5bdc82a32838c8d9cae912d683dd9c67f68e15b3fbf9d7b430ba239c8904fdd2bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000001897956bc232fd5a9b0ed1b533bebef8ddd9e97002513eec71d67ce1086ba8473f2c013af7d8ac548290453d9f71bd5a000000000000000000000000000000000796da5c8ac165d416c8fa36d84e11bcaa80c1bbfe18efde4b4b2c71d6d00fa24f3d51eac312cad9e854f094dcb6ec7458b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe4000000000000000000000000000000000302240769257e92899da03fcc4abe1ad3944b74c3046e790e4e950f2958426b5fdc691401a1c8a531f42185d382fe5b000000000000000000000000000000000053750b58b6d2fbacae94e22b397261e541eb4abf4715b3f528dbfc3388122918b1b4b506f2fef89ea936efdef0105b3b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe000000000000000000000000000000000168a635a14f61734372f4bdd2fd564d77afa8588e1828d88c4c90bb50f57473b2c20585dc0e93726b84e73c61f29ef1000000000000000000000000000000000e6e92355e59304ad35b1dbfbb98db803d5fadabdef4fb1b2a54080ec9a33a7147ebb4d5219acabd949337bebbffa793b049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc000000000000000000000000000000001797bf2ac9b490cd43a346fdc64bfb22301a0a0e371bb4df8ec02342b4fcc99af43b4735665c6b1386fa04a3dc5406e3000000000000000000000000000000000fcc20f4aec04b7896ddfd86f58c2e1e9dc6f863ec3b477572c073c0f4fb07ee8dc0d5a843321446445b6e7846fbc5d556197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d9703300000000000000000000000000000000013ddb8ff149222a5a0a997c0b89aeee36a6ff2540de3cba8bfe6a2a64fb505f13ad956a3882082ab85bfbe72f3a3a6b600000000000000000000000000000000102c1a1085f60cd5326966a2dda0872290e1658002ff3ed95c47cc0345565076bdecdeab7082bcfb439cf7f3e445faaf721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000189515e637d404ce6db58d24774609cf946074aa22066d808dc022824a26b381bf09148005c61156a976154b025d71c90000000000000000000000000000000009102e313c4517cdd3d07a66e0013eeafc996c21fbf5f0f3e7d232ad5adb781cce1657bd5750193cfc0357ff55bd012a461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e0000000000000000000000000000000002e166e475ff083faad64667b683e546b2358f945b8656f9c2f3f6e87a40dc3fc087dd94874bec1c4bd5929b7c96024a00000000000000000000000000000000022bb4ba4be638d8c14a16c94522c41cd3b3ad917daa454f820b8fa35e5a48c676266feece6986e8fe920b2a5e43e4b3569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d0000000000000000000000000000000004947bd8ea8cc3b116fb7320c573fff0f107913c18cfdba2e7e9a4c8715e334a431156f384548508df8950d681163aee0000000000000000000000000000000001e9e7494c295248184503344b8ac7bfcff41a4561de03d78691ac47980f14aa47c1eaa3cca80103f0f2ba14a2842aea2061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000004789b01538cfc54cad0e99538e874d13eaa7f07199af29d460927c3e622c74e0bb4185afa12c53446f56033348c332f00000000000000000000000000000000154291a8bdefbc91445ef1fe123f326b8aad652c8c54502920d4dfa912c2f42d784fbc5a16d08468d2d6ee56e7e8eaa24129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000029048f227fe8d1b7247a82cfd3e1b4b60cdce6b52de42c4b96641bf8fc5ba9b077e33bd4c4fce9a51b63a6a2451b427000000000000000000000000000000000c83518e1b7700d68966d592cb2e3295a2db5226eb6fef972c8a84721d1e49a30e4a8ee3494ed4bbcd2a6877e1ba597d366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e83467290000000000000000000000000000000003e96431aae4330d3d204093b7af21343ace4f1960de951eeaebea51e778b1fee43ecddc46667d096edbc5ff4735586400000000000000000000000000000000183a282f4b0513be661b1b38eb5f02b51aadc591745e0bd5d2d4e5545739e26470a9ec20d78ec284268d9c54c8e4f7b6d997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f5000000000000000000000000000000000904c85edd36dfa18ddb4e1809607708142f3c0861570f2bc8fff14c462675661f2111c10a01557fb21f7f38957bdd840000000000000000000000000000000012a3a37f34ebb23d4c9268ec9e1d53aed4747aaace497695e6ea8fdbdedd58031cb479003e8bec0d14aa1d062fa30f2ce881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a337100000000000000000000000000000000148532bffbbf8bb1688f6448854214b4273b9d5adf132aa9142c1605d1882879678b6cc70638713b9438532d427f447c0000000000000000000000000000000010971ee30d83719e10e91aad3f1f201fe35ba1a057531b1905bca3a8391a3786cd077ee0f104305eafb3c94f4546da9edcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e65000000000000000000000000000000000f73574aa5a06ea569de88e48fcb96e822039af296684933c1b417dde95e08d2ac9c6ad4d525b0734e24807ee99ba88a000000000000000000000000000000000523deae09e75121a6d89b45161f69f0733a9e43d88d8527a03cca8cc126aeb7a680cfaf291554403723e20440b79437334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638", "Expected": "000000000000000000000000000000000db91871e4cd84b3b58216b6c2e77a9521f3080182e78d3f66fe33f313013f06aec2dc5a6d483f35fadebde873bff9490000000000000000000000000000000003b9685de062b09b9e277ad5cd664d28af59064448af2b1b2b2357df6fc88e3ee7e0ac837100e0b7593944e8db43ab0f", "Name": "matter_g1_multiexp_99", + "Gas": 64128, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG2Add.json b/core/vm/testdata/precompiles/blsG2Add.json index ffd18a5fdf..64ca2006dc 100644 --- a/core/vm/testdata/precompiles/blsG2Add.json +++ b/core/vm/testdata/precompiles/blsG2Add.json @@ -3,624 +3,728 @@ "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3", "Name": "bls_g2add_(g2+g2=2*g2)", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf300000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e849", "Expected": "000000000000000000000000000000000411a5de6730ffece671a9f21d65028cc0f1102378de124562cb1ff49db6f004fcd14d683024b0548eff3d1468df26880000000000000000000000000000000000fb837804dba8213329db46608b6c121d973363c1234a86dd183baff112709cf97096c5e9a1a770ee9d7dc641a894d60000000000000000000000000000000019b5e8f5d4a72f2b75811ac084a7f814317360bac52f6aab15eed416b4ef9938e0bdc4865cc2c4d0fd947e7c6925fd1400000000000000000000000000000000093567b4228be17ee62d11a254edd041ee4b953bffb8b8c7f925bd6662b4298bac2822b446f5b5de3b893e1be5aa4986", "Name": "bls_g2add_(2*g2+3*g2=5*g2)", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", "Name": "bls_g2add_(inf+g2=g2)", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g2add_(inf+inf=inf)", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40", "Expected": "000000000000000000000000000000000a9b880c2c13da05bdeda62ea8f61e5fc2bf0b7aa5cc31eaf512bef7c5073d9e9927084b512e818dbf05eab697ba0661000000000000000000000000000000000b963b527aa3ec36813b108f2294115f732c878ac28551b5490615b436406773b5bb6a3f002be0e54db0bcebe40cb2e2000000000000000000000000000000000bd6e9060b42e36b57d88bc95b8b993da2d9d5acd95b73bad0509c2324212bcf7a94a46901932c0750535d00008a34f7000000000000000000000000000000000a374afd32bc3bb20c22a8864ce0dafe298bda17260b9d1d598a80830400c3fd4e8a8f677630eae5d4aa0a76a434e0ba", "Name": "matter_g2_add_0", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633", "Expected": "000000000000000000000000000000001963e94d1501b6038de347037236c18a0a0c8cec677e48fc514e9fc9753a7d8dcf0acc4b3b64572cb571aebbe0b696640000000000000000000000000000000000d9739acc3a60f6dffb26f9b5f1fd114a21f2983deea192663c53e012b9f8e1cabd4942ad039badbd4745ddc0a26a91000000000000000000000000000000000b4206dcdb80d62195febb6773acab25fa2c09a2e4be9416ca019faeb72f1fad1dfdc51e8cea39b371a045b18947d40a00000000000000000000000000000000100758b888fa27e9258ddd5d83409e8aeac576874bc399b33b8bc50d77fce5358cb091d42f9a1b1ed09be3f200959989", "Name": "matter_g2_add_1", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f", "Expected": "00000000000000000000000000000000079e4fc2190d3441fa76c2d925d23b81e353e09e9138fdde51234195e564a32c98aa0d240f051298bf966d17adc2d6fb000000000000000000000000000000000aa327776fa7e15000dd548fcdc3a1cc6f9d0ab33046dd4240a3002962131b738ffed579945a348c795cfcb33682cf3b00000000000000000000000000000000179232ec56602d1ff79861cbfa2edece34b296541483aa65fe0cb493f520b7722cfffbe04294dd054770a38bf75d927b000000000000000000000000000000001826b88a6b411330757bb304a380487a02f7cf421115b84b3f468d11a83dbf304ce7a5661f4f01299d3c7865305a0006", "Name": "matter_g2_add_2", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c", "Expected": "000000000000000000000000000000000f69e3616e7122bf78230461bb1f4b194988adc6149372691d8794d0086fba0870a2255a2c79cc3426e7ba4d032fc2ab00000000000000000000000000000000174752301e05dcd62f7a3ae3357344e64d1c94835b2b742ac24449ee2728d693a0df10c3beaeb45d1b4af4ac2bdbb8b200000000000000000000000000000000051a761a3ceb275ec28a2a269b5ded1d9fd11a617c958e73c07de3a92ac480aa82c7d2a1852d291804e734526277f5740000000000000000000000000000000009bec9045ea89d5d16588e3373cc977f6d975d0e2213b171403a9b2ca460b3b2e1106b474185516d4200655b17a179a1", "Name": "matter_g2_add_3", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca", "Expected": "000000000000000000000000000000000c079610e6f8770d65352f911863b6cb4fcb25cacc4a42f75e34e29e977c93244a6241cf3d5bd1040ce7d8987996f87e0000000000000000000000000000000010d08d8f6fa8ee7042c0891ea0c3b9b59a79da52cf3a91627c79d456212e3f6f39e1f69aa0053bbdb4076a3f7d05e5dc00000000000000000000000000000000069047218b0ac1e07650ac8f4a1b9235f68408f543517c4ae3c0ec47c79b468713c704ff3680edc8abd1bbed7a5fa75d00000000000000000000000000000000137737706162e02cfa75ce2154d57c9a3520818cc04626654824769ad92ff7977942f3881a28284ea47c14f353772d0b", "Name": "matter_g2_add_4", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e200000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d", "Expected": "0000000000000000000000000000000004dd5dfe38fa70625216ecfec60ea8d38602552726f0fdfb8f392362ce845fe0fda76894d0e456796e08462bb941579f00000000000000000000000000000000195a85cd0685f4053ee539de7e04fccd2380819b291f89cbcd63d5a0015b3214500284a7c6568a71f52bbdbc38be410a00000000000000000000000000000000107c211bad49c7dd8555e30f2500c67e7175eb98a8494f3d5309c65a93cce89572b7b5489428eaf3f0a5c1be323c5352000000000000000000000000000000000c11f978150ac35722679cf79443b3706d288c968116ddedc1f1d0fca8cd746e3c92dc006330be14886c53c41feebbf9", "Name": "matter_g2_add_5", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4", "Expected": "000000000000000000000000000000000fb33caed4de22cf341bb3e04d41c0198b064c1d371a24f5cf59595ab4a1edfd379916a40cc405d35f0603b2f8fb987400000000000000000000000000000000131ad6172c20b3a1cc2542db037de1324086fd9cd140ae97987980f260023d91b24504181af6fcbcfa242f48e99559320000000000000000000000000000000004a0404c00789459395f5344544041785d10f2fe74d4bf484966f5e9b6b4c4c8cb113a811a4fa82a1cdf8e3242bb418900000000000000000000000000000000086ba6a914f3f07bdc6750fcf6baf76124a17964bf9eb9a12982e8a28ca04360da3544b69436d5663e4e94bf7189529b", "Name": "matter_g2_add_6", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091", "Expected": "0000000000000000000000000000000019c8a1a206c0006a3033377abba4c31c55710a094d8c9dcef7560818e90411861ce7d189e2763f8fe69bf75e719e4efe000000000000000000000000000000000cccc6bba8691c210aa0a67d26584a359fab94041d853160abd9669893c0d398c805cc37fa3c33bc5ee5ff915b985c45000000000000000000000000000000000e353c1993c36763acec2a75495560e743d099b565f3de195e011afcacff3d60502801f47695da7dd589af81e772eb7800000000000000000000000000000000100c6123cf08eab6c59d78b414fa504ed10c204851289b0598b40ac31971fa12cfda4ef7cd2d64f9797d4d2b193e0bd2", "Name": "matter_g2_add_7", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2", "Expected": "0000000000000000000000000000000009fc3870f88288c680b43d63d3bb5305b99fe461e59c07be981b8819fbee0d1fdfae0c037e830fbbabc40cedac7919720000000000000000000000000000000018bdd4903da4d14fa28af4c2cddcb708238cf68673ce77a04a3926c4aaf17d39a831c5401e84dd042d6adf595a1763710000000000000000000000000000000002c398f0e8ad9752f4aded980bc5de2d91118db06818d815c11e818ead47e7065823737db8e304bae32969cab065d1ff00000000000000000000000000000000180642a633c3aa402e5c0b18fcb6fe8c115575b863abda59b5d91997ab01014faefc975d0aee994f98cf37ce79eb95aa", "Name": "matter_g2_add_8", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb815960000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90", "Expected": "00000000000000000000000000000000199600699a6108599c638df8f965d73b5de4ca74598df281ec95c539de2c7eff9767569692d8e0ad120fcbb3d9335b95000000000000000000000000000000000c42b11e2585ba93521b3c968e9dee07e4f5168c11087d8d750795555a105df70c969bfa79b1ab4e5fc8d81657235d08000000000000000000000000000000001370daa4699daa99e9940fe04f69150e6f752798cbc0e66c91c3bd46149d935c1815f32d7f14b510e16d475044eda9cc0000000000000000000000000000000016c7a00be10de5732795cc3ee2951e58cb9d42f9b05d02fbff1b83fab5d3ad830cb8178092b76172108d7a53afe8c539", "Name": "matter_g2_add_9", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e97795505419600000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53", "Expected": "000000000000000000000000000000000669cc8a3acae17f99f805afb9012a38851a9e8d4fd9895a9946c29fc859849c24d7ab7b6278c449cfbc5f1d7ea1fdbd0000000000000000000000000000000007a9095be808d0ebc99bce94e851d2a7cd3e1977b923064ab5bbed2347cf18f3343e60120fa051d12fe27da3146cb423000000000000000000000000000000000f1e7f75887651f67457f6dc064d7c11934035d15fe4dc40bab970160ed1b1aa230a3fb84dc1da08770d847c0216347a000000000000000000000000000000000efbc62ade1678cd70eb38c644038bf19e52b0859f65747068d9f3124762d951e4a6ff05f34b6d14919774f8409adff5", "Name": "matter_g2_add_10", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a90000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773", "Expected": "000000000000000000000000000000000e25a38d0ce2aabd2538c95ed463f226e3f29ce7f10e1be27af2d3db741926d557178c4b125af8789b40480d8beec0890000000000000000000000000000000002a94b7c57fe2783d055a537004a3b67e41f5374da0813094f5944fbabf4d27eb576dc8b21ccc15f8339df14ff8785220000000000000000000000000000000008b9efd8abfa4fd71a8eafdba9df38360ef0b0a117c0052528d1c24df5032635eebc7b201439f5de858514666c68cd270000000000000000000000000000000012a2fde51f6f4a98435c325dc3b1ae846bc33a5ffb3b13fbe3fde2f74dec0aa815fa8e42392b3dbf798cf547fdb4db0d", "Name": "matter_g2_add_11", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b381731734598360000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e", "Expected": "00000000000000000000000000000000067265782d58b04a2ef3dd419cee506e076e49d1119e28db1df7f0e22cba9bbdabc560084cda50bc8db3915fa9c489a30000000000000000000000000000000012448a61fb2f6fd8e355111b671f0e888304284b72d5688091f2ed00edf7ccb7e5bd8a733a910d6964dde07d393798470000000000000000000000000000000005f687356ff6c634eb46613be8e98540107e706714434faff54510234d4aff42ef7752e154aed63fa8ff905ec0af628f00000000000000000000000000000000180dca84a37c964b30f5cd11a090e54acea102f1b884319f8d1252a37bda005512ffc39dec8e33af0dde0d37993f846f", "Name": "matter_g2_add_12", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792", "Expected": "0000000000000000000000000000000012a29d35c9af52f172787c90c5a3e77ed29d66feabf5d7bdd6bfc14dd9a05d402976b84d44647628c908d1816f4e7100000000000000000000000000000000000caf3c372e36de557ecd7eba02e6a79b1b4cff30343119df7a23662c8512095e051ae2dc27e577635c74a260be2b084c0000000000000000000000000000000002ceca293a58bc9beb4ee9a0679eab037f5cf7b326d65c0efeefdbf384ad8e4bc08a3a75a02e6b9cba8963e65d6e76ef0000000000000000000000000000000004631773a6590bc89b49a75bbbe2e732f9466ba259ef7a04ae69b6aa5d5a2621c1918eb213101f6f7eeee4656a7b1472", "Name": "matter_g2_add_13", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5", "Expected": "0000000000000000000000000000000017f155ed9911ec56d71d63d57556de071ebe89be36e6bc9943ec068a70dd5a6f045dfb9fde5c1e29d52c9fc17579452e000000000000000000000000000000000a60d62ea549edf4b11f62f2321f39d41bf11f3c4f858dc7db85b1dab1b7644e27eeb1d022d6082f59c65155068d2c390000000000000000000000000000000009d309145fad15860e556ec4b4aecb415865954247c2034d5bc96026e4d6f7612af6e2db99f4e462acee2b303134b91b000000000000000000000000000000000114ed157e3d020c5397cba7e10cb864aabb47461f166a6724614e689274ae74c505fb6ebfe3e88da0d6c272a15a0527", "Name": "matter_g2_add_14", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db", "Expected": "00000000000000000000000000000000118e0c81f9157395578f0fb83b179721de2af3326d13189cb8f43911d8c3268a11fd9702f09f14c115bbdc43d5fbc08b0000000000000000000000000000000016a548df8c87f432c31e4e32c3e5b4d48d6f29fbe391d1181174be9dddee450e7e96bffe8c9f23692ccc080116592944000000000000000000000000000000000eef72a5c698c58f1d2ae9415da256b54d7b1ac37a1d1b88727c0afcfd854a41973c6cb10ecbc3a90050fe3d8d3ce8780000000000000000000000000000000019b16ca8f955dfd21830a3f7fafcc97d7de977bafe1983892988aaedd430d22674d97897d24c1643e99bfa6256df4bf7", "Name": "matter_g2_add_15", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca800000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e", "Expected": "000000000000000000000000000000000f2bf3f69276d390c9fc2c15e9f5f5d0b3cf9a6eb028c44811b481f376ab60e17d33a04b78348e46eaa94332c5f16ff8000000000000000000000000000000000bedd0437fb3f4baef87e56f33c77fcdff6a5512571cf11fd9605697abd8763315f1fe4bccf04acc6e971d6aeefd9c1500000000000000000000000000000000067c3ff69733baae2fb4ab77cddb7563047c428b40a257a375f8cf8c9d230a6619f7932b86e0836fff0c1c60d2c4dfd900000000000000000000000000000000057526faed8d62aa10e89add5a338320c748ca1f96ba5ceb579efec69d17475571fc4ce6fce3a93398ea88340f0e969d", "Name": "matter_g2_add_16", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91be0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9", "Expected": "0000000000000000000000000000000004fc19f8fe47e6acd37567016704b07f906e8741fcb196f697e1fc24b0204292693ff424bf1c5e407f5bcba5a3b1ab85000000000000000000000000000000001816f992c3c461fa6d2014ced382a35b0d70e61927d72b4d661434efff3dafe2f4b6cc91bb1a5dbf809f10f3ed7f36de000000000000000000000000000000000dadf7f7223ccedbeffef31c97df7e01f99299da71b589c8828b65715012aa343d7e041dacc57b34a6b5f84523a7938100000000000000000000000000000000167f7e73e22df81bd2a7a6f14e940a401bf414e5d18b3aa610b2a82ca8f46aecb5721d0092b27f8968b2302c37957268", "Name": "matter_g2_add_17", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7", "Expected": "00000000000000000000000000000000041a5783c748247f05457d30d16f93431e9046a236d5025cc07a27b9f2abaaa556e2df65cf0f0015107253fe94d8b4dd000000000000000000000000000000000193638bf69c7508c4b12808a62e89883c34f97ded6e1b5dcc3f28191e5c7fd901a72a85ae386acccc9865f8144b1bd500000000000000000000000000000000180e8184ab583da58b77b8a4d108a366dff3e3b336ebc5c9153fa815188edc95e7067ef25f7d79526c295d634bc98f5100000000000000000000000000000000125b147100f6df0cede8e22151b3423b1dd364899fdee103c71a44388ff002a367627a2342e15833644bcde61f2ef6b6", "Name": "matter_g2_add_18", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780", "Expected": "00000000000000000000000000000000095fda8adf3981f4468fb82aa0ccf80e55138c922c6422cd8e67f53ee63e7a390bc345469e9211a1f8d810cf4ba27d0a0000000000000000000000000000000015c19b6af21f75e8e53fcefbae1c8d7f97853a8aae5fa62e606cfc92ae71890702ef9dc5609d3ca8fefd415fbd820c04000000000000000000000000000000000007b7e908766d34c5d99cb7cc76d5d5ea83c29ae1d9b83b163741bc9962e293926b1e251b546ce0c1268def728da78100000000000000000000000000000000084fbd6253211f7d66d52b7f14360729d54b2f94c52f2b76e521dc3961c40b4f19944923f64c6425a44eb158a9727a4f", "Name": "matter_g2_add_19", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e170000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e", "Expected": "00000000000000000000000000000000121e7f2eb906d0b31b8ce5cc46638428b6ee57a1ee70e4ec3c2bc044230b9b86875abe0862145b442c0e34308efc690f00000000000000000000000000000000139120d0a10b82737561d0b3fda01b6df69d9beb7dbabf3ddda036f9b4c317f3ac1eaf400013fe5ad664bea44a73b336000000000000000000000000000000000a923184b381027d8cb3f82708802b204566b2b8bb6a72767aa396324d8a26b4e0f0cb92fd1914d77a4e9af2f1ec31e3000000000000000000000000000000000409732f2225cb5e5c002bef17512519eb1a18bf6c3d7f834d0c7ac8a38433c88b550b3f443d259313eb1133620ebf0c", "Name": "matter_g2_add_20", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b710000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67", "Expected": "0000000000000000000000000000000006a200642d5cece5eaacacb36000b4b897e8d8c661c8282f90495002aa515c7638183cf1e80a0b35e953adb92b6bb845000000000000000000000000000000000e88d4cda34e98df4d727fda79b67961b5b8efb1b125ef2a8eafc481a2cb2fa1530e59a091f31c25cc49d38f545491ff00000000000000000000000000000000082f38c1a1c35981f537547dc3b59331ab8c5e8dd261df58fe6f0c44ef1e65d0cdc1980e1a62f6248f38d0afe91e5627000000000000000000000000000000000eda1002e202e9ee4df5354cb87760d4df32eba1eafdad27cb0636879370a8f93be0bf2a30f15f2fbcd7e52c1bdf6b05", "Name": "matter_g2_add_21", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4", "Expected": "000000000000000000000000000000001341cf3316152ae8d57ea2194224f04756690133d2e02d077dc271aa577278e346e0ff66e8a49ff8c983fd34546e1f6f0000000000000000000000000000000016c9093da650643f4b4061e1c6e55da6ebaf9f234bef8325aeecad3863a0a2f53e1cdb2d54aa8b075ce6e6632fb4cd660000000000000000000000000000000011eaf3dee010bf2a16c5fbb1f7aa559cd4d831f087d9dfad4e157a6d2b6495e370d9791cbaaae19339a65726ebfc3b910000000000000000000000000000000008476d793305204be414819fce2ca70754a532682876277bc0586514f2096ba9998ae848c722ead6722d5af9395ff77f", "Name": "matter_g2_add_22", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa097000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa", "Expected": "0000000000000000000000000000000009792d98ab9b90c2467ad0d070ea44f382ec7ad5290a59d889313c5a55d7b8e837333ad7ecfd97221d405cd6c549dc8e0000000000000000000000000000000002b92dd07b61faec23f48b8a7893dae29509fefd688a978bc2e870d4cd6f963d708a0611b4aa65f5644fbc6ba4c5e66b0000000000000000000000000000000011e46a283946a8e033afbf7c14ce3162a05867809d7de94a090c8cc2cdca8bb79add21f6e2fa8d7f39ea6d26cd37ea850000000000000000000000000000000000fddb7cdf1f1126e7a6780e4892601121b289a386ebce0caf96cd392ddc57c47e3f9284889fd8a18fb330d6c40bdf67", "Name": "matter_g2_add_23", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e", "Expected": "00000000000000000000000000000000054dedc002c5f2da8c6e0a0146bfe5c83200b276b074e6d6f2c397e1208f152d3ea3e8f0da7da62cfd2a028d4c94fe5b0000000000000000000000000000000012ff307f86e266e7a212484a169d3e81df98217c6f715176913b0d383cbe4e790212da7feca0cea66df09d92544fae010000000000000000000000000000000009c211438dcf8ccb664b535e73eff304b92aa2f568aeaeb8e10ec142f92b211bb8147b250dad77d508cfe353667b6f150000000000000000000000000000000009d1734f4ecc88fd56f412f9243c387b9da659faa3fe7295580a6b7519b1980bd074339fa9b0bef44dcdd0cf0c4a629b", "Name": "matter_g2_add_24", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f", "Expected": "000000000000000000000000000000000896a38ce734c550c178786092292e737d44fa5f503d6d3b66c75e6bb70b59d1db9e8baa1ea3e256e2dfd8a942311e75000000000000000000000000000000001231db96a35229a4c7507b0ec193491446a0b43115c27d18b3715fcd4aea14d4e5c99db5934e73bb0b86f1bb91ee96fa0000000000000000000000000000000000d6f95d5637b29ea889c028dacdcb484d8ccdb243da4d5ff49e5ad82f234d414dc1484e9ed6cba1b5940eaabd3066860000000000000000000000000000000007de052fbb76902e06e1783fa8afcbb54a5069b4c5e9cee78d43da2cf76f24843a740a9eec6fe9b8f9bc4ac9baea77a5", "Name": "matter_g2_add_25", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b8592390800000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f", "Expected": "00000000000000000000000000000000156914a9137e52abd4579599dea4c0f857eed0457ee1d80635d3a6ccf0c766ba8ab1b6f989711fbdf125c4ff06b597ea000000000000000000000000000000000c60184e8ab32019ce20d2d137130f657c8964406fe4abb26da232c9c5dbfab243837d700c88d6b9ea4b8f0a2f514281000000000000000000000000000000000dc3e6e3acb898552791431859943d0a83fb4ccd62e4ab2a971370a93a99a9dfcdbe4c42535aa063354e0f2cd48308c300000000000000000000000000000000025be02da875d4990d1f0be626ce634c4856ea91f88f636bc27e313e73897c9c13a1e3ae70c1227dfd4fba97f521d6af", "Name": "matter_g2_add_26", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a300000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352", "Expected": "0000000000000000000000000000000010124c1c1c10868b570d2969ebc3bf5cd6bfab13ddc93f0fd2b8a1742eb8e04d31063bb81c52b92e253128d4cb4413a60000000000000000000000000000000013f89997cd2ddae00cbf24cb66a92146c553c6fae41cdfaef14d49078729f239ad2661937dd0d4d6ffd7076b03e0aa84000000000000000000000000000000000ba2ecf990cd846c95b35ab60d4f97f5814c8189190df9d521b3dae462f2d44db006a0daecf6b82c1459006bf82ef7c90000000000000000000000000000000016dc129b83cca5b3c699628d081306c5fa61faf9dda5e92894931714037628fb829c595bf64d4a7fa295f136ae244601", "Name": "matter_g2_add_27", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e055086016000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6", "Expected": "000000000000000000000000000000000a66f36f2437db57473bd8b7670994f1cfeb8b43c0ceae358e63a5e4e52b737fce6b3d24cc4de593bcd44c63f2c5935900000000000000000000000000000000070b7ad970f03a38c8a31452cf11422159cd3331d746031781a5861e26f54efbaba63dcb1db8bab997eada9c3dac39cc000000000000000000000000000000000ba4a9d7350adca1ae64e722df11baeea77c5fb75c5b52c8c46b9d863a70bfed1ec47888e907213f4ed4dcaedd37f20f0000000000000000000000000000000008a64244f1870a1dbcc4bd4d5c9eb5cd5225713dc73aa22bc46b1cea36c88a66f85251a8a9ba7279c88bd5dd37a06f7b", "Name": "matter_g2_add_28", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3ff000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703", "Expected": "00000000000000000000000000000000079f89f2defd1f97efe0ba1db28523abc88cdf66efd39918a600a07c5ed5b72ab9d3354a172735e7749b5f6814a48f4f0000000000000000000000000000000009e361b8609be8057e5b3c99eaa1727fdac17edc59239af17f55d72c8b8daa89726f4ae240c742ec4b02fbd89d45c46400000000000000000000000000000000121b475a2ab50357ce80fe01fc461195029de20f61474b0773d80434253adfc268a775e1a0e3b7df5e85d1ff8c5008960000000000000000000000000000000019a76aef4e04136b1ad0d03586a3d8608ac4573715f18d5fd6907d03e5fec7c5659e15c19fd87f242da972b651dff5fa", "Name": "matter_g2_add_29", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b200000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb", "Expected": "000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c6700000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02", "Name": "matter_g2_add_30", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233", "Expected": "0000000000000000000000000000000013f8cdab447ef9be450b87f941c96d4e93d5efd811d80c6a910965728f7dc496dec132f3fbeee5d1e84ed7c24ca9c2a8000000000000000000000000000000001537d5caa13ddfac93f0f86729c743d9a68175a78c730528b581fb54b1f4d020473b3b766e3882a485ce5d02ab381c33000000000000000000000000000000000b370903684ede24f3df80e3834ed414a765cdbad98f20c49bef8663a82a468d3911d6bbcdc021e22c252e83a857e55800000000000000000000000000000000100cc8d05f071904753776c6092a38db84c5de751bf93216131a0f9a50bf78a722344a14b3be2a9207568d1f669d208d", "Name": "matter_g2_add_31", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f860000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce", "Expected": "0000000000000000000000000000000003c5498b8c2d4765a270254dc927c6edf02acf0759540ddad951ea8c097bddb949ea0bf19942accd615bef21e8572dff0000000000000000000000000000000004c17bb648909bdddab4dd86560cb6b341e96f58c515ce471281f226181bded16b358b56d72e363f9ec491b8a9dcd92c000000000000000000000000000000001828973958204f8ab8cd13f5af5f3529f368a149bfe931a8002b61a61895457fbcb0cc6874631bb55799c884b998d8b9000000000000000000000000000000000f61460bf61bbf3ce38917850bfd3cece1e3955ce29d200c6f8aa89076c70919c02668678edc0bcf94efc9e9ff6a650e", "Name": "matter_g2_add_32", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac5", "Expected": "0000000000000000000000000000000002c6104b3494fdef86d53f87bea68d313188c0908b935fb3b9f636ccd401c6e9cbd33bfcdd437e1a0150d0e4b9c3a881000000000000000000000000000000000bdc88396f807d1ba8d4d6e284d008b5e40445ce32c23a0178824fdbb6db3c5aede7687eaa2f12249125cded57052ad2000000000000000000000000000000000c7004365c1d3027997b55bd258dfc61ae07a762666fba2a14aa2ca116673fc03a6f694c069f53cd915fef6d37513101000000000000000000000000000000000ec17688d8f53e2c92502091c859cef4fe9a57ae984cb1e72686bf1f0656b10246293cae4b96214a38dc76cf2709bd59", "Name": "matter_g2_add_33", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c99739", "Expected": "000000000000000000000000000000000a44e6a48ea0a95667f607ee66290cb0094c964baed779bd6656941db28e30a7e9effe49a617be9ab376af4f535cc28f000000000000000000000000000000001933b87310bf5fa60b1abcd13bb7ac3f2ec0a278f6a0a70c953a2905ac1d3bc5a70cf1da885af45d1c7680bb4f7ff74c000000000000000000000000000000000597ce9f1bf7efacdcb0250427d0341e142226aaea060983175ea149912c5c4f3019fe87be6d87d186a8f562fc3059eb00000000000000000000000000000000198b5a891722a237a5e23e3004798c8d3f069af3267152508e283b4549fc5e8388330343f80e606eba30af51c99c7020", "Name": "matter_g2_add_34", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe20000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0", "Expected": "00000000000000000000000000000000047c2ccda315b9c013e87bc9168b3b8dd6d463403f1cefd824fa9f93a99f4c4f98fac5f97e4237f76b1ec91042f99bd600000000000000000000000000000000036861fd0a69cbc851741475905441b51af12c5b2aaee6ce9a27a01a43db810be9c7d6fa401406e98e327703404b83a5000000000000000000000000000000000310cbdf53f6cf8d87e2d178869bee4359a8dd666986d869761a79963680a33ea3ecefd40a1e558acae5ded2ca04447300000000000000000000000000000000108bbb28c73ed7e76a51a78e4d15a2c88c25e05c7127ae89d4347cda00be231b5e70e0b0562caddd4a7083efa4516722", "Name": "matter_g2_add_35", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a84486000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c", "Expected": "00000000000000000000000000000000137d23ed3fa0d7e5928af8d1f4bdfdef08e0b4c0f3bf6f51ed28960ce9805eb8fb254233bb18cbfecbadba95e112fdb80000000000000000000000000000000018615147d7a8cce1dfed6de25cf2fb52f54a243bed4913e20e66673f47ecddad9c5e4ff9653f522180de4b90ddb3ad17000000000000000000000000000000001521f12116b13f785b5211aaf438aa6668bbfa318cf0ed6d91aae963f6f00d32cc5f25d3a02bd902ccc25f847ee2db830000000000000000000000000000000014263b23396f4facdacf13c79864157823db724350bc640abf8fb6d62663cec1069eef9db56817660510e2417b51c616", "Name": "matter_g2_add_36", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e370000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d05712", "Expected": "000000000000000000000000000000000038f9df6c14f84b8ef8045010c8973e5c2f8d2e37268f6a674298de7b15cae82361ebbfaa00ea1cb2653c5d00886b45000000000000000000000000000000001376f7e2d5621aa9d6f7ce45ed11de7e0e1095ebeea976f78eb83189c6852ee199840c14059c233bc3d40efbeeb5eb36000000000000000000000000000000000c7b0e53adf4f0fc5172f903e3fc479539348241edc3e277f30ae6b4fc419aadcfb73a8f8a09a1ae1dd885a6250de0040000000000000000000000000000000007a00b57ecc8b056436ecacd7e0fd346b906b15042e9a700f54f8c3b1d251c566e0c55bd34f7a9e30f1566b7f2ab16dd", "Name": "matter_g2_add_37", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4", "Expected": "0000000000000000000000000000000012662e19e41bfacc0c792f5183596bc7f1986f9bea72c626e187d72111b6ef3f36f5afeeb640cfda99b7044c0d0b846900000000000000000000000000000000050ba08e1b9fe95dc67e6ee1ce60664b291c80fdb59729cdea75dfd18f22fb88f837b439fd119c46c996787d3008194b0000000000000000000000000000000004ea0f488fece967675abdd3c42f8fec25b547cfc45d42fba14bbc55ad7e1a75296a679113d0671cef0aec0c2165f4a0000000000000000000000000000000000f617f51800b09150a7560505079c785ab45cea4705992fc0325edaf4ceb30e1f0bec35a31898db5f810685e55634076", "Name": "matter_g2_add_38", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb300000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc", "Expected": "0000000000000000000000000000000019c774e968049bde2188e844c3413203bfe2c4355edc8cbc2cf6f977c34c0a42a206194e6eecba3c97b24558048f3aa700000000000000000000000000000000081ccf6f111575a946341759b9faa13f3608998fbf4ea3b547804737e30fc7e33495caaf2aa328b19bd48315c5c7f9e2000000000000000000000000000000000a4098536041cfb808176c7cd8e980eda613a2b390e8d63d607caaac26db02fccad6d87412b90cb4b3e186bf9ccd31be000000000000000000000000000000000d3c784c6587b9f786c06099a62aa639f40535b512ac2440912f04dfcd1cb5851b7378f381fcdf02d4e58312eb7e442f", "Name": "matter_g2_add_39", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05500000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6", "Expected": "0000000000000000000000000000000016fc7c743c5ba747640a6494fb3c30caad5a1e9719a1994d0ca73bd1645fec118a2887acc8876d105102241c10274cd300000000000000000000000000000000058a42a0095a7388fba7ce71dbef4ecfd2018c3fcdde14afd2be26588de4689d8de757e1e3ff22645fb8c17aa60265850000000000000000000000000000000010bb622f649e346834b95e82f93ae83c71c0a65df7842c4ba88df7f6eccb0217ca9377167a6d14777e0474c24821f8d70000000000000000000000000000000010c180c685ea3d0146eb82c007fec3efd129880f18f838f1cd2f80181f5a4884d6b5cc8247430fb0c1701a57f9d1d485", "Name": "matter_g2_add_40", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d03", "Expected": "0000000000000000000000000000000019419b635c3742cecffee02ee7e2b1f18ee9ff15e647ca0abc4398ddc421ae7e0444e3c1ec377def9e832d8e64fd40e2000000000000000000000000000000000d9b4abfdaf3b4c7bf00fa07579befa10a3418d8fa0f3a9c31e59ae48b0de50fc8e6d583aaa4d0fe6048bdd1a9c60eb60000000000000000000000000000000003c96d57034ec97c4abef1c2c81f4d4b0f4b6eb1e9dc5464bcab28572555b9b874df80325941501c3766fd7e06bfe7360000000000000000000000000000000002dbb3d72385b562ddcb9a80400ab3770f00d22b880cce2fce1641042b9da669b22b2fbc97617648c25ab644e661e2fe", "Name": "matter_g2_add_41", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbc00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d", "Expected": "000000000000000000000000000000000d32b00154a5fe75c576c098419744ac36b911ee800f94bd598ff9b6adcaa39c836bc158c5d6af72c9e715a242d0fe710000000000000000000000000000000006e057c13885d6c05f5d92061fdc4d532f10d31d472c371e71367fef7c5fdd3741e665321d1119b895660fba3770431b000000000000000000000000000000000bfe695c3364e15479741e974f838649e789a76d073e552aaa60981fbc6d185eb7b297fd59e51535965214a02f5cd67e0000000000000000000000000000000014f0a27412248e3163e5f82fed02a25d953b336b0201692f08a3e8e9a9d223b736c70c1a39826a0888fb02a314e223fd", "Name": "matter_g2_add_42", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6ea0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47", "Expected": "000000000000000000000000000000001566022247ce012b7de92c8495876b4de91c36448f4f7e00f6e154185d38a735e701dda989ae9e37d332a5e60af5d06b00000000000000000000000000000000065aa42560df7990df2098827a55ceaabf3ec592c53d2f20e5dddc1481ee64381accbc8e58601428d33589b3af78a4b70000000000000000000000000000000002d9b0cf8bfd1adf76bca80ca351a4340f02434090518807e07ed76440497042f13a0cd7a9c30086872d6f145808fb290000000000000000000000000000000015daaa131431e3e78a6221091640811fcf88c835ac975a041a7ab50bc1d06b80e6a3c9ae77d2390fd14cc9bb009b47cc", "Name": "matter_g2_add_43", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710be000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf", "Expected": "000000000000000000000000000000001290bff629c93d992ad2cc709317c48980b0e56a32fe239258c7aec75e4523e0bc0b81319e100d10568a44847869a8d000000000000000000000000000000000055d9098e08eabdf2b883df35efebec9f6afb16d651ebaca1067e2129146268664ec51c8a4f28f13a250f3e9883053780000000000000000000000000000000002424dab6f0d18ea8bdded2a72bcf87c13307d27d53e8ec35e91eeab97fcf3398135fd436c530c609fd47a3508472bad000000000000000000000000000000000b25d0db1e28b98d4f9d3c77c0b71489c51186105d93be7fc2cf8c72b8abd8959340114635e705e698b0f257855ea4bc", "Name": "matter_g2_add_44", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be100000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2", "Expected": "000000000000000000000000000000000cb2998b4e634bc83b5585b0683b7b561f260eefb826719bdc3c95e8ae51f8f7b442d75d69e0f9228dacde2ce80ef4e60000000000000000000000000000000014d30d1c02122143868ea01b454a4f33432d875f8ba66e6bb1e02fc161bb5f9298e673339a9183a15759f8b94b519cad000000000000000000000000000000001068bf3c768e8c9e9058805050394ea820b5f60bea6d271f8e1fb665d3b7931ab0cc03dff4cbd24577b2c254a956e8200000000000000000000000000000000008b7f4148bd1f4926d2a84497b60a48701057ea08855bb9a2f838d2464e66360a59d058d9072f1416023cc72045af558", "Name": "matter_g2_add_45", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da900000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb", "Expected": "000000000000000000000000000000000a7843a1d67360b8a6976aeda2e4e98f1ea229a4d84b947dcf5ed8215173d5cf783920a7714f5b048778df30f01a0bed00000000000000000000000000000000035663ceafda9e5bfe934cff725b36b258f12afe749f907a560a06da4abf8380853f8de31adf14d62cdb310d8740e29b000000000000000000000000000000000f210d576aa5d4cdf5aefd8e55be099c422debc217ddf0151b8801f7d16456c97d1e134b40e6d71d296ee2518e50af9d000000000000000000000000000000000219efb35c68540c6bb0ef224e68dae6f7d48425c2908440072f5f63eec3c8e750b559c73e33464d0b5cdabb50fc4d3d", "Name": "matter_g2_add_46", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee", "Expected": "000000000000000000000000000000000ce704e650605f747cbc0bc76e82de8569ba7b3d897eac2bf5f79aba17ef4c989731e959c0bc0b7988000a9b0aef39430000000000000000000000000000000003cd3f3d978d6c85d98812ea0e3d21149bf4151ad1bef966ced124ad62dc7cde55f16e8d08bb1ad54d3a23bb73795d8f0000000000000000000000000000000019d37a20fcf6244c2898b271535e3b8f279eaac5d8fb1ba142096da383488eba28a21d038d7a9d3f9e8a008d6d3ee1d20000000000000000000000000000000001ba9c1720a4ef07ec752efa1ddb629505b3586af415c916fb0ed2953cd8943d9343268f438db860f0bced3e690a66b0", "Name": "matter_g2_add_47", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c70000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb9", "Expected": "00000000000000000000000000000000160d8b4bef36fc3d09af09dcc8357067c22e421f3811deea66faec42a2f00fa4aceca8725cf99062613126a9fd7bf7210000000000000000000000000000000004e8691a42c8f3ce0e7c0470446689e9d2b3cf57d55fad7387d624857f977cb9c6864c87bb4b6a2c17538478ac5fb5960000000000000000000000000000000015e20f6baef033efbd38081d5a10eeb3c67d89ebe5cd652110b778313c9e86cffb45231616d5b67e9ec8b7be15980aa9000000000000000000000000000000000af75dc221050256015fecc2bd8113b42afc9c624e5d28d7ff8312af499e34a603d66a4304f263729b440b6266538316", "Name": "matter_g2_add_48", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c6", "Expected": "0000000000000000000000000000000013edd8f016f6af49e9bc461ca14c438a32eaa3d1270a5acec99a666aba3f0a7e7eccea81720971cf4432bfa94cd18392000000000000000000000000000000000dbea5617e44c82da828844a5a4a1426d43422fd0158204a99f53cf9821f82f0bb0130a2123297a6941f695e172d9c5e0000000000000000000000000000000005f65a445e9f2d57dff2b210209f9faeb1c8b446454de4724d990aab20bd68362dd7ceb5b95de361c129855abba83f7e000000000000000000000000000000001219ecae79d62d3039e642369353993b1ece049331f06be256f06b01a1c3b0c617221c8d8f0bf4b6a0abe1191a3ee8e2", "Name": "matter_g2_add_49", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307", "Expected": "00000000000000000000000000000000158da32df45fe3e9102010bfd7faf3fde936bb8e52f68262ef479ee825a0d7169ff753aa042883a5403103a9bdafd2be000000000000000000000000000000001800a5776a47f52d2af08144364a6cd7442a0e2fc214a2d8d285a29bb7bd3a0293e89f0a1856223a527100d0abf12899000000000000000000000000000000000a6079d18ff3367c47fa61a57a967b782f3529bee93f452ecebd4f5c404b3e1769c100da9b8aee4258b5191ae1dad9a90000000000000000000000000000000011d3188a927e8f13aecf7f8637be6ddbbce309393a94fef77923c286244f8531d3e137e031d8c1af829891425afd53a3", "Name": "matter_g2_add_50", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52d000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc26", "Expected": "0000000000000000000000000000000019294d87be784f0f8fa29de80d45a697bcb694b32f3f6d7641d4b08d8a7ebdad0ef78ba5ccafd6b7f240e1cbde019c51000000000000000000000000000000000645f7851644e1e7e255d0b3dca769b987ec3ff2c9eda42cab65dc39be2f9858c31f307d59f6a2caf9dd932d873d2b08000000000000000000000000000000000e8e93f39ce05a11d40f3b52262980c79ecc52939dd02b94df3e5034a57061d040b0c8894189f4626f37bee485712dd00000000000000000000000000000000001e0b7c9c3d7456b2c0ad842083e9ce2a00da91cb1aaba371ff4b9370f0f2c08f4b53b8e5a3030c99b2957cbe5f9e967", "Name": "matter_g2_add_51", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d761800000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb34150", "Expected": "00000000000000000000000000000000040f355021ba50c9a3b2b4267668ac8d76dd88991be984ab5bab9c96faed6dcc6e8eac78ed29cd6f7d687dd55cc5d5b70000000000000000000000000000000017853cf0a39332e3c7d75b08b2940d693ac7cfdac46719787c22b55a2ab1036d6f95b68075f1c585942843aa486f17bf0000000000000000000000000000000008696feb333417a7262e8976d1546b6d0a9d5970095485b18efcdee8993b16f42e6dbfdd08d30c45fe4af6a5e203de07000000000000000000000000000000000ec26926720243124ca505c0e04923f3cf5eeca2abfdaf4388960b87c6c1713fc54cdd1c825e2ea359cc67b3bebfa2f9", "Name": "matter_g2_add_52", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e69000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b1", "Expected": "000000000000000000000000000000000f3dd56c416db1c06fd27e18fb852c9e1662fed42005e253230a7a8f7c3e0b8ce637666e1d20952c219cd2068d6865f1000000000000000000000000000000000aff045afcbefcdcb5255805a86e8af3de881e5482188c487d15ad1b799cf551c1d48c7665028b05ceb2e82e15ea4ae5000000000000000000000000000000000e0e6ed04926aed1f8c6a4e13227bf2a99d9d6d349a9c86214373be693db702a0011b4423defdb7d842bcb6f722c70b100000000000000000000000000000000148b1af285c65b12eef498f1c9e57a673e7a3803088c56e32aaae13dad3977dda8d3e27809094f8d8ed607239610a1a6", "Name": "matter_g2_add_53", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df10000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e", "Expected": "000000000000000000000000000000001220b3da7e7d03823458bcdcee82db56957e5aec335e9b543ebb0f3cf4fe3cf6ecacb6198c886b9abbdaa42f528b4963000000000000000000000000000000000138233b166547e9e9ee9d11048e2d2579b2b111af5cab372d36159c4c45e28d836d733a1265e8833da64f461c0a32cd00000000000000000000000000000000005f860a0c72034f1a928501d9f549e5c2a9dc72670272fbf35a0b301025c0fc751d55ef6fc2c5bf7ff42df7693f3dca0000000000000000000000000000000012c73105adf97bc0dfec1f56153c57c6fdb9d68341f4397b72f5b6c667873ff7ed5cc841451b391e33290cec256395c7", "Name": "matter_g2_add_54", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0f0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e6", "Expected": "00000000000000000000000000000000014933a0923416428b5fe5be7120bf399ab62ca091b07d03da3fd2ff080b9c411c3cda3bfef40c8450ae31c412dc5feb000000000000000000000000000000000214229a73780d4f260364649e9eb2ed751ad3f687a832a3738ca2cc81a3acf12757651e88c4bcd79239bc0b0c40e5a6000000000000000000000000000000000548f20fa375e578084e085ee71df5f8ddaec1db03a1415938d9521b5d9c914b5295835fc07263cdbf49d7802551156a00000000000000000000000000000000063ecd9efe55229a76fc848728e940183c23bf47363cb34c5a49837e6df8a5f0dc29d7108cd10ea08e82ccf017d246d1", "Name": "matter_g2_add_55", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a330000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c32", "Expected": "0000000000000000000000000000000008a71a08d2c4e2ba3d8774dcb42d3e96c7f72d36fb3b880a4049b078d8257a7a9a51b0b34c093568baf4aa6de70e709d000000000000000000000000000000000daf83b5ad4b91b557982fc4b9b7dbed2998aa39fc4658ba671f5f27b3888dfec7602949cf626c9e6ef21171acb185600000000000000000000000000000000013a7ffca291d9ba8790ca0462c54c147aa22e03a2413b756f27583155932aee65060924e46db321b3fd6f22ff7f54041000000000000000000000000000000000289d7de10285285279aee024e52476fa6fca85550f7af183a161e395d72e1339b629c64127f96bc85858d80e73dcbe1", "Name": "matter_g2_add_56", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a6000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95", "Expected": "000000000000000000000000000000000a4ed8d613cfe4f5dbda1d0c6812d0edee45ffc2667323c3828f8ce4ab55c119e92a82f2c3d06afe3adaa4aaccc18f8d000000000000000000000000000000000fe10c5e185f3f8ba81c93754132d76e05eb3543d8aaa8a2d0c98833ce5fa9e2b84420d6e3412e005cf89d11f5400a510000000000000000000000000000000004ac5f8cc614e3833b3b6dd9eee9ac29501002ba9054554314a4c516bfc8cec870995e811f7892811346574f3c58b2ec000000000000000000000000000000000a6bed54d8ed4ccb09211ae7773c604edc6ce51a05c9acc94e8167026906d387af681fb33a40e72e85cb076e072db7d9", "Name": "matter_g2_add_57", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f254000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150", "Expected": "0000000000000000000000000000000004d145ad2575313a922667b897052063139eef8c61dd375eb055c4a5c52cfbed35391a85df915e1eea50d000b9b6bb5700000000000000000000000000000000071cc73c16a234e99faba9b04fafaca1a943f2bdbb68dcae0a1742acfca1f90c5f69464aba42be6c18be31f79ce30791000000000000000000000000000000000bf725a2f4d7d33c66fefeefce13fb5649a68a93fb7086c943a7bd5663b5788a5ceaad7fd2a219ade832dfb3c0022a5a000000000000000000000000000000000fef4a2610610afef43da2161b86b25a8f6e30ed90053d57f5ee0a10effcdd2af769d32ef6843804b2b6590f95eccb4c", "Name": "matter_g2_add_58", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c5600000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b", "Expected": "00000000000000000000000000000000151ec7c35a67b878420e198ee7bf359d0668ab61ba1a0bc2e5e57b1b7b18838a015464f9910b659fb7d1e10af2801d86000000000000000000000000000000000511536f34067fe931c6e829e22443eb838f0c938eeef6f839eb322d72e2011dd1c33c504dd044e3cd721065d7075b520000000000000000000000000000000010c486f846242024f9bf40d805c8e33ecf1b44cfaa04455d5584db7ebc32c0d29e8742c61886d4ebae93f22c518ea87300000000000000000000000000000000072e184c836a853fd1153eabb1b645bd35ef72eefde4a52db169acdf2d8d68499398599cb4002994c6f4936de1da75ef", "Name": "matter_g2_add_59", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87310000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421", "Expected": "000000000000000000000000000000000642f215b772d17a3aa45ee3aee607321c02b4f7a7df3884259a25ce78c73e9536d46333fa388e506fdc79c708bfd9de00000000000000000000000000000000145864ce36521fdb641761be541a27bbd3f4797b923a870148bef1d5b4b0d463c0a7c8ef07954dad464510d836105e05000000000000000000000000000000000ca038e667fe68111b583dfaa95f88d3b9e46c0798abccd1476071435067e6c0e2fa81d25db6e1175e60efa1705538b9000000000000000000000000000000000cf1cb1b155e4ea47077c42a1a99c3f11f8b27516a808b5e73498ee12363652bb46eab7e55de93513cc2d6272f26a537", "Name": "matter_g2_add_60", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab71960000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d400629", "Expected": "00000000000000000000000000000000128c909854a20ccf9e8e396b617b36f233909a5f6c3524c93cc659d22afe0e7058a438a5ee4345bed914288c64802e29000000000000000000000000000000000239fc43718cd27855ee5450cc9be5be5d9bca8188c22601242a1bb4269ca0fe62ad5e12b2c65558cd3dfc89ea31205f000000000000000000000000000000000a0aec9527febbd35bf041a901b0b35e5e0d48a2d6d733bb557d0767798369a7ccf2f1c278710eb764f721821f9aeea300000000000000000000000000000000194931bad52daa16a648ccf1ba9a4768e5e2900fee4f9bf46ae07d1aa605aabbfe96684f5d2233c0b254cb4ad5517775", "Name": "matter_g2_add_61", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f20000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d", "Expected": "00000000000000000000000000000000189ee5ac642bfd0b612058f96e63acb1feb6b4dce125bf0ea1e56e846775af1a8b0864d4ece6bd96c3b5dbb04e2f6c33000000000000000000000000000000000073d57ab79314e38267ee8015de3156f2c1d5dfcb6655a150b9ab4a3bc9eeddf7b37b3681c49611e02abb012770b3f5000000000000000000000000000000000cfa1363275c7bc5bbb9bb7c03e7bb7f6d6d365e39fccbe62cfe0bb93280527c9ea99079fdf9871abed035b62079856b0000000000000000000000000000000010048e4e96f26710d254110650de36460be2a8302badfc2da8b26147da498e4620e79b4329033fc3f3a9c99b1e12aad4", "Name": "matter_g2_add_62", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e", "Expected": "0000000000000000000000000000000005889133be5f447013d779f2b9b0033667c5af87e1c8a16d239ca3ed238920004d87e00119ded46658026c26988ee63a000000000000000000000000000000000d4ed8fd88f7e1394f2b5a65588bf1c461a292acafdb77703c2790ef249f2de695524293c826252c94967a3ea4a3a28500000000000000000000000000000000001b5ff0aa278c7e87a89d4748aef13b516c49b7dc9f7cd5e0448dc6fd860a7a8af7183a198eebe6c7dd549fef806db00000000000000000000000000000000003c9e40ed44427cc3cf886ca2db341ae31f015c542b857f6702d25cb5036e3e6abeb8d4bf9a0e203281ab85ad89ce0da", "Name": "matter_g2_add_63", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f", "Expected": "00000000000000000000000000000000093b692a68536b16913ef38c3bba7b19ba94a6af1c36a2e54b8ac1754a29c29882107cde142deb95365af00f2d1f537e000000000000000000000000000000001035e70852f38f860a1a04f33081e84f3ed17d83ad894a6800e7b8b9259067b755fe7e08d4c1b297c6d53064ab8209590000000000000000000000000000000013d38db0d8575131865bd7acb6cbe994812bdd8bc7f51b810bc382a6eb379d442c47be20a2c8e751fb08ccce8fea68690000000000000000000000000000000000bd114951193e3bd58cd0025e0b0c807ea073b1c1f7bb04a2a00771b6442e70ea20e1124572ef5b74d2bd87c93c82f5", "Name": "matter_g2_add_64", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d", "Expected": "0000000000000000000000000000000006db1eef1f614613ada8383e63d631484015224902ca38f58ee384a70af0a0575b0e7063675d2dd997ed8a140e2598470000000000000000000000000000000010d7b833f050f18ff4e3a8d0df227a9494dad9cbde88f68802b23e87387622a5333dfb7bcdcbfe2d4d137cb532ef4a150000000000000000000000000000000000c9c40ba972ee0be2823625a23345fe352d701cc8bf9a153d5a55c205ef1b7e5544d0a7f65aaa24bde8d77cb4c31ab3000000000000000000000000000000000402f170c4c3ebb9b1e7d64765b66ba9b8d45b2ea9fe9517626f38e00a11d180e1f8872bf80f6322bdf3a8dd90732ae9", "Name": "matter_g2_add_65", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad300000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0", "Expected": "0000000000000000000000000000000002dccab673b26be02d2c645c82a2c73290f0eb053e07d4f81d4d315d9483e57c58b65cfabeb0172934b9fbb52ad519210000000000000000000000000000000011c34a27c850fe319fe89399e7680064caf6dcbad171c3a23c45b9883ee06ccc3482b2b81e5777759ff81b16bcc1b0f500000000000000000000000000000000119adca3e2b052c045124f021fceb03c979e6eec0a270c7f4ab13674e461839a4d3a10fd48da4e9ae750a238a2649ace000000000000000000000000000000000fb5210677e1096cb5448bcda16646d6dd29ff8a0765c5aa51d83fc952a5ab8063aa96e97f33abf701cb8688c989c363", "Name": "matter_g2_add_66", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0800000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508", "Expected": "00000000000000000000000000000000056489b2248ba672501069ab6742016cc8ab2af50a119239bbd3c0a4b9b56e014402b78bf62b2b37bf4645c3bd3d95b800000000000000000000000000000000046956432001feaba6d230da27a72e8db5c8eb3d52f00616f87b55c951217095f337a302562cda789e5714c4391ac27000000000000000000000000000000000172c2a583c9563fe02d43b2b767c4ee4e3990fbabe4ac536d64cfcf059f0e38672876289bc86915b6344eb398fbc4ddb0000000000000000000000000000000008915b0edade80caee9b386e4a560ff4b9dce33946ee992649466315786e139e3ce241ebbdfa7ee28fad7e6214e65666", "Name": "matter_g2_add_67", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e1090000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f", "Expected": "0000000000000000000000000000000005b81843ef3f98c6a6686f1fbd26f77248497ec3d41aff4be5968d13ba86f86309b0ec4792d74220ad8ef147bdee9aa90000000000000000000000000000000019825376b243f3e374b6e9e7e51e0c969bc72b39cde1dfa09187a3c7c5c2c752ee16fa5a4c8fcf94464287419b3a3845000000000000000000000000000000001308cc0c77219034a9fc3018f1d668a41e6959476aaaa5461ec73d7155c6a68fb08e1fdf8140e18270cd338c266a83f4000000000000000000000000000000000fee2a6e245e3bb570c3b605f7ad805bcd68e9a1f2bb2282f92e2a2e83b69e275b21b923f33a65defa8c4224934aa588", "Name": "matter_g2_add_68", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b", "Expected": "00000000000000000000000000000000166414455bcd0e8e40397f4cafa9628d1a092beaef62d35211cf49779ba98df5c1d692f650c1fcf0893a9d4ae1926b1c0000000000000000000000000000000003dd898d0725ee899b913042da8566a1379aeb4dd5f0222ac784205b4e74f32858ae490f981801b166a01fb96266dbeb0000000000000000000000000000000019f0fe4f12b113b337361b977aff7cc7dce50bf37c2609b9f311ce340d30225de178999b73345ef49625518e52aa4d7800000000000000000000000000000000090bc07c6270901d706a8d28d512b07fd0e03013d94d4e43eafbee59677998bfb7c2a58aa93571fb49c35518b6331bca", "Name": "matter_g2_add_69", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b22000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d", "Expected": "0000000000000000000000000000000019ce0f31d9ebaed0ea1d12d4e232bd3ad48373fa465af44f1c8015102b624d2f8330d1323fb2fec524e83de0f6699ad7000000000000000000000000000000000915d65fef96562ea3b76f3152aa1b8e445ef50fa66dc487ad0c04cfd7a33b5ee48aed919eb81fe83b1f4dca59b4990d000000000000000000000000000000000e4731ec887261f29475523f7dfc5d21cbbc1b883439701a33cd58bd24f5d447267707c2b60ea38b04510be7dd10d72b00000000000000000000000000000000146a679d7a81aac5952645b2635f24b96393529ab9571ecc1078c4c20a77e59acc4591b9f45df00428250c5e31b1a8e9", "Name": "matter_g2_add_70", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f", "Expected": "0000000000000000000000000000000016790155e57f7103d9e325a1f3a64c0b8a1875365eaa0c01c515538b64bd8265e8392e755a2f7314c37ec09026f13d290000000000000000000000000000000007bfe690fc4ab166b29de35e341e8faec4bc3c2d4ea2d42c9f4166c0d748b92b743ba646c86ff9e570612c75bcd522a9000000000000000000000000000000000c11b9ccf990162b772099fdb4266716b11dcf46c5abd12d03caf222c571e2a9e28cfb47e11db05162967ad4b430930e0000000000000000000000000000000000bafe02785607bae144d9ef5391fef02b9f2fd5dcd436e2506bd40866d8726eb83c223e09c00f3b8895181c6710912f", "Name": "matter_g2_add_71", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a00000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd", "Expected": "000000000000000000000000000000000965966a8a463de1f3bc49d9873668e87f54d95612231458dc8b885681cee8e2835482b4bfc476153c41b206f427cbb400000000000000000000000000000000183639fa14dd74c33e8696496a3ee269160f88e5daca4fdc468724d9b6af8e7d0706867cdb1bcc608029b89b94c531a800000000000000000000000000000000026257fc32efaf241c7712b0a7e9f881763d8fa0711a452d9b71ea25e973bffd88433cba768f1e5b3ea15bdae9cb9428000000000000000000000000000000001527afbb6594dc0f472673606fb8f4797fc855bde4d308ac1acdaa26f19a70f80f2d2bbf3498b53b887b79fd6273231d", "Name": "matter_g2_add_72", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db100000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3", "Expected": "000000000000000000000000000000000018123e82a5572e6b6c62d5db07448838df9db7f7d15dac1adba1fd924892c8bb3c417354e838f706564a9ac282c2ac0000000000000000000000000000000016613fc38997d39b2761aed3485de4d7c273e8392e434185605e968ed942b9d4712cd0d538ed5ed1317870d0cafcae27000000000000000000000000000000000354365566b6e43f8b7f4b94a6343146f35ba3abf61a204e9c976b1ad1a90d4d493494c957def69ff270371c1c8d953100000000000000000000000000000000066adbadf1b69dd16cf19349c82e362be4a3768551599b81a4853ca524a24326e6c9dcc38b5a60ed6fdeb3cc4e7973bc", "Name": "matter_g2_add_73", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d", "Expected": "0000000000000000000000000000000018ba8af47c5cfa552374cb1b25ada1ac785381f2da0501f86c9e7b11cd4417e64095a5c4bdc2480ee10d215ae2296063000000000000000000000000000000000a2e09eff98280f6a9863d8b8faf8871b44650496eac1aaf90fc2b256f88e937101407d722c95fa76846776d4e6bf0dd0000000000000000000000000000000003824f5bf25fa4aec5a9e044703e5564122bec11da155c01ba8ab8344265516c1063983235863d826f68bac455327c65000000000000000000000000000000000ea72f8c6768736800b141b477610e37477d926acaffaa1951a5bfebb042c94c065e984a8812430153d529dbf07ce2bc", "Name": "matter_g2_add_74", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296fe00000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6", "Expected": "0000000000000000000000000000000009f1339cff0b58b00a871add058929ffebdc58cd1bd8a9c2c965c63e1843945b28138008cca8bf7b7cc9afb69a11767100000000000000000000000000000000011f65b337710a4043e1fa58bb41d80d505e2aee434b6978129c80fa1b124db89e61617e89bc0e596507566f4a484e9f0000000000000000000000000000000017560f768496ed583b3522c4a013f8b96073197e5b53e9041db6dc935a266111e21d8c54fa33b7bda944a573f6e1f07d000000000000000000000000000000000168a0742af91f42058e6501e122b6fc50dc966c2f5981372704694544aaa68fba2b6483752fa2464526d5072f84d8dd", "Name": "matter_g2_add_75", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb519000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be", "Expected": "0000000000000000000000000000000005daf8338637bddeba63c788d78faa622e014efb84d3ac1d655d15af06317fe31d1782b2990354bd507632844cc87f2700000000000000000000000000000000185550250e2d9eec798e8b8c483dc37e2a917b304a6036e8ee518a0738d6bf946d99f6b7ee352b1a259aa894d53a8e1300000000000000000000000000000000105a4865d66ed4bc4f51dc52ffcf284615593d573b6beac490c3ee8e08ab83a529c8dd062d762d1d70b9b3290b6e8bd50000000000000000000000000000000014f598e5d0e40090f29aec1ecaccbebbf2a2d6889bbb9439798924db41b70c0cacdcf1e8ff6906f61943e9a8a1ae4fb5", "Name": "matter_g2_add_76", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c", "Expected": "0000000000000000000000000000000006b63929ce97554659ae731d60d11abe858383e39a67007877f68233cba8179777c0dfe511fc730448da3f1c4347f85c0000000000000000000000000000000016d4df414c287b0871c69f9745a9ae68ea3a1ff41ecd17d87623338bb8750bf12be52caa81537bacee06cebb86f894890000000000000000000000000000000007ad72c98e2428b90bead3616f1b31b26e978cd3f9b6b759ad53056098c18932c48ba78d3da112d7a738d7a9ba21d84e0000000000000000000000000000000010dfcfc53d0458296686fd7e0555593e0378d2cb176d456abebfd8322012bc9b408bb180d4237679985457e689131705", "Name": "matter_g2_add_77", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94600000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3", "Expected": "0000000000000000000000000000000009b166f124b5b85875834b5b0c088ab79a2dcf262240b284f57722e78b6eb56a192cd32544c1bb93ef492fe6d7a6216b00000000000000000000000000000000189b9792982b51b13cc3fc1691e0569b6c8d998168d3a3376e63ca60de4b30a84ce8d04fb265bdcf73f158d8e316bdda0000000000000000000000000000000005b99948b635750040b5b59568f0e8bacbfd512db2ae52c5032cd23eac18ad58d83b8f78cd26ae979ce2abeae8e1f3c3000000000000000000000000000000000d0b6561a49c358101b30f714563bfefc72e0febea857b1ce78cfeb9508b0108c2089c9b35cd694bc8c0ea8afc8d047e", "Name": "matter_g2_add_78", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88", "Expected": "000000000000000000000000000000000bbb59d3e6b0b4d86ffc89bbfcf543a5b8ff922f1999a1e06c501a734b19dabd54632132c865c53e5287f69f06942a58000000000000000000000000000000000a3bb94431530879a7fb46b317d4f3d65b5a790739b396c78521a20e1cfad9c44248c9576be11c70970a49a1914ceffd00000000000000000000000000000000198df068ac5d3cfb9bd6896ab64495f4b9933a72872679ac3a46764478f043e9fddf17a7ef85fb72a8dc1a722804198400000000000000000000000000000000155c1a9db0c90634a6d214e996b13252bd4db3a4ab84ca7456ac3e7899e6fa096904a90f1150026307a1cac8de00c6df", "Name": "matter_g2_add_79", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d70000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff148", "Expected": "0000000000000000000000000000000010684ea0303f0e76b60eb96c470e1f0466f1f2b073bbedc1a0c0df1d2f6c66d77cb90ef9bfa4fef6a6a9eff8f5c66f9b0000000000000000000000000000000010e7ced79bbf01ae9f65d26894c73a905514296f19561ab4d00c0cde31737d01e7b4e8b8e6050054a7a17e8acb74d49d00000000000000000000000000000000174f771a98e262825ff2db7571f5f5475007d2f73a2c265f24e2929671bd173596b8b163abd46b868a644dd464dcc7cc0000000000000000000000000000000001cbffc9bb3195672ea2d998b169f853d3d4b4e147379329b1bbe69ce76d08ad78f87fdd876af227a050c31884fda084", "Name": "matter_g2_add_80", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d527500000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f", "Expected": "000000000000000000000000000000000fa306f630d06c801e0203525c75fd6065bd12bcb3c4d45c7e02b597f85a53fae1e65a969feedca75068433547e4632d0000000000000000000000000000000004b1bdbc29f19f6484ea4648c70eaa47cf5bb07bbc255bb72dcf68a7b661de433dafb682d51321369cd3372288b2b9c400000000000000000000000000000000136671654b24e1ff2e8223ba747ded51f5c826b6e2c0f02e2865fc35d15045f41952835800406f60f966d1f241914726000000000000000000000000000000001007b5e8ed7f0d25091dd959d89732e9df02561a829ce013f5ad1adb8d6d828a8ce87b52d39fda1b5dc2b581ca420e22", "Name": "matter_g2_add_81", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae452300000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf", "Expected": "000000000000000000000000000000000fb74d9ad4de11df81c48d10b9a14fde8353ac47dc902b4420be4c086332be480552e26fc42b7c0f30e34f740bf9a4e6000000000000000000000000000000000612a7e23bbb525f91084b122dd4cfce4074c9e6eedaa7cddb58a14e0b1eccc2f08296baea3eb3e003e576fab7c557ea0000000000000000000000000000000016dea145df47a2c5262893c273c6158ee14d44c3740981c161624a6e9ebb982a52c1eab6160c3849f2bf3821d953f4c3000000000000000000000000000000000e920661772b8b737f1a663badead0e89aec4cbb86e6dece5d4db8a673e75b844bfe81662dff671658cb8386c16a7f3c", "Name": "matter_g2_add_82", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627c0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77", "Expected": "0000000000000000000000000000000015930559743b21acaf390b557fb960d3021f3cde80630d8867a063d445f860c8a01037057de1929be16d879416b12a6c000000000000000000000000000000000c6074c54c83f717700f61c5b6bfc641502121b59b196a1f8c5f2945e5db1bca0d7a94fdae96bfeeb6204c8c3f4d048a000000000000000000000000000000000b3a78454479c0990e4c65e4f831606c7eeeaef0faa86596350c9e43e84ae959a0f32c8d03d1f631d9b2ecd046efcda6000000000000000000000000000000000aff797d7572f20b06bac75bcf8cef879df11599ba7f8b86eaa28692d1239cff22841b66e28662309e81a6a599e79ddb", "Name": "matter_g2_add_83", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ad0000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee41", "Expected": "000000000000000000000000000000000351bad2f1fd9adc84280515c2d9e538b69dd63ac93514987ecace75d6bc4585199b742eae0d357d587924333721a1d90000000000000000000000000000000003e495b544aaf19a6415d5558170b8686968dc922367c5c8c212fa1f2785535fe0e71498b98b9a39c8b1f2384956170a000000000000000000000000000000000c7040f34872eea5f98ddc78737dd01fdafe75081cf66ad5c7c900674fa90257105b4f4fc59103dd5b92727a072ae462000000000000000000000000000000001312bdd27ef038d4a89b12c86281975bb34b435d42642fe0732709baf55e9a0ecc0ede8a4775a33e880aa2e1fa7b7ed3", "Name": "matter_g2_add_84", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa800000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f", "Expected": "000000000000000000000000000000000d521781f60198341d116fa5cd9e2b5c2fe51f91f6c8318f351df007c96086f6c3baa5cd2b9b4f442305695dd9b01ac70000000000000000000000000000000013454fc15b1d182bc98d75947547b3bbebef6d5e2d38ed7c67d76eee8da89ea2be19280af4760282fa7576412d5f2107000000000000000000000000000000000d866015c84de74c24dde252542d0d3823f435203c71cda140af235d88f3f4b736e9d75ec32c09ab73bf74083e76866e00000000000000000000000000000000147dfb5f53a9cc61b6788c911dd8649c09cfffbbba368c1872a31cfe3bd6d6427d7b00163d39f8e0b81fc4c40dc60b87", "Name": "matter_g2_add_85", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bf000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c", "Expected": "00000000000000000000000000000000059fffdf2d79b4a297f6912e3035cf0b07db9372f3485150e00d60bbe2e7d86f45b5c2ef062dd92c7e8b1e2be5e9bd140000000000000000000000000000000016acdc57e7231b020268373ddc8b8a7318ead02a8c7181165ab045208409373eaf57ace9a6db1fdedcaa477c7a0ff6f40000000000000000000000000000000012fe630f7de8ef5a129b99faff2de080849bf3b59aae1af042c29b1cc49c8825a4f28c4ccffedc6d568f306416b5bb90000000000000000000000000000000000d86ab3e49ffdc7c2485ecbd00256af83e7f3f064d212ea91245d86ca75e3c7f28b42fa9496a5ccc0514cffc60c9fb83", "Name": "matter_g2_add_86", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e84000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb1", "Expected": "0000000000000000000000000000000012ba9a8fcb69d15eff147f663a5d7927b6f3f79330eb9ee625e0100b146597554debfcf97a3afb51387a73554522ed0e000000000000000000000000000000000a63a990d6454d4db6d58642eb3489f79e517fbbcabc06f2eaa00c4b6f9a07aae97991f169d90af3461b7a62db276e00000000000000000000000000000000000a95203a1628a6ae2551df832f7ab94ffcdbf985e4c9744e244214c8e8b8079af05a9321d1e49b7240c2bdeeb7b783280000000000000000000000000000000001ec747203be73526d3f943e0af814dbede34020144bf247eef9a6ac2cfc83ef63f18a73d3baae18bfd8d5e83d0519de", "Name": "matter_g2_add_87", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbe0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc", "Expected": "000000000000000000000000000000000eefda9046a950c232c6244a79c33e7135d0896bc57839a4f971030220e3ca8196cd0ad75269f3cb5586a384dcd17f9f00000000000000000000000000000000195ce623693996f5ce9e45b4e285adb969e6771e6b0701fb5c95715523c8cb93aa641583821a3b360ad6f4ea1aedcc9f000000000000000000000000000000001553a4d0f965d26fbaba56294591935bed63c84abfedbb9d5c61f3d43484ea71600935fe3c8b6b137d7a9074d907e86c000000000000000000000000000000001673c42c88e4acf8ca38680694b80458f988403a4bd667468506452303000d13649c4f610b738a94ff88b65053731c08", "Name": "matter_g2_add_88", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba800000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed", "Expected": "0000000000000000000000000000000007145ce58cbe48405392edda6022ba8942df055ab582ac402e7c9a0a951cc6a38cd147903f042273e736f30849996cd10000000000000000000000000000000011b457ba464ce818a34a11afc3c0007908091fb528836691e6eccaa9a23ea90cdc746769c4b7ec73efb1f2878413c3b70000000000000000000000000000000019ca519fa6a91cb7e83704daa9b92da9bb70b003f9e9bfe9f323430bfec9b19b01005aa9fcd19d5b1ac59dbdab0c0d84000000000000000000000000000000000ae356f5e5de0d7662bab8d947662bf87d792a3438ed477cf6ed4b27c935b1dd76a5aac446d4dc36db544d4aea40b505", "Name": "matter_g2_add_89", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591c0000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c5", "Expected": "00000000000000000000000000000000135c42c10ef97279e3d152b18cbb8dac11ca8c805dd1d80818851424f592e7522589ec7df6748b5c72d0808399e629cc00000000000000000000000000000000083ddf3843434937e05ba9e101096371fd8fb34f226bcd517716200003ab9855f7aea94980c57a6b933494cc57afc562000000000000000000000000000000000be9215d936a49538442189c9a0bd3be07d4b0b1d14aa45afcdebc1fde17d33b66f7dc36da1ea5411549577f5a1967ff00000000000000000000000000000000176a4a4962c4af75a712e5093ec2cd5cb5c0433aa0657809dffbc0bc02b1ce303ac084f39a5721d482d41412d391317c", "Name": "matter_g2_add_90", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff200000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de", "Expected": "000000000000000000000000000000000bcd916c5888735aa593466e6ab908a05af528f34a7901fb60feb1f51737c73612436c192dfdecf927019724ab2a9b7900000000000000000000000000000000187d4ccf6c22381d0c40c9d7820ff8efe6298c6dad0caa25402412661737cb482dba2719c3a50ec08cd022230952dfc600000000000000000000000000000000164510d4f2cf1e14e039561f1baf82bea678d0065e378d5bb7443fa782e6ab2a3bf7e4ea125d6415a8277c60f5346468000000000000000000000000000000000281f2e28b73eca4db9966456b75de9ae3830c74ac928fc4c36b4aeaaffd47ee587d948f68056df2826ca2775415a53a", "Name": "matter_g2_add_91", + "Gas": 4500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5400000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157", "Expected": "000000000000000000000000000000000cceccfefe04f94e0b67b29b5df8007930665006cb5a59504c3656b8c0bfb52324cdf50fa2722ce15b0ded0efa7fc85f000000000000000000000000000000000cdf34c330c0125f524f0711197639f8aca3e7c435f8c5ea30b78e9622c4bb72a7e584980cb4c3c6ecdd0689daf36b6a0000000000000000000000000000000004b1505d7fb65f6c06ef23aef85b16f3d991218187c5782fb635ba805da463cec9cfdd670c53d680c603adb827a4460a000000000000000000000000000000001104af6bef6482ae64b3b6b39664ec06c39bc18fa91b7b4e5bfcd444c827bab30ef548b28ef5487582d88fbc6d7983cd", "Name": "matter_g2_add_92", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a70000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0", "Expected": "000000000000000000000000000000000e1ef3003fe3181f690224cbc7008856e1251430ce3cff56a1965c89a892604398f5101d1bec7ff1590b0cc3d23b854600000000000000000000000000000000185b4d4b5fd8313c31542bd1bac034046ddc705b41a034a00570181503a6ea4c2d808bba0478900064270fadf3d655920000000000000000000000000000000005bed63ab9898b89f92027c04ba256569e6285c851753e12760129c98899bcbab34b62172906a1ea4cb056d4d0a5717c000000000000000000000000000000000961129a3e212c7412018d7407d7ad16412feba8c138f4f6ba69daa1a25c6b23f3466bfde6f5f0d09ab67248a2abdc68", "Name": "matter_g2_add_93", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa2000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc01204", "Expected": "0000000000000000000000000000000001504c47ab0c410b32d5f1fe3d3996dbf1b21c5ef5aa3a2862a9d561b419f818f0b32b8e931c65fffc393ce7beec70ee000000000000000000000000000000000217e9fddd2551a171a13183ae3aba6bc5ce99e8f3587b92a7cffc738b478d8293b8c71989cabf9a55c5f5077249345d0000000000000000000000000000000003874de865d93650a95af4e153fe557c45bfdc4837bd6e209b8f05ad12b8fdee6432675cd92fd739b7e98e56e7ef16b60000000000000000000000000000000011303c0c7ec1f434cdf07c110da5f0bcd85935c3a0ce9fdf5546ca61edbc2d478562dbd9aa45a5f8d96e033feac2fdd6", "Name": "matter_g2_add_94", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99d000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6", "Expected": "00000000000000000000000000000000101ed22b16502de0d83303134a97db17ce956faedf47256a9ac86004bcd3ed112a71328a58f98a85977a7f22eb1352c3000000000000000000000000000000000e841a88d10493f301af54c5fe07a31ef90de106a6c87d5631b6967fd017f561a56176a5f3544dbb34b9f94040ebd2770000000000000000000000000000000001bde3c0076f26973651cedd3da97c7eda24451bda856026d1e22d3b65c66a3fcbfbf506b4b664b5fc06fca2d712d8a8000000000000000000000000000000000ce553ee3b7d5389798cdc5af8569aaf477b5b74ca1138454dc61badcf3ecf5e0ee8457e374b5735d0b8408b04fdbcdd", "Name": "matter_g2_add_95", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d00000000000000000000000000000000030dfbb89bbe5c14a7a55e68edc4fc38eaee9fb539a6b2f941264c7dc295da5712b0af0f2bbcdb74f785dc9ba038b0aa00000000000000000000000000000000132b4e02fda605a69251a4a6289c47536f9735dd90908ed1fb619b3ab808b3a1f1ca3fcc8f4b35c9864ae311c15747f80000000000000000000000000000000005858ece0bb09e55e012450551025ad2a6d93a15d29619433742851a62d987e7f8bfa6c6faed76493a27060ef5f51805000000000000000000000000000000000dd6b393e6d1b8d546e3f5ce69bc1737399e6ababc628f25734030e10d82b5e9370edfb5da15566d80e23d2fbf8aad5f", "Expected": "00000000000000000000000000000000182f90f5d3ce3f5ff2d91430376144583247def83b3e83524094d57c0f1be98b1c4946964deccc25fc303d6450edfbac000000000000000000000000000000001844806f711735c5ca18ca48e559a9e327b87b91d22a5ef161da7874668130e21a9499728fbc2c88366bdb59f8ced0cf000000000000000000000000000000000815e7cff14b4ceaf26d1cda5c267f432fad294b6baa239b65d886ffb039321f9e24330ae738a35298c6d1ec1ce1c95f000000000000000000000000000000001188a4a2f0920ddeccde1a47a0636aa7c404fd77fb9c828e4fdb5406df80ee6c258c2d4a89dae5e2a2b05210df9100d7", "Name": "matter_g2_add_96", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e740000000000000000000000000000000017032b16be8656cf23bfe0abc8c9e6aade223fa9bea6fe25f95a025da79cea6adf38536eae3859b25ad1af1756b639cd0000000000000000000000000000000010975ed27cefbb43bafad0fd14c87ada8e84525e1d199fdf1e77caa0b718214b33e547a42a040ee3bfd51621a20d22fd00000000000000000000000000000000133d29aa41f92de37523d281eebfe91103f017e5fb390f6bad9a2a4419fa4702bfa04847edbca1da96eb1ad563a92c8a00000000000000000000000000000000014af850de7e800ebee4be1a33c7e3b30aa94106db7defa148568ca3c8d82edc97ab5769ac40162d3728687cdac201a5", "Expected": "000000000000000000000000000000000cf42f2ccff2e0cdda7e5f1d7652680650b4afa523c8f9a554ec18b905c837a189fff73982cbccf903ea492ea902b87f000000000000000000000000000000000d38219770f669557cdb623f2476b5f3f7478422b016123bf86a17bf75848548d1a1ce96a292637b8d52481321d80fbe00000000000000000000000000000000170d8722b824e3291b570ba8e4f9279c1dccdefb95cb5b7a94d27ad8a93513737f12d18ef3153c4e12b530bc457af34100000000000000000000000000000000021aee9e5f578328caee3177a4e08303c3b5533e288dcb75f94992db3520a6da16f4201e60367240b29c48d175942cef", "Name": "matter_g2_add_97", + "Gas": 4500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d55300000000000000000000000000000000185aefe71f24281e5b03dd41e6d6d45fbc8975beb175118de7568bff0a9ccf917e9df97dc26bca16e8da06b0e9a8e7bb000000000000000000000000000000000015b326d401b827fdf556e4a24a3dd6c8036b1c849751b5ae3c3728cad88f931b06e3a345523a723481193f7afeb67800000000000000000000000000000000054ca16b4c87293002c31e64ad303e8f040e11de8b45c5fb9aca9dbec59b29dfda8532a8ef5ae6a92ac8ea90ee4303e0000000000000000000000000000000000b65a233a7731366cf24c801724265215a8626b1290d86c60bf1e74b021b0b44d7d6552f936fac7b5e60cf1feaa1d82f", "Expected": "0000000000000000000000000000000010d1b2f595166929347e06c1debefead06334f554dc31f320cb844abdb1810b5f7c4b933ff8072dc03d303f4a6d0d09b0000000000000000000000000000000013ab41dfca0a7cb0c58c2c19e02f675a94d9e73312cfe2999dbac34e6a80bff9472506b48690f24ad3171ad495f445420000000000000000000000000000000015bfd0db53fd4da538caa3aee7a90a669cb84460365696ee79b190d09a6d4c3f08965de7fff4efeae435db52b97d213b000000000000000000000000000000000182ffc4304b911b47b092ab678edd63ed5f5e8a9069daf9247f3bf9c0dd149cc9992728a13b0a236fc9b37714b35882", "Name": "matter_g2_add_98", + "Gas": 4500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db400000000000000000000000000000000085dd8bfc00ba517dc8d7ddb49d711d35bd36f9fe3843689019e779624a032d2f023533b8184b73042d1a1953d2885e50000000000000000000000000000000009ba8d5d36e6efe02097a3206bbed68529f0cb9875ab81deafd886d9243bfec8b403d2abe713a2ec929b93305dd2da220000000000000000000000000000000007f8f90ebb2771136a92023901ca85e87fb7c8b1a40f88ae564a124bdd0ff0bc27ea98612a817e2c871fb4bcea3bb06600000000000000000000000000000000152de417d02f1d14e5899201db8fd5db8ecb40ea8d415dcdedce8ac70c28d851db68e9aef94506a50ec28145547a2d68", "Expected": "0000000000000000000000000000000017555399f979745302f08210de5311a6401b6b181100b3bc6b6d450f0f62079d2f02d7badcb164f50dfc46a975cbd6720000000000000000000000000000000014aea86c06e4c1fbf0711a8cfced2544c7624abc7ae7906cd992bdf575a702540c45c2117e221446ba09960cbc9048ac0000000000000000000000000000000002fac56960c4989a84e02ce36e8970c2e847ee45579d31ca77f042bf96505af574af822da084ae64b22ff876610ba9a5000000000000000000000000000000000a481cfea2aef8975c80a297ce5a185dacd25649d41f8466d3c63d786e3c264a8e4ccab5ef6b80ab1260e86ab6d5b3f3", "Name": "matter_g2_add_99", + "Gas": 4500, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG2Mul.json b/core/vm/testdata/precompiles/blsG2Mul.json index 15815dfab0..886b0c6adf 100644 --- a/core/vm/testdata/precompiles/blsG2Mul.json +++ b/core/vm/testdata/precompiles/blsG2Mul.json @@ -3,624 +3,728 @@ "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g2mul_(0*g2=inf)", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g2mul_(x*inf=inf)", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_g2mul_(1*g2=g2)", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000011", "Expected": "000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d536944", "Name": "bls_g2mul_(17*g2)", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e", "Expected": "0000000000000000000000000000000006334ba1e361fd94bbd98f44b75ae9ec00ecb4d3467b5528870b1a1fa9a7d04449f12af90bd4c7a1e3f29e717d6d19d3000000000000000000000000000000000bf4cc1626393956915845ea7ca43d30a59c7196fbe309f2d5ee6de7e40c191d29821dd6aae46abecf634b904de8f7490000000000000000000000000000000014aeb09e252cc74610ab956057d4ac5af95cbea8a6baba9e5062643dc037d6841044cb38b22d7dfb978fe0b58f94cc3a0000000000000000000000000000000000fdcd73452fc1ced1c06e6271410a48dea05afbe889a692905e1baab8d72418c62531aab8b74842b51016f0a9cbb93d", "Name": "matter_g2_mul_0", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d", "Expected": "0000000000000000000000000000000010e70bef8eb893377e7ff92168d7acef11c9efab990fbded728b173b94e1d99e471a8357f16625d353287086543551850000000000000000000000000000000014043c1f00221c439e5febd12724a9224bccf0389914461644daf329208e869b1bf149880dccebccd440b1748d15e944000000000000000000000000000000000f7dee1e7d122e410b29a9eb011ee700c2f230cf8f611e196ec66e153c1fc331175532a8f9b060b573bddaa705430c2e000000000000000000000000000000000e1f659470eab7c0741bc8777ac9fc8dcd11a6f1b30ffb4265e96b879e795a4dbf851d1149429dcab95464e89f334627", "Name": "matter_g2_mul_1", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1", "Expected": "00000000000000000000000000000000119a5147fe9ddca7123f721b5662c1a44b0964c37a214cdf3a4fd34166e3b25210344e65220c38ec84d0e3b5ccc7e46d000000000000000000000000000000001642dad5dacf4295b871fe9b2787f0861f158807b2b6c01c2dce12ab053c9472bd3cb98de5dc33f40053ff45ce5c9af40000000000000000000000000000000005bb5761602b6639f2ecaf79f2d1f853fbdf75f4b3852b90808b858993a83f8a0da8a2ce7072aa91e3b6b3ffd0b3d1e20000000000000000000000000000000000a75143b9551d4ae41fb8bd71fdba7826b994c65904d9189a5ac5130a59cbb9d8dee0e016735565148fc49823d3969e", "Name": "matter_g2_mul_2", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a", "Expected": "0000000000000000000000000000000017ebc9446f8c8e17dfeddab9188d0c808565da29c0bdbbc4138a44ca3196c4564853be28286b66660cda36832d6940010000000000000000000000000000000007f29a9583b4ae83d3913dcd72590a3f20f39eb5a6d36663c1ef433058e76550085b9c01bf797d98d0eef45cc22ff8c50000000000000000000000000000000016eeaeb123b12d1913ff1e50f974228c79f2b995609d2e3835c8e1d68773b0cd484df57b86111cdb75de1e19eaf062e500000000000000000000000000000000002f5688c1286aed42309896bd65d1826dc64dda615238fa9043669806968b8e0e1e3e77ef192b7df540aaf0ed282a9a", "Name": "matter_g2_mul_3", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89", "Expected": "00000000000000000000000000000000042d0c1941ae0ed5e8787437ad5e2753bba02185317848e8ec2e425ac954e0efb1bca534725adfe87e8507851ee337af0000000000000000000000000000000002db55ae8126cbe86327aab880381a81205e33a351d172c883b9cc184799866a8db5a6b4321496e05d3ef62d00416d9a0000000000000000000000000000000012c45444403dd62d7be3e7658dd85909204751dd7d085f6edd38c0aa9185d3c32407d8c95bba371b380f788d0dc48e0900000000000000000000000000000000111421c6dd0db595ab731adfb4bc76c84a61197cb023b6f17e7176c443f20a4b6f8cd0a00cfa61e831ed20b3c6a84d98", "Name": "matter_g2_mul_4", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944", "Expected": "000000000000000000000000000000000ccdb2a0b670f199a9b61198e6a2ce2117075733e6a1568c53ca493dc3674c6ae85be2491d2ed983f52e2c7040824afc0000000000000000000000000000000004f52450d7e041c561c00200d5b142b32f2df2e2156e4f6c15d6c00e185e135037a1ed6be15e2ed920daa00e2f9bc8da000000000000000000000000000000000f39c38c18f03ce6baf1d016cf32d7387269940280f2e8d21db4da33dbd2d24ebb93ae3dff9f79b015eee25813d677c700000000000000000000000000000000189df61f7f1025fa6fdd0a4708ff1d53db7d414019c4828de2520af3d36776062350061c2261e46e746a6475fdeccb2b", "Name": "matter_g2_mul_5", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1", "Expected": "000000000000000000000000000000001388a59c57ec8ca5e68b99631abdafca1b71352ac35003a55bbc415b48b8171857adda31123ec86a6ed9e1060d56aa67000000000000000000000000000000001471913b1ab5bcf9336665d3d44232b4e58da70285b7b8eb1dfd7c54442afb28c339f56e6389f89b84db0879e1ee058300000000000000000000000000000000022101b4de40b7180ea17bb36bad0a668a8def3e7361a96fbfabcfc4cdbe6f607ee4ee80d0eb2418b848ad056520092900000000000000000000000000000000103cda694792af5a51e04b6422600a0ea6f50808ca54423cd4f59dfba633daa5afea49c85b900f52e182610efb62fe7d", "Name": "matter_g2_mul_6", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c", "Expected": "000000000000000000000000000000000cf5cb957a752ce9187940f63b13080790348814debf84b91e74fd6e822c2735941d61d50d492439475bb3ea7aa849ec00000000000000000000000000000000012e546ff33dee9875510a68301f46d89e6175f5cd9a6e179fb8599a580e9478fb8d92038982551dd29041d8185c7474000000000000000000000000000000000d52fb57bf2996dbbacdbcb4088df38e77e25598b91bcd5e41eaa27b1398eac150586b142f068d5b498e0ce458d3e8950000000000000000000000000000000012295e1d1039abe7a5fea51a04a34e9e8d44a0f24b8c032680703c119d54274d3bc2e548854021ab027b693e43964314", "Name": "matter_g2_mul_7", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108", "Expected": "0000000000000000000000000000000008e4c57309339400ac9b6b5df16972c272d47cf69ba7baf89afa4f4e72703999c5885253cc35686f6c8d277399da2a390000000000000000000000000000000018ad4e1f105f16b0dbb4eb089c51e709c25e407e54b64346224b1abbe15d62fabb231e36a69eb05a9ba7860f772634200000000000000000000000000000000019994d20a7ecc0f234ccb6b1793fa7d1ece64b3e157c579fb05a8c6cfcdd6f5456ac1f4c1beadb69206988ab543bb8bb000000000000000000000000000000000d435e74bed382442ab83ec90dffb91336137932524bfcf9753fa5ddfe038d0b98a045c8ec9deb53172e5662d3fd67e6", "Name": "matter_g2_mul_8", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672", "Expected": "000000000000000000000000000000001425890b6c46c5a07a79127de4ddbb751227dca4481ab7c2f601bf22b8f6a149767c73bfbf57ee399c0f2d0b12852a0a0000000000000000000000000000000012cce15f53fdfffb5f71de3567b0c0adea65b9321c85677c574787f7048c1bb5e2dc985b65fbc48115aa129e6000fe4100000000000000000000000000000000041398497f975289fb9fc6ffe671a19fdcd3753c82ffd3b2084574107bf7fadc8de462507f4484c32df39967c3751a480000000000000000000000000000000007514a7f246006e714d4a8cbb4e89d81b951b5c41a05bcf35f61283e888074fb3686fb6ecc1a66e491ea1e1ce0738102", "Name": "matter_g2_mul_9", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea", "Expected": "000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0", "Name": "matter_g2_mul_10", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76", "Expected": "00000000000000000000000000000000048ea2c854a0df7b10a2147db6eabcb16eba340644f737fc99663d1ef26d8ed688c2baaa7d7699c5f540d7605eb48485000000000000000000000000000000000c959efb835d48d3e7a8ce643764f27c365f6248a88e39092e3a6498f04ed851c55b796dacd62ae73d7edf23aa45fefc00000000000000000000000000000000114337b8caa68cea6f22a25c0ce3b247cadae24c63fb02c6a98a728b54f97b12b1473c8e23f55338326b9575a637bb2e00000000000000000000000000000000033167b0668ec650581815cefab61d13661f4cbc6e01711af0aefb699e1979b551d0031c603ee5f6dd4f716ea7aa4a6e", "Name": "matter_g2_mul_11", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c", "Expected": "00000000000000000000000000000000142f6b71471f3665ee6269cf598fc3587a62523f9753eec48a2461a2e313e376828cf6d1a9ffc9e64353c8a668718736000000000000000000000000000000000153647cc4a5aeb8ea52f845c415651e167ace9f331c1d73eccbbe20a4014f9e1158c281495206de4b841839438a595500000000000000000000000000000000151d07c3f83217e63b332a6c47e91ef2418e9c658353f8b644f23266f5fbc727562f0935b4d892db947cfbd0757ed61500000000000000000000000000000000035bce4bd2d8261e21476c325cb68e581f20513eb5e0e6a0ddbfd4ac4674bc323590b6f52d0cd50010c13642e7e03daa", "Name": "matter_g2_mul_12", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a", "Expected": "0000000000000000000000000000000014e83f87e7f66d8ed880ca46a76a5d3bbbacf2dafe1ee055f04af568738f4c6ddf2a93e1810b616da6f64f25c35a7b5a0000000000000000000000000000000003d14447254b61168d36f92710f95f7100cc8f278b0bc9528da763a18a5386b3f5b83b96f4dc426e4b0fbe755bc986790000000000000000000000000000000017f1a79ed64abfe5e960fda02cf3330e6ef5612c1b8639386959f86c970adb797bf077a468273d37996a65685f75ac30000000000000000000000000000000000d973499a7bf7132541c0976bf2e9bb26a2b6cfa5bda720352fa7a180a6b8fe95befcc13de5a2efe58be934cf7d8e664", "Name": "matter_g2_mul_13", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054", "Expected": "0000000000000000000000000000000018bb69dd6db0beb468242265c382de5ac342d465b5f72d4e5a24c67a48272d9a1f3af28e0bd3712e16a854c5d91c616b00000000000000000000000000000000072fbcc86b7dee9c2dc177dbabdbbbddb630c98ac3bf3737fd22f99e2b2b690175d9c5aa4b577f78c545dc6a5d2d03c900000000000000000000000000000000161c4218143ab1f0387f19bccdcd08f9caeb2d1331ca890741799ff1b40533076b6a96a910714176c770b25d2c17715300000000000000000000000000000000063098cd9d1eeb899724b40a2d10ac951ba0277db09aad639957f58541dd391fffadc5d97833bb9666b054e12debfa92", "Name": "matter_g2_mul_14", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", "Expected": "000000000000000000000000000000000e43672f1bc25e7e0e64a3fd26cb246bdbd6fb5c9084afdc87c888634916e6a6cc9a351cc67a6ac77ab8e132ed6cbee3000000000000000000000000000000000dee9612527c8ee9c574a4c51f5d3504ccf1d5781b59c78ea15294332c6acfdcc7bc68853e70f1f72524c930e4c3d2eb0000000000000000000000000000000017eba629eb14a0636926275f1c2109318ce8818d8171c69fd371751b6de47bda5b00a0b0e3765d05bab7b8dea9add90900000000000000000000000000000000052f0a4cd9b91695e1e58ead1da1480fef08cecef63896aa51ab16da373b99b3b91767a374645ac5932d9c7fd21d4636", "Name": "matter_g2_mul_15", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf", "Expected": "0000000000000000000000000000000019b7ea673dad96c8352870136ea262c9ed105550cb403eb1e64ad598b2145fe1b95e5d61f1b5a6ebec47568c67b68086000000000000000000000000000000000f06ff9bcf2ba284e705b12ef2311f1a9b867ed742ee0737567b5c878547b18394b82c2bb97e16586515728245692cef0000000000000000000000000000000019dfd2d8fc4f2c989c7e1016e147f336174c84d380bab992bf1adbffe96d93d4d2d1d1dacdba3adfaf283b184478229800000000000000000000000000000000068d230422006004cd88ab0dd46a84af3905c7a1d329446cc23c1c5adb401a86a9fa76aaf577f77c2678cd8de8685ed4", "Name": "matter_g2_mul_16", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da", "Expected": "0000000000000000000000000000000015ffdd83355978ebfc386e13987effac0137ec628fff1667ede29cfcbd05e31cf8323959dd0247c20cf28978dc242c790000000000000000000000000000000016b1f810da2ae3c2ffbb6b83c47ef03eb0f298ff4c304ab0dd7b97207949d62858458d789c86c0cd474c34fa720ad3b70000000000000000000000000000000002a2e1463d5e795e6a25998a848b079363efc7d0337c3803385f4f17f11726b04108adfd87a811d709cbb6750c969526000000000000000000000000000000000289a3f472799c06a84bb1f377a36bad910220e1017884545159fe1b2505e8e7473882fcf324ba0d9125495bcbbc7226", "Name": "matter_g2_mul_17", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833", "Expected": "000000000000000000000000000000000b02ddcfbf391a2d6953261c786945093b09377352473a86cfac6456a811233809434b566b9301eea3105eb86922efcc0000000000000000000000000000000015430deba91113b841303120f0738012d77207e9408474998df5e68d0d61f1a64afb947ff93116ae766ca5325046e263000000000000000000000000000000000ab7094055919f6f707b458cda552f25104d95e4ec8d020ea4c17ac1d7efef5c4c3a769120718f1d5171eb8630a3018200000000000000000000000000000000161e7209f8c98e511a698fbf01735798cb632ae1afe00870654ffa0ba93a549edf4b97d60f03974ab0964cd39298401f", "Name": "matter_g2_mul_18", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f", "Expected": "0000000000000000000000000000000006cb218607a1f66ce361c89fd20edc3f00421611adc9aa52ec35d45e023174962c863f740ac36c984c2b466cfc4827a900000000000000000000000000000000152b22d46e9660da8b1be4c5b14da613731e750ff7eebaf879f7074bf3c33e1528a2c8479e0178707e3855b49f85f045000000000000000000000000000000000c928cf78cee2c8b9da8215d33d189c5636df1e8e9bdaf143aba7ed40f29490ca2328b4a20cfc56f62e4ce49d9e77f14000000000000000000000000000000001574b7a9c3931933160ad4eb17400b6297210db47bca034bc1b5d17a0cb8c41834636b9123e625e5eb0b01738cd6b9af", "Name": "matter_g2_mul_19", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc", "Expected": "0000000000000000000000000000000003e17452a80996203fdc4037db072c452f9eb2dae689c77c88b299d7ba266d111ab2b9c4b24149968d72cd143a34fc4e0000000000000000000000000000000014a057d7a50c9b0f34712ff8008770080bfa671650fef43c82726257da180dfb9672b266d4c54d65fdc677d917e6c5b80000000000000000000000000000000013b452c980bfc4a484637b578be100753aee9dda9487d5ee5c017c689dda838fc673804369328192d780d60a9a3de0f700000000000000000000000000000000103aa86d1807de242a6d4fa4a49be6c91cd757df5808501acfca44940733c6a524b851ac962b99a9be41bfc8d6254478", "Name": "matter_g2_mul_20", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52", "Expected": "0000000000000000000000000000000007c616472f9ac60f749979c6f870b587425d514395ed07558ed287fccabc77f0c90872f3885d0780bcdfffedd124eb3d0000000000000000000000000000000019531e9c25e84a2a968a85d9f1ab61a372ebc59ba5bb7a2bbb3c0d6e4c9d04061b28fdc719735e97ccd5f7243a58cdc70000000000000000000000000000000007772d3cff12bbee916a6569edce0c6dbc2bd8a794919a4dd7bc37024c8273245210511b8f6da551fe626b7b840833f300000000000000000000000000000000186a3e858a83a7ea1bfdaac65c2df1076059aaa193961559792373886c68acd2f9fca61b166a0ee55084a6ea122ec3e8", "Name": "matter_g2_mul_21", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1", "Expected": "0000000000000000000000000000000008adebaa95d10b9fc0f1a1f0d52dd6741517d2ba23e3f9e7a9221039684ae226ea602dbb50df0efd44b2b5bf7495c0b50000000000000000000000000000000008e276e78ead2473602d37cb9f2f589f9c60514a1fc5c215acf487bf57c935467d29945d3d671b41a8e47c9495dbf5c9000000000000000000000000000000000fab06240cb8cbe9afcc4ebebde50c2881e4bc4d4f2ed09a1065e3620e6344fb3c5f3019250ca4edaeae4902abb7400d0000000000000000000000000000000003fa6c48ead374be1dd45c8417ca8234c15ddefc5039151e6cd7fb27f866e134cef2f59ac9b2ec1b26896eaec9213549", "Name": "matter_g2_mul_22", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9", "Expected": "000000000000000000000000000000001412bdb48546014adf3c4eac4dbe79ba700f90c8030b063828fb01be5977bd73107533a4e8030c8d9cbdde9bcf10649a00000000000000000000000000000000126d3e1006abfeddd810cb1e12c898cf5f543e414438e600ce4c94cd8dbd1e17c0f3b9831add397feda74362eeace6fb0000000000000000000000000000000005b3159638afa34f219513cbcbc51567b16fd5598b85e6ae0d232021133cec25a6269250df2ab7b5ace726e9e2fbf0b0000000000000000000000000000000000c35bfdd1c10e903da6d41e9afbe65b0cd66addd7893fde41dfda8e543a93938cdeab52cc9bbdbe61f93d651bd1c923d", "Name": "matter_g2_mul_23", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b", "Expected": "000000000000000000000000000000000bcc781f144bc148687875789fd8c54dd820170984b6f8ae75855f7e45619c1d2ff85c330b7743e447b5fc831dce9277000000000000000000000000000000001409aaf3c94c9a6b5123c82a7f311af7c2f60e9b197d49fb5b010f84faff972151b383a83c106de43454f8097005f6c800000000000000000000000000000000064a91226da8b9cb587030f1f4afb0d422a51e4d55212f26c621abc06fc0c57a473a9be75518a5f4f9a7f8d4aaba69830000000000000000000000000000000002cf239343bb77865ceabfcc1fe34cc9be4a1ebc3a70f16f8b7cb84eed5843524f95673b01466d6cbb0d8d9dc00793e6", "Name": "matter_g2_mul_24", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6", "Expected": "0000000000000000000000000000000006bbdabfe104b62d22e78bc8f3446a86cd5f10c4c5a54501140768b55a7e6940b9952c9a90a14d8fdc7c04600195cd6500000000000000000000000000000000172e718c926cd393bf303984518432693c304a2758174dabba303ff4c0289b5bf5376b61e8821abab322d53e88f71d480000000000000000000000000000000000a2f84fbdb5b05107a0a340e81b56ddf6d03c23848448f841dc44f07cbf8a575289cf6d53986f581fddb0f2d07e38d70000000000000000000000000000000005cbc10f143a9a1fe23f670a4c47d385f5c7069d8c46580322d6939122b2d39d185d6a8c2e51e88a1d40fd2e82d08b8f", "Name": "matter_g2_mul_25", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36", "Expected": "0000000000000000000000000000000011769e191fe258ffd1922295a9fe877ad5a52fde6e343730f8f5ec6cdcd584f8ed1dbe0f55b5dd81f5f78b7437f02abd000000000000000000000000000000001253689089e9192d10a45342214425de36740c120e49f596d24658941ce2b2ecfb50e879be0125e3d159088f88e234f10000000000000000000000000000000017b642d1b5a953f47fff8f0649263f16f41a0ec0397d5a81571174aeb85431c352e2bf6bafa6894d2e6cdb5eafff16d40000000000000000000000000000000017b3438d0ddbd2ace1e63802013b5bac00d31889dcb2d9653a6f6412d157aad2fc45267322a62129087380bec65ec169", "Name": "matter_g2_mul_26", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc", "Expected": "00000000000000000000000000000000089a07bf63b8029e0506393828d8593b94b73c750815552f9a3c74ef7470b5810bc27212ba02ca6fdcd97e1e28a52a1e00000000000000000000000000000000051a93291d4b912f0a594d45c0264a9073663a9ec75e6ee81e13e79383d96e9330bab845fd1e5163e5b28c41c4a854c40000000000000000000000000000000016610bf2b2006207046e489294a132937edbdf95caf508f0df3bf8502e641aab9c44903cde75cff3c1f86873e06cc58c0000000000000000000000000000000005d33669fd8a6256dc55f513bb93cce8bae62a593eb8903cb7d7902a7727efb8fb4bb2e5058441c30b99f146ff5394c3", "Name": "matter_g2_mul_27", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792", "Expected": "0000000000000000000000000000000005aa23543088a9a833d773a71275e73fc3081e13c907b8a04a330df7d6c06618fe69e644e0ee55869e364d3561e40f300000000000000000000000000000000010eef9238d2c520f32243f07161f3e35b15fc949b9401baa1a9c5df7d50b2cb3bdd237747735b235862bb57322fd9d090000000000000000000000000000000012dcc16496c95e39ecfd8f0514b5ab2569d89826d957478cdecd4e827095034e974039b37e767a0f25bf057ed715aeb00000000000000000000000000000000000d0593865fd2172ebf1b94c7511ab7d433a276bf833515146adb6d79b6e09d7c18f4c7f4d3241c14d01a4ad0f31580f", "Name": "matter_g2_mul_28", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c", "Expected": "0000000000000000000000000000000015785bae0c27680cca2097ab52306207a61ba9903723f574091ef5e57c2e871e076d7f46e6e39f65a01e183e7bd822f000000000000000000000000000000000071110a384248664db46f21d87b455a3ad3c43782c68304ce17f52cc8579fb2e3378995d6eb3b8c97665e5fb7de665fd0000000000000000000000000000000019153a01c2b3c5d481474a71e5c67f27fae3232a0c8f1655ddd4da6b4c79870bfb0b6beb4af8c54aaf7e9251ad41d639000000000000000000000000000000000c58375439a93e0763467c6a11dada3e579ec53a968c9b9c1a446cf3224ea0c89c9ec218a8b78de91fc12f087e722f94", "Name": "matter_g2_mul_29", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa", "Expected": "0000000000000000000000000000000004c7495c03fc3fb4d0fd4e0e660d6424de9e060eac72eee3608ba95bac294a3a62d246f42dcf3b575ee1cf8e20a9106100000000000000000000000000000000091140aee42a9dc875f87f3ba29beff95138790140f8bb522c6c15281b3545995f9c13b0b73ae691317e674295db6526000000000000000000000000000000000a945a215b2861427e0fbbfc6fea04e79edeaa1eb87df5db8e5e017cf98fde7b8d5a04a1b2129a4aadd2e3924ecc0bb2000000000000000000000000000000000a43f8d3d92a03b7bd4c8a34ce31729ea0b8e6b051c30241dca2db31a02b6e537071a914d8f0876f944dfdb613540c6d", "Name": "matter_g2_mul_30", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", "Expected": "000000000000000000000000000000001821e14e70e12c7caf2a1ab651eb81dd61c4e1eec9a02fe4124abb865a7029e066f03b62e6ecfcf0fbae5151272b524f00000000000000000000000000000000044ac4a7399d6a67e7ee8cde3f5fe20b0a745462c870926f0ce8554061eba5bd62a8a08c798d8bfe30fba5567d47c7ec00000000000000000000000000000000178b8f061ad9282b3b2057f20c115c91df994ac40aacd05b7669e934bc7d650a0cd88f9fe17d7b766e34bed587ead58200000000000000000000000000000000188311eea279ddcf75f8dd82643ca3efd560ddbe6c8f2696cf7da03e65cc90d97b9f9ce99e29269644d8b881e624cca6", "Name": "matter_g2_mul_31", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd", "Expected": "0000000000000000000000000000000012496dd3c1278b55bde81f6944c4bdb71869f5e5e21db7b1425ea32fa1dbc8c301e7f5e68cd7629c91650265d1361e690000000000000000000000000000000004a1251591efdbdbeda21eb89165ca61a2e090a73426451b6933d939161364c4064a67a90f859a7713fb6a9c5321d5a200000000000000000000000000000000163bcd07d030fd6ab8a8e0bf39b136dcb34f03925c3fdadf55e94a90bfde0ecde5c51d2f4d06954aa6a96c913f2ab4610000000000000000000000000000000016dc065a852ef9e038d93cc583b4a71db9b96a7e7a819dc530598f1ae256368438f52e4b709f15f56279b9c7f9db8785", "Name": "matter_g2_mul_32", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45", "Expected": "000000000000000000000000000000000a2397fb3a3891d1703eb2112357c5fb8acb070ba9f3a39050be6f05b49b8d2488e94adfbf849c8b4a42e287077e9fff000000000000000000000000000000000cf2c02a97addbc1584091e411f9a07135f1fcf989dfc8ae29155ac90b214ce20dc11a1fc75dfb697694891d934abf0f0000000000000000000000000000000018fd4af647bf0456aff9ef80969613829f8eb837205df552aadca46bc3bf9838e0ff2515d3fe869f80d78e2357091d8b0000000000000000000000000000000003c5671ea4723498359f29d49ebe974099da3dd59d21065a721f7a4f14dc7fb1de3a67a707bfa4bad7058312632c6113", "Name": "matter_g2_mul_33", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b", "Expected": "0000000000000000000000000000000000676bd7ce63d8b58cc1e5399ced9b495baba4cef9503c44760f92d6d9e092d6d5308fa88144491eda6c571a8c308786000000000000000000000000000000000605cebb4c20bc9dff0258f75a825f55f23a32cd0804dce56bf3cf2f19a3504f0345e0f1b839d4d5920aab19b363ae19000000000000000000000000000000001512f95f60a6dc79dd9261c321328ab8e22ff314e7582d8de83aa3bf280805cba8ba6d359a620fa6f0564396a45ca9760000000000000000000000000000000005837474ba78e0700c77141d70af1d8fb95a97cbadc95996faa93c2e81b7c8877d08d5287f83219a24bc0080e630e39a", "Name": "matter_g2_mul_34", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca", "Expected": "0000000000000000000000000000000010b2a9b32e431c11ceb474942bbbd6915a3cff64a74d67570fadeb7447c5abcf1bb35c822d4441565322ebf75e61f64c000000000000000000000000000000000b75a0212232af0a59440482a1f953cc29bcd35272ef407925eccd70c1dc4705dc1e97d2da604996d3c52155d05d77500000000000000000000000000000000018751bc59f5907cbd7f1d503bc5aa266f4109fd3133a1c4c2e58e4a17250a40053b4489da4825b4c368b0f4947baa6240000000000000000000000000000000019b41fa1af9488596b09c587fc33e044d51674eb6087c647d5a762d85e38a587eb5482687d9346a1a701bd3a8bd36a61", "Name": "matter_g2_mul_35", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc", "Expected": "00000000000000000000000000000000054836eb7ef9edbe914bc16d1498e0bc3c978bbed2518802c2f8e1c0b59fee482cce0ae8e805c33861d4cd595f6b8bf40000000000000000000000000000000007dda36d55aa7a890aeaecf2528a390c98d9ecfc8a5c78c2a6def30de55b90ae408ab770cf9a9a4663ba601c4f5765a00000000000000000000000000000000007ff7b24c8ed9fca572069e72b1e93978cea87a0fac7ba60f54aa573d881f21b73012b010e9c0fc9324aa7697bae0c4a0000000000000000000000000000000002d9773bf294efe64021e755e4dd2936a5060bbea5688b6369ffa3b94eadcc58cc3986c74ff365301be1e6c785939b69", "Name": "matter_g2_mul_36", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447", "Expected": "000000000000000000000000000000000902c1082ff09bf93b91c9ef5e447bd6832fec9297cdb065f11fc5ee626e6e8834cb5d74775c586609a0394e6114e8820000000000000000000000000000000018e414a40c27430b98246fef556e74dd3dd7adc601e3c05b79f8c29169780a173be9a725df3318d71b6e82abf97930bd000000000000000000000000000000000f924fa88f43c86ec98b34379b9a649c7564ef0dc596c95df19522fd50fb3a37cae031e891a7a7aa6a5e6a9062c3726a0000000000000000000000000000000006bd3340412f64d02d0cb3ac44d1f31cdb1906e56dbfb66d86b60a74cd26c1e241963fcd8bba4109c428db0bb083e81f", "Name": "matter_g2_mul_37", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828", "Expected": "0000000000000000000000000000000001415fbd8afeeb5796460a9095f14a8f3f6fe0374d4cc4160f030710a6d4d3a92febcf4dad770de3a3ba1a2efbd858210000000000000000000000000000000015792220c7e53262b56224d230a8a4b32019c77548704ec16da5ce167854305e6cdb9924c248f222d6fe95a8383af7890000000000000000000000000000000001694329d8e0f41256b703a8bb6548f1d9e0749a55c124c9b60361b4cb1daee24fcf272327ba598022a92815764fc8570000000000000000000000000000000003350658842c5b6fc5561a14df27d950a00c5bcc13d6d9d014bfd6dc95ec1a030594625f41d439b90b05275a0ffefdb1", "Name": "matter_g2_mul_38", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a", "Expected": "00000000000000000000000000000000054c6cb26c8b0a9a4700e0b95348e6fb1190c577eba03a44e84fe7744c543321d02c4d8f55c03f984b44ffbd899ac53a000000000000000000000000000000000e7ab8da5d573cb88a78f6a6ad2b307bf867777f79a643b6ec89d9cb208711c85d7d2cf8f8ac69a8b322000fc7866024000000000000000000000000000000000fbc5926b9dcd9e4d1ca1a2b43dab5c98aa20b37aff0868c54441de44eb014e5283010642717fafaa95000f4313e14840000000000000000000000000000000003671ee05bc20bead72f2306203dad55cf20b13d3bb2cca079bf4391411b85ed4df55e1426645d73b6935889d4450c58", "Name": "matter_g2_mul_39", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb", "Expected": "0000000000000000000000000000000013fdd394635f42a926a2324b8cb870b5995772ef4e25ebc1da41dc5bf724f747da8d95a28dd703b5ed65ada5555c8b5b00000000000000000000000000000000118fd550962d1de8f1e60c312643ec7cd306f0bbcc932739270595537c8d290ca7e20b962fcde570bd2ed7ea43009fe70000000000000000000000000000000018b25fef4b75fc7649a489d078311dfb6da9909f472de7bd9bee9c3ee353f345c83119269ab797fabdbede41e0fe6169000000000000000000000000000000000b7c2a73741f6944ef4ce8fa20b2900612645c224818b7faccf6597827fa07f7262295f42be5f34a751a6400495f7eaf", "Name": "matter_g2_mul_40", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425", "Expected": "00000000000000000000000000000000177d29de8a81db2e515d4241e5f7e3d35de22bbcf9aaa616b057cbf2dab57ab8d98213cdec82a2034964f3e1def8a4e3000000000000000000000000000000000a0cce8113eecb064a60ee2c470dfae8b3921f8da2c7ad8dc918b355ff44542b007add28a44848fa8d8f8671617431ff0000000000000000000000000000000010470fcc723286327e951e758fd0474de394778d0c1ec5fe6f263dea1957c60f05dc8f9d82b3c6a7d73b3e783f35ade500000000000000000000000000000000098a6ed331f03da7ccc9148f07b19b132152e15d9fdaee5cc092524b33795edf2b458b4e8383c5e29affd3f025094033", "Name": "matter_g2_mul_41", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96", "Expected": "0000000000000000000000000000000018a1f1a60172a65abc8f2d855ee7510c1e0af9bada084325027bd493ae86ea2c62c15ace7f63562a82cb80ee7095661b000000000000000000000000000000001736b977fb52eb1b466cec3d42df7e89047784f0e8362eb6425e37adb1e84d0438f5a6e82c7b31d59b0959a5f4aaf9310000000000000000000000000000000013ea0f849830f8e48161e840295637d8596b32eb576560289620b797b14bd395d835e8140b69039c904ef1d07a82127b000000000000000000000000000000000d7f58873701c138cb7e18ffc36cd0e47b07d70448ddd9fdc4b947003fb29cba0775916c752d531e527ab744c277e5da", "Name": "matter_g2_mul_42", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc", "Expected": "000000000000000000000000000000000290fb3f38937ce4439ceaa21cf3b31db8a22f9f5ad9db0fd7d38ca978192bc05d41152f8f86ca7b2ee0bb58e125f57f000000000000000000000000000000001775913fc24699bf08f25fb946fc6527178ebb821c654b7bc69f6f86b5168fc42057a5d3bfdc53b3d57fa1ac05f7a0930000000000000000000000000000000017b9043cde8dbf500ad90463250a49f56b35713f2fd9a35d8391fc36c78c083e39674592a98cb857194ef9e73a62a397000000000000000000000000000000000e5e62e39433d443e7d2d32754d2ca2556cf6deea45e5076ac040e3d6de14e9965c53f8c65bd98ae7d17ad3a26f3accb", "Name": "matter_g2_mul_43", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca", "Expected": "000000000000000000000000000000000d9927347a9ac9b0290e68143fbc6a5f4476604c3fa5ae87e729a03ca055e4c6543f9245a4592e195180d88781e46ac900000000000000000000000000000000175e0ee8de4002b18f32f70f1bfa9e0be87288cddf1c436428c2969884112bef5db19e041cbaeb23596e25cabea3777300000000000000000000000000000000074ed9e981818102b9ba818d478ba27033eb38e3fa19cdeb9f5820e59a64dc451342a160359c54bc8ec7d866b62080ef000000000000000000000000000000000a853930020bf01e20816d3aed242e00792b0d0e78fb15403fc3cc255f0dbd99ea6ae1d59d5978e562be4862b3317324", "Name": "matter_g2_mul_44", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a", "Expected": "000000000000000000000000000000000e9c290ba8a22f7bb3f7dfdcc9f5a221a5ce838d4fa85a00473a4dd830bacf583dd91a6a6f78d2ebb54a4c1bb217f793000000000000000000000000000000000dc51b0ae8bda6d28c51016764fc028258171d7c7646393228692aef7f1dda4a83e53553f63d6ba996d4c0a802bc967f0000000000000000000000000000000014ab155029dd35206811be9ca4efbf762a1673367e6b57528f79eb50008ce7c3b49a2d25da0ae68ac4030ab4bcc0daba0000000000000000000000000000000008cd743bb52e7908aa973c8518eaded75fc2858f4edb25fb7f2e09900f0abd3ac87e93cf1068bbe0c7d99619aa7a6b76", "Name": "matter_g2_mul_45", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4", "Expected": "000000000000000000000000000000001746a449993b0684740630f3f0e46eddfa135371e33e8de4dfe553c78845399e63bb3da48798b35df48d27e1f991954400000000000000000000000000000000057e0fb1113968858981c9803166d8b3eacc91bfad320ea0e610fbc5b276da1b46d74fcc54183ba61d1b2fe6743097c90000000000000000000000000000000000b3a178ae3b739cae3e80f3f44db42d8c465a5cfe4943b449d4c3b7f4ad153916c6cf4fdfece14a00b271222c72764300000000000000000000000000000000041c8b293ded0c647f2e4d6f9b35304179b723c3e6e421a5cb103e561d1655b92e74877ce22c99f22a3700c3aba9ebb9", "Name": "matter_g2_mul_46", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", "Expected": "000000000000000000000000000000001103cc395acf81772955bda38f951a81c5a6a476c0b5e1543616a5a7a7be22dd487ab2a8586524891300adec5225b4020000000000000000000000000000000003479a08e2811ae9aab0301d66ada470935984d7466201f3fb28c610c0b5f67e7305f5ad3514cec5f30b51d0aae775d40000000000000000000000000000000005ea37a6d20c1ad0978da68ded3a5bfcc5ad8fe81e39b525fe7d1f2b2b1ab0be7ada80173b1d0b7fe1e06ab6354e64b10000000000000000000000000000000008f2093151a285dac511df1755e99a652a1cad0af3a019650fbdead1421ba8e84afc9eb0a4fea651f365d72f031a0ca6", "Name": "matter_g2_mul_47", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7", "Expected": "0000000000000000000000000000000019f79677ea0e011e5c9a892a407646798b05be05337c73135cb771abf101f450bbffd08e125f077f5ea989decc009b9f000000000000000000000000000000000ed15f35966024cf1de2926108151e976dcb0d51b2736b0877d79de81f6fccb9dd299d14855f4e257cae33ab7455b95100000000000000000000000000000000125e2fabb5cc95c0a7890e9ff2b70102a97a03f2d11d915cf4332dd049a467333e12ebb27955c0310ebdfe2afb3173ee0000000000000000000000000000000011718167000f9b749f1615610a30023db4b986364da5bbdc4506c726624a073548a94307b282590cd8a43b4900a1afb2", "Name": "matter_g2_mul_48", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602", "Expected": "0000000000000000000000000000000005af8fd9e79568b46fc42b2c1bac62d115365834e509dab032f66425b7a571a4bd3bf702299d3c5f36c372750b3281f30000000000000000000000000000000018499089f306b3c9f7a645ca2f9aabc4e57c046992fff87e832e21e21875c6adaca050ea8bd7043afec3a36ecf8eafae0000000000000000000000000000000000827fa0f46134e2dff80088129841f0469ec7360fd8b9864e9ed99c5fd3458e6360661ab4c671846681d491b8b823d200000000000000000000000000000000120f829e8d0ffc360a14eabaf52bc653b1e90a36c0a8af806ca745fa306a9739e31435039a377e0748caf5e80c2b0b09", "Name": "matter_g2_mul_49", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013", "Expected": "000000000000000000000000000000001745500b00e5ebc6f71c779ba0b0f8d6601a065c550ca19de9562455423d2ccb507e659b0dce982faa841267fb1a27d90000000000000000000000000000000009c36b54f12d130868ff9b9b61b714fb1067dc91637c09614c51b5aafa2cbe3ca7dce0f3e366d4200cbf603ad4fd630000000000000000000000000000000000172e543708bb853712d81c000c9f9f2378e628b4d13b074317e95deeae98e11e7f917f91e02a0b18cfe9b25f1b83f16700000000000000000000000000000000189fc572ff6a8c6606ba0cea7da7040898d9ee85a58f12fade8c5a22031ff26c2f9cc612bc6e1b82a0999fa93c6fdfca", "Name": "matter_g2_mul_50", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8", "Expected": "00000000000000000000000000000000013c6f777df97ad3ddab9b7486d54d1bacb3b40ad3035b47a25a66c02e8866955e27a8ee52872c8222ff7466c1310bad0000000000000000000000000000000014a5eb510d7c743e824f4daab21c43db4d6de8ab2e825d13ae0e186aaba828d7b4a2343a11011a8ec4ea82f456e394a70000000000000000000000000000000017a55d3827b78a9c5ea792b705eba7777df74951930791b17ff5b861e98a4488f83007c073c3e904ed4ee328b6f6171c0000000000000000000000000000000019bae02f8d6f1e31dfa09f4feedd5217ade66f6e8248aa98b273574f72aef83d5048534ed38acab9e0eb4c64f4389af4", "Name": "matter_g2_mul_51", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b", "Expected": "0000000000000000000000000000000006490c327790b4c451f93197d7db24211a3b4b5f573a6df409206b4bbfc36bd10d2d0c989889efffd8f4daa4a68b211c00000000000000000000000000000000168f224738db3f07af77494f52ea5e957812a1acd62615f0eaa95c1d363cfceff29be9cf3be5329bb41175a0231ced4f000000000000000000000000000000000321f06b55f7dbfd4900b329c914f9ab9be2794e51e54498e18f83ece5bfd205131fbc254bfbf624d57ec2954b05f6f00000000000000000000000000000000018ec54f3e09bb2a6b112b575f9481bf1c85666133051e9c0ab53369d14eb90e27d2ed02dcda1250d5d539df0d0cda37c", "Name": "matter_g2_mul_52", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244", "Expected": "0000000000000000000000000000000001641b4ad10da5089164809d82ae47f74e27eaebffc2a2ca3c1b924fc69c1ea80ba3da78c78e86957f6a24e7f75dcada0000000000000000000000000000000014e781e4fe79ea1654460f4b0daddaffb29b287efd8168cb20d7ac6c729f684c5f2a7cfa87885accee3a797febc904c200000000000000000000000000000000001c9a44547f0c5b1f4df190285644c5a31df61e3de7da085835ebda917d5e4163f2deea9a83d641a4759fa3108567ad0000000000000000000000000000000014c3d2a79d80687fd6e6aa423257644fa5d0cf641aaf6a7c5675a810767904166fabd9a2ced0727e3badb932e46fd181", "Name": "matter_g2_mul_53", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83", "Expected": "00000000000000000000000000000000129554de7de9a2b73340d94d96f0356a2d1c0524cfb007d76a75f462872e831f45553de05f5b6a1f9eeae37af7f6b4c9000000000000000000000000000000000b1ea2a649ca13a3dc7882f2423036670f68aa05792a8fcd72524420e37381a9ca80dfea701fa5e6da57afa534059617000000000000000000000000000000000b7ff27aba408f9759b5109600cff66c03cdb4bfb3dff64a4838d0516fa46bfcf429fcf9d5cbf74a27f70fdccdb1238c0000000000000000000000000000000005a99aec88967fe775c691d443e2dbd45080eec97e686ee6d7b32e801efe6563315bfafd5c7622d0543519cae4417029", "Name": "matter_g2_mul_54", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852", "Expected": "0000000000000000000000000000000007997a499b2194cab634750a189cca6783ff17d866d66f5998603f8639d2242e8039222c65b0d14001167a9b09afb58a0000000000000000000000000000000015050fe6b335884a225efcfea4acd025cfc05e8f5fe9a0e22a0c91b55664c118d79887de91f1ae6cbc081f6f55f0067000000000000000000000000000000000195b23c4c2c087082c30600ff00485d169dbd360643d163f1db363f270cd7d4f177c36b4c291d50da4101e67b229d0de000000000000000000000000000000000df596ba2350ff7d3e75b4cbe5f8d6b2cc0e14b3bd6dc021936e3371ba64031f6266fb1d2951801309f22bfb1c4b27e4", "Name": "matter_g2_mul_55", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c", "Expected": "0000000000000000000000000000000001fa243b548f8f5c2e5d7736ca6fa95b74dbfd31f95fd532b94f81a255c73e7c0e000e20f9ca6750cb0dfdcd2c1aea8a00000000000000000000000000000000132a893a2326bf61962e1855331a53667e6279ed7358bc84c4a7c218b6cff1d3f449954f56daea72bc2779c60f1113400000000000000000000000000000000000091dd23c75dd8266f556bf27ba54c95c3ccab06168e4e6d0747239722afb20f3db27454c6db3a88daab0ef10659a66000000000000000000000000000000000d3b2e3fd358aa3dae983e87b5d1fce6d5688e66ced6e3a2c96b8d48041557295d5932af6532c13965d4b383fb252518", "Name": "matter_g2_mul_56", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0", "Expected": "0000000000000000000000000000000005095d1becff61df906815842112c6508d6cade4ef5f4b7418f5f01e8c5a383addc1c572237613dfbbb88bcff80e4a44000000000000000000000000000000000bd2561e7bfbda8a48ee038855e37b03fee805689452e9afaf0da4185e0c194e407ce7149b713c689d25f953da36dd1f0000000000000000000000000000000015ba3ae4d4238175425ac5dcbd9e6e9e055b8c1b7752931b524fb546f7bee8723ef2e69351450c6d1ba3c366a22355e20000000000000000000000000000000008c17d77dcfda00a1d75ea0087c58e74263ce5ce4066e979c66397de8e236708831c3a9ca6b35ade8038a28930655eb6", "Name": "matter_g2_mul_57", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45", "Expected": "0000000000000000000000000000000005cabaf39b93d7fe15ef6a7a3031df58219bce702a5a77162551a3d916c22e8ec9af2aa20659e7c4ce5f6382a5f82726000000000000000000000000000000000dcefe1a48d8c239164b54771118f7520ac11a7a6b72d8e17be1cd788cad2f26d3a0d9113e6536426800a744be9f0d4000000000000000000000000000000000199d95a44a4334c87aed273a0184be9602ba443d5b8d34f3495b04e927f4687fb88487f586395c7babb4f218fdbecf8c0000000000000000000000000000000010972032f9cb3e8f45447bdd06df82656fbd3ce38a9f7564c6e5d62ea3596c9b7e0a94046f1c65bf0452ca25b15a885c", "Name": "matter_g2_mul_58", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f", "Expected": "000000000000000000000000000000000f250b5e47ef616be106a3334e2f516061eec8f7ac69f08f6dfaedecd76fb1c9685ecdac2c3ddd534e3947d007ab177000000000000000000000000000000000073819a6de38303725aa3a9e5a7a9122b4d1e60ee8deb3554b5e06ef5e60d71517c2279c5066af003b32cdf83b7fcdf200000000000000000000000000000000070721107ac6dac198f7ed1a7f84697cbbc3199a220d1aaf82e6f015963bad863f99190f18a482f730254cef753ba22d00000000000000000000000000000000169910eb30b8fe1ad8f84c4a132c6c74a6ff06ed6e792af3baa6619e3c8aa6cc3e6f687299467ec9554f9e91bee77aa8", "Name": "matter_g2_mul_59", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d", "Expected": "00000000000000000000000000000000106e892e336b2155909946ab73b980ea761cfe8c48b13ae8a5302eacea08b9cef3e60d5b33c6ec4033218ae5761433dd0000000000000000000000000000000015daeaee59f3b4cc26d3da745661e74db8fe1ea115d50ba49ef5e6151a9ac2f3135f0232235cac7a53e1e8a70eaf0476000000000000000000000000000000000ff494d17c735b934c2c7fb8f413103188fdb116fa8f4d4e43262968ab0fa1bdec23b0d4d8b1c2defe624092de36610d0000000000000000000000000000000008f70b7e9f2d7083774fbce3bff58a1c73fbcbcd9cb049cba71c0c3f0c363517c8956240bcacdfb7934d4c67b1bfdd2b", "Name": "matter_g2_mul_60", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a", "Expected": "00000000000000000000000000000000098f32b35e3b7dc1862ca1ca3c76d009f016c6b91c227f2cebe8f1fe87567d936bf1c54103bec31b3552c077c0242fb40000000000000000000000000000000005380a66d48d348487624a15b63d2ecf6976b5b599901101ea8b1f57736649b4397f6679ecab0ae29573695a921ac475000000000000000000000000000000001710c368f70a2b9cc92ec65c4c2ca35fd63440eb350f488e7c6646f9c42bf680eb62a887d533a91e47988221b46c868200000000000000000000000000000000033c3327da938dbe4630dbe16838229d7d427f3adf18dee6fa26b1c8067838922c1bce78cce08d590ee1acf2baebc7df", "Name": "matter_g2_mul_61", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997", "Expected": "000000000000000000000000000000000404587c60a4bbd8b5b929ca2ec2a9ff2ba4733f4f2877478a669b238d65ca130cba398899f2910d6de04615f8ffc99f000000000000000000000000000000000940659b3e6de7c3d8de9169a28e68dad433bda78de0991fe4a1d404e5f4babcba9d57c7f3d638aef264642f87c61fc8000000000000000000000000000000001676ce240e1ff70ab03f94f3ba3acd31725ec306ce1fd707e29ec22cf91746216dd998d03ba13a79dedf878fae38d68e00000000000000000000000000000000098a81422511f77191ee15d402614c86f9447ab78a89cc348414108f36857a1929f2b92ced78752ab3604f276861803e", "Name": "matter_g2_mul_62", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", "Expected": "0000000000000000000000000000000010a4ba6952d22a51dbb6762a3f9bd09712c2be5a98bf0ef298d7a7e3a9735ab0d3bf39e40b334895c73a36c218ad24b50000000000000000000000000000000002860f38ef61b497bdaf4faeee7b406007981c17246cfa36cee906452ae85e1c1c6385898ebadc3b4ef8887fff25b8240000000000000000000000000000000002dbbca9034fb17c3f37727d44c027cdf47c36f3f628ea9385fc9fc371d23f22d983656caafbf1cd1f8bdeff4ad7669d000000000000000000000000000000000b7e71b65765c4113a7884771952268a9fe10576f745038912e6877c78372cd261220793b888c43accba1646e902fe14", "Name": "matter_g2_mul_63", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d", "Expected": "000000000000000000000000000000000e9c1a6d591be4da37fd6dc283b8d899b625ccc96371dd3d7731aca66cd2a978810497171f2aeded64fa2b10e480de2100000000000000000000000000000000006d2ad7287847255002480627782d513eaf1f68a3d583d4762fc156b8eb40deae6969fa8a7d8f8aae923800091386a00000000000000000000000000000000003c7eae0eda08df9b9eee2605a44fbb486e3bf2e409aaa1c8f38c06f969ff1f74338004b01288dce99be26a837e45d3a00000000000000000000000000000000178174d2f569a9392eddd2715ceba8762c5bcc6325217db5e5f970d6fde069d0e48a824e5b6ca017891de175c92f6b29", "Name": "matter_g2_mul_64", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616", "Expected": "000000000000000000000000000000000ce12c9010b4c4afbddb459c1b46063a8488277948188b4ec0b739e1cebb5653681d0e43a0d2c6b3f842bfc609bbdee3000000000000000000000000000000001123f60cedddaf4385e63758d64d4facdc443854176ec199ca0df0a9c258517f2512594f2441a4b9a68aa9a2b4a1f4bb0000000000000000000000000000000007cc6f77d181d13bd9736ee23a33b25b0bd969760642ee19004e095ebb8e2b3c0e09321eb15a2f7961803c0fb10b6ffd00000000000000000000000000000000004d8dbf2f0c14b07ebed2b9cb4bc87df78ac8a34ef0b05cbc2c6fb8e8156415399fa52dfb968ef0e6ec697030fb003c", "Name": "matter_g2_mul_65", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af", "Expected": "00000000000000000000000000000000172805bc715a8cfb2e25c384214f4005aa6d3b809a0ad95322209851ef92151526a9d01a914c4d7f0c120b9bf3837010000000000000000000000000000000000473ceaa092a5ac12f38b4065477672deacc08e553d8e9e6391bac0d9ca50015934cdbc340deb05aca916cf50c7915b30000000000000000000000000000000012e85461fbd26c2d0235acf5c8665750656819bb939e8fae77a8d526ca23443aee395a985cdd4b1eb700311fb87e91a7000000000000000000000000000000000246d45fdd88448c93bedf4799becfc7c80e67abd483f2a0aa41e8bbb3f38cbc900314436364f1db6e1d88595544517a", "Name": "matter_g2_mul_66", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145", "Expected": "00000000000000000000000000000000122e1f2081cbde0055fc34d2fe61307bc333b35a1e0772a0cd6fb25338c89824bcf2f066bc7b571b2fb314ca7f45106c00000000000000000000000000000000027ed81b54372d858a6ba2faa65fdc132efbca6ddcd56c3625bd9267cf0ae04f6d342209b995060f584be8d40020669500000000000000000000000000000000002a03427a093a3000a1bed9eba91a82dc2f2fcea1a16a1fb8af29c4988b589abe6a505ec87a82864b3c683beaa6420f00000000000000000000000000000000134bf64871d69a72e42766c2903fb4589b84d7772a62f7d2f8f8d02a914f4d3a278c680c626ef4d69de8aa88b57589a7", "Name": "matter_g2_mul_67", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a", "Expected": "0000000000000000000000000000000018fa44efeabbd1cc47dd9b1a1195ca921c99c77ed43a44502aad27b6c663f5ce2623382c3ddf208f42e3eea741281f4300000000000000000000000000000000138d11e497e3c5656bc8fc0ae4322a0bfb6fc20e249a47a103b164aa3d9fdbf7df4b1e3b0842b4b12568a31992a151f000000000000000000000000000000000182490d6ae35c1208c0d608984df4988d057f3ce5a25073c77cd5b224a5892768badb1ad5cef8f41d1d2022573098c320000000000000000000000000000000002a6e0523781ccdebb75063dc7ad1a9526f9ff8ea1364bae487914f254c0eebcbb2cfc3715fecb9599bfc2f5feaa62d2", "Name": "matter_g2_mul_68", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0", "Expected": "000000000000000000000000000000000dc7488491433d5b3924105c01ffed4f30b755d7253d867fda595e7d80197823e56e4d182d5ecc72d8ef1ba9bca15a310000000000000000000000000000000007bfeeadd6fc468ef6340a2b394c155bf50808cb11e89adb0de5499fbdde91760e9531c1deb23050286a15e5910f1d5a000000000000000000000000000000000f096db706b08485fd577f37b7bd232b5a10c3f80c25bcf82f7a3b666c6efaac8e856bfe5f7dafb7457e33eadcb4133d0000000000000000000000000000000004460d1f25159ce6df59efbd7c693355af4634dadeaee2ced68124b2a887698c10e9c4b40c4f4f9c8444acb881ceff65", "Name": "matter_g2_mul_69", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258", "Expected": "000000000000000000000000000000000f1aa4a7a22c568c41270d24824138bf9ffc763a5356b7c0bc1d051a0a0db12616700d9214972b63eeb2a398d27dc83f00000000000000000000000000000000020d0c2ff8f93db6b415c2a01712034e46bdeb6e665a5177a3877db9f5401d3dccb99907ef843062e394c1428983725a00000000000000000000000000000000088abeb6fc3ead45d5b261b7d684f168ca8f5f163cf338863e6b102dc40e2cd0ede97c47460ad6f560c27e95c8b71ca8000000000000000000000000000000000ca2e5cec212d581c737928512118e2f51a0d74070f40a998b7b06d22b9fc754bb2fa5499308058be9ab81521d057414", "Name": "matter_g2_mul_70", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e", "Expected": "000000000000000000000000000000000cfa23c46881893f6c50d238a83669deb520a7fffab4f912f77df7cca43f6827a1a0ae0b3f36c8f116ecefa33b8bf37a0000000000000000000000000000000014b7e5c18d2f9bfe15b0c1af3bc6e230039a341e135837d123e91cde9cbcda298c66b93f692232c912e5d7d3d6331c430000000000000000000000000000000009c8984999ecd3a4144ccb925d3e5cae5c1662dfbf8871013b1cb2946482fcb075c489c61b8d6261f2574b44da3fc1ce00000000000000000000000000000000196e7feab383211e4825cf98219c63bf9f45a72d66030219cb585d5d25237a01a97f00e122db6a51325022e69e7d8cdb", "Name": "matter_g2_mul_71", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83", "Expected": "00000000000000000000000000000000005c0282830934ea09c9f51b52cb6dee75b874b155c63076dbac2cbbf220863d55557ff1b7d681fa185435df1522f49d000000000000000000000000000000000a1680ebbb185c8e7d8a197a523a7a5e618f97c46670622034d312b3eeef140150e03b00ae3dff8d9f1d872f3d3dca380000000000000000000000000000000019bd2eb4bc25f5aa6bce206f0683dbbbbb002098a118fcfb060c1353a310c2baa1063a782bafcf6ff6bb8edaf6f1597a00000000000000000000000000000000082edf49a0435e0b9f3dc7f207711d66004ae688b18f5b62fd1596899ee8edfaac7da38973d81f12200018fbe8151572", "Name": "matter_g2_mul_72", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07", "Expected": "000000000000000000000000000000000b8a715c1c2792a30f7ad752a808b621c34af1fb7f1e3392a36ca9481a019108a21e3ef338a1d05f2f23ac3e2cc42ed500000000000000000000000000000000101375c9de592031c55a7a62189fd3fa3c565abf7c64724796dca3b1c7a6e6834a16ef1c4e2afd6ce2e69487260f0028000000000000000000000000000000000cd385ec8245431d3b1aff88453db7f66a5d7888a5c1e0dd0abe9ac7db752933a343b8be53b7bfffb704768ef0a3dc5c0000000000000000000000000000000015d55c8cddb8715e25fa260d1e1fa672ff76eca7c80d19d00678fb9d08759b810cf266ef0a7e9dd749a576ce07240fa7", "Name": "matter_g2_mul_73", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6", "Expected": "000000000000000000000000000000001311de31229f1825d0bd2c9d726fd71e05828a20406a4705ea65f441537486338022bac4e552bf3c25e15717bee00ba400000000000000000000000000000000052e082cbe36c854a028a041981fed87d39fb218a88208aa1096e260a3932a1155db7f306c32d133070b0a5bb6d161760000000000000000000000000000000003269d4afd20002873f4305018a4432c1925eea28486d657cb458198ff2df9d304bdfc7455233243b1712d8663591d460000000000000000000000000000000013376fb98929cbe7f7d090d1c9d5c4f6332bbf25470aa03c35a70481931e4bc91c937029a5e11d2a3418eab698361227", "Name": "matter_g2_mul_74", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659", "Expected": "00000000000000000000000000000000021166263d1a443d5b2eee9aeca3678ae4c2b44d556a7cb9631d47e4fa3bb05ecb94d6582f4ca0cd787027fb5f2efab60000000000000000000000000000000015335d034d1a0ce78e1246a16e35e0075f73d4a392da1e05c11388084cf80bf31d499e57c48f4be6e72d3abc7b387ec6000000000000000000000000000000000deac4ae1900a4e1814624fb4b8c7a3149fa9cff2ca97f02e7d6765e034a1532a7b8475ef7aef5ebb851063cf4b9e79500000000000000000000000000000000161e3af03f226278a07ff3b08e5788f6c5029b2c8293e7a7e3ae11c4d78676b60dc0208cec6b82e1714d976007fbb389", "Name": "matter_g2_mul_75", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d", "Expected": "00000000000000000000000000000000120b4434babedbd8ff295a6e2ed5fc7af0548d7e60663110050be797584c0cb638988201ae7707cbedf0c8b3dc5ced85000000000000000000000000000000000d2de0a260bdd241a145e3f68a6de48da4c65107a500e02bfeae6ae7dc428026c7c3e9bdda9a3069d2744705df2eda9b0000000000000000000000000000000018a237906c0e277541c4f00c4c2feba7cb2c9b87709c18b62b7c36d78fc118cfd65c127765e01dc0ae5875b9552bb45300000000000000000000000000000000197485daf54e98e097b6bca24b0738682969256decbf3ebc05f6982e4608829f37e2877937b3f26b88efc3deeb4bfacb", "Name": "matter_g2_mul_76", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712", "Expected": "0000000000000000000000000000000005de82540aa67c69b962d292133b09e6593961da8944ce02557141abd19ac471f766b4083db85c67a44b65dad2202488000000000000000000000000000000000cd999bf3cb004074fe9f355cd8dfaa7b9d3439d902fddd2fd0688419b5b7f8c4300ab26b658936a90c0b8e1488249d1000000000000000000000000000000000f97ae779429a5afaf7a3343586eea84a4e76f00a1852ce42a4940babd565bc8d61bf72fca9b123922f1ccfb1db8c06b000000000000000000000000000000000935960fa941c27e74234a07857ee680f53c31047235c6152d1669724bdef37ba642cf4e0dd355443ea470e6430def8d", "Name": "matter_g2_mul_77", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72", "Expected": "0000000000000000000000000000000001b0aba02b0e907c03d2f4003083c824ce60f2f55f70dc6ec7c7f81f3d0ef4bf533b4c94833e36e8aa7aeec18b7255de0000000000000000000000000000000004fc227a6ae303f3006f75193cef7c653e6bddd28fdb843b41c7d39966a701ba8fcf611efa71abf059d7d98833480e69000000000000000000000000000000001077fddd0bf3d5c80eec653916f9095e900cf165315d74a872219285f62b5412536e43c4cdbc120ec5c7753318852dfe000000000000000000000000000000000ccd90e01c1d4a00f0d9e29a88e8134f2cf68162da66bd343645a998730190114a6921c9b048dda58b60b42a133287f2", "Name": "matter_g2_mul_78", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", "Expected": "00000000000000000000000000000000185520023714580a3f235e24316478b8260565ffabd39670811519066844e131e337bd62ed2069bc6d2305e6638e539700000000000000000000000000000000055fc74cc7cd3fc393d5b5ab2419414effb783ff4da2516e5465a4acc195339c7b5238be4e0744b3d7fdbce46ca7f5dd0000000000000000000000000000000005f584a0311c02d611c15163529130a2fb3dc853083e7225b791ce5ff32d5ef7039c80edfff317ce9ddeef84443b5a51000000000000000000000000000000000f9d5acb355f767cc6286cc09f6df232532f9a0e9e4ed1fe28788abecb200e22066c23f3ac6c49c47071cbb023e70183", "Name": "matter_g2_mul_79", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b", "Expected": "000000000000000000000000000000000ceb56d75f3aa1548c50d7780ea1e33c3d069b2f37e7f96be6a8ec03266fa8d0868822afb3b2e54750722266f6032a8000000000000000000000000000000000080f15b7f9f2c22f1afacf558267b5b84f3a6d199fd3349eefa2e46c4f332849c0955d19d4513151dc0f3b566c0058440000000000000000000000000000000013305f8ff6080f7da05c28155c0c2bc1c78d855cdcff0bb2c6b82cd5107d7a070d0830e6705f6832ed5baf75a659c8870000000000000000000000000000000018f4e136859b4ceb230450f9abde0325a4d59db98279d7fbab710305ff53250dae1c8789cccc27586c9b9df5c0c4722e", "Name": "matter_g2_mul_80", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035", "Expected": "0000000000000000000000000000000002a0214be95f020c70221fb4fb6856af7ce3845a4b607340f85127b52f8a204efcd94a152835860a4ddeef84946671b1000000000000000000000000000000001767777740a9922a91c39a36e2cdfcd544df902b31812ffc88418dab7321f73406ab142055b5bb264c187f2d4f2d6f9d00000000000000000000000000000000026e6941364c74997506df0f9fbe6b2769839e8b7c7293f4e63d13bd7bee90ff779cf82adc2f23c569d1e13826cdb0e4000000000000000000000000000000001618ab2ffd4b823b9c9776baf849641240109b7a4c4e9269f3df69a06f85a777cb4463b456023b7001adac93243c26f5", "Name": "matter_g2_mul_81", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff", "Expected": "00000000000000000000000000000000054ce66b9b0b3cff6637d6cfdd788719d4e33516b98402d8fba54725309307711fb576299ba99104d4e7df0deac9ea2500000000000000000000000000000000055e06ff52cda9116a98ad3709f788d39db53844b7db58a57af52848ce1c59ec2a1f083efe79c5994b9291a2d1020fb900000000000000000000000000000000040c9ad63698ec78d06b41bdd6f5eed089b67f106348f9300f822a2d61ea1e5d2ddda0efd1025825c99cb0e243573f7700000000000000000000000000000000195dd00c48186f8d1337ca857aea02c4d199d638133e9cbd2dfc5f633502f656343746ec2a416465c3c0d4e9d53fd097", "Name": "matter_g2_mul_82", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b", "Expected": "000000000000000000000000000000001141b59af8fe6cafdf2e247fcb0ee4642a9b4022b6d71163ec9b6ac2f7d10ee3c5c0173ac686b03cd6a7086b039ec786000000000000000000000000000000000f05ba6973c5d865ac5c037583b65eb4eac826b5a04a7ebed1e10bec6ec7dca93b1c2eba70ee0189fd552d5023f2a87c0000000000000000000000000000000002e54475940985ad2115223c5ea3a4c95890f3e9992e3e1a6df2170ab77143bcc5d29b9dcd1ed3bf16e545e9be21a8640000000000000000000000000000000019acc4705955761518cea482b83e3726dea8d1f66a5f19b06cd7ff95828e15d1b139077e0d274b0e6fb86c027844d97f", "Name": "matter_g2_mul_83", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d", "Expected": "0000000000000000000000000000000016fb5839fde95111742255b33f040c41dbd0f142d1daa8abc7c63008ba9f63f07381d9d6128240ae9b6cac5befad84e5000000000000000000000000000000000389a11727c356b8f3bdb6a73bc2f6d2d73d33d287365283359521dcac64f17810bd58c0ec5bef4db253bf630bdd9599000000000000000000000000000000000629a8af1bd0c1b1b6d7e447bb779663d7bae8e895e09418bc350e644d7022fa877496f30e2018f5dd1c9683b2715adf000000000000000000000000000000001950185d2574fe0c8277e3f93f59dc5628ec3487911ba9c3194a2f716116ff0bb9a39dde802dcfaa61633ad7657a578f", "Name": "matter_g2_mul_84", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33", "Expected": "000000000000000000000000000000000024c03edb9b54034eacca4b321d51397348c57f406b074b16a9d6215e03f842380f5358f5c095fcf5bf3cabcbabdc260000000000000000000000000000000014e62dc442135d729f65090475fb408ebae132cdf2c2932582af887ed54696f3cd15b163f11285b99e8d8f809aa2e65d000000000000000000000000000000000438a2c99df216c67d92b99d9ee8cbd0e9751e538074d146767bde9675ae3a05bdae051efcdc6bbddeb1b7a8288370ed0000000000000000000000000000000007c462a8f5720e442e1917bf75fc3c3dafab6c39c80d0b93d81d1db4080f6e199be092b4b025e7b02efce4f30d00299a", "Name": "matter_g2_mul_85", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f", "Expected": "000000000000000000000000000000000e8137c15436264b5960c27d0c22be7fc5d56a12f43b3832ad0d7f5abddbaaccefd140e2f7c476b99e6fd9b3a52743600000000000000000000000000000000019ee3caa56f0329a2e2acb8907b3edb21f4eee73e312352796b51282e097f9b10af61805d5c222332888737c7f8e227d0000000000000000000000000000000012cb9c610391940fed7882a5cba08eba4226c36eca8a2ed22fb5e752e0a1a5ec556673e47013258b499268f1de77bdf100000000000000000000000000000000031b769f606fa25b81a982db86a1cd442ed738019e7e64728ecf485cddcc17d9dc271146196178740b9f05f56627b061", "Name": "matter_g2_mul_86", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c", "Expected": "00000000000000000000000000000000080807a0570b628549629d2eeee39de773badaccefb76e01efaecb0ef0356f535d32c3947f0613bc7d847ef8c8778f02000000000000000000000000000000000e8c091ea30465d204ace72015cbef29645206390fd92ba7c4aa0fecae4ecee53c0b06e1fece99511efd8c7e9cff1a8c000000000000000000000000000000000c881c678c94d80164bb3295acf4341fe6c726ca64a1a015c890450e719b85720f41f80369f99ad3e7e3169ede0113e00000000000000000000000000000000008a2fe01a7100afda40091eb0b2b14cd00b7a4d8bb5cf9d9a3847970a94f2035fec7f292c04c38d7e49890e612830aeb", "Name": "matter_g2_mul_87", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1", "Expected": "000000000000000000000000000000000d17f6d9460566d0543df2666d6ade685565e668521a87fabc58148343085415fee92c32907311c9d04713c34bf7690d00000000000000000000000000000000185da28f07b86885031ff5cda913a85b0e4d07673f456ecf2a9f0fd1b21d99e22442f9b705039252d57380b6a42912050000000000000000000000000000000014a4bde5973ef43691b61b3c0f6c2fdb4bcd6ea88e53e2787a7d93ad6e05ee2e69f2799712520f72b3c577ee278008ec000000000000000000000000000000000d92a565b3d8d0fded054a75198b31c521e3223650cdf762fbf7b851f7ac0fc66b8c86c20b905117585704c23b27e7db", "Name": "matter_g2_mul_88", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb", "Expected": "0000000000000000000000000000000008b1ebd753364a5a0a6296ab48b348f91668525c0d5f7edc4f2d29844592f34a209f9e77f94ebb38ba76bdb3f96063ec000000000000000000000000000000001062e0ff0a67372207052e2520d8b2823764a5075c94011afd6c60288e187ec77e08db01c95dfa195f2409b58c9dc4e5000000000000000000000000000000000cc2b87b613d97a716586f371c457fa869c2b8d1fa1cf4b9e8c34bae23e0544752b997df4711d0712ec11d3a9d96ac2600000000000000000000000000000000140eae891c87c2026f0b1293df2bd8ae2dcb0ab3f8de74676f37c905334ac1f53fe4b75511691dcf108fca51abcd524c", "Name": "matter_g2_mul_89", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf", "Expected": "000000000000000000000000000000000276a138edecfc9378be4e241d64cbb48bfa6fd4fb1788f8bda870d5ec8b2132fc9ec888ef84c43a50b7de0527def36800000000000000000000000000000000153e90d52c747859f88223555bc8bc4e8b6fc846fe7028de728a4dfa085c6e350f9f1d12b9dca4ca8e07377648544400000000000000000000000000000000000cef00e7217da6df0a6d85f40be69f154300c423e86e54e513b2491e65002e308445238082da69aa9e5e83b5f4fc17dd0000000000000000000000000000000008da1da2a0d1da9d2158b9408dd9b0eaf414d237b8219fa7661e40c1a88eac2f9735d0dd6ad67b85aab85952369e8287", "Name": "matter_g2_mul_90", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5", "Expected": "000000000000000000000000000000001484993096c210c7bebbc4c0bda24b44a70e982b2528215c0e8578ea55f1181472758caf935aa0a3d6820cdad753e2f90000000000000000000000000000000011802324a6e03c3174bbe7261ecf3812c1a97e1be27269214f232274a3bf82775d47c5fdd70fe1c57155068b296d394200000000000000000000000000000000050f43c874c1cfb5fda81059cb7b4808492632fa20369dcfb611e503ded81a49dacff253e31d7e27ee84bab79e3c5d53000000000000000000000000000000000ef945b6f210fb09bf0ad5bbd4b5a6630f43304ddcb396807c967eb5146741f7432bfdcbd7e5f3d29917781efb62e6ff", "Name": "matter_g2_mul_91", + "Gas": 55000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e", "Expected": "00000000000000000000000000000000028233bf12e8dbd8510f119be30ea1fc13b755c6ee3ca2a3637a3bf8f73776c9d1fe231b713396ffc579ef9320a05b150000000000000000000000000000000018e7c00b8047d64ca0c5df54486439c5fb3d1414c2f71cf8a3ed591b7c45bf18b37473daeeadcb625eda638885ddb9870000000000000000000000000000000018b89c9b6bf9ece36f1eac08fc35ffc9f7f964a0a9b19d495ae1361fb4bc98aef8770efb47d9961aff694b878d659818000000000000000000000000000000000eb2fda2c29c6761e35ca4c9772bb232ea0d297582af4f50ef76c0b74fefd414b535e356c069f54ef5224225e95be6e7", "Name": "matter_g2_mul_92", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5", "Expected": "000000000000000000000000000000001239935827fb2a269ab064a3ae2bff2555f89bb3a71a47ae815ef755fc1363a89d20326855cfdd0e13f6c85f727bbe120000000000000000000000000000000012fbba047478b5f5b07a582200271a0c331d6f76864f9b6c6ef8ae6b0965eda481eddaf72c7a887b21719164c633d39600000000000000000000000000000000017eb4353b413437244983554a639a9253d105395ff9652504df7700d879cd9a32d5f0824b1eaa532bcf2fea34f8f08800000000000000000000000000000000054ea45475c01ea0557fd143b21c7bdcab6d287bf6bf4f88b6fb06e02ac6fc5ba96f323bb1fda3a1c4d8f42d01d267b2", "Name": "matter_g2_mul_93", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0", "Expected": "0000000000000000000000000000000015a145e379b7ecf4566a039b753f91e8ad75d9e9c9a20725ce34a900eb9a1bdf66cabee2100208d7792a963d1fb8c02f0000000000000000000000000000000007f0ca14fc4e34bbdf5008d632dd112c7368e037ce019b7c4ec412000ac02302c85ae64f9ab495361fa5b620e46420aa0000000000000000000000000000000017c00a08bba18426dda40e773d79733030b5b3b199a62436ed06b773fd1f10688e8af00e8a223cdf242bd1ebbedbf634000000000000000000000000000000000a17365cd9f7655793682b72e342227048da0cff88f6ace33ddab548ba126017e4b7f7439373a893e3b5803e662814b8", "Name": "matter_g2_mul_94", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", "Expected": "000000000000000000000000000000000081b4dc78b74250a82da9d803876add659411cfb467860b2ac6f0f68929d6377deb71d6acc9ea8fc8c1286b8f92056e0000000000000000000000000000000002c5fde71346a255ee9dc896f654eb2e0c66f4cb4c51541d2bbccf2463ecf0085a22b9d2bdc5bef39d80c4477824f116000000000000000000000000000000000ebda0cd8bf6ac7e86a1bdbe44ed1e15f8ffa1fff92afd67fb564306882f35037b61cf0d93f278f15149c04a2e83041f000000000000000000000000000000000fc38aa811f5ec015f10a99bf175f1479d4983c9d2180a5e3da88b4e9b62ef50560ff0a6c2fb7bda4c46c54551f8390e", "Name": "matter_g2_mul_95", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4", "Expected": "0000000000000000000000000000000007b46fcfb2cd8efe32754306ff2f503d7434168c1c3cbd7c80470cc5a5c8bda10a80bfc0129da349724d2d6431c5ac90000000000000000000000000000000000e1078f4f4ca993d90accbfc036219507bd22d00930ffcfe1227780c00914fcff845698b2541510daf59cc83d8b947e7000000000000000000000000000000000b7c6d9951570e685d3a71b19a38f5485f974f85fe8cd4b4c196d33a18750b278b6d374483d81dc3e15c9b8b9b5dfdd6000000000000000000000000000000001003a239ea4a2f213f0f646bdb62cbe4f98cfaf7298d8b2e0eaa07bf3f939e779caab5ffa0033467c5b297166df657d7", "Name": "matter_g2_mul_96", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125", "Expected": "0000000000000000000000000000000000ea29b1e059560fec21c3692d4e632a45c88a807c953fa23dbedb271b049d7fc717333b498ed12573a896f872e795dc000000000000000000000000000000000de0d10c47df92010a6635e3403dd6e91a1bf35bfcae82c1008998e86aa2d18a6cfd3f2f1207fde3bb39b723ec4d3ca60000000000000000000000000000000005e2aef9cd37430b15e5e76b2c7870630d255f630c12e865caefe308a39833e00319406746dbb2af3ed32135e91eed49000000000000000000000000000000000c229fad41b0d27ad7b5db33188fa70b97f22e323e429ef65fcf98f5339e908c31df8859b863356e0fc90538c5c49cf2", "Name": "matter_g2_mul_97", + "Gas": 55000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b", "Expected": "000000000000000000000000000000000444a00cfd258bd46f659b09eef17be9929008d3d1c65e46cdc762eeaa2f0b52abfd636e6094e21983fad8171194c71a00000000000000000000000000000000090833e68614be5bf298e04e44527480cb35128bbdecae15eb95d6931a718f66869ddb68352130b4dd8a921ab3f26d080000000000000000000000000000000000994015b1b55340c3839d48320d178b2ffaa0bbff038f7aa63d4dff41a217582fae9613bc537fdeac8d0670c0cf479a000000000000000000000000000000000fc486e2a1680c10ca28d4c3bb22dbccc9572036512645bf868e7693ae4591569c973f9ea26342a573e23a06c2fb4b70", "Name": "matter_g2_mul_98", + "Gas": 55000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf", "Expected": "000000000000000000000000000000001375bd5ee66c330796bd8381a26cefa3f40f8cc8de42d4d59a7adbcd3852e6d632422e6ad9a06a6e497b23b17b1df87500000000000000000000000000000000165d8e7be17ecae9bf51a773da705aea42536d0fa3a2206267da50451f5104ee241811dd0e6710a80c38df77b126c009000000000000000000000000000000001559572407aff34969f83c394d2b095a7ae9f53a8e6c923910f256bb87b6ec076fa6acb85465102fd24d34031f88f7510000000000000000000000000000000015ff9ba89b55ef75f63732dec1e64106d7a912a6657fcc970dd011a03b5364117cca46d6cbafbc0c5049db10fa83fe6d", "Name": "matter_g2_mul_99", + "Gas": 55000, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsG2MultiExp.json b/core/vm/testdata/precompiles/blsG2MultiExp.json index 639680468d..b5b63625ac 100644 --- a/core/vm/testdata/precompiles/blsG2MultiExp.json +++ b/core/vm/testdata/precompiles/blsG2MultiExp.json @@ -3,618 +3,721 @@ "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000011", "Expected": "000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d536944", "Name": "bls_g2multiexp_single", + "Gas": 66000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000019d5f05b4f134bb37d89a03e87c8b729e6bdc062f3ae0ddc5265b270e40a6a5691f51ff60b764ea760651caf395101840000000000000000000000000000000015532df6a12b7c160a0831ef8321b18feb6ce7997c0718b205873608085be3afeec5b5d5251a0f85f7f5b7271271e0660000000000000000000000000000000004623ac0df1e019d337dc9488c17ef9e214dc33c63f96a90fea288e836dbd85079cb3cec42ae693e9c16af3c3204d86e0000000000000000000000000000000011ba77f71923c1b6a711a48fa4085c4885290079448a4b597030cc84aa14647136513cec6d11c4453ca74e906bbca1e1000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000176a7158b310c9ff1bfc21b81903de99c90440792ebe6d9637652ee34acf53b43c2f31738bbc96d71dcadbbf0e3190af000000000000000000000000000000000a592641967934a97e012f7d6412c4f6ff0f177a1b466b9b49c9deb7498decc80d0c809448aa9fa6fbbb6f537515703000000000000000000000000000000000031d84356ef619e688a10247f122e1aa0d3def3e35f94043f64c634198421487ca96af5f0160384bba92bd5494506c4d000000000000000000000000000000000db8fefe735779489c957785fa8e45d24e086ef0c2aba2e3adba888f0aeee51385a82898524c443f017ee40be635048c0000000000000000000000000000000000000000000000000000000000000034", "Expected": "00000000000000000000000000000000158d8ef3d5cdc8a1b5ce170f6eeadec450ca05952ea7457a638b8ff8b687c047799eb3dd89c2e3c6ca6c29290b64f5ab000000000000000000000000000000000807d135b6b007a101e97f5875e233b41f12bd2ffd77fe1195418a73a4c061248118ea1049aeea44750cd5ec83bcc1ae000000000000000000000000000000000f04136354f45a85a53fb68527bc8fbc7e8c1a0056878012b548a97bfdabcbd3fb8eb3ff187fbe65e1ce233afd2825050000000000000000000000000000000007b15428114e2ea094ba1e64df4c244f80aa2f75bbbf21a407bc84e80bf2a5ad787d02ae8a90cc1c137f0d898edb1684", "Name": "bls_g2multiexp_multiple", + "Gas": 126060, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3000000000000000000000000000000000000000000000000000000000000205900000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e84900000000000000000000000000000000000000000000000000000000000b7fa3000000000000000000000000000000000e7a30979a8853a077454eb63b8dcee75f106221b262886bb8e01b0abb043368da82f60899cc1412e33e4120195fc55700000000000000000000000000000000070227d3f13684fdb7ce31b8065ba3acb35f7bde6fe2ddfefa359f8b35d08a9ab9537b43e24f4ffb720b5a0bda2a82f2000000000000000000000000000000000701377cb7da22789d032737eabcea2b2eee6bb4634c4365864511a43c2caad50422993ccd3e99636eb8a5f189454b18000000000000000000000000000000000782c14e2c4ee61cbe7be6e462a66b2e3509f42d53ff333efc9bfe9a00307cd2f68b007606446d98a75fb808a405d8b90000000000000000000000000000000000000000000000000000000004165ef1000000000000000000000000000000000411a5de6730ffece671a9f21d65028cc0f1102378de124562cb1ff49db6f004fcd14d683024b0548eff3d1468df26880000000000000000000000000000000000fb837804dba8213329db46608b6c121d973363c1234a86dd183baff112709cf97096c5e9a1a770ee9d7dc641a894d60000000000000000000000000000000019b5e8f5d4a72f2b75811ac084a7f814317360bac52f6aab15eed416b4ef9938e0bdc4865cc2c4d0fd947e7c6925fd1400000000000000000000000000000000093567b4228be17ee62d11a254edd041ee4b953bffb8b8c7f925bd6662b4298bac2822b446f5b5de3b893e1be5aa49860000000000000000000000000000000000000000000000000000000173f3bfab0000000000000000000000000000000019e384121b7d70927c49e6d044fd8517c36bc6ed2813a8956dd64f049869e8a77f7e46930240e6984abe26fa6a89658f0000000000000000000000000000000003f4b4e761936d90fd5f55f99087138a07a69755ad4a46e4dd1c2cfe6d11371e1cc033111a0595e3bba98d0f538db4510000000000000000000000000000000017a31a4fccfb5f768a2157517c77a4f8aaf0dee8f260d96e02e1175a8754d09600923beae02a019afc327b65a2fdbbfc00000000000000000000000000000000088bb5832f4a4a452edda646ebaa2853a54205d56329960b44b2450070734724a74daaa401879bad142132316e9b34010000000000000000000000000000000000000000000000000000008437a521c900000000000000000000000000000000049cd1dbb2d2c3581e54c088135fef36505a6823d61b859437bfc79b617030dc8b40e32bad1fa85b9c0f368af6d38d3c000000000000000000000000000000000d0273f6bf31ed37c3b8d68083ec3d8e20b5f2cc170fa24b9b5be35b34ed013f9a921f1cad1644d4bdb14674247234c80000000000000000000000000000000008b7ae4dbf802c17a6648842922c9467e460a71c88d393ee7af356da123a2f3619e80c3bdcc8e2b1da52f8cd9913ccdd0000000000000000000000000000000005ecf93654b7a1885695aaeeb7caf41b0239dc45e1022be55d37111af2aecef87799638bec572de86a7437898efa702000000000000000000000000000000000000000000000000000002effc7b302730000000000000000000000000000000002142a58bae275564a6d63cb6bd6266ca66bef07a6ab8ca37b9d0ba2d4effbccfd89c169649f7d0e8a3eb006846579ad0000000000000000000000000000000012be651a5fa620340d418834526d37a8c932652345400b4cd9d43c8f41c080f41a6d9558118ebeab9d4268bb73e850e10000000000000000000000000000000015f4b235c209d89ce833f8f296e4cfb748e8abce6990ce1a5a914b9416c08e0d3a26db89625915c821a5f152b7fa592e0000000000000000000000000000000006fcacb3ee6650a1044852d61c9c20bedc8ee90aad97de8e24670a9ef57483e678db11dd95428915088d76e30cb01a370000000000000000000000000000000000000000000000000010b4ebfca1dee100000000000000000000000000000000018405e4b67f957b6465ead9f5afc47832d45643dc3aa03af7314c6cf980fa23dd3bb8db3358693ad06011f6a6b1a5ff000000000000000000000000000000000c48e0d4f9404ae0a7f10774c55a9e838bb09d3bae85b5eaa6b16b0f4dc2354368117f3799c37f3f7126d8b54d3f83930000000000000000000000000000000007e61f4ec5bc9e2cc8ca471ce4ed40e729b1790cd2c0d9c1cb50e615ec7f346636e77e1cf632c881c07c5385898607620000000000000000000000000000000011dfaf9281901dd356fc5dfece21898a93d9ad9e4e246dd6e18d3ee46d58ab7e77401a3e8d04057e5638ed74fb95688100000000000000000000000000000000000000000000000005f04fe2cd8a39fb000000000000000000000000000000001796abe0d9e4a703962be528e6a5cb65c60725886f925db0e2a89107ec248bb39fa332bc63bd91d28ae66e0dfce8f754000000000000000000000000000000000fb665f5a7559cb0fa1300048a0e6f1ab5547226e86f8e752dd13c28eda4168492e3d3bf2f8a6b230dd57f79b1afa9910000000000000000000000000000000003422dbbe4a06a4c6c9fdf35e54f74b4ab1528abb7249e99898e6fd7affebc7aef95bf82d328dc01d63c25f6a735c35d0000000000000000000000000000000010aa5504b469427eb3584a286191149f5c3c5a745f338278dd95337cd2336d3c4e7532d98eb189fa543824953e7c1c170000000000000000000000000000000000000000000000021c6c659f10229c390000000000000000000000000000000009303f04d568e289a35102b6df883d5ed620355c0eb5d02236718cdaf99fba6e19ef5cee2996268eb9a53ae1ee09bce3000000000000000000000000000000000190be857d602284393305bfe0a29e29a6982ed3f04ccaabafb7e59cdc7eda85c22bc3e8690355c7a0fb7590ae40f1b00000000000000000000000000000000016efd497a0c5c6b59a1fdf2b590eb67a7da8cbe72f49084e7050783ff12a783cad1859e1a0b0ec8ff784c703617670330000000000000000000000000000000017a957ea4d53f4fc8412cb015ae91b38445cdb3e7078d875c465c941e0d9a852c78d90b31b6b6010efe8bd5117e831630000000000000000000000000000000000000000000000c01a881f8abc4d8843000000000000000000000000000000000173ed58056bec9874464d3f23c3e7d3d429d6c8a167fc7f39368830eca839d0eb8260d64ca823f6c785c71f85893d8400000000000000000000000000000000123372d7d4c91a249df8f3e4f8e669087b252ab5d8cf2529a87e4ed3622e4158cf17dc44b473d5debd273261383e8a0f0000000000000000000000000000000000c500eb55ab86381a1725f339f686c7e38ce9113493736f57e999badc661b5b8494d220ded0711e841228a389abdb820000000000000000000000000000000010a4025d823c4262367c53f50e67cffa046e4a1e7c69ff30373772e49ecb310de3b313d83cc41f40a00205722f233e270000000000000000000000000000000000000000000044496e633650ef8f6fd100000000000000000000000000000000152110e866f1a6e8c5348f6e005dbd93de671b7d0fbfa04d6614bcdd27a3cb2a70f0deacb3608ba95226268481a0be7c000000000000000000000000000000000bf78a97086750eb166986ed8e428ca1d23ae3bbf8b2ee67451d7dd84445311e8bc8ab558b0bc008199f577195fc39b7000000000000000000000000000000000845be51ad0d708657bfb0da8eec64cd7779c50d90b59a3ac6a2045cad0561d654af9a84dd105cea5409d2adf286b561000000000000000000000000000000000a298f69fd652551e12219252baacab101768fc6651309450e49c7d3bb52b7547f218d12de64961aa7f059025b8e0cb500000000000000000000000000000000000000000018461a3d444ec527fcbf4b000000000000000000000000000000000027513925b419f6c581788578379995290ab9478e08ecd1999d5e1a05c58144d2f9f06fb8c7fd1586f3ef6a973a3ed7000000000000000000000000000000001292b2ce751f6f859ec7882e14083eac9841b035f9d5ed938a81579dbce07dec2c0202b7f6b25226831cd9c578e893d00000000000000000000000000000000017f36da49414d7706209d52840250eea6f33970fd7eac448ee122f24c62f6a6e09757aa29761160be0f65ba3ce7a153a00000000000000000000000000000000086d471f958f3ff679805751b183fb6310e871ba72bbdefd59c58e95ea62de0820d5affe601757e318abaa5a0c2715bd000000000000000000000000000000000000000008a0eb53c748001536d7ffa900000000000000000000000000000000090721a089bbbb130c21a529be0ede9271a91a2dde9cb2a8e091a19fd2c0a40c390ac2bda8304085c2d6e38e520eae44000000000000000000000000000000000cc64109c67b342b6dbcf86cb60fca7ad378ed6398d89076ed108685c57a07d26e40ed3d5c4b3560b21e519db5875d49000000000000000000000000000000000b0ddd488f5a6f61f087cdbf011b50209a4460c8aa8c5f48c0b30d9cf6cf24259f4e7badc42e1b7a33352949ae566fc100000000000000000000000000000000038430e8db04d205d81aa1632d23919c06f89260c7ac5850bd8b780f8388e53db3a3ddfe98cc55d1c686e582f85b0c8900000000000000000000000000000000000000031133a6c7d698078a7ec7e113000000000000000000000000000000001800ecc167bb714100f31e7610cd3fd010ca299b394c01b1a89afd11b051e92989f6336db5e6d3212f6b04673526d83900000000000000000000000000000000070401d9bba01c0445e0a682406b099f21d16d9c348cc97156769084055ca328a145c134b8c8b58f019d62882b2965de000000000000000000000000000000000287f071bda99b0318e386b27a492a6823a9664084b12acddeda34cb53f827a362ba97c0e652c37bd9d6023041d8c8d8000000000000000000000000000000000fa708ca7dd917541cd02281e525d3367b5ebf5e9353103e1f83f3b894d03d8be7e4d819c123492788855d1fdb63f2e000000000000000000000000000000000000001171d5c4909480aae3b110d01c1000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d53694400000000000000000000000000000000000063376fcdf64c9bcbeeff0f9f9f9b0000000000000000000000000000000004b6570b4a6affe97649b0dd7a0ad0df160b37c332a8a7348dd3994cc6b1eb65623b4a9f0a3f320e7278844e261546530000000000000000000000000000000005f8fb4cf5e5313f403f15c59c79b9cebaec78291f2053c49d6427f40f2db2aa659d3a8fed7c7b07b7a5680c7b95ab5800000000000000000000000000000000045cba5ec3fa9acd1b11e1f28a01ebc028f89f96f814513453c553f58785baca8abd4150f334b405fabb925b71f4f4dd0000000000000000000000000000000013daf00b8f53af776c2e8c08d55d164aa15027611188e294230477dc1c926102088f0451222fd2eff9802db8b884ab9c00000000000000000000000000000000002344b4be368d3b617df4aa8dbdbc190000000000000000000000000000000002b29192945df0a74eed138e431962f1d39978202d247335ffbf29d8a02e982c69e96b58d7d92528baf5c422ed633f1f000000000000000000000000000000000d52c7a82fece99279de7a49439c0ff8463a637cc6003320275d69549442c95184fd75ee5e7122e5575af7432e5159290000000000000000000000000000000006ddbaad6cc16c9e62b0da9ab0196dffe92253fcfb2df9aa2076d3f16b3284997d6558cc4432d2aa1705452c4e951e6e00000000000000000000000000000000175f906a99c9d65c4647807879e5eb781532db184d28a326ef9691f8738af067b6a80147bd69327d219fad7c850a7545000000000000000000000000000000000c896c3f9d64341ba7c5f8a06271dce3000000000000000000000000000000000c86c92c9598dde7e6fc5e05d70a34c7a14cff5f400f33cf6cc26e6bf6d9a0bbc421c00f3360721f51974d76be43bd38000000000000000000000000000000001137d93502ef32471f47890a181d7823b3a86dbfcadcc930ae53952f528d617e742a52e4f243c615cc28163dc31bd80600000000000000000000000000000000088f7f8bcbc6dfcc8005b8308cd4780d574d8530e95e7831e52eb2c9a88b846852e111a8389e3d3a67accf78b08326d200000000000000000000000000000000149e43fc675dd3bde8b89cfeb29456f130bbf674cea0266bd1b2e7de23f9a7294096327b452728411ca58acc949777fa0000000000000000000000000000000474d97a9cf29e85d4a35f6102fe7984b100000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f00000000000000000000000000000195894e95ca3e59929612e77c1075322aeb00000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000009027ceef3ee429d71b58b84919d9a8d5418900000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab1600000000000000000000000000333e268f0b5b1adf76b88981fc305f03ce4bb30000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000123717b4d909628d6f3398e134a531c65a54e8a10000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000679956d49265608468757580db6b8b1821c2eb13b", "Expected": "000000000000000000000000000000000728c5e6e69b9103d82358cb6ba3a45a677df1c3eb3cdccf694fd71cee94f1e591b8021b0eef638cd9a1d878937b5b2d000000000000000000000000000000000ba9bcf9ccef956f2af8dc4c3fbf1cc8f3f284b04ae8710af6ef4fb36301254c777d4461858fb38fdeeb72c0d8589af5000000000000000000000000000000000224b80a57d30bce4c752664f3b5b5e3443aefa6d4e95dc334821f754b8b8d8fda4e73d03cbd4070d43b18324a686b500000000000000000000000000000000016909a02214c6c0f6682895aa99cf6cf0a22eab6f0b574437ef9c36e9df32ac3b8c5adb9f6b8827df0ccf51b16f824df", "Name": "bls_g2multiexp_larger", + "Gas": 409750, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746", "Expected": "00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca20000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b300000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0", "Name": "matter_g2_multiexp_0", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c520000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e900000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a60000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c", "Expected": "000000000000000000000000000000000153da66acafe91b6f13cd739ed3342197310e4824e7aef2e3414654c2678b8d09b296c3f928f3cc489893420031ab800000000000000000000000000000000010f501a96b86343a7c8d8c1250577cc9be6ffec81b5175ed07bd14988c5bbf7f2f3e7111df7d941d0cd267ea191d6ac70000000000000000000000000000000015e0d88894f7f83aacb6710f6c03ae60db8844dd3beec160fdb1df746b1f38a5e23def0893a0b39bee47c97af6535fcb000000000000000000000000000000000bcc275115e87f2f88c4afe8bf4faed46e6ad0c0357884356a26120591ba283f06b464c4853217865b1d2301965f2bd4", "Name": "matter_g2_multiexp_1", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c4250000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556", "Expected": "0000000000000000000000000000000013b49054c3957d1e77ba2dc3ef75775bab9f0e9f76b33ff22e244e897b8ab80ee0749c81eceea259e99b5d2a72251e5f0000000000000000000000000000000012e017e4354ef86f73ec51921cbfdd01e3113cff044a049bdd34e36401712420790cf718bd28afa280ad12104c1851ed00000000000000000000000000000000097f28bee5d903e3c6de14e834d5beea5c847c3106742978e586ba7e913f8b631a69c473aa10e19df9795ebfa3ea6a98000000000000000000000000000000001953493daf65b974b549bb98e735da44b543d6fcfd97176fdc7f6f03617d90e6bb952a607fa8e5791df5dc1c9bba2286", "Name": "matter_g2_multiexp_2", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f85200000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da000000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd450000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a", "Expected": "0000000000000000000000000000000000fada9f43b29abe15693d047adc277814cb94694cab3be56b92312ab7666649b8e9d92aad81f8e487be0f74b9ce8c250000000000000000000000000000000007f6891775811a325cd7f548011ad4c705ca0327ea0484d938ce061c913a7ee6978293c3258c4b865d5c2325816c39990000000000000000000000000000000016761f859beb90ea03aa35e954d112da02daa8e76de80297afde9c29cbfe8ef4d42dad535917685a99b2a91b1f952ae50000000000000000000000000000000012a4f24ab88341dfb8a60c19993b8abea96dbd7033d3686c40903728b4fd4da7d07961f2584b51e9e6c05976d555757e", "Name": "matter_g2_multiexp_3", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e36160000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd00000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de070000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b60000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6", "Expected": "000000000000000000000000000000000b219032a2461a5fd1e43361c46beeae92e30247acadcdd241692abe81691c295ba38a1f0a2a45ae76b1b95d7d0fdc460000000000000000000000000000000016905f64e581aafe928520adc27c24703e7adeb36dfbb416a159cdb9b9a26c9cef0821ccf52f5ea5253b7c9d78769e9d0000000000000000000000000000000015cfff195b2123aa140f963628c41deaf19dfff44d26a38de4547c3d15edef10fe9f65b1802dc374d7ba8fb62117c8880000000000000000000000000000000018dc725cc8d8919a7414b7866fdc54c4467b0f87cf99fc9b36cd65c0ec526e32649f9c57495657a93487f1f2f5769168", "Name": "matter_g2_multiexp_4", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b3300000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f10000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be500000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c00000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169", "Expected": "0000000000000000000000000000000007638fa4e8823dacb40ece440f8f1e57cc5c3851f94357a5325207db92380dd57a7c8709e4d00b670e8af1b77368285a0000000000000000000000000000000005b66a6e6b13ea0eb367a61ffe7c620d9edf5563cb4cc0cdfa68b99d9691cf9a40efd967c1e880238eec313eaf4c92ad0000000000000000000000000000000004f7156c69ea88a71a0af2922d1caca24055d40df058eef02bbf95d864156f62fb0e17d9fccd193840c36ad8449bb4f7000000000000000000000000000000000b8f46fd695c5d96d939d42c65c3b709d32f134710a67909dc4bb43d752521a8d4f0465d0590f30f06ce42bf5f8cac28", "Name": "matter_g2_multiexp_5", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e12500000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000196f78b64fcc342ba4f4edf34a3080ec950532a5de21a875dd061f09351def5ba3b85745a561e38117a14c20d33a14610000000000000000000000000000000003929c2bc55f323d57dc3529bcf6644e61c941b72b424d69969c1cde7a804d157045bbf6d5b79a3e6686509e11ecdac0000000000000000000000000000000000f6b659818510cde463c52cf00bd99da045c80af4d5cd0e55f9bdd81f34169fe869c519f37a98ff20c56db554469087600000000000000000000000000000000129709e97757724e765f6600c2b1928286efab55ec8d16876a2a3210bf9d31cc5425265d0576a2d5469cbd9a6c8c27c012adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c60000000000000000000000000000000009f5f167c9b61a0ef76415fcceff04f3fa57071c2d79f443ef8a7e6049cb1352f650ebd8f358904bb432d42772c29afd000000000000000000000000000000001524a875d73e03c53b92465bafca582479110611bac6a98fc7d76966e9781308a10cb202289c0776cf5c36515733ccf900000000000000000000000000000000002b1acace94a6fe196b217a9aff413fe0bcb55122ce9e344942843e5afba0d5f2cd0bba14c9c8cb9dd1c3e9024918fc0000000000000000000000000000000018e4f85c7663e596182603862adb559635fdf16ba35fbce7278680ea289f871bcf6755d85654b2a37ae77a37e77ba06ed1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000018ee4b4855f866781f38a618c2fe4214c63034620ea5b72361079b0a5c2b2d6fb9ea73fa202db3a2678cf07219cde81100000000000000000000000000000000180870513afef93870ca64e2363fa1aa43a599db97f3b807ada1c25ae331c80b8ead5cd69b6f5a65a083606591de90ff0000000000000000000000000000000010afd546703baa35a9eabaeb45d301bd5be115557bbb4ff2a0e493668ee790e947eeafcaa923f62ca00b8e635994e39b000000000000000000000000000000001089996b218aacde4ccfca4d2f66d79fe161d962baaf2d6696e1a76ea40af4ae7195e8cf9f6417ffd054f20b65ddfb104c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d30000000000000000000000000000000011757ad74a3fb341c8eb6862978ab3fb5e8cfc8fdbda7d82756532a890d61919cce931872ff339843805e00d8c62ec4200000000000000000000000000000000060783a06e93e82cb08e5dc1aa31202ba11676511300e186ae8e45248b7fdec3b7d5b6849f8b79b8f78ad84f36218544000000000000000000000000000000000ecfd8ab18066fe3408fd20f2a4478156e9a19a09b58da76486c9f6a013d861960b6b99bf49cbecfa8c9d01d5615c1bc000000000000000000000000000000000b45709845d35d7b560745375df79fb95df15e85b96cc1b98cc832c74621339c609018d153bff93f2f5493a52b7326073c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd0000000000000000000000000000000003f9de90222619216852356052e9819d7c6e8ff91e0c6f1d8cec832770ed9001db4569fbf579ab16964d76ae7d1b89e900000000000000000000000000000000010b7cf8f0d283cc22942ed73c599115763dcfc1ddc98d87979fc3dce2f33ca3531cc2909d94f86736dda2a4e94a4f0c000000000000000000000000000000000b0aa4d947644cbc7df8d1927cdec66a68862e5a806e25554f27cc1a3701f429fc7097497ad0419e21cc403b472c8ea900000000000000000000000000000000146270ecb66e1763437b824f2ae122f72f20eb93fb30474691a0a192ceb932b1dee111fa44954075335ab360d31ee68d61301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000000c977cb8de4b6e2e33d916f74eb4e42f089d22b54b59fac9aab0e4cafc8aa2b0f8c55d7251662b3499ea140e322dbbff00000000000000000000000000000000106944a9c2d2ecd08e109de29095f3460128bb751051a1f079acb58b6a60b0bb5f52e63d47b688f4a382a77c3b039eb5000000000000000000000000000000000d2f8be1c78995d54fbccab61f816b6ec52dd19aee6aeedc0e4bde2898b2d07c2925da0440a38c4c965a823fff10389f00000000000000000000000000000000183b5d15b243cc5d9584842ab1a0a1e01ad87268728d72aa8c0d7ec6e7069063a11fdd1525d2b30b35e4568da7c44c5495cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c000000000000000000000000000000000f65ad4c21fddadcc49a8f7bc281d2b7901707f51a67122179fe97da46ea5e1bc6e70d68eb4eb6776307510a67e972620000000000000000000000000000000009003dc68cb0cdec4a502436718f066348f1957ae65ecca8d32c5fd776215cb9a098c0ffe56c92d79dd68d251f49f13e00000000000000000000000000000000038ecf0bb98ff2e84b388c58059ba0de0cff3d5881ecf01d668495ce81b76b00323c665ba88309af5552b7950cc8c08f000000000000000000000000000000001924aa0f460659f552458fb469467a2925fcb2420d4fa6249310456853be3d08bd5c37a3f0a9d6e94e434391d20cccedfb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e00000000000000000000000000000000189c3ee691387fbbcffdb147c880218c3e5c0bf78c44461ac1bd3ecd5d4b85225e46cdb068049607fedfcca14882e289000000000000000000000000000000000260efc08531083db2839d1413c90968e87d79bc1a2c730f0020e40beb92e84b73ef43e80f7c61e1a30c0cee11b3cb370000000000000000000000000000000005c852ca0aae2c575c65ef18b624f50a32c007d299f24a3ec6cacbcef1d6e3bdba9650fd7d639bdc60a3e107ee9c013c000000000000000000000000000000000321c01a9de69d6b89db4ed88dd48261ee28facc5e26511fb2833fa45edfb58051c8c3ce9501e8b4c3cab9c456705889bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b8600000000000000000000000000000000183d50635b22e4d620130e0d4008e3bfffae5dadd7e34f4496899ca54eb4d9e3e95c54ae1d9664609c58d02ee5eff65500000000000000000000000000000000029e3b4496a379464302b1476a4549db371f5d6721704b1d6bd35e2344d7679f8a61a0c3b12f287fd86fd247f9652cea0000000000000000000000000000000012c6a3793fd23e955708f5aeb4d6efb670d25a38a67813ecc72f899cd5f926ab7ef198bf6d591328383aaf54f756c66b000000000000000000000000000000001914d3e4b6ea96bb91333468fe8f3bb74636e9a4f2ed198e9ff01b49ba02791d5bd63224f6a38538aceb777168bef688e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d250000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0bd4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b0480000000000000000000000000000000003760958eac45397eca1a1d951a80265a728dc3c584f7dae111e7ce04248885321b69b334b00cdb0334a362676c2d32f000000000000000000000000000000001031e4a63129ec40da5fe9dacfe148a67662eaa00e1fd5c30336462371c167348a10e50f4dc18469a1a6b76485f77e12000000000000000000000000000000001412dbf993c557323426b486f18a91d16b4baa2c497b30fb332a710ac901c96d46a577d04ea87afb08258aa6d204a1c9000000000000000000000000000000000da015ca09ac0c3245c090f39852218f46fea62198fba35ebc4a7f14887943c3bd1bbbfbfa300611e45f419b33988e404c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d30000000000000000000000000000000015c9121f72e2425cc8aa4c878907628dfe75a903b7f756b9e13728372cba598859d20a92a8297d95e1fbe25fd1cd968300000000000000000000000000000000025a3faebfa53918efa733949f914be08b791794bd4963f0c3fd78df48b14ad214374b08299327575c0731b54eafed76000000000000000000000000000000000771782ecd9980da521618af2f9eb55d91d67b20ba615c7b3cb1a48d483ca405fe99a1cdd17e4dc7aeffce586987d41900000000000000000000000000000000136000da90a76d538f336608ce877be943025b4c8bf15880ea9c1c001c20c954292d362dac9783b7bf66b8d51ddaf0f2a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae", "Expected": "0000000000000000000000000000000014cb24001bd933b1d5866cc3de9f4b8479fe23e4fc26dd210f9d06e7a05449b9f5ac4e2f48fb847599f625824336bf1e00000000000000000000000000000000033fdb2e899427f1cb9757022c5b614f08c64b53583486148b7431311a6f15aea3b968913fd5f3e9b624705351074be600000000000000000000000000000000035420be9c7ae3203d0dec61ecea70e22e62f50368be870e74f9a7349453647a7f61d2a42cec6522164cca0c7081d4de000000000000000000000000000000000fea43388e9f6e31d419c7f9fbb9839b4cec04163a7b401d8f7de73a4560fbfef4e272f1db9c9d5b37693378f139452a", "Name": "matter_g2_multiexp_6", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000039dc2b60389f6c893c44072f4db23e7df4c2f299d6b70b70784d9370d9ff8e5413872c227074d429db999d30dc9499a000000000000000000000000000000001702273db356abe7a3f91a9fe4bf56584f13de4069a91daa6c0b552089bef60da98d32c615aa5610842dd8a507f9477c00000000000000000000000000000000095285e8c508ff12da79e16e0391dadbe9a823c586a049e729596864c3cae117305c05f009f9e8ac032abaec8a63f8de00000000000000000000000000000000078fc70e926decf7aa4c2e4b395e88f367757dc47a4cedcd5e632c456a4c160393837196af474948ce6ad53f830ce8aeb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff000000000000000000000000000000000cc3cb5e7b033cff3e5cb01ba29ce8e9f4a93e836ddea7d417f7b07ba8aa71a0efae2e1d7a8ec70bdff12d84d229245200000000000000000000000000000000019ce3c830505324b9bc7cda1fbb328150d71310f06a8424dba861d67a7bc0428beaaf697646d22cae9e00477cc8066f000000000000000000000000000000000f6ff67efefa5636b104a0351c90fd3e89a32b8a9beb0d123d3d6ae42eb5e8bbc19c7a972e27762daf852259c65fce6f0000000000000000000000000000000018d98c43fe5b13b701749f4a5dc25f0e713d241d573639fcc73429226bb131d448283338a909670066045c65789bf9e7064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb3346100000000000000000000000000000000003cf82958d68429503265dcc7d88a3763cca32baefe3c8d32564cf30e8e6b8255d4a9f6a76bce1da473b50287deda74000000000000000000000000000000000bfa9cde6c06b2a2ff8f877ca90b3827d0aa0408c4ccbed23ad461433dad71017d4dd387f49c5febdeafa17d06ba784e000000000000000000000000000000001770fe70513533d91c83449ea52964cd8b449aa81f71e71995db5b19ceddef18e2919439c80e10086e670be669696e4f00000000000000000000000000000000194c20491c9d5ed827cd9d370b9bbec55e4a7b1c34ddd1d80201e7019d9487a747b4fa57b480dbdd09af73aa4f5fa0e9a3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca000000000000000000000000000000000a79d9e0ff43249ff54526c5e1cd55a9bce93adf272508871326c933d526602dc9dae5b6f129a0f1c38139ed1c39be5c000000000000000000000000000000001458b554e0387c1ddb9dee9f4e9fba9c81c15807f496442f4b7210267912b9439a19f95dc80a1e09a0e5cfe750f43c8800000000000000000000000000000000012c06b19ed4e8d5d1b9fed56bc5bdaa3bf0112db997e33aa14899d53e1bddd6aa91dce7e9d25473b66b8578d398981f0000000000000000000000000000000015369b2228e728894f2fd7c2d8c41ac3550da4f297de445cc0f0ef7134c478f526987643cb5408a0bbb79f5f983c085ebd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000016649a8231407074af5ffa93f9db5a2ddce8785be8ee77149602d6afa24ab30b26d2f74bdb5f7464333924a817e242e50000000000000000000000000000000001b990f5ed0b23e113042ff004236646c6eacacd99d1d73fe0c3d9351ce8d622327e827b2c0556802c5657f8f06062a4000000000000000000000000000000000f002a2a5ca90285f9b2fd429721c2daffcae5fe48c571ebacaf475606f96cc8350ce88a850ed75e5aae59d445249bf00000000000000000000000000000000015157fe1a767dabc185a8dc8fea3cb208fd995ecd9acab762638faa987f8367ff7c1a60b657be6e9461acc9df16381e5562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000073884ffbe6deff99cb4b0ae1c0e91e2f4a8c2c7296339b1d7e117d5d47ab055743d643155680740befb379a1dcab666000000000000000000000000000000001995bdc23991dd4cbd973e915a16691fb860490bb54011384c553dd14afc37fe673d13950c1e7eaa29c324fd9304624c0000000000000000000000000000000012197a19a498cd94ecbb3a409337b04e76e1a52715c40203add20eb80f7eac66f3386242d51bea34ea016d778248836f00000000000000000000000000000000101069ff0af2ac4dc7a5bf7bf7b56d82a310d67cebc41a9abf1e1af489e1acef3e726fe9571b4382777573712663e26caf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f000000000000000000000000000000000849b88e7ff52d8136a120f924b20b45ea9ae654a0fa037b62f3c275f0661091038a4c1d6ce7d50512e628b6b397c9f6000000000000000000000000000000000e50e82e9b368f2e316d41febab6b0f626d6588b7217b4e28eedbdf50a4abc9039be9e66c97790d12cdedc90873993e2000000000000000000000000000000000bc5d2bdf06fda1e1d1f5c5eaa7988dfdd790bf4d952f5d3a532bb59edf619dafcbc29274fd3661a35a3f15933b1849300000000000000000000000000000000162e5ce45499e620d0977fa26a291a8e75943c4b5a2a80be395ac9b89767ea5a06606d6b75ee4c8a286d2ea5a197baa5e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e880000000000000000000000000000000005c11afc970544b96fc1a4cbb27259e19b5fd588d1be1c8f19eb4f111882292a463c951521388cb8cb743e5a4a1b57cb00000000000000000000000000000000013dc433dadc122376b75fedc923386a7ba5a363678fcf9edf165a50e160dadcc151b6f402648193d9ef960f5e401030000000000000000000000000000000001893af155aca343bc29989ec2b5a583d020a7558c7663accf6f3e40d0a8eb98ac548e933eb8e2d5fe3550927acc2ed4900000000000000000000000000000000043a79bcbaf07bffe6c6890d95c7e74d127446bdea51a0ba3adb164ea39684bb3ac552020ca28b86e34692c9b36f4384396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000e6946ddc8a9d73e5b140af80cc91b31b9a226a945a9574f0629566f7ee7650730c5ed758cc30442770ed1602b84175c000000000000000000000000000000000da0abb9f5bfcad73b3f24903e9ef887c660447332e5457e4a5764f6628c04d6fe903679b8dc8bb3aaacde410812286a000000000000000000000000000000000656016c01d3405dce9f7d40e47976bc8a84abc370e7e42849dd0bd93ef1da0bc88e428efea43dfea37dd834cf246d69000000000000000000000000000000001939b2c92c8299d7ec1dbeb9f291c5e1c9481e10df10e6ba18ae695a780aec5a185ed4c7e82dc2bb5af87a74552c2ea32087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000000942901572722e5005a9ef5f948c8cd6f557be8d114d2810d3cca29933a94de3c7658e7e28675c2a49f138d9c98c524000000000000000000000000000000001908e8b815e95ec07a90861ce53f545f0cd44aacc47df40c24d6cbc61e7b28fb91cfb1cb3c67b6c5b38c34fcb2ca35710000000000000000000000000000000017bad3616d8e510e325d9166790239c8c817c68ba7fb937fd5fb70a4219265edf6625b52ff26f0a34c0bf481c482b2c600000000000000000000000000000000023ff8a50a9c0e9ee829ec81972386ea012df5e8476d8c342df6b98fa1faa1382ae921c2f1018a918868672450355c44f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000014675ab3efd44bffae321791e6fb35a24b9c07405d9985c685795df2db183ee9dadf18c76cf4095e1e0695dc2c08c4c4000000000000000000000000000000000835f2cf09647061ced2bdf4211bdaea408148100f864f47ff76c0c63a43e44e8ddd9e01709b6ad129bd574d71a1a63c000000000000000000000000000000001017eaeaa6eba76923ff27e5848e5f3b09e7b2b9d55b2cb7068f39defa8628d1c8cedcbb0e1cb5810febc4ccea712b7100000000000000000000000000000000054c873449c738383e9fc2f0f74a6334904171fdb704f5ac35a483ba19a8f661187d36fb35014af9ecf88225466c86e48624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b60000000000000000000000000000000000439ae88636244d5e09607960fb033e4217343899d044b21e61335425b94a5067c941e83e5a77f4b0690e1de037325090000000000000000000000000000000003a67653818cece3ff0390d097f1bfbea9ba954a85710f5c24d1de1893f25f2863991fb9f330e60cad725708e70384b4000000000000000000000000000000000243394c3459a3af236189ec6155418c1916b854a20b980ca1044b48e23b725dab7c60a48e89f642423c805c117e64870000000000000000000000000000000004c8c9fd9f278dfe9f5e24e0f5b42699bb9751b56520827afc2fae8393c690a63f10e92f77c4a10b0c161408da9bf505b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb38320000000000000000000000000000000011ba67024503301ec72bfad101a48708e3521c8a23c6bf2994078690041cf7eb75675cf5f20c8e82d11145e31751a2300000000000000000000000000000000008ace953ed2eaef19595cc7c9fb1806d26cbf1e888075e3985b28f8d93b9c0b4c820c8e8b50fd4e0b23923d428da3efa00000000000000000000000000000000054ee6f7247296e0748d0b52148a97b930e69991a242767d80bd6434d42b0865a64d3ce60953fd2631aef873d8b2acf3000000000000000000000000000000000077748b724301a8bc48efd1cd66086e727e9872e4efdaf55ba90ad1bed7e229a9cfb79013333b50efb46090ac0bdab488ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb0000000000000000000000000000000005008a1d62dad51132ad38a226e8abd7421392414acda61111c728713a2ece284b04d75c2bc58d355bb1d3061415010200000000000000000000000000000000189725b7fc48b8a648237021e9a2334247f1cf18ca50008b813978db01667ba08f00b23b3aa0e015f549ff2d5e5c535f0000000000000000000000000000000010483cf2310f64cf0baf556cb2f2828a1c15922547bec03cdb182a316aa86b5473f03373cf7e59a9a78f73193c1caf520000000000000000000000000000000007f635394301441bdc57dd1f4f97656f4218ebb139c13a17e12839091e2e81327f3353c56880c608de824a07a17b2bdd951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f44000000000000000000000000000000000daf4090a229a1ce946064cda1c4b19c88100c8785c69f2eeec3aed12065787ab0abd797ceed07617d55a9c70ac3020c0000000000000000000000000000000011d77fc28355f61037cae3a8342bdf8d11e963495ba3b5d67055f790b1fd632b23565cad77a3d9968d364e4e2a553c9d000000000000000000000000000000001038d7e8fedea873c864b79d1cf8045485299a2bd4d26c5ab5c8d4a073e2c3fcb38cb230dc6ab7e8e228cabc6ed97da50000000000000000000000000000000009de9209ed14d62625ffbf770e8c528594aeddcaf1aaeedb4f3ca973e7b9f9f1a40370cc74b154f3bc641665d8e4d96b7056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000a6b0dc04591cbbb1b82a059e08b488fd66edca0f2d264c352f81cb6ec45e50f0af16917fa4727ee9888f84b6c888c60000000000000000000000000000000001369ae16bb0743f65cdfc8082dbe0d588cf8aa5406a095c3deefc27eb3ed462dda9dd4921cde6a1d878a805cd144515800000000000000000000000000000000124e08d4de6e831229005663df4e4bd5bb7af56dfb13244c50410e6d0aea420ba19208bf1a774207e0e0170ad3a9b4f60000000000000000000000000000000011b2973743034a2c362281b11a1ac1c89f59ace09f0a53afb0c2ceb061726c7aaefe274f6dc04e5d0dea2b687a00609a8aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a", "Expected": "00000000000000000000000000000000136ff52e440da609b6b73aa838f2eb9791221291b7b14d902458aa7aa9e37114c573edbe8cef7a98dd07275a8c3fd650000000000000000000000000000000000ba625eb47be09ac8cd1e2ec9015640f416af0e3e0e79d39ccac600ea08bdae7a2bc9144f13168a8cec03ce66b9daadb00000000000000000000000000000000095c51e81b5881b009b28006286c704ce3b002e4ca50ac8ea8e574d1e9665a5b1efdd60568d4a4a656ca6a2d1750a39900000000000000000000000000000000143c0c4b3b720fcd0b044a6f420961e2b7eb5f9f1b0d200de56ca8b02709d819f47f0a6ea7d6b49c4f30520586a45616", "Name": "matter_g2_multiexp_7", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000da4cf56fdbaa9004bf8ffa12d5cfb3f296ba5262dab079c91bdbadd6e41ee5f89912bffd5df1643146bce1f0e021b3d00000000000000000000000000000000150227356e48f29443a0ab4536e7a2f86f9e63840e23bbf1b091a59f52c27978bd6a15b29b105132298de45e51134da50000000000000000000000000000000017f5271c97d84f55f8b7ee0d73267bb69cdc7565c470a4b531f9dcd29596eaedf46e61bd79e71e5ade7d000c1c1d81bc000000000000000000000000000000001322812590e6c22bd90511ed72553c1cdb0ba83487b00e3adcb01a9abb438f365ca23fae9ee4a953544253696ddb0bf1a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c40000000000000000000000000000000007c59f94693320b01b56b36f8d1c39fc9e01bad289577738e648771d8940778276cdbfd59f07926e516fcebb70592de0000000000000000000000000000000000aa71d6dcb0b225526eb92b79891ef920634a007b87986fc0f776f85195ad7ec2d84b9bc684add947df8ff42c33b034d000000000000000000000000000000001362cbd6cca3d5c1ec68928be38aca5de1f224e7cd4f5c3ab1c2cd589bbd7c31022d4adc51720bedf2580d2acfa0f06400000000000000000000000000000000162bf0f38e19ddca9aaa370f988be9b35461d2a0f46143e8663f1fa549d0afa1596f029cf2f800b027b90d1eda6ae8a2b54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000000eb12a92fe65f79c646ba508fa615d09d86e582c3337ae16f66cd3bd74a9caa9dc17defb4b4e67ad62f0665c9ad1b6cf00000000000000000000000000000000058b6ce2582c46c0fc108a37e1d2713ff21ec8b1d8c18da0e69f0dfec7f2f327043e174e16d9d64f9ed4d3818a302bea00000000000000000000000000000000068192bd2ebc0a23092bb98c23f5792e179913c4ff1f23eb27296a77e83729803764b8db3b7ba4fe154ca467475eefb2000000000000000000000000000000000482b16e876aa90da6da35e0d7495a04d5b0a1d084c61821f23e1ad63cb1e66ef5975a3cef9ecdf2e696e9d9b50bf9b65849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b712fffce3e63362bcc246da566a14139a3d12807ba83ab3520b0aa3aa20cecd5718e2b7e00f24e6fa705315bc2175800000000000000000000000000000000057a66fb12f27e4a5268e56805fe2b61b5ef019b31fcdd861e2b0beecdffe1a3a69e8d193815f97740324aaa40ce34a8000000000000000000000000000000001080a9e1133f37288dbc3835e45b6611fe84ec4790e23e5ff84a2f72bfa2837f55cae9177e5a3a918adde777b7298a9200000000000000000000000000000000142dcaefd73d7f6342e87fff8c6cd161389b6049fa077f35076eadd2b4aa66f3a1819bf8272cac1c28cc02bb6440dc42aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000034c0f8249d6aefe4cdbf84d151ea9f84add42ade087048bbbf9de4a412cc805dd9b608fdcfa34fa224066b5f06d18630000000000000000000000000000000009e235ce5eb936bae00d3fecead8859e6d909da3d57bbe0a8aefaa5efdc94969a1cb2e12642c0099bca4e7bbf9833469000000000000000000000000000000000b6fbab498c2706f0efdb4effaf79218cf4b652a5205eabeb84f05a060da8cd18c8154a3d37594485ba50a8228f27f6800000000000000000000000000000000130ab70e17dc73f773df99cbe3f978bcd3fcb92a8226a1450239d209cc6969e2cecdc0bf3cbbe9a9c1de072bffbccaa952c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b20200000000000000000000000000000000031a2c10e95b841ecfcbddee4b458385e5650dec9a2d1e50216d9fc261a9829eb5fe894e47f171c8fd2f4d5d89771341000000000000000000000000000000001378471c7f770672ee82b70fc87af5ccacdf8995df9ce48aa9fc2f638105a2fdfa48b615970665ae4869f1e2dc7988e8000000000000000000000000000000001969517c503df5560628555a8780138e4c340d9d49d8fac4a8a11c894d283d49fd06aa81e9f0db8f015d9372762dad75000000000000000000000000000000000f5c2d9b7fc33167a6e9b5a5fb8c5d16ca009282edc05cbc8a048b835b16ba33515c226174d6ce5f9836581611ab403bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd90000000000000000000000000000000000a6a32f2006c4b7804e99011d934ac91b1b3fa6f5d02c574cecd6570bde1e998f135449dfc148aaa8fb8757d0a7299b00000000000000000000000000000000198beb461b59f57b85d858b730fcf853d967a1592e5e5787fd81c6a3d9d9b40c1cd7912cae21a47aaf78df5540604cb4000000000000000000000000000000000955701e84721866683b4eaba82c2df8a89bc906fb0a3cde565d314cd7278b0c56936205cc8ada10b03e69b93c48067b0000000000000000000000000000000004740253653a0d6cb15c76e145dc0b1f811bdc964f7d595b6027bb012b42409deaa8da83e6ddc3f0f7b4b237eb62b537915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000000f88e1e30674934bf1062ac619f1834f35f804a958e82121255f8087ae08f10525e740ee53d7514e0ee7c49e324513c700000000000000000000000000000000019d554645696b7beae881ef62297283c5b68ad3fa9a84a47c29cb53449d33d6ee7a5a3cb83b6acb75cd41ac3f52fec40000000000000000000000000000000004b32776966e52e8a72c88a689d6c56833296d384e2059d8f615ccd3616972074987f839b4689d5610a88addcd836d930000000000000000000000000000000000fd4d21b00d81ec993d2350f1fe360576fa983754a7159c2e81024a00931d84e419e8b5231ba8cf8f05a0ee6ccea7e558fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000177830cf34186191fa295b7f279bc819d8a53452e2114dbfe709971584ec7a2da7453aae3e64f4b14c261e22314027c3000000000000000000000000000000000ebf2aac35fe070403a4b7a5c2f102c67300bfd68af7863b45185b37ade1bc53d46772062189f348647e74c77caca4a600000000000000000000000000000000128dc7846b2dc5c453ba5fe4675d0c22f4d7089624ede05b0910c34ae623d4671979fd73455b35b61a57c51fe2895adf0000000000000000000000000000000008e33a3c3735be035b550613c712b220595a83c1953b24b3efd38c5913fc23df823e00ae5a1c2ea8a8eebbb93c5c721dc29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea0000000000000000000000000000000000a060a957a8da4384e3436110657110653685bb621c32810b6516c690a00c13e37f70185958beb0ed886aae5cdd611a7000000000000000000000000000000000b5afbc85e274049985eac230b2aede7b2df1485c9539a4a4eb6aea406d0f6515ad8bbece7155fb0dfb2123919fb8af9000000000000000000000000000000000afa722987390440a33d5103445dcef42cc4a3c461daa076d56fd38e0b220016ed2bb8e99b9a8da4af96b7da64ba90950000000000000000000000000000000013ea6b8d327191e53bc71fe43fda305a4a0584cad04048afc0480f179955cb27f2ac8791d847036470ffeb47aae36877dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e900000000000000000000000000000000032b490f795ac3242b8c7185c9e19f0440ecee3a65263dd4e4c9a431571deb7339bc6e2d73ec43750f6f027bcfd674c400000000000000000000000000000000076ab4ab3e8ed6ea3b882fde5cacb3bd094567288699e11f368c3f60f4283c5bcee7b4c5debeac541ead983f5936d9f80000000000000000000000000000000012aa2060e421f4f4249e83ca0ae1752dfa2b7ca958821841a18f05071a35fb9c1448619bd96f8a7adb2202d3ffda8eb30000000000000000000000000000000008b24f29ee7571f31ff86574e654a5d849acbe92653ae1a1d2baf4c9ca6e67da4937bfda51a70931a6e60d90162efb4f21c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea47000000000000000000000000000000000c8b41e5c47babd6ea113c0ad9f45a75d1ef6bd313b768ac01e6f581ef6630ada623c1a27d4aadf543af4055de7f6b73000000000000000000000000000000000a0f73af06f8f0115bf17f7c5db0a6bdea77a8e3d8fd0b52b0d4e2c558f1331f655dc272c86d98bf166b532ec8e45285000000000000000000000000000000000499b55964186bcc6986e7744c52babf47e274e47a202abf6f816bc748baf846df2b5ced2a5f61fbb0aa2047bbaf82db000000000000000000000000000000000d6c2a9a3fa5d0524f772cca2c7e72a5f2da1a6a1b9550997e7a6cac5b6b6c37693a01d30bebe4b9c742b63bd31487a1e1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c9600000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000134125416c7908cb4454ce6aadb30df46042ef2a6b4b69b19fafcb9ebafe8b5579046725590266cfd10fa26e1b5ff3dc00000000000000000000000000000000073f4147cce24e13b9eefad7c69b457acf126bf278a58a26a7c7c6b482edea6dca9725d7e5e4138b4ec81bc2505ce2e60000000000000000000000000000000006125caac1061cd6c556f4cfc122df8e949622a46ca707b48ef088ee5623df058bada1bc0cce1399f0be1ee86225f13000000000000000000000000000000000146e398c161e29c90c8a4fc44bfd5b3dba6f9e80ead561fa3d91ca5f416e06318dddcfe5147ab5def858fb025a1562352c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b6000000000000000000000000000000000944942effc77ad02c5ddb052acf86f3a9dc4127dd032181450295464b49ac1dc0047790acb378221fbeebd4c92886820000000000000000000000000000000018e1d201b38d88665696ee6cef11fb19f7daa7f11c5a5ccc73e6b66ac7b89df8437c9f07132ec8b69e13f63424ad694c000000000000000000000000000000001463117fdcf17f28956a42677b3ff431cc17ccbde067b91ecd6fae51e1e24ba8d594ea368d041656022611ad3ed44a6e0000000000000000000000000000000009715cc5add17395b7ddbcb961269fc5d4739d799fe9554b3c9e9f59c895ca5df8ec75bda05cbef3e6a165f7987e78662908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f00000000000000000000000000000000007c07cf1ac9b8b28e3d2f1f4ce22b8ee46e99914ba20c7362c679559a1618a906c6ea65c475ebbeca4947019cb6fbec0000000000000000000000000000000008b29f72cda71e0bc2246ead57b2f758b741b9232d87be75331275a5cd63afc9aa98b0e42c1b82cc258e93c97e596a81000000000000000000000000000000001512548a4bbd537a4d5baf673fb76ea7e35b2977216e7b29a6375e1f92049d7b7d5fd5d8b4ae6191f5592b738e149a5f000000000000000000000000000000000cc9d646428135296919808c6ac10c142e769bf71bc1490196dfdd4e1fc7b84e58155bfdbe77a9e684622ffd83e97ad3e8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399", "Expected": "000000000000000000000000000000000ae9da7d12d0a03cca3b41ad869f762784cacb988eac7ce904ec9ff47824e058e2e211e2285f9fe2aed0b4385949b4540000000000000000000000000000000005b0c873d20f7be1410d39885ce4f79884eb6ae2b2f27510d6f6874dacf2a66c64e56b7aacac61ec88261624936e695700000000000000000000000000000000076c6076175ad748dd68fee64431e5e4ad013797de4528287e7226c3df90233799ed5c8b36848c1a2e1c02591a013d270000000000000000000000000000000001f7f6972121d38ee2d10c621a38448ed12271f7e0e9e4567fe1b5fcb469c7906196fe92c66c37f8c5abc91160fea8ae", "Name": "matter_g2_multiexp_8", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000139cbf360b10e59c20dd4155af2023d5dfe0681c62351dd541cbed41b6a328aa44b862d1985b132a4d4ca61c95b61ebf0000000000000000000000000000000004af6b5a0f7a02d5c93304be0d31959bb4b1a7a5838dc3f9cf50180c4eaf3d32e68c006263d75f9735c8f0b5f811d3cb000000000000000000000000000000001644937e5ff3b8d2005a2f3b0984917837d44412362a121be481526173a4c61e20d61076aa10b4072b79743c5f7f4c4f0000000000000000000000000000000009bd399b55a59550dd876f35e54a5803058228bd6ab6c9a66e438cae473c63816c96bdf378ad426a236b58b90e737831e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a8000000000000000000000000000000000f343e5118d7dc3a38e9975a5f40084ee5f2305e45a8aed28ef105f76345d9f5646b4f3924b92978846b4e605b78fdf400000000000000000000000000000000017e61a2ecf9b3403b43f5a10a97cf5088b4f98e5a4513b0912ea7ecef44e6809f10dee60367cf2fe3e903dd68c2a97c00000000000000000000000000000000039f37f414338cab0e12f99b2aa1e3c02cbdee3406d1bd17c359ba899b7cdcff605f894530895aecb469f41628c3da120000000000000000000000000000000001b78bf69f1b7168d735fb4b7b135fe70db79f50e792eedea23f83cee9b48e092536c2ed146c7499cf047c5b4f0a08735307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af000000000000000000000000000000001342346f1b553e29e661c9f6c0a24d8f788db98262d6af86af55d313a37eeabed1183e367ee3d83faa3f284b260e786c000000000000000000000000000000000960c8af3f7e6587cf83baae447491e73cf41e637e1efd730e3acd9793717e57b85530584942e7a030bad3b91a76996300000000000000000000000000000000166daca4ee2cb9516b5178cefef0553115dec8157f6194d24d191cfe6340406071883c89246c0cd5f89bbd5d0f1ee15b00000000000000000000000000000000187f668086b9b6307899d301bdbfec915cf24ac0be10d6897b0677e4f1de6a241f3dfb19225644858be0941530e67d0f7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e22600000000000000000000000000000000032c3783e701bcb651aef40c91682eda03f9d90f252740612c85a5727f8bcc41a886b328d5ce787031c08ace235ff465000000000000000000000000000000000b0eca06f9fb69ebb46d0af56d3d934b333514d7f31208b4ee2fb92009e6041749028a78246a0adc324034a94503e80d0000000000000000000000000000000019eb24ed35f6c7ae53047814cab14d51ae6cf336d140a17e794d5cf18450b7fac3e6f990e12d340291459197bd353861000000000000000000000000000000001983a596485e657deaedf01614dcd5f4ec515c0050e8068ea02c9833d0b165c0f467107a50da0d8cd43bfcb59db6e710eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df0000000000000000000000000000000002f1c29ffdf7bf20fb8a13363393d5f1cca5dd9af82888f0102030fdda641abd5532ffaa2669c0c4159a989cef1c5bdb000000000000000000000000000000000bd548079899d49cd368bf5c246aa168fc8c777bb84a7930258502c8424a4b68e1ab20dc9ef39c307e52bcafadb0c8e100000000000000000000000000000000070c18918f037d5fa1aa005e2c80ce6a80b4b24d33ce72a2bd824f9a061af1db236f04d6041314310b31b805b8a674800000000000000000000000000000000014422b173840da655aac6ea4b7a04313d5d0675bcd565258c73039f879176e51ec0c8a9deba9c78c33179a5ba54492012c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000a1be8e39a47dbe0bd19b8108a5bdac582e1d11ef7fe28df1f12da52924e734e1d591e8e33ec20c6d5af5bc8c1161fca000000000000000000000000000000000eaa7a7cec93b8d5eb933103b52a35b3d58214feb8e2de0bba3a0e57e7993a9df0dcf8089142f57f8e0d1d303588ce9d000000000000000000000000000000000089fbfb389ba448eb77722994178ee3cfd15a27be4ed6f4d4ab6ea1a4c10d6ee8424beb17d08190fb18ab8498d4a4fb000000000000000000000000000000000ab02df2eb474735e28c45b915299230ce159816419fe9c99a7da397b7210590705262ee14c2a244f4922c35bcb119338dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a0253800000000000000000000000000000000006508fbef44d36cdc6fb37b6324810ab2a1d94e39abdf09d530df34714168105e23a7d6f7fd9caf31f263b658f16b76000000000000000000000000000000000b5bb1802813f9f8a16991d41275ae6d18532e3dcd2eae091da7256aaddd501855e775b779959fcef2822685725cd43b00000000000000000000000000000000052146ee63ae277911fe491420651a96994a30c7d1b19bab32eded008a125369baed2ec5a963bfd863a83c29bc1afb23000000000000000000000000000000000a180d79335347a8be350a92491760c6bf1fd56604d4d99a1c49bcbe50b2d04b7cdde55b4aea8ddda4bfeb8e79ab6ce4146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000015dcdc17a9afbf88b54af22ed2168329bc43ba50d374c0507c790f37f9669d0af167328d50d322a827d45f39724d2b2600000000000000000000000000000000169b83f2567e921a4319fc03b2a7eeefd2aed79914bf608d9e0a54aa71b9cb3e09f1cbfbadaa520c0f77f547fd407ea50000000000000000000000000000000009b7a8ff8388c85a0fe3860f26b09b81b5dc51e00a8961fdba96eb462e1334e9e28a2cdc4be49dd8b96c548c64921718000000000000000000000000000000000243782436fe7cb20a3242a3a21402a43a2c4fcbe77cc7182ee3cc04f4795c269d8a64ddd25e89ba4fc796747b608092de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb94973680000000000000000000000000000000013ce6856b6df48e4c9e3fc0be0aca5b139e1b874de6ddc148c1c23a846d61e7a531cc889bab99706668a3b69d32b9160000000000000000000000000000000000a459676071c7f3065a6dd7632edd5842db34aeda8fa0e7d7a8ea29f842ebcf2c5fdfa74ee7685caa51481c4f46952240000000000000000000000000000000010c1d9ebf7bed9195cf0bfefad6ba45f1bd19a9a7d340b7c630b9953923efe4907bd75a3da066fe3d49d656f3ed91d2800000000000000000000000000000000039189de73332d5b5a160c296a195cb9d8a736cca23a92948d513da7e4fc46e1ed9c207e86751b3cf1310d8a7284877ec2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf00000000000000000000000000000000129821e97c65ad3801c011792f4c099e19919d7d03bf9fcba30b3735586bb7ead7d4f9bd10bc5f0e5cf1dae82d5651ef00000000000000000000000000000000038cfbe45bbdc494988a2dc72dea6a7e36652f5e5a2ecad41b4aeceec05dc4a389e54cd3aab349adbe32e65206eb481b000000000000000000000000000000000bbab53f2be2c471d6e9cbad719a73c00b582d0983e25e1969c0be1faa56b1dfa5b7b55797b3340cf8c7eabc560fac71000000000000000000000000000000000b0db19410e552a2f7889c2204a93c5cfc71c360329e3be3171e88fc7aa1e993a5d089c28b1a8f8fc80d93ba194c63ccc1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b0000000000000000000000000000000019320bb8d29b7b5a7130b87a39e87e271b96656b5a2749f13208520634009c26f9829401d3e21cee5a757782c6bbf9ca0000000000000000000000000000000009b37068d72463e72f3a89b9093c1b09f01770e647b5ff7daa50e0679bb76404cf7729d5575a39f5b9b3b371893967df0000000000000000000000000000000019ff29e41db50c736e12f62d76a28f4ca4f6b0f4f61aee00cc0e9dd4e5a75c0ca965b82698f704c604bb309aa5b457f100000000000000000000000000000000062c352a554dc4bb96b459378c21ec6446e15b868221b2fb745d31dece854bc281bc22827d84ea3b0fecfe5d156712ce6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa200000000000000000000000000000000021c52e82b0012537b57fd92fc276e8de842a59355cc15d69a52effcfaa7cc43dbda0c34e1b9af44c2db8e9356b9c71e000000000000000000000000000000000371a6da5dd39092b6108f631a0f4c4401464a109ea1e5d14e262c8a9577e1421d41734d2c3ed73645cc13ef3988e9e90000000000000000000000000000000004054159263ee60f6b1882ad7c376c738c7ed87e6b34dfb4be2fd7aa29ede414c2c6c3ff098c53f22a1c1cd836a6b0600000000000000000000000000000000012d7af6b57c688e1ce90e9f2796b0e525e775fcb6be65f5d2fbe3d1ce1e5d948dcb098c98d495a6e3dd813527b4635258e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca1000000000000000000000000000000000e53128fa5392dbae9e40ab1ff0149d5b577d9d30dcb85eb5e4fcdc17c7daf2ff1d6fafd4a1aba88d2e7aeb45a01afc60000000000000000000000000000000012972781f214511e9b78d276767b1b64bfe5b43215c7680c0063b6974f703b209b2929470dbae16f9767a7cba5311fec000000000000000000000000000000000cf6b37c5a60851d03752f68eaeaf37ac67c661f644cf507c5458cb5404d0ce903c92ef66a657b25ce07e5cf5d956929000000000000000000000000000000001835f202705c8b984a4c7a6cd219c718ab27a96671574cf7cb618235d19e9046a15212e0da6233f15f18bbe192df29c38efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c000000000000000000000000000000000d01c6e8e34e646911391b012680f0dd8f4b8d77c10192ac09ce57b6524f0eb8c7f83ff8f26d856e0945d7a909eb790000000000000000000000000000000000070fca42e34dacce0051f9e26c7c0dc328fe652110976df6df77af04202831dd095715af1714b60a99f2177e86a3443d000000000000000000000000000000000063ba43df0155373df59b009a8083b9f62004327b16ad455037487c5b8325e7eaf57a4d05c533e284004be6de79ad1e000000000000000000000000000000000870c2e5a7d26ba54bf0d45ddf0a4c3011152dd12a5e01a80e42bc4dcc784c7ffdb66f9d6d69ac445c1d9aa29586245147f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d53000000000000000000000000000000000d84ca02ffb6d3cf6eb27a143ece73d5bf006ff61569f0eab00c5a512c5b46e1fc21e8031d1a578010c9582d75e1faa8000000000000000000000000000000000a41249cf01ecd23d06f6a3bb8573186fe47e5165ec0d447df62bfc236f4c203b4feb8e2a4785648af86646cfb0c4e32000000000000000000000000000000000244fa6caa86fd27e044145557697ea89baf718746711c8dde334a2c5ae3c73d7a0e04fed6289ddfaf26e47a9d26b09e0000000000000000000000000000000017db897060c0a8e3e5d8eca9970407b46dc2c2ca0c004d50a171450852f585268bfa8a379acd01b6d4685e04c0b8c106fb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b55800000000000000000000000000000000071e13963e20eb1dfb671aa4a090973e4a4b7ad3578f8630db8a865847be46c796e6f9e095a9ce558b93d702f8f8572a000000000000000000000000000000000dfc4c89ceaad07e3b4c35d96e8534122ae48421cd4443de478ddf9a8867ffdab279ad745e55c87b731afa7700bbdb110000000000000000000000000000000015dd6b0c26f6821177d0cfebb7f1481a971e7601fb24ea365a0c3127a5b1042eab69446de05b61cb6ac0576752f87aa900000000000000000000000000000000156326c52bc78c82f5cb4aec5de35e3c128c5561dc80da2cb24d68a7e912b1f2dac2078508fdd4ec38769102c082f0f74b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a", "Expected": "000000000000000000000000000000000b537dc10a6f518122665f7d78326a4728a2889325e5be7da7e25e4752c680fd786cdaadfcc426343a9844efbbce8f2300000000000000000000000000000000085ba3a04aa8cea82b95dd994f5b3bdf0dcf63f13909aca2c2d61e4275a7ea22445c953b927ebc6b0987e98b553469d40000000000000000000000000000000019cec2e9fab640cc88073bd39e46cd571324904b1950fa8f626e2725936d80daacce2487f46ad23fa8af9c6ca0367fdb0000000000000000000000000000000007039a0e11cbb8bd940eaf4a192bb94ff8c6d6c79f775fa67821b5ba411641c09dfe9fac4cf45eb5fae52d2fc4beb6bf", "Name": "matter_g2_multiexp_9", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f73a297cd6444809aa11b0756167e71986ab31b52b57d3c0aac5637129b8702ff21ec649541e79644c27f0017c8ae3f0000000000000000000000000000000016f96d6ba02aab604dd918cc799cee61cda4c0164ed9f07d4932fc4ac3eeb92b1e6b40dd7b18cd8d26056b486e57ed290000000000000000000000000000000012156f3ca3aa1e79014dfd92fbb6c785cf0ee449a8920b89ad04355e0fb7c8ea804bbad082b4edc9abd3b24ab0df2b61000000000000000000000000000000000d51b5f62a6e70816d7671bcfc52f11bdac6221a23287286af78605b99ae8bd0c722e485bd0381b958a85f61e05de68368ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000acc52d0fca02c3228cd2e5202c4eda297b8227bf4e64308226bc487e5b64738efa4c07a3738397f90251ea9a1a9da29000000000000000000000000000000000b85b853826a28777a5767d5b1966ce12fa8999ceff5d6deab5c947fd19d19de9c103bb920bad615186d132ec22187320000000000000000000000000000000006b5a83827dc7b3580579ab7976a70ee160b712580919b6f5d4e180165e50f5a1698fa7cc63846eb1f5e6df955c3eefe0000000000000000000000000000000006c2957d8adc55931900145388583e5c2d5f6bd784e022702801c38534d2c92c6df9f95d022aa6d800e1e458eb7f313061529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f810000000000000000000000000000000014e4c5991f9f2ee262019c1344a0843756157dc85aecb15718217a2fbe23fe0843992dcd3953ebe79acd85517acece0e00000000000000000000000000000000076a18fe710aca2875bc102f21782c9649f107684a4edcb0c4538f1a2890a2ae5b46a182d5470e620375327965b6d37700000000000000000000000000000000142a0fb19b28a034d326121458628356561e50cd3a471ee78bade0733597b8b90f647f5199d4b5b1ee6be4e1870bcd310000000000000000000000000000000018f8b5933848813cc2c1a0f079b095d565e7875ba6693eaa10967d496fb47257c9c674f301349dd8f2d22f8857f9d5ca44d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c000000000000000000000000000000000db331d2b965dbc053b01a61e671d2ee6b04b072b6494e482f48f12221f23e3b1ccebf48046d92b4be2e4283c77f51380000000000000000000000000000000016704f3e1ce14f49df400592ce29627833ed1dbb91ae5f00779eef94fe9ab313c3e7c8da940085034e1a49158043599d000000000000000000000000000000001956d492f5764c6de0b8e9a716766c762620ebd3265a95b47a8ad2c0614c337692108800e22abbe321d77a6cc17f4b880000000000000000000000000000000017149865739d6aed0f2a4c3c71c2d02f8080d9339025b03f89a37a165fe6e5a4cbd489b5fc90bb2cc432e5baab213c8424872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000036804da102cce975f980ed5a69e0464241b5de87238f9892c77fc2b6e5ceb00d7a37a45b5520fce5f094f8b9510f49b00000000000000000000000000000000049da8b6c974f2d680a80d2007333f15702f1517d3dc11395662ca1db945c795bf64167840c4df0fda68a69e127b2d590000000000000000000000000000000000e94cc66f1ffb2112e37cbd5b4feb7d65032c2e57260504a42816aeac85648558f6997ef12028655103a8cb9de1297d000000000000000000000000000000000abf7703ddf6995d5c29124ba9a3f890854fe0622d547a4f24d6a60b036ec9e58f7ec2deca5a71e1fce2210cf810e2f901a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc00000000000000000000000000000000008d22e456c643ce680f5ea14553a9c249a43d4f92d94135dfec85bc58967ec01135507bd8ac3954b5876c5bebcc1179800000000000000000000000000000000022029d4abec7fc9ab3bfddf2f462660bef7449c4093144d9b7d6f9e84f4f1c947855ca6e09bbb3bee4db096978ae0dd0000000000000000000000000000000014beddf6a3fbcd621e2a592e1c87952ed277163ebf390896f7c668944d6e0a026d3df74b0fc877ed560527a80b981d1e000000000000000000000000000000001414af918645ce0d4d1f670333fedf286b01213408019e327d3cb9321f06fae311b598c2f78bb578e85692e6cb787a52c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000129f1e25d96b8c879710a81b727b31d27ce9887c245bf908a3768f3606870ca6bfa70dbf5135819d36582d55f230e94c000000000000000000000000000000000e91eaa33e7cacce4e1d6d0fe905c72221b534a72cd51e1de79a25ef0c06ab454a849a241c023b0f82aa07de28e35869000000000000000000000000000000001379e390f2f0f3636312465469b532d876529d58dda8b024b6b81d242af47b5720af4360d5a3172ad80fd9fd8a14ba2d000000000000000000000000000000000775992d5a8ae0640af845fae03dd0b2197699f413f90f6130d21db0dab042324094b36acda26ed86c65821d2d8a29d9e62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000f5420156358ddbabf31fcc94678866f899e38747e79dba8ae280704c4b199a03eb423ceed18b5cba7e7ce84583c84a0000000000000000000000000000000001127669ef3ba3785a859aa4e942e8fc3181f2703b0ece6ddbee8830d7ffbfe498794f1ca2e67c3ad39ebd33e838dbc5300000000000000000000000000000000138113386846310db8e21fb8bfe40035cd89e51736b491d5f2d3cf5672e6836c25f62eab80f25ab49d16dbb83796aa5d000000000000000000000000000000001711d74ef4995b473239a574fb8ea6edc6eb7a88793a093df4652da240d069c5bf9249b58e9b1e11f7d6619cdc28a5787a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000d32ccc6598af8156f1c5b35e69e7c7f57f9fe18748510605a2a81b4ee09882bf3fb26abf50206cd57c77924ebeda8010000000000000000000000000000000009043d364e0637c60223f9a5db8c50e983746fdf4c9f7986d27f5f4f3a6df487592ea42078f14efcb3eb1b7e81d058eb000000000000000000000000000000000233495c4961e71cffc2abcde4007c0d587687aea905f3ac5758d0f8d9020197adb6f9d7b86a542b8efffb05dce997130000000000000000000000000000000015b084e773e66ab1459825b6e6dba055a96e4dc1d94ac0b640e906e0a9f12d2124a58537c458e6e1b571311b93acc26c0f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce8880000000000000000000000000000000000e4b859c679a90c03ea4d4b0b3d38211f685db053aede0f7f359f712e1ae808185758546877502d57200da2c2137f37100000000000000000000000000000000173b24ca19436b51aae22838674c41c752536eada3197de6efc98303eceb3e6e8e47ee6679e61e3cb5c8c734c96c98720000000000000000000000000000000005232b8c97a4860a23999d6ed6d173d300ed50b77c7b3ceb4e8407d9d6877a6004e2f76c553bf458b7cfd8d1e6fd364e0000000000000000000000000000000018a115201e3f4eb308c16656b3ca0635e6284169cee3f28101903ce1cab0659c3d83a449918df6e58e8af2e001036b8d9b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c000000000000000000000000000000000131232788aa3038a6b8a055a896af4f8129e3dd3397dfd90ce86b3e09a775e5b5e19f4387f4c02200a36bc2a1e09d98000000000000000000000000000000000eb8cc0455cbaae97dfd05c1246d3d5ee58c286d263184ae342f5c0ef432355a574bb9fb8ec67634f999b6d1419f2b6900000000000000000000000000000000188b8a85a6b255408f074b3cab66b95e0e1a1b5b8965034246dcc196f2bb84aca3a78907409826370bd65cd4c4d0bcf30000000000000000000000000000000009603984f6d9876e9c235621fa817efe45727fd8c4f76abb7b0796ae721701161b39ff7cab4c57850014e7f1750954ab6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d40500000000000000000000000000000000025809fb06c8a31f31ca5b4a5c795bc93355c78d9a2a4c1d707e32ff2a71d94cc1bf7b709cd5d6a183cb05fb6b5f360c00000000000000000000000000000000127bd8c9ee6388905ffe59bb0fec0e42b4aa44be74e5961dc2353e474baabfea86c41c6173db413ee28681a6bfd3ccbc00000000000000000000000000000000181f40dd8581b9adb2981dbcae27c7e906138569ff41a833ed3e6ee4fb0baccf2ccbe5b28ae2ff8e08c4f534116b58c40000000000000000000000000000000005cdd822cb47f35f31e0cbc26f6c957d51c6880369af94fd84daa1f1ca95e41e240b910f031585842fd2dfb170d618aa88e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000004a06984a3916820368076ab8cad6ffffded2cf1e67ac33f539ea8fc7a79580c1969e55b2a2fe3b31de912d6606c20780000000000000000000000000000000008a1152a581b6fad2a23aa8b0b51cbe523e701193207c896d08b99a672dc047498e565a568b79f8f9188767ba95212be0000000000000000000000000000000003539e82e5b88ef660b6593fdfd9591ec23e7109642f4aea0570f1f8f8e00822d2af277632ba74910459535b35ad47120000000000000000000000000000000015d3441f621c7e6922c489e474f80ebeefbef66cc59e4350b6f803e409034b7f498be2dedc97d902590fc1e296fe983c26989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000f775e13276c2e32dfde955009422557f332fb42dd9ccc3246d2b080e3ec44d910aa734478899698a9b04f6fb1a8f922000000000000000000000000000000000460ee4df6dd0184bcdae6d53cb66967c2213fa878a829c3196664f8d594ca6d60bb2a56f93bda3b0d2e6aac0a1a222d000000000000000000000000000000000fc9bf81d4cc80ba4e4df7307f976c2ec1ea2415df3c263cc970583824cd83703aa994daaa6e5c20450da2ba90a242830000000000000000000000000000000011f08ecbda9a192b232e8330ccbccb16a26bcf4791707f2cf52c2e11a8b3993221666563a772d82f4665804275b03b613d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b100000000000000000000000000000000027fe7ca0fdf1cab9a52e304e55350195492abecce4289b0f1c02235412bb012803e7eb59e23c665ea86dd4f74c35c440000000000000000000000000000000011301ecfc78ada92885bcba8af75da6cbcb448e0c49511f3ea306f4ab944f5bc114e72f473cdadee2d0e84021905c5300000000000000000000000000000000010eea529fd3162ad7b49638a70f6f2c26a6844251b2c2f9f8ba54cd334914e84e5a1ba9c7b4e7a8b9cff1a909db78bc8000000000000000000000000000000000b8a6235a7310d52fc8050bcc484e6ecf299099e193f91bea9db31fae71fbd14978984a9e6de10939d0fbba96314b0a55639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef", "Expected": "000000000000000000000000000000000de312093622aabdc7523cd72f568060f4236c7287d61c3372bf81d9bfebfda2795c3182d508f0268d8f445f6ea0a5f3000000000000000000000000000000000b027f117583406916a8f139d47227bbea28502ed0df91cf0841345435376c944a587c3b4bd60f8ae0be7c7bad1c8199000000000000000000000000000000000e9a7b96136b26b0044b11288d35969c17146241aa529e581a8fcf000c33fcfff2dfe1e55c0fb63f6032d0b6b0cf81180000000000000000000000000000000002a442e740ee390d87ec657fc218b76adad7f6a766cbe8f34f4824ecd1587deb3706af77a95c1d5f8e79eab1dc482c45", "Name": "matter_g2_multiexp_10", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f54bcf1637d03854cc2b785e52bde25de7e45308048ed8ec0169069c2124871782bd9d26471014d039c9aa022e1a99d00000000000000000000000000000000106698139b096a5a79d43321ea64adb783011f04e5779625c9f77e5c390b46ef0d249387e978e64529bba2db8d7aef2f000000000000000000000000000000001668d5261a4ba37d79c76f44eae9ce2aa3e216c5fbf6cd2e90c6a73cebd8b59600303afce70de3e83a08c20de4609b100000000000000000000000000000000004b1b122cb55e688f8297913b84d466c6f3d99c09f4b039660238c8bcd0b7f6977851a6ea4b1deb01346db06d75180c142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000192a28dbc40d5ceee4d33b5c2778cacf8c3ed7d3227e7ea0d6fbaa7cd4a81134b63415f4f1960656b1fed15023ce3a4400000000000000000000000000000000138f296c45594a930b949756d0ae14dc9a720bb2bd9e93c7895268121a086a9d55c10135962a172c02da1eabfcb8caa20000000000000000000000000000000001605ef8182fa13a09a6b7661472296af2b0fdcfd7b051e7cf1d9e6d7c7f4ad9521d7732733399bfd5d09a088f25d215000000000000000000000000000000001928f2e5d47d7273e035114cbdeabaca724409a56056b4e95a4ca3b2222716b3a5368da3ed406d73f43e9571d1e04902253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b70000000000000000000000000000000009b35f132a903579d82cae6a321c1ec7fb0281c3e82e9af05c3b2830ecb4a941da5b1637c1bf0fe9a39fcc9ceb0d09d8000000000000000000000000000000000eef9c0846064c866ae07b3709091b8bd48bb6b20f995b44fb49e030b5cb6d78b7f8201704b53697190a5e36e9a4541c000000000000000000000000000000000a98a5d0d5640d6399a3580036f0e5cd693a7cfaa26438a00767d5ffc0777b83c516316d9cd4597cf8601544038f4d9a000000000000000000000000000000000e59541068a62f105a0d26a5f79fa5fa8b41b2211f1fe674d84dd853663962d64a7f70e785b51ac3cc07267c73400fe6acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000157f58b1c7152a7f931bccd9a79073967ec28855a6d74fb8727f59c5e3728fbf07a5032dccb28eb8d8b24229f2dc1880000000000000000000000000000000019f41bbbb853edc1fe3ee82f901e613107dd4ba1d880284ee95a2c4cfb2220ec1408f8bff14defe59775136bc75b4a1f0000000000000000000000000000000015538789157505a0798aa36fdd171e0bb14bdac75339b35805807c18bf9175d877360748f97a8570754af0e28e89df660000000000000000000000000000000010500aaa99216aa979acd66c5b0cea2a6a973f1cd10c412e823c61cb897bce54d783a6c0acee22cf9052166a4bb5adb8d9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df000000000000000000000000000000000439729e13e6a9b5baafdaac65783ce79a5972791610a333224e61104d15c746d7cf8350e619f0f72cb73635f6795c5f00000000000000000000000000000000092e3c976a4a5424b09e50e6513a9e1f427356ce161e742be31f0e589e9ff862460d41281f0bb2d27b1837a70a5938fc000000000000000000000000000000000e0e51e92ac3cabfd999cd72b67cfc488e150b11b18f9a31b1c2338fd4f2c58937521b5a107752c342e67666b99fc42500000000000000000000000000000000023d8884aa3f556e98e006960293230ac966ad18f3f715e6ab31a6bf0872c04e6f115fb1608cd87ffb369ff31012a11705fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c34000000000000000000000000000000000be33bc145611afdbadc636e9d7cb7e3a9c92c32f6944a2b7b5f44c248a0754c174e3286ad307fcdb2ea02a3578aa588000000000000000000000000000000000457de1fa8642d302065319b1d32009c64e7d941fb43d1b3cf455248664b1db516379df87aee05a651c132eab8aaccb5000000000000000000000000000000000a711f3bf1bda60ca49271e8a3143330cf924328d3ac6f7a802c15be1d7413e300f398274f338e6bfd0225cd8ba25fff000000000000000000000000000000000a786c5c7b4f1701e292aaad9b2e47bb883409aae0c44ae813ba48f401f4e2146ea0b1d85f2ce862b6ac9ad3015d4b14d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee1190990000000000000000000000000000000007d03697e195a6b714fc9785b49e54e219694250cf5fe77553434eeced15422de3985f8c736996c1763d4b9248a7a7e00000000000000000000000000000000015841a70a168d2f356a8ad929e2d1433b782351f4833c51b50f3a1af48a85468c2ec02699550d21bd919203df73abeeb00000000000000000000000000000000170902520080c46faae2bf35de396d56921bd0279fc889f0187adbabb9ae52b849269d8097d5b3f331dd5a817f9b2ff40000000000000000000000000000000016846a000f037eaf5953b7c4b477e441ca4fa738895aa24dfb0ef01a4c8fc21a318d40a9424e151380084578ca413b3344c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f60000000000000000000000000000000001a50509bfb12040c0271b231c566d13510e6ba84448e59685f5bfbf5b008fdc64cd5e9456beabd23ac011b071e3a5fc0000000000000000000000000000000014a964c9faf1752170ca40cff1b9b4fa17f8d2b56a4c4bd7ffabb65798771cd624ba61ee43160e70731fb9b07af8ecc2000000000000000000000000000000001822ceaae7bd0a734f57b67e4834cfb00a6b415459d81c7d380a2e5b5c795eb1b6d63ddffb1131cdfdf0d76852c75a70000000000000000000000000000000000c5a1575b30e5470151ba055f577a0ea49cff869614c50194829e53a3e1a95847fa387a0f45d537cabef3a5925e61c432583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000018cab86a0d70fa30b4df3e05a91eef57f6505cbe4bb7284de56d420ef3bf315be9249eedfae92561c643bac2c92301ee00000000000000000000000000000000098ca598ccdffa9bc9d464d51b46ed8a8f22a87ef408cfa45fa7f78ae2dcb9f861d9d6a571f6fa702a71e783ee3395cb000000000000000000000000000000000c073c0a323c3051c302c0558463a5c030539d74b440fdcb16b42ad5ec097e10c16bd9a651d149dd719fb1fb865420a9000000000000000000000000000000000164e622bfb8ecd5eaf691abad9db38ccc64ff0fa1784d26db8c8fbebc929bc6d4dd471321e01233d55fb4a9661780b5506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000fa48147388181e8d0033004118848c50c6425f2e5f91945a17abcff4d11928d298c092d60184e75e67c7ddb9eaa8255000000000000000000000000000000000c535bc54df050c1ba8d858a346d3a644e03fe24873b7dc3e23518d44b06fcb3f52b4be6f11d3b66f0180a0a95dddf680000000000000000000000000000000015e279a2893c205dadc8e1cdebd9c85454cd4b5d7537f984c8f9d451f8316620279357e218fef87339f1728fa317fad5000000000000000000000000000000000316e343ba68c8a762f4c8f2a5c20f16abc4a7a8365556c1625df832219670619b6dc70727e9bd9a64ed491dc22cb9d57f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca60000000000000000000000000000000011dc72100cdf676e41f21015fa7c57897da8260609467ffd38c17868a4dcd2bd5d4d72e89cd0db2de83618222ea3b5cd0000000000000000000000000000000007e074f73287faf304f618478566b91c8e191b229ab40743081342e676be09c2523681cf7ca6f7a396f8589a4ae18a6d000000000000000000000000000000000ff753a16c16bf0dd1de9fa9316694214aea6f99b81f66b6bffd58837c00d7f5632ed5f8f4cdf32ec59c29241ed5e28b000000000000000000000000000000000851e26675814612bcfa639fe567633e1960578a0c8d2e6568418f633eebc109e6c8af97e77bb28ddd47c6bba8a7ba724429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c0000000000000000000000000000000009eb2f172db0fe9ac0332381d929fa200a97047f6e732570d23fe27f5ea3013fdc52fd0b5ee74a4387af44647b75f956000000000000000000000000000000001355f8e1cf45443855f2d62dba0fe45b2bfc4e0d06aa7aec7e4f7f9c4e25b33d9c46a01c224517bac9a1390a9806ed4f00000000000000000000000000000000179d47a62a5c847f47341b1ba58f2c3b073c5282f925f57efed1fc43db04185955075255e4e4f6c209757ddae59101dd000000000000000000000000000000000ef5f74d4b13754ceb3b468879f1a8befb8bbbdbb143eceabf2dc8e68fe6cc8e1ea4f3eca1b23a1175c9f5f5c4c20d3454a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a0000000000000000000000000000000005b70a4d5b91b85971aef26b1521e12904b7ad224f25e31ec6ef59856cc702043a3eb975bf21dc8e4fc55171a3865bbd0000000000000000000000000000000007cf7c3e75a837545b53ca3e175a275dc6fe42fb88678aad45910d150ea9c6c94eba615429540348bb2ba8efacbb20e60000000000000000000000000000000002eacb469f5f8ee6c9f557a6ddcc854e955c5b9203b4ca5dd2e097d3e021479e13629863eb5ff17db46a17d3b0227f58000000000000000000000000000000000905e66f3a051b304b110a8682169fa749ba0de7763d3af7edc3e40f2d22ce7b6aa00cd06d2c82d74f3a9709d955f44e13814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa0400000000000000000000000000000000060ac9ce51426d360eff0d911d9f97a86494340bc5c5ba31ef146b55ad3633ec57a700f04b0cb9d4e91e13c2cc5e68a8000000000000000000000000000000000df205ed85e27c25ce27270384d7c3e58c4e0a9f214d74cddfbc7904eb3115e7bf204375df7558c3e65f7a81a942c5160000000000000000000000000000000007a220d42ca8906013479442d7204457b3ff37c9ee70d64f9f6858ba788b7fc13b71d33ad527c6fc673ad8940b0f01cc000000000000000000000000000000000ad481ef549de13b174d82fe88fa57b7e31ecd8999bcdb0c7a8735ab619a13b1e684b9473f0c59c734567cc08c76ecd6aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000000366f604228e2dff2348a462c56e0043037d1b415ffaf155e72c559d185c6b0a0d125585d060f159a8cdad959af631f5000000000000000000000000000000000f69e829a0995914ac122299d4424b4e2e120fa4913939d2f18f9d1496e7255d00ff0829c20521ef47bb0dee06c28dab000000000000000000000000000000000a3efb4a376281a60f5246d8fc10bc23cbb9cb71037f8f57271a9b01f5e0340a562f9acf0e9a95b8c65ab7a5cd95520a0000000000000000000000000000000004a4ec86e2b04bcb35c7840d85cd1dfaa88e17ffb557ac591640ed8e563cac891793b92e349a7903c6c1f88d26a01c88c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e000000000000000000000000000000000f5bcc27c243ef65dfbfc0de6d431706ab20d6cf6408ca989a2bc1c52b78ab63de6f58b70bfcaf6878a2746f249b6b160000000000000000000000000000000016a4c9e8ad0634e8afa8606a1a7bd1d8cc0815dfc6906b6e6446e0ceddba4a4a2df979d27cd07b8982a12550bc700fce00000000000000000000000000000000051f8d972362caf0a8a39045bb468112f2e73afa392079f8a4dc4c3a3cbb8dc224c21b6633a5ffbad08796ba2f8df44b000000000000000000000000000000001825aeffda04705ded9c702ba30d24b9fe8eb7cb106ee5d4e4ba029dcb57bc42c74e74e92ef8360cf130590b838645429680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd", "Expected": "000000000000000000000000000000000d0ab61b29ddea1aee0ca4e81b5369f37cf45be383f64ba0b1a5a74b790d7264016ee671959444c94b8e6291c5158ea90000000000000000000000000000000000152bf3709c56b3add8e3396d17abcfebbcfeb230529ea8144d6a120a0a6aa83cb284e40ffb9fd9a96f8a2f7244212400000000000000000000000000000000041f516a7cb2a7137746d028b0739c79ffd8f7535f20ba3728ede32504fe058baaf684cc7677967aa46777818b1fb6630000000000000000000000000000000009f1035729c55cf6ee090983a54d8c0574bf96342901f471a2e5380f11f235a075b0e157c38c456b6eeeaa10b87d3afe", "Name": "matter_g2_multiexp_11", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011ada4731ae7df493e405383603a8d79ef77f3fd14fe7b8bd9d2afe068998cb712e84927d5e6ea6e94d7f10270fd193b0000000000000000000000000000000008a14eddf88826dc3be792a0c1f7395efdf91454cec7e26c89f6beda37b194b706dbdde8745129e821b6f4b4ea6118490000000000000000000000000000000011c29513e8a826e6b3eefaa20ad841605d04b813cca282fe02dca0f588b9a579b2195b0b080cb6d12c1a7881008117f8000000000000000000000000000000000689c67d05ca379367fec99439e3806f827218ffaae995bf38dd8e2919fb2e751f426525cc2c6ead3b9aff2e377fc99e1ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e000000000000000000000000000000000dd683a8e4ad54b1a95826a3000750c6e3cb250ab5d6add63c21b182d736b220d917d4e70044ec7101c3bf8ac620e1dd000000000000000000000000000000000f3e411cc6800b304fda1373ffa60c7718e20bf3e2e5f9784a81b47e398888b366e1f04f48f5aa070a661b5e2148d4fa000000000000000000000000000000000b0f8d0b695e000158ba80881a9256ed9dda5a7f53b550bf3b5c67ab160060fcbf5ce07fe38253ce037abedf4c6f08d1000000000000000000000000000000000bb92d407c457e9ea7b9851770d2743758e162dc9cdff2dd54b8271c046f642729cd2f10576013adac84a46d38623b932943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b100000000000000000000000000000000023e880685aa69b3480bf2b7f2aed1181e094322da9e79c9263d50a49ba4fca713740bdb55886fc81c81a51045b35139000000000000000000000000000000001707049fb8b7ad278be2949b9eae2e28bde9de1d9eb964eae582541c2d7a8afc4c1489624a0919047a167028b8c77e3c00000000000000000000000000000000062dbb2bfce2f67c32b87ec2fa01ebf7deddfcbeda2fcf0ef094b1be77b7411f657e745350b6d2da16fc83a96f6f20e500000000000000000000000000000000062daeba038c7bc379f56ac371745b91fdfd5b4cbbe50d9619bf1d077f3cde966f81f9b851ebd206f2609a780b6dbd681551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b553826dd9e2252c9da74c2bf1bf850df3f9c37439859f93df3fbceb7cca4fd949dcaa7fff31c9e06f41e51ae0b30bc00000000000000000000000000000000187810711ea5911a437a62e2ca483983bf2535ff9301a1cfe1b4d41902ef689f8d86f817a2a7c77128e4ce1ef6b037d60000000000000000000000000000000010170cf5f2ce08211cfc41bf54cfaa16584f833f7b97b2f6bc436eecc56ef44463690ea1f5c8c2a8f69d93a25206282b0000000000000000000000000000000001e627a68dbab6b0d05c85e49b966a769461ec38c38fd94992839bd0d46e06410fa7a48d418d65a8285f7852e8af4b318eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f0000000000000000000000000000000011ebe2edc3de58a57aa9ab4d6626d7b93235ed24efc3d75c1ecae376c00beffc5e89ec509d243f693d327f7a4551921f00000000000000000000000000000000088ca2fe0651e4d8f3958454640a58ea1cdd804bfd2700bb1bb8e26ac50f2d7fc8c292f94b0bccef5735c4548025735400000000000000000000000000000000154936de8932279cd39ae803a5d814864953f647a5334bad958222de765250e4bc847e02979689dc9cfe1993486b5750000000000000000000000000000000000c7ce07c9746c6d72dae11e243acbe12dc23423f870f3130b244eef34524d547fe0b2c4b704ecb6b2e6c32f5675ce67ff72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000031ea855125d75321a2a86a93e72fb3869dede7531dbcc1cb07ea2a352f3c6cd913275d0d43ccc370f4539f668f205f50000000000000000000000000000000006c4cadb11361f164f5899c6b57c0c6d8af365d902f4575c9d2d14dfd880501ce9ce218544b44bf07f0f04ed68e8f315000000000000000000000000000000000131332638026fd25b1a849c984f9dedd71e64fb52a61968666ba80238673077ac00b9e09817426ceac8c308f475303c000000000000000000000000000000000c7634af796e7aea4d4d83c9972fc822dad951d2473210ad82706ae0aa023ea85c1c467bdda68881094ad2a4f54cb33f331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000fcdbf0083065e13deee2020bb6e47cb9e482df3768ce569f1f7c0e1c6083c97d9f08444e67857c2dce40e4a7b8d50cf00000000000000000000000000000000010f246e8ffccc2e752049f638617e122773a6f10220cdcc0603d24f1a94ca7c100f8ee2d9bc7c0a931fa0385eee456f000000000000000000000000000000000f8b68941df75cac3d4b6b3bee43fb357c8f4e56309d8509fdc62620a085d7ee58f52c7dff28525a449cabfd3b7ab3dc00000000000000000000000000000000019f934ef0c7c40786b073d38cb3e4623544cad59cb63440d4a6e76944d491f6b982e3a5e84124996634687d4618418316d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000c0208c1f3653fb3a5e2acbbb42f2598b22db1a714d616ee6bb501c3338e80db34d517c7086d43ddc77e0134dc5a4f290000000000000000000000000000000000a528245342e44e36f8e02e7259749e63ecfb38cb0609075e871701f2b3bb0765277b78d28cc3ecb7aa8c9e3b27eaf10000000000000000000000000000000010446583a905864064400f9ef168a122d179d46a058525c9be8a65a5d2ac5e967d51185d4964f81a5571123717210d050000000000000000000000000000000017da91a1d0358271b11a0aa524341ba1ee8c31bed15efc4c9183d60c6e1842ec4383070a09914fda991a63d55efa8f2156be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef400000000000000000000000000000000176c64efbfc9958b9c8e71b55e9fdf525d4e5a0265ff01ba95bcd5c6093bd063726f8e277d00b138fa4d8c8f80afc4e200000000000000000000000000000000183eaa6c3c605828852ab5e8a9432bcb87411dd18d574cc2491f1a280e7a267ff9ccc80b06c22e95107a72f22ba2fafc0000000000000000000000000000000013319d3a8564ffcd6fc7accdded740127ef205e8299b390d21e96b2609cbb463569c878f36191d43927868b06dcb912b0000000000000000000000000000000000fbde0ad8e89f5458007ef6ba0f01d0aba04217e06745a5571eedaf544443150f59117b56937f533b4974e5d57c41cbaea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef000000000000000000000000000000000c044a5116e175ca1d1ae59d400de24e4f47132251b4b3dccdf458623c36b4d3d83abc644a2247ac4d0e3f195d12e7b000000000000000000000000000000000048dff6bf65f158b19b992167ff8adb5c858a154bd68bf0c84e41351bf47a8f870cc735d1be5d9afc62bbcda2fcdb1c20000000000000000000000000000000008c5539746d2610eea22e79b3fe5b33a47fd3bf9991d34c6f9d824a46458480b735c0051d7b4e4909fdb1f2a1a4e4b3a000000000000000000000000000000001936558ac97acd903a29d07c4aea399227ea13fd6dea820813c5519412c157e1a477fcfbab60a787c6b3834eac4522889ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea0000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000908ebe27a1bdf0b9e56325c00ea3814527005793ea97eafec541c01cf2d7c909d2521a5fd475589a31e297cecfd5e7000000000000000000000000000000000017e3c40c60cd369ce5a90f6c4aff14896cf73fe06432e71940bd8086e36c2353d6bf9dd414bcf92889887e2d49fbbf5000000000000000000000000000000000ded856e5b2b139487b3816351584f06582a933af2bd4573a89aab0a41af01ec1cb928a7d8035228302032d399bc7caa000000000000000000000000000000000833b77c5d5c98ad95a144c0f167fd3bd62b03f4ad721561ed1d84c7137dcb19521f781bdd3ddc22afdd52c75146e101f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba680000000000000000000000000000000005c6f28c5ebd981fff3aacd70eb18f134bffdc8507d1a3aa153e5787b68fba7f4a94c43045d2676aaa992754783ae87800000000000000000000000000000000148ff39e8062bd488accfead42a684f781c4ee579af6204b5b8dabad9022b029139b1f3670fc270710ced9a53253850c000000000000000000000000000000000ff50eca1a92f123e2534b3289f37ffd5d4e05f7678017ac20e35c2deca054dbe376c5529cddb5e58973f5c60914f251000000000000000000000000000000000b58298ba9496fe32891f4c1cff25395ac5a447205cedaadda4dcb929260ee55781916ef5e4e39793fa2831142111226d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a00000000000000000000000000000000184d156f881f7d10d2f196b7599db85ee826c9c95383978ed68918756f642a2ed1c951503251b0778dcc39598d79fc8a000000000000000000000000000000000952168761380e8fc90a4966e94b8d2b88a784f6e607c99d9af1aa902506f59d6879153339fdb7b8acda178b9bce4ef90000000000000000000000000000000009997621d4e17c76b7798ef2f99d3c0a7519cce278cf718789cd8227b2b1459af7fbbc93078aa0aa361167b1d1c9363600000000000000000000000000000000005369eb3a77d2e26f9907a2d930f39dbb87634346cf10525733aac8ea10eb918d4043d2a05ff8e80b9c69a670e17f15c629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b0000000000000000000000000000000019bdb390c66f7d28cfaa91bcb34c5c55bf93a9f2345ea396f18ed33ff2221a39cf68c5514fe091f7882e82470efb1fee0000000000000000000000000000000002d0b48d2c0377b0dffca247b7625f9901f86e2161626b4154bc25d6c643a48e9addd260298bedaa80e42caa5b9fc5b10000000000000000000000000000000018a2b0a760652e546eeb42e857ca48f59741eed91822c17692e9c41358b213c82537c9c6898713a13a241cca627a7dc400000000000000000000000000000000079c02f41fca45a56d9d8e305141b4fe8f98d102197e7864065d342e6b07f65b62632e0c12660f37de4d698c0df3d0f3c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d5000000000000000000000000000000000c0448fd4ebe9b5615653336fe0a618fa281b0fd7d72a8f956a5fde84f7d356b6be853bf823436bc0b61a603636db9ef000000000000000000000000000000000dc4f2b4d810c4290e263098576cac393fce137cc901b3be23507cecbda7d86d18022cf8e1a7df4b1298520ae5c9314c000000000000000000000000000000000a39413967b558dd8a6b2bed972687d984fb9abd0662a266680f8c90f1897e2aca1ba37b41d7d3fd47406bc5fa3c5b7f0000000000000000000000000000000000550fcbe5bb75afdd8d5f387798a8e83a8dbb6da4918c24eb2e5d2d8acd3512f6649a4ac9c8d3e6794e6f4f8a87687bc685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7", "Expected": "000000000000000000000000000000001654e242002aafa89c6fdb9e8fe2c197ad2f8aad11868568dd39d68ca35919f94308a80303655bc83fd130de6f9723a900000000000000000000000000000000062b5a064840a5a28b4991ae949f9508586447ad5e8c463593503c0e5857c5233b7ce7ac03e555c2675f2e320e8cee6a0000000000000000000000000000000017d65fbd7caa69629f66be8b201f53baee5ef2957a3c04fe384ae82959105342b52483eba6bcc1442763c677f515f6cf0000000000000000000000000000000002ef8f8ed1114cc9d299e59003c61d62edf8971d65b1b621779bd7b270c4123eb629f56dfa2e2723501588a0caf1847c", "Name": "matter_g2_multiexp_12", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001392409b92282bccbdaa0268e1173e60911754eb3cdc28a52e93f4d82ec99026f314dfdc59b39a4f988100f9c30cbd1e0000000000000000000000000000000016b3c555d5c196551ba715c6c334a668bcae80f5a17f038038d35dce34843f79968a90e2102f0faa22a93d3240b58d490000000000000000000000000000000002daf83727fdf45dcc1a15adf47de3f8a1724cf4d34116f52106a9e6b22dc24a288e89b940cc57e5a6bb87ee70f680a5000000000000000000000000000000000446009fa3555e4a056a820efa7da52117c15eb105af57985d8e9b33b0b22fde6aef9bad30480c2b8c1246519795f61fc067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000000372ead514d53007690843484c966361661816e0d3949b868176d7a9bea42064f49113a74f2572a6dca7afa0642fa5000000000000000000000000000000001199d3ea66fad87074e62a0b77d3fb962db17dd948f30c38f5beb0e44e1cd11d9172b878128e9a64a08394f13cd786f60000000000000000000000000000000018b7db157bb326ee2f72d4df2b1e0ddf0a90401ccfca1d4ffd6379c62acf5d6e4176a23ded2f81653038d56d848b4fbb000000000000000000000000000000000a932cc9740812c8bde33b68d94220690e0f55618b7e51d3e3fc29d0cb9a8d42b8f8e1efbba5984c3c1007c9a80fae408b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d0000000000000000000000000000000015798d10386f6d24caed3859875be5fb1a43ac753f725f28da6b3583bd9c0e404d36265c2305d7d194e2ad84bfd2bafe000000000000000000000000000000000ef2ea5f3b6e03e3c9693d6db60019f2efa4ea586bdb7623f03bd035c603e8996ef2ea7cf745aa31f60679ca04f93875000000000000000000000000000000001792a66785a3087a80c4b8652c1e4db8f602cf75c1a6955f480a977f92ea262965dad84061f6045177c831dc4a3bf8400000000000000000000000000000000006ea3862318974d6347639ec0d70afe748f4edf32b9e437fd98f38eaf72168a153cac180c2d67bac8a358e3a4d57a2b32db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b00000000000000000000000000000000000974da7500df70d888d5876e7c61bfffcdf830b49bdd40edf65a2ff476e9add35eaf9451a2166e9781805192ffd7ac000000000000000000000000000000000cb2e7152b5b40758b18caea356dd8e095f400282881207c4b79d10d741756e526be261b98b726d5cefb668dcf73a0a00000000000000000000000000000000014aeebb995d464f4d77bbb72f15d9078936b5ab68eb8022bdd97d050576dbe46e6010eb72250c8ccf2a59138efb38f9d000000000000000000000000000000000cf7162768e8eb50e21d3c0a076c7bac4920c70f334336037fb40e57e0efa91eb025356ac3f0988a6b127408a02eb53fe2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000b1d6214796b4775c2b50e634a549ed104e6ebc0e032967b17eece6cf88c93aac23059f263faf3c3f38463270320135c0000000000000000000000000000000013ffa3894a36226664ff53ba9256d39c6312303f5cbda6847b4f68c56134b7d731e74bd711014fe374f909a081a7d02a000000000000000000000000000000000ae4590cdcb1367392635d0f8dc6b9557abd16290fd1abca6da354646d8585a7c9432978dc616e5fc38cd71d55f139c200000000000000000000000000000000124a7b5574ef52359b4beabcc56d3286db8c8fe4ca4718f75da28d89a8a95efb878c18b48360dbcb6fb50a9f18f0d559aa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a00000000000000000000000000000000148ab0e847ecac963f0156da025dbc52e765cd8827fd55ba2969da6775649529226ab13ab8537ad0b89e8f1ebc8648ea000000000000000000000000000000001395b1adb6a56b91c3621a4ac5886a7b13ec00f1c74d5317eb74a766eae655e09e269ec48cdf740abc38f4d6fe52dd0f000000000000000000000000000000000f70f77f07ef2909033665bc05cfeea7df6ed55f2f0b1b87d9f247b6c07c7e22f516840efe68005c3953a2702573a9b400000000000000000000000000000000166a334a711416cab180cc498308487b281711f2d1b832c410ebb4c591af54b154fc8c8d7ac9a49a241f7a3840acbc75e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa0000000000000000000000000000000008c19bcbdc2ef26a30dd88f3e35dc7fbb3c81c0224cbcd6b12c90883f3973bd7089636f997e5f213fbdcb79514c551c600000000000000000000000000000000058620cba8ed5b738167e809cf71392aadfe8f384a4cf397d10f674cfa914e9e02bb1518e42f16806214fec52d880f6100000000000000000000000000000000048ac1120d26e4173bb33a58c0ce86329cdbe9df6a6f268c8d5ee4f1d6110f9d81cd50c46256198a2462d50be3e781270000000000000000000000000000000010af13ba791d554720f5075d46d03b55c0c1dccd679cef5a7d439ae868d3ff2780cc3ab151feb72b8b92905a205e630449510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e40000000000000000000000000000000005c30a126c94b87c54270d0f23a486c3b36a8b491bbd805ae0d5f2bea818a87ff5aaed2d5e6317b786ab5a23f1cb48da000000000000000000000000000000000eb2d4663eca7f8433f10e84984781a57fffcb8f9535518721521ddfc7a4958778915ea3c57bef399a453b8ebc10befb00000000000000000000000000000000161947f57d97a858e5b3e918dbb22dbf28629e51e81335a9bf105d0fd660ef80087c8d69d8db9841cc69fbb5e7f81487000000000000000000000000000000000c52b6a559928fe4ad984a0569c081f3f71eed3d5b0d3c14d1a23afa45594e0fbd94143348390bee178720fc603145ab713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000071aa47d392e1a7787b37c52acedbb4632d5549fc11b79919bab7d22f1bbf1c3a239df622b8824b07f6e35e627283b8500000000000000000000000000000000198e72e05388021919dfc1b2a58ca72bf7655cc6c9b62abe3b45cc782ccfd4a2334780e451b8a6b7c311887036813fe4000000000000000000000000000000000e20cbedbafd96c42612e146debae48c7fab4846b20ad0848c4c42c6aa0603e72f94dfc938ed9e3a9886d221ccbdef70000000000000000000000000000000000c861d1878e63e313e672bebdadd3fdbb691cff5fecbc24da895febce2eef0a3c774a8a9d751498e4fc8e2b71daeb40dc040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae40000000000000000000000000000000005f7cd2205fa2e17fb9896efe3fbe110e1fa59db1ae5f8d6b5f4510abb4da867933d4fe3caaadc4457dcbb35f1b9c62b00000000000000000000000000000000126f2ef6022a7211fa865c1dbdd5b84d96cddff424b06647acc462408f2d31f34ce898d76e1e124db7c39e08dab0bff6000000000000000000000000000000000987f916ad6f718695f3c40703c59ca93eba38931b45d7c33c64c9f75556f075b744dfff8a5f21489b3db6c3846ba09e0000000000000000000000000000000013011b8c72f3853738e22957f742b05ec428ab0da28901800f787b7c3678449acd0359fee93c40c69623aa4acfc0a81017b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a00000000000000000000000000000000014b88c0586fa18333ab11a79acab8e12c6257f82a4ed16d929768a60a3a5d780a22101c32ea9b0099aa2816f18a0351a000000000000000000000000000000000de0fde69efd2cea7ae08d6d2443883002e0b4e11da253222429f6ecc67ba8d282eee84d7f46e0ad00b039a2c2ad226f000000000000000000000000000000000aedfa0a5a8b7577dcc1094469233f8b07e6fc32af26841894d498d70c6a9a046ad636086def948d21e39833c5b6c5a70000000000000000000000000000000010ec6aa0efba4995582585bb67f997f60741648156324696312d17656baf6aeb3e2db0d1a272912fab2fe81d139e971cbd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf2575200000000000000000000000000000000004a847c06abc8ae7ce6e6ff0ab856889dd3e9697a75e3cd4d2af9e06d4c2fc48c0562289348ff52f4d9855ad03d83aa00000000000000000000000000000000075673bc79bafa9a64de6bb0e9dd9fa29cdc9c82e90a7348593eec673cbbf22b1eca436ecf767d45852ed888a3f23949000000000000000000000000000000000f3f8543d1e667404b4564dddba4d7c11d13881fcd8ad774c8eab8fc599f55147c353cd6e163cd7b9d5da55ebc13c2e800000000000000000000000000000000069edec7e7d26962d88a89dfad213daa36046bb2851e5d67adbaa227220f29f83ea67cd3747e6724f148dac28308604cf221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000c945e83822896974116663d3e2769f3df5a70d55b8392c1f6966e330951f3cc5688742d4588648a6988b928b9fe00100000000000000000000000000000000003e94b7ff7c71d633ce69bb44d0ba1bfc7c27a5ee618e703aef81a45ad61771a2fa8e3dadddf7c8038f1f65ad7513801000000000000000000000000000000001727d768c1b51066d2af87a9da3e24ea2a75b0f75b8ece70727f9f54ab77d841e7ae01c9c0760f4186d02a28d6f8ddfb0000000000000000000000000000000000a273f9395cd49b646e90fd2526d5c93fd46c7366b715546529c9edf5cb3d274c9947c21a03add3e7b20612636a6745ba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e000000000000000000000000000000000c996c16a16879bd3194ac366bbd11b5863123ce6fdabeafe56407600e5d49c92ba68ac1256e1515dc9256de14ac26de0000000000000000000000000000000018c584d8a4f14900b2fee70b50b700199ec2372b731dd1380f42ec7fd3d01f0c9a007554059b85946c1c4f4e2fc504ad00000000000000000000000000000000073d6c7d671762e5398e4c9d57f6b68c3d97dfe0d01783f124256fac236f03b774db58b79cb4d5558e1ebf18bb9e19680000000000000000000000000000000008eb2b95e17fdda916b08ff2819cecd2eb031f41c8299b308339b7d9836382ced75e8eb1514a70356882d3a43227a9bc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001029fc28cd502caf3ea3619f6fd04bf457e6a452b5cad680ec2d4f8222a5ac2daa92b880bda76016973494e605ab28c60000000000000000000000000000000002c672c7571b5d8e99de6e47e0a2eb71c6d9bd12baf2b083e6f88598b32c4644d1486aef582c5936e622058bb141db1700000000000000000000000000000000033cda383a77d5b3adbb0809e834993c56717f81f8c66ad2d97f2b298d5a46f7b29a74d35da09271b7053a05af096393000000000000000000000000000000000132da041c6e3e1d68bbd2223f8531eabde8e180b36b2cd0ed4fca248f255cf3eeccdc5f61e1c581ce54edcfb2b73e0787eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000009721f22bc49f68d703a4dfccc3bae791caaf0d73892bafa6e9da465ddaf0fb1a069ffdd55306acff2407da64c1c5a0200000000000000000000000000000000056c0a4804a19aeaf1b4fe52064e43de8e5d41a8d77de054e2cfdff078eaf468d123d7317818d1bad1bf3469c0070b680000000000000000000000000000000007f1f318aed043d9ad7bdd53eb6a8c3167240fca75925b04795210700463c93a66ed64851195df1bafbbe4227d7db5ff0000000000000000000000000000000007b8945e258311e7672e842b91b540fec9ef4a79296956a5cba3749c0ad95ed83d7b0b48384ffb3188459e997b86695d3bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded", "Expected": "00000000000000000000000000000000086a1ab4c19c27f70aa422e8292752c50b365d6fe3eba21e8f2ed51f283df0446020834ad27c18b5c7285d1156049bef0000000000000000000000000000000007288f40fde69bd350ce1f4d0f68e645f42de319cc032250b76fe4fa305341e244e5b2366751d5311105e3ccd30e701c0000000000000000000000000000000011d0c487c4eceaeac009b694931f8eafaf8eecd6028f14a4de33d2940bbb747025eecd509564721b50b7186910f81949000000000000000000000000000000000366f0c901fb859b4bae006fbcc9ec7e456eedc7366c899f68090fbd457c37b03ab99ae982872c7888b65c1a056c134c", "Name": "matter_g2_multiexp_13", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000072f3f03bb09ca30239dd8302b05e0d9dc4e43ea33e865864a82578c35eafcf6868bf0cd9431b92b76f00990b780ffa400000000000000000000000000000000170b76cfb7944ea5ea055aeedaface3e8f0fa4d0ff657fb9d5311f3af6e736da84a5e2bef5188e20f76fb42591267fd9000000000000000000000000000000000d85300009165a8da9cb8e590f7f8d372e4264df150b1551185c80e49dbedaaf872ef69c5763fc3713d0c087c89f21050000000000000000000000000000000003ba59b682174ee61630df95c8e2b1c48ffc8f7f8508c21f3bbe8f7bb3266521fcc06c8f90fe5126d872707872db6d59f44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000004e8f16480c2f080a13b9f2b66e6480132d76c4ef76e8bac995a8e33280073ed5610865260e154b32f75f527d89620b3000000000000000000000000000000000f9ca48d732a8055d22fbebf3d2bc1e1c9c815c184f594ad2337731709317ea6a205478ba05ee9271d35a19dcad4db5b00000000000000000000000000000000078013b9290284e7ad528a1bb9a2a64b3ef43964c7226ddff8ca16ab17b4a2e8a2a7d921ba924a718587954f586a954800000000000000000000000000000000004aa76bb1122116cc0c04d65265d8652f08b411632a732a9e66d7932801b77c4ad398d582e446968f7f4966e9167894de25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea000000000000000000000000000000000145de5d101498bfc7c57830eea2931663ca1165ec85b77654c866b04ba6a28bfe710c1aac9876a68cc6ca119708eaf0500000000000000000000000000000000096f9df9d5723e8379f2d09c76a3fd059be47d2c2ed8905d333b2464f72153c5f50b6345980626358839ac691c26c967000000000000000000000000000000001788ffa765c19758da6eb6c38e793190c64d4a7b116576f6827fc090b0f65304988f6a95cf4397f82b7691fc43960ee8000000000000000000000000000000000746e040d7aafdb06a31ba3d7b590dd28f0678badc261a93dc7bd9a605047ec67ba86b2b6dd72637a449872674d6b5982e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b21000000000000000000000000000000000d8f3cfe1cbd2629f3899313cff16ca3d8f964ec1cc0508341936a7b3b49240db1116b2c3de28f9bc45cdfacdb5fd98c000000000000000000000000000000000fa642ed31293e44211b34bb28bd5b389ae6d0510cdab46c89756f31795506fccbdacafdff21b0127e80557e5ba9afdd000000000000000000000000000000000715a8951cb358b0d8cc63377799a9a61ecc85dac795d726fe60e429d492c9ca843be2a2633c17f830f199335e5d7741000000000000000000000000000000000b88a23fdac7d35fc135b45d7565854bf010a75f072b32c57ca4d0979c111aadd84c71df6792dbdc8e975ecd46a15df2e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a913000000000000000000000000000000001171be5820b5a19c045abea399f2b8ab9905d2aa367c6c8c0f84eac132d26150b759a9c029414f1c8f7e4880214446c200000000000000000000000000000000147f0877321f2709183f0b617a7c5ce898db508a3ced4148cc9f7af011fe8040e90885ce817aa956d9f5d19dd968f6220000000000000000000000000000000000acb005c11481b214a17e3cca02c2af266e4c8cd928e3c4e221d866e9f296a2e913bf34c4e051c7503a5e4e7cd7449900000000000000000000000000000000125f45d0af1c010cdf8438bff0f406007853e566fa646df40a581f65496197755eeebaf4f0f77e1e936f399dc4c6c020e6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c40000000000000000000000000000000019cce3f872af5cc515ac4cd7825a5318ead5b464d50349909a70b415a8950206974ee0d4203f208d8e6d14690158f5720000000000000000000000000000000002e08e8accede11afe3e2d085f35c08d7d414c26a9caa992d5a090a43c9b0c0cc1471f3693f9d342a973da65189c888b0000000000000000000000000000000008a984ad2ca60c492cff2e95d541d71e33b269b10d3df107c0513dad5af511c51806068da6cc7226df1cf5e5a2fbe707000000000000000000000000000000000fcd3ad75bb0a5c046cf83be3d973bb3685bc717d7b8262fb8205935db6e632472496907f7c965fc6b52042ce69999f9e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f24350000000000000000000000000000000014ccaf7594d8ff6157f9439ba63480d3d07f44e62a86caaea510d0ec456cd8c6c4b42cf9e38713213eb4942ed45df2ca0000000000000000000000000000000015c2061c532cda006addd2fd6ebbae458197d55fb336f75ca7decc05dc6d421a65495b71ed11874aaf24a0ec13a7c65000000000000000000000000000000000101f953aed7f23b5b6208032f05b818e0147079b7764aa3134dd9e4a316bbef0309ac378ca3cff3bdeab9ca56cb78e60000000000000000000000000000000000c76a2bc721a4d3ead95af79ec24be9b7624bc80d7debc07e388e52ec621082b9a69f48d157b168af4aa73629697f784c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000761832bb5b530b80c668234ab5996bdc225c0c696ea07dcc61c330320404827ada9d58d658e230fcb39a96b339b830e0000000000000000000000000000000001198b85418421d96ebfbf436193b411a3a89c206d006291bd23254ed5fe12ccdad15725a34d962005c0ae60e202bb86000000000000000000000000000000000c1d7ab83b1d2ad57a407e248492773a357c06b83c16c6ce1490e84bc4a3cbae395f160181d2bcca3edc34b764754ab0000000000000000000000000000000000f1e9f0cf96d7671763739b6c37fd442f0e816c49d9c8e001d322397e9d6741dbf8769ef9eb83d08ab024294e279a02838cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d147400000000000000000000000000000000063049bc3282934e29f3bb3dee432bdad6193a5d2247270e88887cac565f4b986e1b3b2af5387cfca64f0d50bc0ee1640000000000000000000000000000000019f0f05fc7f8bf2f0b8ed375690b53b6dafd0a07c49fa55d36e040798334700a3aafc4995bb90de9c4dc0e077ee18b58000000000000000000000000000000000fbe702d148609dc8feb3ac11c5eac8e32a2f7221aa135cc33a585e9f4c97afa1658d8962fd96e26e0c4c1d5108229ef00000000000000000000000000000000061fe418d3b440e84728091a4996119b515118900f54a6f2da2ad5592f48ebc17bba50b59ecf435de3cb892a123ae9d18a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f80000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a4553780030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f6000000000000000000000000000000000549f6464b657eac28f838c6a8bcfcb7a189d6b3b9712e19c1a23503ac209da5f2ad4df83acd505b0231f00eb88515c70000000000000000000000000000000001bf4a46dfdd70542e9d8cd6d6215174cba28f9adbff31c02482ca38205cb4afa2f7fd65ecf57b39e4ee5cee320e33800000000000000000000000000000000012d04a693d565f96566b7c313c47d272fef0ecc828493b0841d58f6bf690a77cb72824a656442e288460ecca7cf05504000000000000000000000000000000000b33eefd5df8b098e6505cbe655a483ab5c6e417a4ed55420beab95e8614c8538dca9296a7848d6aa0495a173df6d0b80d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000000fdd9702ed88aa857254c3ba50b484bfc324e583659c57055e4b09eb1662af2f70b547a1eec139193a0d3c75b565d3b200000000000000000000000000000000193df0fbc5f24065008b5e98c4c4bf9f1e743a6ee60c3700ae4a9108639e540384eaf1f9d7a60b8b6a5d79e1f34949f50000000000000000000000000000000001022f8a254d17e448cadfad35b7a54dd2fb319c8f9ba219874bd8280a5077301ff4332d731a75646cd93bbf31331154000000000000000000000000000000000ca1eb350844ddd0a65a4ad56e1a96821de2c6633a4a45be976577c223e367853e2b1ecf2cc40b8595ba5591ae8e40f3969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000001f9cda056a0f8803be581634562e975223b5311f4752b189cb6bd6df1ca5e3824bbd2889b9b93da59e4f08d482734240000000000000000000000000000000009f43c25de25c5d76ee1a03691aa434de6a063bb3a1133b045797a279346fc938dd2636abf0c4bbcb528c9c28d3105c40000000000000000000000000000000012afc29245da8bcd3c0d96c4ee61617cd9ecf42a47c2ee822003af26aeb4e4de8e432ffb6b2d8241090b814401a8676100000000000000000000000000000000053edfd98742dc70d510f1836fcffa6a3ba9ffd4904c7f5559b48e49dd21071401362d0b39bc0d786b7ee2e84a76af0d957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000001dc3f016ea1a74ae50c21c1955ca1eb4a911026a1e72b316c7bbdc708caef63f0c1efecbecce8901d65bbfcaae429da0000000000000000000000000000000016ce9301888808323c9baf6402d7073fb85ebcd389334cc69d7947e345748ee44b2d6aab3ef818beb21b54a19ae4f5b5000000000000000000000000000000000c49817753eb6459cdb4bc737d3710b5f044bc544c8d92c8ef138ec9d83889664267e1a5691f4bc3fa235ecca2a973a500000000000000000000000000000000074a8450e35f1da18e6de05960e21b7059ece8972c36f000bba9e24488730a44ce3ce200c437e06703addb3b442a790a8e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d5000000000000000000000000000000000bc1f2e9af093ae8235c93af098e692e697ea0ab4c8f53019a6e950f7072b56d5eef6b3237710f1dd1cd1970668d06d0000000000000000000000000000000000d9a63f7a13ff9755c6a3832e3c4c852919514523092367fab7886cac317e564d57fb4042ef40e696edce868e697c45700000000000000000000000000000000129a30657466460db13575dca367105c27d631eead330319b084adfac591f5b3b94988925d778e6d4645d1d2816baad00000000000000000000000000000000005ad64d6e761a9a301589547929f4952ccbfead278cbf6658255a075966340f185d5f356679fb02ff2197468ed7de19a71a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000077d1e5b35c224e2cdc849c02e800c0b80d1c19f3d74d9eec34c40f56bbdb9e2b5d2ef274991dca843755f91a50826fd0000000000000000000000000000000014f3b653e0df0c608b75dee3496a7af04a828e6fc5604f16ed49c39686ec757e96adb0a667853006a8331c3d63ae4ec2000000000000000000000000000000000aae011375b337940f2a53d9091d3581e8197e79251b19c7fba01de987721a9d6fa694b7978f0abf877f46ec26147c98000000000000000000000000000000000aaffbd468a2eb86a3cff59e2e9b7ab88286d2bdd19c2e789b1a68810f0cdc76171a2661ab54e81b17643ff0275eafd72d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641", "Expected": "0000000000000000000000000000000010a2434fd3150f6b9b491d3a51226bdd457504077ef2ed5a11ceaa8284900d1b84039a34d5239a863809369bf20a704c0000000000000000000000000000000007934f34fd50a98225fe6578d3f34ae5e5ef5e104bb9cb398b2ca4f09048ec39cf52e7fdbac48d45212e9e4c1dcc6e120000000000000000000000000000000013ee70f1b52cb8b07ad957a7565b3e3c56306392cf7b5aa29047b23e5b41fb3239ac3611bcb16ba7c7ffc4213e3d9cc800000000000000000000000000000000035840f8ecf56359dc3239945720ad08702b4ea8d0fa9bea3bfb234431df4618e960a1eea87da72ba4d9443f14bb87a3", "Name": "matter_g2_multiexp_14", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d04bb9b75bc8078ccfa85e27d32e137ff5f05f9241b19ea835bba2fffc9255a4a3028c0caf9c32d3d27666e1394fe820000000000000000000000000000000013f59c3d8aaee34230cd7715a32e4a45487b9b16ce68d178f95461229a4d0fbe7d31edc7208a7338eed08e65847f8f29000000000000000000000000000000000d63ca2bafaa54e93ea54846b26f88b4c6749953f9cd00c670914cca279b794c1fb5e2664fce44b8c04f01c68698a8b9000000000000000000000000000000000b5188b4b7ef78d3662baa01b1813b4a0b0f855e11397584a460d56f594f11ff2e5d708a23a8e64d0ab337c7076872527740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000019bae57568c879cd743f7def43b6b994f29782c6a0c74734f35b97042a916da00daaea34f321481e6cc4749e23297c1c000000000000000000000000000000001853fd11d4688b027146a07edea647502e80750de4e5e2d105faad3f71ccc90badcc750f76f1b02db3bc0a1a635b2bbb000000000000000000000000000000000b1e45b90e6a7032179236f13f01ab664c32ee5728414ac0d6b9d79510e8c5bd0f5b62e6c59c1a3c88998bf45636cbab000000000000000000000000000000000ed16c2f88b5b8d29d7e01633e2876322caeb740251b034e5e898919f836ae73f0296c62253a0329ee8f71fdb5cac3a1d226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000001455764f99e5eb0e0371e89f88bfee1c43224b9b5202746bd151f72336285556acc5ff36bd8ff87378249e82214cc5e500000000000000000000000000000000007fcee74e5335d96714e4d1a7c6f5c211b1a460efa283e0d0578c6c1f56dbd252198eebf0625362973c40d95fd890d3000000000000000000000000000000000ede26cf87e604507230ad996788e85799cc07245cf7191a6c3cecf0bfd5747b3a277cfbe41252808df6da19f005de9a000000000000000000000000000000001855991a4dd78dfc6088e6a43a64b56c8d86a0278b899bc8a1979a40a287979dee567217b006ca71374156a96b79c176c133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000cd032a7dfed029af020bfa249e6adccaaf5bcd2ccf33736281c4fce9c6e2b2e87fa828cc20301269d8e0579ffb866a1000000000000000000000000000000000765c4d6c4062cfbf7e24f9772dcd812f7e707f2b0ccf9043faf10018326834934df121924abb74d736b0da47554794a000000000000000000000000000000001540fa51e4580ff73e58def90a6f19557dec3c8306e2317ba0c25ece3eb4f8c39beb57741b3c4b9b8554fd2597743ce6000000000000000000000000000000000d875c822d0ce50dd638254cd4aad5dea1443813689a940d72cfa5db9309b171299ca3d69b137dfd37f0b7538a0852750fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f0000000000000000000000000000000011c230689175cc672c25f3c56ef4eaf2bc5766ce424f6c596b40ab24fdbfa56a955205419c149058dffa4d86a48ad35d00000000000000000000000000000000078d493ce3a8038134541ae5f2a82b5e0590218a499dfd78c7a9c06b92307003fb62d6414d6c04b22f2877c3de0b65ca0000000000000000000000000000000001d53c22a622c5d91df934783f8c0cb7e370043ecaf99a0554987e6c5120a0e5f4ede023a9ad988d30d945a2132ba5770000000000000000000000000000000015b1f36a00fee95e13443c9f6e67935a840cedc7c3fb7833ece8e180991909922f59d4f4ecbbf23f16bf5ee7f0b5851b70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d0000000000000000000000000000000014adff1607236910597a951ae169a7f56d6a3b4e0f44ac63a247716bbbf61feff7865d075f79e4108cda6c0731fdcfef000000000000000000000000000000000d740b13885c268da876898b77914bf4a002beef5bd2a3edefbf366e45ebcdf593ec6d9ab21e983fcae9a0832986182d0000000000000000000000000000000002a0827e812e983898351d9f03f660317d41669b0fa378e5c7667b73df299ddc4a32a529ca887a53245d7e1f946623b3000000000000000000000000000000000bf09a2de1a8ccf24a8a65dda72adcb96535ea7235de87f05d27341738b0b4ab16afbc5b37c97e255118dea9bf180ec2ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d7210000000000000000000000000000000015fbbf7e8c26e2f41be32daee2c81390b9bc4413aabb053e3a88bc6117377bc16011e81ed167370b72f84f0e77c2b8680000000000000000000000000000000013d48a27d06ff00048b19879493a5f8ca52b7154be2fcb468b9de9edd1395750434b0e95ae6dd941e84fd6d8918455bc0000000000000000000000000000000012fd2bc91286dd46d68d87a3f8793db997ee684dec6b2de1c4202e5e7eb0e4a8a21222e3dcf80e1ae4a3a92474107d330000000000000000000000000000000004d8b71978c9025dabb3d1b1b3c7f4f13f166514b8b356fd064842269a36c6f1c07f150c03510af7d0913103afda4a68fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000007d679ac21bd4634b415ef8e0e3670a8a1d673f6a4f7f3786b92d55458af980b035e4dab165a3b773ff3469fdd9d5135000000000000000000000000000000000fdb82db6e1096e73322050f828ba41b3012496a4fc4cb481f11fee338243aae20b205ee06887e28f6ba6dad00445f9d0000000000000000000000000000000017e6894b48f60b3d9b4184d58ab9554851e285a1d445b4d97cb1a7ed5a984ade8b0f62ab11ca75fdb280cc0e526108ca000000000000000000000000000000000c03b61690cdd9a4c6c83d03749db72c8946c21a944fb292866cf3a2dd1bf3dcd95743227709740ce8124319d0a540555af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d7000000000000000000000000000000000289f850c4834153f36bfc4855f89e9437a172c35a856117f8b841e5ad4ef973d3aa33fa73d8dbba4b9b2101708006bd000000000000000000000000000000000700025f22c0460613c05f8941f8a79a4319325c37c2b8f099cd910df5c0c27121a9de0e40adc7ba0fda61ea637b47d600000000000000000000000000000000069e17e00d4d726e8eaca8235c88967a7c093c70e5a46b1863ad097acbe233554048838a0a486a72cbed7001c83a27db00000000000000000000000000000000016ce4afb84c1a9e0216f23bcd2dda0bbada6a4acca78e1e0d765a5290f6f4929f6d0eeaf1306fed3c9766ca7c7268acc7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f10000000000000000000000000000000018ca46a89dadcd3b54f60fdf9a7b97c95b9e0668ed9329bbe4121e588a1ba773c9d086dc35b699d65487f428c00ad8c30000000000000000000000000000000003ada6835a93310d0ada01bd7fd6778bd07e718d1ce05aee2b4990bf32322fa94ca898a531ec6e3b8cd7ae3bdc77e0b70000000000000000000000000000000004a8abd2b9f7449213e63ecdb435e5e13fe2aaa31a2c38673a6adb5e96f4dd383dacab391787f6c17579c78a1cefa5450000000000000000000000000000000002a8768d98ccda80149a767e9b5a3b0bbbc0ab4b5f696522c8f1c664f1d27f2f0a6690531672ba2070355c0e77095dc02d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a00000000000000000000000000000000048fb58924bd5952d3bd7b1cd57a1dae6c1034df3a420c1151737f88760e4b0e78fa3f891a0dc32fcb50f89e67b0f08300000000000000000000000000000000073e9723c80eae7685db774d3e2bced53a52f24504fc3aff98e2becf8d59c6e83373ed024ec1ca50101d2d613abd286e0000000000000000000000000000000003b64c8e9a1341bc6a444a871843b3add7dbf04bd1810e1d6da7d31c7c2b7a264c362ac9a366dc8d93bcd9392c6056f000000000000000000000000000000000064462d424e54f50e9849a2bba1b0caae966a8618fda0f8965b1a841dd2173872a44a18ace1e2aecc8e3546a9558d7013549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab71000000000000000000000000000000000c40c8da9281a8b43478c28b2fe59a3cbad0a818e2077d40cfe44624dc2e46f72d4489cccf63eb8460d02f895e78edf5000000000000000000000000000000000735d768f6ac999a47c88bc2f3375f01052259dc69011480e468d8963ea8eda74726c4ef32c8feba52878eaf5c0147730000000000000000000000000000000010adb3ad214b17b963586a10701934727edf05fcbdc94d98255632647d73536decd0c91363840e1b55f29f7d32f650410000000000000000000000000000000019349045e6fd25960c03336888679cb53409027f35a1f211b40d24ebf724866c085a978ffa3a91d989da1a7902bca018c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000a5d2dcc05e218b0633e0a965b6d69a3c6c1c7837e1fff7ff75cc9ee93a112f8e34cbc95bd9dd8fe6ed22f2e9221aa110000000000000000000000000000000017d2e5d2c0578b1ec26b57c3305b209c979bba6925756892f031a7462ec44e8a4a2527e6aa2fc13bae91dcacb8c7a30f000000000000000000000000000000000d437edb45ace50700db548db68b9e8376b3039fa00cb98dd00cd197c14d0f92c8a3945127c43b10b34bef7894fa43410000000000000000000000000000000010d5a2e442a2eb35aa85fdaecf094c1e1f307dc9bcc540693d7206cc4e0d050ab900f17fbdd0754b59bd2aae705c60149ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000016c36464b426c3066aead1aaaf65ca637e93279e8ccc9d838b9b3ff1aa7b896f36de506efc2b0864763cb6ecca4926f30000000000000000000000000000000006d88d5764fc854ed7d7cf1c0e210496ce347bd887da2a149a09679469e98c453d85115afdd2fc4987b64a88c4a6f0a200000000000000000000000000000000053edcc0ca4c205423ee6a7031939379e552bd2d2657f8f25370c9f0ea0a947e77f18b5f218f98d12d720667844f3795000000000000000000000000000000001292909190854cee4499faa602af99dc49d1354a71278b439e983bd89e6c504fa5fcaaafb6ea26dbeba9850bcdfc1f69bd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b000000000000000000000000000000000997e79a7549ada9ee0233b3bf9289df3ff797595f4b5eb2e7dda6977ca981c1c4a2b91b924812b95418f1b1d9d0cb830000000000000000000000000000000000256b830e80f238e8494387429d727a91cf5d323ea87f7dc143058c05e11858796adcdc677429d1db4dc2415cf23808000000000000000000000000000000000cab529c6b86beacc57c874f07108d1df7d98fbd59fce44c48afe9eb2dff823f4869b620bbafc121b4ead2cf244974de0000000000000000000000000000000002774906c1a0acd87de224a9450617db37f8f36a0a192f5daa2774eff0b73aa79b4804342999df761f8572974c697c6010bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000012abd02540073017011e186586023adfca36fae454350b2015a796b7991eece65b63964fcdf581b4b51dbd7ddd506ec3000000000000000000000000000000000ccd3f2d9280908d4b30e924e4a862810a92e1a880cb56e842a94a2a5120956e8713f548ca279d66d06ab23e4976e54e0000000000000000000000000000000000c052ed00fde2cab515694d8c004de910e62d07c462345ffcfbd3904a0171b970bc58d99c5833059315283004f3390e00000000000000000000000000000000008fc4860366074ec0c7aed2c6ffae7c93ae0a81067edd8911b4c53393ebc0f23243823aa7aa2b2e987cb510f6e0a55a65c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d", "Expected": "0000000000000000000000000000000006ced307065868b6d082bd205bfbaea3b0a8cfdccf831bf154563b5a942154622b0d7689819b337479480d19aedd85e4000000000000000000000000000000000c0f04fbb26cf85c2c22763f3e78fe255d8d1f45ea47232ab58f5b785ad9f2458b0b28f3cdc25c4dfcb47d59957ae10700000000000000000000000000000000120e38740eebbc3eeea9beea483e70d6a9c30a5abd61b86e5f94bf65ffb40fb92c8d246edbeca425ace175f79c9c8afd000000000000000000000000000000000d5a503a26e50f9be34c2e64e4a80402ca6e17f09db1b334a9c1f6318f3e7e63b3847a7ca38ae6aa7c96ff94bf5de842", "Name": "matter_g2_multiexp_15", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000036480931a5a585ea54b6dbb01759eb1d86804e3f03326188c71f859613722e662c453096431171a49eecf8653f14d470000000000000000000000000000000015fcd6a30b9d59a90d8595ca1758eed7d6810d2916638dc2cb637aa09b16b5ba4920df7d21fc0b923453a6c7d32f056b0000000000000000000000000000000019aa4d8e98808c2fc1273d383e836876b087ad5a7d01743bded01314bc62ced94052d75d312a18839c1b33faa9e2e5160000000000000000000000000000000015747ce0f1171c0d0ff1fee9dbb2e5673b9db0b0c3618cc8bda474f378db58ea42184f907593f3d6fc2fa215cabb7b2308e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b79000000000000000000000000000000000222640c1d64948daac3ff93e86ecc96bcf9c93559266529a37ef1372a81952431673d69f1220e07b8aa0a4f3164c83b000000000000000000000000000000000db593156078821cd0ce0270e8a444d0d204dce0583774496620bd4752839f3451e505aeb3db568048739c7e71d279b40000000000000000000000000000000019932ad2c7e857c2dd51f7846534050b9243e388260cd47a91444fa050a9154eca88ab4d29a37def16d4a11d35683f2f0000000000000000000000000000000004d15ec653a72256ac6b616e9870b0acc7d46286893c0eec523dc27bbcf5fe596204cbf83ce71c2690af67b3616794225e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d00000000000000000000000000000000180622bfa9a1c452f343ed21a3e9c6fdf76589cebfb9a3f0a53782a3e7c9d066294e10699c386b5d0525003289f0ec580000000000000000000000000000000006615ff63c856302dba6d4e25d1070fe873e0c4950ee5ba8bbbd4b94ceeb181f1ee450acfd22f21010b88f0b88375777000000000000000000000000000000000cfd3940b5eeefa92d775792affa34371d13f3098ede3007e06510344ac8483debadd5a2baebafb5ddcb45a9449768b200000000000000000000000000000000145be0107a1e3acecc89a116668f9887579ed7a72abed3f4236930edd3f18974465c99ada86c4980c88768824216170f1362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000820e74e6d0333b6b36590ebae78960d019065f1681ce68a2a01a2522496c840c668575a57f9fd0f50b87f928a41b0de000000000000000000000000000000000dee60d90e96019cf2bb552d016419e92dd358ff97039a61838b0a89ccbbd537f2b435cd11f7b6e75a4ec6675964e7fd0000000000000000000000000000000002ca767de9fbf8af7c73d41a07e1c0e38e3fc971472e11928b65393a27354b2d732012dc57f498f94c0b933565a7493200000000000000000000000000000000134fe97b24e153f0e9a27d3fe7b89999c6a19e353325e0746ead013198b8e00ca6472fcbd2a112aecb9ddf671aaedd9174d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de00000000000000000000000000000000031a9c29323196ef31030ba73827d228e56fd5209eeef0803a189e0c0e5b186ca1f342483eeac99e1e1b12cf490856460000000000000000000000000000000010deea45a01370602bf57a1f81413e8d3b337d7a1a33f9525e4ff7003454d1da2cfb1a9b42c4a654320f91fe7d04b6200000000000000000000000000000000002bafb7b7452a173a3971c2ba1768061a043307d2c32767056f18c1bf8b066176937876a87055e54675876bc1b2d2fc3000000000000000000000000000000000b5c77dba3b4136a7efaa8c2e28f39e88afbf26a7313b52ad6e390da4d948209d96e39aa08eb52200dfb890d7e88b46a389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000018bd1ea5ee8e39c43d442e9c6fd22706e582cd80051f18334c4db2ea91ab019f54bc0074c8f0e52e50367197a797e7520000000000000000000000000000000005c0bcd1b047fdbdff25b138248bf4da4c013beff7dd3030c348d6b2b8724a147cbc44d570db5c4b273c94d0b99bc2290000000000000000000000000000000018e033935c20be5940863f7e9e39fcbdc29ba031e58c10beea90cc48e9da9988fdbf108bcbd87948058f386928f81fa800000000000000000000000000000000107d179204db7b288315e8aed7b92ebfe53b7ad2366d5d7944b3df68d9d9faad023e477213f85214047645bc05fd4cde5f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000000a7b364fbd3bac7e2f2e7ee501db2d248bd73a76c2a12a3e51718b56ca9a8ded14b83b8cf0b5bd46f0c26896a65fdb15000000000000000000000000000000000eafea7128fe20ddf740a6396bf18ff5f2652a0317ea9b6e934927c3ee95b59c7dcd51f7c895b3989d40ae5f78ca508f000000000000000000000000000000000bdce57be904236a8df532c2c0072165b5cbd4103e9061fcfc0a45a67e4b25d11b9f816f63fc0eac4d6d3e10d2764c4a0000000000000000000000000000000012419f94ddbd8275054f8f89fdc27a74afca2eef314393236fca65705354e5cc0a470818999c96b5087997813823e9be0dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b60000000000000000000000000000000011e4010d0cd7855a92cd5d4954ad735363c0c2ab00053db5e078f34e772969d8c492892329cb95ea8893b4b7ff7aaa5e0000000000000000000000000000000013badc54d90a19b84d76b30fef8e3ad2cb268204fdaa50ae951b63e48aec9cc6d585751dd48e4a8d4659b835f38f8da8000000000000000000000000000000000460728f686b9b15cc19ef135af71312e174860284c3f0e7a84cf85a5c934e2bb6cadee8e482d88afe788a796605f79d0000000000000000000000000000000019a50c06ba307d83452a30fbd862270652cf5c7a09b150fcea858a8102ce3b1e9ec13b6abfb323d63d2c4edf209c7cafc579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b5360000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d69f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f10000000000000000000000000000000011982264c8c078518cd0adb05034761224e9063654904e06fb5e5a6eeb1f45e4ff3da661f1232693b79336215dcc0cc40000000000000000000000000000000010c96c872160d2de03a16e85f2828d0cf2dd16a3389effacce46b5b5eecfea1042a77de653da5a1c0380a84c435723fd000000000000000000000000000000000a4ad2d9956bd407c555b26c192c6bf59bf89e40d9c6f9c90780bba313a39db71a73e7633397d47a3f58f61c81edee77000000000000000000000000000000000a7f912530d27a7bf74e01d8e48890cc66f72d14950554991ed1edfc504062ff6bd3cb6941bb398df9fde3cefd33fc0676618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000015bc12aa9ecf417fa5bace8d9e5dc4a418555eeddde1da8b624bf7d6e1873ec4a257d5f6dfc058a8d9b02528e699abb70000000000000000000000000000000015b41567f8c780f83342449f27094bc20a839602ae482de14b92e40017e7acac8857db48a2d27f1f1a625883b6e5255e000000000000000000000000000000000cbe79ac0718555fd8fdc38b68eec8be83b32499d2654be44888e45a2d610b0e81ae12fd56550524ad85b5a632db32ce00000000000000000000000000000000069f46b5baf4357d8010869685b3828c0dbf6e2338598c9b42dfecf0b22d803f95fca716115f74c77778d414cbcbd881fbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a07876177880000000000000000000000000000000017ada4038189c544902167be958e43ee133730e5cd329e572dae2d853b694f5ff8032bd9ab41cddd11c51e8284970f810000000000000000000000000000000013eef75e6d28deec945ddff33128c199fa52565288d63677c824b8d56a6c29eb98d34c5834e84865be35d40c1c59a40c000000000000000000000000000000000e2fb4f9c7ba6bdac1d4ff5055be609abef7fecd7923a753a704da537c0ff41951552420bd78d14cf972dc84fa3f5dd9000000000000000000000000000000000805376b814b8a59435310d49a43081dd7ea36dc7dcb40d38068ae9085b3ea9a3b2249234234cacc76724d8ef84a2eaca0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000002288860f2d671c84c5239313b7f6b82e31c3976e6d310e15d3bfe1c566e2ab5d86ae6ed0df02530f9f7893ba419f1870000000000000000000000000000000017365bc096e260f8dd7b189fabe10eb66923783b41fff70a149251576b3b465c13230dd0af13cde562751dacd8298335000000000000000000000000000000000fa8eb9c818df27181b45a74b333ab481dc7212e417c4e12634816f9e177064f9e1101deff26156d26bc6574db9617080000000000000000000000000000000009379598bf02222e1ec37a721b9ea31a3adc33524c6a41bc58da06caa3da3bd730659f0a80f793a0fcb9c07b43ca929c2c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b00000000000000000000000000000000136870e08ff5fabf36410629ce5c23470eafbe73a7dceb633df5c1492e39445b86ce15c22bf4c421cfd0adc6518e78c30000000000000000000000000000000010aefa3cdf1225da09b796430d096807a83eb2fd5a58db3a4bfc5e500dcfcd472fea3077f0c059620f4ff708f37c95a90000000000000000000000000000000019ee2c62ff860338af623c535979ed31c42c0d0b2f82cd56c153e80e6d92bec9ce39bc8e8f285d1efd1c1e969521dbb50000000000000000000000000000000008ed69eb0a16c8a35d507bc3a50bfc97e18143fef611263715aacf5400cb1aa285b6d2ebf2ec219d2fec477360875a03141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c5920000000000000000000000000000000014abc4eec64f2611197d0c1322c3248eadb725049379e64682f2b3d7f83f7bcea11358d88f52711b3020924b6ddd84790000000000000000000000000000000009fd78c5d1d2043d83be30a88f046f5b633c6dbb11bab25fa3037bd250b6b9d9394327aae25d1939f777fea9f3df46960000000000000000000000000000000010f413640aaa16a95afba98660f9e1b03a8f3e0a7a3d7f2b971f71b5e3d09016ac2b410f97d20471f48621d5a363e9e6000000000000000000000000000000000154b5df93298a5a14a6157819e38db33ae7f2d11dfd13f7f2a92b2fd9b053fbd25f10a8c45db3026f6f583bd56eee0f1d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c000000000000000000000000000000000f1893df99adeff5e4042c4c5e8557e53f7c34efcb2a7953d5347f81d2f4a75ca0273a3845f54e795ac1c1f8ae7240dc0000000000000000000000000000000004856b05d58898be6aba07fcffe487dd895144c7ac8fa8bb1a37c61e73bcd062ff541d510e24c5bf005c8351d3ddf61c00000000000000000000000000000000178b22c2c698dbc4929b119474a741ef44d6275fff5ba058d9debe9475e71398e464aa14a6712c5deeb5010d1c7758ba0000000000000000000000000000000005ad09389c35c45f349e6dcaf1cdb3b63648b3df427ea0c2a371f45634635f9253957ba6987df4aca6cba4cd472308a31b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4", "Expected": "000000000000000000000000000000001090d83d501373cf07c75effb1c85852019b39eb0d77226823aa3c1054d4e408e82fbf0f4420a30144c611fbb856748c00000000000000000000000000000000120a1e3795f6d5c4ed5b886256c611bdd209677f8324b7091cdd7cab11788b1c0f780e8b4c38b84d7c2ea528123d4783000000000000000000000000000000000d250df34d906ed421eec2a78c2ff4ed4eedb717358d7ca879d58ff5b4d2d72521082dba6ac5d10859125e32c2c8b490000000000000000000000000000000000476adaed9d80cb1545be505496222dba1f0ea85d38d5bece0663461e0e9d47abbefe95303c926db008d08b8aa162e27", "Name": "matter_g2_multiexp_16", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e1a9066289392b0b0b8f0986366c2975463c9cbe7a74f2eafcb3b8b6d4ee3226ea5886aaae374341bc76b53b165e22a000000000000000000000000000000001557cd01b61b5f2361f6b558a87c67f2778e11c21734b7ed25f72a88cc62cbed396d583de4c2190ae6bbfd096c33bf73000000000000000000000000000000000eab68305118d7e7076043719ac1e13ecda4497df2cf392d6aae4b7753f114d30aae3e8535742947636901feac4b620a0000000000000000000000000000000002cfe5014446556b82d60adf874cef25e58eabd035deb4717c93bf0361f37a4a67aab70b95627326bd97f111efeed57f58fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000c8fa30f6055357f6b697f2115203428b8005ad03286d2b3c805bf3d4dbb461c30e6ee8b0973ef41f884b91e857c53500000000000000000000000000000000005e1c785feb4c4fb7e960233d431d51a4fe471f10321251d018a950374d2a686d52ee8cdd855a29e770bdc1bc565f471000000000000000000000000000000001158d31faab483832d39f5431a5d8aeb952d6a63b82ec019f235b5b2e5580df8cd91b46cd53d4a90b9db354b38c5a1710000000000000000000000000000000004a389b09be6fb7ffd14d7f3359b17991e93d92a1c0b9a89faceaf71f5ce77a1875aaeb7a0ec3b2dfb363c47dfc9875273b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad0000000000000000000000000000000010587118c5f90b545ee707466ea2c5f378e6795c260235cdf9876aed8bd753aac592ee05e23882ee77f4a13bff97f5940000000000000000000000000000000000a0344aed244b90c4fb9ac337edb01429e09f951062b06025a5212300f5471a95f28e09bbc715417a6d98423b518c3a00000000000000000000000000000000128457cf374e5b8864b8241f476da093f48553d609a5f30c0f0f235ecf7127231237b6c8802f2904a8304c7c237842620000000000000000000000000000000004d55ff04eb09b33ebfe90f2a0966a1b59cc224215c0359a4ff0c09e60f9fe7ad8342868184d8cfcaa1d8c28328864241ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000019e6992c3da47715bf379a668a15668508e7ad27bac647490be8e82759b9b79c996735aa1bfdc3cef217750e4ed36fce000000000000000000000000000000000828f782c5bd4f2de3570a4930db2c020f75f93adc98aa0e48449d29c7a3b0d5c349963d956bab7f985ba6ffe59c90ec00000000000000000000000000000000062c7a730d286e895c57b75907713ebf1d20650b5e621f270f1d22a2ca480d022346def4102a62eebe867210e4b6122e000000000000000000000000000000000d6c29462ad449ee6cd122e3dc00d56dd5caf17a2510e5305aecfe85626cf73adb401ec2192eb693158650893fa67412a691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000098de9ab41c289a05ba5a774eafe27d91aa8272fe9f81fadefba9a0cc0e31de20f808ff454a8647c44f5aa632742af9e000000000000000000000000000000000c96019bd5cdd62df1642656f0832ac8ff6aab86f671e18c1c7023dc16b8ff54a8e3e446b19682a23b73ccb90da2fdf0000000000000000000000000000000000178e3b4366b2517d4c19fb40551be6979d46319d7040682241b046f10ab88d269dfc097ae02952d46e69cb1cf159da50000000000000000000000000000000008341bfe1e2fb999f0c3f4e79523c720edd332401f9dfdb8dddba8d1342c2c1fb20ae2fd9dda92c7bde5a0c95ad971f80d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000001004d60af8c21f7c62fcba1c5c41b94fc77f64b89abcd23a218f0da8f47d2ae6879ddcde52f3e6feeae2dc7b2720577d000000000000000000000000000000000b8e8a7da87aa62ca852e2984b0f12b85052fdd03883f01f4496df0835d1cafa48818b5ff1e3cb0e9ecd66054540a0d40000000000000000000000000000000009c16854580ad8191e3e80a0afa8da759a8b2bfa7e0d556418b5c96d97e88a12fb75a91cd68c2f4336c3ed7ac99199fe00000000000000000000000000000000195ce9c562c460c7e715908991ea8b017b81561b45133427f63cdfbe8f65202bdc8e8958ab0977b3a244cfa32fb35f37c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000107292f77666064b7d80d73ea8f3b623170ef79ccc7c228b8366675a422a0cb8491586a2e4ab1a067c31396cd670a8900000000000000000000000000000000126f8136dd61d61b2a9c0f4af3ed44a3cec3ccdedc74821f341d200601a7bf0a17079c824de6cfe28467e843d0c74d2a000000000000000000000000000000000bcec8afcc7ee56b36d6d08b51f61454c8fb15ec5baee1117ed55af8fc85f68674250334f79b0fce632e75623dd173210000000000000000000000000000000016624d64660b63b70ed197f6a675911b02b0bc6f880348faa6ce4727af74127c509ce8535d8dc8db5ae2d71aa497e0756fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec5000000000000000000000000000000000f57747c20e1b3923c7e1d8bd7d877736cccc0e0829837a086d62d48cb54f323d90b57ca3339fe4b256df529bff11363000000000000000000000000000000001940327a1b319dc4212e7a553d3f49904660722c89636f6a38604d96771fa0fc71f57674b7aa710db4275822c2b89903000000000000000000000000000000001956b81bcf961d16e50c053ca07ae67cb8597138f34a9dad4d82e0e8d23a7e08b751682d588f229311bc63f9598ef448000000000000000000000000000000000208981064443e8c72987945e399b45b74e529a0bb75e99b7d6744728e5c182a6b0a10e449147bcb0b0cbe70edcdd845bffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf0000000000000000000000000000000018c35ca3a63053fec853e8fda5920b560f1be28431f2f4b08789c7a202336c8905a5ffffbf69ae4427f267b1e13288d60000000000000000000000000000000019de96be76bd93886cc486c2671b5b0d731b568638b1b830a52dd4c481b9a1fbe2b3cef14b46e25f1188ddb3c158da6e000000000000000000000000000000001813ab16a11c79eb3d3d47ae7d9a7c05401ee91eb1183266d23077ec4c0c8f3ac7188eece06876025dc3fe271d65d4ba0000000000000000000000000000000004d2a416dc874e956fd6d29a3fb96195019f4136561b4c127541ac171b5a6b229746af6d6e535a8017e64ce06709e52e57f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f4000000000000000000000000000000000623b7a8a1c24dcc603f01589e6679c74c4ed3452894e536a4cea69e99047092acc877dd0bb395b0cb693cb1702a64a00000000000000000000000000000000013de9dc75e42f12e905d729a52f25bb1a4125f5edb435734649281bdfd41083716d0797b0a80d842c2503d09cc61162a0000000000000000000000000000000006453c06f56dbaabd4530160bcd5312b8a148dbe19fdf9f1e44b7b047a73ee9ef9d981116d00269942ef73537885eb7a00000000000000000000000000000000075376135ff3acaecc0eeea32f8dc15add57e8f0297d053ffaa0fb0a8fc4418c5b142f96b6b9ce9eee2f949c960aed682d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba84000000000000000000000000000000000120d088fc12210c1f5f6cc3d1091563f9a37d4d0e0d2c305b479f4d7e893c4d5c8170eb164e34e4843a21c9eb193d11d00000000000000000000000000000000159de80db3b1f0ffc5fa8c93e1bd54cf8ae19cbc9018a5dfed86179cdbc976c1c312212080ab221806bbe142d496e7a7000000000000000000000000000000001103abb75a78220218cde4bc4c59ddb5fb647ff808754dda200bdf586ee9c47a09e03762bb726b085928ddcc998af3ee000000000000000000000000000000000bff4bea17eae0f2ff3e7f99bfa91e6ae8aea28f6f3fb6080eb644861defdefc26befbb7874f612edac0cecf70dfb275614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000012dde607a2d4452c6c060054c8adb6307743edea3ccb6ac34c275717f177f0e454d9e33d4391208198cae39d7eb6f6c00000000000000000000000000000000014cb4d8bc98060ee68a8ddbc44b83db5cb6d09f09b0d608357629251c35e44383e97058d0d68fe2df3bc47424a5dda03000000000000000000000000000000000c14fbb6c844fbf896fbd3cb3464a83aa4c6e9a7f0450ad96a07527df6f1eeeaf587f60a990bd6abe7aeaf5eb46f362d0000000000000000000000000000000001d9468774318ea711b79f16303ce86288cee312af296f1c9f607ef5f97c7d1cb48a7218775c8aef00c227ccb586286e7c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef728400000000000000000000000000000000181e7f8d0ec7a4a7858bc96b61484c24dbb9dfeb3746fd3a231a8e442369e3e83516ee6043b1c06e7e2043dc86f6c75e00000000000000000000000000000000184c1d667c0ece59f18fd2eeafc66f1ed530b7d5f4560a6c886429caa13255c63dea01c3e357e3408af58a39420a8b28000000000000000000000000000000000a8475ea694cf607246a1c50064cf90cbe50ad5cf8006934a1fdf1621ba38d20e70860a2b5aecc05acc60943224cadb60000000000000000000000000000000008afa03c2df8e83fb64523c57d0daa7cfbb7af6a4bf2960ebc64515a61a659b2c37ee661050cd538fa00cb34746a371b64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f0000000000000000000000000000000008b33a297c8f86f1e9d7166f9e905283c8e1581e582b879caf48585d0bca3608fe46d8d9f6e7c90855aee9d92283d7a40000000000000000000000000000000016962410d6b4b6f91437617e84bfaaba49de0369b8748d2e2dacb63b421e0d7de4514e7fd3e0dcbcfba8baa4915610d0000000000000000000000000000000000efdab72953b870d0e113efa7c183d99aefc100ce59791aabc72423aff70a5b74c577c06ca94bfd6a7722199b4bc22660000000000000000000000000000000013b18e31700987dfa4344384f9b41e72afe92c39bc961333cad3e7d0a5efd3842a5e849cff5655c4673f720fd0127dca35bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf0000000000000000000000000000000008b86f70c8d8b03b0e9a8975776d7fb0d08f95eded0a0124551d363c2df57124e0e89bd45ddd1cc75c258a4ae2f87916000000000000000000000000000000001120eef9eaff7c308b629deafb060d2c12b20b57562007fa810a2191d99fabe9c7d3c364caec1724665ef556de66b57e0000000000000000000000000000000007698bbef6dcea67a2c643342ab2a0f830c329fb6244d4a98512daa8a3c9d808cd2acc0cebbe3da920053ad73eb7cdc7000000000000000000000000000000001155b6beb28fd88d252c6b407bb9f55d22103257287ce77353bea580c90173b5c3d49080b319ea28817d67c52bead96f7980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b2800000000000000000000000000000000142b28509d72f9e3be9ee916827fc1a8dfc4ef7ae2b72eebad5db605fdb2dfa4492b50cc3e472df1b52baa6e2b0eff5500000000000000000000000000000000134d6821088ce4a8b42383d5a43a32bb0cdc96c85f304a2601292670633d5e231b9dc479d199829a9ba9f39c162318d5000000000000000000000000000000000636da344fcb0fe50ff3e22f8591418f64cfc722b2860b4a5047f973f42e4cefb93c2f8eb8a14b4d150758ecbf3cf712000000000000000000000000000000000e6fd06d5dca702cc9f199f7583add86c82f7b530d4dfb9faec36dbb669cf7c1cd1260c7e4f3026824eeb5b979e9fdaea256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca", "Expected": "000000000000000000000000000000001684f447f8929ec0187811f66e985f0014eba46eaa87de2d4ac2347d10c0550e4044ec7792d9f315c50081dc2097ebdb000000000000000000000000000000000ee0c46efe930bc98f39dee8cc6a792744e84de4fadec035d25ee8ba82e1c53264d0885a1fb05b2b8dc9c6a1846c28320000000000000000000000000000000003a5ef98843099235a2ad9522c9cfce1908bef77b45794e7df9eb38a4854460031829e947a118e8160365fbec3725b85000000000000000000000000000000000dd205e195abef6a4cfa7da66f022a418235e1a1b2fefa6bd3ddf8a3851d8ca8c27652bf87ac644cd189ae55e3cc7808", "Name": "matter_g2_multiexp_17", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000064698182f90c20ed6f6da7248cea32a291f901876a900d344ce4dc1b07822b480519cb8d891b5ee4f33b4efd90742cb000000000000000000000000000000000e7e9d2e79ec4b07015baf69a283f6a4abc8d7c1699f3356fdad6ea9b1c70e41e73bc14e500634d73749f9900eeb65f5000000000000000000000000000000000002ddbf40619ea5123c100e2d6553213e37883fb34f0f0f2124795dd971892d5c9051cd4aa78b9d20f196301ca9bb4d0000000000000000000000000000000017a07b32fbffdbf7a80f0437eac1ec5fff5a68f3b053482f034064992158b604bc34489dfd41a24ffba806ccb871fff15805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c0000000000000000000000000000000018b4de9c04fde8b708408efb3aa7f24b5f7bcec14e7d06fd5a5b36bab528e5adc0bbb1e378a5ff6fcbc95aea530ffc6a0000000000000000000000000000000010da98267770a47e5ed14ffb3dbcf537dd14ae5eb79522c772a7a2833be214690db0b4e86621de1842d88018fc0f348400000000000000000000000000000000135548e2eec9ae7c3d23618d8286db13a5a628fee04fb6ec9da980f3a46838899cf965c1cc6f562e71d5b5c7428cabc8000000000000000000000000000000001669fcee7804df9b7bef32e2ffeaf285e8501842efe87c9e827fce872dffbf92255d3c3a2fb5c382ab7aec0bba1ae0e5502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000010ed20c069bb300a27571adabd239e70b767af90b91c4d0e93d88278a6da47b7c12fcfaf62ac0a7b9966968cc9f3770b0000000000000000000000000000000017273eddc25cf41f2d7734a3866711e83d4f2823ee6a036942799f837d5ceff10dd6022ea25e3c1e28c7b14ed8f4e7c5000000000000000000000000000000000f201f314f66f6b2c6e1365c0fac7b187d31bc45b5edaef5243b5488e26581dee24de4a5fe493bee44165cc31d8d72ef0000000000000000000000000000000009dfbdd86633edfacad6b78d292141a1e653a1bfd8c48a96b2f6bf8271ed6033c0511628caf2ef258eb64cc8b63d8e5be7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a740000000000000000000000000000000005c4a4a5ffcb4a39c8809821ff275360ff937070cb97a791cc9ec45f429256a6d2d6127248b6ab0b6c71c30c4fe84ff20000000000000000000000000000000019fa60f481c5be953c9c7dc86903a89af0ca2b4205be3a00d793d6de7103852e147ebc7d983c6d6e8cd99e681241ad440000000000000000000000000000000015b3b2eeb0f81ff8a2624e2ff2396bc69feffeef62b1b6a1e73ca4b9e60506c2950fdd23a37cf56387b8794449d3237f0000000000000000000000000000000017021a69ceba3446dad9fcfd8cbe5b89b61372f57d43a8d2e2c8f4534bef6b91408409dfda9438f24526f7e6bf1f4240e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba0000000000000000000000000000000007c8f07f22a3412fb4638cb704751959cda4e42e4612edaf5b1f22c8f9ea314508353445114bab6c07ccbb4b0d0bfa6b00000000000000000000000000000000062d087155c8722d0102c8e5084f95f5f58ed626d48197297d21d2108ee05f70f16d595ef73e8e1207a3c0b013fe16710000000000000000000000000000000003b6652934f3acd4c91c6c521c2476bcd2594a939ff2e7ebcbb0f451fcf0a656a518dbd4f36f165f9b2f58054e9f778f000000000000000000000000000000000bbf21158227e0ad5461de9ad8bd580f9e65327dd4e23f1ad55618f6b0aec45aa6076fa88557953ad15d385a074bc7d96e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc50000000000000000000000000000000019e336d4d342f110eeeba9773b8e351f26bb56361c77fbf12fd9fc218fd075ae38b95f4a8a5ef830fc2cd92558b1711e000000000000000000000000000000000a112725046ca3b6cc43207e6b36f38d96ff98dfe3444d67ee3f4b0208f3b8543768dc9989f936637d7819e7dc5740fd000000000000000000000000000000000527682076572d8cca15e47a2faf62b129baad29afed22d32ea47983a8d0b138653c1353bfc6fbf9fdbec2efe36700f90000000000000000000000000000000007e3c5aff373b5154ae66f978fcd66d09cbebc7e0c96b4a4cf23c4fa5f2fa655410c7f1ce597a3f5f155017720f7c50f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed000000000000000000000000000000001865c265ed4606ed16056c0b28f953119751d7272bb33b9865eed312ba23b32d01733ad5446cea5873c2bbe37fdfce7e0000000000000000000000000000000007018aca1e7ac211921cab1cc6bb18874d2f39f00d916b8f3d46a088a378f3c9b49ab8a296d0aa21608f11b144a0c687000000000000000000000000000000000210561c0bbe5a9f4b2237e5bdf88bcd73326d395277deb2a883526978df90792993e6ee520c9d5ec0a6f7ef5c6b3542000000000000000000000000000000000cdd344124b7b5da556f64ac5d651a6f9b74427fd712007310d720f3236724e2284aab812d739a87f3a1bfe8737dcee7586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53efaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e40000000000000000000000000000000002dd11f4dacf3d9c46579182df1c1c45a364a8dc1eb7aa7d54d0141306f1c23bed85235783a22b8e6dc4adc35f9193ab0000000000000000000000000000000010d1c642fce533039e98712bdfcda86eaa62d2d69b861ec4fd835488732fcea414cfb6f3f8414152f9d5398c73a74fd2000000000000000000000000000000000c6759b75b1e3fe86c00fa124d09c5b7438ad61fd1bb71695743ed7793f39b7a0fc99b055201ac1e3aa07ccec61b24a80000000000000000000000000000000017580c9341789484fb31386eccc9c344539a09f1c4421dd124b1a0ce61f2d0528942f7fe8df67c6b2bbf782996def47b39d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca8000000000000000000000000000000000d9c48a111c8c74bce8cd78d127999531e46a411b2f0be3507226766bc8abd088638a237674ac62e0fb7dd4a86d09b79000000000000000000000000000000000073675bb81e2bfe6adb5cd929e0b7280f5d60b3dee7f797d65ffbefc2c2944a9c7207648bb096f13292ff4440c3f03f00000000000000000000000000000000024d2e0d5ba1a804520c72331fa23a2a326d461177fa527473240dda130f4ef893870e893e1dbf7c5dbb0178dcd29b3b0000000000000000000000000000000002a4c9487485ec33f8fb347d246ab0d41b883bec30d2a5e88cccafa676569f25ffd8341cdf6c09f68afae442a574f3334c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a000000000000000000000000000000000c1965a745e42853b4d54739b2dc507d68d80b330360a4020e4412ba5422daaae313fb9597c98575c66ccf351e62a527000000000000000000000000000000000844439e6f08a411e61d37b5b2b07921049432e1833e839b00d6cc11227dfc8770ad9ca06037043668fe7ce3bf3ce84200000000000000000000000000000000152ad6fabde2e0310c978404a5244209a9363cab1f3ac9f71339cdad6d40c84f8e5a8a196283b581d0209ce90e1e3c6c0000000000000000000000000000000010eb6af62c7dba122b0e24e8326dc906370bcb4ba791c47630f05f657a228c20e010c065b93537ec84fa14a756b199789af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000001febb2cf2d664e4a277cbf08fc1fbacd05db415a12329f7be551ed56d67f0b5dcc917d1b02951657bff3a26bd8c178d000000000000000000000000000000000018af160555292b2f7ce27112c1d60038b564f5427d62604387de97dcf48e4473107f91936b5e8008065a1537f7ca340000000000000000000000000000000016bbad2a7f5451098294a7cab2fe10d206741a99b128dde5eade581d02ca849bab3662fc3400fbe055dd93a418aecf0b000000000000000000000000000000000b1e9586cc1b357da6e58621ce09288e62a79517144f6c6b867359251baad6d40217578d49c1501f23206b125282bdf4c72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b000000000000000000000000000000000b88892250c848e7bc7bb7e42cfe1048a1f61dc546929211846f49501ad8c7c8817f5b5b99ed092d5a2236d59d9c8eaf0000000000000000000000000000000011680c6549f6b7d9d187a6409d40cc26554df654083f1e8a47dde826149d68da756adfb1b65bbd219f79a10d8454e881000000000000000000000000000000000f9596121dad98bf7acb3fd65fe7e0bdc8924e2390341c11d9cc9cbb0517f988ff79a5e1d60bd89449b5f042f0d0b0c30000000000000000000000000000000008982832ef53bafc23ea817be378532b95b5872217093e7c7c2f4512d03a9c9a6dbb7950563a520781c7ae213fc82897b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000ea5bc2f8bc2b4088d1fed7090ba389577b11a3ee0775cb3f0657ab5b07a6709d3a18fa5fc33554dea235c60baae4bb100000000000000000000000000000000196b6259b06a4c91a0bb0adecea134c8609cf983c2c87158a69c9de3b6768510fc56543a84d1266dda78d90c3b0516ac000000000000000000000000000000000d0222d8ef278cd0d85dc8765fa7c4256394a5ef61f91301af6c7422b4cb17889224c75ccecd2df3ddc9bac98b493863000000000000000000000000000000000548809ce26cd498816ef1222d062b1ebb7313a07e99e3aad1431f984e9b8ecfd43357ea57da7e0c6c011c5d5400f7ba986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000000b95455351fbce6f73de0345a195f91bf96abee361908cea6c4dcde72048a13a9a23991a75b9c988ba0afd9491d15696000000000000000000000000000000000305f29b05fed06ffab484cb065d4852eb323fda8c9b7c0a78843bd7143effa95cbe5e50c1a0c3a9675bb5381709b6550000000000000000000000000000000016ebcb25f1b8e8d7a8f7131455ed2be084bdcce40034e7ef24a47fc29e447f912c20c7c9910e025aab975cd2c8cf1a96000000000000000000000000000000000d84a5de7a5fd8592f6cc2bc7c3d93c06e26185787856c922d95eeee345ddfb7cbbb60b6d992c5ea4dfb33101f2ef1dc979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000001377d654f80e933c4598aba1f637d1e37d66a96680c3a89a762f412e187817ec08f0ae897b08206a73f1a423b742261900000000000000000000000000000000014b71954b9bc22ac22cb2d7d7f373c3238c923205b223cce6c219175df2bb6d7258ae46d6cdb019311bd386275499fb000000000000000000000000000000000a08ef83b67bc972a67b9174d0e5b1536af882d505d03464c9a97f68061aa319d612de9db84e1e7b12fc3015fc2973b20000000000000000000000000000000005f716d0ffc30005e4a744092704a9e29f58fb06bf7d8d6fdbb95a4c0eeb5c39452cf662721ea3e0bcc67f25931a109425ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990", "Expected": "000000000000000000000000000000000c53f0ca8901f4751be4a478088b30dce70b9ecc382455049df9ce108eb0a8d2696bb325fe9ebfd7d967ab5b9b2c2bd800000000000000000000000000000000033460babd2984a5d8b7002409349972f518364e92648927e223d7a3b648e952482c06cc713bdc29ab83f2646e9398510000000000000000000000000000000007cb9dfe603dc070151cc477ec5bb5a2a949062e8442399597c5eff8f1decff538cd0aef1384256dec73746e63a6c66c0000000000000000000000000000000016b56ee9b21c533b9c464178d14ba5c92a90e6a54c3ed319f487c2082b1ce1d0ff81131a5fb3dd7d13e0fc1d9ad9e4a1", "Name": "matter_g2_multiexp_18", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000104e0b91821c59290be48b97936458af89078b176b5585ca9a79070c7050309b01df4b0bcd84f137f58304d90599212f0000000000000000000000000000000013b00ece925fd17a8effc43e21d982553ab2764b13defaae5e5419cb9a23ca7436cfc44088c2aded63785e4f07b6e186000000000000000000000000000000000267cdd42febf0706675b60af8c0953582ced84dd5ae870815654cffa46eb14b747fb8fbb3b014e59c929da49c6908050000000000000000000000000000000011c5384d7c3e0f4fd66ba4b4c2ab60f6f78f9930e1fed233263dad25294814d9e2aaba6388ee9f924e2a323693b6e43bbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee9670000000000000000000000000000000018158ad70994584e6f2443b8b96c1e4772a00fa0bf74865c76000eae470eb02cff627579126cc465046d4e088782557b000000000000000000000000000000000d72979d455733756a0849baa8afd79e18960f3f6dc9676c33d1663961617831f3266015cb998fff28b78300c87c2a73000000000000000000000000000000000056192c20cbcbde6099256a8f40c78a32d3fd212fe9c511951c7523a3559f60662e070f5b5e5f87b1686be0bf6cc890000000000000000000000000000000000c7b7e8ab7486012d95af5b2474ce15db612bfe1508852b8d99f4402d0e4f075ba056c19df3caa3a93bb4db89443096143cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000005a2741902dab47e8d38992180a9670faf56d1849dbeaa75b2b4ded93ee5494184c8658232e9131a8b08ac9b5460bd400000000000000000000000000000000189077d5130b3a4d7d4c3074633fb12739f95b8b6ccb082dfa61d845a389e6ca7aff835fa0f194dc349e1584b3141507000000000000000000000000000000000f226324f242cbc5f616c4a897f82bc5503ab1963ca38f30070c7c9916ef6bef5caa7e2e26b3f9fe68a1d59f19a9831d000000000000000000000000000000000a999bdfa10e4838ca69694272b0187f7d0198d6db0fd85eae688424fb09baa165c623dc6da567fe034d7cf9f9a0087e733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b6000000000000000000000000000000000fe85d976befdae8fd0ad33a4404415304afad1c5698b91bdc15abb4f268807c906410a6ca827320f5271c8fd4c8d6fe000000000000000000000000000000000cbff7963daa20c1d20717bcd47b872b3ecd5f38de1a467ef50936f13d6aebd978116a736cb6c5d676c6a9525bb0b7fc000000000000000000000000000000000c3d20ba17a21bbfe873d88e9221571f1bae7f02f35b8e677c9c42907673d765150c737f0011fdbaf4faa883b0dbf0280000000000000000000000000000000013482c68a5e1084faf12e8aec92cd9f0692b173556ac8ac3c7519beb4bd75f847f41ab9432421c631b14c885c001dce25112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea71000000000000000000000000000000000f9091a0df2c989e12a844c447287b704803d1532a3ecbcc890e6f6a885a54b969c53323c105b3d14d12f2cf766b8ac8000000000000000000000000000000000e54f3a9def8b3a9f972726e606195849584b7197ab70a28cf5644cde15e70bb6e3044042b649825adaf5e37c2d5e614000000000000000000000000000000000cae412d8a3ee3c5af38d7a65bdf2440d9cc2d6348dce0791f4a7e71ac483d7487b6c789be0a401777de3f57ec65de820000000000000000000000000000000014df09fd2ff406707004f6afa366d06bcf8bf18f5fc4b444b07c98b3f358247c6056a6337f5b53c35db45904797fb4455683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000a8aa422e1d58fccc84615f9ca4a4743cf5efe3a1066c9819f05042100bb8784fcceffc8b3a739f549b42f34d62629e7000000000000000000000000000000000c737cf78b10e82fc0cc9823891f1a5f1e9229d61e8f369c589512d01e5180246db46e4f09e811464c6e1ad930226d390000000000000000000000000000000016017354434899e2285da6ff4b27fbaab633d962197d2ff4fa5f688c4a85e1817434cbef13a6b018df4e359d7b9ab7cf0000000000000000000000000000000001433c364428ac69ce4f5678aadfed4e6d076241519310686de01572da5cf78af4a98b3502519beb0dcf04b748d08cac5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000001f8b803f3f76aee9825a9a960cd2f9e8aa931568b32be6169036683b4e6d8c4abba6bb73b137c7c6d6b6ea92f2023ab000000000000000000000000000000000fe9edeab60bb55990ad2c85c8fc9341e81de54324652c08c615a745813f08153bab3849dbeffcf4073f087f7c0cf0f6000000000000000000000000000000001955289b1210fa31542bd89f95188d60751b32e8d54f1d4d280975850e57db7b151b872bd431c528c22fb89c9b8784af00000000000000000000000000000000079c8a56c72adb9fc9baa503db394635abb10264dd43c60f2c82d041d43240321ac1028688d92c4696395d8840d52f15f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da0000000000000000000000000000000000b0fd79e62c6129fa115d821b8f2a58a4564f5ccbb14088f59d5e6a17a64e803f32bf8e5a415aac4d6491612d95ee8f00000000000000000000000000000000008d837b6c70468e1e10f6b979b7c0694d65942aac48b5baa829c191579186314ea35fe440e6d843fded02b95f9816890000000000000000000000000000000015a05bbc4607b113b37dc0b4b8add23736e0f1bb1e48aabc15500fa6941b17153918d256b6442687a432dd9ca9a198c70000000000000000000000000000000003546953d97306266bdd359d4daa939e05c0466691de59d2dbe3584e2ebfd9a9e1516cdc9cb643c5d31731835dfb07c657a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000009a4366299290c3c6651b22865fb22cc972a05ca5981f5682574851e41096d531e375e981c4e1b1cbfebbc70a41bb6ad00000000000000000000000000000000001e6fe2097fca2afb8385a3100dbd5ee1b7ae972e06ef9f5e34eb9fbdc65455e1c822299e06a9dd5a3f71a0c1efd44a0000000000000000000000000000000005ad2ffa8861848c46722a7924ece68580fe44e03157c982b7133361e974b59dab7b75358fe498fcde9f68b5b99f23e0000000000000000000000000000000000adac33e0b7e6740c980a4f297917fc4fc13f53a71909f2eecd0067656c6f82c3b371cc638509151bf937f8257aa415d86ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb000000000000000000000000000000001462d509503d2c33829c3fb5380199b79b970c2ae7f944e54a6d0f0deab3571976916cfc311ea6ce6128c467665fbbd10000000000000000000000000000000017f6fe356cb0dd5bddd489c26669f0f365260bb48a5f862e9bfb778a7ff5392938b905759718d050f7d93f107236cc75000000000000000000000000000000000d9b3ca93c5133cabf3d3daa565bc6b51e63b7e37f68f3bcc43b9b3ee7db15f8bb33052eb7e332ae3e9ffafb17cb77d60000000000000000000000000000000017d6b898d9799385990c9dcc3f72ed93333486b98349ef106a230a71d768b75cf56cd946f5952075bc41f26dca9c83c003fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac3773213600000000000000000000000000000000158e5e008796c10f6050826c29523864d06e68977cdc95d281a8606924aeed0b475ab152bec5bfca8e0ec53691b307f50000000000000000000000000000000006fe8e75328c067546eaba93f4be2b15513bae4a3458112c3ffa457d15c23636816fb469f071889380f31870d713e949000000000000000000000000000000000b9b21cd58f8742ed094e9b770182f6f3f855204d869e53c02d0c242a133e957c53c9fabc827d6379b39541170be313000000000000000000000000000000000014eaae1f0789f0b1e8ad3b452b4ed3ff87bed49ffedd13c8c35c35668c33537b63050c06a5bf3d88d516cddac13b4c935ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc627910000000000000000000000000000000019f976b3584ffc188424614fd287eb79f060c55e9b3dd2f3eb99760a7cb5b70e2b62a0895b05e7cce2e390853fed61b3000000000000000000000000000000001117181241fead3865eba4804ec2c14f571aef5351d5bce29399113d007cd4e9c262af1c77daf9183346153e562864b2000000000000000000000000000000000f823f71035a4870be2ef20bc94e97d74d18c0a1be9895fb27c54df1f663df6f9e6e45ea5fe4502143a84c05e517b02b00000000000000000000000000000000141250f392fabd4566e0cd3a472a4b2971a432a3a5e1d9c924866c7a9516322bfa691e9dccdd5ef14c561bca6dd70ba204a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa008452700000000000000000000000000000000014d6827b9bc782863491bc7c544263f58dc04c18e08a87ca2fbb5799c4aa70bc039416a85dbba67dd83bcc27b70748670000000000000000000000000000000016c2816e93ea9d4bd6e42a9720cb89d637d88e00074da3300c6409be98a03403e9ac15f83167cdeb13800ad174ac47f10000000000000000000000000000000002aebc0116a62f93a6e86c7fce86745618e08f4aa9cebca7b520e9176bcdf1521cb2bf7eca7f7af9487fdc82dce76bb50000000000000000000000000000000010684e3254207c4ccdd49e4775198df981afcf7d9f89b894e204c5dd84ef42b89fe3e2f6b9278470e6cde4d3f4abb3b003f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c9816210000000000000000000000000000000010ab1d5494509060c9784b4744a0572a9466d6c374524a6d338ea12ac5ad89519217c462c3487e398325439311bea86400000000000000000000000000000000197568cb53ce03f00aeb04278f355da862be757366dad14ca6d30b3a537df9855a1196010773768a91cb4bb664a34f0f0000000000000000000000000000000001fee249315794d30eaf929f44b99e07927194c6015ff34a4530698d7d68239240c9cc48530d52ea06218a826a655cce000000000000000000000000000000000645b5d701bf3422228576467120935f014c754dd68bb3555b50aff5ca04001a26298982c97a64469aeac3432784efca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c94234200000000000000000000000000000000113e17730f8dd7258157085c30cd9d1950a26c848b55e3a8a55865eb567edecfb09f32ba27fb3e2096ea00c30f31ced8000000000000000000000000000000000076db9ccf8df9530b64cd43ef7b496d1f432885062406028901bbfc5882fd12533f84eb12aa2ce8b7adf9dd980db0870000000000000000000000000000000015e487de49f1e494ce9907cf0ed31fb0a159c5290538ad969b2c8a504986dc9cccf7c74a61f622154e928aa2dd689c0800000000000000000000000000000000195e887083a98fe3f50a9ff4b342e004398cdfee55c4b02a4db0f65a77d3c0b142a45201674726c96d5f79f8604d61860033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000d80c7e50973205585b20a068c64957cf4572eea40e32ffa8b759c38c6ad6f4468421f2fd6a6f5da1b0d008f625b3e6600000000000000000000000000000000009242dc1de055aea82b3b917f88b6232c550c3aff41241a7e54caab4c234d29b5d8138968846f7c754d73ab3b4e7913000000000000000000000000000000001188c31a9d8359d737576f4ce7a7900314aca0eb3b51baeccfdc9245bffec49143a11b3331f9126b01de0c307aa4e44400000000000000000000000000000000104ef4835124fa6b30dd551653aca25db5a544af6782cd0b1e7d26178253e0e33cda77428fc1dbcfe6114a758cab5c814c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87", "Expected": "000000000000000000000000000000000e79d18633c18ac818786bba87d09c9bb1571e179d8769f8fb82e2e2b7a6a8695c1f4f06deebcb84524e8facdcb49d0500000000000000000000000000000000149d0231fb030a1bec170decd307c10e72cf1cca55c8a1b67aa94ce61e4c7d2ddfd0b8e71598e1abb054355dbcac1528000000000000000000000000000000000090f5be784dbafb0a8aab1516c773720341de6176017e0fb43a275d60de54c1189144956d4876d989232b362b90851c0000000000000000000000000000000019dba28eaa6706361f285b3abebef68f764204c74ee93ea011db01c19591ddc6f98799fb3026c3c223effe4489a7c676", "Name": "matter_g2_multiexp_19", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018a6a982acce5693e632901f3136eded40071e8c7caa7887f302c32621c5bcf9478991ca519978b52f8f69415c0d070b0000000000000000000000000000000013420ab920c8ecad5b2f9aaf9b0074c2386b0b08c81923558770d4c4a6b206a865af8322e9755706cd5e595bf0ffe564000000000000000000000000000000000c0e5bf5465d564e3ce86d6b742ca687448e6952439b1ff44b86ee6461464e07f8039e8ae7a301c6caee7eb99e38fab10000000000000000000000000000000015eb8751b750af62f57971e88b436658758bd5712f98861fa07328d2b11e8725fb55a2a00252e0be06b0c73aac0f7b8cdbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc80000000000000000000000000000000001328927910ab502e573188271108706152f562b1d5f6ec074f8f9ec5eaecc6cd5e8284a060b65d26463d22c8290ea4ca0000000000000000000000000000000005a1fcc348122350981dd5090c865a2aeb851ba8b6e0443c32f48b157ba673ae5652a70390888b3458afe6fe975321700000000000000000000000000000000019edc749a9799c8d3df75d4024791943a8fa02ba0cac90b6819f0bc42687b044457bc7cc6073506e8fc19af37f224624000000000000000000000000000000000fff20fb2b554b63758963c1583b996ad450cfbd5ca9952e38f38a8994809096086ed86311f7d73a0a5898ac261ce09e57df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec00000000000000000000000000000000002a7005dd32bddf1031f27c2ab999604c048a37c39734db48a30baa86c61ef626cf82084651ae9ba8a265333060a408d000000000000000000000000000000000421bf913a25108b8f520b2becc6f8064029dc046d0d5effbef31f0af59eee71cfce83fec8dda7983d50c6d5cbc8329a0000000000000000000000000000000016c75708f1dbfbeae3b06e5e9a7fb676c27100b99deece14d979b32a9c3cde6e9e96c8560a00aafbe6e7decc84e7e2780000000000000000000000000000000000ce23c27b5128bcffa424fd1d181d21b06b77bd6549ca5eba9a28cf18bb9a979270f6a5807c640dde57a0cd4f3af8cbe2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e000000000000000000000000000000000690c7ab321c0c93b5ae4ed77843ff4030e4ffb504c685d28573e98836e8e56dc19d662ae9f496a346bf2a8be5396741000000000000000000000000000000000fbbe3861a8d202b10801cdd606b50db0ad6ec7b923b90ae81ff5443676c3399e249e9efeb47b72d2b0a54cb0594686500000000000000000000000000000000148a27016968f0258e5eafe0a8182c22091873a5a58b27aa2160674584e06d5b2f46fc57a00617af18d0688df75294cb000000000000000000000000000000000148449d00b3d1b5b43b08a0c6e909a2d9c66920b60224a2c6a2521f0bad35b99e3bff8be0effb2f7f34438662d7a4882897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a9400000000000000000000000000000000026b55509b81befaf6baa682a3e92a0ab423fdaa84d2897613fd31acd9e1590f81581ba0ba87d68af76b01c36093e183000000000000000000000000000000000c675e190570bc5173b8f508d5bd2768c83e7f56a08cddbc636792dd75386939942827617c4aff8628a74b74195adea20000000000000000000000000000000014f59f38ae9e77f3a76478ecd47f32200567bad11f191d303cf15d7801ae7b5a3286095fc8726acc9818914b27a776bb000000000000000000000000000000000da89fe9493b2d9d46596d80162f5831d4fd8cbb83b46e84e95d5d684eb927022ee62ebc3519442007fdc543701f97bd2f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000170e52a240a7ccf2d57ae92ea8dabe62ca4b458a5da42319ae89cad22ebf13541b0daccafa1b1d3cfcffe81b500c4cf400000000000000000000000000000000174879425f3bfd40fb74a88e3dc578e45b0e0eaad94da009e4076dc42d234d78248ec3a035666dd6de235f87e1a47bcb0000000000000000000000000000000005aee47acc3260d11fe0ca16050a29f92763b3cf8ac78da52b3b2b3e26d8ce7b6ccc187fcd81695aa456e9b94a84269b0000000000000000000000000000000005eb297abf35b51d57474b4989dd8f793005bf8e82e49859c41b786ae39217b2321299829198bda4aaa261a2723d43d6ce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a61000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e277b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000002d947e728a3b376de520bf78e56452930de42544241180906719a24d72df65f8250402ccaf14d69935b1ecbb0b4d34c000000000000000000000000000000000d5614ec77a9f31915dddb3e4bb533db001702891a45f0bbac49e73d9c19a235a00442b52d452d77018f883706a616f1000000000000000000000000000000000dfc6a73a8e36b7b2d0614b1c6f7bf1ae284ed740c768f08416c0c09a601fadf3e4d7b17a93601b1803d19a04ccd570b0000000000000000000000000000000010d6a8e4eca2e818d6dff13faf0fae44a7fb90be436a9ef3aab05515a35cebfbd53e9af866cde1745f0e2c3b045486dd2b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000d6c5a6fe9b4d4580f8e1d89f0510bf5dd04e113d6ae5db04af2553bc0eb3a32fb881300f638fb33f7c4bfaa10b063660000000000000000000000000000000013e001b08191707ad98e21b3e0830286c6f3bf587b971dd4ce39e55f06db427676626a5c31c4a67a996a5725ec8f402c0000000000000000000000000000000012f86ed85113ed1abe9dd3826423911e63df0dfb51ad3d1e0e0318ae95991a6a11150176cec77f9c83268a322cb7e934000000000000000000000000000000000dda719cd2cf1aa769f94c21af20ab076b8f024e0a4903e38ddaca21b6bcd6f00baf7e1ed23259f135eb8bcf9c3f97c44f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000195ccfb9038bf9e637b88c83c552ffbd562357792513b15f703bffbd373ebaed715a6772fa7e6e5678c2e6422811dae1000000000000000000000000000000000c5a110f31d71b12cc42974003ba39d99dfd91769c2e93393449083a9b84d31473e3a7dff7ca40164e6e7215b03f44ef0000000000000000000000000000000006233b2dcfed96559b565928a494f2a50c2c375b3d7c60ee6b286c538f4fd5ca6f8b2a61654fd04d679bb3e05b9bcb03000000000000000000000000000000000d42233b7b5ad809c735c89c455ba1e8fbd623e1602bc729c01d362368666e4f90e7b076e32468041f3f5665c6fddb0d0ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000f6d53de4f8b20de19b2fcbe8a6b8b8ec4bb801bce7363f89b133532ca7ce4925312e23c618a0182d158037c0d0bf07e0000000000000000000000000000000006ce094e24eb14b9bb1b4a1838d8b6da5f53b5c5799ab8dc8934b488cbabf698b99abeb016259a4e1b0f626d27f2c950000000000000000000000000000000000874aec7c8ac360e3980a6e2cbf3f7468f1df7a8d9158f8bdbb0f387d19f3b05326a081129576251ec41a926f670e58f000000000000000000000000000000001711c9b2ed7e2f789b29073f180e46d0c373d6e75c587ece67b8aaca1e9d9b43a96d04dfdcd42f943eca48e240b72ba8002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000e8ddf88269aebf190bf9bd7a8276de92ff6039e479e42a490fe4ef00f646b049eb8ec4b8e073caa000bfcd86ee8724a000000000000000000000000000000000a9623655c0121ea0575de714e53c9e304fa3309f00828ba0e786112781a38bd458cd67864ab17929448171b5937c1d900000000000000000000000000000000198fccc4a333322599697e904e9096240b9c54f89ee6db97475beead62ebf730da1a179409133698ef13abe1310689270000000000000000000000000000000017b059ac08a3fcebde5888bec4d7cc2c70b147b3b1483fd001330637ff1c036faebf292801204bf2ba49350795708dedaa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000a36274f33b4dc09e03a5ad648af0913e5ee95af83df8b4f2a158456aedf0a0528f9b4832b11162dd67e4d22b26e9f940000000000000000000000000000000008ce96d8bc0aaf2dea732dea188870d398b1f3c266b9bf019e1046cca05002416c910e02e998a1604a17c333c65c99a0000000000000000000000000000000000c1a0e4a80bb0331a94ed14570053f941a0438794e6f19d976cc62b3806a565697720ea03c2531004f13453991bd99bf00000000000000000000000000000000184bdae93abbe4d931a6a51ec85bc330d6181da2d34f2cc530e56b6803515ba87f5719fd6fce6a1a8bf1ee5a968bbfbedf9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d8600000000000000000000000000000000016c3782daa55312a7cfa02c3be73ed75f4b726df5592351fffae19121b5cba73f427d35d5a2df7c63e2a5c68bf57f3800000000000000000000000000000000018b608343616eff759d512c97257f2103cb0909afb4c24a1cc9d8204274b7c9ed51bc762a6280e223a6116a9b23d1f1e000000000000000000000000000000000c687c11a879ec285180cbae3d2e4219df4614e238d4cbdff148ce5a8d21647c489ade3bf6f738052f149fdbc76c8bf6000000000000000000000000000000000936b34fea3a2633b9aa32244329891e332745876d05f95e4efdef859b23ceab4869db562555e5c8edce87a6fd075ae54005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000000d4636a5e13bb59878319af6bb7c98e5d247c2c9cc970b9cec98027de2d4a8ad12d50906fe302c3d055c499a3742ee30000000000000000000000000000000002b0214bb1ee887a7ff10d458fe35208573456f685ee2fb93bb470762c9e27595cb00f2eae7574c8467e417c63c2a960000000000000000000000000000000001710d130f91861230562cd7ab87984ef45916af8e1168fb17b9765183d9d3f9b2c81c649687842de495a757471e28067000000000000000000000000000000000dd15fe505b1364f134ee77e5e3c1a497a20849b6ec7e201813677a1569a9f5a9edbe3df4c36bdcf9ada139b20e048ec893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd151570000000000000000000000000000000009a48d7c55d24ba49f890791d0f6a8a5ae08a19177575dc0d734fa37b52c3adc45b31b5e485a5d4a5533470c3549f5f900000000000000000000000000000000090f680c6fc1f0588add04ee03bf821868b1ce588e3ebe384dae657ba7885ef74da0bdc98d9d9594a9b979d5b50b93df000000000000000000000000000000000314f6aae1e99dbe3ea9ef85db7e1693a30869f48e05cdb073bf8e14865a671e75abb875d1b41f13d4eb74fc802299c70000000000000000000000000000000013c698b76dd68d1b9ab41672c2b07cb9a63168497d1144b51509b602c5acd71ca6cd049616d949214d95ab7a906a8f8bf685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b", "Expected": "000000000000000000000000000000001747f6d3154e0717435fa023754f115ce2a2b3241b62525cb2833473d84a8ccf4c95e3ea030f2b8b0ccc61124095ac86000000000000000000000000000000001827ed7d84a61c21268857036e91c732b304f609f285cdc4736c951fd8954b10267a8505f25d8be666792358632058b400000000000000000000000000000000121ac61f59051e6e89a7c1e2fb4df4b3a5b7773f46495a99e55348454e1d9d42254e5e11b841a1654ff9c80b157389c70000000000000000000000000000000001bc60cd06879980bc6ef2ca109d31f12cac28ebe4d2a934076d720b12f430e1bc4d4260f40045cc7a862726521a69dc", "Name": "matter_g2_multiexp_20", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000012a6984f0f8967c5ae6b13569a62095b5fe61ec607daff1845961bdd827c00fd56ef864802673dd21d90560fef6cbea00000000000000000000000000000000085ececa080d0f4c996d46c80a1fbad2ac9cff8b3e324aabb67182d79f941927050f025b633fd5119f30bb29b8e4b6f2000000000000000000000000000000000987518a5edfd5ae2616fc60000e117a4f1dd1db68195c3fb68d8cc639e4200945b2864d41ad86fb3e11c504fc1f9766000000000000000000000000000000000310939c7e11b93e5773cfd36fa70020c85396e525840742f994110e20019769abcd339db6881291639c193b987b68ae94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000ec6922dfc74009c3750ce2540558c7c1e05cb45a5d651b96427c615d8fc563219215a0ee431c0a4827e40b26c4f8d3900000000000000000000000000000000040a4189d002a0e1ec600e71303575e82414e6400f06b9abf57151a28835d454f56421a6dc4049902bfb94dc0e9967ee000000000000000000000000000000000dfefc7c163c34cc004e9d97d812b2717d4736d0d1c722b6bf1a29676a32c8b46878d05a2d137cb7fff5fed8c0f02474000000000000000000000000000000000e3f0c9cbc778693c8ba88af8306d45477493ed6be1bdd9c81c65341239eb510fc948142cc30b73f570819b38f13e20f6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed70000000000000000000000000000000013705ca4ecca16559713df65b376c7c5825b4f63d001ebbfce9cd1b592af5f2ddb38ac7c5ce3c5f7af4f39f909887e8b00000000000000000000000000000000179efff38ea1044e91ccad467cd2b49438079ccb4d0fc692e79e0bc374abe064fb9979c4a1f4b92c15cd1b042b501d5f000000000000000000000000000000000b6fda2dbf6339af225515681184843f1a9bcd72f7b1389f186f8d0e048ac16e20967c28e087cf09d7bcac597a85398d000000000000000000000000000000001946fca8c816e1e11187aabc40dd2436533d537ce4639eb2d08630ed2ce402c1806b6c2b3e04a960408fd4d2049849bae5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000001802064095d029d3897725eeb93ed6e3b090390769026120aab6977d0de264a262aa312c5777ba322c9eac29e5396fc6000000000000000000000000000000001410f17820941e6a67b1b4993496cdcf0d4fa2d4fda3d43ee985f2606b1408aa9c9ce412c80c90a0c876cb5ecb76878c000000000000000000000000000000001514e9b2c65ca86713447f2d5bb8395fe8552e059829afc68bc43ba9267ef41ec6d69d06e7407a731bcca77ed5d9716f00000000000000000000000000000000025b5bb18cad46179fab15b2ccef17858f9259a90ea4548852b8c6fca69f0ecdf0b175669bacff1625a7143e762514194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000146eaf5da57b6ac788f8caeb4b2ebf7c8999e03dd839977046ca834fffa7e57cd949e3fd44999a007b5dcf3c8621ba2f000000000000000000000000000000000d859632d3424ffe4227ae14856e05c4e750545cf276c97aa9ec03ebde334144eea670dc68e92b61fc775e477a2154040000000000000000000000000000000010b44279c0c80886e52fde5e71726422da2f9457ff86b21426d80356fad95d5ff3a7491002364d9de5ca99c2500f344d000000000000000000000000000000000851b769a691f0ebb53ee3693833881fed8dc6d9e5f1dfeaf4ab1aa7ad54e2fcac246b70d81110451ed78044a98d1547de4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef100000000000000000000000000000000131c9a76109929fc977a0a6eda0a7c71cfc744f5f3654e2221ce84c70787598e24c5d8049f92a7c4d78fdb869cbdd1ed00000000000000000000000000000000049872d2c7d472e090d2975daa64fd96f33e7f934e739633b1d7fcd5e771673ed8820752a0d5c8b0c6933318293a4f27000000000000000000000000000000000dd68fbb592a3957ef893180dd758f75978042add36c91b7bf87c4493b0baa875e1854fbc09e6856688cc241b76ab5a20000000000000000000000000000000006143699816cad8ab7583a72b6064fadb6caeb51c8625ddbf7b2911426cf438534da1bdd13e22cd545495c486c9733f7bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006c14301984607d569ad1bd774135e7c9e328be1fe54c3b543276bd06bc0bfff11f299a5eb43b5218c3605011d0ea6d80000000000000000000000000000000012f0a848022f95f4884380a5b8e3637a41e3c399a8d2765aada85dcf4b7c2b559122f792850430681a58ca153be2768a0000000000000000000000000000000016b4cb233e1bd59b7b362c64620eaaa5029c173a05e2278774ad6ed746c70a2f6e76c237182f5d9d790966ae69da5d44000000000000000000000000000000000c277d54a7a72c8528188f6cf29d934cc66471607e5e30d493cd11be6b203bdf734aaf37b686cd7101e8599b69446991c3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced432900000000000000000000000000000000169a3928266375dd5793b7504727f939ef0ed52d69e569b1b75a0e094698b37bc70472578beaeebfd0c3df4bce6177810000000000000000000000000000000008936d470dbb86db1567bb2fe7c09971c6d12b07208d9b1b403c20fbdc05ef8984dd576457fc6989470e40ebfe4ceed30000000000000000000000000000000009cdec9d80f2bf3ebfa9a3316e4250741d0d089245df2fd3c9bba4bac1c2dadfe212682166a0962f78c4bf25b618da900000000000000000000000000000000016521411286cabf3fa2c8f72ca62ca311738fbe63717fd12916a4c9e6af9b05d1f5d65cf60e84d9fc5f7b7645fe9bad570fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000006a827f6149a320a74d9d8c1ae8861c1cb963b3eff899710eda642dae6ed4dbc247a22131758d9f843c62710ce083208000000000000000000000000000000000c83a9fd96bcfd4adcfc6d5a47e84108bd763366e91bf06a7431c6c3a107cbe5647da99ee6c1e57c376d366b21a923df000000000000000000000000000000001604d5c0364afb5503b0e1d52226988d7f7f043ce95e7c0a09d7f96e24a58f089156f0e6d19022138170c1b4b7dd33560000000000000000000000000000000019a11c86f78ce462f46e0462052cc3d342596b329fb62a282a59bbd64c345bd266922b1540e40aac147681754643c2e3e538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000015635de295c16841bf44c73639f047f735175e8906301746837838d124bf0d2a1ebaee142393ce9a0d58107c7cb036e90000000000000000000000000000000004fbbd4252fb901d0737d1bf4da62010c06d690a9584c7631ef5d36f1d8c37486a83f2a1e2db21f05c993fd117c662e8000000000000000000000000000000000f4cfcec1545a08e0e0298753ebcef5f61bfdb7c1b9af71cb4c2f783e4fa3948945d357e8302d99aca96df0cb0fc01a3000000000000000000000000000000000f543dad6d4b797f6fe0b00215a5f70f6340ac6bf7cb0bdfc5bc7698dbf0647e4098413dd19ca7af01685edaaa190c6e30b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000ed552e94021d0912a0e7563462570cb572b189569eb847bd12ebf976d22343b9ad04d400ae98fa184b10ff36720f12700000000000000000000000000000000178727c3e6ff33be9894ef26347b104023ea0bcf79c1a33afc26ac0ee9879344964fada757118829214cfcdbbc0c5a30000000000000000000000000000000000b0a6a575afe5b0c1e287815612fdd3838ab39e8ee7795855837588614715f6687910c42217ad52c1b8721a9e1c908dd0000000000000000000000000000000018cdbf244c78cae1993400ae164b42c09dab4d8e3707a69e25ffa8d0b96b8270c022c0375f933f16f45c9274132a0a633a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb460500000000000000000000000000000000070636611f903f55cc9499481bc3415a6de62d5e6bf8bfa82a8ce665f85bcf01690118441961ff46ff701e361db208500000000000000000000000000000000013d22dff8f6f86f659ad17ef91d90a70c180538f03e10de20c445d22e637015d51a311a3daaed90712d04c9a3d992d12000000000000000000000000000000000db3535057db95fc262f8adfd7f08f3237fde5f0e2aab589d4ddcd9c23aadc437e13644dd3b3534dcb17936a7c610cf200000000000000000000000000000000044c177d4484c07fb04d1dd477b188a2c157973cf26075001d14d2b07ebb9dbf8e495dc23b32a2419621e1c129b08c5ac7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d56000000000000000000000000000000000e10fd069f2f5fddaa0112e70ae89d1ecf034defb24e2923731a7c0068780177c186fde92a3c254a1cbdd255111a4b7c0000000000000000000000000000000018363e01e86e2e922ba435651ad892bf9288be14b54dda821c397ae6167f9478c8132e92b1c2cb0c4037a4e020f08291000000000000000000000000000000000301b5ad2d5c35ebdcc7e7cd1ebf0405cf204d6f5e30ae6f46d20534eb6d7013682c5ae1bba76d2811124ebded0d2a590000000000000000000000000000000015fb3a8afad778031d04e094cbde5f02dcc89ad7b7d452c6c8f41be336a4c8b26e75cfc685b8776cbe5a487f09c304083563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe000000000000000000000000000000000a7880b00f6a3e959ff1bd207fa503eff6e7279e701e37b40735e2bc8bf49e355e92edcaf23aa3654bb26fbfb07b5fb100000000000000000000000000000000113d9b792f4e3dcd958664a8778dc4b177c430d8db9da7805595e40293ef2c0a40f7a843bfa70ec134ed89a453f9da50000000000000000000000000000000000d7f92148dca4a9c96c47a0eb284f1834cf3d141be7c0d9a7a060af6e28e45620d8255e465e9a0d8f78b2ffe17d6b04e0000000000000000000000000000000004e7917a8f3070c656d324c9a816236842fbd6147d326652667e7bca0666d214233ed136dd9464c4ac619d46c28e2393833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b500000000000000000000000000000000160cb05390b54151f6b154b396bb400a91fa83d77fabdf31fba349d1bf3b5dfb6476ad4d714af2a2963e41b077bffcb90000000000000000000000000000000012885f7ec8e780cbaa90a465b5706cf07d45bda7755ae3477c79adbd7956b926e0ef5303fc13f0b97349ff8b754dab500000000000000000000000000000000009ad7509e9e7f5018ae3d1280e881ec12129cbf825cb6606459211ed7b358a97cbe430e94dd9f5e4f6b74fb7287f862e0000000000000000000000000000000014d5d2ac2dbc3d5a061f4e52dbfa68e1eb1d3c818ba26686a3171e310c63cfeb188030b83407070019dc5c42dd079413d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000008ee93fc610712411634079be0bd96c3969b48955fe5478b7a31c3ba7639c18291034167eb62e6b15c16b0dd5145edf500000000000000000000000000000000158cb1731b71905d7b958c5407f090a2c8a9319017719da143a3f4f3fb3982abb83b8dfe14facb014321b4f5edb5e41d000000000000000000000000000000000a9f98f775f06055ac1f137cbc1f95f4afa0d1c4935f536ba2e0569d874d9d76b7b86f71afcea07e2e785c7a6ee1c84400000000000000000000000000000000072f8988dd1ab0fa8037d3620068b34848c65e20dfc90612d123b6f9dbcf9d9d699d5ea73739d31ad54c22116365ab983ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0", "Expected": "00000000000000000000000000000000161203d8db1381722644f87b04f47e4be2ea2bb105ea0e67678bc8d29f8a8a3247f8c05e057af8f98032faa93d896aaa000000000000000000000000000000000d3af4842627a095a2dca99b52d802b2ef8c8f3d09873ffe39d224333fceae84bf74780956904df6c1dcf5ba31be218d0000000000000000000000000000000001c79fae014e55e5d0239645d618593bfd5aef665b3e636dac5d191a8b88949d207cf0ae9822ce8e1997de302b386b8800000000000000000000000000000000136314cc68b372b06e7771d82b7ce7bfd0e9fd306787e07629f831c7aee853bed80172121949a940bc59c4a0b76f0819", "Name": "matter_g2_multiexp_21", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000099434adf799099f2e6e2fda4c905e1543893462133ba216aace25836db37b3dd5bd80af1a8c31c7fee56b5ecf9a0acb0000000000000000000000000000000008a6890e5bcacc13e116e3fe2d772ff49839803e4f81d6b088ecb7835b1ed44f2bfa04de1d46dd352346cdee71774e37000000000000000000000000000000000e94fe40225e863b7bdfab4cdc0c1c8d1399554ebbfa3f2c95ddeda74b3dff03d5cc78e295accdc9f02f3f89b4953de3000000000000000000000000000000000b23f2912fdc7a5fd1de69c1f479228f8ffc9f97c40845808cf17a6fd8131ea60285640d32bcd64c9be71d419aae82fb16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f510000000000000000000000000000000004c6f5aaac90132b2d0c6a4e70354ed2e724df7c3e6298eb9ae4ea92e3c7981944c89140c52e893ef2edb2773ab36bcc00000000000000000000000000000000021e813378be9ec30395b917ded5a0424fc7eab0abfdcd2328f725bbd6a1dace0a5aadebe40e10470df0c09b3f4b68440000000000000000000000000000000014e3fee16a833f8c543860ca438d763f764f488463601741a2331fa90efce9f6d54ee0fb7978460a1ab838039d398918000000000000000000000000000000000dec8bb882fe6028a4155e6e2bf48ffd314b5519dc4560f8f7410209214c4a8e37b2b36facc53f4db11ee63ff11f9f228c02014d5392d30863a12102d1c9315839b5611dccfdb489207f9186625138500000000000000000000000000000000002d107029bea087a2d53b6b371aae06c695fa85631450f4ad92c8948b09ed568b28948f80f1455cd22e2ad44697290b00000000000000000000000000000000002fab10cdd8bf17a633c8b3ee8ed2ce783f64bf978c384fb7dbd7e4f0da50b65eb9530365d982bcc17ab91a29eabc065000000000000000000000000000000001369237fb3241ac291a868e6f4610a5103d93aa915e954f18bcf348ece1560a12451723b96ad5fe162a6107dabe1c986000000000000000000000000000000000cb70b7064a2f94efc86060431ba4dea38bc64822efa73c76f3a4500ad23c452c8f2e72713b066a45bfa49559d14a719d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea2490000000000000000000000000000000005ebb9c8202cba234851cf5e060a4114c6fee0632f37e0c52aeb852637f362ce64403347d336c32617cc59f23cc7c93e000000000000000000000000000000001126827b6a0a8adb698854c0089276861e3cccfee420512f0966df78ea0d9c55e85a0536f14ad40e649b8fe4384c836c000000000000000000000000000000000998549680649b294d506c529ade746aeb087f75d62a246b7abfb69397ed67f0f2ccb4811219b35aa894b2f87e3fcddb000000000000000000000000000000001027b604f877ade32df8de6162251acf2751a9bd770c21f22dc819a4f5515bb276a246ad667fe7881965f0b083d1f76304753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000005d1484bad44069b16d1ef4e9ca1db70ec6cd82eca645c2fbd4029ab4ca33d79780ebc144d8774d82518c1fefaab38530000000000000000000000000000000019abc7063361ed64a5750b70bd59283e6a61d55d49d8c2ea2f1be8ea425f040d3865c399a66c253bf38355360f06cdd40000000000000000000000000000000010a97b13b3b579ab5f7fd9801d9e4fc40f3b2b2acb9f21bfcdc6b6a3168720fd0abc2f77ccad01be6a6e268fddf3759c0000000000000000000000000000000004126b5454050d761047e5da23c0b2f9370996589c04f255a1ce8ef37a3a7c8078788a0125e4aa86aafce8df33f322d3d1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9aab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da0000000000000000000000000000000016ca071d741363e7c3297355e49cfbdcf03d419813ed7b329cb2b2a26fc6a46cc52149ca3e9ca3ccd7284cfed97b985d0000000000000000000000000000000018da360fdee88e806ea1a61c01e86687f8e5359730c36c876ad2acb0297bbc1ae13d790d1edaafdaed65db9dac02a74d0000000000000000000000000000000005a46e4572f667b46aee36b8d377c249de25e797b31b822474aa647ee68cc7d40b083fd0a1d938e2b8d85508004c73f40000000000000000000000000000000011701bf88d4287c98996ea561c1ab2f29a5da9138338c7c7539a5fc8355efab6f58e240df4b0e0cb7f01df74bc8010501d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a000000000000000000000000000000000576e79e507d250eb4040197064b8898b0142b3a2551875935f91f22705bfec6da156c7858fbf77028d4a00957553bea0000000000000000000000000000000015d39a325181d6d1a809b1236f4a1ba66a9bfa6c448470425aa5c8ef9fd00b5481c51e8752088dad62e928b3180408df000000000000000000000000000000000aafabc2f68a4933c7d734660e422ba154e37dd90114272e948f79db4ca51d5ca75d504cf74f2dd0479871d69a08386f000000000000000000000000000000000b017c731f63bbaa8fd0b0d9c17140060429f515d2e85a938d10f6529deeae4818c29b9a628802d0ffbbff720339b7bf2cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d400000000000000000000000000000000028c4dacba5f33ba66368c19491f4baa6aea4f309afafcc8f464f2886b1d05b6397142d02f0295fd50825819621673a1000000000000000000000000000000000849e1b630e8db8ef039f280f8d401957f807ca90479745b68c3db1b5ce3a02fe2c099ddf9c387d7ed76ba75d6a9be9700000000000000000000000000000000013b43fabc3d4df82058db215a69776ed5dfd4c773d7a013dba3b4ef5cf65e25f79d7f76a06ca99132d6fd1fdadb59d400000000000000000000000000000000072cde8eb3d3e1a7f7e4a9eedb8e56f5e103db6de6ccf833f818f02a0706b2043d4ba0d5473bbb6472e8aeb28364e1d8214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e00000000000000000000000000000000007a33b95f42cb1d1ddeff3a199ccfd9a5d47c9fcb89dc09b5b3f59dde2b47d24ff29931920b76ecf6deacd70e83576970000000000000000000000000000000014c0a63e0152f06cfc32e6034b7829f9d9d09aca0a6ef821dc61ae8d99b77d76c1b2fafb2a14938a82ec72c4041ebd9f000000000000000000000000000000001433135cd913b05b3f58b2e9c1a3bbb951d2cf6c92fddb21bd5e1d9c44e464d5fe98f0791044d56e50b81a83ef6cb271000000000000000000000000000000000be12ce3bc47bf69a13762343b5e39c2a2f285896e5d1b73c55203cae2f32cccbb4f7b8230b2026a0c8b2f63db5e5bedc1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e2000000000000000000000000000000001494984d478784b2ab3ba27464109f99172033fcd5780a48fbd5a2144354157f6fca2d70b15b0081dfd306ab4239cecc00000000000000000000000000000000078aebc22025af53c6542abe56cf72ce5eb11d3f19212a0f7442d0a0df907c8aabe0ec01d1245ca237a691e685011bb8000000000000000000000000000000000415a1804a46f4595014ef29b12d99b89600aab1d98352437ab8342abf479bb2215bc687532e75f140918b3d030ad4520000000000000000000000000000000015e7b0dae7e3e80eee3c7a9ed4c739288ac2192f7d80b2c8cf9934cea5719081803b207623c771051d7694e705744dbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f74000000000000000000000000000000001470f82372e197a21aaf46cb2bd3c0b77c3428bf2ba073311e75eb65471a8164753ff1d989560f1ce477952bb6555200000000000000000000000000000000001645b5e5b4bcb5f6d34ac841e3a80f09a86a5edcb7f2a7e7bf549b022c0073e01be82e4c9e5c8e8de76ba367595639af000000000000000000000000000000000b43f6572553154e2530fb448d5bf20c3a182cc190149d3b1d75b60e45baa048f44884500fd02c434f9f7eac01dbe4170000000000000000000000000000000014adef5a52d76a267f87d9a8b5e9f570e7775ca4f6a55a5afbf80baea311b1866fa0689271799a654eddcfe36a6bb64c61ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a000000000000000000000000000000000345a2ffa21eb06fa1d76fd81b1239147688093c6a44a40cae37f2af26add812884bed3e8b4643675b1a45320c64f7a8000000000000000000000000000000000c58eeb5ffdf886d6319ead9e6e190300ceb91d58abfb79c0a322de3987eee73ab82092eea8e1249e83ab67e33b303e1000000000000000000000000000000000763a3fba513b6731fb501aab39a4697f3e4de89125c6884f9782bfb73e6e062f17d34555a04a8e2959ee4e1a2ee284100000000000000000000000000000000024180dde2d23cd88cd29c8142d32435d0db57b8ce8e309701fdb963533c1cdc2595e3bfc01d8c0d08d594e096afb34a681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da00000000000000000000000000000000022d2e7502c4d9587df7ecdbafcbb813b1812d76655cb7f9f57418d5ac83d4f60b84a0ab5b53a5eee3c3954aa9fc70cb00000000000000000000000000000000083212aa1316561a079cb8d027bc8f89161fc828d050c8837a24fca6f7f94b6dbf10d6032fed895a427f07827deaf3cb00000000000000000000000000000000021552b99dc02a051ea3af1b1bbd0a7ef64088c3aef4a58b18a29ca05e1f442f8ea2c8fdb3642ee94c5df501ff6898f40000000000000000000000000000000001015a7987d329cd1eb5f991c270643a05b8e1bc35467130e9f53c5d96fc3c8336a00c060dfa2d3165358b51b6a521e56f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000c19c3b9d7c7f520968d8531966cccbe6f0c3fa0938480ca3591b7489febdabd56a70ae55cc309e04d7acb3de6f41a3d0000000000000000000000000000000002ddc64023f0de2730d3affb695927eaba50ecb91cdf1f369a511a8cc8dae8913ada2d8f27a65e75deb9b8b648e4e2e00000000000000000000000000000000000311ef260debf2310fc31fb8ecc802200e11400909eba24b14d9500ff47c1c36ec540eb970c9262dac947b0c2053d6200000000000000000000000000000000199c19645375dea7602b74301adcfd9af259e1c7c20f377fd10d56b719f7a6e0e57d780c976124e0675c2a54aae3e0f5a8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe600000000000000000000000000000000123fce6b793de0ce2d31f2c7c4218fb20f9db68946a7d57914174ea773d6e6fe1fbb1de141c742e0a8154fa1d81a91f70000000000000000000000000000000019f75536e004a61c6d7f466bfa06ad0c9375a1028eb7746406e7c71e551dba249b5c6284f635fe26989aeea69075b3fa0000000000000000000000000000000013088eab16ec77c7ce7e84236337e395690169a4ed7e44e23d233d36d5d25e6afde794cca2bee88fe749851a71aabe24000000000000000000000000000000000e627130da43a6ede3bd6f2fcdf008c8f5c7b7b1fa56cd3b367d3096317948bda115d732346e73b731d1921a1da6aaa18823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b", "Expected": "000000000000000000000000000000000e3b85a3d6628e097a3d962f3c9aa37e3c5be43faf2a12cd9830ab33c4a904eda23027735bba563d38ae5ae8b302864b000000000000000000000000000000000c92be62cb091056d981ab8872292017cc22ae4eeb0cee03a60cb5d57d37b264fbed2d752ae9dfd76c0bdde1f1dd10500000000000000000000000000000000019e172b23249a17924b890cda6a65287039d0c32b2c0833409816cb21ceb73ac95928234ccf566287400a2ed7d9de771000000000000000000000000000000001276e206235392fdf65e4ea6210d22eb7afd7783caa0777ff0af719cc1822579d5b82fb4c30f07dffe7d68c649b9e2fd", "Name": "matter_g2_multiexp_22", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000a3d974770eda8c742e5d380482d36fabe18901b0217485141c3841003aeac4459ee28b280550e4643d0f50862bf2e2000000000000000000000000000000000369c2bf3beae4e8761f6c06d9bf5261bbedb177e609c81c9bd81ed0a17573b6e10e7f0512e06109cacc3d483918ed9400000000000000000000000000000000030253d0a050986f49c77ee20ea8e3e07de3ba72c39ffda877bcfe569eeb29598588f5a7cedd9e2e34491a059ac4e707000000000000000000000000000000000ce201f07353bf82ec894ec66c7012d17f3c7968b28b45e88f091510e1646380f902c1c5b036084f9497e9a91476dc2c9f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c54070000000000000000000000000000000018f493dadbcd93df2c614af310e5aec4fac9e502843b8ca8c3de739315d9e9a380f826e2470c96bffa8789133f458d0a000000000000000000000000000000001768f8c3da107b9ac30a12b99f2f3a0f21483c0be334377733cee6024d85af91b03c7ea1c548b42e7a7869141816917a00000000000000000000000000000000076cfc99c16c270d2f6e34aff84832f9ee6493ab757b6361cc921823fe9c30f1c9b1664b650548dba767616bec0fd5d80000000000000000000000000000000006c5f580c9556ed31847b1a3527ac0b5b5f15b9c9197d3cff061c1cf72dc5c96cb5fe98535a4dca8c4e20c8c02158466c8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000010e2d7eb4e874a9c72a98e4c36701a9fa11051b683ac8ab9ac20d14929d72ff7b92a9048a11bde92dc2696467fcb48e6000000000000000000000000000000000eb29e621e9d0af8f661eb1ba90b307eb542dd84a486568f85e19055bf7b8f0a76d34acf276897a01349eff2c36e4b43000000000000000000000000000000000b5f890f22658b207dea2d721d90a8f5991ea2c5ca06b8d1b293f60959ed424dbd7052e010e594a5ee0feda1e93bcef4000000000000000000000000000000000082cdd4d8452078e8b853f196dd76505ece5e98df3e6a8bbb21f422755af23c5ab261accea48d8e4193d6c884773cf6e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c00000000000000000000000000000000077501a457d5f0946d25a4c5eede1b7fd80d90d502bca47d8cc40ab2f9a6d827d7323e7d4035f3d32b02401141f0a89d000000000000000000000000000000000985410246c1db01b42728ea46758906883586cba5618b66c21a3cf58cb98e7c6e0dfbabc5505d1d11ca9d047fb6d25f000000000000000000000000000000001775f4008f688882d02355b6eaa1ab108f239890f71238b36c76228cf2f575cd15f15293a62a72f6ad0ff17d7e8ae79f0000000000000000000000000000000004b6967a5ee180d8b92e95c5ef26baa56d8509e1acc710237083d19269c1c5a1f2d1680e85f0bf040747be1d301300b0f7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe22000000000000000000000000000000000fee170589e8a3d3fdd93b536347af5002e59e8ef2ac8327a7e9f382560ee9bc36b3f636a3f99fba8be7b5ea3dfbcfc600000000000000000000000000000000032380cb6c043e3f9ef7169da12df1c6529d776b049c7061df660df841840933e514eb7ea3152ddac38daa2c52d66191000000000000000000000000000000000620ebccfd931eb70ec688110975ea24b7ee0f9937841aa1b7bf4f45af88b732b76a26299f0fe48259fdf08abefb4314000000000000000000000000000000000dee6bb8c198363fa4107996331aac07216b82208242c73736be31e14e4e04d97a56a1c22479dd94997acb0d32abd3b0557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000eb60e98d6cb4e4b3e58271d47261d05be892eebb9a37f6831ff19d0bf2fc235e655f0eb9b01494868bc082c58ed82d40000000000000000000000000000000007254a64a0d94340bcc2b0142faab2d73e8189dbaf52ad0c3a9206e802193168b8eb03cb18b0e4f1cc95b98b943910db0000000000000000000000000000000001e0051fafaf454072051d2aa9512ba2367778aa1617cecf6a7f989d69c7627c9070c349d363f56711f172d43f5730cf000000000000000000000000000000000f4141c8a45448fecce09908ddb42f7b5f6b5bb53b9e1ede0417bee327644af5c98470e8b5364642fc7435f84be1ab443313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb7500000000000000000000000000000000049c28e0bc677ccf54f4cb46e953a057ffad624752332fb9ee5295438fd5bd61abd2199a0bb729bb7678cf3077e32ec10000000000000000000000000000000007138a996356ca3f5d63bb5a36dfe901254459ed515e18ec8d91fa747a691b40a19878d9a6f1dc74e4f18374a399d38f000000000000000000000000000000000a621b36a3cf04e6a5cb699fe4ff7fb8b3361207186848e81972fdaecf667ceb35f413bd68772f7c1f77c1d3f43a3d610000000000000000000000000000000010becda5a06f3f077218d4387158e4a1ca5e0ef24d4ed304723ed5dd96da7cc9325f7e4ae16d9d6c348577697aa6017b8faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000163ee307e0d0c3b61ade05a022ce2bf315d820ee8ece60f93d63a150e02be843a2eb2240a4882c29be2c7403932c348e0000000000000000000000000000000001fc8e9ca23e8dc8457df8f255db3b434f52cddaf05819dba7df1c5bfed438f756c8b57442197af18bf83fe9ee2b765200000000000000000000000000000000109cbe5279ccb592bd0b33b1a482df53459c48cd0913549739b784ba7ad32872377c2e3924c4d45064b0cc4764220513000000000000000000000000000000000d789795d556a37a375d83120a022f57e26da6e6f9aa3e40e1f32ed04b50fafc4d40d0b9b20a26e4d230dd789e20823013994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000960654bd6e6a6b2f7d87c3c4d6e3fe6c684a50b62f7acf82a67075139a614c056a41cd49769960e229cf07468fc2dcb000000000000000000000000000000001727f2dbcc8d889127060de0079207eed1e094259b59a20fa42ab2783bfd176da00e61a65709dcd60402398fadf30710000000000000000000000000000000000c17805a01e64c320601e0ef521b6573e9c2eb354157cf0412e5c2b13f826759310907c4b77164f5899958cd30f78c030000000000000000000000000000000010fb286ce797c0429ad3385c709259b55cc962ae02c814e537e5261e897b7ee1b7c660273ec908110f997b166c14f5c181eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000015d96a0f988f4951206aeda63af85910db49ab817c83e218ec74cbbf5f34f81279d8a3f2fd1f3000f73b8c5550af3fd600000000000000000000000000000000186d2eca1cac226227d8981324859126864b84e8dac563b4d92357591c2416c93989cfd9e1ab6ad257dfeb168d829a09000000000000000000000000000000000a8a7247a3b09583cd2d4949721160573f1f88221e6eae833128914555a594f21a3fb2bfe3b1f01f3dee90f7772dc97d00000000000000000000000000000000132361ac1950756549c957c174cab9ef586eb2057a4eb22f49252cae032975f56eb0cb7ea70810afaf5716afde5b88015bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d000000000000000000000000000000001328e22bb83331adb09dbed0a8c58040a3564fcae0ec85794f26c077de69cc0a7555f011e028879cb3aafac4dbecab33000000000000000000000000000000000a93db348adb3886802bab1e993f5d7275360a5b0466845055d5274e44716f3e1d03a6e1796ed4de4c157dc8a2d92c39000000000000000000000000000000000dc0879a8e9556b7d9b6d5dffce5e648f835f10acad3afca7a73b0fdd5d5babaa74a1ca80aa4f6880d9b015501e218a20000000000000000000000000000000003f7ae8207de4a179ae48cffc8c6e926455e46ef9e109c08be3ae7401bd36e0876642ae9ac4fd75a74c67ffb7790e265a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d2000000000000000000000000000000001078f43093602a2dacf9b5dd7ec41d47bff02e0dd27a996b58c73febca06e3d977c2fbd73f63508243696ab5d8b97b980000000000000000000000000000000001841869086e850ad97b3122fa51c437113d2bca14deaef5715c354d3845f6829f6aebe668844352d5af3509c0d8da7800000000000000000000000000000000047c42e83194143b9e977fa1babf80d455fc86cf6cb491ef8306a1c32bbf8c868e11bb3308dd5f65fc2942b3e49ff5c50000000000000000000000000000000000872ce87ecd22b39b14c9036e971a562d51c5122bb10939cdfd1945dd1445ac9f5de06b70931aa5c86cd0fda51b89952c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b200000000000000000000000000000000157820de2a134081eb47b1800ec72630348583d77d512b4c6a8c8e581810471a2f57a8eb6b0af87a91960424009ff124000000000000000000000000000000000378cf11b0a2848b06412aa754ddbee5660730001db073724caf902d4b4894959f035a8838e28554b0efc2388f2b4f27000000000000000000000000000000001301d15f290dd11c3f8e53407195e02dbf8f13e4fe25fe38e84740753b5a0032f8dd07df3ce46ba424f6772b3aa66f4f000000000000000000000000000000000d166040d457187232f8f38f2beb1e0e0864105595764022c282867346166e46eb789786a7ec7c00b0446207e9ac1ec05e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf042275000000000000000000000000000000000793797c5bce4b1cc3bcd751c5ae1d293477af96a0e7c6bd392ab4410f806a53088cafeed51754ee7e60e61dc200ccb00000000000000000000000000000000019d595730af1f3039e37494b86a638a528d8bd24c429e3f8bc97076c7463e7f2618e23bd3f300bc7e7a4674f14f8295d0000000000000000000000000000000008e245c7590888fd8dd58f93332b81f48b6e3acd3cfcf5f3b28df654eae1172f52ef5a121707aa9cb111b0b402d1bfa6000000000000000000000000000000000a7c6403659e1a0c2dc7cc2e9b57a452bf553e96388676f4bf4a6e26b3ca2d3cb82006850d8340dacd65aaa0d20e6fba606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000054c37e8acadcec8a795619647d4cf1081a0592de02bef916f847936a1736e74cc3b7ee018717495def8b4ef1d098fc9000000000000000000000000000000000291d89d152b414fb5e7139d6d0bdc7b5b9de1fc44b49f895ae08718b631f7652bb4a895fa11149b9a9db30c344108ed00000000000000000000000000000000107b30992ced35e4ba874e436bed5d88aadf0a0c944ca3eb8319539017bdd652feb7483ab6c705aa17e845723b2cb46a000000000000000000000000000000000895dd8e04114fde4a4cf19925004a72f617f2ff146dd650a2cdbeb12977dd2b34ea7d655dee16ad9560b144b81212f5c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b85100000000000000000000000000000000005cea2e036a8ce057e4dbe2d9d786eb759c2a75934580480f78d2e228c3150a0a1d8c95ac5013aae3ab6e35f524d37b0000000000000000000000000000000000e18c18884209f9e4fb17431248a5f8d29c616a58af16e949f4317c2e117b80d531a39800dc70f6b161b98ba040a8af0000000000000000000000000000000007c42ce885d1bae906128589b72f2e6c18e4eeacb78c853e923e6eb785c073b6490b2f6b3dff2276916d96770ad5019800000000000000000000000000000000132d809c37c341eb0304ec933a6b11bf9ac0d2a13ead818ab6ee03ccc94160b405066381dcdb13b6ee3f5dca48ee10ef47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455", "Expected": "0000000000000000000000000000000009406918e2dd6f06f4782ed110e29516a911f47133ad8adc58f5780de916a8973ad60e05ba931d66de7545a92f388c20000000000000000000000000000000000041cbd52cad2a5f4c8353c7153b5711ec23fa8bfa2f34f5e1a16d8a14cfd47c237766880debb992a05ba9ed0353beea0000000000000000000000000000000017d4211c827379b310956371129011a92d62d11f0ee5b0cbad9eea2d3f2a95d364717713fd0c544747338725adf27248000000000000000000000000000000000a61903fb81064614c9c6894c7f3954aace7611cedf6bab8e751f0c203bcab827d296016947c071d7b6ccc742e28ee9f", "Name": "matter_g2_multiexp_23", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007f90813f8c3eabcef04dc1bc9bbafe1dafe220e2db24e4b714aab2b164d7ec9df3e6a3f903e8b7b96df2ad8297381d2000000000000000000000000000000000e34371e51c4c952a0f38c4aaa5fc2324971ade310af2f36ed511fc5fd7a602a551ef77775fcd0f1fccc718710239561000000000000000000000000000000000787edf7a6ed6b50afcd7c0d3876d8919273428bc49833e3503f650e48e788b15cd82eab2672f612025d796bb62d72bb0000000000000000000000000000000006b49e631ace4f72c959919df5d64c537537ccaa3d1890ea9a1d70f9eacbaaa2ec361edf2d4880c9810976c6073028bc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a000000000000000000000000000000000cb63d7eef2d6614d1f629756b3a619a221033207d1621e4ce4791db4248500649b91ff07cd2f1f06eae3a9be5b6af080000000000000000000000000000000019aafbe56da1569959019033e8cc785c9b98bba6b069603969e7ff1150f023706b461913ea7949306a44c3b7d199e86e0000000000000000000000000000000005cdc3a7004f7a7f79ffbf4c4ba7c5dc30ecc62f270a5c231406fa63d82fc64f45e94779cac851ff8443040fd3b2ea6200000000000000000000000000000000040f30dc98e8668194c9278b189e0c0f7b76a4c686ce26a4a96b93190938f07c5b813670e206eb6b5da29624a1b6314ba59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000a5a1bc231f803ae272e497f812ebb663c2ce8b43a366717fc6349264823ca93e29e30762c1a366d8680f81838907f59000000000000000000000000000000000a88fd59ee380449d632d7e1b926210d984d5298fa807570a63a63828cfa55c6e2f01b7745848281795dae36e562181b00000000000000000000000000000000025ad34537909e07beaaff09f22e91e76d93c668d1b45cf6845ab8ba0129e417b758e85a7100a31a9037e307f454bd370000000000000000000000000000000013590106126231b1c616a5dd7aa7ed6946aacdacec963b507907950d6ea11cf1f5b59f819a43eeebaf51a1faa7daa8e719ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c5000000000000000000000000000000000938d43b9747c926c3e2dfaca2d6f1e6d61d5a621ae08c66a5baf33d9241771509689f9ea7d75af607d76b66faa8fbc2000000000000000000000000000000001889a48a74966b9748f4a6128dc3d75a69499db1ba1bc9aa3a9428f0efa898b5f78a9e2dae942d3794ab3d1157a1d305000000000000000000000000000000001129c9bf343f476541980b85229c5c25289ca62173e29b75de431b572c8f01f64ec1aa4625dff9e7df535194c7f4e6e7000000000000000000000000000000000fe95c71f703dcc71cf409b332f66fd69c330758d41832236a510ec4bd9a28c4732434d4c3f97445e6301e3070153dbbb8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc10000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8dedf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d000000000000000000000000000000000b419b675ccee2509daf66e5da4031b08792e1181140b30489ae21f7925305d8cdd8a104580ae5938586d6b8e74f750f0000000000000000000000000000000012e070ab7118991a20b27f1a87fba1f5815665d76269f0d3d460a6b701e57ffdb4fed2c53fa63a3121c74f67e770f31100000000000000000000000000000000124218ca85f235eac3471e0acdabf73f79afdd4bbc159c1e34c641b97f03735e4c3430264f2d94f640486488dd1067380000000000000000000000000000000011c24f4fa1862779f22a628edf9d3cebe0f7593964b642f889201ae85e8fa01e00e48355053f5a7c6d920dcf6a7ee1d60dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd000000000000000000000000000000000266bf0d9d5a4fc713dc0fcc6ea6edae0b326e22cd97bc49c48a7ba398fc87d7a0c7141ba24d80df454de66c2b5a55fb000000000000000000000000000000000aa8f95c7cd61733b0a260149d6608a73d6c1f989afa8cb2aa4098e1fb5a66b4ad5a5c1c4d901aa79812385fd507f02e000000000000000000000000000000000a6b4929df13e1fe7f0a0cf699a7fbfaa97d7527cc3ea1f728ba59def2e75fcf3490199bd42e93b7d47985a307add07c000000000000000000000000000000001719321981d2085ba31c9fb131d6b79c7df5d10d6ad0b5015454329697860121e781093fdde1f19e897dd6f2c272f87a109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d37000000000000000000000000000000000431002c9926aa7d2b06412f544a868a7d48fb5f077dfd098febeeafc28b876c434daec809e5cbf50ff2395ae7e456560000000000000000000000000000000005a15f713b6eafb09495cfb1c89e9421515a07a99ca0f208883f11c430ffe6f2592dbc41bcee5db36385a26f67cd26bb0000000000000000000000000000000008dd30fdd7767486844967c5da0803b52282178287b8ef28e14f07b487132fea3a82d86d414b4d0a25b3dc538be11b500000000000000000000000000000000002dcee67e2d17b3106dcb9f4117456a037ae1996e8f7a09b179baab1ee8345c6d01eae554d3f40da86bd79a04702fbf76326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a000000000000000000000000000000001629fcc374e99fa8303a715fb5077f266b13367bbc0098b5463d3298c0892f83127d6b7f751446575b88858bc742586c000000000000000000000000000000001100783c10618752d25c235e1e76dc64db94adce05651fb8df0a5ee7c299d35b1319f7009b857892ddf9e90c91f7d23b0000000000000000000000000000000000ab6996e4935131becd5df288dacfad1e69b41e200ca7dc841ecc180a81b9d2ca14fc8a76a4e7bd6f924bb9f473de62000000000000000000000000000000000ae9b22f8dff29e5e0a2ec5b5641f53fb5e1ca03130b49d0c26696ca4b439a9d998d9a364ac9cc5ec52df699318cffeae005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d0000000000000000000000000000000019c557ae1c12ff8a7c00b7c9e4bc3d65c92753549c193311a38a84bccfc090052a2219461a9691affe2d67ea4357cdeb000000000000000000000000000000000cd35c5dd126bd4b90dd671f29953c5a49a14b6b3fe946991416edf235c3eb3d574613d27b05cd879518fa7dda3ed39a000000000000000000000000000000000224392063b0825fd332bbede23588c1912e7670a013a99da5507f650dc4284431698a5b4e8c180269af8bb30e4fc8450000000000000000000000000000000002ab8d3250d4bb8ceecc8ca2003f91420d0ef8a7dbc2361e5e7fbfcb59471a4c525856bf796a2c2608d219d215cf83fe3917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000000911417908c2bfe4f63a388f699b31b47df1ea0ec289ee3f96ffd0c71f3deade00d1841aa56b4bebc2adcd3068adf920000000000000000000000000000000005467c7e58e82089fa285c28ea22c759c7806d86fbdcdcc8e09e847d6330922a61bc331ae3b5acce777b7809ca98213f0000000000000000000000000000000010f376fb47933b1f701dd81cebaebb2d8d8f5510a26fb3e9e156ac5ecf2b943c5fa2812d52da542e6c335abad8ecce3c000000000000000000000000000000000dcbf467432acfa4eb9ba11a7cdf02f9110f44ac371128ff8f1f98fc70e4554f057a4608180bfa54d99fd2da010594f6f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000179aaa7119f6fb986714c03b6db16f25eca7172d24cbdd318bebb633bf08920f9e2a8136c94e3ec7c19e57ab51531b3f0000000000000000000000000000000005937c484213ab5b2ca8ed1c5c90e8d2a2f1bac044b88c04b301ff2fdbe67dc4ea42779d919ad510cabfa2ccd178cd9f00000000000000000000000000000000183cc23fd64514ead63f55d375a07af7cf2a56aca64a887dcc542f8a396468a6abc776170a5d4b4bbcd4dbac285e7ffe000000000000000000000000000000000ce12228dec2f84219904d9ac7923f122a99803a9b34749ca68ba385c178811685c19a492aca2e1123ee82a8a9cb90fc3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb4420000000000000000000000000000000009612bf9130e17110f8b15aa6f3317071daf3433bf6d008c383bd5c2fdc7ca03f25ff4cdb483de3c84c0ef9e579f38c6000000000000000000000000000000000c40172540a7e20eeedfe02c37aabac07165cbf04830f20fa76fe8b05c826e7762c9f7567a0fb972212bf736e627948a000000000000000000000000000000000f49e5b1929ad3ed5c07670c471710baa24e8478a50f72a5b7bbc23a66cff91d30a3d68961fbc2e6e8003d08196f325c0000000000000000000000000000000004ba098f915ba9e934384682648ed8d4e1cbaae60d596655fcd9c05f4b049ba0d278730dba5ce3fd4892531a3153bb955ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e99188000000000000000000000000000000000449b15ecec6d6fe5cd32437b54218f62527157aa6344c635fcec8f8305c8b6e44c93105984e0832536237606f07792e0000000000000000000000000000000011e40e8aaf75f5ff8e4040f725ac27693d7b24805a2539ff54b3a6e90c048875ea9609fb8fb3d8de63ca1118876c172400000000000000000000000000000000006ef2a24445f728b53cbf01e5b076acfa7761a84d8261cf1a1b99cc32f330f32fa5ded83d5cd51cc284207adb2451ee000000000000000000000000000000000977966380e772670447b15ad9917035273eb71a21c37607a761aaec808909fcfed50679769aee1573d73cd241de6624877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000074e475c0ff1a51a24be3c964c45c41f767f890dec82712d92a965be504fee43fcc6c0684b2b17c5b294a3eb7ceff1cb000000000000000000000000000000000597b7dd287f3fb27e35a9e4e1718b6b1a4addf9e95e93aeaa25aa34023669368b794a08fdb178d9bcda2738534d1962000000000000000000000000000000000a492d648393bfa317165ccb552e045fefce5b3444d5ff770f43a08a68efefe7fce1216114ed1495cd00f832538198180000000000000000000000000000000003d85cea8063828ff025ba599bdf1efe0412ed5ce06ad5faa841c6400e4eeb6aea1470d48f4e66fc768d7e7bfebedb37145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f6070000000000000000000000000000000008ecc3dd40da2a7a348b4817d9c84242f2f07c5d0ef810dc08311e9d4090d6d96d68b6c725ee6c24de076c71754bc4b50000000000000000000000000000000018fb3a1dc4e0dd9227fba310236a6db7953f0b716fa995b928a2a8de38edb97eca09fe2ab385037dfdcda2ee577e677900000000000000000000000000000000062fce7fe7810273a80760d9f4b3be9e7c821f38ed3e075210d3aac6aa7a763e3cda56465f88b34540b408ac850742080000000000000000000000000000000006fa94466cc47990a80ae6a310ea765590a0e646b5988925f03cc7e30f04fc0a8044b403212290b2fc46c77e84a9028dde4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88", "Expected": "000000000000000000000000000000000f41bad0a932e28096e51482c646dbdf294aa7b91e0ec258670e7674864765c76989a936fb440bfbf4268a49f450d3230000000000000000000000000000000018282b76521db98f589b1c14e603b6f5d27af357553bca761189a38a944a11c66480f7ddd89d17e4aeddc8d78a2b3a0d00000000000000000000000000000000007efc4a90dd97f1312047ac78a3163dc014c42a44c7054daeefd5b72cd0488832cb6396e02ccff09e4171d790954fcd000000000000000000000000000000000e790fe8323fffc96705a42ca071532d5359641ff7cf8714789c9c578717a054c811cdb581df8b6a43729c6c3e3255ab", "Name": "matter_g2_multiexp_24", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001304e0ce6a4baa6e0545fdb314523fc91f73eee157249b94f284ba7390b12b23b1c849c45a563ac82b62a2c48aec24e1000000000000000000000000000000000a2d0e9e222db70d49d1e85f587d35bdf5e8328aad14343d296f95b152a79c83a4858cafc350a5df1ad0194c49bb929400000000000000000000000000000000199efb09b34d0699eb4bc1c57fef9cc5d98453bf522c504fe7897e22bd0596a3a6c310eb351e15e3f6609b074b240f7d0000000000000000000000000000000016b69f12ce30ad1a65150094e29d4cd82fbce5dc343517ba9e5d89245ec083c44af9a3dad2169f713d3b01fdf70d20642576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e0044839000000000000000000000000000000000b3ce4ac12861052c602e71906a7c9f3e2186bd2b6eaaf222d8e80b48baee537065ce78372ed936e6728b9642ba1fdb9000000000000000000000000000000000e8186561d23515bc58c77769c93db76dc9c62bb715b283cbfb71462451120a6ded736cd8a292a6799fbad7617d9aa84000000000000000000000000000000000368a6dbc7daaab0a786257c813b1a25c97468732c27cc759fb921cbc3c9a37a46d7dd0298771c447d36ef0a10579ff5000000000000000000000000000000001348d5e34cbe54e3a6b357c4e651acb82d2dc40ef9ed8bb89f0cdf0882ec6a737998f4e4dd61e296d101cbaabccdc3e779f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000004ebb53c462239a78bf13f29856ddc4d78645c457a656f3cccec9d3c032ec19c26488f39e0f5bf0d38424f9e3a9bcc870000000000000000000000000000000002fe1949365831f7c38b1cd6cf2e22345c4ce40cd73def77889c214d1077d70e39578e8be4fe5998f59d47cca7917280000000000000000000000000000000001152f2df1512013a42ac056b75802bc35c1883efb345cefda8276c594b061a0b0f4a49d8bafa6fe251658ee76b2493cc00000000000000000000000000000000094f90cb386f7933b2ffcdba5e46e09cbd7d537c12bc223e76d3a88ce9063a7b3574d3306365d65dd4c6505f1dceea53300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f6571500000000000000000000000000000000169d9469c53e55768c9312680ee82ee581727e28cdb1d6fcdca25d0c03f3da2ad6572039f12c90b09cdb843bc506e07200000000000000000000000000000000174528257f6d3542f754ecbe97eeeea7d196ee4dd01852f6cbad87fbeb4dd7d3799588f17aad129a15549bb787468772000000000000000000000000000000000c9ab635bdaca1c488538c0830453ec6ab3b2b62447c03ff6ffd2712bf62e02a63c76c79d41644ea412e733128685c45000000000000000000000000000000000172ef0fda359bab149c8c04f583f4ace4d1b148426e993996d278f79ed2c6d3933d6cc5fb62ec4869aadc773d3084ca0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000017060fa73b58957d12b3996d67b7baa8b7f0943ad52e80e5c4f8830d33dc74c0a39e08594b647945b402299ca861f7b10000000000000000000000000000000001efdc7f783f9977392e2797a3e0bed222d5b661d056aa0c7e04a493bb9b18048bf72aded134941ece78d63df0a0868d0000000000000000000000000000000011355198320af05f2121939e6489f31e9e13b3cbb2cb30c9e675854cb8ec038f80aa2f4b6f995774b36f5f1b6a84298f00000000000000000000000000000000172e18c490d0cd5ba2449362c0ab296212dbe69ac25515d0f91941d300051320f067f946dcaf999554f55f1f616adc0f3685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae82060000000000000000000000000000000005854f4dba62d1dbbf3ae16f70792f1bb39f111309b454a6400d2916e619d4f70764ecfda7eae5c28cf1d178ad53fe6d000000000000000000000000000000000ed0bad1f5d69a0e621d137746a9ecc764931ab89f24ca827e0340ddc03571ed697f63e79cc58b946e8462099ce4b1d70000000000000000000000000000000011de76edd1cc2f9ba06b98593a24a7a011f2701b451ea3ccd04361ddb678e06d91a676e3f11b62c68cfc05242cb8a859000000000000000000000000000000000599726b5f5b93d414f9310383ed9414e4675d644f83ebaa63dceb2bddc7dcfcbc17c7aaaccd0ee32b0875952554b4e660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc25400000000000000000000000000000000031110347cbea2756b5fdd549d6c0b8f4036f5718d41dcd6c854a91c9df29bd464774be479d0efcb8a3f82cc7441a6c8000000000000000000000000000000000e24a52dccfdda3689c87395e45dbd46156676d9eb2cc09dab22ef7ff0acf5ea243ff117c82b147994d65aee8605b2fb000000000000000000000000000000000e0cd6ea0bffc591c13c48bca0782fecf8e128b0b842aecb06f803a223d32cc350db869b7a77f8e31b05f36bddd587ea00000000000000000000000000000000042ff4ab4596d610638ad23eea904a82701cdf61f9e2dc5832a70e11e717711a2d0e72f32f74706d385a9567426b4713addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001517efd853800946aa37868b525e58fb488bb69755ccec806afca2d21bd3a30ba46c39cdf694ad0ca92841760437c3c1000000000000000000000000000000000e5591c339e88544660380d6362f4119c5596f910d4ceb96ccd4c4d9672efc50805b6fedffa0a48d126aae69b241d3640000000000000000000000000000000010ea5babb0de734641f63eed2eba6124377b5c55e429987917c0bd109d7904766a10b0d2dd123413816d0fbabe25050b0000000000000000000000000000000000efc89ee2ffa56193129062ca55a3350bf50e8fc7d586fae3636a70e3577987fb0f8674d383def4b41225e490d3d81528416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae8000000000000000000000000000000000dae4277d62e3f3dfffb80818a5ba5c371a48d73b92d69a168ebab897ae8be206fdf776e9f955136d7f7f7b2903040270000000000000000000000000000000010ca635ee2e49cd6c951d75ffddd11557432726d26564239c611b139329a28812afe21f094c0585675f4f233233743050000000000000000000000000000000012378b2ec31119e508fd9ae0ccc4c2603b6820283284a278fe16864e5a18cf7992d850c1d6ebd1253103c219bd95ca4c0000000000000000000000000000000018cac4f0660240045214034cfd8a7e40bf0aa12f97a23c4e27db0e05bb25f4d755276a91a4e882a0be63437a522943ab78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000002fd5571c818322d207d58fe0a898a045a26c95c2490765dc9ac663a0de78ef5fbd05b20ea96dc5388d5b2ccf13a5e320000000000000000000000000000000006ff29ccb768da45061ba4e01c90459ededa5e79513917401e7e37151095ccd4656aeb9cb7c083cf27b69377295934cc000000000000000000000000000000000414d34eac47430495be735eb5c4b1a68372abeb43658f27613a9c8b78f17d9074174a8deeeebb1f9cda5d6198bdf89d0000000000000000000000000000000010b11bf63b8c39c1370e8fdbfdcd149fea88eaf1c0a94a51bdd061e4c41abc626a448030bf9ba880032e9f1642caabae871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce00000000000000000000000000000000023eaa08a44eebae674434b013ae9992c75690a3d0de53e4b05d1c0dff249feb24a12432bcb5defe25ee4e44a56b27eb000000000000000000000000000000000f146ac27e685cca04afe8fc58fe853825f5b0009e8831eb0d0121decec23b25bf8521da2fab1508a3ad8254865fbee70000000000000000000000000000000004af1a525d3c33e0b1629cbdb90c56a88d70a28037c87db81c59bcbc811c8f0b98aa9dd574436c9f600c0e8e2d194c0400000000000000000000000000000000170efb5e0e69e46a21ec3b972265bc04b9d5ee926254f61c0e18fed013922e00f1897cf69889576bb5d54810486e7f2776ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000111c832a96329d6db203fc8b6bb5b7db01521529c91c74d9cd71dc78d067b36cb7eabf1af80129a7a3f44b719235927400000000000000000000000000000000097339c17816795238629d4ca6c243a14e9e227e9bfc30370dbb9e1475f6d03020dc35559675121792436bacdf9eac4a000000000000000000000000000000000805870a1efd1fc34c9b576b77418ee8c0d36aa9caf9994a051e1d55b49275f34cdb55edc74ffc267c5776c8d0e113ed0000000000000000000000000000000001513afdfc2f000e3b725fcd0428fe72ab2413ff2aa91b44458a5249c9a160ee27bca01d2fc2e230f4a80454769961af95ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c4000000000000000000000000000000000485b3b1f812b4a28ac87d16f86d8d634e85d49d6dc460646e1224de662e906002c44a1a269c3bc011fd22afeb2d58df0000000000000000000000000000000013ba0752444a794cd00c99eceae51e61c382d0abb26e5e0e595d59321447400e8a8f7d97390bd217fb50bc22cef34b2300000000000000000000000000000000184515a36024d0bf71d9fa4cc5165363ff94ee9f8579bca653ebc0620a9d3146fba70a2f4a9f6bd3777101de0d32e327000000000000000000000000000000000e041422088c0343f7704e726d65ccc4216c4a1bde3668108983643663cf0249e992f9acde2dd8ff478dd26cd8d9434d06d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db0000000000000000000000000000000005bbb0c55fdbc59992c83fc0ff03f677e58b6de6f8649141d88963ebfead9383d692015a7b765b727eacb6de250351ad00000000000000000000000000000000183057eca610b8e07fffb60d21bf2eb87981e6e881bba04ceff420ca38228fce2f94d40a993e2aef09e209f3990dd14a000000000000000000000000000000001231bc55242bea6b589cedd1d82621fb71c606ca9306b268379dbf83ddb1420dea228ffc05cd8b67c38206f3f006ef18000000000000000000000000000000000f2c943e7a8b0ee00fc4e4ba912b94f68f504d2783babb90a3781b666b31bd161af2f97a77813eab9ebba76040b04155257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f09800000000000000000000000000000000054bd97b9cc979006f734ec433e215a4e8afe468e69173384bc895e10ead3749d991ff8ff203abff30bf5cc0d2fc8c6c00000000000000000000000000000000066b73a98d5f5ae140a5784c5594892c849aa7f2db3b5798643f755743d401ca745d810fad5f4a33e5b3cf0fd7d96f7b00000000000000000000000000000000007caea93ff5cc6ffc033717220a215ac4ed7283945ae77e62320a0bde13f2153dc8dd401297cd124b4c67a4f3839dfc00000000000000000000000000000000094568035ffff439e3d3201466f3a1d43414e3f6455627c5479c8b7c55130ccaa5007ace7ef6a2b3e2e5a4c9543dad9163d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc30000000000000000000000000000000015eeef8bcbfac04112931e186f6fd48b7a8ea891ab364ce8266c5fd15f072f08fb3655e324795df182a5ed1c917a5db000000000000000000000000000000000028916fcb3b30a7f95321a0998e544f9f4f578be7a9f866cf72d6b8baccd93f8935f105ed26aceebb3f9c96073a8be180000000000000000000000000000000012b11f356a7e32f3d9281a8999363aca0ae5c1a058724cefb51583e5f217257d47ca76d21e54ab62260796b95f9d3ad0000000000000000000000000000000000d83c75c36cc8dea4aab47823edd26b4492da39b93a15fa454aed4175f28a025ad2c576ef2d76a66e666bedae95cef1a7a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c", "Expected": "00000000000000000000000000000000059443f363ef0c65973d36469ac651eec6e52485a07a6d28112f4d0711802d182b7e6fc56d4f1aae51fe1c549247d885000000000000000000000000000000000d22118a6f1cd06ee14c63f0e005076bfb061bb85ed184b5444c08ed9dc35f77217b6daafeac89a973f2c73f00e0d3c800000000000000000000000000000000180430caa9917cbb40e3ada2de8d685b4daa99639669a643b8f5cf9a4a55d6162e9fd7f5d4989a1a6588feb0273669b90000000000000000000000000000000015d01fba1192f0f1acf8fb32fe790f0448c6563cf8ef5505d9378fa2fdd38bd99ba938077f71bb8eaa91a99e787e840b", "Name": "matter_g2_multiexp_25", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012d948b5268524659e29cd407dbbe8f529e608193ab9452f936b2f6fc0b81d3a63a0e929329e2d89b5475dc2d73ebd8a000000000000000000000000000000001219e20a081837f4d4e33bdffda08a946bb9cd876e42e2f561ebfd18ec439e0104b43de61f47b8b7a0c346c33e632be60000000000000000000000000000000000a135c72c45f254cc1c260af803e14cd0f89c2ac3029629a86b05acd3440465aafa4cf84e69551ae772bb55802a90ef00000000000000000000000000000000052750c3a99974f9044531dee9129110b99572cf283b61e6606f1137a87de7344bf01d2ac2f8a1db8d815b6d9e7511fa26a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c00000000000000000000000000000000136b2f21aba94bbc8e5235951b1b186fd4ad221e6ecbea5c7279cc8ee8b01edecedddf48cca47624ee9b155a4c167f140000000000000000000000000000000019852d2bc9c8abc92503f3e7eec9fb20df108c23643ba8a2fe16c2cf085bb4ac079d3f065a1241067daaf401b662288b00000000000000000000000000000000018bf1a4e74ac9507b97a990f3a41cbae3f32e263e9937a8a62679bee93296ee5cd25110833eb5d136425bae0e9dcb8100000000000000000000000000000000096ae4bfaaf4f18d3e987d9f287fdd3dc9b497cc84867e757da52bd5f58688403e1c9cb432a2eb87e239879d52990ab5f1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b60000000000000000000000000000000016ebc2ee18515354b7af5d924c895ffd5556ad088560f89c59a4ceec229279d4075f732b884a6ef2bb2eddc11d27572500000000000000000000000000000000110282084ab6f3e76eeb9e5e8c56749992913c2404b003df9c2d01d72751f879538d23f612c8faabbccff45185f4c6a40000000000000000000000000000000017476677ebf052d13f60ac0ec5e572c398f1a478d60ce92a3de88a74a28688d786d30b1ea8008409e45697db0adc628c000000000000000000000000000000000a5e4239d938bfc7c05f3b3a850ebd5f7784eee7aca48c861eb4bdb1ce6321fc9c6bba997e143aba13a42f69ea14937397fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d670000000000000000000000000000000010c703e31f2d488812a387596c797d8d414e406bd82f238cea50a459d842502e11220ad82fce5dd36635792ff5770bc50000000000000000000000000000000010c11caa640708850e1dddd48bae22961a45029971d823b53030979b7d8ef2eaf2ed055436105697c5b0b31b1a9d0a7a0000000000000000000000000000000006b98568b2b7f0aada97310f7e14084a14bffe580ec65bc8fe5d19c6213c45dc1b8e1da5c6c1b8555729f6c781575278000000000000000000000000000000000f2c506f3e41c28a748656d1dfd87e812b3ba21815637e497a30eca4fc5de18257846f12b67919dd2d739477cf5ed0ae8bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af400000000000000000000000000000000102b6d561172adc9316b3ec11f05e66e7affb1bdc70a364faffa57aa5938c2ac08863be8fe79ce3f627558fcb2ab1230000000000000000000000000000000000c5e72c271a1ee186d443a96d53f0ba0ce226c76aff2a7c3215c2110f96cb3301bd586f509edc45cd20e662756897b78000000000000000000000000000000000d546fbf485bb283a04fa05aa962ae8d77ec4d26f749d83b208f77247778e32a9a2f1483bd84488806e27b13eabf41d30000000000000000000000000000000005a42c6ce8d43d122bbf984e9777f5d1c15057f27e70fef44b97c2c6e7e2e303fcbad643027b7ff3167916f21a723ea98da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000001788323aafb95f8761f87f771fa05a8e49be71e397849daef5877a7f486af13fa651be7a93bdd9465df7be4ff65825fd0000000000000000000000000000000014b7a56f3f7c12e39be76b3872c1ee648f62f9cb6a1842d869e00a5dc2ac8cb4ecd96ec2483d5eade5b0f9113133bb050000000000000000000000000000000009a30623632b757ae8d03ced0c1fdd1877718f8d84f34ebb42426284f73bb7e8abc31a5e5ded57a02d08adaa90abfb2600000000000000000000000000000000020b47acafefce7f617081e22b2bfc566acec6d2cad5063a79cf33e02cc8931bb698b72184a11fab73e0bb0aaec76c61d6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000d1190466f0e8f03d2cac4a5e63a13d7c6d0cac9f2065295e2de818773199d731f8cb7b2be5f6ef0a246401b345a2d560000000000000000000000000000000007d9c5d187494df79c25b6292527b0d6d8c50b6467bf76a1a1316556e48159a3b5dbdbd9fb0bb901d857f61f423d15db0000000000000000000000000000000013e4401fe76e3f1ef73bd244189cdc81fcc152f71449c11aab24c4fa1d123c5aa8c68a2d10fe88c1c6631778dc0bcd420000000000000000000000000000000004ccffb4296883b8690b2f3fe17e4e9ab24390084ac917ed28fa1e04b9758373abd348290d24c915dfcaf0649ddf5a87e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b10000000000000000000000000000000000f4baa5e531ae462b95362292d5366daa89f2fb2707c58568c094c58578e84a8d253fe1de26b917b84635c0aac3a63300000000000000000000000000000000109057e5c5451eb9f85b95aa5ed2615d2faccd0539b1e4481923e04cbdbd2ea9290969022cfa508d3fd050549c74940d0000000000000000000000000000000001c3e147ad9c31927207f2344fedd541316f4010e3de194f924c4a1450a221285b76ff1894f8b1670731007f44965100000000000000000000000000000000000909cdf5c56dc177daa1f3fd7cc31d79a4f6dfcd462c07812cdf629426b75bdaa297b9d7e67aefdbb58175a21e29edada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000009b4c6bd1c460d2e93febfe523c1d54d6bf6af50838e7a10b732c1be8748a0752a517e7103d0ffa4507b086626fbfa8a0000000000000000000000000000000015bf2c13891dfa8dba35b5da1235563d4ee1dac33e89006f5c9fcf06f2fef7b31ca845bcaa8ac608046e8b01c8a61fd2000000000000000000000000000000001898dfd6a0618df821474b90542f261c1febbf2e566978b0fafca44f6dadc57202f88366b19d2c955e4291ac21beab520000000000000000000000000000000019287e1ac6b3eaf412e58511b40d87558e7cbf90dc8af2f5d33825b40fd2f2425d0be3a05d0a49076f4114350dcc601eb929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf0000000000000000000000000000000000c0993401c024d32cecc0d86d4cc52c200e59acb34fee2ae052837f467905e736a1118260ee12a963ca2df6e1a6c9d0a000000000000000000000000000000000103f78f0e7c9a5628a66efa91f150a87e67623ded2560aef278a8caab017fdcf181981952b450c67e3b4d3f362822a80000000000000000000000000000000000df01ff335f23652f1c34480d23c62d705572321c0e7fe92556e033dd3cf5b78a3d554585403a7f3c71744c20d17579000000000000000000000000000000000a0e2c9e2e34e5cb36e96b29231f702abb127a011c7ea3e21d59e5c55f745a02039a68d59ce8e29afac0752d1939106936999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f000000000000000000000000000000000eabff0e6ed9dc358881796441c48e722ea171f26011ab898c5a06758f61a629ae21d5a2595a22dc9855fd2e516b30fe0000000000000000000000000000000002732155a7a2791078dedfedfd3381281554c389bf9b5baa47593153a2acfd22a08557d7a1d49be298e416051b9137dd00000000000000000000000000000000116faa2e2a261e6a3e4de6ad80d75ee05aebae47872e2eed9cd91aafb94a706de673a05f1b86c0b0131cf148a90b2b7900000000000000000000000000000000009a04c09c2a4fce22d237bbe930392dfbbe5c82d480abefbb3be876015e2f5889a0922df6d00d4e94be0e9fb8d2f4a1fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000002a6402848507062e5c5d63b1207a1a41d3b941d21792391f2feff95035f1b4625541770fa5e0f87585cfca670976533000000000000000000000000000000000904095ce640605c957715e378ed733ddf1f94d3beb63543a50c8922ab9f8092755fcc65e2a1ed9232c8cddcb5816371000000000000000000000000000000000ec62b911b08d3e8618880c3784685b2c6cbb07a4aa4e348ab72e4f918152622ddd7748bfcd79f35675cb956d11fcd650000000000000000000000000000000013f651e9104d48a081cef2ae0648816b2b4b5f644a791514e94a8e3dd3001099c27d1f9860337ced1b177b4ad7cd5866c36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000016334abef2a21b9c1926b2086075471bc2d2d2f66b963a41623af91fd2fd50f254c008fa3bad6b53658c2486edcc94aa000000000000000000000000000000001063002a5d17aab2bbb5da49e8bde63a1f3c4dcbc8800f9487f47c6d707109c86d3cf7f9171643418b195e50d7483af4000000000000000000000000000000001213004f31fdd0b0df5d8e3677c4f48624691e2534c02881c6cc6875b9abaee56ed5739c2acd66cb1b10553ba066ef1a000000000000000000000000000000000fb7659081cfcf8beaed9c1daf9e92702977c37a54376597d897082a25f9882f1ae14e7724c0aeb9e002dee708c6b4eb0f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e400000000000000000000000000000000078f06bdfcbc7c0cc491fdc8069314c8a395983f9a2e5c2d1bec360f36e365da377885f897d8d711e33270e3ef9dc4d80000000000000000000000000000000007d43394d5175e020b3a5d768b60ec763d60cb1bb37c0343930fa82e92fb1becde0a178c4565df320824bdadd54ecabb0000000000000000000000000000000012f9fc96355721c35a6f5439065d89cfca5345622b3f38041b41c036b9bc6bcc980498ddc7bcf807e1b97831c099505300000000000000000000000000000000105307b482467b881a59eda1434e31dffdea531603fd3c460aa8d4f58d32668228bfa585bbba2dae7346141af59190e2c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba000000000000000000000000000000001576521fb3be8c3178549969e54bb17b0a3546ac4aacb470e935359e36bea4f43dacc06c151a527f441ab9616e07f7b90000000000000000000000000000000018dff940a21768ee9b9450fee7259663bb29af645bda2acb4d43f4e9d631e0127073f2db04293266e6fd6fd3d005e3f0000000000000000000000000000000000ca6a977016c1ebf52827a5ad52e5efcf7517ccc3ff40df8141f6335fb6c77c3fb8f6b0dcdba2596ded7c3838577e28000000000000000000000000000000000150cc33b55586fac30d316cad6580cee0a070900fe7d540167560b79f4cf9690a5e02cfce9946cf67a95dedc9a7d9aa35398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc", "Expected": "000000000000000000000000000000000adf84ea7681c442731be8db9508a050d4689c42af8f7472491655356a86fd9a0afd91435bdbaee98db3b1d8f26203fe00000000000000000000000000000000090a7dadc0a14df481e44e2005c9ddc6e026ce2afaba7badd18492bd3a338dffc349b4a339f56221eb795314252d53640000000000000000000000000000000007390fbc06077cd167f53a16f347eaf50ce8c9d111afeabf3a672687441b80e66a66ba3fdb28e2ca8282f3ae3dc81be80000000000000000000000000000000001998f98e85285a428a2860c22a00d0b317854da4190dcb4dcd182ac249e13c51f5d5df5db6a0fd61d01232cbcacd5a1", "Name": "matter_g2_multiexp_26", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c868a2cce65692f83eedbfeef6f9823ae9382fa5ed23395ff2444807e72403d4f6ac861ecd3a51db841889fe22a033700000000000000000000000000000000111c9aa53da85a63ce1870b963415f0d5f812e061aa6bff57425038d1b65fff57a78bdb963bf2450001525a93011a28e0000000000000000000000000000000011770810c16367d075c695981dfa69b072b82b034f8ac371f26bb157f9f9d667aa555a5c6baca69d08f421cd569faec2000000000000000000000000000000000df6146b29bc8226dccfc95a325d791b30cba8ff2495434d75622b170a634ec7995c5b4c689c73582ca861dd21d8e1e49f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000000fb30aac6502ecdd3544f1879bf1b3f4c19fb897de6c3a7cbf08f36244aa8e9dea8aaf781f7509d3ece16ca144a601e40000000000000000000000000000000012304be931a1d7440d67740f50b1a281468b412e8b6c54c62b993ec609012c7056fc7e62405c7530e8f5136cacb5926f00000000000000000000000000000000182320f5d9211c08f3ba5d40ccca45cb0060a6d362b4422084617b9d8212e94a9b878294ac176b8f0e959bc124a753310000000000000000000000000000000010be6678910072ed9f932ab01a2d72f7374a2cc82bbd86a6006a495272aa89fd655e6719ab8b3a0643d002021f7b7ebb4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000f1ffed9514ee81e9b3fef4162c8f4980fe0429e57bbc224a9c9976cef7d26ab61ea7b0cd42eda30da97e3f8f5ab5f0600000000000000000000000000000000035b9b349b531d85361a4618a172b510dbc924df671b3fa707b474d0d8b17d30dc8ed208d66be91dcb7632d2f05ce31d00000000000000000000000000000000010030dcf6695d44ad3236032e47f7aa25b9f55869f5207e7ac8641db8c01f5b59627dd3442a1834b8b1fc595e47cdcb000000000000000000000000000000000f91ad5c923572a75d32962567e7b1b0eb84a91d485c968b5aebf8b3a772c2f94e47bc1d5b333fe43574308a78e768ac7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000000ae11c60537bbcfa46a08cbc219122ed66fd0d42f90e68243c32010eb99942554c349c021f0e3635bb50f7ca3d106a3f0000000000000000000000000000000019a61254aaa5b51b4d354f444706ebb0bc3edb87ec2d83e830ffe0282bcaa3278e947d053d6678549a098129bace43da000000000000000000000000000000001100f48a07456f01e16bcc833ae0a2835c964e9b0aa850574dfd8b4a7f06d03059e9b4df8931740ce0621ec7eb31218400000000000000000000000000000000003072392a824c386859735e2d203c9d52c19796ccf8538bda3b1436b2f6815bc86d05287f29fd0bb0569a81a57f0c22a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000392760f98883f9cf6c0f0a324b9a645cbae12b780896f6a3eee918c44a815daed156248d6afb25901521b323f6baa240000000000000000000000000000000006375c6629f30b7a36785269d691772afe1b95d6e1bfaaba9459c31086c2697e4ce77d148fe2ea166cc330373583f4730000000000000000000000000000000000aa8e338df7eac5a7b070a69d3ed1553a0c52fcd894c2bc8d1b8cf6ed38983c6c392a9a045ffe8ff40b39d18e7c87c9000000000000000000000000000000000cbc73b589cba1bd47161282642fe6f51f2b3edcdcad6020bdaef369d3f2c11ea9cafb9a7fdccfb89bbbe13560d42d1d97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d0000000000000000000000000000000000e71692cc2342d1e93e0ce72be69013023d012dd2294249dfd69e1d610e2236ee2cdef22446f1996bd3309825989930700000000000000000000000000000000013a1bbd3237dcbe44e05234f7e41982f4fd951d3741a3e90345418af1c922d35edf776a27bfbeaf7a15658db67164bc000000000000000000000000000000001197a2ee5c2541e19b5368c97abf51fde3dd0b922c3d701d7d84552c9f47b38ca09a8aef8240abfdcb03292ade1ff04c0000000000000000000000000000000010ca3c22ff8a47b1c683a58086ed9d831a5c25b6ce5a1971989974b4760cc9e83a1bc8d819825989751405b242eba379efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000001306f8047ba1a3417e7993bba0dfee9077eabfc275af91d0b882a53199874e0777d8dfd29767186d922d49087fff38b20000000000000000000000000000000005371b760380a6d287e129b329e735413447969eb9048def44f5c5987a64323d2a5c81484c40b20206832b86a4af9c4d000000000000000000000000000000001552eeae620c42d0bc4593d7c8e2c8fb4d6dbfcdde68d57158a7dfe837a1870a73b45a97b02abdea174a475a7061331400000000000000000000000000000000033a6dec61540a5cd5773b76847dc5016b309c5a027639598f51ae5b1067b3f7a02f5ea11b0e1be77a3ac236cba15c929a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d4520000000000000000000000000000000012ad5ff49459fd3a7940a69e2a78919876e9b3a4f0c142499e7b5dbcadb5c2b5d79c5dea972f0f0acdfd10ac53bcdd92000000000000000000000000000000000ec1be9cb379bf1e24bd5429a4a91857bc3ad45095d15bc5537c2ba39407e9f2edc5fbf711ef4287a73ea466d4f53c3800000000000000000000000000000000173605df66aaf51810793db1cf2021de6a7645ae84a5d439ee035b917d037d9f9ff072b5dfe8b9ac69feab60fe2d70bb000000000000000000000000000000000d0bd336825381ae1e18ca37bf6160ae32b653ec9f9dad159006e92c24b661f22b5629ba323e9e06ccc5887a962ec23fe9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000000f5e593c6588add92cac2c9467247fc6d900f20b4d3216c258f88f3334eecaccbf3eacda227e2da46cf520e5102a9cdd000000000000000000000000000000000458177ad6c190222e53e054546413c13216286d414e3509b7dc794dc0704afd26bae93ff630c6157d05d46d805a04470000000000000000000000000000000015df8a7720d389e6112707e37694afac2f97282676a89964deabefddbb3a0f1cbc885d4c875b945b8303c1ed2c0f46b8000000000000000000000000000000000e3c7f1af7cf5923dccfc1d25bd86088706a3a44f5fa7f97171228e8f2a2b18e9631b2a63bd5a75ee0bb83fcc91a45c30a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d40000000000000000000000000000000017823fc8a56e6e5cb9924037ad6ad1b43237894a877572dfe3d3cdc1120fe83e01de112b55f7f334dcb5c6247c210613000000000000000000000000000000000daa01f90cd14d82d4fc40b60b463089fc6c0e567fa46bae69184d0e3cc5acdb1d759e3291e2781fe0b65c734ddde28700000000000000000000000000000000164e742b123c19e52e2d7a6727689181f323990a3f3238072f7cfd7fc0f55b7be4274c0df194d85060a81f3744d3978b0000000000000000000000000000000007c03a1678b6e91c1bfc66ce8fd419cea13c7cda3213856ad21823b06db94538153a15d43a9d4270edf77b9a5ed490e6cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb000000000000000000000000000000001865bc91e645e2e24c3efa3afab8b0e278dcf16b29831f75b3eef0b342479e997b9c5f8ccf67c789c830609b3cc425400000000000000000000000000000000018dda7857f919a6a49f6bb465c27342c8fab6afe6350c43b98e91a3105276f3ac27268454e9a9c6dafeb2218ddc7d3cc000000000000000000000000000000000b098258ff8b185a5c59b46150954d52db5a5f68bc7975234491406131e4f1286ce79156dd1290aafe688f936ad34e31000000000000000000000000000000000b294e9ce904fb9e243d0790147b6070b10ff611a06e3f639aacb744154d02016ac08f6769732d4f6944ce9257680d49e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac5768000000000000000000000000000000000e541a22a7a36adc06e445f42497596e1017a1d99de85bb945a195cb3cf0c14d39eb7a2aa994cf234eed77f6307cf6410000000000000000000000000000000002de753e41a16565e5ab1b61debdad54950e9930e04badc6e356f10711d7688befc6827040356c0f0a8ce4f8d7121b3a000000000000000000000000000000000f2202e34ca164f1a6c0afbe179b714b303d87ef14534fe3f4230180f709dc63af17f04487264b3dee6b24ec4d0a423f00000000000000000000000000000000004044d9e3b3a77d6a309780c870a65e05e1ac531c5420f6ed0056f5e728e2b83a968ca90d579db50c2dd395f7e40beaf26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac5794600000000000000000000000000000000166d7692fd30dcd06b9f01ba2101870ed347840509b3242f7cecf91fbed91abc24b08b08cc39c508e6499a2f8bc3637700000000000000000000000000000000076ce6dcbc77812b4d5b44a50edba5a082cc36dc24a5cc348283a4ce1518198b56134c9807ef850edc9e36e9a282b9ff000000000000000000000000000000001261d9412245abd7ba3fc1597f34179e54766c49306725d42588545e14f4e450ee1c7af913ad7225275c57680c23aa6300000000000000000000000000000000096602b4eee053998555ce522c060d5e04c7961eeaab0145d38c9b13362624f54fcc8d0b77f2bbaf8c312a3279f06e4eccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f940000000000000000000000000000000012be34c18145aac51a1494f4052edbeff14c2812ff494cb78198cd7d9db9e951aea80490c55c4ed926f6a96a2c337c880000000000000000000000000000000000536e46a63ec5ac0f2f4eaaad6df98322c6a981cf2fc8ef253269cef20a76ba1ad089c24cba4ad4680dc4192d66595d0000000000000000000000000000000005363b9acb66ee95713b63dad076529805c0dd8921c738e205e7b1d0410a3ecca0870aeb2e64cf45270d49b473371ddd0000000000000000000000000000000016749b2b09d889b883b6fdaf518345d4cf097a728b833e92c4d21b5c41c8d5cfc0758e895b60ad101a74bbb6be6ca0c5fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb34000000000000000000000000000000000067a684b55fdeea39a29252b355700a4810f083909cf2c07a80b362ac1b4d58f5900c68d266f7ad81ea278c0931bc1ec0000000000000000000000000000000001b1f78d194d77cfb4a2116ce9e29438dbf38c52733b0295198159d7cadb2584d86a75c24aedeb36234a0becf9d38a870000000000000000000000000000000011fced2244cd959872a25c0c7bb4af6151d99e1aac079c606db4987b9ba111261d4a16e7d82362b865324824445a946f0000000000000000000000000000000002659e7016ad615ed80ea1ae020903431b470bc0341f8e0918de9b8d2e933dd9f2d9123e9e9d20bfb05d49f71c3c454cd94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000008f3c5de8c94a98dc5ad7846c53980384f997d1657f7349ad9b51376d41f4b21861d212fb6428bcf2347d8774f44156d00000000000000000000000000000000110b245b1e788da41dcbf60a3ac4987c1925696dfca85d450107f654fa1230adb9436d60c9e742dfb4e453ec4944c56c0000000000000000000000000000000011043b975e01df36a36307ba9234a18b97aadb9da509513b13e4f3c80432b0cc5e69a3bbb3cbab8df41bbcc92cdbf60200000000000000000000000000000000120aebda10c52a67d23842e2bd9a897cf38c58fcd11e4e8c5614db5e409a7c03111feebfe2f1212ae753497dc59d6ae9122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2", "Expected": "00000000000000000000000000000000021067690e6e001e3d0d01449a7257348c4ef68e66dd47b9014d7687d44749be1f33e6be95df6a204169ab7103dc2d3c00000000000000000000000000000000062efa0c36462ab0734128dab5da8635705bd1e1b540817c5805ed9417f176723eea92425db539e8763b1c79b9923e9700000000000000000000000000000000176c9af1970f026bcfa87e6f85a20ed498c28c6982e21bc050cdc29c0f0af832ed4424082e4862d985b78519cfa75b820000000000000000000000000000000018718b0d0fbdf4783cd0b01524ab153b891fbf08cad60241a3f3163d2c3496c36afdc6de62ab3c9a037f88ee408ce5f6", "Name": "matter_g2_multiexp_27", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018adf92d8da050c76118a3a3b2ee43955ae8b14ddc8ed64f5672f40de475f7e0ba6ff60c4b6ca3e863d7914e6de2cc330000000000000000000000000000000013d1e19011a1ea90389480d14fa608985d895e05edd9c28fb34646f70fd7bdb7857fa785b1e3c8a2997da6c3b5337ccf0000000000000000000000000000000015764827d9838c2b011660230ef9805af388fd997cc229c939bc5f4213d517dd837328c45b0b8ee1d6508cb70629b7bb000000000000000000000000000000000d58fa30a2d095ee8d946e50a027ac4cfdd557b3fd9c82dbf1536ddc0f42491a176ecbdb026306e6ebf1bb182a4e8199b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb000000000000000000000000000000000cacfc8d0bc6f9db737c8a316043a6b52fd5946937467afc09ddd14e509a89f2445065ac8a8c56454d529d67793edb0400000000000000000000000000000000148b1b941f159d93170fed949d5f53bdd2603d78a49443ac0e2353130ff914376e018c3db3d12b807d105f2d50eded8c000000000000000000000000000000001382a3e98cfd072807214479900a8602bd666cac7f19be0443ba1354bfc05666f40384e9ccac314b5d0a2bec1c90ef0c000000000000000000000000000000000c12c2222f67a5adba78f2c0be5be95ed743e835857f4204cf47b67fa2eac45cd5985fd82c7a3904944e7b84737374b17e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d31000000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000137b12f731ec925dc51e20a9c90323d14e1497e16b3a4b5651135054ef0e58e9b18167da15220b9a4f7d81e9a7648fc20000000000000000000000000000000000b2d3ac534e1e5b2c9ff4092c2d8dc5efd99121de7df953e5426eb33934ef07e41b196eca50f5a04a936881a05f2b2a0000000000000000000000000000000004feae2377d950717695606844a4873ed7b5f6703d7a63dc8b960b99b68efbba710c2db0f1371acbee314875b97ca054000000000000000000000000000000000f49ce3061e7254dc1bc8af3636a05e098cb96d81fb31e25da97c6266adf3c41a74d46ff32f4fbdb4cb7e4a3f69e827bb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a00000000000000000000000000000000064ae3fd67250f2c6332e1e149ec09946147e12e0d871403e559b49aab68190a1454b3ae924727b6dcf6e1ab327c9d7c000000000000000000000000000000001131f91c7a0e1854bba3958b36083c27904cfbdb8b8cb3fe68cf578bd1cb6f7c6eff91d98e4b99086926c5d4272cc1f200000000000000000000000000000000071c6a92a8d460ff72d172c204c8a69d6b6752b8c1f731ec63f7f394c0c3a2a1bc15e865172f693f523c11cd4ab1f88e000000000000000000000000000000001193876df7f4a1cc9b337a41c9faebac2f209b9070bd75294c2a88d3091a1e55b51fad482fd2aee8f90458eeb7e981fb8902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf2062800000000000000000000000000000000025c20ed5572dd1c9a098f241d2965d8739878ddc57c017632afcf6e54964894adbd6d30f62f316c9c3ec7a08268afc70000000000000000000000000000000013bc3e930f4fd5766db8f04e1ebfaab2b67f620119c39d687c68619b3564f3e8b74666c9f8bed6c1f080a9e23e9c0f22000000000000000000000000000000000973a3cf19312f90843f1f013b05484064032557807ca67b2ded4a27fdac12d6cd0e1416c8998cc8635ce10046adfbb900000000000000000000000000000000108903617c78fc608eaf007aa13861c970557f2693b24e8a278920897be9694570ae6e6c7749c3eab84d5fa3af5164b1426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000005695975c140fa14998e5916268bde2135cda80a45414fa85193fd6e13c6b5a6486898f590d76175d8ec2629c923e33600000000000000000000000000000000033f58b1cf67e51e9ad817b31919530cfdb5db5ca4a537d9b006b63399da49b2a5077bf5c3b3b4fb10b2478f466542540000000000000000000000000000000015c532e40ec04d9143e308895b2e7e3d3daee093a5840e1e76ab528fcfa5be57d9796ffd58ad5ab7df6f88aaf34706f2000000000000000000000000000000000b55747d1e8b66e2b2fea67229f2b7b17d58ef547ca841bea8db5b53fafaa18390f11b8170c41a5dd29331917fa2e348de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad00000000000000000000000000000000001ee5ebf73bb40a5c0822350853bb5aeead3262380dc274faba6b04e58e7fb9d5a4ace109ffa5011e73e3d89ee6fd77000000000000000000000000000000001427659e5ab1f8b47edddd27c613b578890d4c66c835c0cf8e8daf19d0ae842f0bba5bc83ed7248adcd75cea5d222a270000000000000000000000000000000001d4560185690ac05e56c2d629d599bceee3ed2919c29e3d1ac54e80ae99b5eb2f93bab865e8c1eef7206f96b2bf4eb20000000000000000000000000000000016ecd3589e3703e5b0ef53790130d5074d2bc0fd5839d9c6ff905746a77e393f73edf53b98b99d9c87a1fee1086aa8657431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb600000000000000000000000000000000163850016261f34de2b831a0a8dd3f224adaa3cc279cdb40e0ae976bbf736dec26c55a6c79cb1c623870b62ea216274b000000000000000000000000000000000a79af5c054cd08608d4be1705058ef7b4ec38a8727560d960f0325d0ef915c049a89e76956d0296bcb6c96333c3470c000000000000000000000000000000000ca89379e558c7308edd25bf06dc05db857204e9351299ab66bf050c8f051341a6c15a02864c679f07373038de3fe87c000000000000000000000000000000001929f42ee5d9dbfd1f6656f61e6243ebf0eb491762b7f3608db3f3e9abf565ab1524f770cd2ade334885d7479342c92c6745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e00000000000000000000000000000000193b6cf7300e47ecd21a05a71b13a8de45418d3f67931789ce6111b8633b9f44063ca13ba8c8a598ee0725caaa3f277a0000000000000000000000000000000016884d982e2ec0fa7e59fb34ae8708d0bf4abfc260837ef4432e8e04474e504b85450db8af8e6809413c90268801fb3b000000000000000000000000000000000fb48a8331f278845979beb8cd21060355566af215ba44029455a03d0c016daf0f6b7c5773d1a99e893e76b4411a53c70000000000000000000000000000000007056e30143058eaea89a3065e1de768d49860b170d4c364a28d38475f90711fba62c1787adda90dd2d347da72680f4eed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000501fa9af88e28d4f0c0590a2624239bf1724ac7174b0f1d5fd7527cff1de9971d6aaf28ba4005e88e181daffee6b20f0000000000000000000000000000000007af5e30b5aa9ad206645ace12cb2b36cc1c6068e604184ca8bfaac5a4ca327f7c43a74d43417918da7df84e3bffd282000000000000000000000000000000000bfc0538d52f277d54749ed0b69697b4c60ef0c5483d21dda76533e15efedc9e2b2ef07618457d64bae8ef922c0b41f600000000000000000000000000000000048935cd352e999bffa613e3be0a9f9a063d5b5eb46cb5056e41ba214e87f871f216ff41ee297aaaf2994a7b6433f58d81633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000016ab1e8b6f41891e0b65f14397c0887b27ff27e7463333e0938a7a1a181dec603056afbefdb23b41bbfb2c05807289b8000000000000000000000000000000000980d0ea9ad5c87bbe1aefb708061f85faae1e1e3b01c55bd577631e5bea2b5ffaf5e2478f5a8df89447fb8a73559729000000000000000000000000000000000784d0c5fa243bf0125cb2c83a4040715197e99d507d71a3bd9ca396074cfda652c1ad0dd95c3cfae369e68d3431ee7c000000000000000000000000000000000e533bb33e6d269dfdeedf7d17c3e0c19f694d151e8eef801c326cbcbc463a42558f58cbc330bdff0d8d91e2974eb4cfc6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000050f005b00f371a7308b5d7d7f67f7c00bf15acc518942607f32686feab5eb503391f964eb7ca711aa6c7b4e494d7eba000000000000000000000000000000000e2ee5092170ea3da0b1397023b2386c65ec8b090484353f2e5d64694aaeb8d5410ae22c92662fcfa21566d70173ef36000000000000000000000000000000001549723160fc7b8f5ef9a84bd1803f18b76698aa7a663d9c107c9ff6c6d02894edc80fd00d436f3a942c05593c5464ad000000000000000000000000000000001032f49e3527cc1f1355c65edb21220c6afc88919ff67ba99c65645cd3b8ca6662dd0146f6a90d92558b3f54815a361d6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000170b317e49f1304570a3a3e6bef78fcf8537a451ebcfef5afe3eac4aa1aa87dbf95d0f870fd3372d37efc9e663621cf7000000000000000000000000000000000269ae0677d71b2537078e96d2593482e4d41b6d1d2cbec755f307735faaf79c01fa27f1103cdfae1a9bdcb665f592c9000000000000000000000000000000000b115d5a9fb9fd9361d0573a8d68c5193f02edc1cf3fecf004c6603f118f28ff394220f6a9e1051a5d9d4b417290b7f800000000000000000000000000000000107b45614b18c2513f8c42a0032cf0f3f300157b39d2969ef7b126f17a9b5e8e9ecc5a61a2ed4db92134b0797f6a0ea35906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000003de39b056f8f0248b138437db1536b7bfee29af00c37fcd14c25c88f0f051eaa07c763d94c8ce497696311736c0b7140000000000000000000000000000000002b52981e828f8dc1cd371e6821d001e1f96d57a865a3c0a255298c43d52741b18fc60903d1a5ef6227061dcb243096c0000000000000000000000000000000016b5335f0f9516f52f2ed45fe723ded427206ba96af0879958f1f22795485b2867e953de3d9b3a9eed2c37f26838e1540000000000000000000000000000000004c860058c7ea2e6e4eb2a65c1dfc20b3070f89ff58ab99bb51a4eb9e7f0642f7b32d1d9f27c668a36a9e053a8d585f394ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000019cbbc125ca1b89330c21ef5b42fe0dc1e795271ce4a9ecabff04eec9029f756f180520f0e7b84be2e9fa4af395536ab000000000000000000000000000000001630cf0c4f3282689a3e01b5c8f9be3803f60238bbe9fecbb0d9e8e49f4ec9f6123c44840acb8cf55f8f6bd15579e6830000000000000000000000000000000012afb848bc0ade8f0c25c6c342bb651a7481be065a48944bbedbc14c095af8a4a048fd1e776126e2128f904afbcb17ff000000000000000000000000000000000dbc984f9ff907ce5553bb11a458deaaee0efea49d6816ed7abf1dee7b70cb18cc669d4808e75678bb898359c7ebedbe6feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438", "Expected": "000000000000000000000000000000000be6dee62b8c85e36a216d16c5477a7c58f03b992277af83d9b53b3b2169414b72bcb4a97e3667482e888738ff17c94900000000000000000000000000000000067337c69c37ef6f0ae59fddb84c46a2afe7fe047ddb57b3b80437609f1a21fa5a73420fa5b44704ca1cac6c7a99d9320000000000000000000000000000000017fe6f37d2410159e533374ff3812714dcd07610d75a53a5d502cf2f51e750c48858db1e109f6aaf724292c1402382f1000000000000000000000000000000000b8ecfe1f5f5d95777b0fe5d94fe81b82656e6e5a62b7591788baccd251d93e4bbc6857cc87cfe6b4ed470c33631ae22", "Name": "matter_g2_multiexp_28", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000126d4a9ae3550e31185aac9011e3f086517cf79a279326c264f51bee6615dbcc730d78055489b5602e91b08f96d23882000000000000000000000000000000000aeff5fc04fd06c26af8b048fb2d0d493525ba5c2bde30664e7371812d529ec7dbd584c056b05fe02179b7eefbbc45fe0000000000000000000000000000000017c6538d2801947cbb646d4ec8b70b1e24453f7a984db7ba73e3a5dcf595bdbad9703f2d846ab02491e5e3a5bcee0762000000000000000000000000000000000badf551dbedcefbe7c303a5c8a52151b5460caa22004028893af4d8a3fac30cb1da1e986f9124acd5db7a634657dbd0cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000bed71c7d878e7ecccd8233e3e604e564cba0b1ce75f726f846f3a6e2f3b4f5b12a28b8638be647f5c33226edc2bc7fe000000000000000000000000000000001914c20aabaf1f6f82063223053809622ad82a3a54668bd600db1aafba22aeee5c8a07584e263c91cb0fc5fb809da63d00000000000000000000000000000000056d9cd8f79a90d16b36bde77e546f8b3064ba7dd0fde78d6bc538bd6ce12a4f32860205d5d396bab3d70deaaaccf9450000000000000000000000000000000012f7e420708b66132157a80753678de292998cb6c4f00244d3c47a6077b3401132b73c7f52369aa2a6a90892f7be4ed913a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b000000000000000000000000000000000339aa1471eddee8cc0a4e4db5a29c3e4e92cfbabe023995a79624614aca522cd459dfacc0cab346b1cedac347e1df100000000000000000000000000000000016cc4ee8cb72fe09e65616fbe9bea1a0077114ca841ae335f1f9eb5a0b129a4bdc77cc6dae8727d74fe21f0d870a43f2000000000000000000000000000000000098a21da6e983228ebbed0ec3704c9d2521e935506c0567e3bbf9b9c379ce6d33c3d0dd8f5e013b431f740964db634b000000000000000000000000000000000a7a38abe8e282544ec6c8740dce8559fd264393d0a5c9af9813b2430bdb92b3150eacb6732b9cc278d0d0e622b263ecace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b7740000000000000000000000000000000019ed305bfe8d8bfcc20794832b3c117715b6a658c0bfeb629e5989f265cbb456e857e53d168932589e4ed2806db7c4b4000000000000000000000000000000000e2ffda25fc316a38f556b35a7a3acb1a2bfbc1f9469a1b6427ed1f216e113a379932b0547f5370be1017a1fa0266cfa000000000000000000000000000000000ebc493c9a79b8ba58f48b90b9d287c74f505dcb484eabda79ada987d63a4df04d671d4c4ae4b32f8ad5db6a1b80f37f0000000000000000000000000000000019fc715d26c0c7a0c291ad8319e2e8f2920c63b4d4ed3f0e2f376aeddd4f7bd9269175ac8d0f421b001e2e48634f3f238d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f536000000000000000000000000000000001285ff533da833a3daae7d815b1b86feb6f20b7592af8b0eb76240f390ea48b69a75547b040e7282b71779f450d3510c000000000000000000000000000000000813d38fa21c1f3c87b9c97ac03e6aeb8fa23e0340a0dff4e3892c774595648743d0b8980a7bd21648ce9b16a245ac3400000000000000000000000000000000020a69dbfb736c64e4cbc800aa415729b24ec05e901f2c7ba38e49a21c3851dc03bd4f7ec829d4326fe6c13867069a07000000000000000000000000000000000d518f3944053c8f74c0aea1d054d89106312880de4479b3dfb45b00945ff8bb58b12f9a489fa9fcd87194a71475d0a1abeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000018c82052cd483eee7aaa421c2b998ab0b4b32326dadba03c1d923726697d3940b40d5109ba34de09439e833ebc19daca000000000000000000000000000000000e4feddc3eeb3fd1eff8316d5b0cba554714713e8a605a55909889970ea2c8c58bb6c568024709def73b29a5a76563c100000000000000000000000000000000098da4cd0281a16e2e3e542ebb92269c8208a3d373394b0af92dc8a2676f9f0b6e85fda9161e32558e0569cfc7b1f3df000000000000000000000000000000000b7b54b51821fc037f02167d2e640f8dbfd1472407278b4bdf47b958da39f28c64569c3199846c293bf60e86aa45f205adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000003058abd4e3d49c86ffac9c95b1f07b66a22c42654dc4a2e3b07b87c22024a8bb0ee084a558ac22cc9fa286861fd77ff000000000000000000000000000000000fc9a89ee26c323df22add487a6bb278ca3f4c9a91eba4e067d5abc9dd3afededb4f98263e10083cc7ea224f28d3bbe100000000000000000000000000000000058eb015f1e14da860215d59165e12feb8d1317f652eeb76b3f08b38ed943c94e632dbf8145233dc93755e44e027553e0000000000000000000000000000000010897d5c2b481f9937d830b333e7649931e801a6bbffb7d9a3ee28ab1e27889691a9f0b9616a8437c3cda942bf07282206e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f0000000000000000000000000000000013b88963296d8c8197cafe160846ee11365b7a991b35cf5613dc57714aa48307f4dd9c6ff9704b29905c18a41a48010e0000000000000000000000000000000016a97fff65fca5ff282a818deb8100104308b8d9dfacddcae32fc2b6082331b44fa70580018930fe1ab9d9c1b13a59a20000000000000000000000000000000019cd2038acd84c2db1f0fa1b7eccc5f7ae3da803cb72c4a1e8390d49e0adff1d88a85696d9daaebce9c6b8a2f861fb36000000000000000000000000000000001271338587f06847770c72dfb3d9a657d05f8c7a012bec77a7d40a98cb1637ae99281c82668486119608b01feb25e6dab3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c600000000000000000000000000000000173ed7c70f4683102cc6a276d192a8f3b189197d5ea5dc813c7d0162a1649e906f76a1c9a1cb1ace6e4d937934b72338000000000000000000000000000000000936d260b789b1a2a9d04388caab364049395be61d320aef66ce50f052eb462faaa2017731518675bb0e4a2f050e4f7900000000000000000000000000000000070bd1254cf4b209ecb40afe248f2e53c390636625460439952ca2977be021d93fbec264c31ced2a810e8a5e54d750230000000000000000000000000000000016ddc3312f8ed359792bd213d086a0ff1540e3e5a2dedf6c450fb96a9b6d1edff9bde31fbc04de382cf44694a631178229b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000000aa4bc6e1a3e6c3c45a29db74b27af27b61856e2cf385ce0e5094ad53db4d31c4af45b5b234c66a21bf15018c13ece8000000000000000000000000000000000188affc993bf6c99103029c1e406bb1a693e4f1dc650907809ba3de1471d41095dc1866578962c72538ca85d09fcd22d000000000000000000000000000000000e487a7151916694b980e62b64ba49ffc54aaccfa0b0fbc5c14fa4a50d1bfda55698df5cd8570c07030f145c49a4ba9000000000000000000000000000000000084a05dced107d29a0fd4cf817ab67017ca33018d5c7302167d08c64c45c5c455fb5c907f21c39b8a86d037a126df4e76b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e000000000000000000000000000000000ea7060a07dacd84287007a05b494bf19a03e5a759b0ba67624c54cac3562c0ca3fa6e444206614d00d6d6684b86bcb5000000000000000000000000000000000eb2f332f4481276f931d2192c1a9f6d7585e85f248a8ac95aed398cb61bda05230bf8b9c041c6f78be3b34668a9c1a0000000000000000000000000000000000faa038219f844e379d8cce55cb8f0fe2b55548a0a0e1e37e25ba4f432e6b1a6451b8f081c171490bf055f81cbfe5f8600000000000000000000000000000000037c70d4e8befff257c4bc98a4726a961f3e2e68e7e02f9f2c94aa8f5fc67a1da44d41394dfe376a6c04240e4cd5825f3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f789700000000000000000000000000000000100a24d21c0ddb20d76b6d9fe642da5ac1de28afd642ab5c08574206b8b64d1fd822d295476bbdf2ca7e9267138034dd0000000000000000000000000000000000aa7e4f2f77acfe8b4c8f3fabd56b17415ee9bb182bca1db15c399479ec60382f980067b9d4c4ef7556d621259ae9110000000000000000000000000000000012f7a7f91a988fa661c661013736f0ec92b40f571ac15a47067bb847b09ba128d1dcaf8049b941a51cacece5db4e1eb40000000000000000000000000000000007528b0ea66b6ab8d5d318f5e4d1c0e9a4f504057dbb0397b614a1adb160032127f2ac35a1a98da70f023cd343a35ffd47944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd0000000000000000000000000000000015d72b8d4e71cc092c2875de80f3d12e003804d980a4b1dd13cff34e9336397c4533b6ae3a03beb2f09312a605947a270000000000000000000000000000000005976027a98f7b0caf4cc7d0d71440d3e4fffb1ff65fbf32dc890b275b646f2a32600a6215d6b2f999eaec8e58cb6d5c00000000000000000000000000000000111583b7734be53a7d4d090486070cd3d9622156c52871ec79c83ca024880684eada56a36b58cfc3490e65de41e10579000000000000000000000000000000000fb670b553c2ed4c81962b149efd4b0c77edf6ee70eba88300cf264dda98190e550540fb9fb95748599bca3abadd752030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac0000000000000000000000000000000003900e7cc0a8e891dc4dfc45f08d97e73ccbe2021a560a92c493aacd9c0614ad100294b5d7ebd634ffe4e5ea301a26170000000000000000000000000000000011ccc136127189728a7036e85d233fd150d5483963c48074f9d8ff83a0791c950da380e717f2bd0bff8fc115e9e886290000000000000000000000000000000007d3e76bd1f22679d228b4ee50a60cf1bd1fdaa171372cfa34bf4136a091abf7e5ef3c6b3446fd41d5de68b563fc7ff3000000000000000000000000000000001107f636d9187155357bea75c943dafcfba2394a9300054026b46d6f9db31eacc06d1f64c2b139af297dc4783026d98f4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd9786000000000000000000000000000000000e3a4101f6af3cf0d5d5aa5a0ebc26852dc69f91c06e96c5f1c7f8e4528c3dd92cb6f629620136ec356f0657fd9ebc6a0000000000000000000000000000000008d34dc3e1fa8bc22258e23b504d442a11938370325c101f1cfa52f313724e0894be722646195fd078c1a49720cde8c900000000000000000000000000000000163730996c79787e7ab89030de2c26e26188187762fa128ba4378a398ebd906dc56d99cf228591f394396248665c196600000000000000000000000000000000008f0a8b3d003b6727834228798950fb7a3cb6b931bced4540693445a007b474f7459ede17f87158e932e4c9c094ab904d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e000000000000000000000000000000000cc2d30f7d3869abfc34719f40b0ddaf00f52bcee7ec09a16de51785d55531fa7fe3ca1544d7103b9caf7105d60d9e930000000000000000000000000000000002ebd8af0bd3f82dc9dca585feaa83071534b2bc2b3d2aadbe0d01d759ade77ecec3b3f7b72f82087365a14dc205add80000000000000000000000000000000011aa3734a4b9168d3c46944cd726bcb203b94b25a97437a6aaace9c84da708bb073ee10585f28bc41e0601567863c193000000000000000000000000000000000ceb4ae5a8b506d31e77e2a43f3af8ba9459b887a927ca5287edbc2ba7c7cbba85a6e4d35c099b7ec7bf7eb2814cc38ae140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b", "Expected": "00000000000000000000000000000000192eb406b52075513584ae3c6093fb534270d716c79961d0a3c4bbc44096a2e8d28228363e2c8da54857945f1b983569000000000000000000000000000000000ee0d95748b13b531821ddd71a15fc529a2ce2c99a66f14e28f97478c3c2d524cb7c4cd7e71a1027030765554b8f50f7000000000000000000000000000000000610ab3e064532ce261aa2ba4f78721ac4f78661cc13fa09ccc279267e6f703f1bda17265a5eccb0061ce24d31e000ec000000000000000000000000000000001966a334b16e64e4dbd66119af97bd2b8d6afec0eb1b8207f437c00ab134ff369b3b3c1bf51b871a7fe8ad1ce93dca4e", "Name": "matter_g2_multiexp_29", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004c22bd94b82ed3b106532a58a0253daf51f579b9d746c624bbc6b58603942eb139c1b576241ca8fab5bf1c457112bd80000000000000000000000000000000010c6f7551d758d1128add57b110227296e060074e4cb934132368f079a794770ff406fc7717867df0f461f5c9fe56960000000000000000000000000000000000048f88afaf6eee5039b76c0c5b4b49671f6fd04f38bdee1b1c8f347a9dd4e6aef387b742c8f9a8aa387ab4d01fe4267000000000000000000000000000000000e7be987d0411dd7138e47ac00f9f07c4737d93aac501edd16362ea5a633c9071a6bf542d4db540d75edecdedc3a8f0ca57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b15300000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae8fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000003e33b840426a6bbe15b23fceba829bda9a5ab89d37e60133874f61bf1b10e05d460bb5d228cb178cfae2a5f41035d32000000000000000000000000000000000a9c5460c6443364d9f9440d101d92a0037343789ca0aab6dffcc2bf81e1aed312299a21556d16e55b1398334d9061f00000000000000000000000000000000015db251708253f7de13a5eeae5aa76fec415ecee1ffd88d882580da5da8d9f96c6ff90d920b329096a103dd71e7cfa580000000000000000000000000000000014c3a004cb6ab8465e05d965dc720b37084d98de424b160062f225dd0b67a8e62ae11a3c7bacaa129a568f3a243357ebb061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d61000000000000000000000000000000000c8fecac8bee21d916cc47b96a66b7a522ef4fea76fcc86ec490ff44b46fc01ac0446e3885e36ae7ab62a409ccffcca60000000000000000000000000000000011676ccef54bb27ab7db0b5ec025a9d1f29217030f3686e71564fa011d9fb598f44a8bed3da8fa7fcd10d01e3f66d86500000000000000000000000000000000093aecb91956215980854c6f19120777983a160e16026560c8076bdc4372f53065f9fee0f5830ea192aa5637590a745100000000000000000000000000000000035d773ef15d8d99b600a6a575eefd661aacb49d6540639223a454594570d0f00ba37340b63a2c8a0d4e53ee7dc2dd91355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a0000000000000000000000000000000007b2891e9cea2a464742c7f962deb1566c9d4f9e4e7cbee1912a72c5b064211c39801bf42bd888bc239e6b4ba71d700300000000000000000000000000000000169cf5e706dff2945145d5ac14bd5fc8f7e7c3e5f7ce733c865e1882d236926c71853efbea26e13efe4eb0d0e7ed5db6000000000000000000000000000000000de9ee19c4bc2fac36debd4c91317e54f57e761866b134ba9a0e84a8d268b11674110ee8f91aa8a6b80eabee2e5e75ae0000000000000000000000000000000016d91408a670e4ee43ab8e21cc341596709113950d22bdf5073cd90f520667699e94f64f76290f1bebfecfd80a9e051430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a7720000000000000000000000000000000001982744a15e8163a6f2ee681bf27a68996682216037d67d91993fbbe040e16ea21a9cb600fc6a40e7289185393544c3f000000000000000000000000000000001131d7dd5a5b96ac1f4c4aa210afe7af8d371cc16d32289aad38c93afcc1d3be53716f82e9d14ce6b1c833f7f5871ad00000000000000000000000000000000009adedaf19fb8823ec55b803c9509ad98217730bfc6424c8b69a071e99d026492e7c8c4a06509491a3bbe5893988c357000000000000000000000000000000000cc60733a783c7df76541daddef2245e6d2b694b94649b13c21aaffdce124c1cec3fd8ed5a5d4d4eff3115ac933e5df989a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000b366a80247a8e3797f1c711aebd60c99ec7caffda34514a3716154e900f2387c46f87f81af036a383e3f9234bd1b50e0000000000000000000000000000000004608b7cea13d08724a2cac691e61255ea7472537f7ff59894d511af7fd99ad72f0a7406271576300a7d1d56aea17bdb00000000000000000000000000000000141abedc914d3d1ed587162acbfddde60f7dbc1ee5e07fdb5f3515b87d1a29024c9e19f24e4c0e3979bd938aa4e798270000000000000000000000000000000010e72c6c0510495dd2c4ecaf13c1c6404654e1be369d1ca485c76d8c2304d60d69b90c2e171f18bf55668232e747825820b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d56200000000000000000000000000000000081d5a229481fd297363e8e217bf1f94a00f54eb6e8a3f95f4de30081bb2b9edd82d53cf287e37b459afabcb73fea1d1000000000000000000000000000000000ab55f52ff7dc578ae8267fe3fa09bdb8174dc30bb835cab9851dbee7a1aeba82e83e07d5e79aafb34643d9fc9a0d1c100000000000000000000000000000000195245c7a762776bc1e81d7111e3b814088f1e0e7d686c3ee3e500cd0a7ad4015851563a1b8b592e491e00078187c66e000000000000000000000000000000001850c1e8edb0d6dab973a9975833cffee8b5243654bc4ebe64972e423799283707f9ad343bfa86548cd2acbe04ede5da3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe00000000000000000000000000000000000007e9191fa9057cd7df8fb83d497ad774735c242bce9bd34cfd21d3f8f2a8e37d1f38b592a61ac8a8d22a4287fc5b0000000000000000000000000000000010e36db1460fa65ea229402f558397c6fc57e9c8a4b0b9e85d9ba938196bfeffc951587353cb7c7d84479f60c087e3660000000000000000000000000000000004d86938bebb850fea82acd336c3900b241757dd937f831dd909ce548325955f103dd57611c0b75bf71412a6ac3d6ed30000000000000000000000000000000013990c82583007b693c1d6271c1e5820d7274c4a729da21a76eccbf7abab1f2bdd6c5d26e78d51476ecf154e4fecd1b87fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000009104610d5887fb7cf6a866584cae30cfeb00e1241083b017ccb82ddc9d72fdc0d2b1d227c22ff6d8497495f44828efc0000000000000000000000000000000002235f959b071f21fd63282fdbb46b1dec27cc193f3e9988def691c73dddd789b6a1adb977a68e2661fb41d62280f229000000000000000000000000000000000ccd46984208f183f0b70c9152c01fdb8ac078ad1d85f41e3a24819da321d9dd9321a8d70103282abe6d8b981447f202000000000000000000000000000000001711057042a54ca76b0c3e7f36f2fd49e339b76cbd2e053d93ec2838848d359865fdbbeb9e75e408b4b316d60ce2741ef0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000001481684941fea0f66c78faa40aeb4b5254bf78c44df7e37b191c095ff12fc94248acf01d2aac5637e9536e73a82c9f0000000000000000000000000000000016b72eff2830f49b24b1e1317c95143cda8bc11b9dc4a91ff22a24e0bc1a244c7215ab1040fcfbc292ab236ac73cbd3d0000000000000000000000000000000013535421771fdad616171f7348cdf32bea7486bf4d836b8b95c69b71ea9915c099e256287aa119af53cf6320ad86664f0000000000000000000000000000000019ba0f36dc556fcf09f0a4a6cee53de485d03d846af7afb792d16220551fb5a42a4261f936b008babc096e6f8f68b63af8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000014b2da0add872d6e61253d6022559f668bf192b4aafe0acfbbf341ada55b404d42b2b31182c1ad50c73673494ea5b7d40000000000000000000000000000000018b76b74e9e6cda8466a354ff66baeb935b5645cf9eca81f4b7342f7914c9bf35c57be402458c09781e66a89cba6e67e0000000000000000000000000000000019bc8c1f32ce934b7ccae6d8ca39a263939585d8f94414c3880fc7bb5a0a27d728708e7ebc42c5a935f769adcfc083f6000000000000000000000000000000001636b62bbbe34bec06253887b78ad5b3ccda1bc5d8baafe450f2d1a8e07334ca79a40c5c4a50b58aaed96408749e6f68285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d325820000000000000000000000000000000013c0fd7a8441b6eb2dabfe8c152aa480015f81139c46440741f3da1c50d18c17526c47e8b8c2fbcfaefabbad5f8a0b000000000000000000000000000000000009da839802e7c6759a87eeae5a05146e1d226dd828d4ef6d908b4a0431008f352539f3abcd3e4c532a3d8204e350a8510000000000000000000000000000000014709634973e4554d2379e439d099e9be8bc7ef031b6ea36a7a85d2ff5090b0e0de7cc1c6b6a004465edcf868ef5fd5b00000000000000000000000000000000146779393d82bde1eaa6205e69907a0536c782fa7fc6e11e5e62ad5468f4422b3688f2ff4da2af396741ca5e0f97de3835bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e44000000000000000000000000000000000ddb7d0380370830803a7eda2e9b694af71381990f182b5d1223992abb5afe9531bbef8b9dba239f411fc422210fdc930000000000000000000000000000000018b685009d012d72193043d09f8968f9a41ce2fed598a20536fe54cb26db1733214add38f73148e754e632f6d78f524d000000000000000000000000000000000b967a7b4ed1bcd9f3da16584b08e0c28d967cebe7a07069abfb3bbce94d26b6d95d8a807879b24fb1f5ea00091d6dc300000000000000000000000000000000039349785fdb7d38707d8136e9a8f650c4491c50d7425388b75fe30da56147992c3d662f22131ba7173b2550e613477fa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c8000000000000000000000000000000000455d7799cc1c2af1e219b23e8683113fec126bad1dd7a441c5d113b064b552ccb1e7314dfed1b11f42a18acace706e50000000000000000000000000000000014d2400aa3e2270714b656bd755c4bba55866d6e313f619e10f94de6d82b5343ae9a9483dc10c1a72a5a21e619a20a8b000000000000000000000000000000000a6caa6cf8609d23b7873c908e5321d064a9c107b5492d296d04f92c308ee705229dfecb1f908bca0024ca56bc125126000000000000000000000000000000000b31c384423c84316f65e03ba9e01a8f626236f76e4df4b8ce2fa053c1c1e6a9b8f0afbc253db8c9c5e2ce9f9dcf05c71155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a6400000000000000000000000000000000097938bb53db8d0aeca3f2bc180039a5dc5269748e9cf065cd88e59b30733d527e54cdfa224e9690581e8c7f0881241b0000000000000000000000000000000002d52d97d4dd415fb18348f4de78c65e2933fc45d5e5e1d8f0f0ca1cd52885704ab12609b91d6d2d1ce13eecc7fa0c2d0000000000000000000000000000000018b926a37a8e0ad836846d06c03a9b84db795fdfe5f15d1fd3e0f8fef1b2825b29ee3a503ffb2f75765cca49c2b3d4cd00000000000000000000000000000000073bac093e958a3a09543e060c81b35b6598521a8685629f77200cdc73b372588e66c247097e7c03492c0943bfac4d6bc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045", "Expected": "000000000000000000000000000000000f79110c74f0e983f3d3618869af1d9b96dadba61f1d596294ef8a9412f946fa26cf63483528a57299dae48f85ada81e000000000000000000000000000000000e1a9cea3af1debcf7d6ef6f7b8566b5bb52d5548d4caf85925109228d7c9b50d65a1b24f089631e75a694f8e8dcaf040000000000000000000000000000000010efc1081f079e841eaa5a65cd7c945d4f37acc92c4ace9ae6c69a9a95d8cf569d604376b1c7e63558d022da90d269fd0000000000000000000000000000000010b7f55ffac8d57c89b664c36c20b2988a493de32f5a956c91b16ff67cb806298a59adcde12ead42d598b6ca3e1b94da", "Name": "matter_g2_multiexp_30", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b139e5dddd53433362c49403838b3e2ecdd850a8df12d4dfacc0bb98f79d40966d62dfd0da1e721e7c0f298457d590000000000000000000000000000000000fa35e9c2e37bee1020ed99516174408ba2cf443fed115fe3a964ed86b5e5369e40291dbfbab477e339003ac85eb7405000000000000000000000000000000000e8fb87794860237066ed1b7ae7c2a783c48c52c2267f3e7295d1f17598b96232954e1eb6d6e80e716628f1db8afe48600000000000000000000000000000000083521e3a6d6e3f99570b747498520db5c89092b0077519c8421f9f41772c7a6e177c9cdca52f89a26c6036cadfafa8b32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000b1415e1dc2d4c1f5619b40e616d258867493d8624857e41d007f82ba8dc53f7ebb36d06f8348b94eedb794899e97df80000000000000000000000000000000001c01656fa47d62b4372361b80ea61501cfda47da5534e3e2aaa27b1e3c4de0bee0aa322e60c476fd4345340e5c00e130000000000000000000000000000000010caa407d9d265721d55f01dcfca52bde851ebd918e8fc4c752a41875940709c64599f36fae5e3ac7f211e1f67890d1c000000000000000000000000000000000b54a86474dd5f410290e4b4ac738fbba5e88c6debec17e38a52090b17ef371dc8feb0573e76c4b61d7688547a89f6a36583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e8630000000000000000000000000000000001649c78147fefa91100738e50034424244d22d8e1bb6a2bf471e4c9b29694a5c9476f4b129912bb09fece53aa87deeb00000000000000000000000000000000117a3e040c1f54b96c2435891a45fb9dd95774b5a55cfb306c22517e4ea72172332d893047f7eaa665fcc58dd21781f400000000000000000000000000000000105e8d80d46e6bab2bb9ce0525cbfc82e8b3320ee4a8b9c0086e21cf2b5895cb35abffedd1b5a9eef21f62a0a1dc48e8000000000000000000000000000000001437ee33abadc8ef6bfeca16c3edcf05480c3dd97db06e396e10d5180472f50074f43f9a031a04dcd11d803462fefadc5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000003b51b10efb54dbc2973e001f0bb634e36f689264484eb128de2882d6600a43ad548bc7d1def4541f0ed88a1fb37f3270000000000000000000000000000000009dd80dbfe6663ab04656856f192002593df9ef7f792dfa81f6a51c658c4c9ce5586a5edaffefd507f51ccb7e8c8101500000000000000000000000000000000144160d5ca6b2ad626e6a3424ff5139adadd3319940afa9bff7dc409ac1fc3775d5413ef4612b27fd22c02c1fe57bb86000000000000000000000000000000000e375ff490a626dd1d933a5c751c88cbd61803986fa8dc089ccbdeaa0a922758afbcdc30d29268fe0a34b7b79d0f76c139c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b000000000000000000000000000000000997ade20fe9c0d3eb79e61a66a5c272d02af668b0f3c8201a1ea071737f3d2ee3b0764f859480e95be75ab8845b407f0000000000000000000000000000000003215194b6a363d31ece09b18700479e6093fa3472a23ef0133e3dce60a3d56b6fa984b900162c4ad56a6899aacf35c3000000000000000000000000000000001647647bbc399f40124c43510469cf613732d0919e22b478b2603d7553927584cd4b3a407e3ec6387c4a93e9e5373178000000000000000000000000000000000bacc8afdd70e927e21521b3f62264ad4f22adbc872439ff851d3d169a1c79a0d02bca2aabaa0b9941ab1c71d092fac12196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c565000000000000000000000000000000000b0c5981bf6ef5b85bbc504fb0196ba442fe87302346688165aa7df8cf2642548760e11daf5b3fe2e37b43379afbfd4a000000000000000000000000000000001086828b9560aaee5e28bcb50db8153c40e632b18c61ed4105bb7f472b6a69ddd8a2836f6605102931ee66b2f07e441f000000000000000000000000000000000f4d7aa3d1a281af6f8afb3d886774f4e4a64490232f63dbe16e3b8c4f626e9d07f7c668d09cadab3c92d6fe852427af000000000000000000000000000000000d92ea3318779b532cd81c9be44b1abb179a8411319a6f8fbd7e3f158bc970917d3e0b25f3f3f6c8e0764011f9bab0398df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000000dbd83910f304d0fb2b6d8619c3a308c719f6454a357d9ced03b2882a50692c06cda7f4331f54eb293ed5aa079121fb00000000000000000000000000000000019c33ec829367dfd2610ccef9842ffaa5e4f35809657c22134fb09b024e07949d8370ba8ba1e9149060e9bd3babc19c000000000000000000000000000000000ac468b42925d2daacb8574d40064d393caa643f08767d20e72ac0fad1447a64d8743523312f3a91a118d3e51e1f52d7000000000000000000000000000000000202d1971fef2938cfd10bef5900b91cc4811939f66f1f5578a8ae0eacb2538d2a51c1e025449e1637b5173ab7fa3b6f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef00000000000000000000000000000000008b6709123b9bd501360fa463dd08076c59177dc0e8035c49fa2f541eef3831e4c584c5a9410c68999dddda6c86fd9d5000000000000000000000000000000000fb94eb34355c636dca909cfa71f52471217b9bc241cd3e98907d4a5c7eb67d5bc9cdb0c73c1369d7950a014fe6069fc0000000000000000000000000000000002e2ee515a5dc96a664bb1f862f21a8d3b7f903fb87f6dac41c3541f3d83633f351ba8dc4661607d24b912dd1ab097da0000000000000000000000000000000008bee545e00e3fc283185a85511e09fd0253e191f52d5c0b440b10228041800c013db3c9322a835e4927c0ae0b21bc1e8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000006ba635e74538748c29aa7c5690a0530f2b1970554598a432d4ea6d2713a4d26786b6e80f67b2f39e218b19323654ea200000000000000000000000000000000133ca9e5e0d4a8200d3522d8e87dec3c72edc1cf16b7305af4abd466aa7a0e30159388d34c36ea030450ef45b7940ec20000000000000000000000000000000004724239afc773688ea92296bae8845f20793c05807a18d6f35f03bef295da06f8ac9dff438b720dbea7ea93f3ea9c4500000000000000000000000000000000149c12922fd69e1960274a8b91384e929fb354936c020911495e6e3c49faf16899ec0c6e87713ee2f0149bf808ac8abfcd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e5066700000000000000000000000000000000148a4fe6ca67b6c785d5d8a784d5e68fcd2bd08294ca37f296b6426433b805507b554eb9f0fadfa9d293e8cdb8547d4c0000000000000000000000000000000003700600c2b7bfea54801ac95ff7a2c069bace31ceadab2947a0641462089fb43f0b9697acc005a23007a923ffe97360000000000000000000000000000000001705a769ce3c9a7a91283e4068c602d85808980d6fb457345a5f9b2499ff8fb3ec8383049b9b7cae96bd2ac6106a07fd00000000000000000000000000000000052b1f4e8a48a5eb2b2580614c656393819b4f0ffea874be899e4964c7e32d54757f2d48ca7b50e47e8bf6d6ab8ee7572a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000012ff8ba50d587765e68f95d276e364c8c40c00b55abc929f9ec240985269eb096dd3cef5826cf6269ecf54bc67773510000000000000000000000000000000000959492d74cb34c8c9ca4a21ddee97df99c8a6e627db3ef72200f39e0402d56f0a9709596189c80aa3aa50793e0f1a68000000000000000000000000000000000f7e5dbe884597054d6dc5e80bf4d0d333025bddebc1fdb1d61482cf15bcb4c8a95ea29cdd0925b5b816cc0bb307387200000000000000000000000000000000194e940c041d71f43ffaa51fbb31eb63c23559069b42dbf8777f35eddf14edbc3f7762c7b354174a584507ad714948234a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee2800000000000000000000000000000000101a8e54d1fc2357df60b0ef8872b729295218f29ff63f7a7b6a70b3ecdbfc6809eaa8dc1f62a664b9987e8e86154c6c0000000000000000000000000000000015b5ddd012b42e1a600d738e05b551d91e7fcf3cb36018ceda9b689b92022224990c11a6fa0b421d5610b7e59b7463c30000000000000000000000000000000016130be17fceab55387d43179cd943c85ce1ff1881c07c937b2cc0645ec9ebaf0e10718ec7fe0d720f49bed2b8caf15b0000000000000000000000000000000017d73650680856bc11619e6acc139e137f0a06476f5f8979b5ba7fb8123d85916915da60d1f2e8c84197eef518b350c2a62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000007d26bf37a97d532ec93a3eac00d9d39b064ecd172ebd5e18228b1601eb7a2c272aff9d88d63781b4a587c2c8582eec4000000000000000000000000000000000108000e850bfbfb02d7acef97592e15ca721334eb51197511b0eb2bd3bb647fc8f07713487b0a0bedbafb106992de4b000000000000000000000000000000001868c0b2ba732731f7536851f8005e8bae7b16545b39190251eb2bf93dedbf0803a42ec24cebd151998b690c38c0346c0000000000000000000000000000000016faafe909a1f926333b12f5463231a71058aec31d73893687d3169c4c3588436f6178447eed307b642490199c507d63a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a6000000000000000000000000000000000331338cbaeb8e304fbb9257bb80aff5d3e043d07dbc476dec2795347e4c25248caad06ad14f56183d2b6276c49ff98700000000000000000000000000000000167e9578304a1162de73914b02791468e14faa2e0f161aa57818b8a169b5933dfcab787ec0f4b23737011163dcaa02750000000000000000000000000000000010aadfd5cc781e73c31f2fb64e7981b2e28614aa18dc7b2d96d2bb4ed8c2ee9089d6ebe0cf85479b272cb049e934739900000000000000000000000000000000128d7ea54f338064cd2f041f42a1a1e77d8b9be4ee55f568786a36f87f965d8142207e518798061eb3e32fe3b0f1541d1f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b400000000000000000000000000000000004539f22654b3182d4fda5ab8d4bce6f1268d4e402b6c29a4cdff3b5abe0618d33db55ccd1ff12b27b2cb0196ac53e0600000000000000000000000000000000177e80ab6aa8512cc9e4d65b06b2bd76e33bef9038cdc1ab97fbb9d896ae2ad884ea16407490653dbe972b14e9c30c0b000000000000000000000000000000000c280a4431e41df6515979a694ce292f220278178f7f36e23c8a4cb2b8a7ebc520901ebe34c72a26b2c8a60aa1a155100000000000000000000000000000000006a0b80538a6c8093f3655905af1c59c235567d22192758c28dad1b715045189a412e4c1edc26e1d8ac95a584277709b3bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000eb091007672a212dc4937b314576963d7561657cf1103820ce9bc34e4d46c24f4891a4a4ada648f8cdd2c30f670b86200000000000000000000000000000000166389a37e6e3c02317d68d54f29cc98d1d1df5853940555161d71df791cd92c483eaad87dc0e765b12408d6ac344f31000000000000000000000000000000000affd0d5734cbc27b192c0c0e464db48d3d76799d2c6a493b172127ef2df6ea18a33898828effeeaceb7a203e35ca41800000000000000000000000000000000155708b9756752c9b44048c91d71970fd2cf2a4cae6b0baec00629c81387c8261150e78f856093d81e816be6403f1ee91b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d", "Expected": "0000000000000000000000000000000007ceeb14945414d96088a7900c1120ff182b2a93b09943c2fd1dc2b0b223f684b0d4c0b1b5803502582f2daf16d81d2d0000000000000000000000000000000008df450fb25534fdc456a8f41cc143a84729ccb082aaa2243c8f37e34a6670f5195750f8547444c49f7a898aa8567d980000000000000000000000000000000008c12d360078d5645b0e095c90d4fd37eb20f0ebbc6fa93fa5beda7e7c78eecc06e0d839268e2c303422ab1769402e0b0000000000000000000000000000000002bd594a21153d7c458b9f804050d05caf2d90bbf9d18def79eb8148b7f89e3a3ac21f84b87fd13c39df5b91cf73460d", "Name": "matter_g2_multiexp_31", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc00000000000000000000000000000000090b834a587521729426d5b134c6058bf7999f4d4bcc0812e8d8b3ebb050961321b5e93356e87171a6f12160749394ee000000000000000000000000000000000cd5148c7eeac4aaea4288b38a02b5a901a6e2805e2b1695ed98ed86cfa0d259d87b65bf3cc9d00b8548100a60a371d200000000000000000000000000000000026db1079b85411dea0b9fca383956af50b938a465f35347605c01f3b72b297630ee2fb5252da20ee0d8ba5071974ed70000000000000000000000000000000012ae26c193e02d7ae4a7a01181551085dec9fbcac811c45d5cef19abf736ca2514e1259811970af5913891abe22a75ecf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda99438000000000000000000000000000000000ed5d8a609aa4f3c65a89b8dbc9334bd3cec6c7763bff298acd6c260e4d3bec0088e15c5d82618571d13b74a2031eff1000000000000000000000000000000000c28f92f018e6f822912b6eccfab37432ab0ab9acab751f848401791bd2f16e32ac6d97948bd8a0bed2ddc1917f0db3b0000000000000000000000000000000014083be2539d914883172cdc70950512dfe7be8893b1ecf085d837c2e9ba7f03656c5a0e15373e04d300869620eb66d00000000000000000000000000000000002561b77cc2658c54d29f8d1988dd7448f59c80c02ee9256404d8ef5536ee50104cbc11b6ee1ab9ccbf0ca55e53c52aae45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a6700000000000000000000000000000000000fff05aea33a9d1e8f7b227c80ae87c9e7589ba2804904b7d8386b24b0e5324e718f29531251969a972870a30c310630000000000000000000000000000000016ecb8f27a369df13e122c981e7ae37882b36d5492fccbc86d606aa1198f3e4ee7bb7ad0555e11949e6c1783d8f4cda100000000000000000000000000000000187f425b675cb12719a01ee3b78ea73d88f70805f72d6cabef6372ccb9d99008bdd7da54f155454c4c59f041deec86f800000000000000000000000000000000151c272d5cb67b3f801e103ee901deb4b3d3bef76ee4e1b2ce1b5e663ed292845ba012c732d38f9209f82e77f1f73cf354868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001791bd59815309f2aeb7b07df8afd89a288eb6f19c7e613f394353ac5398267e1388c97b17d83104446e57f94581a79c00000000000000000000000000000000154cc72ada5a9c99dea06ebec143a14271cf332b57c631725ab30e2d308d6b688ca08a79efb6fce632cb1216ac3d077e0000000000000000000000000000000012b4c6fe8c17274ef57539563a736c2f83c4cb473e9d075a976e18e193255057340f45de373c7d6e3fe5e08ad0dd97d20000000000000000000000000000000005aef16e11bd4e7787bd5ab4427276ecdf9c6c134b9fdb2ec39e87ae4a5b3b674b5ceee29bcdf804ebd7e83960d8d7ef7068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb0000000000000000000000000000000008e57f905fa202c7640500746b590791cf9d0f160a77e5eaa5a30280e513e8e801c4b6b04cc3f80d9403388571d180ba000000000000000000000000000000000da3c128ae234bc27824062832ac10aa9cd4978f37855a8b4cde3822f5b485fddb9a475a9805e795519d7f138a8199cf0000000000000000000000000000000000ec11b7e07710161fc557a56e04337f71aaa1a0f070cd84525965e53a1fe445c91ac07c618ec349997890ae893c165d000000000000000000000000000000000406b0eafbb8782d11f5dae2f6214282252af9ae9ebc5c17a81d4ddded40f05d0b534d14019bcb6cf4e49c4c182b90f00042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e000000000000000000000000000000000b1d456e66671dfa72ef3a56523eb939146226111fdbbeb697983928aebd5f50b0518db841a3d48912a7a780785c1f180000000000000000000000000000000007a15b2253496b78d270dd55b80bff90583a95283a89d40f6df71fadce56d103f0d365fe79256fa4f93b2d2bf4c06a2e0000000000000000000000000000000010829223166d38fd2c3041dd5643c9784da366a2ea8cbb3abdffb5fe43e975318c86de0ac9ec77c0126ee75bd209f7300000000000000000000000000000000004b124018e83e1e5e77bad42eb831798d450f8ff4a79c9b14f67f080047c491fbba45db79b2cf6015188f9fa6329e8be0a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da0000000000000000000000000000000011119be42b90c7857079a51695dd5be08e59374b0d1c7e12d0ffe870202e1f0c62bff84c9691679a82e610e788b7b5e1000000000000000000000000000000000c7a64524c5dd1bf10d16da7f15b39d05c9ee1620d4dcae79c60316a1f522b238e7934d1be897a441d0c8e621b67d44c0000000000000000000000000000000013045613a090d05d07310865d977c8e0bb1caa713b2249d6676e7cfd6f4e3ba8e667deabf9fdf7fd527685f7d251b178000000000000000000000000000000000dfee7f8259701b5726b6439a7ce77b92245499906502c7dfb384e29cafea61f3b1f21fcd7888231569ebf29d3035a61ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000006d77669207bb2d064824cb56fc786c631936d30db630be3c08e18d7e95b1c26e2d4e7b2eddc2f946fba6e99acb2198a00000000000000000000000000000000168bd8f291f8bcdf8b5e9fa915f7f24856a62803bbbeb9bc38384149008d4e3129338035061631f1fbaceeccfaeef4a700000000000000000000000000000000146bf2dedc262557dec2b4545c94a37434e20e4900b1693e8fa9bda9a94dbd07e0a3bee5f3bedfa42148791f4951db7500000000000000000000000000000000138467700fd5088c76af2f77fea4b746f98701fc0578571997b0ac2fc343354ddc8b2dc57d5298dd4daf767573d8bd3d5f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000001536da0df7c91687339fc93608eb404c5f46adf4b9122b99b1e5cee0012e27ddf30934d8f669bd39091f8673aa3b3c490000000000000000000000000000000002deaa8f9349e7c551e39751b1454a00f8f7896d63110e8e42607e8023ae3070c4abc9885ed54ee37a82f6e5c68451e900000000000000000000000000000000079a62eb17f7b07d4117956d3dab5d16a7f90e98948d5c3caa124fcf755c73f060a90d002cf880f5246a87342717b4dd0000000000000000000000000000000001246f0f3ec2af7c0250ae14cc67b5a1d42309f06c6f47b89178ff7534c47e8413a26a43f27454c0f946c66634563d41cc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed216500000000000000000000000000000000046e4a08785de985c66c7417f9262d363b9acee07e250999a4a7124f101ec4d82e3e4b2b0d9736471329fd61d0cff13b0000000000000000000000000000000017bf1e20ac181780ced62a18c78b378fc0dad157cf30d6026680560b681f5755183bd30b4e454764c08edb93297590b5000000000000000000000000000000000a57cbe93254bb0796eafc0a57330e38bfca37f8b94c4d21ba656e5616239e1e18ba6d632c0129d30291736fe37a4ac90000000000000000000000000000000007f31df7dbe9abe15f4024d8f6bed93c92ff5bfbd7835e08e870eb1bc4a6f62b3809b922c6d5a7350e2e5a978c80a67397b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba5916727000000000000000000000000000000001577abdf6e915c9c3b3fa50a4601709cd629397f2f91784528e4cdbb140065fc2a6ee3830983dcfd49a928e78cf530aa000000000000000000000000000000000d6f98df9e41009837cbb05bc3e3340d38e56a448fe396bd48acf03f061e7489d1402b36a84b3c56eb859437e9c406f1000000000000000000000000000000001912afae5361c3d8c6141755deeef26d1fadf6b0036b9d05b2e0c4d50f42328741f0423ac772fc66dbc922bd4a837ac40000000000000000000000000000000000616661f049b5c784ba05334b2931509e1e033bd203fe17f04cfe12e80e73eb7075beac9d379fc1c457bea1b6adf365752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000f98fed7e4d67a513c746d2fb188597a605165d5d299072aad6d621e077845f93804d575a5796bfa726f529dbd90e014000000000000000000000000000000000adb2d0b6c02e4e8fcab11c7c8819e87f73aab673ff9dbc5c50fee751bc7a6a8d386c8f9fa830b5545f94a73ce6e1f1f000000000000000000000000000000000f08e05ac40655cf59ee3ea9f10fc900315c6f06ffd3b80853560559f580ecdd65aba5ba660c729e0bb9576eee3703710000000000000000000000000000000009da46469f4b8fcd8d2b016e96f6e6582fb01c75407c36c7f87b4a1cd8f08ad06e962a0ec2138ed6fabaa1cb0115f97e2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee3000000000000000000000000000000000047b45ad2ad4f7b5b72194f98b98b2150b5d73a9df2aeb2377beed9a1275a882fa2d849037ddb56af632489f892a48a7000000000000000000000000000000000e1b0d9b52c0c5324067857ba4701f5f20eec165be418871fc0f0adbc3a0bbdce5a33277a33b79013109b81e006c621400000000000000000000000000000000179c471e01e340d8e6fc0f737ec09f0180bd2dd2a86d0817f753d1e9a9f8cb18178e9de68c596dc6a824e6c3c151d8b80000000000000000000000000000000019405c1e571a9b200ff2949aa74647dae59d92a8669d4876ba23f1b4a12a1f9412412503c68acbd619cae3ff056bd346f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db4458000000000000000000000000000000000ca8566b9bd088c471fd33fb7b1bf760ee12cc8b0cfa9ad92b45012cafef5c0772d9bd3bd9b266d6c3e3890c8f00057300000000000000000000000000000000055789839e786ecee7fb7d10f3876359fcc1bd6f2c5cf25c8337aff7fdeec9b43ffbe932cc4936bb708571a59e4339990000000000000000000000000000000013cf827bd57d8179d105f34c147665a072714ccbc114aa4e878d04ce66ca78bdabdc4867b3968c75dead147257197c6a0000000000000000000000000000000014a8dc5ac1858442ca627eaa194e1ba64091b5f9ace551338d770c92fb49ee12449dc200c8c35d70f9e0652b4d9b90da1c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc000000000000000000000000000000001124ea2b97a6d73c81387a51e814b9bdc951a773db2a32d50691be60f1d397cd4aadd9b06e4f49c32b12254e9f824fe80000000000000000000000000000000014cb365e9780feeeff3548f34a56548302ae0dc73402c40317fc819969ee9c4ea2a181381b94f82dd97a236671b456a000000000000000000000000000000000064b769c4b785d45472038aeeebd3ba9b28b3132d72023640ab2d7512cc6e31296c5330be5653ad6902e4e15e57e2c3e0000000000000000000000000000000014c7bfb1f142d69c17f73e23011aee0063a97a99d982d25ff72791a65c7a68941a80fc216cea8a49f3df2d0748b1f95db0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4", "Expected": "000000000000000000000000000000000fb1227806c750e0eec0b865daaaf51afb74a88589d1c035c60dc1913f05c8ab18de24903ea876fda27b97a5eaa2fd7c0000000000000000000000000000000019903e1341f0285658164f9273b5c958060bf836264502b9dc298f75d4104d7a43b8d5dc0bb934a506ce1273ba839d830000000000000000000000000000000006e791347b54057195189e8b9f10fd42d170f37d455c0af5e92cc6a12e2c23990253be6855f4be6c84a708852c19a6f90000000000000000000000000000000005b72c361dca430fb2414b9d5a326cef8b77cfe5310153d6994dc1f8b9e74e8fbb43079e21956f428ed8aa48d6897e32", "Name": "matter_g2_multiexp_32", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000052acff88605f33a0cf1201e8101c95ca0befd443c087265821a7d954917a026d41ab24d29bdfd972bb52ff4ad6de14c000000000000000000000000000000000e134b2aac3f6270e43afd994302426796b1e362031638fe0263c0ec212b828a30d8321af34ef7bf260652150cf2293b000000000000000000000000000000000d6628f675008099e9a75e1294803e86417ab22670d36316798680289ae61a26821693f2f9efc8468721a1097c3bceb20000000000000000000000000000000006d66ffad1a2e0f39488fd3f6e0214c9407528c8bfb8d1ebe6d75596a3e3cc844d00fdf46ce7ff6cd6d67732874a24a484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd000000000000000000000000000000001160bf0f7f2915cfc64e12a5a91b7e2aac78d4c2ce362e7677dd0e9c0172b37fd1b52222a13c65819b87593ee32a9ba6000000000000000000000000000000000c8be2cbbd302b31b1ab6dcbeb57b4ad428447bca9159fdfd007f5375218d121673a010f2c7fdf83fb45883458fb068e000000000000000000000000000000000363d3d492e6e6901756bac13b5c32d55aabbedde878115aa41b57d27b49a0f017a61fc90b13a20e009989374b82f5dd0000000000000000000000000000000009302fc26e6d750ff9441d7471903cc296b320128de71f86c4eacc80ce0725e8eea6acd2af056abde2f61e0a349f9bb5bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd1000000000000000000000000000000000b12aca17efc103cad500b3d773e43cb24df6be651963c0f30bca489f1dd7911ffc7204fcaa4511e161c6f75da4a5ff600000000000000000000000000000000179a36e9292d3f78a5fecbec1175f001bd4ac0ff3610f596aacdba29a12ea4844885a7c465e66d3883c7fc99d4a7e06a0000000000000000000000000000000016bfd0758b31f54f90eb8562bb693c45a92a297a3d302279c9e3cb8263efc0f31579a3af8e8f5a091d9a6a36776f445d00000000000000000000000000000000020f6c66fa554a5cb610ab05d194e7583e6798a113b8fff336c986f7358bb9fa6a7aab0b04be9b5c44a6fcfdd21999e83717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000004bf40c1d2d3574ad7fe128ee376364591b6f647f939b0b556ac3fdb5a92873f17c007e926b8a39a97c814728f355bfa000000000000000000000000000000000b8669e10e0a538a421b717287455620b82574b96ed09f64db444ec73a67a3227503e1b4fd6869314214071399eeae0b0000000000000000000000000000000006ddea4adb703d7205b6d2af436b41b4bde3a8c5dbed9dd161c9b3b466ebf06beced64fca25c3bbb97f232315daa5565000000000000000000000000000000000d97248a25ddf0ebd0200c6abbcac9ecd9775cfc5ec8da91634e77488bb592e5ff277a9607fe721990f403dd73f746e622788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d94000000000000000000000000000000000135bc4f28663a6d7d995f6b876ffb8e6ef1d2d0f232388aa5f390c57e8c48cb84d370ebc4bc267eae4466a019c9ed56e0000000000000000000000000000000008b6a9d13dd9d7014df6acb59f80b335a751fa2ba4dce63467aed18f68358f5cb743718112b3cb2d0b5add34bb6989000000000000000000000000000000000013a5389dba4da195f34fbe798b254403f0bc5632ed98bd6017ef24fff33640ae493c1bb7a77a0d3c97649230e455eb51000000000000000000000000000000000a69803a4cc237ddfebc51df2d90fa1ad03359f9635ac1646bc942546575d1558f5f2c3010f6e2207849ee697be41d093c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd384000000000000000000000000000000001939c2431f8ac4ab19d2735f122c0424af2ef18c0028e155611237e86648bf1d74fcba3008f5c6aa30feb5d4a14a3f3f00000000000000000000000000000000174473eedb54aafc522973244ec2feb3f7e95e50a1e996d1100c8da4fa59428c280f76e9e7364906662c4d2802235aa5000000000000000000000000000000001021d15f8ae2f62dfd3862944bf3be88d86d8113f4be22544ae5e925d450044279c5bfa1bfca44cd5934b42a27096b510000000000000000000000000000000015e0f20efae92e1fe8dea2222ce808a7de9e9e861c333db139f8ac11d7c4fa9ae6e49f51095f6e16bc738dc6d094b4cfccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a0000000000000000000000000000000012d70691721f5787ea2e2a652f9c65edaf763637f95c285a62d32dded18579b7257493e01eda19631d00ecdd4e27a9ff0000000000000000000000000000000014da9ef6076e646e7d5b52d1803d3a6d897664851c6de2a9b330e48695737e05f0369224c3eb357bf557625bb94e6ea2000000000000000000000000000000001554f68124a91be5b9f325394db23ed5db8f6c46eb46cb50e57947bae00819b151afbf4ab4949290ad41625499f42dc00000000000000000000000000000000009fc0d459e28cd1239d227e1d2f7d530b9d14ce5638cd308569300a791c997a51dd5a98aad703239a23cfe7cef7f47f6ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f000000000000000000000000000000000452580d6a37a07038ce3564a12c1c7391fdb002cf27a6df7e194b38f3c12a3026f2a8acfe5e634cf89140da256d0a420000000000000000000000000000000004b73c9a4f9d41b8b84e53de538e4b15198f50247e75c274c14f136d7d91dce4a62c5346bf11a105f035e29ccac3dbb70000000000000000000000000000000008a8a3b2705a82b551f8913853f682253e7f1f68c8e42f349337f4f1eaa5103f59430af0c4a124b6a739bf88298c5f6f0000000000000000000000000000000012f4220609899e8610809bb3a4da46e0688c285ba2e8750b4bf44a849cf15fbf5c016e8e8f9372239bb562e7f38916e921cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c910000000000000000000000000000000001884558e709635c046bd6ea8872bda936ba4d5ebcf7a0208cd0a4ee08b69f36dd2e136ce655ddfd89a5b1cf8e48f5ef7000000000000000000000000000000001357e2dd9fb603e5190d7b7ee105668bca2ed23ec6a248aa71aa430c2b2755747b8dfa3b147eb51ea644bf0354a61ba000000000000000000000000000000000009b0b0a76c6980e62e4893157b85f59345e1ac81e1aad1e48acec44c4803e2a9080f0d193fb799e0277ae6f1058839e0000000000000000000000000000000014c984ae4ef5d9d319fc89895f34a7db02747f57b206b0b30e8c9757d4b47419e6c0c8378fdec5aba364936a3b1922ca2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a050000000000000000000000000000000003e1bbb872db172a1fa615155f81aa75ee9760f8602e4135ef9f1640b7f9d54bda911a220d211dc4bb767bc2b5e6e23e0000000000000000000000000000000008464f23cf693b1d4545b6ce4aecdc8fd182cfb288c5ddb1f78ca578e9b16342c8178d886cbb6b8086c0fd1318c4ae09000000000000000000000000000000000af574c4d0fd86087e23daf6d9ce98765e1e445ef9152dbd68152fa4833ada0be440de4abfe7c07dbd4ee67f1a4aec9a000000000000000000000000000000000a8227b982f9286b03c4d49766687622206213d88cde007360df9b4ca5916c44ce44dbe6443577998b4b0d527d22593379cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000012e31070a501a7df7be43dc23e23dafa32ebfbc10ffb4c53f5d36bab2af69db5a05ad64b9ed116560e40b71f9217189b0000000000000000000000000000000011cbcd38ec3c6a6d49df6a8d6e1029a0412b42bd3fe8b42ed625adeb5a2f631e97bfad302de82ae34f715962b5ba0289000000000000000000000000000000001019b1b619fde9fb885d3c5f03a4373358107af7509754ce1ab2deb67df536d05e07ca7d60d927c15b549502750054f90000000000000000000000000000000018f1768b7140484105cf3ad2daa7c565e18eaba834db3f6bdfc9ee37445f2d6f7dc2b4c986b7efd5373224d2c92aa5a81973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e4000000000000000000000000000000001847b14146cfa2e1700f368f414b6a66ccaa02ca2a90b40a8e2be2ee4eb66af77ba563d7507de63362fb18426b6149610000000000000000000000000000000005c028d2b344ccb6400b53134bd179028b8774000ace89369bc655bb9dcd1643aaeec830407ee941df5432ba27987e8f000000000000000000000000000000000c4a680e2157dbdb53ae761209d505b4cf6b18fef5aff1c5009ab41295e0ce2ca23bd7a4f983fb9d085e1d0dbc75ffe40000000000000000000000000000000013c0cc77a5d771f1df99d1530e65ba782604c1ecf67d08572609de9f18405b9b817c2643226cdc7c9ad35beebf87dab0a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000009f32f2f83c21875963818872d243cc8c70b75234f53490eccffbf060cb3b9c53545c1c32025b271514f500b20b00ec10000000000000000000000000000000002491b571087a9e89dbdd039ccd2c37d5d8d25587495b2d7b0066e9dcca02d44b2c134b0128a9a1527396729f069df83000000000000000000000000000000000264e9c47f72b639597de8f26a42ca7d77324f8c0db705986fc3b40dfb46f47764b69c70037a68d76a5de49a278779a100000000000000000000000000000000090614b3bb302ed9fb78b8756524fb78d54a4390b27136087181342571f994b1a93faee28256d765a8ff4f448cc357c199ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b0000000000000000000000000000000012c716ddf17fca0d974e8d6003d99aa90f06b201fd141c74d8fdf1167030d14dc732917d3c6f736c68fbde9df50c098a0000000000000000000000000000000000261ef2b47de8e1576aecc6e19ececf80ddc1f4e28b2ff27953a65199f65a6211db7326632cfe04d543895c727ef8b600000000000000000000000000000000044fd6b9b4a1bacb8b7d4c53c106b025ae78f17c3baebbccca4e18cfbdbcbf8b3ef88ed5bd9bb36d9aea9e24f4117e760000000000000000000000000000000007721612515fd075811ee804314acec9d389900c7ef883e866f71fba00c49d5c4dcc7a2b8e2366f5a93f4577926ed171fdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e900000000000000000000000000000000122581659ab1712afc23c23c2986394de8e155bcf722e944ec05e7e42e05acc366d9a7abf2136b5dc68a8dcfd4a640bf000000000000000000000000000000000188842cf4ef54cf77c145acb685d3187cd9c842ba6705bfed846ace83dc4400c45120fc1d6a633ea879840d3d0c902f0000000000000000000000000000000005c8966862ed4458a753155ffe2c64655779860149641ee5511a46ec576798fdb5cd9521528df77bfebcdaae2f94b865000000000000000000000000000000000cc10d888d2b7a97666de99ac14a501b7e2171f074d30d947efd67d85226c312a7977cf923ddbc88c533f08a99f2045f786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f285d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6", "Expected": "000000000000000000000000000000000c9be91da9bd8774f18efa3ae9248e4b03d11c49b377c372613b7e745882b2b23c49d518672e58eabd4d9b510a25d8fa0000000000000000000000000000000019687b9eaf5d68b0e795cd57055a74e44efb3e997cb038b7f1cbf08ca70e80a1655cdb04402c542a92ae4e435c22d0b90000000000000000000000000000000010aa1514402ce348d1d61b8d38b53017cd3977a84dc14445db64799cfe822b56a0adbfc5332093ce7ea1f0f438bf15590000000000000000000000000000000019ade30ba0faffcaede95aa272be042aef090f89d9ca25cb825846c4bf9e4c1dc575f8968c88ada51fac71f26fb01517", "Name": "matter_g2_multiexp_33", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a1346771f8ba25fc44323d5290068e46b3f756de6d97aa934d511979a1486bc32173575639a7e54aea8eeb60f32a8c3000000000000000000000000000000001958ae7fce87db47a65a03402313b99f659ae02e8b62db3525d48dc9075aacc5e5abb50156e704701f3ceb18747e0431000000000000000000000000000000000f98778311e28b4081aa76a3f9546b94c29d86fe8e66b905265d74ee21928dc3ac463049f70d355d8caee5b59d65e07300000000000000000000000000000000185cc233ce72770ae26406476c1779858523e7c940d69adf2750695cb12440440686b6b918f4adb3b14aee9aceb6422119582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d37000000000000000000000000000000000ae2f565a44c8e07f2a136368798a44926cffd3c3a6d4c2fbf91763c20d2bd959271343b80eccec4d59a84394c7a3ce70000000000000000000000000000000009481a5fb276c938801133adf10dde3e7da2087d0bcecd3c9435b7de544271eb3b07a69efe7e168869e727868f24b0d90000000000000000000000000000000011774e0197866b1c8b3428d353d2c9f6326a77ab30d5595e2402a0486f03ca6ebb1e8dd335a60a772dfdd9a3dbdd3eeb0000000000000000000000000000000011ed2480d79f73a67a2adaa6da3ae4f1e1c28feaf0e4cb9aafac658901960129e40f6415ec80a31d72004899326f714bac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d1000000000000000000000000000000000a73b0d8c31af2deed481faec54095875639233bb09f31b1c7c745cb54778d1c8bd0a230e963ddd2ec8d178d31fc14740000000000000000000000000000000015a889b16be93d0b6dced01f5e2278ffde1cef0576d0b04b49996cc5252854f879e04b1ffeb90e222f4b9d5fa350767c000000000000000000000000000000000b53dc4d72e90330ffad17012bc7dd2e497cf8aa6ec73bf25c10427e23fd28137631249eabe9d0308c956dc7a9e92047000000000000000000000000000000000930cdc5d04ed2d1eb62937d9f72fdd733c07a5a0e392fd5216100216b1a2e3cde7053bf766f046cc470d92bebaf6290069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000131c4e590400b69b3657f7c67272b1e3491983997993ee87c94043001d78e605965abf3c1a8c8c39cc08d5a5ef05520000000000000000000000000000000000124f71c136dbb032504da910958e8a7949f1dc5c061f21d50e439e01e67919891633b3bb84fa8a54c69b632f78560ca70000000000000000000000000000000014a4b1a05f1060853f4294e669a20b91f939793a6eada6dbc84fda8ab11509b256d8b785b252a3795f1d2b99a51df05d000000000000000000000000000000000be2489f1f91d7adff356236859679c46b6bf8c1b375e8bc8bd1e97830b5ac223ffbbda60ecda168bacc2c0b90ed25d3be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c00000000000000000000000000000000111ebb796e8770d5a69e724a8d3ca62ef1f13778baf4ba12bf462211d35e325ff8e455c85237a73a3046e531f2e2255e0000000000000000000000000000000004308b76b06067e0a07bda143341220809b481b40b78edb2e24e83aa0f003d209198825b5fa9bfd92597e27a4054d3ee000000000000000000000000000000000de74485713f5c95653e98b96aeefb79b59911a610c2a848a807653c19d50394fdb52178947c779134d24b6d396ca36800000000000000000000000000000000069f47a71ad765591f6335b962e7c2d87b556801e1e6c25b449edc83432612fefd405c952397a704e9aa5a924769ad4e9b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000a3f89408ee43c0ba6a7c9c479327ebab426d430e3ff212c65da6364b16195619d27eee83d701a2ec50bd4b7acfaa06300000000000000000000000000000000092715831af983f740ca2c673e7c9c47727d64165c59fce19dc3fbbdd0b6a7be66288ea1f033ebb5ae2b38b3762edaee00000000000000000000000000000000071ca6fa9e546d4bce965b2bd0f0fb97e6833f05cedcf66d43ec88aba411dc4d6db9f1591de22f493f49a1dab1a2701e0000000000000000000000000000000018f89932ec032fc28775d34d588169a1435bf4ad7e2ee11c9d6934dae31324ddb96b3ef88f95d1bb2e52c3c8d9c01516d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f0000000000000000000000000000000011b5cc382164fc21c9a72cd85acf61c2a78d00a16a2dff938f0b36bfb3bb7075845a1616001ab53271a9a257a38312cc00000000000000000000000000000000139ba2f27e545d45027a0b11253532e28fa691170e08608472ce3b3f9a3e9398c5ee76953b1a1d01a5e79f194c32d1f5000000000000000000000000000000000d875f44829555cec695f3f4a28078b0a6f168bb0985793d003443b75a141936f3c7c633518890e0f7238416d46573cf000000000000000000000000000000000675420ed817ecd24bc5172d3e7df60ac4281b24ba91e8b5ca8bd6a8321f5c7312a6ba043fbcdc467c8a5c957590a692cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c400000000000000000000000000000000107bde844286cd3958cc7a1314127322251699b51d8af8e6b57495497f21a84e05612b1569b54fc5639a75e9f9deef750000000000000000000000000000000002355b1a60e24e4879448437d2c1b12e58f02d7eba88583e96e9634f7e2c8c6886132ef0488918f665ae3f7b6977c7c4000000000000000000000000000000000fed531e437b70bc4a19ad63c61ccaab49afc50fad1f156b1c8ecba0e1b703f8aea61882c6327d4d8fdd072df9c4e73500000000000000000000000000000000182177409579ad53786539514753c696c8757b8c4d9b8360392f24b591e43ec20e84c0abe468061a9e5e879c5c81314217f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b40000000000000000000000000000000001fb25395089228772d6000025cb0356eb510c964bf7d0c12d47a6608fc18cc448e44880eb5ba8475cbe6418fc9d8fee000000000000000000000000000000000f3b9de9980e5afaebc59c56e02fd75fdad13013842ac035f8d5569a46cc67f0cee461a939aa5a3d8fec2966294207930000000000000000000000000000000009a223ac0edb164845eb8397e0cae4363fb2c8c996c3c5d722cb50be56cc3789c732763cfd4b61470886dc991be39f57000000000000000000000000000000001909f17b229eb351dfe8317a8273d846edf14ad5ee0ebe8cc2b595ebfed19b73983035e19ebaee3d05b1dea35968586961ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb500000000000000000000000000000000081fa9eb8ca7d9db52380e4c408e6d5d668471bafbafd62ba9023fa08f6d300a45295b583677824c29ddc3254439cadc000000000000000000000000000000000e2e613043b1566674f791dca9d860a49a75dfa24dce3fe18f544a9b24ec5266a64e77386b672c93fc4d079eb8e76a01000000000000000000000000000000000f471b86ac5783d720e7d73e8871474c8665e8a109aba27c1172ca24217eefb0f66c53232df1672dc0af6ddf9640e10d0000000000000000000000000000000010667cb22a6a818fa7c729e40a7e70e1f31b0ecd568b54a4d352d5c9df8cf1072ebf2ef1e612efd96bddcbeedd8566430fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be000000000000000000000000000000000a0ed87b01f27f26380c6285e82bf2f12ef3016c7e7f3a13041d465825664573db47be6cf099cea615e21f6a5d759b6a0000000000000000000000000000000007afb2a1bd50fa0fd3174d70f1c8d5c229627a496bc9bb89d4f52d47b1862e14d704dddd80045e58d00336e898a996eb000000000000000000000000000000001698f30f824ee5cb71b3f2451953c371987433d2eda570f2a13262ff9e5e529e316b06ef6aadffc152803b076f22db9f0000000000000000000000000000000009eb1d5f3da7cfe9b40a70e1b3c3dae36436e8d068a79dcaa283905614676645c99a5a165630ad46b70bd6be8b1f21a8d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc40000000000000000000000000000000005c4390b8f37cc3fb9f248470b505a5d9502d44e4a4459d1f56452cd9aec89d114f1402fa45935930fa00888a4860a9900000000000000000000000000000000163b0ca84b5cca4f124bfb5a13a4a3efa677a84dc89b6a61e69d0aad34fade528614e549a7b2326d1f6016bd0d35465a000000000000000000000000000000000bf450dc8af483a9f993a29cb47d5362c9f5ef38afc2fba8040e14514eb834fec6520a413fce5868aa9a2c7c3ff6617a000000000000000000000000000000001063619f384102949fa1f8353f0aaa5031234d736c54103df6ef6fcd0df02a19c3aef471f0413a1e19febed6395459a0c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e35db3017963d3a9d62b7e7fbfa13ce4f5fb46a90c1285ddc0fa481d9379b95a77e8cdd4aab5c33059bfcdcd82473fb0000000000000000000000000000000004fa27c663c8d21f041d15cb199d31cfcb96a56cd673b730dd111bf03cd954cc33799456674ed4d58e8e0dfa826a6b26000000000000000000000000000000000e0df4e7f943db5b5c27bafc7e1ce099b2caa64642bcd6336ef926352682fbe81a1945b266cba7eab52b16f4aa63eb8500000000000000000000000000000000020167756b8c68f535c4691b1249ca1ccf0a539f7274623ada824d0ba789ef44ebb20ec1ba51d46c0a42da78653d287e26cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f00000000000000000000000000000000178037c6b5fd1c6c396d8aaadb712863557feb744d2cb9165ae5c36376d2c066f7b1648e083f81c2c96da6562e0b3c20000000000000000000000000000000000b805b4e1cd5d45d8b6ed9d4f604ac0b40f336b8123f7281df43a6e803f8688bd8087fc4d5fbae695d06efb0fa35e18400000000000000000000000000000000000a947562dde45f613ee1d15614940a2edfc770d733a60374f8e9188675d4cf973a5c1081c11fe5a1d93bbe85e6f47800000000000000000000000000000000059473d80c82c6ca06b4aa71d072f4751b3b053b53ffcfb4a84906ddfc36ec5918668a62f07054af1b241bdd4485edba699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000008b9d0916a9f5689b8fdac84bec3a49d0224dbadca6329ecc156da633e1332bcc6735ca3ecb228c22032dcb7b2f372d3000000000000000000000000000000000cac0c264add10bdc1217384a7379f65b93cf822418f7e4e2b48eeac45f068a61f805cedfb1665dda06e04cb726d245c000000000000000000000000000000001578e98a40a64da59154b1c3d757d8f1f8cdc500482c7b7d65b9997576f745442fbac654c19331977bd210df440372970000000000000000000000000000000015ef69f82e85c81d28893d94927068f14c6516eb7d09898d5d055cbb7a9b55c6d7f686f067ab164160e6d6a8f91ea19d40e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf95230000000000000000000000000000000000a6a27b498285085139b8dd0c37b700997134337e696c84b5e0cf70ea3991cfb40ca3a3098a3b3a2fa31e91aac78eb2000000000000000000000000000000000bbd7ebf4301c5eabd4f448b89f1b227415cede3247a1c8dc56a02247efaa99dc78cf370f644ffc06cd2158fa25197dc0000000000000000000000000000000004535a402540474d53c084d4fb6d9e12dba6716ee13286ed758aedc1ef911b55c572640180a54cbc084ff57ceae8a4b4000000000000000000000000000000000759de2a9e0f3c04b4f629a682dbcadb2140e5b935845cb55bd267e230e08c6e8cc5426057473aa03ea2196203bbf6dc062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d", "Expected": "00000000000000000000000000000000134c29cc5c33c10f04b6c09b5db71b10304028d06ad6acd4f4b39b16823288085a84a0380a1549f04b3dc692cb8216d3000000000000000000000000000000000a0a9379d63527ab9b5f9c00be4acd54e5fd683a0a2f37c85ba570171c705eaadfb0f4e4be1a8836c9de86dff46138300000000000000000000000000000000006ce78f135dda5af34a0e069d7ef13fd589cec5a6128512bdae7f45f28b09c6e4b3cf638628c9f4783097cc00082aeea00000000000000000000000000000000141e710ce7a979dd1772150d0cb2d5b269d5cda50d1bf7bd0cd827b24f9cd8c1e2775f495cfec0428519627b7fede464", "Name": "matter_g2_multiexp_34", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018aba8353cc470b287a163fdb9b8b4cc46071543ee8261f928a7b856287946637d9b36b728a54e1df5f185a47f1556060000000000000000000000000000000001129541b2e3b2e1a553995b603dc3eee44a5ea440e687739ee9e1339dd79bd96c67231ac753d483e0ca96b27054997b000000000000000000000000000000000e1cdf3591aadeb56dbd80890ff7d5639a64847cec771a19c769df7da732a6d3179d3a89ce0052bd7c982af0304408120000000000000000000000000000000000f5f5f0ebfd2b632e15381ccbabfa88eb774f2c61801381ca73e6970965ecd54f5f3a9af7c152186af8fb75ffb5bc25764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff2000000000000000000000000000000000e527e40c311edc5dcdbb4d0b70497eaee14533aa8ec57dc7cbd7d839fe6c6ae62b1fd0be2346a038de687d5cf5394d60000000000000000000000000000000005f9fc63027dbee5e0d55cd6c57daf5df7af0d138393a2dcdc71ef9aeeb204ea347f7d574e71f2ffdd37d8f05dc7979f000000000000000000000000000000000f8788034c9f1c9c2018a52326c046cdba8997199732152963819b663c6e58e9d6a0065289e2e25a97ce5627505900f20000000000000000000000000000000002a747bb3bcccdc6ea0af1bf1d0ce55de3f41b93060361b30c76063346b606322a76ed7eb260219c83aea0806ac7d8583280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000002148c043e065132e978e89f018a5b728d278c95c9cd1a6f276bd13f0cb708422a62fa22f7b377adf33055fcb09a6a8100000000000000000000000000000000024c4c721a0574e53118bdcc3fd41f73176bc8264d2ff39673210525166bb3513016b5c9af67a47a7032b74a62effcef000000000000000000000000000000000797dfa8cad94896916b7d55fbbb3eb0eeb74f70231205388d0dda69dd8abb436c22addd22c1e3689093739af957b65200000000000000000000000000000000010dd2ea2d45528de8bf1b5c5dc3267fe8951e48ff5987e67ec52d58635521cf1905f1688894e3e23a659764880b2301d4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb11177000000000000000000000000000000000eecc0a4edd3cc3f70d3e0e43ba56b04cfb3f1ac23c657048a94318e622217572b0f929c73f545d6f5f5613920c0580200000000000000000000000000000000137a098ea8d3aed32c197a2d244a2e18753045b55cfe16874f79c728c664b7f23b10476f20dfffb2f80417c26dff4f860000000000000000000000000000000004a7789b02d7d95a2ce0c7bac39d5b057509200393450a47fd9d087a353f866921aa11185550537b98f3073650d9a1370000000000000000000000000000000006ed63730bae06403baf705da0e30c6c00739799eea4a312d06b8d7dc35cb43a4f1e941a69e55ddd7ab8ce42d8629fdcc66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000ee7ddbf43f17f722dae84d34d26add8c1d732918b8c75c6b295f2f584075cea0c655911410b32c06868c1acf36aeaaf0000000000000000000000000000000018775682555d9f5a576cf9462170910bcdd083671ae2e4c8c6fa99a702548f1ce9afe90e681b00d194322b1a2a3be7ef000000000000000000000000000000000f3935bbbf58b91fe8176f3e25ad3fdeffdd6b369ae70b704d4e54d4fe32fe5987e73aa5aa975e958497340274577cf3000000000000000000000000000000000c088bc439d638d86aba6bb1e6e9f7540ac2da3b96080aed455edd1fbabfc141e26f125cc3a9cf72070a24f298dcc3ef4b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b8450000000000000000000000000000000018d8a47d1a13b9b8fb5a1625f9616ba120d5c677bcc996f694b7e15d251fc4bc938b0a7cb5b70f22b8e9f5b416c513210000000000000000000000000000000003d0646458bbee7ccab27f858b8ab0af0cf583da12a40ca5a954d7eaf97c897d379129a63d8131036f29c30c6e644149000000000000000000000000000000000d5466b50975c5a2dad96e4e24339eafc8c85c2497a6f19e12d96603596498654cabea6995a92c91b8319ce06f18d56d00000000000000000000000000000000191a96d62139f8219b9e4369a783400d045d72ab2dd83fd229e08a4ca73de59a11a5add86c739cb3bab4adc5e9f79685708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a00000000000000000000000000000000032eb1f7846b563e98fca0cd44ede4909b6e16a893f5ef01eaccbd7d8aa11710606bbbd0ee6480f7cdbdc9ffe66c3a9c000000000000000000000000000000000c31bb6fb537cfcbffe815d86ebfae1f5053ceb756818ede8a58cd84cb34d0eacc70ab9095f9db1691e4fb4bb816d570000000000000000000000000000000000a8fa1dc2f28277a4bf8fd9665d4b5c3baf1352d89890d4af94a3657cdac7fd72558da1e65cbc5bfac142f0e817be74f0000000000000000000000000000000005ff65c22ff0abfb33518791823c5f2202ea5f7258c0a507ab84460335ffc2cc8d7c7f670752a7647d6a6487ca0c9adb85ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000190379b7629f74bfb88096dc9ffcdecebae0d653410f032a35a811a09022679c9be19f3790af95c3205a396819e068e1000000000000000000000000000000000b6f114fc277ae8f0b5374dd349985bf955dff7fcb0095e0e1e137fb539814be78c924074bbab54f29dfb42f3e7df24a0000000000000000000000000000000002d86b0507c147142d03d3461bfea4c3af7e57a6edbb372387de24a27cfe27c44ee4b9571325a1b3f5e83eef450f2fc6000000000000000000000000000000000ac3b226d5e13c36c3a8ef0c8896d9af55bcc0cb67ac1cee57a5c6519617ec77af9af60ed44e0a8284a2d59816ebf848fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee3147800000000000000000000000000000000079cd4511e953e4d1b3f4f3bbbc66a62772018e809779fa39aaeeffac737cda9a6116293848f967577f03017f33231d2000000000000000000000000000000000ce3cf48be423a2fc0188b94f2a22579872e9ba140798e560ad107f63ab2b8c601831f89d06a4bb8e7a758cf836ddfb3000000000000000000000000000000000a6a90f735f215a79216fc4e7daffbf74775f38824952af72ac38c38a77a277483e34bc95031679494d76f109c0aecc4000000000000000000000000000000000d55fbce780d885cf817cd2126e7acf115ae9c72843af23c376f3a5d4307d1eefaa0f4691e7c09b5da1707aeaa5b675af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf200000000000000000000000000000000008924efbdb46b9324bfb79b922ba8b7d83f5e5e4b3b736105e5339805838171801bcf17208f3dfe5c7475d4e45b6ad970000000000000000000000000000000007bc0096fd23f0c93f0dde8a3974ea3105574e031202f6316d5940c85164c6d6bb5b86078a0c68dc822c0fd1b3dc8cc10000000000000000000000000000000017276b3208b347388a5657b10e3c8e4a187b376e42352f76ee3ee88873217b6b8185022c93097cc116abdecf3cc64467000000000000000000000000000000001915ff932acbdeb52f07b664bcc47c3a5b096c6cec32da4d7044326dfe84358e49539fe50782538a901b99428446b0f50ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000009e295d229b543a17db1cc85c846111b7097bd169d19b410de78f8da9684e664922eae77c64b0db430aeb422016cfe7d000000000000000000000000000000000e29aab30a1da56b8590e9df67171cc1b9c847696b51147cc57ed6c3b55819cfa0992c67e15e4ca6de2573c9e16231c10000000000000000000000000000000007cc9990c6722645e320dd16a4be8adaab41f958f769ba0d22e235549a7457778cb9b14aa6ea5caa9e0bd43f8d04cacc000000000000000000000000000000000b2dab5cf37ae8e76b71dd8748c86e8823142792445fa0b140de31957d35bb7267e3d94e0dc92f4342d9f8560c5d9d86dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d09953900000000000000000000000000000000128e2aa795f8479da3ea2a4efd12aa90a6fb019d4da89fd372e6848ff7ee17da689d766c9e49c88c962eb4f682c56fff0000000000000000000000000000000000fd68bb80d6b2200297aacae1174275f864669e962d85c9105032d7a352fea548e9fa0629a6749c789fa0827a40190700000000000000000000000000000000175bc3918dcc972fb728f1d8cc30ce9887efc6e0b254d8d22af87f95cd4182129d494c43d11b028c4b9849f5520a4fc00000000000000000000000000000000007c5363f507a01c0b6935fee0413345bceaf1336cdd20f69060bdba2e411521a61a549e6159b2e006ffa16e3bd77e998e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f90000000000000000000000000000000000efcd782b89fee74ebc037160c6653ccc104260b5f8989545b40d51ead6ad6ce6252e1232281c813e3c883af86e68ca000000000000000000000000000000000b68ed21f76ce131c089dc454dc48ef948cc7c6d5fd87d647db954c9eeab2f7f76ccc51a1cff8612e89bceed16ca03ba000000000000000000000000000000000cd776670d5171610046fa294fecefb42f9bb4d71baed4af65a09018b09ad9341789abc23c9feb85adf96b4203b0c0a0000000000000000000000000000000000ec4ca0091a28b73c9adbe7120f2bf1a84a62ebba1e86b1948389b1a1966c1de4c632a5e245ba634b53cb932f5847f6ecc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000022f319bb5167c2b945a69a438f712df8975a0e262438ea687e2b0d824e2d1d14bff1065f50fd6ae92494f6f3aa9472b00000000000000000000000000000000198ce9e4ddb6b423788dbea82d75513f43cb43ecf1b27c8788f041248f01808644f60fd823e5862cd7afb4f7e8b6b6a100000000000000000000000000000000119dc1be1bbb7e678319db73055ccb88ef7efcf6119f8a9c43c69247ff264879a627f653a10a950e0dbe69155ebca4f1000000000000000000000000000000000692a0ef5a75d42524e3fd52ae073b0f2ddf6378f18a5dcef05af4868a899b93c7f1d2691883e5c85f97052ef1f4177d09a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000001423d1707e49d2215f639df75ee0e13bc724efc7d099259179260ba0f17157c4efc4276844bfdc46c61ac2185f64beca0000000000000000000000000000000019ad06d215d3c819311938f89609ea7cc63fadaa11bcc86cf5f26370a966eaed1aca312c18176674b5aaca3ed8ca876e0000000000000000000000000000000013bf3f13e87f3ce29f0524094e2ab8e39679566add32e779256006dc92ce09f60d5bb9cf0452b90ece71a5f6981d77f300000000000000000000000000000000112e4901efca14686c30a883ecdafdc389303f4cf46345e229885c76d900b0aa084a957076009ce22ee36d4e285d410c9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12", "Expected": "0000000000000000000000000000000016d1fce53fc4cf40acb0347c0983dda46383e4828c16985459ac89a2ce8d3c2a26cd9acfaa2ec899cc63b4c6bc351f560000000000000000000000000000000019c9626363b511a79f297dc79c5a3b7a2e5127fe49a2fac5bc43a4376f170404f044f9f84b82cd09a306012fc81e3bdb00000000000000000000000000000000062e324f3d7c5bd39808b762a5b009cb30bec14a9591477959339bf2de9ef27eb42a0eddb95aa5fdca9bb9d89b278cc20000000000000000000000000000000000f05225a4d3bf910b0ac0103594a90684ffc0c09e2c21744032e30470d5727be3c27621dc2377e9845ad78be67b856a", "Name": "matter_g2_multiexp_35", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c64577b78ff306186dc44131cf8bd946a68e65502c4af9613154a7e3ffea4fe4e59cac4a500894b470a74e66db1834e000000000000000000000000000000000b4311c295bd30174f17b9ab3544f1496b9655302a4b6026a113b1aca31b555ce7b2d812bf8fafb6b075f67cdc59b77f0000000000000000000000000000000012d7dc3db10ae6b4e3e99c655aadb71124a0bdcfa6e30ec13c7c977d39f83aba4979a1f45881813a3a68e149a40120a90000000000000000000000000000000001b958c47cfecd619c05a2c54315585d15fe377beff889602ecba6ea3b29d51f6480f09a0a8490e4c754045f0bfdc3eb7b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d00000000000000000000000000000000010db66653f2ca73e55d85254f62e8b558584d546913e4b8f530968088d90cd5c4bc164fdb77325fe0bb2fb1a5683896000000000000000000000000000000000a1af9bf84f0b351c8e8c082a79c7ccae768bd2ed40eede38666aab1901a6eab0cff258f440c8c8eb4854a824b9aab4b000000000000000000000000000000000444fa654afb57f1b01d10be08a330f23667af58e3a40c630a2e01c1809125b3ff8f76e8a39418688a023293ff1a45e90000000000000000000000000000000002ebb117ea107a3598b888dcbd5918dd0ca307b21d65843c6c28b3efab89d0e276e5d92283bbb345b674d4a300595425c6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b48000000000000000000000000000000000105818a11efaeab4801d4fa7558b09bd56294be7f58e3e54fab1f4f7c243ceaf08c99a14c3128ccfd866b6f93caf989800000000000000000000000000000000091ca092e5f83a04e872e621e71d54dd6886d088718f60ed5b62d3e4c165d3ff2cea1e50fcb94fff251df0f5ee453cfc000000000000000000000000000000000b42051a1ef52f87536b9bca459fa7070ca17bf825950b13b3bbe9db183ef722f62af6c05c174c65d59b722e1d2f5b0e0000000000000000000000000000000002fdb4a5480418e43aea28e5041af6ad55a6c226e1eea1a0449a99b5a937375285feecabea95c2652da5113dc17d8ef4410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d000000000000000000000000000000000d8f93b589678d4e93bdf8da7834bc8edab648ead731b7f5f0cc299488f07262877ee9bb1174ccc106204dcd3f1f416800000000000000000000000000000000160f740ffca48d3a10c43e591cf46c129507f10e65d76a031fded2930d6c2dca4c79d7813f63e4ff71aee09d672173680000000000000000000000000000000013c768a4889315faa3976c8e43b4d466ea594bd94773f270a198f2571ba9662d10435d1e541a492055c333eece9bb29a0000000000000000000000000000000003dcbcc9e6a0cd5741d77da88fbbc269202e8f944a5df5dc4f9145758654934d5e1eedd596325080382458961ed3d21ed97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b3000000000000000000000000000000001344fb37c1d7dcab01a4bf6fa50c6bb7606f7db22b85a3888ffcc2e9f229f196881cd7c82160730727e49b9e6fea04320000000000000000000000000000000010c7b15a6355d3152eaada7a606031f28809f278a1d0e04d264b563185ac7d9e351295191a6a90ffc9c6dd33995265db0000000000000000000000000000000014438086226a061a1bd557dac24d9333e14cdfa3a7bb00ded4a450e8889a3028b174bf38ae1347e6aad19ebc1cf5ff7800000000000000000000000000000000105165703c4592cc4f1f489d78426a56434dc77327c13221b582dc25306f4c5bfe596f3e47abcb741ab553fa14cad374bdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb36000000000000000000000000000000000ad8b55a198a5e788bb54c32112761ccba9863cba16d95ec9e30181376e7eccaa2741660f2c5f708300be058e566ae27000000000000000000000000000000000b9bbca7db413964d2ec113cdee2d7a7bcdb712d285655f6b2897dcac61456ba4d08e25e8c28627231824829bd7d13800000000000000000000000000000000001ae49c10675256651e3e038a2150d85993fa6f2a97b9bc02c693ed97ad52af34015176258b3b2546b81010a4381d85c000000000000000000000000000000000c8f9668a0a497420acff5207a07cf780e0b2ba78083eb0ed8eef76beea996210541bae2e64d825000f307d54cbe3b2b43da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000012990a66c132a31d60d182f729b52d9b57d9d1eb1988b6f0b4d9fa2847f26983078ef9bbfd0e888e14bf7d1f92d09e54000000000000000000000000000000000585215ffc2673a197bf9cc6c6424547886abc6ef5c6edfeab2ef0c42034a4a458fc7155c35c84a8e9e9d89fbd4aa25c00000000000000000000000000000000118fb4fe0d3498dd2b55e62272e95a1203f9fd22314921d3e044f1b162376aaa7e8154a4e2184b66451aba98729330c0000000000000000000000000000000000364b9032ab9cd9f599979c8a93acbdb831707f1f84fdc92844b58bc7e4d72472ca5b09c51b1b04271ed9f0e391552463c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b6805893000000000000000000000000000000000ddf9e4e302169e195f4f88fed06e0c93fd1b542abbfeea5da5d47c662ad9a16b8f4aed7874354fb9008d073920e1e7e000000000000000000000000000000000043fd1a4b781f25e8747ecb3eec45ce90760e0d5dd701e8193a7e726684ccb8ff21f0219ba15e9e777d339a3d87a1ee000000000000000000000000000000001117d2ca429048056084e4847c7169b4c8ddaefe1d48079949f9f14e4d74f0e0b38a95d0f17389f61da7b2a6d8cabd1c0000000000000000000000000000000007adfc7d87b1c533b4439f6b30d38c694b65b3b321f3eec13cd7d923f1d5f117005be6c3ea901a9479d79fc553e34e6c51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000115a81aebee0329b174c01458f8714b13ea3fb2dbfb051b27b29b940652f27e01a84e522626d12be80da7e1039e2baf6000000000000000000000000000000000d9e37d2e5e7160db30acf5593d1c282541a0d4ac0482f0759fef8704b9ec3ab1e3ed248e37c6be285e890ef1a520d0b000000000000000000000000000000000c198a22c2f590df2902c8dc2bb1ee427b33e9687767666140f9d3b51d73fef18a259d43d86fb3559b1ab0abacf547a70000000000000000000000000000000017e705af54ab76145a79e747167a4fec6ec3a16f3ceef86b1ddd1be144e616ea7d686bbccbd1c5c258e4546405be023d6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b0000000000000000000000000000000007c4c31287ae0b3bb90475f84abdda36610f887aae311d8e97bf97bbdbdfb11d38c7de331cc9dd022926678e5180c0770000000000000000000000000000000017f4afe28adc4b24d16b9cd97aacd171c2104b13b079c643d664a7c924151a401c352832c4967c0e5cecec5f1d1dae290000000000000000000000000000000005a8aa8a3a91461e0ba256e44af56875f8d07e24628e738ffc057249d8380417884f40c84e76dc6ce5816ffc05c0d686000000000000000000000000000000000f84bb7385a6936b519e881a708541570a31a9d7897ab8b348a350adb0d30522567fb917c9b6db661b6f53f98b5e68aaf4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb1000000000000000000000000000000000a8726ea352582ed52ab4e440102963891f059cf5a3f4901615733ad560a99930efd8692f3c30256d58e5cfc4f0803bf0000000000000000000000000000000016a623dfeae872639d99e3b8209748642f20af796325630596b6ab6146783bd4f2247c7ae38d53ba9a3fc0cdd6f29087000000000000000000000000000000000e40709656e569e4fe90eb85d8761c6ce44a4272793ccb7635ce75d67d97464e8dcd8c32bd4ac9a36fcce706006915b20000000000000000000000000000000019e64802756896206a9600f2d888f3d307ebf958492b1b5153c06a11231e2d7d2f1786368447f380093a29117cc25da9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000019c7409cda6084edc6e559da9b361c96cf207f1e2cd63cabc9b86c5bcb07a59b43e9c6ae3e90a61c872f168ab89cb2c9000000000000000000000000000000001101bb63a452b766a085fb788937f6b751417dd8d905ee50ab5bf96cdbb9d7b68c1735460a71eaf9e9bf662734f495c20000000000000000000000000000000014a103871fe523cd01053a992eb9884ce83c6023bd6a8c2cd9ca60b8780118c88502c6980904f2d2bf9ccc9fb597d535000000000000000000000000000000001929f25d52ee6b9a44333237c732a63ce2abc80c5510bd67faad1d7adac96eac5449823f3a52ed18bb90b93d9640d0d1283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e50000000000000000000000000000000015b0a46692f57ccd2b7f53040dd75f30af0196aa3c5499049eb172b4d927f96a59c42a129117d6162a1bb31d2e8734a4000000000000000000000000000000001366dde2d9070a2c057744fffe78effdc328b122e356a6aadb10c3fd2e8badc0ff70bc6d18293b3c52428e2ba78766600000000000000000000000000000000016fd48b067b949ed75bae3e4db29b5785bf672bd01032a925d653f8a605998e1eff6c77ec39dcfccd417f1e0a9defa820000000000000000000000000000000004cf22bd706dbb1cf8b97187ed97636380871402b3ba9de58f174bf50a7a0b528749762c3f55f5f835a276e43b46e669d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b0000000000000000000000000000000000ff424ae9372af46de34210bb0bd670eb173bd49076df5caca4bc4293e742121267a20506f931a4ae77cc36fcbc8df4d0000000000000000000000000000000015a6815b47966fb84aad5de62e6d4280f9135e129f33fd01e667f4d6e1bf7204317fa7741f3cff3682e251437927131c000000000000000000000000000000000639dca43483b79ba8043130e508e91fe3f43bc362fd1dbb135a2eb8f3b94d5cc4af70f1101c790545a0eaf2408706e1000000000000000000000000000000000045f0a04a642bb6e4db34fbffc8adb19a24648554f36ca371fb1a851384a4516a57f1850f7d6be59ff67029ec4002de310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a99000000000000000000000000000000000e796fd500cb1a25b834baf7335641f34ccf04ccf60f82367f0e5c8c7fce8e3030e7b916752bac8e3adc01cbf4b319ac00000000000000000000000000000000142e8bbac9cae69ba3dca48aec045e0c4d7028f73c254433f921b7240761c661cf8e774a21da249f7758234cf7607fbe00000000000000000000000000000000045a3d80767d116e89bab0e9de812ffe7ffdbc41b61f5f17ad16be5bdc9968e34f46b937c5f94f8197e21b358f44b5240000000000000000000000000000000006978b93018bfdbaef0d40f1278e831a1fc50b44fff39b7c93820a284d90b699981b1f422f751a33094ae7b5cedbbb2691b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab98500000000000000000000000000000000003c3561f5d255cf1f83cb5f4df8e3b8d5655d965826d56867ae66da631f8e7d489f733f5824c36652ab00586d9c593be0000000000000000000000000000000010b3adb0017e2cea1b71680ca33aee368429880759660dce2d3cdf57b6cd7339bd8853e5efafb9a5aec3f7e22da676c2000000000000000000000000000000000cdf976e4c65edb79ff15178f6ec5bf0a77a30d97b799e433f216a2fe3eedb10bc6ecbee2974167128773cff43f1922c000000000000000000000000000000001599b60ee70d927849764880830b2e7355daf95eefef39ef61569a2b83b2bcced4dfb28047a1e5350cc87ef3cd5cf1d93e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8", "Expected": "00000000000000000000000000000000123af49ac2981e14a490a4a6792f21343497b562266a47487cf6c650769c810852e357445bc68f3460e7822e8cd1e3f000000000000000000000000000000000143e79853e4bf6d031e1116dac2c6eca4991b8a1f822fac1d352d2cf1b88df239d82df886f0b24b3e7f305286cc1257e000000000000000000000000000000000b621056a9de2d83c946b1e2c7a07f9beb8e053202407323e412c0d8f60123cfd5818067f57034fe1b1b5a3d1bb285a50000000000000000000000000000000001642fdff2c52d58d38201cf44c31e00df47ea1439e9896b5ac5e9372482f4ffcc698be46c2d375d57a72fc677a9fc8f", "Name": "matter_g2_multiexp_36", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011f78204afa05c3717d3908dc7e4356ef96c426ef380b6abba3a5616d9ee01addeec3369967ed42e030c4b8ff9939c4e00000000000000000000000000000000175a19c86e7eff0f4e809a5105503ed223fe327ee4617f7f51257426fe408373899f39014821292a75e4cc4eb9f7f31e00000000000000000000000000000000052130dd3cd17840385db424802d869d7eac781365be25ba401b7b0e4025353c8dbf59e5997b5aac74c252192061299d000000000000000000000000000000000457f4fc7ac5d7d4fa07e8ed125df4c4e950e6ea926be9c04b6df3c3699a10e99af7ea8546b8ac70c3003468a75c821ca2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000005182a3af2b52102e09214d048a1a29d1e10b7ace6afbc4e6b1ebf16790be372dfb6d65cb8fe08c3dbbed8c5435a59a3000000000000000000000000000000000f2a1261463c09a88edac443ec3cea8aaa19659e8b7ec2e8a403dcffb1e50ebb3d07217a9ab057d8d097c075609c13900000000000000000000000000000000007d6525fea8fbb685fcf89bd772d48c406aff7377429fb199f27c3c3337f11f8e24c4d81c9026b469600d11e8cce51be0000000000000000000000000000000004b6d6102debaec16c34fecfeb444e7ba573b13b83ec375f14d2c541a0d1fa528ed6599a0eff4f8ca527c5baa579f762fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af000000000000000000000000000000000ba1f2b2c3f1c57afa0ab647d32af5d036ef18069a4abc9795dd9927ea274a718b67373230e337cb9374ef73b5e2303900000000000000000000000000000000016458ff2f5d600af9d2983f535c965a2a8aee48c0d95095bee642bb7bbab8bc87e3e7c3b52a787c53f0d1e00cb4ff32000000000000000000000000000000000d11324e812cd4fa65d20cf58f88a9bc9407657834d7a92f80bfd32c7085ffa2f9f78d7e18c1405a03de939bd0dbb06b00000000000000000000000000000000144a0f4d50bcb16942d22a12d28bf34d2b4c51512a3f11c130f1566aacbdb63ec3984df5569f41ef621f50d138883d0dda609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000000d7b138fe445d7b7e130134db653022ebb389075ffb62ff9faa544cd0fdb9e78e313d0b1cb19bd812421d38d1e9996d00000000000000000000000000000000084411aa2719b729a1e299fe8a710f767009060f1b2becf2aaa92efdeea8c91239aa5d2504c6e7ad2e3f39d89ef00c1e00000000000000000000000000000000017e86dc0146c9bbfa5ea1e48f49918167dda13b31ba73311fd5cfdc12845b95b9e90972a9a4d36203be8c5920f8de5600000000000000000000000000000000150e4b6fa9cd9a609241d1de8a637c6ab25207bccf8e5eff4a97ed633b67826135172880b118037649407a3e1b1a0661bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000ad7430b7778248d63a06e26119e5600ae97071fe8827b24440587e8bf6887b646f342741af69d20e243c9b45d7dcf24000000000000000000000000000000001230cba1a5a66197875240fe00c59b796ba1db5ea5653cc76bf43d6adce0db3a109168593beb39bb45688c1d124b9eb300000000000000000000000000000000144652474c58413cadf9b31715152052b7618e7093e931367a7ed0340e66d84c0471b6ec178e1730cf10e749e01815780000000000000000000000000000000009abdd0210f25d12146f2911a60035867f59cc341b35c73bbdf8f7a5a90d0bb6566c6ba0e868a3d62d3557436190f3f63222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb40000000000000000000000000000000018ced3cd0c169693368fcf9c3dfc49fe248f0b9b5511e9407b8634d8ed7b54ae2dc4ac6ffde8b3dea70ca86ddc89449c0000000000000000000000000000000002f6b227e699dccf7ab1e0b1cba4cf9f05c4dcaf9fee6cd94bbb79f42bc9598fa23eb2c653a7654db73feb511b24829f0000000000000000000000000000000019785959766eb8b00ac2600d87240f2876e049725680f4504f59db6642ff8f82d4e1b856929643906c3be7807a2443180000000000000000000000000000000018285acdf25a475b37ee4da872debba4297fc8731eede6b22be3b0dff12117634de44b84a18042852ef419c3ae18a46b7431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da10000000000000000000000000000000019c7950b01e15669cc1f96fd94957535f32132ff6a5ae788f6f660024c332593942bd3e9603f862756edd4f3ab17b20b000000000000000000000000000000000bf3a6bbe10ad91d687a135f4863ba0332e9b04271d437a6a4770056e6b1ca34319dc895f9186482bbbc815aead03392000000000000000000000000000000000a3ef4d4f7a15da04a91ff079cc40040993a90e9ea21f53e31f7dede52dd513a97ece780374c5f3aa8c8b2e525ee31d10000000000000000000000000000000017749fc7761b06432632ac686d93484f08407504e58b04b3890cc2101f15d21f46ec0dc1e9028c8ef8df10f9ae929887d474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c730000000000000000000000000000000005a36af876edfdf26175c185c3ef005530e02474232ea659f5cf251c5de5721f1b44a25714967d283525632789331d2900000000000000000000000000000000130a6f5edf94736477143b1efc316f131b36d9658c484821be08e7f5b9c93f60cf34042858664db0ff0240addad8782f0000000000000000000000000000000004fedf49e6d49c074dcca96c01607da2105d8053861b4c677a69cff0f82e66a2a63f32f3d9fac8e6c844a1f77055bf31000000000000000000000000000000001528541de3a9d4a216c0e60c31d2b7c7cb91b839fc31307cc70f18e9b87b92bf5b9a9dc4eaacdec6e6bf7791e547d8a2976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd10000000000000000000000000000000013faf7b2b8514f77021d8927a3b63bb7c57785e581f40ca82882341c13a9daf062a26b668844e58291366ea6ae2f179f0000000000000000000000000000000009060f9e1047f15f175fe95cb0914f4941bcaf071f24e856eae6f36263c812689a9217da277613c10c8e254a0933c80800000000000000000000000000000000154619e4ae3901789ed3ecdfc76069d8026a3e2cf142a144e8b58482233380690e378de6b81af0ed9b6536da1cc2a30b00000000000000000000000000000000040c1bce922503699e1fd5ac67725f11d7f9bb6903ff9204412f65355be69d73cd7330a3f7bfcacaa9b078ba6b9a9f839db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000103d91916d537379d6d8717b17ac5b7e9fedd98c24890b51c027cc086458259767d989b3ff9d6adad72bf977e4d378f400000000000000000000000000000000159c01ee371622378339518217dfc0570178aecc938b4a008dee1a6661ffa605c0f1472c107558ea791e0959d7dc1c70000000000000000000000000000000000ea3e10cbc3a55ef2dc7bde7a2e80666557e9e8fd9ce77e2e92c2c70777afe43c23072e263e1def56cae4b6d3772db96000000000000000000000000000000000cf1db638331c47f9080c04117ddab4ba79950563810d50e04af819f14ae0981f6e1e94a635fc90226c8d7beef0844354ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce19970000000000000000000000000000000000095c82c58182ae9a1ba14421c2966d687f7225ccd192b24097f997b471d13b46a048202712cb2d8b1be0ff40755dcc0000000000000000000000000000000017410aca05935a06942f673d1937a593423cbbd226f6707c5922306d28a60396baa08a941122dd4c583331c9481a734f0000000000000000000000000000000002c1d3a1262ce8aae42a6ed10d8020c31a468127e1a59d57d2d409ca9d14143d9fd21353b260edd8b387840580698846000000000000000000000000000000001512e29256b6b9f5a7ba4f79dde2c915b162e4881856258ac2050f02868842381518da4ed824243692b131710d7201f8ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000ff83bf1d50fe35bb3d1bcb07b02d78a1b44d2e0c6bf82c600feec3897fae8b93c0ef05006c1322af0a732392dad86e8000000000000000000000000000000000d70c4957cb3615027cb950e4224d41849b9ff1b435ce936384fe17c4d7bc2883fdbba5123ca0c0c010651500557e1be0000000000000000000000000000000008b16fe9af45fa913aa7e5d01b5b58f946004eaaeeeb493759a5bc2b192d2dd71af24ecc5c6838b5e267ec2dfcf5c17d00000000000000000000000000000000038ca027c985af3cf60cda13e770fbe4919d3a5b413763c8ad155cb4903312822366eb986f2ec9e0804594ad4894e468275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000003a313d6d41f1ebd6b98b2061a2d85943e52d89e4b8680611d41ea182385e154da24248faa1563e6ad79172f91a8763f00000000000000000000000000000000038d9388fe9169710e1a205ecfd03f674b47ba2275794469dbd5f193a55e00765c8eed026363b41afda417bdd8910ea60000000000000000000000000000000007a75f53d9b8e5eef19ef6f5fe8ce5d5308a1a7d02e0bf46f91a1e0cb22555752d82d8471c123269050fd8f35a272f600000000000000000000000000000000011f313127a036403652fb2f83c5122fd12c362ecba2251bd6c357a964dd758eb2a2c3053dc668b9a4bc071898d45cd46ec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e450657331e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f70000000000000000000000000000000007860c3403607d4e13f738357e18bbcc4df10fad4aa25776f84d3c2758624a83aee0996146ba17a812384e1d67a7c54f0000000000000000000000000000000002169148d86b1f7a0ef75d9bd19b6d7cd66da4293fcf33fed9241544dc2564d980161a6bd959f3b43569312bff7a23cb0000000000000000000000000000000001897c121cbf5e82424cc50078ca7143a0c670f1217a9180cd2a4700e06aa895cf84c0af94b7c04bfce047a7d1f8443100000000000000000000000000000000040c1a0c4257f90bd83fece3c9372842a148132d2dffa956729e741ce996d229aacb04387d51a72630329230020b2235f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e00000000000000000000000000000000058c001ee1343c6cde55bbdc4c538f5d14b0e8c199fb822f080ad96ee764bd1908f92260ce60cd521919f223301ba1220000000000000000000000000000000000f8943c35e7fb8b58963719f1b9820153e0831cf81dd208176af7527781ceabcf6ed2e2276cbf374e0525952bace0c80000000000000000000000000000000003b43ea8c32a13c014b05326f7b4ad5b5fc1bb2367866a69373ba91402f4b45409c6d034898e8b0ec3b93c2878d59b72000000000000000000000000000000000101c371ab4d57ed2cf17dcb731117b1986bffc586529fc1edc630de1c6f4fdff1e10b0895907bb81d2ccd3eaa96c04a67b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f", "Expected": "0000000000000000000000000000000007152d538d0f750901466c1ea34a16e7b0e1296a2a3740568812587affa5c0c76ca2055804e24f3415a403f06a717c0e00000000000000000000000000000000119c0c282d22a01524d87eb850789c4816e7dafdb2782b57c32409b1016615beeee2067443835466283543773cc8b427000000000000000000000000000000000d68137c3df081a519747c044950c3231ef82295eea5b7040843668195d4549c8ece4a91447e0ec89530bc51277535fb0000000000000000000000000000000000d81a4fa2d32ada3e08a7bd4471d45a6afd2cfad5bbfa3d378b1df2e0749f9b05b465be61cc9d1a0f4abd56dce03dbc", "Name": "matter_g2_multiexp_37", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004b153d6554c9879359052717457179d8318f8127eb73edc1d6ac2efb1ea9643c4357cc813d1df49730b77f995d6d449000000000000000000000000000000001533a450737b4bf8edada15446cf21ebf82aa7bec7943025dccc4784e1070fbce430699cf3a37a36a3ece692ce87639c0000000000000000000000000000000017b8afc300bd70a3221120f6fbc37a8e6158c48b476f00992c6f41808036765071bd0a76f7c641443b97ba523153947e0000000000000000000000000000000012f686b4759a3d5db2325508f148bfd6217e027fe261d3ed7b8fd0526036044dfb563e1c4399ec266e140ca372120e289f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e00000000000000000000000000000000061c8288b7bf2856c075a176d1086fc49f0359ca3e7c1aaf5f151e6462916a4e1b69b6decb18823759b620f7593079c0000000000000000000000000000000000877af18cfa0d029e7c9a5833b346c7fdec06e54d9641d3953d3cae0e8912bac7c990f8864c2adb6e576442c634865b6000000000000000000000000000000000331490f42993de3ce7cdd53afb4b310f25881710a23f601ed95961bad4e9cbf57d3077803908a91b65fc32d1a84130c0000000000000000000000000000000005aec079da804fc572bb8eb867acb93a24ffb6611eba920d2ce799c4c80cd8e73b3cefe989885167ab685365529b4f2a8fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f3000000000000000000000000000000000ecc7abf4f6f9692cf3a118cd01abaab4754c90d1a59468d402bd699992800c2994f47b2094878604bf7825f125133e4000000000000000000000000000000000662396427cc596458e880bb8a43dbe046deb85601e3c64556990de36e8637e8ea3b142a8195762910a83609ca311c3a00000000000000000000000000000000198b9c52be68d073910f5b26bdeada9e9b308e4541561a8ffb21fd8e69ec9d93b01ec966fba65be27ee53d4857a43e120000000000000000000000000000000005201975985cac810248e333ca714cbcac0ede46bb915d8c857837c80c805898d0f9ca0940819878a26d269faa02cb86128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000004e61ad4818ea3c98ed3c0d247798a1a6ca6bcb35a1cb60bda9394272ec092c385661ab93a42af439f1b55ee8b9c0cd4000000000000000000000000000000000ee0b71ebf39e4009bdb310fe3b555cdc860abd47a67bf481ab36b5ed0c00bdca8082abfb75691d45e10c2f2d777be19000000000000000000000000000000000e9582e3b5bd580f3ca7ca1f58e39379918f2d04b82b418a91e133117a9703f7df4aad30d48a47e29aeaccf5b8e33559000000000000000000000000000000000113b4c068fd040cd3300a2d1ef658955b014e571e7c77594edd31968037c1fff241da88e7a88669a569462564e28cd7d45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c206000000000000000000000000000000000d8b0bf633072f19db61ea263a1dbeddb326738396caf1196e31e2cbe99a68e8c70f8db13cfdfa4fc4494e54c1ef28210000000000000000000000000000000009ceaf2a0c63604afb8a903195933fd1ada0e5314255be3d74a95679c7a7845785e22d2c0c206f3afd62110ac9810c2e0000000000000000000000000000000003a135b405f46ae3f5cbdd63f4964cdc5014c9f3405c2062ba17423dcd22b8f2011638d520ce0ec7bb0cb5b03e8ec01700000000000000000000000000000000066eafafe1cea67aa6de267c767f49d4a3fd44c28d45a920fe9b3cebdeded883d8960f5e9fa4cc179246918942b1428d28f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c13220000000000000000000000000000000017e37a2f1c892fdc58ac3f72cc5a5e2b7c0c87333afb06de89f7a84b1267695bcd452925fb2f15f8b7b20aaa85a6b5650000000000000000000000000000000015b2919343962337a41b54076d6735a093190e1965faa33eee800f5eaa43c35f349aaf93f19977b6fcd19360b27edd6d00000000000000000000000000000000161afb1494482f953007038557c847e2cbf84c57c5f5b806e3b0178e71e3238305f733943bea7ad6f2bc290778638e6a000000000000000000000000000000000c27a2170fa584863697292e626e2539aa15f3c8eee65cbf1f1b7ced6297248d059fdaeb9c955437a51cb016d1ee97c3e6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000313a30edffaf864d0f1c6bdafd7d1e563cef434d45e71489e9f9e4cc6700e44991a99220f53f0cf5e7de5f6e4098bf20000000000000000000000000000000010429081ebd2ed6fa07de6ab0b7bd559a26a43df99fca0a2252411b4554dc69821ccf3df1b05114da84a616ccee0a9c800000000000000000000000000000000131a31442f80da4621f7691664e9f8b467988fa039bd086a2d64f9810878b557614c27745b2e821016f648ec36ee797d000000000000000000000000000000001160cef9f5e4d022baeacdf10b3bf9d7ed5e50627a99e29df1be3667cb872b2af333f803bf426314369b490c2eca642aa83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000b20ec53bc643bdfda1e3947b3773d748cce1992e2ae27c6b7460d90d48e08eb9240879a5a7d3dc3189f486706438dbe000000000000000000000000000000001024bae4a7f71d3d2fb8246e82d95664c4ee8bca4a380c293ea084f749911f984aa4c6f266ecfff69c4f57e20c0660ca000000000000000000000000000000000b58472d81a9f16d2fe7af87170ca0c8c51dada62a4b2a713cae053a0066fd268283a785ccf269e05d8873cd686d2f4a0000000000000000000000000000000016b68177bce92fedfbd90cdb752bc332f46fde6673911c016fb9cff4912d79d3267bf629c33097cf8979f2b913c0936d4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd37000000000000000000000000000000001030d5791bd2a27469d242c62403883ca167303d907839e608acd827b4118b752840a4eca0acbe5df0b447d6651e517800000000000000000000000000000000106d65f922581237f779ba3e66608729dfddb2c487bc927f34e5e39707f2c8a82e8c96af68e3257c7a9876a05a1b01d800000000000000000000000000000000115bec40b8fa914305b1d5a85b65f0811517d36839494ba69c929fc2422f7e8b85d78df4e1687ab0087287eff29c65430000000000000000000000000000000018d78a75ec057cfcf179fa2ffa7dba79cabf6525dabd69ab95b23dc8f293aa077e46e562caf447dd0913ac9dd60ec76b29d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e600000000000000000000000000000000023f7736d6de94b08d9e9efb6f32f8c17cafb1e1b9b1f3db6e58df72a451c3225d11f4304eb0d702b07a7966f95a11fb0000000000000000000000000000000007b3204f258c873a6fcb48d0b36c98ed5f99b424cf4f92a028174e0e93db2af549648ea95fa8c7bbb42b2a10eeceaf8500000000000000000000000000000000165d6e769b7df91374dfb44e18d43e03ae12ee10d8a618a20f67332cf96492ff514eb7de06ea53096e823770c686c32700000000000000000000000000000000012e69ca1e106411165c06ca15988362de583c4a05425e2f4aba4c14cef6d8d04c52c87b4fb26b1557801f55b02ee8ba971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e4000000000000000000000000000000000e8e6bf1c8837c31446959242285e9b85978a5349e1f0b3447e380a7bcd6bce758bc6192cb880f9c09d6ad4a0ee36eea00000000000000000000000000000000199b361ad0b435d7a66b46a43d06e5898376a6c260b68c965f7b186fc75d2f321bf883646e7551eaba03181907d3aad1000000000000000000000000000000000a76e3f399f31cddc4dd4bc22187a68fba31fe2371291ab515d22730d320ae4240911c755750f687c7d26aed09da4210000000000000000000000000000000000cbc8dbc004b9253ba91b2238c92bbf7883360c7ce39f6e15592a8668654950a3fc5a94cfa97f5ddc60add40c32a3630659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e0000000000000000000000000000000005448b623604262a9cf1a9a292c36738960e132bcf0ec8e61a510008c2ae0b51b31da25f2bcf0d7c0d4ce15b1d7179fc000000000000000000000000000000000b61df56ef891bac07a873571f68fe43f79438a31038cf8ff97393ba44cc47408e5a6d64e9ebddf0195bf914f141e668000000000000000000000000000000000d196ced22ddf11132bbebf6c85bb3006a194cebca975d74992ecfcbac546f0f25a39ed5d6100768c1f1a791f3604d12000000000000000000000000000000000f727cb947849d2d7b046218f084283e5513e8582229085f9f98fca522879543429cb8ab435aa3dbf01b68ed258d82c112ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c6000000000000000000000000000000001044bcb16b3384a1f350cbd62bae568c96932a364c16b34d91ab9b1035ddee93a02920ab4dbde2c6f254031909dc3a450000000000000000000000000000000004a29aae48210289e5f588aede0756ddf60724b8ac54de5d9159ea834d5da98b7a9d09a6f37bcaeaabc559dbdde58b6800000000000000000000000000000000112ca953b5ba652c715fd20e3b85c5bdfeaa7d577aa49aa4656d142c9c2afa3d8aee151338f59a199f3c0c3f6a430d6a0000000000000000000000000000000001ebc7a17da7809f9e744cf7f13fc437de34d3472f022493f58bb979e2282368f989ca0982098a7c377498f1d8d32583666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000000e7c3d6bef4b1723479ab6724cf7858c221993357b194e5055db96b8168f8d78f72aaa4a2046be17ae9a7eb00695ec510000000000000000000000000000000015e85e85cec08133b86738e1f7a738de455930ffd5073997a1f1692c28044ac00b634b90eb24938cab56e286ca0dfaa400000000000000000000000000000000164646a4767ed69f9280f96be9a7f988d17c187162554239797436a0bf4c4ffa7e4f8387c3d2406a7528c021f56081df00000000000000000000000000000000197b1080bf3ac3ef7bd6123a55f20f1002f366d4efea9e14ed92fd2ef147e2b5d9251a302a85172235438bb2d35943a740a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e40139000000000000000000000000000000000db7cfdfd58a6ce9dbbcd5d65cbf22b5e1a81acc70f1c85651ba962d61fbd7ad83e5524fb9aa019c6bd75dade96f7d4e0000000000000000000000000000000011e269a390fd15ab1d52d38de78ec97eb6202604fab02c4598ecebc7635ac91ee564e751275a485fb43b933678f11fd8000000000000000000000000000000000b8597a00d2401664405dd1fc7d69786353c86cd4699af981fe869f266f9087b00df22a46ac34883173bead870798f650000000000000000000000000000000009117a49b3f2a8a850a0179b558319bdd19a5f1f4a45af0ccad0890e63b222d028536e9bb612093cb3f1068d262af90d662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000002e1311abb9df5e4d76959276b6f725f13728844f8c7dfe5e25469cb95c6937a822282b3baa38817e24a6219601132bb0000000000000000000000000000000012820e6ddc50e19a8f98c15013ecc38901a4ef8ec2b79b85c7f7913da24404afa1c79045f1318cdf271028126f9420a5000000000000000000000000000000001794e653c5673e51a3ace631c1a1265dba07fb74235506b2149d42b90eb16afc26ec0ddc54d03f7ba2dd6a2503971fee00000000000000000000000000000000112479bdabb9dd057b325563c666910c01ef66adf47aa32f5a41bc9cb8234750985c266fcc329ea3704e2b8d9b15bfeb59401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6", "Expected": "00000000000000000000000000000000168c90045dcccef35cfe8eb642924ec2629db886366fd9ebc082019690d103627865f0dc39ffdd2167111f68d8d03c89000000000000000000000000000000000b6f0928a32672983664ad15252b3f145afaa04f11d5f43a6169a2fbdc0b0a04902a183b25e38987c45579ac6d11011f00000000000000000000000000000000195c4d796989630f85df4594eb8353d44bcee76d82b73ff7a57069466337b49b875b3c1418d22d79716ffded7e704a6c00000000000000000000000000000000032db644ff8ca6a3b1ac7bc51ff783ce0cdb7bee8b2c21dcfd3adb56a3e210390756211f22feb3dd4f706e13e5cc163a", "Name": "matter_g2_multiexp_38", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c675cb5e90e45300619be91c752a5831ec47b4143c28330422cb57139882e776c1c5f000d6032cd87c16ab3b1c08ee0000000000000000000000000000000000aeb4e78724d46a55e5f101564bc05e0a1be1d80a781ce8a19607322e82c7ee59db9f53ec34c70bef0766a5b965f54b1000000000000000000000000000000000933e8d7c2420cc553afb1c88b5f64c7a39f78272b34b5611972dd5ced3f639ae2ed2aaba470abe829be6ca6d666ddaf000000000000000000000000000000000ac0a9b46323ccabf4b2024e3a5b4717cd8de9ed7de8a78e33a38037f802651a4b43380a746890d93289d547d94b61bb9c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c000000000000000000000000000000000196597114fbaefb8108c185a85d0fff0f6bffecf056902b22d61cc70b49a747bb35638f5b663830a8d2ee15df9fd5a1000000000000000000000000000000000616ed44a5fe69da994e2ada03a1e09065964223333229f5f30ba7a452830848f599ee21810a95e3659cca495897bb710000000000000000000000000000000012d0631e524ee9d3c776c79137499f8c9fb752ca93e92497d89973033d60971da23f672f140c1a753b4d00d08a00babc00000000000000000000000000000000111159e95d131c8cbe8df75853fe9b3f24013daa083e57c5b716e77f6fd3872dcfe0156382c9d2778fe886621be19973ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000a7d253487591fbed97381b3a430404b87aac04073e5931ee0bfd9ea6e0d38a41090c6dc7f6a591336fc58a97a3bea8d000000000000000000000000000000000647c67f1816ae6fec39033c3169eb1ea89e5e20e755cfdea33572d6397e7e87635c7439eda4912361a32de313893206000000000000000000000000000000000e0cbd54634d070aa3c7a503df1171a5cc435d050def17395707bdf7a61cfd539348ee5a4c29c7845cbf0e5df0531f530000000000000000000000000000000007d006601dc1e092a616eb470be35b5d32742dc6a2a4d71cda8865f071dbba9d8a3a8cb10b486253b1633e4590e716dddfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000c502a19770a892b2fa1ba59900a36c0ed054a8bfa0c4e32bf471b90d0da9edca6c06b133c8f12e233b104262a81dbe00000000000000000000000000000000011801f159086d07833a691182595a42645513d316c084b2841445c4a63c6bbb402664a9a9a100e8d6436337ecbf398bb000000000000000000000000000000000f2b9bfd8ef6286bc41e9f47ffdd3efe437aad553c9da02b3c22ca04b5578d634c0543a07bea966bedf345562218c2190000000000000000000000000000000010be5ffe0cc9f580c74e027aad09c213189fa4b7aa92160ce813b8d398b2e2803294e1a730cf5c891cf1546c6bc91414f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c300000000000000000000000000000000132cd5220c125759a18c31313592eda774247f97b5134111b01ef28dad5c3ba4d3f13d1af9076d663f7e217258a6fdaf000000000000000000000000000000000f06a5b03daaf8f92f9a302f06413044ca0dcf2be81d9cf016120312fbd41b273650fbb542d419595fd2815a809c4b960000000000000000000000000000000001b11acf12cf46e40554a1d6a833566cec1b2750f3f72ef77496477831d5933f477d59463ba19c03dfbbbd02fcbb680b000000000000000000000000000000000b2aaf91827ba923c8a1c2fa1d6fb92384c9f48f8f77273056b94245114d1f3cf66fdcab330673ceb2e9dad6c1aed0d4c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000051858339be99d1271152bb390e9a2ec0c0760b7686804ba072c46db3cfc4472404a9f87d868a28f2aef16c9e989d6e90000000000000000000000000000000019a33f21d0bb8303f540bf26816f145360bd1e9a8229dbbe7981f1cb5b099e814f2691fadbeeed8e4c4b772bbd27e60600000000000000000000000000000000073eeb49aa7e601732dc0888ae6b0f5e8dde3d97b818155221f5ab8c599eda75b25c86f15ceecafdfb9ee4abe3419e10000000000000000000000000000000001507073b97d494de26e70f18bd1723d931cd2a88903ab6da2aac3b80fea78ce75caaa9b99375780d759fc4a1683950bd458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013b5422deb0e80bec71309d03fdba007eed33c3ce0fc6d4f9a0d063136b3b85a6fce90ee59956a9b91e1caa519f813e8000000000000000000000000000000000829a11eb50f3bb1a47b72cfeec9d1f63e02b9f7b2592174c481ea7b72a121645ecb36b3d1964b082bc6c7efb4483a180000000000000000000000000000000003d3aab53814f55fa97285af2dc6d32cfcf5a08032d2c15ac83ea036603e08a53e0d2b8d93a25dd969937c113e78064a000000000000000000000000000000000c938a68688138149cda64f168ac1466c401196eaaa44a464d9e345c422948767ad1e25d1ce4cc5996ac5d5dab61516b804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000001ab96f0b60213855fe221fdbe2fb22da6bd6cad8bab8ecb747c9528d3511976236ffefb34afc462abfde13a99503cb900000000000000000000000000000000182fb121778cb002be3f90e2d6837a406edbb609bfae8fe59837aea6f5f6131a10791f92188958b57059b7b9a9d3a24500000000000000000000000000000000159cac269098d223ee6d145a4489f05875b6a546767c023dbea62b3cfba9f8518c9f4d2594d00ceac325f3d8ef551369000000000000000000000000000000000c0d2e4e7aaedec7e53bfebe8f7fe5115720e58768469b6673cee3473b08fb8cd1ebd0514689ef65d78d008889e3ed296f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000079c40bd7fd2ce0f48806dd2e88850ba988e5adb0cc5120977da8110b07da264318fa034c0c213590a2616f0ebe40f21000000000000000000000000000000000905f41389be39361fbfe7641394d30870a079f230dacef89149fdcf81a4d1e0e10b9fa1c0c3ecadced9aaa19fa9dcc800000000000000000000000000000000192f50e08e497f902403df40a504a1b4b82f1957572a9ab7ef97f5ab93c6fb876d8b08f318244cba95ad5200fc2a6e34000000000000000000000000000000000be7ef45a14871dbb344a69c4036af4f994a22ef14540377d1144a92978a23c2d678cca47cbc18e8c036714112d11f7cfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000018d7410f0105ff03cc4ddd87a6e0b65ede4abd4609db5ae53720851c90255757e63c6482de4651eb1d3669b1e1a2f8d9000000000000000000000000000000000d4223be106693a672da890b64d2653135119983639f7052eb32051c34113022080ce2355a93a2f64a75d8e0578b2f95000000000000000000000000000000000764780391249d0c987270bd181a44f6260ef82eb00c06585db7ef09e8b069e46c4e0e659a081ab0fda491534b71b0ba000000000000000000000000000000000a8546031e6466ae43643462b7617703a63841d6d4cb0c09ce63b2fbe2c2ba7cc35367191d0313717b1daa665bcb54551aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000001922491dee4e0f29a1dc090c9b48fe8e6d70c3441e532021985932005b22cedaeea7d9ce1796808d756b740ec63f8ca80000000000000000000000000000000005b34dae0e630be6a59ccae17b44eab4e7f10be2ee700bea15f9771a724f0979798617e129540901a8aa023630a446f800000000000000000000000000000000095bdf612289258b31cc79188566ceeef6fd66858b4dc060864d378cbbb69f951e9c6bfb3d1384014507ff29f9446f410000000000000000000000000000000019f06f11a833c06c1c9227255e3a1d74172e73b06675c547844065dbb909ad66bbc150ba396fa1ba22b7183c0fe80e96c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa2890000000000000000000000000000000003de8082f828ec51e23c864a16147546ff60b5fa71897ff4c120556af5c6616bde96b6e53fa673cd1f8af503070bfacd00000000000000000000000000000000093013f75b6a19b5433b3b5ff044384ddfa258420c80fe81e0424e3102cbf9e550a946e56ba9746423ef745e33da51e7000000000000000000000000000000001227cfc3e9a8d6a71738c514c05766ed4f1f4605198f5a3ad8309c0a49499e4ecd34ba1ba7677d6d90203e54d7611807000000000000000000000000000000000a635221d514e58170ef299eb7f5b679050ee24c589cc7e348b2905a3cd1b7bcf2010cfe168f5aa60f4bfe15e59b4436e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf0000000000000000000000000000000009e8450887137cf45b04184b3c6fedac6676cad416a7646e9980dc99a6d6b62164dbdfae7cc20edaacb84432627e6e550000000000000000000000000000000002acbd87ddca9dc775da01ae026f1c60f1cb5974ce40caef80cb0d2eb7839777c1f61eae0472c7568ec9d0ebb2ec7dd20000000000000000000000000000000017c295c458a9dd995d848e3ba585f8dcdec4185a953e4b8e3ca760eb3e815e39a8ff60416e1e6f974cf7e7b086ee4baf0000000000000000000000000000000003cd8725e1cadfbd80585bf5a19e086abd631d6787403edb4bbc785d1a81f6108f451ff642f4df17dfcf94dd6107352bdc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e000000000000000000000000000000000b3cc99db523b3647937b694fc23281a74010079351b2c7d1ae4cc9167917f06c06e627c4ec44af6b09f2886ddf309b800000000000000000000000000000000001e2681dd123994627adc92e6ddd3ffb006521d8bb03040fe1989e4f709e4797d143cd0bb749de33c8109933c709e970000000000000000000000000000000017df13f532bc9894be932e72c609c0386d32390dee95dda45821bedbc1067043d46007b39b6ade871bd36d39a17dd04d00000000000000000000000000000000162db4d1e956fa5b5f9ef244dbc0c6d27718eca7dcc512d1d7b97bbfd2bd00cce7941d1b9a170da6341891773a729e9ae4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a000000000000000000000000000000000f0e4b606ba0a175bf57d4478aa286640ce4b5507f9f9e354fd96c45443333f6889a93012d663d78956bbfa7c645bb9d000000000000000000000000000000000d85dc4d733f0498fcb10e1e814eb61245203d6c1a46181e5a388fda2680640a1271a68d645f8fb179c0dc3107fb788500000000000000000000000000000000185b02140f6314cb62bd7977042ffaaec41ba8788d356047488004d609ae680c2f0cdc94e59a3cf90b6651298b6a81d000000000000000000000000000000000038ce717d08d367a9f882f2241ae4cc0e8a31418498bf68d05805db2e162d053a10dcff85403dc473598089a78dec27e03f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c", "Expected": "0000000000000000000000000000000004cb919a72e67c31b3409c28dca1d57833a5066c240d2889f5bbdd5540ab2a49484c2462b25da197ec8d93dc8f26ea83000000000000000000000000000000000e1ac1dfcfe22ed7ac52c701a7221b542ce72bf59d62cc49f95f8ba64c05060671098d40c83947dd1952494833a19b55000000000000000000000000000000001331f6ed8ea5ec9b9e1a14997c2c9bc9af2ca305b313e2bc5c5bd35308b7b451a362f8ad61d636dbf77d1b2388702d8f00000000000000000000000000000000186b85e656e45cb5ac9a2a2009353e45749b53dcdcdad4f378431a0e4a317652301f834617e14dfac9836c3c11512aca", "Name": "matter_g2_multiexp_39", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e6292b4d3031fcdeabe62921f0c562606b1ec6139b9c43938971d7851da4945cf69f39652425396ed1b2e70e65b9f55000000000000000000000000000000000e94bc63f3b8944ea6bd7bab811c013fd61303aa7713619faab85a271308bb220e2a94b26f5c7e4136a3d2761dffea610000000000000000000000000000000012313ef65ba41f8e0a57e9b810c13d23241e8430c6ab967a1a9bf5bd6308e89c135e00e789a5610694d146840fbd877300000000000000000000000000000000165ce83af7edc9e701eb57b332597305fedf4b939f3a13a95a0bb3d119c2a9204a4991388f7fb344ec8f15d32cab0eb5eb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea0000000000000000000000000000000005a88477765bbc8290b7eb137e6de78e62bbd929ca511cf0aa701f926440f21d33bcc6ac8f2ca5de57ee8116c685ba38000000000000000000000000000000000738074a9365c707190f882780b27dbe96179224103392b86c628b601e33b092a03e24a89bb6d1d1024862a9df6fce8d00000000000000000000000000000000188c713945046771bf852155ba412b4222173b6dec8320ffd1c59e9b36943c2c18b0dd3bd551b7b1367dde3e8031201a0000000000000000000000000000000017222294bacd664ec37e9b214407e5325eebe9753b430589de2eea13360783be52a479e2b0e9c5dc4907dd5f06a7fa822b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a000000000000000000000000000000000c97299d7e18f41e538b91b75e962c3ce4e068202271b40469c58cfc477d7820e90a0e91d647e8ef5fc0cb822daefd29000000000000000000000000000000000bd1e11a3646c499a240bad708f97a49acaeb653aa5bafdcaba41c1c9d32d32c516c94a3db8816e0a43d1b1eceac7243000000000000000000000000000000001223ecf82c4622653ce84460c39afe8a967cbd87a2d75cbee1609161837c15b522480c4731c9e6de9c5c392ef1db18e10000000000000000000000000000000016c5e98d3d17c723548427868e3e6d7ef4bca339e41acef19e0710459bd4732de4a556b22cbb49b823c4ee656fa354f1babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db000000000000000000000000000000001125735092842142cb5387f1ef8fb69a3535e1f0ccce59661183d3104ec1ef79dd87a7fb36159bc67bd73ad403b46c1500000000000000000000000000000000162caf579539574199d56f4e756f1532c66278a55b4f67f4f4090368260f46023543a8a18d49e8c5783cb65f93d750480000000000000000000000000000000003accbc87996a220a625e36d5cdf05d8c16fb353068ad819f94ba8223cdf6436f8d822719153bdba620a07c5dd955fe5000000000000000000000000000000000b53c8a4b62466c998327e0c5ad65818ea383650bf0977d98a8a94fa9653fba276f7781af9f5a4e99052ee3ae65c283d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000001264608a59c0ee9a26568cdcea8801cc8cf6616773bcb0971234b2d987563270c7b2291fa035c8f2069ac99e16c68fc0000000000000000000000000000000000e839d8d982d6663ca4552527f4fcab6ad5e0a444e7b5921055c774871601d342a151133ae15bc76c023b7ea643182ba0000000000000000000000000000000012ffd0696b7e29b305412fb840c596b66b77ac2eed936fdbe0562541e4de6b3166a9991dbdfa0f79b78b4b86f11291de000000000000000000000000000000001777ece357f82d7303aa816237a0dbd3a1398574f4061dd2fbf6b32af38a65abf5ec9bc53bb8ede932db9cfa0842d53a1a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000002b41743325db9550c3a84af80bc20c54b8b0b685d7f84d05d14dcabed2f450b91675aa8c5c650eb81151bcfbf1603b4000000000000000000000000000000000f3d3e69d475fe1d4259f18f193cd84a90b91589a6502588106f0a577d1c1dc4b2feeec20a4fc30b3e403d6ca9e03894000000000000000000000000000000000c10e2bd1335363fe958eb50981b99bfbadfd1c414830857b5257bc8fa6e26b50989d9adb5b3a2fa610b3151f8754309000000000000000000000000000000000008c825371319f4ebd684f76b567c4e9a389dce96068c101568dc8cafcc10896e3c20202b591a344d9a1c1be02310be9b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa45300000000000000000000000000000000101d3f92fe64af93468229608007f50e3406719572acf265fb8b2a7051525a9cb67cf2e46fc8e098cf081e73f3b20c770000000000000000000000000000000017b1422f8208c2521e3896820b22a65bb2a9b47d7fdcd2ce57196123c1ce43c1db6d00f236d7582795d00ef33ad6d585000000000000000000000000000000000e261500a9c64f5ae107d6ccb57fa9151f5321ef4e80f0e271515f1eaaa5e3714c59bf97b39acca41b15d90c0505ba9a000000000000000000000000000000000c08c955b6df18444ce3726711d29c2088721fa0aa6e317c52a05f73ec7171ef8bd61047174c74afa1dea804c68a28e33b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f646000000000000000000000000000000000985cdfb3934e0484805a1965984028d6c459654a3eea6ef66e867dfc737e1bbcd92e31020d5a4ddb7f8091cae2371f8000000000000000000000000000000001998c5682209153a261bf981e16bf1f7a6f8e5e566c1b0f975253ea62439e5b36c5e5060751f21941edf0d348bafd18a000000000000000000000000000000000c8822c1d6412bc45fea05faef33c65d5a6dd13aacf1279b9cfda2a2ee34df3146d45e3434ce8e5f242e9cf7d3ac27180000000000000000000000000000000019191b51d6664a3047aeb5590df2939b2cbb115ded70fafc2de4c2e8c2a955a957375314081a8838bf89d5a140b7b915f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b00000000000000000000000000000000156fcfffbf01ff3c8a97e7bd99e59327d38c6f7f1083d068ae158d1901808b3c9ac96f95c2bcbdf5f74b36dd8ce58d7d0000000000000000000000000000000014c64256d1cce124c01fa727482caf8ccf007e4ae00e5277d984f31a11ce584e7633565c61d47bc8accdf7c28bb266b200000000000000000000000000000000052dc9f7fce4859c852d3d9e1e77bb7887ffd35d4d550726632acab3d4303ecf8b3ec7f4114dbd590ac20d748570899f0000000000000000000000000000000017abd1e5dad7ee06116a8131c05c9b48defaa92efc636ee34a2970d701c02b6be0345a58cd8749e582ebd105c02f10a06440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af0000000000000000000000000000000018ce0fb077dfefd57f7943d432e12dc9bf92dfaa30f8341397ff8906b1abdf0c02b599edf85ba1e5bb6287aadc72d7a50000000000000000000000000000000019e5e9e3b0632ec10a26b7c1ec40248a9a8b230806c38aa24e47489a8aee5abb5450f6e5679e3f13c6ec7a79560689050000000000000000000000000000000006e257a74f45142817ea8044f403e98c99db8355d626c59e1d11c6859eb0dd1dc8af389f07562259c1f85411be6cbfe2000000000000000000000000000000000f463e345b004b1364894c6e8ab5d35bfbdf6b7544a576ed6b5c5398ca2074f67e2d80af1ff5b721fc126d3afadff1ef43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f00000000000000000000000000000000174231581338fc8c461c981d4949d18f5b753d27184ffb41568f11e178a271bfc69f8c73f2daed0fdbe5bdc7fdf8ef56000000000000000000000000000000001532474399d6a73501801e5f3fbfc6f13bdaff7a3ea7634568fe82745752ee15af23b16809be18788d295e044e29c05a000000000000000000000000000000000912eaef94ab1f3b3257b26c5e8bbe3f99eaceb8c7ae8da577ef98e24f3308abe6e6005ff674a2af01b4242f8ff87108000000000000000000000000000000001925cd635d0ce770f4925a3117721e96c316dd96708b096901ee04ce02e7b357428e4364cd488eeedf76352a26cc1d10a40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e0000000000000000000000000000000005c4a7f26ef0416f34750badcbbb3bce075606435ee7f69b3589e21e37491f0b4a7a98c825ec222848f5e29618828258000000000000000000000000000000000381c5f6511c9f06ea1a76ca84adab4a26a3cde13e0825b3d81899d6ad3191628894d0f57787f854aeb9e4c57fd15d32000000000000000000000000000000000bd706a5b5ef0d4ee1b679a0af90c217ddf9242b7c39523c39657962952dc14e5e07d02154e05693bad08bfb24a2b19a0000000000000000000000000000000009f28a84aa5bd39eeb09f13fc8770fa7e2e053b6f5d7e6021da77f48b9c3807ad917ac671de88b28dd343c2847c5e8eee03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000a8ff1b15ddcc3684b4d4ecfb53473497feb8a04660350ab84e5719fdb0618d61acbb555174b0900b32341154eb7bec9000000000000000000000000000000001464d21df798c0242ac6aaaf3c579eb66eb8cd53eb1e5ab2727298ca61ea8ca4c7cf815bf5c9f94c2b76bf659a4e2da50000000000000000000000000000000003a25752a4360c84e9353b7f1ce74d5106cbd637ec5ecb03dd0752660fe5c7622fe2d0475a4db98f785307c6961f14b000000000000000000000000000000000163601a86f02900d214ff8fbd041934189503438c557138b6ebaca8ce3c109af50ac28074223fc81d6476a3a99559ac565cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c100000000000000000000000000000000189597e6d618a20ecf9a87cc70b3e0eee69ffa4dba75056ebae93cfc3c2ebb368532b17d9f6c06f09e44d9f101397b2d00000000000000000000000000000000086ba610e490588e9385c8b6944c2bad1eb03058e927fb2f9740dbefb779bdf669a51af88b45985e8345b8cb168c13ec000000000000000000000000000000000db8b9cdd4a9bcfc9f7de144da0b33981e4dd53744cd260c4bf045d643a4ef5f25aa19edab7be0c7f8f5ab74a4b7f1820000000000000000000000000000000010198384a646807b16e2ed9186aed99ca3197b05964dd0348086f446d3ebb847907624f4e02f71a1e866d17a125e07e93ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000a293f07dc3f0da0da4bee671951175a4480a719d44cad3d627878ad2f17596f0dfbd6f43acc7a1f9857c5d1f453e5d5000000000000000000000000000000000be6382cc7a00d590f2aada3b4b75f01f8538caad2ade90227ec71e5661ae353e68789807a13f28b23b17dc0dafc19b70000000000000000000000000000000015a9ad5a6f1a511ffe1891ce260ce442996fe4d8515ca593e3e869cab9b18af57956b1daa43aec98a0281143b0c319fb000000000000000000000000000000001807a4ddb73a9aee58b54bab2b878bea8429cdc91384c8fa533a8c9d15c966350e892bdfce16d37a4048a763cbf25d71e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a30000000000000000000000000000000015aa6b865796f88ffe770bf25612ad27942213131c566a446dc149fcc70a018230f1cc8b20461ba2c55300fd27930bb0000000000000000000000000000000000c39c4f229b23c0f65ed720d655121eab50f695864959a2aa49771b848730494d14597eb85ba35743f64eda897f95917000000000000000000000000000000000ad44cafa754f06e45dfab801998c40e5a9f56e4add5c8add1d7ed9e05d12459f2efe3f3367cbcd161f524c714f7782b000000000000000000000000000000001437b1f1a1399ce2a860f7c6517b14a2db264b2602c1c57b8eb04e165205842b483497e98e6b6f8a62e25ab8b0e722f04c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d", "Expected": "00000000000000000000000000000000077b81fa5997de07738e1b34d8e17ef4a9bde516577a4290253cc759ceaae745e10a457204b9ed0069942e0b487d106e0000000000000000000000000000000015e79be67a752a46dd0605e9d07d738c5732b2b605457ce056deaa1f7093b0bdc91b4c81c4c5462a51bc713a7fbb86c3000000000000000000000000000000000cfd2e6043263bda2b97798e1a7dcb24c63aa7197f2749f92524329e97f93dcb56429d82c1d63c067d7ceb38e6c65b5a00000000000000000000000000000000026f352d2f93e6407c59d58742dbd91ced464a3746dc1ad9059e6bb9c146dc1e74235bd54b1d90bb3399865cd3102f3a", "Name": "matter_g2_multiexp_40", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001387fb972f997ed0cb97a5ccdaa759dcc3c2c7f4f15e5cc4fe74685e42cba75e778772d795847b45f274d32cd4960de600000000000000000000000000000000150b1ad31a3d434c1cbef877fde2e105d4a047dc34e3889d21544c2143e7b41b8e0024443a774bd1e09438293860a43f00000000000000000000000000000000065033cee91f5c4d429a074be3d2a8b001892455a11dc708ea73c0082bedb1cb8e8b567a6ea68a1296ad2b80e4b5b08f0000000000000000000000000000000001991ff6fb57e8cbf9d228f1a99697f785261ebce9d3c1f592389fc860b8d7a069896dd48debb8cbe0c43175cd2ecfff5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000000a0fa2d39d868737b9a0526296335256ab4894cc58ffd80bc6334e80d1314bdf017c8226b41ea135f6adefd07650ca1c0000000000000000000000000000000004334f7985211061dedc794ee8931ded12acd39d7e6a6ef44a749118d19ce8204d07935fe62fb2a8ea4f68f99d7c5f5d0000000000000000000000000000000018850a3fc8c851a06781511faaded1ce0752e7ef66da82c2464eccdf78c32fae306da3cfedaf76dad371cfbe012f2bee000000000000000000000000000000001296ca0b0e368429b122537b096fac77d6367988956a7f6cf70c7193b7033ce42fa0cccb8b84b9c78b16a68fd5f4c14c2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000019deb7de7fa5254fdf5ef34fa616651ec70548187fb0bfae9f512e0bfe1f662783f06a9a99e434ced84229deddab9d240000000000000000000000000000000009c199ef916e6f6fe0677ab07beeff221a5687fa8da3ed3ad99a950b7f27159f857d1b561006bfffab551d240b764fb300000000000000000000000000000000148a211fb58b38072cf7c417c70d3ef92e9cbe22b31b2b626198add01dbe1ccfec32d333abf42140b9316312ac48aaa2000000000000000000000000000000000b551b57045365d842133e46814d5d0084248904960f8d2fb28e9623660bcee658582928703f86261cd70e95cd20cf3a530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000185959a297a8f434cb9529a1f7bf9009fc1af3d09efb0a9dce1b9e7d30699da64e4b1d32cdb05b068621db092c1eb59c00000000000000000000000000000000106ef21e9031d108364e93ae4b5d21b0d6d78c2e86e0f8a7af27ed3d38dba0192954e8c716665333e5dcf21387d3f2b1000000000000000000000000000000000185d21efd7d613c409b6ddaa66eed70c235440974b2a9154f3711e3969061461f8824b4547c65e9db09ce875512ca2b0000000000000000000000000000000001aa46b22451afb12962bec5c6309feeb4acefdf3c98c1ea14275409b7111aacf7c92a8e024d01d4dcbfb1c91fc445a1d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec049913000000000000000000000000000000000557370d81bc3da4c50980106b8e65ca2edc757a475194cef201c9edc0f50363cbebcf2750acab0b67e1020daf5660e7000000000000000000000000000000000462f1c1379be9bfed97a1a83a00428de63eadb6360393ba162af3762a99d7eae8549d0cee218e469e4997ada7b35cc00000000000000000000000000000000008aa5ead309fc703f6de980dd43c294530cc2b38b94d5281e9cd9b0d09f82f747a7107b700f1437f3abe36c01bcfed1b0000000000000000000000000000000014110a19d574f26e11e2163a981c3388c04854c5693e9033a474f1020d5f980666d84c60370950734c46663e194bf0ec42a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d60000000000000000000000000000000015beff8cb3c79098bc73dc1ea4b240a4e0d094b3dbbd51592df6adc9c9847beb436ec83df6c55666e296fa843298446a000000000000000000000000000000000943aca2a6e57e9897ec764ee2911d9ff0a59d9e903c70a8494340cef2143895e79d3e6c03af2d6461ca199dfbd0ca0d000000000000000000000000000000000b812ba87c4989af07af44f3dfa87de119fea28ad598cb8e52247cf41bb8bd384c0d8913fc82e4cc2878065e797cc581000000000000000000000000000000000410ed148d1e354653f9d9d17c50026957fb03fec64964f2bee5eeea966b430e77f7b3538d9f4700a673fa07d0daac6b7a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad000000000000000000000000000000000de0a9bbd63c59767938b555c7f9284d0885ca23019818c213a7d4f1594b028965da871cc5818240d155c05c69e4e25400000000000000000000000000000000079dee5649cc67700e9338799a9810d352a5c68098d0676e42e00bac31f37513944dcf47408288cb7f1cba121506a10500000000000000000000000000000000101a650e84352aaf3817b400da0aff40907aae3d2fcf16739f8ee8d5bfc62c2a0dd518201701932728a41134ea3f6278000000000000000000000000000000000f1f9dcc0b55d0ed327f667cebc052c4b6116fde5e3076dd6e447c3214d4c8847885be9547f95f341c42e7c7fa7e2c71bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b00000000000000000000000000000000148f706b4c93e739324e5db40d42025535cd33a32bb3f211add618c0e2022068384a5612da67150746896a2813a664e80000000000000000000000000000000007204ebcef495ca8232078fbf1539a4b46e89506a09dc008da457dee2792acafb6baac4f6cef2de15cbeb48bfd12bfd6000000000000000000000000000000000bf8900e48a4a56b653b1e02c3b9a7d81c2045dbf6297f1ac2acd69d1bf9e06480ea917e3a616243c3a30235abbc426f0000000000000000000000000000000005ebe0ddf4cd1aee76d0b3d03eab754664c8b36fb20ab1060900909e0e0a4abdb45bf74a0b1d40fece9bf73360f580bcbef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d0000000000000000000000000000000007824d1c48bb2cc0f406e356f6e52b66392f6203f49dca7ce03ae6302ce3e8055d071cd812f97481acc654b318d6cae2000000000000000000000000000000000ae89f9eb1abe452efb7ca48f8f939d835f9a79e05211ed9f4abee06b93e34b17d920ddbca3d8bf18b96c3705c1a064500000000000000000000000000000000119ac787a7f3e9b7ee34070aac1a769430eaa8cc838f1752b573ac7f3c02a9f490de9600c856a55448598b149f5392e300000000000000000000000000000000193a3655a80e6e0b1278730600fd4f645d54947d193484131176b890ac197702333ea847317568230ad8af1280864096392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000012f66629836f0f57bdfd9bdeb2c9b7d6d5dc55c586e15d76aaa04aef06722bc8ca156fd1295b3063d738a85b3e8746d900000000000000000000000000000000097825c5db7289b1b9e640d19ecaaa81ee59e5b9884713f6d312604d8ac367634a264c316d73a9cf63358c8fb15f8c5700000000000000000000000000000000181133d027b97d8e2bef308a93b7ea2a35824dc7d01a3ed2f404fbe12ba3b3e51d94ec86cadf3da7dc9ecbaa23b411cc000000000000000000000000000000000a28a609d0bb015e375e74c087ce426dd3c20fbd8b374d3817c626faa81469cfd11a2a4e418a44f4d7ca621d0564bc4920a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000000feca6f7e3cb286090fa3df9c5ebd10c06192fe14af58d46b827acf48fbd462f3f76d9d20670803946028437410ea52800000000000000000000000000000000183dc7085483bd05c27691c25588e33296fb610bddaac253af5b2262db38091650c1c3185d71a69d1a63770f95f381d7000000000000000000000000000000000189f9b9ea528bc2377ca3354fccf440fee059f5732dfdac320fb58541e74e444dbdcdc008c7b47681c05502f0b302f5000000000000000000000000000000000906162085e0e299a07e41b9d62668d4810b97d4be317bf376da537de7adb06de011f5f40af834593761b774771a80e4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c47258800000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0c8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab0000000000000000000000000000000001d04065373ce5d1ce47e00476f07708bb028040edb9ae7e8e00e2c6c460e1ab8b730ff510a25a3c8114c1753b7bf1ca00000000000000000000000000000000001c87217f150694a84a4e5aba8d188ebf7224e76b078dcaba4a91de6b4ab317966ce1a9267a5a27ce556c3386b086620000000000000000000000000000000003c8422590826e0999e7ae3ecba84edaed20fd7f1eba02b9daf1c46c2aec74d5fe63319047d37f5115f243ae0ddd4ffb00000000000000000000000000000000136ae093c3bd55ddaffc2494f3ba8176947cdf2f1ae408e7e786b23b6a65ba8c4131c83cd890386ba531b8637b3b042c8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef2000000000000000000000000000000000d09b92a559b8efe5224184fb4f43779d0b8c8f23587f4f74e2fc6fb1f94e8d2e0d591eb0702cf51a9eb402e79b46a0a0000000000000000000000000000000014ec2e4702f1ee1074cd1ad29791cf4903357e62570d16ac80c5e8ff73b255ee03a5ba070091cb2f984b2139de06a97d000000000000000000000000000000000d22fceaa48193756ce7331952a2d9a8057b67bede729e07cf8422bfc79f9ed2aeb99a9227af256deee9f8a6f227faba0000000000000000000000000000000015d9322c3a5a7ca404259c4cc7cb93dc3d46dd8dd9475756d2ce6fea527642f9230c7e94a804ecb0b4adec7963fa9cdcd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d000000000000000000000000000000000e16fee11affc6714c7fc8fc5e7cce44d8afe645861dd2f0b8e58aa93d4f0de9b7e73020a1537bfbb0e2c8327c4aae03000000000000000000000000000000000b7745a4aaa8ab4593daa61e375d55f9043fbc7385ff229889fca514562168a4e769c5eeef4d564b41cff28b4efdb7bf0000000000000000000000000000000017f6c5b1fb00746b50ee4c7c743ae57fae2742617e5565241d012a0ef6067d9ce59be749a99886ce9836b648525d2e92000000000000000000000000000000000a3be81720e80f6aa0570c89613c78efe95d87ccb374e7f77065800590bc71d23ae097516ae1e97b498cd233221cf717b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000008080a7d91caaf2470f9632575b43990a9523219d75994f1944979ed5b650be1e3c93eceeafb0875f66a40651f4c6dfc0000000000000000000000000000000007a19c4a6340e39230a33b12fe63e47bb0d1378420ec9e439f216699e512e4d70571a1670eaa6b60a5c899ac63360a250000000000000000000000000000000016898d22b2c123003480e3a01965a72de94cdfa39b20898c49e451dcf6a4727a1ebd629172aa1a1aa6897916cea192b4000000000000000000000000000000001217a373c78de9d3005690023b9e56bbed3073f13ca2408a27a3480578d8013fb9d3ee5cda95c3cdd091a5cc68d928da7b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f", "Expected": "0000000000000000000000000000000005829c932c80baa420602bf841ad9bb24fa25c61f33f5d88693207b81271c94eef54bb524aa830fdad8caf8c082bd4990000000000000000000000000000000000b8d184316c2471ec6875641ea83de4f9b7227041922415b38b07a0704d01f2585ec2701bb4ae0bf6a0c0522efc0c630000000000000000000000000000000001dd81e075620914254b38ca5a7287eb56f2f31f6f8fe02fa51488d45c7f4609bcf49972d0ae5ded76eed5a4c096939d0000000000000000000000000000000008067feba36999b58342ac54e48b0fe28245f8ac2498b60093082822d19854df5c3168dcd55ccb6b2cb397b77e712333", "Name": "matter_g2_multiexp_41", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016ed84856b9f41be9bc6c025a9b79e2968e2ee6bbc27608093256c541096e2c9eda1159e6dcdaefe783aa59d52f28ee90000000000000000000000000000000014aabafdfe8c7369f93d5472a9c6c4d426e4b02c943488be993d04ed24aef5477f6d455f82b4af78381b8bd16f42b56f000000000000000000000000000000000af34789c6c923103633e5b1b9fb447b671ab05265c16488ca7224e49db21973487a5d3de4de40b9d8a97ac9b1966619000000000000000000000000000000001123a6601c5351a586f27f8264d4227f5e1df868a03e0c3df5c148cb523cdd178f96fbe52464fdab210564dfc22b29536f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d7410000000000000000000000000000000011b82d78cd9b53b8e7e5c14a7371f34f08546896bd59d1e7d8be15d21742180aacdd01b0d08da2cb24873ce75e166bd500000000000000000000000000000000161ae0d724085a6e801edf73443cca87995c2d6b37e962db5719f4c480cb830e379fa778fd2f29e75173e1c31daccaee000000000000000000000000000000000a2c2b89d00b7d19f2b0530889905c30cecbd4ed0b56ca82208d666e7576c32a6e90cf867ad87f19e4fd367a10c449a2000000000000000000000000000000000b65c0226743b573dad7ff25bf1885e3dec686cfd5da2862ab300fde4fc8fa9b587d0f2d11ebe1f6a6770bcaf2588f8f5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000012d4f299998aa897db9e3194244fdd1dfb95225e3271383b5cc296bbc51c4e1af52e849d8244f82421cd198158918d8900000000000000000000000000000000110638a2f7cdb7104de8fffe29be32610063bc656e13168921501e1614f282bdc9fccff4eb3c479a42b240a2c8014864000000000000000000000000000000000b0adbcbaedbedd376efd20a417bcce562b87b7449cac1e90d44eb05930e6f558b35ef755457305da012a231b5675bc2000000000000000000000000000000000db6fa926c7e02f633730569732fd9239bbacf2042599e79a4bee76619872901c6f4ec4d4fbf3f84143a0d17b167130ebbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce500000000000000000000000000000000166578f3087772545c0f47fe0b3efe32874d26463e4f262be65a3bb6b0fad7d0f779808f69362f3fe63c72f24ed03d70000000000000000000000000000000000a8e61e8193228fa1825cf14e94f68a5eecece9afb48b44871c5ad62510ee1fc4e9c60d5f2529b8685e6aa13ec91979b0000000000000000000000000000000008d25d81bc4bc92508c8cade33c305c11d71a06bd46f184b05dc406f0939f0e0967b02f15b4f7f6984c9fba0644ca8e800000000000000000000000000000000113660a7d2152346500a1578641aad4dac2919ce63d01d8ffa6dad72f524c888fc2e9d2876859859e47d8e884f170f86607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000160529ff217934c85cbaa8b347151539e252dbb502c015e8e45c128df2b8a737866737d5cf0eca6f76e4a16790cd02a200000000000000000000000000000000127f7b0e4f9351836db9c204386a199293955471dbcd7b4ee9186f0434b46dcacd1edc02fb46b4c377c4e62cec10cd6700000000000000000000000000000000094abac17b11600d7447f7ad0f21d98c14e439c4a4a6572b00c90e14d9fc54e85045d0576f74b054d384179afc0a70c80000000000000000000000000000000017165c32410a498add8e1dd55ae43f94be234ba3859fc6b4816d7436746add313f42b1fb49e0cb6c4b7341f0acd09db841c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000060e503ee1c5d3eae4bc0eb30fd86303a5c48c10cc7b4736d17b8774c78a8c97ee05b40d366b2cc9bc7781b1e4a192f200000000000000000000000000000000034e7012414edfc6a8f7b2c6049236b6fb77eb94b05d55b218851fc1e553514e6ad388fac08a24c33bea63ddabdfd8720000000000000000000000000000000004c832477a90683d417a00a698b69c643d6dbf82f5afbb83eb3946f8098d80de6f2d457c0a06d0051315f06e93b5e13b00000000000000000000000000000000048c3339996948974f2bac14d8a6b8430897644ec8e9cff9eb369557003aa2827a4f3fc3444c4df73663ebc9325ff317c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c00000000000000000000000000000000082466944ee7c62788b6fa77816094ea623d03c7aa2af249cfbfbf78eed26a76cff8c23c2295aac7ee1ef8dc84630003000000000000000000000000000000000a8f88adecc3f50d8eb329492f2c031e722f36627cb3b21415781156ef44954c5b8529ceed5978a37ae1248909d38b5d000000000000000000000000000000000e08f628aa014152b50a85bb6eb947d53c596d82c0d03594ed3b64c486b8630c880adf43fb1575b02e4eb8174a04034c000000000000000000000000000000000776844f28958d3e12a5c163dbd039e50df44b1c6215429381790175a609a339621475a5b9a06c3276c9177d2dd2b576c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e9590000000000000000000000000000000004477f153c0510d8e50bfdc2db69182c05d5ae9b94bb1880de239733e380e03d50001378432312b24b5bf0952c38396c0000000000000000000000000000000016663990dbe529a5658f2b3044bbd390ad430adaeffbd5306f758d86bd5422391bfa1d21e88c63300faad55e6a2d1d3200000000000000000000000000000000188f701658558033ce2c41101a611f74ad6d3cd075c195476bd2cd59a1a9dcfe937020737250fe418b4de435f8b3a0380000000000000000000000000000000013f8d3625309767841603329f56686a99e196d697802cfcf31f8b48f9c76f77a321276a0158a22b94e91d6907f6ff451e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd2000000000000000000000000000000000f184d22f3c0431b031ee0ee7ae9598ffb511a2a56f5c9f15c9a4b0c53af2a10d22a311805786e303e234239326dd74b000000000000000000000000000000001062725b8c576e79e314f6a56ef9c41f05a65d7d0d57d8414e2ae9cb1a520b16ede7e418d3a9413c9c1660dd7508d5860000000000000000000000000000000012ef02fbe96f9a191804b6c4a0b65b6024e3e2b1f8cff986f5a950cde9a32ad50d4f7a72804b2d18b93250a63a7ae97800000000000000000000000000000000000b3b0333d61fc46653a7172f5a813d13ff5a48056f9689c78c4b18b8aa3afaeb7cec305d98dd600786351338a2185a651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f2690000000000000000000000000000000010c91e1dda48dc528f618f01abbe01db1a7b6dcb0d47b83c7b7db3331f7156f7b2d0f081458241467b0078935a7b4a4c0000000000000000000000000000000006f87f782979d2adc02e65b56a4906e50430cb4e0913636e9aa0364535c9d7ecd3b9433358e00caa8e90e84b7705bdfe0000000000000000000000000000000004635089c7706cfdb5a22ef643d1a9a5021847646ef01ea559d1b655299b65cd76a73b04149adbac612e7aa756cf30060000000000000000000000000000000002d83d82bc9fd66c558e00547a8c25633899584c9b855195c00eb3c8742d22c601982f244a03f8e0c5c21caee24405481a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca0000000000000000000000000000000000848f10eeba8ef9c7fd0e679767f6b6a2392922092916da8f13573661f84ec97c65717e55c65526cedd59dc1e096f0840000000000000000000000000000000013781974518487de12661bedfca5fe72205c51cab461b5757ff14f319d081e7845cf8e099892ea85470039713e8e48cd0000000000000000000000000000000004cc1a27d1aa88484fed40ceef72e6bd201e5ee276b5ec27624286dee112ece767b37c6f1f7846d71cc0f4042f04dc170000000000000000000000000000000004f7335d6a1463976d9fd86e2baa45d08ec65059b14449ebe4aae99971c5666cdc6e40cf0510ae99dbce97ae8b4598067db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf70000000000000000000000000000000003c1b1e0848bbe37e62f1ebacef1a574400d5048f1e09d935af2052da29140dc4074175e4d6ceb7c2c071331b2f3d1d3000000000000000000000000000000000e1c84d6b20553ddc5ab09049ec488ea2839c5818e31455a7b231cd0455e2945aefcbdc6c1979821a80bb4f77d46e91e00000000000000000000000000000000199ebb31e8800395a9c2e103c9340444c97004186929b52de33cb8d9396e7ab8d5af3fe6035d4463701ea41e341f577300000000000000000000000000000000081b3882bfdf83e67d2dc42b211069a4e93c0f173263f9f20579128391e7f2de70335df949b9c0e9b834b6e574f2f8cc14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e60000000000000000000000000000000018aa45c6b3898a5fa618f87f9a08a7234c1b94fbe38e2297a1f9c7a2e9de0ed83023deebd56560b1928c012c14dd7a860000000000000000000000000000000009ab80da6c519aee8aa1fa68c35bd0fac78b55f88d861e8fcd445f629054325d63cc4241f61e5596dad0d54c94511e4c00000000000000000000000000000000105f8253f37f5538a2c25587fd33ea61fdc744a7cdf4ff23a55e2c66a39040d4de5eeacb7e11c0d2a483d59e7c3186aa000000000000000000000000000000000f6b10cd6522a1e34c87c702f58a07858cb753d67da9625155bd433020775351a9ec4ff879f91a43f63be1c969afe675ed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000001618dd5de43a6bcde91a6a03fcd88fe59d1c8c51d3d85cd44a1920dabd2608a0b17a987b76eb8f5b20c7f1dc0abb383e00000000000000000000000000000000198034b7ab8fb8ff267a52a9423da95bc587eef8684f18639df5db44e50bae7fdea5c5e5ef37ff14937f86cc948a34e500000000000000000000000000000000106d1f017da463176bdf55e3ada78ce70da4486be42dd0095e3a8a0f6e59ed503324565b717b45ee38d90dd3ad13c10600000000000000000000000000000000112d425765fa2fc28486b95e49db63346188fc5a6bd0b7dffa4430dc82703eb44d98d726edfa4a275aa5db5028d01ef530fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000001326581ac1a1a960db1ff2e8b89b1debaae46d1e2d0aa6ffc6c7398f207abb699ac59186ae7222b5cae3abe64cb61c93000000000000000000000000000000000218753594c63ebe5fe503aab4dbe1e944b24138948542c7c43d92ccfeba5854b7bf1bbcf8078d85fb0b8701b8b092fa000000000000000000000000000000000c3ce8c17f75e78a8c9980e9fe125290d377a32ac46411876ef011e169e86e1458ac5e71cb4a446f6c640cceb8d5617300000000000000000000000000000000176966eac1e20586ad2a03b4a1598b4db1d7c66be70b1b22833e4afe0e0b3783572f791ddcd4eb70a88f4acc28b6fc7a980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000099ac8430fa411e74082cf3282f9a456d3826a7df4f91ecf621e645a1abc057e1bcfaf9ee73f149bc447cf4230f2f6c90000000000000000000000000000000004e93d7fedc9e2d7423c9e111b4674a2bd83de28dcbbcc54ce4b324c96318a11603fc9ea385f1c02364ab1f6b5458481000000000000000000000000000000000bbb29d70fba5b12fadb02a24bfe3f6a5362c71fe5f964dcd0e01442781d0462a873501029192858027d612a8572e9d30000000000000000000000000000000010daa9960005562ca2d18eaf4b4bf081f194fa824cc77515c81b2c836627f21b732448f367e2cc1830ad0fa4ceb928e1619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1", "Expected": "0000000000000000000000000000000002a61fead6801f41f2f27603cf34cfb4b917f2f85cba1f9c684995227653c9dde559e1e8497234fba9b2e4c119cbd9ec000000000000000000000000000000000085f73b8e835a10bcb9312931eb08d916d2b93a1da843fa2f4530cdb26e93b5dc95a555dbe8e50ca465b162661ce1d3000000000000000000000000000000001442fff9019b5476c217ff725ad95c92c17b42471ed7bcc121e8a0506755ec279d4e56d770a322d56f56bc6a6b0a41160000000000000000000000000000000017e7710c4639d51c4a79c5a2791101b52742df202b1827192872f168bd21020bd068160a587fc501782c1301c231a0d3", "Name": "matter_g2_multiexp_42", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001213b5d5704c454845824994769c8b300676e75bafdeb95202001161aede276ab7967ea7362d38e97ca1484cf9c342fd0000000000000000000000000000000008c7c1fa04bebe5a1fb8678370563db63e7a10b30747c2ddeb4aabd4fc0ec93220d578b8110c6bfe8a3a6ea2820f0db8000000000000000000000000000000000c4061a295120a00de52300ee625ac46566464e6702489467316e8c182ca2168052c50b5962ff47285866c17d213fc8400000000000000000000000000000000086c153169a9ed1aba10a6cbebff4911b37907d6398c441ad47da17988d512d822ee36f5217355b93c9d6dd8dcbc8e0b119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc0000000000000000000000000000000017929edde8f9940826ed739bc9f59099ce76e85950698ab0140784647023f96afa064aa4a49b9728f496515a0a807e5900000000000000000000000000000000198d98f430384c1e7fa9e2403d9c3d2f81873fb7b204378cec95b97e674e10a1a43af97db0488209904469989ce80a0a000000000000000000000000000000000afc9b5138999bcef35613e38bff4f81cf532e00346f5205405470b2424622826c746ddf0369c7bdf77467dcea5cff290000000000000000000000000000000019ccc05724b3e9966bf918f01312c80e8422b697be89365b6ca00eb31b0bd08fae942e90a75bf9da1b3d264e416060f1f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001568e52c2760d895874527d1ac8597730578176bdcfc67aaf69ccda253f6616230811dac59bc27cc1e57b94b5743cb3f000000000000000000000000000000000a4ddeb8b56f105ed5f47a538052f3d38a23c0ceaa2dea241554e6508f82f47d32415ffeeafe5ae5664c936b78e07648000000000000000000000000000000000b3b335a390aa0090bfd6467d6cd02eea1ced347cdce3c9ed85dd46e38e9f2ae9642392c2875a27618ba8f2c555d5b190000000000000000000000000000000012baa4b29d116eec749353b7658af70d4d216189133db707e56068c8483af43ba86583862e6b39df13b88058536861b9e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001888b83ca28c244a6178377b4ee6844dc916e28c3f56312ecc0e29d08e6254dcda39a36ccdc317d1908303db3c028dd000000000000000000000000000000000f4b73d9316fee42d60f8de402a7d07765508b84d8f2c1be1f3f9e802ed7b0c6c5fece3db95d5287225026e73de98ea4000000000000000000000000000000000f1b48122191e1bc421881de831293a80566b9a7f2c9836f7718afb69592d59d2a714cfddf88945b94fac7a50b743eee000000000000000000000000000000000f1c6b052dbd03795433d7ad122473f109484d50245021c8727d252145e7db7dadc015265d1547f9c748409d74f5aa33f7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000011e8613c3a771a177b4b85f0c6f97a53fd7900cc23566aecbf115058d2863189c21be36dd5dd736f6d0ffbe88182b400000000000000000000000000000000017b2c4e8d8aad0a12fd7130789188bb63a08f2b243c8f7700599dd33d7e176f70f2b1818e56540ab3fa507878d96a46e000000000000000000000000000000000e2b5ad5ed3578dfdffa414a4a2142846b1232cd2de468725283e3f92b536d8ede74bacc236993f6f68a16fc6a7828d3000000000000000000000000000000000fdbf06ae4cfedc462f5913bba9bba2b5c86ecd0e298bf27a21317fe74af6ab15014c62cbfb617356548cf808599caf4b1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef000000000000000000000000000000000b48aebc6525620b99cd83979658a35afa233d17849bd0dcabffcf3b550f875a386b6c0b4ddacf18a23843629072c0150000000000000000000000000000000010432e5abf862d3be10ac5677b9f296ccdcedf1480e45de631b6bfec42f20edf62034f7205f659f11fe5a6aa9d882c7a00000000000000000000000000000000011702a3590e7aedd6948bb94bcc874e0b8d77a18126ed4ba3753dc98953ff941495486c14c6d801c71fca3564ded9910000000000000000000000000000000009faa427c0a7da26c92b451c61f5b5e8804fe032a4cfa014397e430882cbfcff81bb22f9c15a8747ef455773c1ef65b0a41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000061a1ee841251bad461f89c52196bebb1cb4463298e88abd62cccd21bbd325ddb33d1306ffedb2734be76c18d80c8dfe000000000000000000000000000000000d05a5ce6372ce34b0bf4b19d8e05aab74abc1cedcc35a2d1d4db38813d1e5c1375d63ca0e8bbf29c510a4319d2aec27000000000000000000000000000000000dfc57aa8de28745b8d28db3769ab5ea26b5115d3e59e51ff19af8ba37efacdccf763ce682cfdc77685705781c3924870000000000000000000000000000000018c17d87411c4f8e0ca51b3eb4c3765d3846e0d1b75574f8e511b2f3e8c5ff53bf7618959ce18dfd9e4c6285e88f094f63cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de000000000000000000000000000000000a0642094b89dc9c6c7c11c1e57ed542982bd246112884969d424a3e091ec4fd73dd40a5ac116f6c68216fd1e733cdc7000000000000000000000000000000000788c7a63eecd1cbc26ee6b14b09d0a3b7a17a848fc0551d50cb7497bc98287da2d9b898260eb678a8a0f446eef5c6670000000000000000000000000000000017a1298f90022ddff3fbbcca180e3f4da8760218dba595a067287a2473a6e10b93dcd54154cb64b6c078b083b42cd09000000000000000000000000000000000116e999b808dcaea0566c0fbef1807e160612dce91756b2cbcf4883b04a90320a0759bba21b41e6f4d8449b52e52f9a96a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f00000000000000000000000000000000064c95bc9c0e2be48849a349f16713791c37310f71b5d0613cf0706febeea3a56a0f0f1ac6b504524eba801e8b759f2900000000000000000000000000000000007088d2f41fc7e1147b92a2ee7062b9bec194d3a47eb9985ac1ceeef57e1a006571e7247a13dc95afcf9905be57e2a7000000000000000000000000000000000e6a0770f4315acd9e410fe58395ab8b20a08240a6948b762dfbbad3414bfca0ced4ec9da982bc9b8798b60dde78a96c000000000000000000000000000000000a70b53a6d71c83971167afe329ffacdd417bd7b228766851c3b43701a439f253a8659312db7e83a398142fe19332b527d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec000000000000000000000000000000000002486eaf9b743d3aa6a1f3e1174c5f213bbf3e3cc0558d63ce40e3c03e1c2f6e8508248bb649aae1bc92f3eb8118a2000000000000000000000000000000000042b03959b40eb0641d39117f7af50dc7ff048697a57b80723aaca164e2dbc647ffe78fea0a6a4c07671f7db6d5b2dfa000000000000000000000000000000000e141eab29f52b9bd0ee44861f154ec1bd30abd715935a7958a19007e789a41cdb0f4b9cf7b3fac0b0d4d77637b510d00000000000000000000000000000000002cc2eaf89cb7a04d425d878a30b5e2e9858ae0b2a2ab28fb28a6db0c7283ad861bb6a92067e969e5721b43466e857db8bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f63b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000007ecfb753be501d9f9b7ae7ceaabaa4fcb7b690ee04fa1a711a15dcf67e4422adef64a0f8118f93e67f24a2d1a2bcb36000000000000000000000000000000000a459e403d85972f7132641c05bb842416a7135009ff46b617bf0918e65cbbf33f76b98c10d901936e589bdf5de31ea4000000000000000000000000000000000bc6ec31a3ff92b4fae07cb73ad7bfa8423044048337b0ab9add09bf10fdf190a5f7996d157483d29fb29a681ed585520000000000000000000000000000000004c622e2bc606fefc8bd83c4a32f7353123205a6d3716b581c2c71360e5200ab069f60c256dfcb04b466c53cd61fc94470de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f5315000000000000000000000000000000000125688e44f593c5f585765f30e9fee5e4f15247cf33ac78ed1744453385f49ac61128e23b1569ea33d74b207a5e72e930000000000000000000000000000000009d77360ea37298fe971569230159967012c4991255fb5337ca6d58cecc3cd44a024a9a044ac98a894cc97dea161844e00000000000000000000000000000000056b2dd9569f0698c732367cfb217af90a3d6dc15e2555ce0aa845616e4067a7fefb304f6525b539555a0a685f0ec5f20000000000000000000000000000000009acb138abacac351e03f7589d4bf29cbd331e93bf538578ca9466b759ea070931c786d35f74fad42261e2df431fd00316732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000013515b0022ea946a8e679b9c0eac6cd67dbc4efc820f0b3d8984f12b7d154c0632a8d7207747284d49c498c79b6bb5c60000000000000000000000000000000004d6765ec6aa8744225c1e652ccddccc91fff7fa8182931c8648b3d8bd33b2177a9af03b2906da02bf117bea59aed3040000000000000000000000000000000006f1d858c4b223552f0aee466cff35d14b3ac6da35b8f482417e8f597514b065be315aec6662ea5c7784d3a9e2184090000000000000000000000000000000000345eaf0d72b9c11fe72261a2fddea318a8dab92a67ccb9438c11e61fd298a333cc42084d4ca127e09792e346cfe0f004a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc2400000000000000000000000000000000029ad10ed6d6d5bb591771cbd597a3a0b841c2347c89027126bfd1efee2ac403933beb99d08721232ab9b7354fcf9aa800000000000000000000000000000000198400d4e026c2463a07ba5a3974c869ed8ceb1f029bfc7f41b23dd7076cf4a83b17c27ad6506c852cd2cf7c4987f93100000000000000000000000000000000152bbf74cefb77fae8e825443e4ce09b4e223242187f563a236695294d0a5f540f0b29d6f93a54cf0a77900e936e61e000000000000000000000000000000000079f4759eaf044a80417345a1b4029f8d4cfc7e00fc625e815cb7daba2243a97d21e42b42ec968dc8647158fbe467088fa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b89000000000000000000000000000000000cf3148d0c30774104a097562cc83456d5d18643d5f7ad58aedd9327bf8e9450feee50ee893442b1cde87acb02b62045000000000000000000000000000000000011d4037dcc15d0c50337d71816a2b77428b8ddd530bc3b3c8550606229f88286ae94ba03578cbb5bbaf118916dddc90000000000000000000000000000000016160c8ec4e2fb780748aac279bc248b2e2f1092262f86d368d2f06a78ebcd27e929930c8f2be124e9d92dff5c6c6f42000000000000000000000000000000001980375281735390f48ddac9d00d4c6ee7312ed0797333a26a1684e09c9575e57bcecfc4a31b8d9597a8ecc703835e22c4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515", "Expected": "0000000000000000000000000000000019ff32d2901b7732df1a924eb3c099a9d36bf36cb32ab322f46a72d99d81c7942d0f2193a4aeb55cf079a2cc1707c7aa000000000000000000000000000000000193561d0433e1031fc51829504ca70e92e19bead2e0bad655aaffb6b41f5f75d18f04a162db7714f3f23da891ea91af000000000000000000000000000000000d010c36acbfb38d9dc2df6e6e21bd75deba5708fb1012eab23d06d78b1244d4daae38aa4f803d12441d91adfbaece7a000000000000000000000000000000001459ebfe65c3b2c9b2684042bd71201869db1a0248c740a54fbdafcf18fcdbcc7b677af43abe803362b462369237690c", "Name": "matter_g2_multiexp_43", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005b9860b565fc64146020647d1902e2a2d2fb2002b54bf5e21b6601124edf14d6e8836f938843fbd8c02ed8530953dac00000000000000000000000000000000104938181f16f16318d223febec3be3877bc57067fc23729d1f5552099125558cb21ee0eddc32ae0b0cc3555219eedba000000000000000000000000000000000211f809b624c4992a43e78a978ea32accf9e61fccef6bcd05155e52adbf4853340dfacebec9fa87e5417c045da25f9a0000000000000000000000000000000019bfe94a18da9ab4ea744389c17870ac96218d02178bf2ad502f166a3a1da8c14e3fc52038021503cd24042cde8f306d0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e150000000000000000000000000000000004fbb396eac2a1de9953febed9fb6e158a3b5a366f783d2105b562e8143031d7a1ef039e3fdcdb675b3d3aa4f4dcbe4f00000000000000000000000000000000155e23b5b70f1ce34fc229ad5c8bdfde7fb5dc0eca19596c658c1f8c38716a0a7b5ff59ff19a7a67e12760fa90eeafcd0000000000000000000000000000000002cc82cf87e7ac05be236104c1e668b5573674d9bd741f2d91d05c8a11af1f72aaa1dc20c73953fea38e6e069d2a43de000000000000000000000000000000000a7d1dcab00db0e7c0a239511d630526fb120defcd9453fbb57ee328f974a98721274144e48d22558edf25595b8ff4cc6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a270000000000000000000000000000000010c9293b3c58d646a95c620a0e0a7a0a55cf43b4abaa0de1d5570fabca8d97c91afd67bd45aa234273715457da5a2894000000000000000000000000000000001454f8682f3736847cdb3f784a098f7c9e488629efc3820d49b36a2e928bbf736dcb3e1b30187c2c0090fff290dbf97f000000000000000000000000000000000a0fe3c635a81f20258db4f1031589afc8c7fd07f2fe1e5cfc8f3c40d08a958a3dbec537c51be2de99b849e006870b6c000000000000000000000000000000000728876e3fdc42273e8d71953de61dd5c03e7c31ab6ec56fc03cdf55c8f0aa4b4e5c8ed88c23c28568be0d864df026af4e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000c9ba542189ec1828c397ace9639cf2ebbd1613356d8fb26d3c40dd00af1f43f5bbb25032561aeebba7b874bf39cb0d500000000000000000000000000000000175aa6e94a9e42cf809f48f51c48d60e74d61388dd217b55f3f63612c4565357581e5c39751a65afc3b7488caf5151720000000000000000000000000000000014c880d35d1d31793145803182584a8da003b0ee3c29c978b64bbfe4e1da82910a4539587ba350d393e1bf3169c5e4c70000000000000000000000000000000002a063b3fcd77180de632deca1ba89ec4ceeefefe9883ed9e7e06301a268bdf377c3a6e30859e5a39419e449dd27ebf5dc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed700000000000000000000000000000000069edfb8a83760e09726f6d1c117d4bb3e499084b65e1e830ab30daf1625c37f851ac122f9f5c795912b5b6f7907ffe1000000000000000000000000000000000eb6e9b55869f65ecdf3ac46d0ee596d07c573f88724bcd802b4429392b9a56730a217a03286deb5103f70aba7a9bc46000000000000000000000000000000000e2803e1a646bd70c51806b676591b328cb20359aadc8e79d59e7c31e1ce2f1473b0b19f7a34f23aae09678b11b37432000000000000000000000000000000000b3c9fb5a39a6c40343259e12ff4fe5058f25619d145922e1d80c3f5d105a7495dd9a4da329a2e78afc31a87b2c5d5e2f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba740000000000000000000000000000000019da372143e30307a71c7b96ae0703301ed723814a35e270ef6a6b0c57144f494df1d3fa0ac369f59f3daf534070c9120000000000000000000000000000000006521d89d810c7542108de26bbc888482a3bcec8cb9b542db42d5d4af30d6c339a5b4e959da4f98dd6ef8075554f4017000000000000000000000000000000001387d9c684a0fbf615e7023c0f3ff47f4d2c5a9f748f0261656a09b23066c745420df0eb180c9716d6d0743aae7689a10000000000000000000000000000000014271b9d0b21cc69072333a6c03493321b9d9028149d24964a3773bcbe5045875c457aee11ab0682c2bdd44f098f363d80eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd51070000000000000000000000000000000001dd881f3d2063adcc5638b4b3813a30e75fd308de3c9f42e5382fdbf097d5796ee9e03cb44752515b2459f131f58bb90000000000000000000000000000000010f491f4594dab938115343edb47b0087d1cd1bc12ef908e150ecbdb3a54d8dd51ab24a0e10c585f235ed99fbd3172270000000000000000000000000000000019d1665d452ce7fb6bb6da9782a55dcf12a1d9abdfd50435b8f2a1bc5b323c004fad35ff7e9aedfd414a9b68fb1eb1860000000000000000000000000000000013828087beeeb85e43e8540fbdf97af189878f5ddc1eb35c95aa06a26923330f3b8a2b43f835186865d6f5f6afdb2b9b067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b100000000000000000000000000000000014964f3576b97c00a8c5f4372e2501944a1e4374a3c30e11376ea62e09d52d40d428887833bc2f06279b859c00c98a60000000000000000000000000000000019ed533a3bf469ce5b3e4e9035af177efe9e4f8b0a0e5dc9721dde49a7fc66fa31c8b1c8d5bcda1bf75a532bd2be356d00000000000000000000000000000000064ec4ed48d63ab62373adb7898bc904d246bf2b3790c3cd850524e50ec38e7fb4a364344a6a1dbd26f2ad2d0fccaa1600000000000000000000000000000000134aa3c6b72d39bedd8f9c619d206a295cbc05c611147d38aa7304e995089ce34ab1fa13c2d6c6807a88797dea20214b34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a000000000000000000000000000000000a2a4c8b457d0d2554a2d439fd3b74b18843386aaa00d1b89a1c2d8ff7192cdd1d3a888994376bb7ddda4d16bfcaae3200000000000000000000000000000000155a7dc763caf6f0fe1ba9537c0f75d3e455c2d1c749dbb4aa7242b40a9740fe9e8e88af6017e8f743a9e4c5dc6ebc23000000000000000000000000000000000a693da3aee178e2f0489af77f671c734423032f30c0b7b48debd3b71e65dab7db12ab1e0e72d3ef686d6c1922aebbf700000000000000000000000000000000109c3476016884386d6206c94073c628375a02c8fcd3041e06b8b413508188a1d26ec5ddf84a77d059e9a039dc5470d08d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e19000000000000000000000000000000000a07f457f5dee69e9ee746dd67f982914a2182b5cb2609d273d4122d57a32c195270c956361d78eb65449cf5e13907ba0000000000000000000000000000000011f149ce84c2a11ff818be3ff0f86c1b38a9555e169a8cf791c79828207b7aa89c84e8012a0c5d8cce4e89d758b90e22000000000000000000000000000000000d636e5b027e41809d7ec8bbbfd4bf641a56599a63a7678569404ec8d45c3b88c1d2969e6101528d4edf1ee9d8e793320000000000000000000000000000000011878eefa5ee49be83ea1f7a9cdcd4997ccc59a9669778b3f006429e1a22d3b2a051924f371a228856523e3a09bab59b29ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c6986930000000000000000000000000000000019e9a1950f663b258474b24c334bd256d3aedcd26dc971a745857bf1fe007da0aa00777db5c3e5d21294e99862bf8ea1000000000000000000000000000000000329a12fa0add36f259e401442bbe6e5f9139e4a46d5d091a2110d2561b5629211a1c1996f20d19327d1782340e7ce4200000000000000000000000000000000032782c94c6e45a88425438324f3a24ebf37f0be213424b1be52c878985633950a022f57f8d64af1470486aa3744f3f7000000000000000000000000000000000631556d52fdcee3529023cf20d46ea09ab3c642a7f4eca2878e4af88801d21b80b829c9aec9e73317252639c148676c40bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000009eb122c61ec44afb56b64929040058a804311e0e97d3fa513a162748091304233480bbc883f6fb66080b563b308a24a0000000000000000000000000000000007d1d810fb8788b9f0cd04235771d7adbbdf8c6e67e8538b2c6f0f278755cc5e57ad720515ca558412ae1fe2cd40b74d000000000000000000000000000000000955496bdcbca8716245a130fe6eef44d13280b2d56f15bfc772f8ca66a52ca0a742e6bc273c28cfc858a3269f59beab0000000000000000000000000000000000b27aaa0d94633912c96f00ecc021773e5cf5e164e20c7a7222a58b0465e7baec4e67fb56ffe564c7a2904f36c265e61574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d70000000000000000000000000000000012e0572f5c84f6082dd05705a3fae738920ffff840c21e444f0ed002df16394afdc21c249b6f1837389c48719539f4c5000000000000000000000000000000000c26bb3ab52e3bddc219dc223daf472247547544e3a9ccc31123b82000b17ef325148935621edd36ced4e702ade1ee3e000000000000000000000000000000000c13a8f02dc3f209e9abf3d316fa843be9c4dd98ce1ca2edecf757bf2bb498750f6d96c28abd45d9c6cf5b8b6334b63600000000000000000000000000000000157a50d9034024dfc7b0f0db4ea0f45323d76c81bc844844ff9bdd0c13f2059066ec3060210aaba61bc074afd7ccaa286dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001054edb092a7053eebc542f690e03139f2e25a0098c665741e8711c8a6b9582af47e467f74fff9aeea098b7732be72d400000000000000000000000000000000084f919e219de15e7f9ee122383c772415741e5b86be6ee7d2193a4f6be5c9cc9b2fe5e8beba26cd768bf2ea1b6ebffb0000000000000000000000000000000001822b4e8fae5bddbb36f5c226216471862af238be770d33c4fc1ec2777350db2f42e33a7ba468c317a128e8446ceff300000000000000000000000000000000130f704596ddb28ec6e335d9527707a75c97298407ff3fe17d3cba0cde4c21bfcfd1ae46272018c1db768c036f215182d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f42308200000000000000000000000000000000194dde06f8c54de9ab0ad72ba0de2241fef32fba30fd6f5e83fb7750bc120d51c461d75e495cee0d1e85f0f39aa9d3620000000000000000000000000000000010646496be02c658c82dc68eab86a4f784cf64494bd8441f884e8ff384cbb6ff3a4bf5126bbacaa556aafd652397a8a800000000000000000000000000000000109807bd4b6613acb3eb7d386e84166219e52e841c41185a269cc7cfc5f34e9ef5cd1fea29877749e0cef93a3b44eb1600000000000000000000000000000000020a388c668c9339e7aab15d03108317dea97720dd27a94cd3bb59b372b268d1a7d7d7409780bc4912c3f95acd42a57619d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb00000000000000000000000000000000161506c4a2d57c852fe8c3dce63ec6673f05f99c1e032c8e591239616ef4469c4240482ce5985fdfd4a80f54dfa7024f000000000000000000000000000000000486c5b106393e544852c143c5ac4a882c79870363858b2c910ef4041d8803876cc55ef59cd6a41869bf5247f0db2c0a0000000000000000000000000000000000fe765ebf3c4edd3035c7bedd4aec918426898339d7aa004fd74bbf0e3236deeb7d2bbba56c31fd447816e301100a66000000000000000000000000000000001917c9cf16032e22cdd3f87f098a532a33c9fec560a88f9d4232f96cbe0fe945fbae6bcfdd2095cafe6e0b21071d6ec53f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6", "Expected": "0000000000000000000000000000000015a88bcfa39f444cd66d0d7e15c4040561154c59b832c5ca895f8f8077659487581681cc8f13be136a35b4a573551ad00000000000000000000000000000000009fb6b87eba1edb3d1d23e566977eac68e8f1a28386fdca9d484c7e341c1b210390787418e2f2dff7a228e1cf10962d6000000000000000000000000000000000978de870dcd8d094072897707313b9f1a18d525e60a7cba2b2a395ffcc9d0f97f84e0784df36247d6c98824aaf3ec82000000000000000000000000000000000fbc6832c324d40f104bf82c8cda941212105131c26f630af1d3f7040ef43c6eb4486766b75a81433e46966f79953647", "Name": "matter_g2_multiexp_44", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007517a941bec38d0e84d21508d8bdd6778a853d9fb4c5e953bcfe3c8fba3732ca0b7f6cb5c363f33d7718b1b1a68f8e800000000000000000000000000000000150c0d975481422ddec2a58a55b3d917b6b7c0510e75442c81ee856e267d7efa09641c6b79fb9e699c6b473cccde7f4d0000000000000000000000000000000009d37bf938ac30fae1cb3ffaa971ff3746ee4090d4bf8b11dff7710b3f2e4cc686813890e03643fd56fc99e847ae5e940000000000000000000000000000000010fabc4048e0fadad73d0481e290c81884f4578cfb66e0a83324739652ccf273b62204f126696a2fc6469ede39e00a9fcdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c900000000000000000000000000000000089b167ce7fa997ca0ad3f8bbbb358824cb41f525bf60352d5df99402af62cc6d768113d2c1ebc7fe8190c5f732fbfff0000000000000000000000000000000013dcd35865e27bf98f1f6508b32c7e9a989d528df0626228087bda0d8b456af3ea2f4be6732edf1bd8cfd0ab9576197a000000000000000000000000000000000333b0612d7068986d21e1cb67a1c7af423e98cb14aace2ce02f84d32a38f97bcdac465f2b22e5fafa6aaf0d40380e4a0000000000000000000000000000000010de7ba4f50b6654fdecc4fc6c41289bec50cff1be18be9d5c9d1f906ae843189bb43f144aad4d2a2cdebbc2697c974918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd000000000000000000000000000000000fdddfceb29fd79c31b138ae8e41507f324abd5e3750da14f4f86176126a06380d53dd5f7efd00e7f94bc1370ac9816a000000000000000000000000000000000d8371c602e393a4be250583c299d069270a344953f7f07a5fe27f8617cbd3ebc91f423dc176b272339bb3bd8a9a348200000000000000000000000000000000193a260a417c9c46da0aaf139e3bbfbaa9f248943048396d95716b3be0b8a148a3f0ebcf7d6f9a318b16d2d850ec2f5c000000000000000000000000000000000be4d0f2bf6d746b930034eea8a19d73377617645c29153b6ae6d3ca6fb35a704b6a0bb658282cf93555c998f6fd054a650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000731c0a5d076d6addb15c1e5d3143d41371f4835d77756418bee452d2f03b1e603230c59f87905fb67d5eccce65a45d20000000000000000000000000000000013bd198c023009190c65686468523eccb57c5fe7b159a1c5ba30c662a275fb24d69338ec9c023ee6a10a8ec9dc7968210000000000000000000000000000000018fb369923ee655839c7d996e264133c49f102978f18261faf2f8eef376eb0bdcb5af375ada2bc783e50df16737f78dc0000000000000000000000000000000009ab8e16e1d0b406adbb37e950bc3820ec13c882ec4483528ebac836726ba202bcf796e84abb3c16dbf6d1131b3854cc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000001401029d7cf8e7d2690a27c01b32008e273b5a33842dcf52d84f77dfa4b2a1fb290f56eb4ccddbb420b27e06a7a3a3b4000000000000000000000000000000000c464c6fdba702f2fbf4232b34d615e66dbb5bdf80233f695e9103272111a06a79f8972d1034176859d0e29400f5a9c10000000000000000000000000000000006cc97a29f4e694f0cdbb099278fa94140b40147f4f911de96a762f2bd28233598a892899a6329cc3cb854b56076787a000000000000000000000000000000000625811cad7c740758388f330c4a56ef30429ea4cdb9a00e2cd1b7f310184a2e6ba36ebdca57c87cebd5232f52c34d92283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000d0a16b5d48eb062c71b91d74a0d25eca0d4bd7082de25199f33a9d3d598d137fbee2ac36e8f877c157be7438ebabd74000000000000000000000000000000000bf93533bf677050d9a77a5dbdbf7cf084b5d934d55318256712ec361693738d48ef27536476fdc93dd8e81f13d67a8e000000000000000000000000000000000696fbd8841e60300602aa5528391aa8b196d8c186d6124c842a0124a8d8dcbba637502f330c980b2f5a900be8e04d020000000000000000000000000000000017b0c51e699d2252f35619520af71775f9dd8c57c2ef146adeb72640bec2ca02a59680153e5c9f66bd513bd8559b9d66145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f472000000000000000000000000000000000eee94b5148ccbb3642e582cf0a517b72e6ea019676a13b1484982de7f4be0346b7ed22979ba7303f6367294a3eb2716000000000000000000000000000000001502bb3964f6b3e862279e15fb105073e876c4e48c55c42f3737dc9efed82b10fe8e39438ccd39c933f5ac3c6768497e0000000000000000000000000000000016cd8c4b3be55474aef7081cb969b75ef5e7cca9bd0f9627928fe9931c6f869a9a49d0ae2cfb8346116eb3ced25d4a8e0000000000000000000000000000000012456eceaf32cbb6514e6211136475a750889caea18ff4f9d5ed7b378e6d1d265721a646715aee6b9f2098e954a88289d9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af0000000000000000000000000000000007b5633f4a7dfe62f11065d44581f5060210f8e572d960eb85ffd0a903d8b989ce10449fc90b7e5646784a9f6df28699000000000000000000000000000000001710f252cb35d88f6bd151ed596f2d6455f050c5e25add394dbaf60fc036016ae07a5a8ed494b95875c02df3c523186a000000000000000000000000000000000bee19779dc6430ebee993f82a054fbd42e5b7265090017e5b2d2f1469bc96a5a188adf471d576a416f6a841081043df00000000000000000000000000000000038f9fb4159e4e6f596a17ddf45a00a9e4aede63b062af5eda045efacd3977e8dfd61c307834c08bb4c284638696e92ef262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e00000000000000000000000000000000197687895f22c4a639bcf2f494dd9e5a034610b0297528235f1d806cf032f5a86c5248a83ed6b12f0de27f5c6e6f49420000000000000000000000000000000011ccd5dd6d6ce553ade9b31503a9e6a6119ae329178706f051581e3cf0ee9d6fb527b340bab8c79fad1cd451c7edb4330000000000000000000000000000000011e9f051aacd69c8bfd2f0ecb566e6d38eabc43f276ba7a1b8e8ab093917dd1c672c61d6dac4651026823b9938d3601f000000000000000000000000000000001362c3b2e6fd9b3618df26ed28f96530c1915f0a4ecb647658d1ae4ccf4c000f3bd1797696c9ac5c5000dbe58dba8de44f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a8000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d000000000000000000000000000000000db02fcda340fb27a3fd7da468c5cbed9c8dce8471843a8ddadae43dbec9957a0479aa52855d7a6dca99e7922432365c00000000000000000000000000000000163503d24f9af34058cb5afd8e9d5aaf29e141c8521eaac282f138466e834f0daa9ce14e0590b501680d5b47f866aa8c000000000000000000000000000000000fc9175e6d20afd9d194907f2eb311bf8134aeb96da72f6423610612f2ed20a074c113fe8bb632d9ad74b2f6e7e2417d000000000000000000000000000000000b4621f5e4465629648b62b7f2b77afe6470f9706f9bee5b3ccfa66c596842cbae26badc689f7f623360cb7fc1d416b84ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a61630000000000000000000000000000000003c77c7efdab9a9e71283b034ef581a31faee417febfa99be3c18e8ab724c140be684ce719bc5a9ac5d3855ddbf3651a0000000000000000000000000000000011889b02b4a1150fc2b7191a95c5ee767f3c9b82a3a53591018242fa8685ee3b3542526dfdc00695a6cf046033b8eb760000000000000000000000000000000016d7463159c4e3cb635f24bfb944bc518369e894218bc49d7b7f0ea99240259f7ee2b4c26c6083dbc4559ffcfbd392bd0000000000000000000000000000000010a85df6294fd6406ca651f15494153e9802f0068bfa149e87fe4b1cc3071ba74940a21dfd55a8a77e7e2a193468a3d2eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a0000000000000000000000000000000016beec57d3049c382fc039ac96b890412c5e8075afcab599fb877f8639747a587e82241d9a8059a0bb45ad49959777d0000000000000000000000000000000000a70fba1b061dcf587f133035a3aaafcdace3b1e771d71887ae914919e5f52a99d9933307ec15b5f0a1623b9592824500000000000000000000000000000000005064161136c04f9f50e42a5cee5dce3fa0ce1dc0655b3785a852cb9741927f6c9b357ac1010d7212533d1593c83dba70000000000000000000000000000000000d50b992bc0eee37a15cfd32eda2c591fc4c4203ef84232d1a1e7a9888005bc00755d76b9d0345bb01ffa7525f2aa1e9d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b0460000000000000000000000000000000007617e60d8f67344ce6d2fb65cfd5b423a1fd091626da837dc8a51d6ffdeda9712864e8f30e45ae8df917e0e4625e59a00000000000000000000000000000000077c4aad14f870ba24703397ff0b33af2e50b026f3e0f13f3ec1aebc9ea3af98cc65ab56cce4045538ae6e5f410196f10000000000000000000000000000000004a31d0eff18afa87f9a53098cfd5d21e913c7519cb171f83d0b73abbf3e893a3ccd5aebb9f2bbdd3b0eb0326d37fd1b000000000000000000000000000000000393052e6dff65e01e79254af757f12eb1931e0b386f8cf0fa0782269f962ebc5d9bde46f5a4ad3806e88330aca59ff01e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f962200000000000000000000000000000000033aa108d252e9107f29cc7da79585d4525ff2a35d31479a099c7c011a9c4414d7bc5f8498f8a204134b2d14c5fcde5100000000000000000000000000000000121214465992bdefb970d420face6db75d531e67314a021d2877643ddf738fbe57625d286bde7f40efc1d329a2e85b6e0000000000000000000000000000000017e14f6cdd916b1fc949be8ba3ef9ae6cc16d64da4dd498b5458ea0c14eb7aab8f970f030aab26397110331da11a232d000000000000000000000000000000000c56ccda2a5cca61025253407e72967c767f0e7f2aa0b97d4e4a09420dcb882ff35039ae504a9c62b3f9e7bb0c2e7bbbe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000010fb3396b0674b9285cc5d5a4e7a41ac002f2b43332c20a56f428d1e19e1d1bb6f886d3bf03f7b0fc509e52d75965e15000000000000000000000000000000001196b7c253c50da10815bdfd7930a69608187fc3ac5fbcfeb35b95754d3017a094afcdaea867c2f08346717dfce7bce8000000000000000000000000000000001021f178c53b7d7d2041a6419203d12ee162f27999dd8f79baa15c37a7401e7a6df6aa4192a310cc1a23bdb0b427d63c000000000000000000000000000000000953c75910165f11112583476574f3987495d33e5b1a5c650a2b30692592a442d9de36da49255b0c01a7bacaecc9b81adcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c", "Expected": "0000000000000000000000000000000014da1d424c936453600a4acbd3666c6188493d4da8b34d6bc508aab07e59e3680a9e3488e69d42a724c9486d70ed4fd000000000000000000000000000000000048c637348fb9a4c631a82ded1fa08d693cfa2cdd6cdffb8bffee63d1bb2ee8676512a1a8d375e7ab942b6d6bdda45c80000000000000000000000000000000000443264e7dfca91f17251c33cf72c56b045902b4db2eb10d1fd856f79b4130afa6f29f3283af7d3b8b2a9d8dd63718a000000000000000000000000000000000fb386f875190ac7a49d4742edb387f72c1ae0366ca5c71d5b7e385c11442941ce0fb9fe2014fc624fe93ab86ebc7aff", "Name": "matter_g2_multiexp_45", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016a539a21320574fc25ffbc0ff10c821d6ad20674413eaeda6f4a31f9a028e21cbb3b224c225a2e3bc3dc221cec084cf00000000000000000000000000000000104e44989e2fba9ddce8e309f5d3fa3129f679d6456ed11137149b50adf8b22c1a148d47154450853e6797aba2b006850000000000000000000000000000000008b33b8cfc992efdf7d733803a6d08a4102e27fc4960ebe6ebdb7949c4ff5af76e55002d93a4f7204eff5f2dc4e37ef10000000000000000000000000000000017c35411c571c302c746a9b79cae892e988d50b4660564660de960ee09b3937b6f5b61fe37d09f1c02528f554210744aaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b60000000000000000000000000000000009cd32594094d4744f59690cf8d7fd260b5ffb2a22945d938c035151861507ecaac9ea553e7b44fc4b3beb03b33783540000000000000000000000000000000006f4de33731b9b13b9cb395798769e54a0679d272c2d5175455e10c790debabae4ee02b6df08975efe806da9c4a208b20000000000000000000000000000000011859798a8383b7f994a1535bc0a96a114b90644d19921f0eec774ed58dbaa899dd3736cd1f4a4ff9bfacbc7370091d7000000000000000000000000000000000376c25b0f70427d4974c4fd1539d40996b6847fbb67822fa01cfd541cd3a3f8a9f3fe9f7ddcc3ce920a6ecb27dafca0c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c0000000000000000000000000000000003a178f91a135d59dbd65eacebab293a3817d30e734c247f56a08812aa540a5c80e3f9908d86ad787bab27fbddd21517000000000000000000000000000000000672b3544dd2b91a626f37dbb389aff073777164e3e20dc572b18a2e5223bd323094e41bdbe2dec9bada227efb37dd22000000000000000000000000000000000f40f2d279c66f22bf0fedd129e02c96d8906f9f1ec19f5a5c1cbd5beb10942a066dd391b69920a0a697138f627a1b180000000000000000000000000000000016ef3caad858d323b752e5c437ee2043c8f691ca0f1862e80857f7cc478a689df97bde5b1d1350892c1adb03c5d2373ba1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000001276e133fc5e708a3265646ef0a0122048ef95d7fb46f78b8dca57dabae0164ca986bdc74e581604ff31165f9f28dca50000000000000000000000000000000008a77611be0502d2ea7fbcf73774fbaec68eba36038e2f34f79caf07f2e4b7444efc49a4e85f88af585fd28a041f26c800000000000000000000000000000000181ab176e391190b1cae2e9b4105ca14cc82d15890b0ec127d8cdb46f30b704a089ac69e76f5b50575ed66176950e1120000000000000000000000000000000004031ce77fe9ee319b8db8f220ef4480c81568b3f6e4043c8710b559d25ad69dd38dda48b2e11d5aead18db0d1cc09b98810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea000000000000000000000000000000000f26e6d71e206c88dc81b8b8a5c05ee84a9f185e7b7f155253aa39104b5de5be7bb6cb6662df4f8e63b37fd1682721f20000000000000000000000000000000010058d13637c8da2e91c8cda7dc2cf1734a2f14b12b798e5c563ef9ef3624255a6e1c7550c37b547c35c55dc736a17ce0000000000000000000000000000000019ed470bd514f8bda8fdcd9c64f7626efdde0102907bd31551b1d1972aa14e1d361e1d58b17948909a669fa4d99cf3200000000000000000000000000000000013277afe1891807e269c22c9aa1598c12081809d888e0eb2513ca3f81308700893f74f176858ceed9c7955dcc0d8fc6893e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a00000000000000000000000000000000083664daa965c4173d6028e047794703a16e52ae459d3db0534d13c72d749d603edd668b9ce500677715e45216367c63000000000000000000000000000000000f4e87a65f4720cbfde7868eaadb34ec1916925ffd84e5407defbda0c39e1c7afcbc90855b275d528e7b63fd3707bd4a0000000000000000000000000000000004c9f689abe0d2dd3d927bad4b39ab44f6704014ef9a1dcd1966777129e1c72515b43c1b92ee60e9611245454683588b000000000000000000000000000000000ecc57b08b45037e62498135643cf077f01d216b5106551daab391446ce7bb37d40f41378c830081bb6a326f0105c2c4eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000012b84341bbad1eaf7fc8ebe56f67598821017365b6f3b4cc1f2355f868e8d55f9c0bed2943ada202a7d85cc884d8e6a20000000000000000000000000000000017693721988f73d77f7a41db108e428b0ba781ea88eab463693ec352cc13d394101b9a2792e0f30c77bebaa395a4776700000000000000000000000000000000093245e2919523cd57a0abd2e8a9c5cbe774bee957f26d3cb502b9c8c06483b850b031461dc2cb033d399651724f4fe4000000000000000000000000000000001530f7dbf6a0fbdc8b4f7a4d298b7824c15035428cb8df834907e25c64b8985186bb13f397b7b99ea7014ae65c428b12abe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000f323f01f2a63bc6eb1b565594ded14043c4ea5d1f0fbf20f39299052617c334e6126afd4273738aeb153c3561348b8a000000000000000000000000000000001525d1e1fa65f1b674feef74f6c81c82c3eeb709e597aedabbfc2b3262271b31d93818613ecdeb49c5d3a6a64f17a5d90000000000000000000000000000000010458c15bf46947a237dd1c61882b1561121f64890681bae5db6fbd24ef6c34b7fcb826eeee1fa328d9ef4d859faf238000000000000000000000000000000000e1f29275fe1805d02e069082d5e9a7acf69be17013e6c4c351277408d49383fe06f00137e777ba4aa49c29c25c6c0ddc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000efb7373e11694b966d0182a9b01d1e52ec1e89cb18275921294e2d36333460b1e49fd420f1ab781b000d1491ccb0b11000000000000000000000000000000000cafcdc2c58fb3fad713ce1a38deadba8636c384243f9971e3930b961efaf303cac4eef1e8e4662636ff91eff1bf52a80000000000000000000000000000000007ea7441e1b2b0f1e42bd511c060b646c2d00bb3e6507beb5d17ab93ff68515b02f82c2dd43ce035ff660ddb0c104a77000000000000000000000000000000000bd04b88caf9dbd0ef5f89d12e72aa47d64212332b0ed871b7eb96b16295cf4810f6f20cc85fd4d1ce72119f80697c1b37d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b000000000000000000000000000000000c3d2d978e23a690e8422fd54f36fbee1f642611b6c3b2c2413844066159bdcd3703d1a392b030446af04b654f8f73b7000000000000000000000000000000000ae652fcdbd8e467ee9b447e61fcb811f8b6aa48840476c92daec3285785a06a81c1705fc2896c0843ab48eb92555b9300000000000000000000000000000000007088e6441cb85aeffcb4a9a0c81ebfc54a61f35c542be3870c2bb94d7081353322d4745747b0dfc3e5db07f9e48c560000000000000000000000000000000006c11f3e0941ea3bde0dd3a562dbbdad433f0b1e99ba34879e86f7951ddfb29b9e04ca62d54d7552a74e8cf1c3da3e704904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000000c665f4417d0163820ac96c83cc2f09b1b3c000023d827e2690aad7357ff59e278832b992703f5f0016051ce0a4510cb0000000000000000000000000000000012f4b6688300b253fe868b3790f6d2f4fc16d81a49ff7a2edf821de16dc992d79482d66e443e0abb5da43df69f8d648d0000000000000000000000000000000009e033750a118d998b136cd671d0e760e3a617f1d6a994db8f6dfc391619f408720cc57fe550785306184b0c824705620000000000000000000000000000000018cbacd471e528535e22f714a841f110fb0484826e30f97842d65072b2790dadf0bd7b28df96bec531fbed1f3f93486b68911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb49000000000000000000000000000000000c42b6fd52cc52034b04078a6565af2b43948695851393596e05f37f297dfaaea931a33f5b4c25980c093f8a742c0020000000000000000000000000000000000fdc7aa20e63743dd6ab32c82d2d6992b29779ec06eebd452c17d844159e90a7f3221f3e0e6b5805dc0f42dc3836d90f0000000000000000000000000000000003a2342a1bd528d701c2a6c72708a16df632f4e4b6cdb3ccc224b58b57af30b44556cc968ba3c0396a5e3f11568a73710000000000000000000000000000000019ccf76462668905c5687b7612a0bdfd4aac70f291d8b772e84fd5d4bcb591556317426471242fb5f44fd695c7d49279481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef000000000000000000000000000000000c8fd4a171c5fbf584f567a1c10b20628e7e0d5d796eac4a9dd2376f8d488da25b9219c7c70709999b5553f8bba915ae0000000000000000000000000000000005d791c907984f2aaebf903a0ace52147745295f0c5e85964999a8fc74b64c8871dce358f26ed1b4af6c6f7f18e8f4c500000000000000000000000000000000110a453bbba72ac171876e0f6b4acd5b178816301e02586a143c2bcbfffcdbf593655408b9aaa4141b2a210599f452ed000000000000000000000000000000001025d5065f9801fcc1c1ebebdf67923b967ce985b5ca27ab5db8af7057fda23561a46b84fac5e793dd9af692c4d56cde72780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b00000000000000000000000000000000023e5ea1909032676cdb79111a33da7ed788d2affbf4029b932eed843268f355dc92905db283d6617fbb530da3d704dd000000000000000000000000000000000b46f07de520aa17d597586cb0a6894a356757941ff9bdc2976f620e1bf1eec1dd9801d6baa2d7efbb3cc7073412ce8e0000000000000000000000000000000010022940611f418de9f9210b1be919d7506aca468fe5853675fe159d3e58685bcff6cbc2c1cb9e7d45a7bf305fca0eaf000000000000000000000000000000001888b5b0dd1648d9a27345f570a1278238957de1bd30c195d554750ea4b119e98b3989b912c4fad531de416c1533467f84ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c65670000000000000000000000000000000011dcc334a5037719256e514b2c3b0f36396d8cedcd77f33545842c686fa0f35558c397562a7e245f8cc412c776a2b3930000000000000000000000000000000006efd32c6afc56a07c813fe19e71f0248666c87e1df7e79b7afbd70178929e5660e85cea35d1c6f42b4c627a94ae0d150000000000000000000000000000000005a5fc2010798c793c1b407a577da0bf0e04b0478f19b7d0cfeff8e4e4fe2d581461831db165cfd17146c49a732c41460000000000000000000000000000000011dfe3b62eb87b039113152af74ae74137cba1762d4ae62d3cb0746272d1c42d3cb4a8fccd845a519fd0650a23a897a13256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd00000000000000000000000000000000110e73e44734b7ab63f021727b75e735702f1acfa6669e0dc27111794ebee371734764bb165132af3a7e02f3605456480000000000000000000000000000000005fbcac7c7334cd0e6468feedebe077b80390833eaa4c28af80d29e75d692a10cf13058526fa5e5ab0fb635335ac8f220000000000000000000000000000000013f537ecc28685aba2cd60d0e3e787bc8104a3373177cb93107b63d39919c583ad3ad7a42e322249d7605ef035fe1af40000000000000000000000000000000014791f94aff42bfca13ab328a3e47b06f7da52e13436ad477cf55e53b54108d3aa531f0a5d73ae5ed7108d5cca1ecf7a575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b", "Expected": "000000000000000000000000000000000bab02defb32b7938372d656feaebfb5431de1484361542c02519d20c6a500f0b0b112c331fe6f4eac3ec7f6ae4167e50000000000000000000000000000000000796b38c67df1361115bbf3a4afad2651664ef55b1ed02d3172f024f90a003fc3631753d7142aafffc64c6f6f57bf7800000000000000000000000000000000080d91637a93a9025e8691a400254af37cfde67eff7d3037d428596a808a01d9bda8025b7246fb00785cd1068b2752d400000000000000000000000000000000182a97624249f0c6d24672f04e2c93eff63fbe76cc11ace0f7193facd0655cc1e1ccb2d89d9547bc352a395efeb95afb", "Name": "matter_g2_multiexp_46", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000115b14c4eb9cc78eafedd2072be4555a3db9e61b5fe0139bf3e40a92cc37b4936c68576fee5692a80e4a9aef05a9b7a80000000000000000000000000000000019c828ea555a3c8d28cf0981e98609361b5bafa8b62e860d121c0f6d0f0dcef544784e8a5fa6a9f1d1a68b30e8e8a6f8000000000000000000000000000000000a2ef5146d2658609fd4eb98fcb5d42f6c6aac4fe53597128bbba3ba3539042ee5824f381c41dc76e2c6e4dbe0665657000000000000000000000000000000001807a12ae5f289ecde8ca0a913647d44209b13fae9dd6aa8fb4365a3beeb81652ec17cf92f6784c9ca5a077824ff6dc31129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b0700000000000000000000000000000000186bfd109ea2369818ae2f466953eddfa763960caeee9d6f1ecbf6a3f854163342c26b56d3844bfedd8f227070f75546000000000000000000000000000000001877077daa2ea074b2868e86faf14efc6ed35a64161a77aec54624d9cb916c45de81b40acc3797c6e3338fcd7a42bb0d00000000000000000000000000000000054be1650d9bb6cae6a1ed08879668e4aa4cd139c8b07ce21d40fb1cf37f11de730ff13814a02d2d6d6df5eec4afe8470000000000000000000000000000000001612b5b7c613cb66d4134aa867d985682f6a544147e7865732887d4fbb191a9f5bdc27bfbefd397f38cb101a2d68b192dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c90000000000000000000000000000000015e2b23aa42f1e6a07b0a31dd4acc27e35ce1fb3333c3f330f2d88f112375cf24e6dd5afc4d245531e4e84f1f82230ea00000000000000000000000000000000193649f3b7efb346e0c1f7bc05b0910311270cd44b5803fa16e06655d6239f609363344bb7c16c2105e20709fb5ff0400000000000000000000000000000000002a6ee30841f471dd2ef13888ba03c9cb93c85cfd0f1d0a3927205e3f57fe291bba7eccbd2352f25cc4047097fbb63860000000000000000000000000000000005482d4a47d6e381f755c4756a761f0310d0d981523afcf288e47a1a643d6be62ac6410521e0f25828f469af6150f41e5a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b01000000000000000000000000000000000178ca460993d503e496633fe5230d895bfcdd0696d817a23ae94e529bb145a0861e4448d3bd48c55907be762192a8c4000000000000000000000000000000000015d2db77105a8ed6eadc05d3d1f26a54b3d1b812a58ab49a889f5b7fcf5ec08c2eea6ad09484fda29cc007037a1f6c000000000000000000000000000000000fd5628d61cd0835fe49fcbd2f17058f23d0ffaca4474923a2c0706d9333d9881125efa2fda56234a82158da3eddb5b70000000000000000000000000000000010ce4a0bcada5f92cc8898dbf5c108c0897322eb6a467662be569d9ed0f6e2c808e214b83969ebb86c84d38f67d20754579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000fc4ed0ca43d5cb172deef02704579187a480cc977737070b8ed2dce48d3f3141619f37e985c220a2840ac01dc5667f900000000000000000000000000000000047a15e96760affa4e537a45aefaaab1e0e18052f63514a9f6544136c87b7cb4a5c8dfc0d9544518adc7932ce9cff5f1000000000000000000000000000000000c9be55c06f81e87de58a5c1df8d16174cf4115f81091937d98dad6c4780a9b8dae1081f8961fadc4f994ef62927664700000000000000000000000000000000165f9b1a8f23831a91be8077b18563c7648e54caf30895983cc26580241ca7c86b9b30408a9b27776286ed9f07bd8ecd4be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d160000000000000000000000000000000006edc0d62ec31b14e87b2ccff3a21a7c8d38c3ba0ec48bbb8df27fb1acf58e1a87c4458dc2b770172460adfb9a9bd50b000000000000000000000000000000000ae80063df8d41d45fc43f3aa0881364ab5fcb9ac526ee22d3870f2edb0aa379e9d81780b0ab08a4cf308d819338deee000000000000000000000000000000000e0898453feebb51d9a1cd2bda36a307ff2eebf44dc8f4c694831218c42b51f723ffe521b356ad4e5f0dbbca9af9ab47000000000000000000000000000000000fd186dbc046dd02217cec3c7894972f71e5f00e00a40fb1521659a33e079b7a1f60b026d9055a50ae18aae5757ab8490983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa0000000000000000000000000000000012134b433877e0a7858e6c3b95b2a1dcfb0548b290b68c209642dadf550db1c636598ac43d101b13c2d8d5ed9602a73800000000000000000000000000000000102b5de123c449a078f6f06935c9537efc791ed8e5475ffc2d9e1d098c814abe56d4b7fc6501c315edf7e64a431c5183000000000000000000000000000000000bce703ba78f45a1c59c69429d3dda18243ba2413c5eab46d469f504da975c434eda451c85357738d6c7054755d5cec1000000000000000000000000000000000387724937bfd817a65c0e0411678cdc78df26ebd4a814d92b023710558701163349b56b80e6bb68a4401f2662a0525506615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c0000000000000000000000000000000005abed9305bb79a0ef1cc70e7fc2eae35a8580cd3d1ffad73d3bdae541ad546b8f74b4ca76f8f374e31dbdaf1bd14be9000000000000000000000000000000000199b29da8d161ab3061a18debc8b7400415caf029ced47131e27d81a0f7f79b6ae5e570f34a4c74fb85fea1411bd6ea000000000000000000000000000000000b8a7c42f5289d20b1a55a42d53d49510d3871b6efbe560bb4d87029b85b930f787c3a42e225006ad62c68d5f96c2f8a000000000000000000000000000000000e74aad2b29a210cc316181863e71a1dce8866a088a072ad5972af57b813a2e968a5b16b294273acd6e81e9a5be2961dd77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba1812272736390000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c41f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000001611787ba658b64467b4a28e55ea24a3b230836af6c2a7072231045ee4ee38e02302a62688d6f988f76cb5e50eba40080000000000000000000000000000000008badcd59d6d30f26ca674753ae9257a853dbcf49a5641999634a9a35a97096b6096b7b058360bec2f9476a51eb0d781000000000000000000000000000000000d30154440d8bb5fa6538953a96ba404658817be8047fa7a3a86493f02399543220758e649948b804f2daf84fb86f7580000000000000000000000000000000014fbdd62f761fa675e4cbcb61083a910bcfbd1f8e37f1fb1915f60929b047c970b87be0730ddc20f9716ed8c9bea7f19c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a1600000000000000000000000000000000143d1f811644e3a51c735b708cb2f8a2a90311f9971c90b9ec8e45bdd6488638b6851dbc882205263887b4dd5dfb4e120000000000000000000000000000000015692a6b06e3bd3100e149c6be3cbf1566fb24531eb29036fc48f85d5da83316a38af4e714a17552024c1ca4a5e39d9d00000000000000000000000000000000172b9c88ed9a1fc2d5a7f147d034fa243d420b129343ff92b79bc4d836e380e5a7e388069e9af9026485e9d3f41a7aa300000000000000000000000000000000012e8453dc64f72653c4e9b3f6f43fdd01b896c642d21604f992dc5591f2cadf71de4099e1075a4ca4b7539f84dd5a908db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c880000000000000000000000000000000003e9b6d23809781f50c0033e53d245dfebbba9e0c4d9f676ae61b80fb6e774509f62fad854fd9ea841d9905d48d943a30000000000000000000000000000000016a1ba62bc684bb1848b0ccba59597b19973b56fd9b1d9d06352de44aa79c6bf65409dafb54f859d4a7c32e188bbe19d000000000000000000000000000000000e782741a4b16c5838a8f6e542135221ab3c6ad180c85c08742992ddf0239388e273735eae76c656e61614da386ce2640000000000000000000000000000000001cf6752e88990c221af94e18744790c30aa6a158b10a1f6a56c2ee3c3f0fdb2fa7213f16764ac9e9f4f65e99e715ca170751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc0000000000000000000000000000000000810b0175d781256053c3c9188cee4f55620a6624bfbd2f4d2e70ee68a105bc7b60bafdb76794a048e9f25da976390d4000000000000000000000000000000000716095f8fd72d9350ca62ca3ec34d2228cb563d4e89b19b152787d42fbb750435aa6233d0a97196a9324319837be14f00000000000000000000000000000000178e939d87c37d4a2f49e1e5596945879f2f0f64419e3dfe2afa06bd58098e1ba57a9b60c32cd6527481ab3b325ca827000000000000000000000000000000000aed480a1da482e40ae610a9522f0a18399b0130202f9ca79e3573987f5f7ae30724feddb52fdd05817a96f7937aaf7984bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000133adb236d9eec3544fc91852278abe37a1da0f32a84477c0d93927d64af613b7452a5f64ddec7447779f42873cb157000000000000000000000000000000000f6bd940b51b7ec5a0d92ac77a55c296215e970e9a499793864dd69c3a8d583403e95c08b719b5d8eb0c37a8476d3b960000000000000000000000000000000007d4444062ae06e65b45c6105af53c487f6b275ecdb36f87ec7b71d5861a1bdd6d735e9a1fc5dfb476ab8c13a98b570a000000000000000000000000000000000e043cdc87c67157b5ea3e5ab1b243aef479b23861f8cd823bced140ee03dd1f8bc6cebb4bde4683ac3340823f4d55b8d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000018c35112c27caa6bfe9cf8ae55f51755ed349ee7e7141c99069dea07c21a6d8634778a91f4dc3d17da04966a9eaccab5000000000000000000000000000000001800c8a9b146dba27050ce63e78895bee2016255c59acc34fd5e6cb926c16a8fcd2e8a579fa02559b3c571cb08011bea0000000000000000000000000000000014afab23fd4ea54b1ef576a12a2a62d42b493612ef466483ee8c4e62908486c038598e72dbd9256166960db73259def8000000000000000000000000000000000899a99ae8b10da4bbffb6590d79aa33bb2adb2444a11627f05622c732b70f90cbd2779362349aede5b591e84b53a8a06940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000567d6458d1a3e012c63adc8b9dcf32254c98c0b7021ec6a8d579dad47d501715d2e42a0837def225515d663e663c4f000000000000000000000000000000000178daae121366ce025c1dc2d3e72068fd40ba9d54b2b3724f7a2071a59d4f17d4766a82364540bc31a46398c66d0e7da00000000000000000000000000000000147b2851311913ea53662082acfba785d21915cf00cd154b1b495246e109ac37c3fb6c63aabc4fe71a0d37c81a40148d0000000000000000000000000000000000122b7b1a81888aee37fdd6c23d31c38e79f28945cd1798cee3f4d674e923fc68311eda8ce45a561abf9c5f0bfeb4297b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e", "Expected": "000000000000000000000000000000000f5b941cda417cce69a30c1ba4a82cca71cb4b953d06d8e545c1b792ae22738dc006627da02b4344bb8be93a5a0dcf07000000000000000000000000000000000eebf4ac30fe0ffb905f81577466889666f801d4d6efe0fb8a663fbf1cbe76b2167243edfc6cde3f49d97d3040a9507400000000000000000000000000000000007ae6a99b86dc7ea95801776589472547ffc7a623009a592403a9710ca365510d85bbf20fa4519ca0e0ca208bf86a670000000000000000000000000000000004b5abf778c72bcc5b887855c582c042a4cfff489b0548785e4c1b735b19159be8a3f4cecf34c769a34cdefa722ba783", "Name": "matter_g2_multiexp_47", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017b47384e6302b140118d0a9247aeae2091607ebb413ffa232223bb42d16753b2ae48e5ad0265e33616b25f0c4234be600000000000000000000000000000000167be566292b835a42ac7c099d80e8a0b5d4ff91d842d4ff6026876aa1570ef9641e9c0cbd44d8578f6a758717bad6f10000000000000000000000000000000014f692d195979abd9c55ac132d0def925d4e158fe946fa7b0a010c475d60171a0951d4b68ae3c463bf1136600a1ddaed000000000000000000000000000000000ddba1f4236c5200aa52f8cb7e15fac1f20cc66dc65ed180745a3eb8308f2c851ed6c1e27e1507d3f902ce672d6f8d24facfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe243351300000000000000000000000000000000023d08e255b244cffe911e43b9b48408f9fb3562edc2c27f405bb657731c885a58392ebbde9fc80cccee2404cc8547ec00000000000000000000000000000000088ef289adaf206afd2b72c93049fca2cf9292bf6471133c64ac4f42015b97bb9a23f6c34653e0218fd0abdefa56bcc60000000000000000000000000000000015cb78c1440f74b17125c547fe7a37611f01b83b91a351664c696e0f647bd2db3ffead880b96a327780026d74c9abca30000000000000000000000000000000004d1a63607b9a5c9ec31168d85fbbee77cea0ae93e98c8c1dde14d0baa72f91042b2b7ca489958344916ce79bcf286456546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000000ea6cb7ff6a7f4ec38ba11e9945eb406dbb8517585fef6cdd64edc970efba244b071fa162f7c8e184acbf71c5d1e12160000000000000000000000000000000001ab80c0dced33cac8a6a085efce71dcd7021f6255684bb631cf5c1716021bece57b900b819e6eb6f5b755b74c677b6c0000000000000000000000000000000005465fdd51352cbcd8b804cd509526c3b6232976b8278cec3b7db7da14b77f78898c6240c30943d1418462cb7a5abf8f0000000000000000000000000000000006b6caa6a0d5f2d671b10217c0ce5b3962b0c3edb4f2918497c316ebbdbe1a15c803d7fc3413907346f0e7d03920005aa9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc00000000000000000000000000000000170db23154805a04013052a388e14b5da00e65b35b8ce2dd967213a74735dcbfd28782cef1ffe9d384be3ecadd101e9300000000000000000000000000000000082dea309092976408a379f1dbed9d8cf91f768e2921e49ece458859c80a1d9efd4d5e588470bd669c777d16f9d2e7de000000000000000000000000000000000adba8ef34e197689228e6c4e13be75b3d4732872c99b865ce7733b7a42034d6d4d7520ef7ab712f60f1ff87bc4d9d8d0000000000000000000000000000000005df0788ec39430fcd0625f8e030d917d8e7c251ee6e3b0e79fc6fa5f6fac2ad736c818bd833e58ec61cfdff52c9c6ee2c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000000922d8b5db6e415aa3acbd0d6065db1b492c92313260019ef1bda0fa091c4bf091de95846af1edb34516b1abf7d278e0000000000000000000000000000000019af4ecc4f278315ed90d67cf4d22ed6fc9af5c0d0ca654f6a74a3c4bc98588bf5347b4536f36ca8b4750c18464f9b7b00000000000000000000000000000000021eaceb11638bda8b4293991983f11cc60c1daa2287f4b4a6066374bac82d117ac3ea4ec73afc4372d254bfc433b8c3000000000000000000000000000000001037fe26a10305cc5dc11a65edc705be5a0082656cad53e63038ee57a79e16075df54331233229a129483c34d6dd92ec9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000011e63dc251a5a1e2ec83741682d90588b6b185365b33dba45458b1f56324a4900b04d61af155a0edb0bdc2971b7aaa210000000000000000000000000000000002dc1bd5448a2ebb9a02509af8777616ba9657bd3be65519233f0187df77c49fc931bbd3ec0ad5856b2ec0dfde476a870000000000000000000000000000000019f0cf8baf100451313711bbb0a0fa318c14224933897e74fb727b585cc8620b7d741c9ca2f0d3cbe14a8749aa48ef3e0000000000000000000000000000000018448fa9e05f87d4991ae1c248413edc9a8c3ee789c9c005e691bfc9003191ff469e26db9e42e5758fac79309a62942c5e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee200000000000000000000000000000000109b71c19cd36ef3078bbae25ce6d0e8f7b58e129407fe68ab09aa747bfb3e90c04ab804fa6b7a223c172146fdb14683000000000000000000000000000000000d297750ba112da88beb84b8bbf74ed134b59fc9496da3045aa6dbcd97c68425fd68b75508de113733602a5565f4c8a600000000000000000000000000000000149b8ba6e05b66d07b353f46ace4e583bb61ed18fdbcea0e941b8d9805d3168040186d1c961add494f98e4e7fe68824d0000000000000000000000000000000013a6877bd46557d23b9aaf371ee5a101227d7938c64503b04b39cc6cb4e8ddedcf5cb6865439c9f8b1bfebb807ce52e24a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a200000000000000000000000000000000073f440c2704fae6c86aca3cee34591ec03c362c2c5153a5e82c7bcdece2af0c58a3484b448c8bf4da851800ead959df00000000000000000000000000000000075a2c26372b482a2420bd3c9952fdbf9e5fea906dc8a4deb9691f8745372805bacd68a4838a3fefc381a2ce946ed1780000000000000000000000000000000017575b016435782cd09901afd2ea6773b11f5a983bdd19d14668d75362f95d055b76e5bf6966b1bd7bfdfbe9a939e4b60000000000000000000000000000000001569d74258298fac89d0d91a9945780f4c08d7af7b942d06255ae590db6e8509c908c16bd2c2bb634279debb72f489b5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000017180e36b925e2ce23c46813d96b919ca181481efb5d1666c4a4e9c8031abdd9521eb8228c4e3f16de0b33da4c73588e00000000000000000000000000000000138965bff7c573546d80ef7efb3d45e87ed20f59adb0cd7ae148d09a97da7feaf1b0ef2455ca19381762768a7d82f486000000000000000000000000000000000360bd29c3f07c5b560e2ac226112a628839da9db18b052991eb2d9c54541c1b5ade9b3c2d7f446ad50050531228120f0000000000000000000000000000000007105978bcf13bbe2bf5c8f7d165998c3ad99b6a2794c90f5b61fb7bf2472d307df8fc9f4afe7ae1e40e7f0eee8ef9466e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000f4434c5180ad10cd45dca62b8da790cdb912c255c0f33950f7039e3885b38fa9e9297c7b0a875380545839d8c4d4ada000000000000000000000000000000000d0dd1429e512884ac209f788b5832d31649a78a8966d3348a93f841be23c8e4e42d6ff0d6c27e8f43daf495c9582935000000000000000000000000000000001307377f55dfed30ac1a406671af1895218a01d063b025d25bdbc53f5f9d535e4cd8053c09b2cebb25d3a08365ab8ccb0000000000000000000000000000000004f5c06f505ed15aa7661249b7edd71855bbf47237e049aa951e1ea3ff88f98591518bac975ac628e417892f8e9e5523428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f00000000000000000000000000000000083336fa0b79691b4875ed27b2bbd2d2586992940356f6ae5ddd2021c5ddd87f07f0a5c1e8d8a2654b99182cc2233e84000000000000000000000000000000001880f3824f7cef95ae5743de2e17191848d8d30f0469f455461c6559ebc75a7afbc86dfa3ee17f5470f74018ec335edd0000000000000000000000000000000007c2b26353e86223e5dbd4ed6d59f1170b9cc9dc600fdfbc6c73b96f2c667a82128b1ae5af0542b11a7d1efae87c75610000000000000000000000000000000002427b7eeb497a20cf15c10513cadc9ea612f3ae94e2ae833d281734e7b5d1d50e240659ac01da7864a95b4cdcf88744ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d97000000000000000000000000000000000e72845430ebfb84f8e3cd3dd418f6dc528bf521aca4f9dbd798ed903ef0ea3cf21dd1409aa3759351be32b21d8e8cbd000000000000000000000000000000001457ad87f0957006192dff7d99815c35adb3635815e5d157542b9f52f1e9f8c0143a21a3be4dc1aea3a895689f4a316f0000000000000000000000000000000007e8544b1037ece2e5a9ea387e0f43b72e895e9c2ca4d205f12bf6df0b35ae62a4d62756221d6fff65b928b7358f48b00000000000000000000000000000000012c5c3167f6ef118c4044c0aafc85a337d305437d694a7bd6fb406dabb7364d9e90d74a8b327aba971421a5b3dd5d06988a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000001995d7cb79da7b6c5a0c8ccc5ba075d8d6d8ed3cfca85e8ecdd2b589986fa58c4cd4f045983e9184d79173678d618f310000000000000000000000000000000000f9f7f6bcff0f6fd621f3f8fcfebac132b3f0d52a34af33bb9830bd714d2982f3cc6674ad6ca668131a5062e5589df90000000000000000000000000000000017699b298a46829020e0299ab89ab6411af0a602dffb0e149053ff40ccaec71a908da02c8e611723cd06c16a8e5c0f2d000000000000000000000000000000000523b287383c1e47a6f31d397359941fe0bb8167aa11604ff8569969eb5ccddf4c4f432d2b6fe6f39204020e850d4f2b30eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000004760120239593cae5bdec813735ccc99a88129c707686cf43efbd48fb08d8da3086879a6042bf118879fcccce0736bc00000000000000000000000000000000105b8191431f701b365c66680cb4eb267681ee4da17ba55d47cf26d21ba1c0c3eeeabcafcc79dd87b6457bcc91e9fec600000000000000000000000000000000126ab502f66e732aabe02fdb2f7a665a9a43f6b4ff21c22fa976e7e434b08b606e9cf0f02459fd85f5a80a332fb3a62e000000000000000000000000000000000b2ef01adea6c00250f2f14c98ec6d6083c45019f3d166419e3a137667324f80c34b6b72e991daf72e2eaf9985d0f9287e8dcbf708682225fe3f71b7a687da23de5ed188e40585be0553358012132577000000000000000000000000000000000ff22a0db4f1b1679bde5853a7c2932501f191f4a9f25eed968a796219cef028e26070851a9036a05a04abd73bd6bd4e00000000000000000000000000000000097e9310749f52a4b645190069f4d52315f0eb2ff9cbbcd31f1781a68b2664bbbf27166e6e74fc2be2e5b1eb3f3d77a00000000000000000000000000000000015ca218d7d128095bd4f4b4f7bcf7666e92b905e551dd22745bc743ad0783b6ac44b841f87d3deac44617a7c9a341c55000000000000000000000000000000000a1cb723a4c378e5db2775f4dde9a6887ee3313401a64130a78b90d65dda3a5d9c8bcbc1a0d78c310c869a7fc4889954532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000018b30cc461a4e1fbefe209a709a21ae201bc6094b2d15f0d6dee5a55dd84ef56b62ab1b6bd513b27c84c638291f4205a0000000000000000000000000000000008a6f2082d6d510b280a270c09044ad31fb18b851ad2b38859138c9c2e4870fba6b607f682a798bf21a13bff116014d200000000000000000000000000000000150ef352d494a97d0a7ffe44903aba1611c8d81fa2788c0f42a6db48a71101e12f07318da5ceb1f0af3aa10cd4c26341000000000000000000000000000000000ffdf3b133cc926684e4624531569bfa09b1658e29ad9c3efbd5e9d18353ffbbfbf23a2ad80ccee88f8fa597416d47173ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5", "Expected": "00000000000000000000000000000000134f45e5409998e657923ca76ce92b7d2acc932308e0694bb22f121f8324d16bfce86f96c67111c8080289eada4b4fb40000000000000000000000000000000008d9063b7845ffc8400c0b7585e819043884f92e28f7e3ffa47a39e808cdbb034ef4230b6e19bebf083e939b6b686b0b000000000000000000000000000000000e95f8fcd6b5bcc9e00a580a99627d92fa7486ff5ea587df5dded24d1b0bb76d339f6765a5a2058a8e227f633ce36e91000000000000000000000000000000000393041eb33f2c63df3f40d8ea1e1a9eaa9eb0a46151294845e542054d503ef69b40b0b676b0e4f3e08f4d26c36a5d4b", "Name": "matter_g2_multiexp_48", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000042a220276f12fb4e81c40ecef3a0d86634b4d1c0acfdc463df4e7501289f0be385e03808d44be053e6ac98f403a8a930000000000000000000000000000000004e3dc92e155aeebdfccaaa1d24f49efc8b02e4d9ba8500a5b953a96e0fdd58519bbf1c279750eb8b98616e6bb9a3f6d00000000000000000000000000000000086bc212a83b09c7361540349767896058d5567d4342c607ae9c07fe5f123d9aaac95ded6cdff0825edb5acbce3e2b6d00000000000000000000000000000000062598cd6d5680a155b6349cd51d636c1350746d17fe0fff5195f164ba2fa51cf52f8662f43d555f7be6bb8bcacca39448da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c00000000000000000000000000000000193e2c749e5bbe87dd5c306d822740969cd69ad6e156c323d217c08b18bb3f97c85aad63aed1e3a455ffa1b6d2a670340000000000000000000000000000000012a3dce63a88ae32a746e3812833569959021e1dd9518621793308f8f11d04829b2c3d0e0ec39fc48dfb8285b6852746000000000000000000000000000000001235488d01c380e91872fc57cbf618c3531a6bbf6cba9dddb9f07168cd459c9e866e44e9e5336369cffd8cc3a36cddc00000000000000000000000000000000010d85f85d6242b63f8421e92f1c37f64d33fed67e0cd3dc4b2b2cb4a7fc7637f9e049fc959720eae6d1f452159d48b78def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000010889825c752d0ae8445a1d0f3510135b9672c30a781788698f637c2f535e35788d76492edce8ea091223d016b5cc141000000000000000000000000000000000577175035c86c022e634ccc9a5beb96a36aa068cdc36e5a4fc2028d5dd099c5296d30a916d3b720f2e051e7d72e4d490000000000000000000000000000000017b46e49ba08a0abb9394479d693d8097a140094d0ef1d1ba7761fb601a686b0b2b4d49abc2e393a99c5cb760299992b000000000000000000000000000000000820f8e52c1b09986a70bff04909b044f671c3933de43a6bdfcbe3712310274ed880d7adc4947490c7de095ea651e578b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000001776b92f683069fdc006904fef8e91f716d9f6bc46306b042228088545f0e11a41b40b60722d4f0483250391febc0ed90000000000000000000000000000000010d5052ef2504115e9d9d4ca81c7080c0868cbed605dc7673f7f94f5959c793c96aa5334175e58499102ff76f974ced80000000000000000000000000000000011d1e719bb69d842df4fc23e8dc4393067d00f6fa8ee42d89b462a546414b91f68dda5378fa093b3ffa764b5fc63b1aa00000000000000000000000000000000099d0784a200e5d2d38773912cf1a49e813c871ede8c50da03abff58ec1943d2adefe66bd2feed1c57f5a80253e091d0a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000d7287adbe0bc3cbb35ab8bfe69064faa83e3e64d73a0c64d960949e10070081a99c35d1dacea5a3b9bf312745acd6e600000000000000000000000000000000034f1995eb8631e080378b22a51ace902ebc9da4589c89ab557b6aaf685fcc74ec1fcf95f6b9a31b7a45cfc5a1610c640000000000000000000000000000000009f56712a46c0fbc199c12d5eb7abd60e660e2c6d437007c34954c6234a0496ad0adf68cf759f8ea30980c9a78175e1a00000000000000000000000000000000073fae1cb78c776188190a4d7223f7cdce9a36488193dc06898919ef4d5136099c3185d352028760c753e9eebb52ac240b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000019944fc459fb601bddf10a3a7eb55f34713d396c3208a10089b8f21f4bf0a5e87e95ccf73e0bd90474d3e043f37a72300000000000000000000000000000000158445bd2b6d396a390a0bd5e26256587f980eae84d7a592b2b4d788c452d312b854427185a770084e1b4c7898962e50000000000000000000000000000000000ba44a1b912645354da7d8d9c694b1d5a9ff2d642fad31975171deef3adb0f8d92b2d3a8bef6ecbe0b8e90470b3938d00000000000000000000000000000000012a040a72ab035684bfdf57bf473ff59cd1ee01ec949dcc6066e5c8afad775ed55123859cdd74c7016a092bade7f991b571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf000000000000000000000000000000000317ca8fdec8c7c56fa3812157f9ca8e9bbf91013dfc7052c0795a04a1b4649b2147d9cb1a61f2c114a705e5133729920000000000000000000000000000000012b893d50fe5ea2eb528d1a04bc8596b10d4714a0dc38bdd5f0a275c07c846970106c3f7b5686685f5c809e93c57e2ff0000000000000000000000000000000014f018a0d13c4c494f4a6b7e836f0f2f46c4b7975d91adb93616a0555910f53574add03b905000f8492465c9b5488c1300000000000000000000000000000000146eb4ef1103b525dfb5c31bcb98e550245732fa252a814824258093a2397d1489df8ca0228d4f5df0a00d473d1566c454c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b0000000000000000000000000000000010e9962dc19aae8e92abf32fb9c8eac44d77f587159af4e3b3a080748322715a958d953d3c057999839a47dcc840076a000000000000000000000000000000000ccafa9761e654ba54a46afff51384f1c6331264082e23f94fffd6c31a1b1b568a391eac79417657f40ce2fc9a154a670000000000000000000000000000000007276b111c94130b2608827156021815faf2be29ae42c454f3e2f95de98d2f5b98cea3eb18335a8fa00e5464f8089cf300000000000000000000000000000000053550896e867e237086098f4493caa2520e8c97a05e14d0ab7012d37b7fbbd42a90accbf0fe2ac99e78ccf0be5c9c58000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b0000000000000000000000000000000016b5bca8537059362147911da9e69ad3ecd3b4a7c43ee7d6d809f46c74c16bc7d69bfb5d7c727b4d5d8a356a0458b59e0000000000000000000000000000000010f3a7eefeb3033a733af7d20c3c5caff4c409305de8d71e08cb9cefbdfacda41bb975c92c5e5f2952c3c1e2bc6ca8cc00000000000000000000000000000000148f5b2bd65b71184ba6974678f709c5f9e3f1a020e3d4bedfa5f5f66478adac47f06ca2626c4a759b5eae09756cfe49000000000000000000000000000000001301306d1259059b5567154ef6e4779fedf98c29ea967ce34b78147c5730f202e1c12d5b5094219bf85fa62834329b45909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000001741f739459f5d462fc9ab55c68101a5a3f2741c05b4c3eec6959b2aa5e12493a19d1b33a9aa89337add642458089eb6000000000000000000000000000000000300d8b7988522706c0690da52d0a67ae41344e43cfa05d22feb91eb8635bdb970810e993e0ebf8fd63ab8fe3e048d660000000000000000000000000000000007c003cfba125692b88feba85e7288bf61bb25e04b1462f7a39b4198737010224ce4b73a320c81b1f70119af34d381d1000000000000000000000000000000000a4870c9de67517f4353de23af21fcfadcfce55365ced33a61a19e5de52f98721b17c6eb382970e7c4acd81b80a7bd2f6d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000002eef52fc72d5aa0456c13808ba548cb765e11cd0bfd0599544793f57c8a27ee90880e6577af1b76b3fe32c4e71f4104000000000000000000000000000000000ea99a4f6772f8114cfb3ae9dc20f11a34880a86088511e5b7fe521d50470148b43f866eb5bf4f67c523266bb55117050000000000000000000000000000000004bd802b889e6d18df7dbd65f39a908cf5889e14be51b5ebd423ccb63e4e5b35e429eb0d4f384b811b47975143ea2ef60000000000000000000000000000000018dded357c546d709beffff2da0c08e8059c720023234c7b53d0ae85750b3e166cde7faf340697b546b8dd7c13b1ce7bdcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb28000000000000000000000000000000001405ef521bcc60c55f8551fb2e2aa7b10117b2f96c03e8535e5bff48ae197b7e5fe69a40eecd25a67f430ca02edcc9d2000000000000000000000000000000000477d85a7dfffcc5a2a1048205362ec42b268e5fbd27ee7c8d4ca77b5c9db84dba482bc4b164f92db2c15cc518b3d32800000000000000000000000000000000060988548ede00aad3682fe827d1e993ed1cf118bec7cbe6f69bc160f030bf87c299d40047a4fb5ee27dc2814649a4580000000000000000000000000000000006b9e0579f82fcb8bc149e40b1199f5897ea48ae5eb58abd2002c923efd0f5275d24a579bd904e49b7447c4a03e3fbe423d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000003cde2bfc5a865cac624d9018c37c1b5746b5394597d79c171b25f84d5fdbc76bb90ba5cf9db14b3b8e62ff91cfd79520000000000000000000000000000000017596885262075e45db62ca68ee5b99d12223bd476e36ed4ddbf5cd56a0c6e9db5d79e7f95b96b1bc323d7c9fc5447d800000000000000000000000000000000018333858871dd41cddb7ad2f179f1f341b2ef20bfc7a1d3cb235e3a1a181e0da7251911886f0788e0f868e16520c5a200000000000000000000000000000000098ce44092980cb14e89faa7efc2906051c9a51cf7b2757dbffa49fafa3a9ba145f809f1212c27aa620bf062e839f83c2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000001180d554fd523a51e0decb92e0134c6064a17dd3aa7b11d590b9b6022f76763b1e20562da21e836e65374efafd78b77e000000000000000000000000000000000488686f793dde899a3f4936f07f9eda7918450966ca85b4715d6fee978d9d091bae1b5d2d04943365c076a849b3359c0000000000000000000000000000000014661fb2d305ec9e63d63e9951d0f081aeba99972b094c922d2797a1100759cfe150812821411205f563e22f01ef29c50000000000000000000000000000000013dd681200608466853cd3bfd20f146a6383151931079654962684d6c6fc3bd6900bb049483c1ca6d2819da456f67e3be3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e0000000000000000000000000000000016c8c7a2a1a76ec05770f2d6c8df35003104c034c76323fedd49663daa759caf2f4fefbe8d44b3abf1dadfec2a06cb45000000000000000000000000000000000837305004aba2e322ae29e8f0109f1c756a44b21c72733019e63ff9886a639464090770d12d35553f0002ad028332370000000000000000000000000000000005c8f82ca2d4f6785e2d76ca3a3d1ac67aedf78e9ac833c52cfda6289e6f5d7a83befbeaf753abce12376889caec312f0000000000000000000000000000000013595cdc9181ca70845c613663367ff774f073774688dc58edfd0c58de5ae12df5acd04a673b645371940d7f7e1601045d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000010f1b8b39ea8ffcb6a96bacd1c00b413c93d3f8da64dcf9257a7cf0264831be23ca63ab8d3d1cea21ed8d83ecaa3a0c70000000000000000000000000000000017a9030fbee573cb71330007900723f85e9e82530283f713f72e68c1d9a5ff9552d0da469a4f38b66e30df1514f922a40000000000000000000000000000000018b9020986a49213d4f3b4b052cf2fb65f82b9bc2051f20b399f2784b984ccfa2752ca576d352c7d65ab218bb8d5df870000000000000000000000000000000015a375a3711f5e9f85ad7266b2d307cac09ace9ea36e149dde5e0d5acdbac3f62e1cecba8be51d88f2143c3070eddaf0fa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc", "Expected": "000000000000000000000000000000000b668f602b9f56182b74be413d36b03d2266d7165386a7f1f0d25d336d06d2bc5659e80e54dc71f153883292df1cd8940000000000000000000000000000000013151d305bba39734538fe9a2717392bcd134ef1f8c1879740c8cce981a2d70c94b23f1a71a0596e7ead55a55eb186c80000000000000000000000000000000000e5e7c268f93d8a9d3ce45db2a95be906483aefa3831ed2ab2aa357eca0ca231927c0e5031daa200784cba33b96e51d0000000000000000000000000000000011d57d9a9123123f9fb56e990626346e5c76bbd1a4b3349c3f7bc44f05a899f9c4dddd67ce5a788f85b4fb172385faef", "Name": "matter_g2_multiexp_49", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006a7946b50749147573991c91f13cdef4e9558be37736e3d990c5750d31ba9711721b88eec529ea4b1dec1c935fafa9a00000000000000000000000000000000078d8a565b7f58b915c220882a73b6aaf100f2d54cce2524cc3a80d9b526c3058903668d17427a617ea045c3322ec502000000000000000000000000000000000733c6562cdcb28d740c64f50ee9d204af4ecc8de2c1719fb73c20f2580fcf01e1e494faf4386764e03920a4162049fb000000000000000000000000000000000421365fa828affe963d145d318065933d4d865f2a3d24469ab0db66dd09a574945f8a8b622d079a7ce1c6fb6c795a8f6e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000a4fdc68bda287bd819ebb0a296212ddd19cc76b042e134f1637c894ad64bcd8431392c9791f2eaaf94f6c8d189846760000000000000000000000000000000009d974fcb46fe81d81d62b24b805ab5108c9450e162454c3260ecd0d5356b7c263be5f78f6214cc7254e461166910d23000000000000000000000000000000001081fe3579cb4d8a7e7d43ca8cdda28e1f9ea8df83c6069f4162a2a0e68e0d5876b283193649018e754c5c8fef101f53000000000000000000000000000000000ca4faaddc4d14a6648e3515a8b9028190c17f771c7de086fe4624a3008d7e6e374c967f303d9511b9da1a95409e3cb3d51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000117318e376f2c130e5bca89b3d700fe76e9603adb22a5ef353bb3b5a8f641c85deb4640fbaccf94e025a59fbf2a41370000000000000000000000000000000000433428497ed89a43ba07d816df224809a827194ca899924c3844650a3800952cda8db82f2f8e513994ed9893fac747400000000000000000000000000000000064889f1cb7d6ab216fcceef7c4abf89be14ef93be2d39bbba2b74d06999dec5ae1941b507709d093b28e030700cf866000000000000000000000000000000000957fcb8658497802e78b8250373f77acc4ec47fa2c87e78adbb2daef70240da640a7945895940f76bbb80bd36b4ba24fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000013f9771c105462fb6b975b0b2fd20d0accdd2d95d879c8019b08db394cd785ed9f151d0eb1adeaa63bbc2686d1172b0f0000000000000000000000000000000002062a5f2db0a01114a1c6e8c739f80f598f4e905952101a244853078298eb443be6839b59d4f0c7745b739cc89ad8220000000000000000000000000000000015b5485439f1b94fbb3a8f5ac6197f0dc0577863f39c44b34d4c5437b6a82a704dec17529654b3037a9ee1ebf14c8d8300000000000000000000000000000000154d750e2a660205812d428cfe79aef4e1059f4e231024a665889d112af37e6e17e04cd7c926b6240bf2f616a1f572dd7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca058129949000000000000000000000000000000000231223930956bd2d36a89a0a0a47aa46b4763919455ad3a3581439d25a82c176569698fd5ca2b9429793ebb16c98e50000000000000000000000000000000000b5dd675af51c18d2dc76e3103da4409f6e8c1cc719a622d4a33aaae3f23e529c78b63c55b67fa999bdcc7095a4ece300000000000000000000000000000000010c971be55cd02e4c97031d3b25acfbf722e47e5179beb26eafaa72d4bd5f47cf280a99e0c3c4cdac05bf1572d01fcfc0000000000000000000000000000000002c1370919e6445994df1e25ff4a79c8cd8805f12e5d8c781e58f04dff68a97424b35d162d875ca2b3f805b4cd6d1fa641b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d6500000000000000000000000000000000169938b4d3c859f97a0627bd1a83fde725eafb7ab77b22cae06d2a776569236d834702081e78d61386999c938c0259b900000000000000000000000000000000091e922f00828488e324f9fea652ce1edff83d9f479e843ed4bffee27805a5025e7a150719b354b5e61f381ebd24d4ea000000000000000000000000000000000334ba8044d7d47795b59eb089502808a7ab8f18e3d5e1cc49acdb5020b3973fd21d6d82557afd748dad88e45a7623730000000000000000000000000000000002299bf949ef249b5057c103ecd149444635b4f636a2fd0d073484404c1ff4ef71820260ea6529bee6f5b07f2ba94de35615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc00000000000000000000000000000000076ab7838db87727fd653a3b561a2a5594518f296284bc24a7d215b1fbc0a6492d425078fd98f92a414dfcb3c92cc1d000000000000000000000000000000000022b71fb467dbd6d9b130763350bd06f52d20ff2cbd46cdea5e8b1525fd73bfd08f5ff171f9fa28050e9a3b296d3e9e00000000000000000000000000000000007e917cef0195fc589317d4a71c14022867dbc0db26c653052e2e382d0dbebe67a0f582bc0a27dd1dfb4703c545d0da30000000000000000000000000000000005b1d8651b86a403ad993c5cea4b6b82a0f8a9f8a59d4b94f10e68e9538a559efdde2007736aa9d04f585851a89af88fbcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000653c5f5b2d97239821d173036929dc716e78d835a80af55868dcc3e218bcebdc2a052d31f6a573572d13f3bbb14f241000000000000000000000000000000000cdbdc3cf52239a6d4bdadc273b00924de8730c03ea82bd20ec1f04375daa4497fff3a1726269a736706355e72be83870000000000000000000000000000000008e0285b177fcd768d3519062177fa1314c4370f872eaf10f3e0dc94e716dc6a67894d887f40104552336cbb5ed614f20000000000000000000000000000000000638db8269ea4c2fecd5b45955609ef6a1c2c6faf6ee5a8d777e0b38f16d1acab2da7fe7b6f6ebb315ccb345835a21d94c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c40000000000000000000000000000000005b4f74cd099eafa6ae59e7105873d4a46e8e5985faf2d26ca564125dca93b1c48187ed7afa02cde8b52df878e1aa618000000000000000000000000000000000cda7f9eeadda16ef757ee8a98be147d374d3a1d40790d20a1ae42c9ed38e4fe22be76ec4f807cf93fff5c6efdb50d1c00000000000000000000000000000000121219b0b0d236a89a857c02249cc04c22299d041d95296dd235b3639416337f5be4a2ebe92a50d192fb748d5d4dca0300000000000000000000000000000000112545a4677ca7d60645cb8bd98689c4aa85a68bb62dc68c0affca5a17ecf0a08fb9b91589d08712b5af4aadf31caad2dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f2000000000000000000000000000000001342460712b73ca0ef07d953c32d280a3441e108abdc2d133265160608986481df3563c5dca20f209ce078b13b49707e0000000000000000000000000000000003580a5b4a7f6d6e066ad9073f7105f6cc1ff35ef5e79a0aba7f48ff2b732c7aec72cc9c5f9233fc9c267d8aa37ac17c000000000000000000000000000000000bb7f32db8a4e341cd9f8dd3b5677dc650cef675f0923bf2e5c8b84c33d447daacbf68631c2388eac5698495e1ad5a3a0000000000000000000000000000000015bf9cd1aa585eda2910128f2b452569abc1c94bc8bd308ee92b6c7315a56fc92d6cba03334bc36c137c14eb1f198b07d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c100000000000000000000000000000000091bca266255d692cdfd10929802d79b474706d160033495decd11cb0758136ec3ae7fd4bb99081e44dd7f25224e009c0000000000000000000000000000000001fbba1ba796416ac22c92f3741e3b268d89fbf0307edf0f25c7c12b5cd230c41582ba69465686ffead9f8363dc0c297000000000000000000000000000000001139590315fc4d81e3e747a53e63ad856635050367ffc143c1422e324d5fe9e4fb90631ff8bba764a87b8077b571aa0a000000000000000000000000000000000dcbba28afd445a57db762d08338a26980b4efbd11668e4050d18234ce35a909d6b563a5d3e8e72892514431fabf0147aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000001322b1f1388a9dd2853829bda1a5120250ed08f07c84fa398e59fa2577454f38f0a76a1e8db897bf15b4b50ff52a847c0000000000000000000000000000000017020d7de1dd424de53992c168d924c42f26231d184ea3cd9cfe64ad9c82ad067540b2d9ab18b0fd28477ac792a80c4a0000000000000000000000000000000000fabc0769b95e6feedc2165bd6d324b7d16247b79eebc1f09d849792255136538e628bd6ad9b86af7bcdfdd991fc31000000000000000000000000000000000144f39f792bf5585f4b49dcd3fcdbb61cc7ef471e08af4c15cfebb855f0ac8d5fd057c9486e53e8e1ee4f66bd5e943ad106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a000000000000000000000000000000000c27ac29db98fe3038fe5f537d5ca6faa240602abe11c6f530d9b18d763d6dda3fb25f9538d316e6527c114405ae54f00000000000000000000000000000000017ecc872183413d8065a99a2d1a73b70150e2c1fca2c13a731a39b52aebc6db79772e91f115a63f7b23e5fa231df697a0000000000000000000000000000000016b9715ce820b619274202b52d7e7bee9a17aaeb06c2ecab8bc77c670bd4c714789e4478178d94d2aad57e7bb0b7a4040000000000000000000000000000000002d0723a3386248d8597d2b63289300de6a16011a38985170a1652ff81ea70a78459b3ef252cc5ed26ff1ef1ecaf6a42c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000eb1ddb7306d8d2858fb57dac71f67473b813f37f02d73b17f375be86028176cc1dd84347f183cb7d427b861be34c3d70000000000000000000000000000000009a8811ec77eb21f2b33a591f2fe6d7b74b40c5045ceeee275912aeec664838f332bb49bedcd958ede0af0d0232e76ed00000000000000000000000000000000156e28ee3c40c6f18c6059e06ac8f7b39fa23e5962f640ef3afce13c169346a4c8e5c2bcdac8fa15921a4740cc5a0f2300000000000000000000000000000000084371522a6ebb1925c8fad3f20277c34e657aa71abf8ed7d323a10c14cbbb1a9e0e54bace32eb845e6709c1c58afc34a5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed49000000000000000000000000000000000be2ef9ef38a5dcb42ad31b1415c8eceae625850db4306a26a0598d4a567936d75b701c81793fa7b42d158df2dcb0d5d000000000000000000000000000000000851b82b59fc15b89e33fb618c56d11a07116ea35850583a07066ed97b8a864f3766c0cf921d007a6cb43931ad4fcf8e000000000000000000000000000000000ea8bdfa3c5f000d7cb1b5cd69537e4104daa15ffcec06f40a91b972d8011e5fccfa911c55a07383cce6760c145c39e4000000000000000000000000000000000652a4165602978004ef702103ef18e8fe7decab1522a76486c742d29103e3bdf6dda2d3cd64ff1b5d5a76f4823bd363083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a4600000000000000000000000000000000015ce5b43e1fd950b77e2baccae8c99b82f38bce09989fdc5d402420e7931a38b7fdac5a254b0cb9bd8fbb488d02493b00000000000000000000000000000000018c5b3ff46a04ed114bbf56399738e5d594ef8dd1d5e2e8dc23a0097893be3da4fa4662686a6dac04418fd2d344e36c000000000000000000000000000000000efa3e970a5cd0c7bdef6a2df3be9be18cce63c10c331a18d628bbeef30488ef73d866f3c8804acb3bd375542e99eae6000000000000000000000000000000000e966d9e2f2d47df5d661a89fafb6d4518fa1544ab7a56716df511cbcca99098f944a981c9da569cf95debb455842006d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d", "Expected": "000000000000000000000000000000000ef06b515addb951b24e5d61f6e6eededf5f93f9f17455e1b563f187f73394457b3b7c1b90ed454218f8782d2bc848be00000000000000000000000000000000167398608a87490fd17506166bf54636aa4dd6d3e8c4d42995bcb0262268eaf2a6d828b295434f45e3e53703aa67cdcf000000000000000000000000000000001602ec6519e4987a052f97eb222f505e241d99602c08ea9c41bc95796675ebf6a819aa0bf87319f29dfe47f45f3c8c7a0000000000000000000000000000000002ad4291ece7ea0fcc9f4440e88eef693b8dd53060ec847bd27d74cf71218eb6210a71895ff1f1f4537a901090f14de5", "Name": "matter_g2_multiexp_50", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000129cc9cf82aa30671e969148f058a0b8d275bcbb1c8da66e52998d9555dfaa075e2fcf39fc18f305940fbc972cc5c0c0000000000000000000000000000000001252482c1419ad72229a00d90f1c09d464e896f47db91e9680efe36822e99e1ca7a2b5ba8b17b5929fdbeff8993bb18e000000000000000000000000000000001287d5054bf5db038ec2f7b7a3b79848fcc8ca42f9e19d5e21d36d2256f97e0edc2608d19c17d3714024e1e9e86ae264000000000000000000000000000000000f6262669e30a5db67550cdce8a4611b9501d02cd4e950aeedd1c87c4f0f63c74f35c802dafcf91c988a154dd690103c67c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000019cccd010df3e668b1c3ec053e76c46e45d01a4fe02eb074e296df2a48e0e4eb647b06c40cf64a0048a8fcc2b0cfa6100000000000000000000000000000000018e07bc50657f3bbf736c38518933e91af29e3bafd776243296cca3a1d975116e8b428b050045a61069adb23baa22d3800000000000000000000000000000000154f51badda1b828346986264b01fb1be4c7e9b570ab63a5eb15cabe9412a2f9bbc6d5111c638ff5118a4f6d08ed055200000000000000000000000000000000064d4e607a8994c0bb65770a14a14ed1b68c766ac2aed45a44c0f7c7cd4c3ecdfa077206812cf9b24e35021060a3668d2d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba94000000000000000000000000000000000b9bae5f720d9bd3271a71d751e4c35c39ad30fa8a67846107ff769c455e42465b2a39dfa32861634d5e323878f56f4f000000000000000000000000000000000a1077046cf5c7a66452cec2193ee21c1ace50dfd79f707c9297737f13806dc05e9e1cc5d1fb4c87b250ebea5a4ca6c40000000000000000000000000000000013e1fbe1a9b5f2ccff51120590ac0cb00cab502726b43a6fc12459e27bad4aa41537d6f3cc94a81a170998768f6a0fc6000000000000000000000000000000000a02c551ecec1c7a415256caaec1b5485a42f9ca8d897cf26546ace1f2bc8c2d10a353b8b84495b8dab5e3c60881185a58c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e700000000000000000000000000000000021b9ab3ad614816d7006efe688e1ae8cd99b0c4437d4363e557642a7cfda2000db6503b32db36b6d1ffe40d967c5efd000000000000000000000000000000000b7fb0ddd9eb2be9cdffaf8f8c593a9296c4c7deeb1c714c11863d71dab1e6fd309b75c41e25de3cb6089726b43427f0000000000000000000000000000000001277065ea9d208777d0fb7a6726e11c8330f0b3ed3c6716acd559aab19b2fdafceca8126c9facc43b9d80534c07035a20000000000000000000000000000000015e8c12065d601dd5ede75bcfceb7300bf6f9fbcdf68d2f093b7654d80d3e565135d64137dc401d691a45fe052f58a6850a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c32000000000000000000000000000000000484c4f9652427d0649c33e93d666dcb15bc56669c00980c53357ecee874bcdbaa016236df65a4339dfbd44e4eb0823c0000000000000000000000000000000013836a7275c29c989891c94e756cee9d6c54a8f634fa570655ee44b7c1e34137edc33323dc0d1f3a0218039fd6f7013d0000000000000000000000000000000002e88c7d5fd87e97a0de1be95021821942a8004115fb4fdd9ad26b7e0fd171f9c7e6f962eb179bdb95ef960cf9396372000000000000000000000000000000001636e351a0ed1a260ffe0d1355e6da288792fc97a7310b040cb9fcd5c550d85d90572154d58a9847dd5a8a06456bb2e43b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000007579785c14fa012cb5d6c116d34dcc15dfc908a29e90de3bbfb8c9b44e0b4258644440d7c78d751a007c10f98053bd10000000000000000000000000000000009f023538822ceba0883a0e3454121dafe8e5e61d4754b54e6417c989efa998334641d458591b3076b615937de065cfd00000000000000000000000000000000130fe7f2d5e0ffefa67ad3378690c53a6e68de5504f3691de0df3a24c309619bd3a345bc2bec4dcfb4b77255cbfe09980000000000000000000000000000000015bf85ed997eef4d97a81f1d75825bb4409cf86b8c8e5f4368cf1e4c803f9e1e23a2a96f7b0a08e5cff55a78761ebce21d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e3921000000000000000000000000000000000a95597e4402bbd17c20dda088f0134d42f14443bd519b3511b28fd8d395a0e50758386498388ea6ad0e7634587336c1000000000000000000000000000000000079f348d3de505875c5192f795cd77e2f7385ed447b06f2dbee18e85c832386b201cb3eeb21aac3f258d2c4b0434d48000000000000000000000000000000001895b1891a08ea42eb1f68698abc20394ffd66bf0c32979668950bfec5cfc8425314eec2ac17ba25f29133a8becc9f5e00000000000000000000000000000000146160336d881b24c6258a3a86c08d346900680324632b6d5d4582ee0865a7e5f2d01677e5e49c5a4179f8382e49d1566fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000013cadc6c394efb2f93e00f3976dde34efe75adff34bfb6f5e1a150b79bb5baf6bf29fa149581fda48faa68653cb61e300000000000000000000000000000000005fd25362d87f9581a202b186d2786d2859faa9966a1ceae747dd7a48749abd424eb9813e44caada0e456ee8bd12e99c0000000000000000000000000000000018e6b279e2b545acf1da29dc0504caa5982522546f83d4d3389e1fd6cb5328d4a167926a00ccaca402b3a3cdc67d757400000000000000000000000000000000089a9992a36b476fee21abd50977dfee01d7c91b24b3e26d7c15b2301352069dad920f0ea93a3e477a48029eef35605f06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e1188620000000000000000000000000000000005200b78dba7e423bc23e87c5937b464e97405f6461d05bd9d1d0fbf8f3c8e64a39081f9e43b4ec416198dc44db897e7000000000000000000000000000000000bdba1ed07c4a570359863a1098a73830818b3fa5b222316a3e0692a4ec65e59ca6b4bf5f72f8c1384e73e807d272d6e00000000000000000000000000000000073fa3eef473707b6aff37fb6f829f0fdb7ae808e85ebba4d4924a185c3656eb2856896307b671080347cebc32e958bf00000000000000000000000000000000076b56330f07cfc0ab34e98e2fa0ce4702b296a00f6ffee07c3ab523fadc048a047ccea7a9003c090951e4ef698d14e5fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab0000000000000000000000000000000007252747c8275f87b21bbac4071c1826d166d14e6205095e5299315d6b6a85aed995f9ba59a2163ce2c51a8e60eaaeeb000000000000000000000000000000000460a000fe29cca24dec469ba5fb729edf3e443bb032d488cc99102a614a5251915267db003dbf395132d815ba78f262000000000000000000000000000000000161c01cb4d0942faf2303c108604babbd4cabf5d3d30c13d7db9428a445c7f72d96a7405e22e4e451058a94e20068720000000000000000000000000000000010ccf8a8ec4e6515b20e07057fb8cbecc5defb87480f3e32a1bcd0cfe239e00daf6a390c4815ef6b85be1f07a4c4bfbc4a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf000000000000000000000000000000000e6cf781162502d2a758d0f96946bab887591b7c9ec9f67a1b0b962e74ee514e84c14bf67ae3c0a9ea2a3e472b7ef59c0000000000000000000000000000000001542b4e97f1e8a64ffd51ca43137b0660f897f6b3d5c6fee598fc4dd03932c3658ea55e1e9e73376e51df278ddd3a3f0000000000000000000000000000000016dae882ba240343e752eac68122424320d1acb1fbc4bd26c3983dd91325f25e1b1f06213e0e06c142997a13fbeca597000000000000000000000000000000001138b71c95d4de320f02e68dae9bb0de3e5b317cb596532c5cc18ca588cc8566c21551d7d55d685591126b9d9e466455cd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf000000000000000000000000000000000904f6a09f3a5f5baac902c702b059835737c06f62c2ffe9101bac32f854e4d72f74031f5410a5941612b1aaacbb50920000000000000000000000000000000012f39e7022150b2be12cdd621ae23525581405021b21cb9e55972724a22b1aeb2e15b135ceade132d3310e050e607f65000000000000000000000000000000000a92b1daaf23524904d74c3f149fcd2c98e3a4c257113533e7cc59c4656b785aacbf0ba6b9df0dc17cf7c25f1ca698c5000000000000000000000000000000000a20a5d7c0aeb16ff498f46bf05e512784d120b9c3c8b2877411852d7da3abde9e83a6d00213bf69ed88bcbb051a486daba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa00000000000000000000000000000000153918807d7da07ec7014154f00a558ebe0d5fb48fba4c16488d61a826a1eec28e3828d6744300c04207e8ff1cb61211000000000000000000000000000000000a755480457896c5a3fde35658e73fae821151c43fb92e9ffedcb05fabad37cb68aa24e029fc33a2518398d723c4859100000000000000000000000000000000148798bdc5b14b90aefc38946db93be1754f15d78762f38971b1e64a53fda92b96b0a70ca2548baec882887ae7f636910000000000000000000000000000000012299fc413dbaa77cf8867e331bc0602c4fb32fe44a150217de9e6391374a9ed83781034e5775c4933e13cdfffd25a9e6ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c000000000000000000000000000000000fdfcbcce1603198fa344487d2d4838b3ff23fc0a73a76222707d9f8623f0b87dfc816be8717b0b12667bee460ef40f70000000000000000000000000000000015036dff68139419db619912e2d19b7d2a2d637fbb8bffcd941aefe2eb4d24c1f7dc32f4f53d4cfce67785e7c328d6c4000000000000000000000000000000000fd575be9bf54128a9a1cbd366339c993ced315a840d60f8b77e035352bf705c01a9def713e8cae3001dc1062cc0723b0000000000000000000000000000000004015ed456125cf0f46fe0093b81ff9315d955d470ad756a9303f548819f339e137305c58e6f4d8db3c8bbfab90718d4f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000016d31e68cfdc5823970c8c2ebc53c3d4517792c44e90c10f920a819e72e4a6966c59a691b905c8b0b612065c56d86ca40000000000000000000000000000000005096d516e416fdc0df552c2688c74f1c067a3e5e7fd782479bfa468096e6ee3e601bc23d2e38ae7500325765483250600000000000000000000000000000000092c994e9dae287bb6450607a4263bcf6267f0f66ba3e63436292af7f6bc8e4ba794a12792b6af49ef59b5fe50ff6d3400000000000000000000000000000000175a645988f33612e969e1d91b2c30e47ec655ad655d89cd8dac151c3bd194cd5a8c28b498b1cc2f2966b7fc37cfc8c532b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a0000000000000000000000000000000008d09ee15c80facf7e32b15418fefbf7e80400acf37f2a1bc6ced88b1591bcb8f86b45b544646c5fafa71b5b103b927400000000000000000000000000000000060865ba68ed8fb3d0a05779c278352b22d4244edb7add23d985a2836d2772dbffc3c82c3134916e9b0900c9db6ead8f000000000000000000000000000000000dce53bf8aca1ed44bee47096dd988689c1e32e1e65a5f8dbabab7c4edba866132ee2c036aba5648d0dafa9a26405fe30000000000000000000000000000000003319995785be720860bbf48692d1507185d898187993865648ded74d3aaca45df939c6dd986db42a51bd13579a55b8f71a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6", "Expected": "0000000000000000000000000000000010643af30c3cdefc30144c5d7cab17c9c54adccb3294ae79fe5c69376011c159be1e43940640bf5d9012ccdbc997e2090000000000000000000000000000000002a22b08904ea9ca99103a01caad745dc2afb7b6d23e666770e81a97031de921f9d4d1c04fa941c433b8cd9cafced3a10000000000000000000000000000000010808e5518eb6cd61eec8820b9f279dba2423b1a3677e21fe3a0ca2ad49fbab2995de1c5adc9ac867de79e3b40ffddf30000000000000000000000000000000003ce1270644d71e0055345c7463d72dc119495bfa04a818dd398d944ca46deb0aee8c7936557754fa18225522fb79564", "Name": "matter_g2_multiexp_51", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000059234bb6d1b66985b79e6a2c6cd158d37fc50ccd6e50ad2fe3b6a02ae2ad35b8b2be92139265957ea698441e780532100000000000000000000000000000000066adc7083a7f3dd75a8e431a36632dfcecc2527f261e961335bbe8fc8329d992880736bb41fffe41484f68c38fda61a0000000000000000000000000000000006aa0794c27d3f60debbee292c28b430c159cb2874b9467312cb857a8777058580f8a2d3b9bf4b8b732edb185cca6ac10000000000000000000000000000000000d81f222ed6acdf29437adf26d2b785535cd6d61b329df98be04114c3ae68bc6854e275792fd48de3eedf6ba7f3849a2e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b20000000000000000000000000000000013dab8066757b8bcce2c9965e600c31b792463623cd5561f7f6d55c5a52c22efcbe48b8684fc2dca87e2945765bf565600000000000000000000000000000000198a0594b5e606b18201fe2706bddffe7bee6c583147513333230715d18295714055b984cd3ff8d1127f9420863e3a67000000000000000000000000000000000ef77ae1e991daea1fe8338cf236ba959b22df4b24f00c6c01483b6956c609b805ab89712f80892bc0160fa3775907890000000000000000000000000000000004d30f5a866a100dfe469d4d0c47872245c4cdcfb18d3ffa0de422691044b52d2b9335682dcbf67aafa9275712ae3f5512a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea77783559470000000000000000000000000000000008010bd5fb5e222618bf4f919c203dadf9a7b7597bf90e16772020614481a0963a8e8b1bd244661bd33e0d147be7663c0000000000000000000000000000000007e21f548efb869a28d6fe39b999ab7fedae9cd6cfa531fe608476ef30c8703951839476811838608dac1aaf9cd87eed0000000000000000000000000000000006cc674c464f80cacb2156fc1eb680938cb38cc166a99f72daa50f9d2c40f10ff07e447d7bd5e59b6b22f0dc407dd8df0000000000000000000000000000000010b160c58ea82bc3904302b1b4fe83d1883efe3c8f52c4e05a3d8681e604eabb1b7f533e61c51e9a172987383506e7b189a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000000d4f8372d1138489913654a7567735be1eacf8c7fd497c2216bf77a94f483bd4a7daa2c8232581d6815af9898a7569b80000000000000000000000000000000013676a1f72cc2ed80fa24f70fe1c52aad9ac13ad6cad1f519649fda6ea3787d86ea164d9ac86de96436e9db4fb4aa44f0000000000000000000000000000000003f7644f7ddc9276ac36aea5c36451f3d5d6e4c508932b16d5677977108f04deace5e8cf0b3b3dee88c328b6030f3567000000000000000000000000000000001953cf03effb7de9e62937572850e9fdeecb74fb4aa655de1abdc6e065920e6d2e51ff28880f33341443b5e6652eff4827dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c76a5bcbd2a61172fdd53b351d143bd30d460e398c9d4b7094a604ab2c0d46d6112bd8a5483c9935f0bf6d84df04b9500000000000000000000000000000000116b15825b780c49fb24617dca620e939e2528e10c49f34971736c82cd35fd3965088595deb86eaca3d3239c6c78a84f000000000000000000000000000000000a0cdaa541dd96fefd46b303b88f1dd4d24257b910a596817f1d946873cfd60ae58a88aa687ba573832331e8fc158db50000000000000000000000000000000016259f7285de159a2c6d6d8687ed348ab97e8cf329ea5de49b6d708b6da5b806bd012ca3641c50f479d85921e20fbaefca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f84000000000000000000000000000000000797e815a98d362e1d7e2ac1fdcb477ccdec8ecdf340d7bded36856ce30e92b661669b38ecbcfb0896b2fd75df9b734a0000000000000000000000000000000017916c559db6b4b28b798c2027e2c70ba1b940212df8a1649b9f6087120660d698bea81258e2007edd4aa7d0d535bccb00000000000000000000000000000000167170a76db0783a8c3228f8246502b15d342b019fb44a46b514f4ba2de3ac66e435941adc3d91874371561870ba87150000000000000000000000000000000010097a585eb9264ea96904d8534820be185d8d9e4b1616439a926c0ff8ceb6f2bc082e5712454690c9c05b8018a996235aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000025eff57e1f37903056835d1b4133ce064c86947f35859817b2cccf1a5c3923ccca766b3e0affd20a4a6df62a45c31000000000000000000000000000000000011158fce4ade070629162b2b6cf1924696f1f7776f3d623cfa3d54c66fc17fa0299c6650b709a1472262fc0abe8d9557000000000000000000000000000000001828a65fb90dcebe25413566deacf0677a3993b39d68854b264fe7807097fbd3106ac618545d3a6a42e197c65f0d2a7100000000000000000000000000000000045eb8164b6ec874467286dc3626fad3c01be61f6a8a88e5f88797978463db648a9b8a1e1a2589364ef2879cb5f75423fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000011847bdf2f67b40aac3426716391da488a8f0462b68bd35a8c1c762591e2f426f48f979a646a094bce16bc99cef7fcfc00000000000000000000000000000000092d61e408120b1549fc8d2174572eb7ed3f679327cb89754f326fa72fbff79e98cf5ad9c94c14dd86135e9aacc98b98000000000000000000000000000000001440e2f4ee2ba254a780a31b02babca093a38e5a1ac09ff388080b6c60918ae5b26e1c0888ea0976527ba103b257d02d0000000000000000000000000000000019797e49808b756128866fae0d6aa7e755a1d6f07f7e6a877bee150fe9adf0cfe612350c5a0e31d68cbeed226fa56f2a28d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b62240000000000000000000000000000000016d723a64ee06a7a631509c6e64b1c8bbe199952da532dd92194147bce9942cb4a76f2358e6c7d263916fa36e2c0c09d0000000000000000000000000000000003d04ce655cad1d63748f6eaa9912d6474a34820986835f60c812aee9980d3ebc18d6fd856a6de9546be024b2e95126a000000000000000000000000000000000ea840bd7f76f8e944f95146cdc9692d97e6a2d7d16d4a7f054f81888472da4d60ae5faccb72d3a05781b399536ccb1e00000000000000000000000000000000155a1c43c39e9dfc6d96e01c981662900fadf1a46aa1c2fdb70bb34e94dcbe86c4f255e259c771ea8ede50b388ceb2f61208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a290000000000000000000000000000000008f189d97f7d82aad87fb71d090a5c99d94079c0b74beb3dc860d440c0f46727fc49104d671bdcbe5b9551552e18afc60000000000000000000000000000000010c4edfb64b8932a617c316820cd27d3f6ffa89b471949362762af8e10d25265b84ca2aafd3b14f33c39a4b533da60d60000000000000000000000000000000017ef3bb919b087fb6745bbf115e2929394fbc9c89f65e7d591f15da93ab785aa6828ebb6ced99d3506810647d28ed814000000000000000000000000000000001591d8213ab349017cc93f1fbe6aca6765dd33ac1f468621e2c79e30aa73bd7606a0e5ba1d97ff03e0029dbc8ab1c5f4d3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000019b3a2df3a9571b066eb451e34d8a38c0d90b6e365862bcd92ae76195956c21c59441f0cd03cc69abdf4ba069759b87f00000000000000000000000000000000082537ef7f4bba5f32db4443abc8eabceef643b0878ef83860d75ba508369a3b459cad96f1cfd872df99548f656b0f9b000000000000000000000000000000000b2fda5ba0c405c9481edd598181ed8a59a8a18462508af8c5d66988a7a58a5c9635d93b5e0ee310bd35e0091fcd4986000000000000000000000000000000000af7e15e0052576f82e36e7e2b614dd835a290e05f2ed9dad7f508b4c04e8d437e7e937a7f4c88b5e66b06e0beffc4df7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000017a1d5add5601010d138263b4793149a02e8f4f7cfaafb69fde7b843a51cf5f0634e26b6e5e3315420d44b0fd205230d0000000000000000000000000000000013ea863ebe1b1cfcf4164d78dbe8fc809d2b82ef3e5a2589ca1357e48dddf2696e910a90301ed910fae77a1e462a5b1000000000000000000000000000000000012b40d9f25dc5a61454ddf1fa9c38e87eee60e55938b411bff9cf2161ebd7d3fc930131a198e7e97dc90cc245164e7000000000000000000000000000000000054f19ed8e2682caeda10c252f11706e7f3b65c81e7ae0a617469babc5f3268fe5c0ce2e85d44fb6731e8ac132b97c3ac5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f13000000000000000000000000000000000ef0378945ae4683666099be36de3e60b5bae9c3137b702e5e4e35afd5c1e81d033c3d6b1debf5bf36bdfc4e3af37759000000000000000000000000000000000c37074af84ff596ff2c7ff963d96968464d6c8d88b69af64ae883457d02ee9ec80720661f39019230a6531a0f2952bb000000000000000000000000000000000454e8aaa2830f07d86eac7aca1d7589fb06aed646146a1b90f4959b5caed73131ab231313b50c15213f89566ed87a3600000000000000000000000000000000143516cd7a1b8da41226cb828887a0b3314cf4f87c207d1d84e9c49f0f7e548ab99e635bd126d49fd2e4dcea98f3adf784ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000019384e15a8754c6d85bd298ed550a26b51b714745bf2980b4920d6e73f59e657d85d3e86baa9bcf7e971233daff99d02000000000000000000000000000000000229d233d605a1a9f060605ae366a263594d8fa2b7797358ffe4c62431b9718d155d24d80bf5af1c806f447b92fcfbab000000000000000000000000000000000bbfb66cc0c7bcf251141c540f712fe9a359d1ed36d228379a1f3791991cccb7dfe1a10d40667ca062cccd55c9e6b08d00000000000000000000000000000000150a4d7a003cb81423604c13d0c5175183ab5f459b96842939f5c4cfbb9196db4667bb4382d2d5c92b70800adf384569a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b50000000000000000000000000000000016352fb8e2751f126fd0f889f2a62a85b95c50d6bda7704112e4487dc94417218b0daa1dd6b998662af2582c44b011c90000000000000000000000000000000016bf4c60eeaca103c90643fe0969c2c261e9697ddbc02279f0d5afb5c905a984ab2396db93555cc2dd5682a1525446d00000000000000000000000000000000014be742feb1215cbdcde21e974c74e23c7bbc2cbfaaace28cf1d4f2b5a77dde2f3910aea74bc200277e6fe0475208057000000000000000000000000000000000bf98dd3e3a8b13e487d8b1a35615b0c6b0f514f9b8da7d6402586f113974c8dc9561db797a96f4f8040c1765518d175650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000362935e552dd01b5fc5a15a76faae937d7ad086b0a67e9cd3558287274106623deb85b6410bb4e64c424d44335f3b1e00000000000000000000000000000000096f23a54cf57aa3306df0a0a4f45aecb9b09bfe83878d551a59c53e18efc5a9f177cb7fdaec1648f66cdfaebb15c61d00000000000000000000000000000000135271fbe0cc0987e82f3430eefa8e3cdcc1be4a441393bb3fac0b8e8f78dc47ba2b833d9dca4277bd60befdf33275cf000000000000000000000000000000000dc1b7512fa5f9d4ea3f4229d947f43d7dc46b7770aadbd7351b6d48d525d0144183f2c84293c63c68d5262851401ae0282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57", "Expected": "0000000000000000000000000000000001c11610b63eeaf9e00552a230bfee290ea49bf9c93cfea1b6f684c9b5a07f341b718a0070534e0da9e6ab1239d800830000000000000000000000000000000017e8107113714ebb1743c34d83be3acde096bfb6cf140e943ecd0831ecfcd097f58d25a45005db61551a01d9da46de10000000000000000000000000000000000c2eff6c7c25885c514aadecb8f0465a0fb4385eadffa082e8d4f497b10df2395be5e7760a87bc26772dd78701146b730000000000000000000000000000000011ad4e20f5c1518c72f75d67a897f30100dbb83365ef7729c3501c6f266d6002edcab8c8bc1f449c30ec3624cda13809", "Name": "matter_g2_multiexp_52", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010a26eda028649789d9e01232884e4e5a6b8e0b17169b9d64393e2568b09ac4d3e61a5996108655c24e76abe9e3fb7380000000000000000000000000000000015e4e36bcad524f8aac7f909fbb884e879caa735f80fe9890d7874be919ee727beb2a074984c047dac1d02b8712afa3a0000000000000000000000000000000012458946f1e853a7a45861a92d4ce707e5aebbf69edfe69190c0bb130141a10869e2a73e06785b568248d5b1f647e63e0000000000000000000000000000000004d8061f25edb5a510a2db9e1df850518156138c78ace50f4c9ce47734a0b14352f5283083a232602a070c3ce94c7bd93d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb90000000000000000000000000000000000baa40ea518227b007b9714ae6eb5a4e92883dc75e6328caa780bb2ffee7573dcd7e9ac47821ac449187569986bd2980000000000000000000000000000000009d43d61f070ae308c5c285915600dd9c17b7de63cfeee6fe33c9ba857b3c72e057bcb4d4ac2b492797e7d785997c18800000000000000000000000000000000185215a7fefb96b3ff9229cc3239c3ce5202a97e275ea9b1541d7bc0a2931d7e3b01942febb45c6e96e66e3605744afa00000000000000000000000000000000103ae58b8066dd62c46c14c593c768fda91b90e4840b5560c974ce69b86bd6d2c13f689b72cf9619e57c9dc8e3d3fb15e71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000075585f862c0e0e031efe12f31e159f2a8b89825ce80fdf65906474f0155f397fdc666292f6a7384cab790f071335c49000000000000000000000000000000000eba3d37a5cae738ab99ed9475c2c7fbb88ff54edb8490017162dbb16c8225102158a266fd4ba7570ee6d5ff6cf3f5d400000000000000000000000000000000135a0b0a38c036919f8389eb7bdc505a375fd75d513eecf0cac134645d60fb6030a437ac6a0fbbd167b7a77a927b3b0e000000000000000000000000000000001688fabd4ad751598ca036ec5ef6d7b314980dce7d8652163e89fad01b233af64defbcf352743ec478af42587f58177dbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a00000000000000000000000000000000062a74a9ba0e2e8d95fca478be8d18fc716243b1faf7365a55387fd7188021f53bbe780e973e7d16c9db236faff176cc000000000000000000000000000000000f949be3fcf9b38995624570fcc9e7df9964d038eca189336ec39d9e0bd05148ff7df0b48436a2cf6e249e52248ee8a40000000000000000000000000000000007472e7c366419a0cab844522c46356acdf6a12cffae941fae3d3b78e7a83f0446c945bdf7b247abccaeeaafec49026c0000000000000000000000000000000006a564e6860b97feff368fc9a349282112e591a7a6987fd10a2d4de8ae4384ce229b9db9a93445f727eeec55a6fe5a9def0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab7000000000000000000000000000000000c073d2885eb125d3e7db48127178bea2c5bb0f09eec7081f15bc6fe6cba156914fe1b1fea6cf14a21a328d831523ec300000000000000000000000000000000010d93564b2facde13d29dac198c5f5fa314a0398f30c6fb7fc9575bc83d4e97edcc1c1d34f78728729442777718f54600000000000000000000000000000000136a4ffaacf0b4a607c677ed343c1ad41a1eca49c7c48fe73ab2f74084a07cff18f07f54a7f8ea1bfb7fa3667863bdc8000000000000000000000000000000000fb0c007a907ecdff7bfe2242097caf0c5001124d112689a74544fe4fd85be9771632e7267a1cc7e9f66d7e4bb4c954c3ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc000000000000000000000000000000000032bb9f20fdb19f578fac3008396f5dd0a70860f77f8ae7771fc6253569d47b72751cd56bd373dbc5eadf55b99578861000000000000000000000000000000000c4a4bfb5ca6f9c1bd69d7377c6da405afc3128338dfddd9aea19aec5e1e0f547e3febd28445af5e27469c87c4ac15280000000000000000000000000000000003b551547af253d07625028db4b9a8da2a857bc925620c5d561bbcd3e063eb460d9407cd4d4813800551e5d0d23a2ea40000000000000000000000000000000006d5c69a251e9a042c66bd4ee92d4f3cd4e79704b1b215c15b319e09cae0d798eb201be24f407340dbcefcf2cb87da5ae5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a5802e388f7605bbacd0bb65ba96689e223379214fd7a92de9a313f55d66cc71ffc9ab3f9979b75edf55647ad3b6c94000000000000000000000000000000000f86f968b5c20a81f18074803e1ec55ebd73bc87451c48d5bb61604ebae46538dcc9d21cce062abc07b4b9e89c85bf60000000000000000000000000000000000f9fceddfa8fb5bd76fb7c8986372c32ab9fae3c26e9fedae892bb55178fa2f3432e6eab5043496dcebef46b20bf5824000000000000000000000000000000000dcf7a118881aea4e6a0e4e305910d4e4a5f3d0a8800f52659ac26f122bd63c8aa2c5583f1121275adc9af1800a007fc97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb59260000000000000000000000000000000003ee0ba2b1de438abe66769124b97a951ce18aedc8d9ed005628aeebd90efd316e7a3c60cb5a103d6f72e7a40ed8f44000000000000000000000000000000000119597c99a7a16d8d35937ea15539089741363153ef898d6bb177d9a9b6c5bb4b79728155eacc5d82571f398ac6c32a200000000000000000000000000000000116184ac845a28c4f96641ec19a07e1f8326bd45e2106148f40277ae6fcf200d64e326915cf5c927222def8deccd4ff8000000000000000000000000000000000f890258e70b973c0d69492b2e7d10ccb3997798503c0943af4255c13b3856ca4007b18cb9d638d5d9cca71c368cdfccc54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff6735200000000000000000000000000000000171035755bd519af04efdd477d407267c5a8108bd32dd6d3f1b9555f15f37ce7598c096fb5301873809f0c000457a4a2000000000000000000000000000000000bd35595246a8337a426c50c02299f297036f710b0979c7f981c6909e835c0d9556cf64e2676baf952a787e10d604f210000000000000000000000000000000006600ff240aaa026941290f49ae8968e72293ae7c2af0df1b4ebb9373199b95fc91feedd2782ce819440286aeb2388c50000000000000000000000000000000015b2bbffac097c27944143cfb22e38ff8e50e79f2336e64c8496b0b25892834efb18a765e26f1408df1d64f4b9b78fb947ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001064bd04edf96a3c76d2ace669ff72ee5edd87d32592213cb5a6a4a482154c1723bc19c7c530d164c31626dbf758d43f00000000000000000000000000000000176ac06390e3629bdfa282bf825c0bca9bc4e0b8fd90fcf2d4ee456d5bcb3ac2882d8406d2fd59faf10c8327b1962124000000000000000000000000000000000b58fbe4e14ee0af03d9aac4131abfaaba43c7cd92d530802516cb67343b382a6d2af9399d93b43d6e05f7ec827d5ae20000000000000000000000000000000000bfd241e3180cd5ce9de831b24ca50db23685bea7e008be0c6ead11abee338618728968c25a8e5a916cef8aa516667214ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b00000000000000000000000000000000183ccf0ddeb8573923694decc02b8f02162037156a8f6523ed178c13113d094521c3d9257febcfbd8f15acfe3d5d5c27000000000000000000000000000000000cf716097aabb07979ee435cf57ae36a3034283eeec0771bea24c9a1a15ea106201af8606d3fc28ad8ffbea2cf274458000000000000000000000000000000000b962565763c4cc155b2d9ea104e754e5fb4745303240688fee7e2256fbda82dfb515a51096be5ba0b111637b1a25438000000000000000000000000000000000df04aea745b9df2df0e34153269958d3640c1596fdff3fba696801c96371420a3619c5ace9210af7e0de4f408b09a7729b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e204000000000000000000000000000000000e6be3275371e533a676f8d075bb2ab8b0216642ecde13425bce4ffa8ac51cb1b4c5c789d82387f5355c27f18da556400000000000000000000000000000000009fa3a3df5195203f967322cee54a15d1e0096922b6b881bb3bce54587fdb82931c0b87de7a9dd1a21b4389a34d161ba0000000000000000000000000000000014dd5455deaa5ea4f9b5a6241c2e8b2230fabff9e1ac08b359f029f4c7838201cb88a92a5b696ed47819e4866512fff300000000000000000000000000000000181085d630d1e24ebf79bfafa134c08c0e75626dd400ce500392adf4462028bc714ca07b28b8b8f15c9cf2934a299c3092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f0000000000000000000000000000000013b60e3be9d7d43eb42f7cc2c0a7efc81c175b696e82b034c87d1238db2798d9ad6534b86992653d86755b4f00cf989d0000000000000000000000000000000009dbb325624e698c76b9d697e4f7f03e502ae1cd43b49a0957fc067858e20e8c7ede3577f336eeccee58cad53eb727560000000000000000000000000000000007f2f50be2c6fbc500ea347cd14ca195af08b835814ca515d14dd2f6078eb6def2b9475c2ce370780acf394065032d0400000000000000000000000000000000109803d612b9e27be5725f162d061b9428f363493c17eb39c097032039387d96d0939a06466470ab62ff507ff762fba78f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000008a9fcb462412c1065dc7c3623ba5a980e6f86cc813b5d8eca6b1b8a302ee4176cebc233411f2c9ff171332c66a0d46e00000000000000000000000000000000058d2e7ee02bbd4896b5bcaac0f2b09c16d1664209710945c1f7f1a53e24496d7eace99488debb32afe10d7fea442cb800000000000000000000000000000000084d7600bcb68d5e375457078672fa07ba2c87c8ec5f9eb7b61a0232988b197aff052e7125b33c6657729ce8a1c668e2000000000000000000000000000000000a07c42468c7c65fcc984bbfc2f05bf452daf17d57e669ee5992ce67517e1c93b5f7f4c9434d40f3b9bbdb3446ddb982a9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000ec380d15e0efd71958978b1f9298ced4cc3322e472d03830ebbaf2a4601c8371e6bc1cad047b0e1e429ecf6fc628208000000000000000000000000000000000b278fcc53b7527545ae1340c24158ff662683919717c220e7d2838a853fcc84ce3915f105a932872ca7f64b7cf096ba000000000000000000000000000000001520798dcd146c0b39ee727e8276fd998de0157a68587c2fde56cd82a9779b6ffbf745ec151210d1e9143856f24f01d600000000000000000000000000000000175d53b992d750b34f9daa39aec918a0ebb2f539db8057eff1409492c90f79a00f14a4c53445c028bef5d6372c9f80c680b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000fb3863bc7b468f1a0ab0e4701ea392bd820ec5cc2d7d86b58949002f24c972f51f0f82400fadebef13b750884b35f9e0000000000000000000000000000000008fca1b30d4e01991811679f261d11723086753e816239c8c7ebb60ce9ac0ea207011a69cdc29e3336e8f589b71bdfde0000000000000000000000000000000010696ff9d78b48743abdc6c1f4b44b4c960aa516623a24da515206d95e65286e453a8f275d98aaa09fefea29e71b5643000000000000000000000000000000000fb4b5eb18b6f6f8ee7dc734e8bdb625a403dcac6d0cae363e5a7f3a834c8eed5f01fbc4dc752e228c41f3f9d992bbe01b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61", "Expected": "00000000000000000000000000000000165baa8b143e3734169986e68a848739ca05330786012de260148cfd0810ffd5659210855f19ca92566ea0d6c48086ec000000000000000000000000000000001225672112e0476418288f381165292a9aabd009b0d9e44d9f8f00469b2c56698f5f985ab6292c9dbcf73bcf610080a20000000000000000000000000000000005418cba24a43fc7edaf2fe77422a0b2e8b38a45415e13654c6176c8f7cf6bb2b80401534154cd3b23e977af589eda9e00000000000000000000000000000000067126ad59105621cb0931ab8f386570b54977563ffd69c2231c56e7961f6df2c5d7b114e0b1ea176cbfc1d657127286", "Name": "matter_g2_multiexp_53", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000139cfd67c3365c5b4422063d7901108c9f33e233bf6413ba2e5b2ad62d188cb50dbd3dac0f298aef7c1d621249d4b0c50000000000000000000000000000000012fcc0d5d09cb3d86895f76ac3d3e9fa9b2495110b0276e7a039d7d2fc2e48fee646fe331c1d8e6f019898ddb43dd09b00000000000000000000000000000000159356eb3ed0d4f146dc929aa6c77057be5ffbb064432d3fc35d346f19f6c1f8552c7079e27f3188bcf29941375e62c9000000000000000000000000000000000fbd4e9a57aaaec40ef9bce8b76b529bd2261d373f05fd69af58d1f23c089497473e44e937b2617a92942af1a99d031f10e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f66318500000000000000000000000000000000022ce2d2bee57f7567e9b52ae8e913c79e3b2dad381802ccad317b525be0b503bdfa92722eb0c21fdaa31fce2421ae300000000000000000000000000000000001177074350288dff9dd85dbee758fee1400cebc173793198a96c0be3bb810d352720e94b9bdcc6f5a8951b3a86b2a0e000000000000000000000000000000000179e21de58ff76427f5ed7c8ca3058d0e5e81e436280aecc75a3d989d1cf11d41734de22bda74cf0dff175ac789532b0000000000000000000000000000000016abe94a49f071fcd5e24b5f3a837fe3fe7c7dc53416f59d0469d71f144f71ade4569bac3aaa202a8479c794bd251645cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000018dee638031a3c9b1198cd4a4f267cdd66849e2b80e3d670897d9e058bbe772936d827eaa4e78283d42ecd25eb4b22e200000000000000000000000000000000009c04ef31cfda7c086a31341434a1698c1132fb5916d359a523b98d05d57bb38a1e2e2bb779d4762f9d4ec24fbf2564000000000000000000000000000000000a788450652e0bfae66889c66b0dab8d1972a626facb690f8e4ebfefc7e1a7b2b58f6eed02c1f10a74b140a49b6c5de50000000000000000000000000000000009e48b52f2b0548dab1c0d260144ad2e66a22e0f1781f94071b5a3a08311d11dcad6963b4339fa63bd82b4ff0dabe685290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e8000000000000000000000000000000001319607058637d4b796020cca79d62af5862b1c186f32d99c0ff53a830888f297ac4389582f9fd010534d824522e6fe3000000000000000000000000000000000608ca0b4806f17b59a805a3f9f75e7a33ac0791e05050d4eb19f2d4c845fa4e4c738c3309e24a4524b6bfe716949ab5000000000000000000000000000000000a6a6201ec077e113995acb81d4d07d0c4a085d367ed740d26c4a0c04ddf28697c1cf5e648b25148888617ba77ced5e50000000000000000000000000000000003eaff54800dfc8eb3ce647ec4ae8c1aab6a87d4853a1ea061a5e6367d8ebc94243837d4752a1933f7eee0ec1ffe68c8b71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff5700000000000000000000000000000000122822c91bfc4f761b65f4066a94c0eb1f53133a1355c019f04003e84edc5095523b2ce87ff24bb42425ce979743ce31000000000000000000000000000000001928bc315800ae9936e5b763bf29b19a9aeb71268cb47706494598e0ea057f9dbdda6733d9ea165acade87bd89b3ec12000000000000000000000000000000000a87c1ee17bcd7d348ed1a5022bbc7438bfad06172584dd8e3b51db4b3b09645290382ba991df37db0ce562c950c0e6600000000000000000000000000000000127c80da591c3ff8d300bbdbe27e0aa21b5edc1c1fd8a5da27f58a4dec3971b3c4f9631bde244a7072d9c19f1c0a46be250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000004bcd0a0321c3c7e6161cd53254353905c27d965f57c9783c3fa7cd5c55a5820116415ce45491d5d1ccef6017ea4608c0000000000000000000000000000000013a30e19c43a1f466c0c3ebb5cf1b57c44434892b18a7fde18a2a29b09a5b4d13d26cef871d689d9855a73a43d22119a00000000000000000000000000000000066d6b3c9a949049413300ec0398d605277911d7be327b1d816cf25543d1b2d7c31d912f426021e612b56ca288b462450000000000000000000000000000000008549f4dfdf018073cc4e32ac930397659ae7a59ef42ca4f864b26e4635c2b7669186a107e9e91c35f04674d2be46051ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000015900fb486bd2c066cea98e51d30424681fc3347a1cfaeeab65989d1adba104a362837bee51b8b953ebb520feb49aa6c00000000000000000000000000000000198ccab1f94fa910f755936e357a92d358e00cf406894b46adcfc301918c4fd7cf7200a1ea515343d577d920680c83640000000000000000000000000000000018d9380a8568adb92f8f9f67c315f2a837d542b32aa82d9bbf5db6dfea27260738bd0a03683a9988c6c3370563e7bb8f000000000000000000000000000000000528ad42f23c4e21a687f2303f495e962b0a90713d6ef3abbdce38ed166ffea9c132e50c5b002b2ddbbd4933e9a1aedf6ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000017eb2587aef34b03943a170d91d99aa16ceb2a36df3068663382ff4c135083c998743f9145a2fd5dd4ce3bb8b64cf3fe000000000000000000000000000000001256fb29c7482e5469d64183e3e848e5bf32f9c495cc495c3f8cd8e46f71c3f9880f875cfe429677615a6803f849952500000000000000000000000000000000146e2f329f86ddf5b0b17c37aa2905122f457c2c812782bdc15e132468af48c49b715e3080da504d59414ceb367596f100000000000000000000000000000000022a8e385972592430e76bd952a700df8d35b32deaf06c60173d0048d6ea22dad95cc62300bc1a60c6452c41b32b504a1271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000009c80b3191783d235814fc86653bf2f9a32cb7938111408087b6ab5bafc480583e7a2a32c6bee0ee4aa867ad5dbbf77a000000000000000000000000000000000a09af60eed6c47a6c2615cbfe62025530b35727b42fd812032671ca1eece6694aaae259b05906faf7fbb54362ea890900000000000000000000000000000000055c5f0818f41e5d73e8cd5f70fa77cf477cad8dca2a88b8970a3a25c8f38382268e439642518f1974c5b470cbf29699000000000000000000000000000000000834e44669043aed8ad47cccaaa7476ad830e38fc1def66aa7e8207e889ac0fa1a931eb1e90aa6e1cd694bb95056c3e63ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd000000000000000000000000000000000190f8da34caaf472ea9b0f41851f808bba402b9be4baa5d02d1bcb2f66acc3172abe78a49a653cd24dea402dfb972f670000000000000000000000000000000019931343d0e59f0f0a060bcbbeea92fc4670db510c017fd94e0650ace68c2925c627f373d8e755813c199b79c70369f20000000000000000000000000000000013ee811cbc036d2786d8ec0339627d6134b10517c8858f6c6db19a9319636459ebaa217649825ffba32a224175267de90000000000000000000000000000000011039d587f3323ea9d3c50027c427fbcbbf7e097533d8a5f7a61520f3eb548c399e401df0f51884395ad6a338c0a3500c6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000011d5791e9bc632eb63bff86aa433e6df463a84570b779c913f67e77fcfefb6af48f3df2174096a511ac35eff64e0e5f3000000000000000000000000000000000282716505907931bc93748ba1729777b959d65aec5a78c9f829ae6f2a94a022116715a8c2a653a832a62625473a0cd1000000000000000000000000000000000f694a16ce7a69f0261a0ae19478003dcb61bf93a2ff39f940fc4718a38b9f4b6ab13527c5b438d22499ba29c0b5461700000000000000000000000000000000031eab53440757e4065804896e9e811d459665598546796d67472054fa60e5da8685d8e847eae342e44730056757c6287275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c61500000000000000000000000000000000138862ee422bc0f38ce3e27ed3c1b71f71a03d61cc474d989b0cc824efc512ef173ef17bbfb2090997eb9435f4d23e0d000000000000000000000000000000000fabf1fac2ffa25d9c8cbd49b3db5dfdbee52adb947ebc1a3423c9fa2f9d3d29329b60ce0c1c739c7fc6d5a5d3b9e96400000000000000000000000000000000090d92e8763d4df49b8121a50affcecfcd632923b5fede480a3ee79128781f3f49b592d8f65d30adfc75d8a1922c41b0000000000000000000000000000000000074456b341565b13ee3862bd87b72f9d01754c7715751738c5b33ee85e3d8a6f731d7292bb485b5fb59bbf3ddf9b0d0dbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000007b85110889fed72b3654a8632625835cc041ff0a827f3e1b86c090d816d98cb3b4be66b6e573b3dc05b1998f2772f0e00000000000000000000000000000000160524507679ee021f4307e5a9fdaf01459cbb9a3fb9dc8be5599431e2a8bef38bf8a05d601580085da503dfcf57aab7000000000000000000000000000000000f98e2e7ae9cef2b1d954b7f26fa1755258112c496605c3c77408786d4b210e51c76f10870f558296993e0ddcec3d76e00000000000000000000000000000000068841825f5f5d8f622c1d43bfe090d11c6996688589c3d644ff5da47b94c0638128878d51dcf6d43637781f0ab21a68ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf0000000000000000000000000000000013ec97016dc3d6a3cf41edcc18f88f58b1b88cb2616bc2a8f96af3e7774ec1aaefe86a86135a20ab7592c874a33a8e1b000000000000000000000000000000000021dc7e4be6462d64ba6c09c2d326ca0164305dbf5ca1981f265a1e50f1a646748ce66ae07297230325937faf60709e00000000000000000000000000000000121bda2855503ef11b043301cf331a0fda6e5914e5ca657890ffba2542d908f8fb02c2c93cb4ac4fe5bb92eea757ca7b000000000000000000000000000000000386fdda56c778a7552dce451a6ade55cd24bf9eaeb837ebef898e2e868d05eb5edfe97bfa8eff8ab7cbfaca3c918910461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000763746ba87e8bb547180b0bf18699ff74f11154a06cd77a76cc9c264db7c48286fc52e3ef2d30ca914cdcc5c4ed46ad0000000000000000000000000000000018037afcabd273413eb4a712f5d1888249dc987a6fdb8befb92c02660604bd11deb33f283b37f88880cf1be2b2e71f1c0000000000000000000000000000000008ecca3d1652be4764720ef13a6ed6164a3ae89d160cc8c2c8c37bcbaa52db0fc0de84fbe2a19b93b8100556fce0fc80000000000000000000000000000000000c5727babfbc5c36c1d57b9f69c5b41823882e0196e9e0a89d5f4380c4257818d90b1fa6d782e774f2424209bf2e6b5fcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d00000000000000000000000000000000142d41630fb9db2f9630e4d5f9c13069242fbcaf1dd02f93224174567c3f944fa02b9791a409d9236d89df6ad785e8ed0000000000000000000000000000000002fb5fa0b3a7cef16e5638f217bb946085fba870836c618a7db9b4394da9144850572daccbff8208f14c8082aaf1ef6f000000000000000000000000000000000a6be9b4a6a9b96d2096eb3a95780f11be1e13bcb6e625517191822403935c52cd40481bce2e782c42b11321cff2cb7f0000000000000000000000000000000019e2d94e35d608a50b5c8b371044f6410dd6c1988ec7a677016d4b52cc3f21b82fbaa7db897f7107d81a177c31f8e52467de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda", "Expected": "000000000000000000000000000000000a6f3fcd812e3878cccc6967d49b104599fdaa80cb5dee7298c3fdc80477d277f2c68f1c941f6e03441eb176c222a448000000000000000000000000000000000a4007cc5586d677e7945dc8a5872b4839d5b256999166e7fe8efe4d56895f93be4659f43aaf68c6070babb6d3328168000000000000000000000000000000000cef5304a1077c8f31d72e6f1f91ef5a021d8ba64719b4527225b34e615af388d9b1391f65511eac209ff5e86244039f000000000000000000000000000000000c856e7847ea0b4a8334d124417b45a8689d5d9f113b99ebbe3af3f9aae1cefb236d751c40488a861a8f0e0326b42c4c", "Name": "matter_g2_multiexp_54", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001805009a7fc3d1705936696191c163a07ea992cbb4bec66884a2d58ac3fc0e16b6e0d2292caccb3541f39b7fd6098100000000000000000000000000000000000f3bcfcb0c400d3d06184563204bdee465de167c7d17bea2e2150fe12eb9bc3285f5693b222fcd224181f8d193b7d95f00000000000000000000000000000000028d60b7fc3790aac7f6b3ec32c4be626a2c64c6348fb8a1f39e58ee56b81469e04886ed9be1388958550c02ca9a75b9000000000000000000000000000000000b60ed8052e43e99d3c10a4b97ac3197ee3cc04ad857c5cf4d8ea1df2671084d02fb683f28f5d499910351354d5e6288624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000002ddb1a9a88e3a0697540cb008bceb075e87e2331f6e9b68f8ffec48752d93cfda5fee121155ad2a142c0ec42808fbc200000000000000000000000000000000144b694018840835fa9c50fdf62c2e32261a8350d2ef074dcf7d016af982316a0c6f9e5d15d29d3a54d8d25aac5534940000000000000000000000000000000010a3765089ada75e9eb61328756ab9ca7b8362cf86cc82af3cf43f390a0745954f28da72a6ea4eb904a040596795639100000000000000000000000000000000056b51dbefab453012b35fb6e06af06ee92e4e84e92a9967b379af760fdca4a3f10f938684a646fd70a2188721c92e98edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c23500000000000000000000000000000000169effb324d60b71dc7ba975e3d5f18700b34cb9017f482f64be37c4df01fb66ee9eb5870e43649225c9a88a0d499b890000000000000000000000000000000016c7ad9c5f7b65a9423f642d87621a5192d7548e1099d774a99a34dd4ec9623aa1168b9adab092b3cf450f369bcb627600000000000000000000000000000000123b35bbcd791ce0d00148cdb3d35ba39054a7126ca5ad3351fef1437461379ef639896b271276a9561b46e270f7501400000000000000000000000000000000161fca2deb729fc55f1102fb75ff466319f18510fc66d6cf95a8256118fca618682f00318b0a5297be873a2f7af1915afeb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b00000000000000000000000000000000073ad8c2f713288313185c3b2455ade93d58e70d5df6b8dfaac8eccd990fca6843778fe42cc8aa6f34ee44aefb49397100000000000000000000000000000000012eb9cf288a366adc58d40c9ea5f2cb5dcc5b04108e3822266ff20eed71f56bd74f1a2727f20d55917adf20b6c4d6a1000000000000000000000000000000001463db177fe5c0dcb899797f89da963731dd4e9e8b2eb77b465b98415dc95f6d5569df51bd2b08a13838f4cca4b62fcc0000000000000000000000000000000009c0bbadad98361209f36eb23a9eeff98f6eafc7d5327fddb6bf43898a2be704520a005b84c5b45c6a68bb7c98d65d6dcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000013c63973ce6549ca3dfe8ea8e3bcd6b0bd88f7c73730834d9ffe2076cd4345090d0364d161ae8998af1048d102f22e5d00000000000000000000000000000000060cd24eea4177c9a5c37038d4cb62aeb709218fa8e64b9084e002f53a0c4c411825812c20df282345bc4a6aabfff6a100000000000000000000000000000000106ea864dd52933be02c1a79cbaf6dc81ae9a2d619bb368c4abc36226104f3b74fadfab906e36d4852a6412315223bdd00000000000000000000000000000000192e45153e4942c88bcce76098fa51782a81b53abddb4c07bd79a2391be68858e2d278969b9fe75bc652d02fe4db1a130b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000101ca1625e9d4a51e08f5eb81387b361f6445eb307d9bc92acd29d62735d4e5078b1a9b36b94e4ea0a314703a85ac4cd000000000000000000000000000000000f134c460c6d931396a0aa397558975ee973e642f1c4a32a3d397051fe250daf4215ff5ac4b2863d570c87f0e32c8cb800000000000000000000000000000000008eeb127a38104351298ad77481c32bf51bc5d3910b03da0cc34062dd2a8766adba6891cb9fc579672276666e1242730000000000000000000000000000000010c896ecd4bdc1ce010da81a51dac96409079853635e57e5c3a5733956a5f5a9c3ea6838849e286ce0405dd54d7e32d6730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000be489a1c71246adaa1c1dd6d2ddfae9523fd1d58d00d4f189f56d08632dccc694e63b371db6922a7f3faa05afbf487500000000000000000000000000000000174212b6840a797f0fe9e209b41f55aa5dbf169a2e2ecf05de48c44e608f6cd6d98ff5269e5412defb431caadc8a09c3000000000000000000000000000000000f4501715c0c511703f6236caa82479b3368de430f2c2d95b39193537be0b990fec1ed8e4d94634ee6233cfa359b043d000000000000000000000000000000000f3b4712f95005004d99fd739affc532d2c4c45970316c1a43f76fa9b57f6676c709e8791c276237b92750f5bdc94492ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000003abd36736fec3e8b89863670666365b169d8510090a89007c7ff3a82fc62ed371544013a1444fedc4358e92ceec62470000000000000000000000000000000008229855468fc63f4024938cd6f41c6e6a5653319cb83f38ab7efb9e9d281166261e7c854bfc08f55a0a9ca47e54dd42000000000000000000000000000000000463ccacb341fc5874f6ba2d44efb5cd24e9409b2ce7f43e9d39466288dc833a45988261f45d34332f416a68c5d10ce80000000000000000000000000000000002baa086177394203a04ce1b46415983399e60986531967b690b1a13cf8ae039b56f0a00bf9aff357d51ac57f8fac8b282b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000000b25a203268100df0510e4155c594a144dbdefbb0ac95e02bb4b3799aee4e738ef4c52f03c6937cdfa7275c28f130778000000000000000000000000000000000c432347a2534e86e90ca346a7b8b40f45075727847fa3ae2f2e297baa14aca88ac6e08342f0d248a92e2c272841fddf00000000000000000000000000000000057ec8099e1e30329762ccf0641b45e1a226f7b66b80644fd551d6fb1f2136afb8e8ab5c6905ffc7c24e67d7f21863e4000000000000000000000000000000000a9e472aa993bea05961affd6782efe8f50d746928efb8fbd328fb50a254db861c90db8df7faa7da8266ceb47fa1a13a2396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc00000000000000000000000000000000025484652f18e2b32e2bbe79916c8bad42902db5528fc45993e04daeca008f3c2ff38fe4b48c292f70a7dc57654233400000000000000000000000000000000008e403f472b60a6046fd190544a1d6b249dc97cbd8641c62613f4de0e0fa9f5456d843ece4ac2b9f4ffa2c0278e61829000000000000000000000000000000000824e0b9b03198597fa54252b3df9690df678e9c6d82301848939dc55ab25a7751bcc2b99786cd31960ee7030bf68ac80000000000000000000000000000000018d1d8c7f2b20f0ba66db616322e48ac8f1d6f4205f228ee8ee6cd13d1f64be9af338c11f511859baabea3e15d165fc09a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000002845c4255819ec6e97abddf4c9db7d91658dd1d55328ab0565144b377e20ca0743d93fddf68acc985ceb7f7431e30b0000000000000000000000000000000001577a5691f2425e65ffd59071c2bb167ad05a8fe23c11c7f7464764442ebb2f7a75a8d02594d4426c1ff022f7a6e19360000000000000000000000000000000012c6ffefcd3964362f1373348404d04d1849e98ffbef7b5ed5704d74b9550869e30a4df26e74b5304b85c7503f7487f1000000000000000000000000000000000faf3dc42113f27ac27aae36725221d04fb1ab46b59e16277be0758b8fad706fa237c0c7627771d8e8d3ad610f63619bf20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb00000000000000000000000000000000044de166200ec06bcb88720e57b84cd8f9534d1fe303a26aca08cc35104ffd7e81a6473c08b28037118dd8a61d090e910000000000000000000000000000000000f4325ebaafc67945de2418c81f5da92da4e67866ab5965eff0f392cc527fc34ba4e7e16b91c26aa370b27eb6a07f6b000000000000000000000000000000000e1d77ccc1c196cf1cdf0dabbee4829d56e937372e9f5613e261ca07e19b3fcf10f7a45c490b98b5a64b955eab5c4f2a0000000000000000000000000000000004ba2e81f901b0da1ead004c76d43278d372456c0c0a8c6752597823d44994177734ed3f355aaa22f325ea36b7c9eba1f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000000c115e270ffd6f2cb9bbb2a62e04c3bf7be9d7db783d292bed272c297773b39e9e51c75e5c79a6606ff7d0bb9ddd040a000000000000000000000000000000000a57b637126b16b23bdaa6a7cf2346f33778cebdc0c9943eb2985ba5c4114674cd596ecdb6959791139c36c22148ab8300000000000000000000000000000000177c7ed16c29d99d3d98c6facca9cb5ffe72e6aa63959dbb51d9382f0fa49b02a1652a398eb223e093516ebf134448c4000000000000000000000000000000000d6bd518678828f582fbb3b1bef725e66f442c4d3e6325fa571e13db492300d03c0188399a2ef9d5687a76e647873c0f52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e00000000000000000000000000000000085d4f90336987f99d250067c2331e7de8f09a80d71fef0570ecfd99e409c1f405058bd3461c9f8ac5ccda406db89bca0000000000000000000000000000000015f310660ca6a0c06b458d0b840a5c1c476d5175d9ff6dce6334466d363d319939572a2b00662247be1ed0f4e6676f8b0000000000000000000000000000000011e9352c0f81bd3857806db678bceb2150848f2224ddfc43fb0c733f0689ab4fffde50d5ce04d54055d27d7702e5d2d40000000000000000000000000000000005d835d04dcf4199130d6a16e86cb97f4ccff58c496594b83524dcd88f5570212f06b744379288f2a737c7a82e897cedd32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f000000000000000000000000000000000f7c4e621c37bd3068a972b9d4211abf9026e438ac7f8cb341516f7e6aa4d8bfb3536389e9155029ce9e8d5d376eec1c0000000000000000000000000000000012a46cab2624797513f2acaefa26fb22c4bf29188881690c350593fd1949cbc243c9d1d7d27d9d76aaccd347359a45660000000000000000000000000000000002dc383d4f9b75907f74bace1769bb5bb1b27a597c9548310f2b5f90098596fcce6b5fe0c72bc8be9037fbf31050d74e000000000000000000000000000000001900deff7ddc62ac302c941e1d2a28a4bd2351edd7700042ea4c4a48145ef91688666d8d7de503913ea259f0b58809f21f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad26940000000000000000000000000000000018ac8048d58f7b1a9407d3101824e3640eb20633f8ffdcc97d43d1b25329a2a1e91added42801c03635ec904e627eb690000000000000000000000000000000000b499fbdbe2ed41dfd6c454796e1ba57021f355a4de8f60964c78dc685e2ffe9c90f5a1f6c9677514ae4a9c95c8d6450000000000000000000000000000000009d10e5e2bb69ea6fd820778f75a2a60627802a49128c3f999d8c1cc2ba56ed18acef354a2e06fbbdfa7e7a4ade7529a00000000000000000000000000000000082839d66a18763656c2ef7196a1d83bd162e1f109b54c5a6095cc7c436e8a4888c4001696958270f54f61b81b00b32d83052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692", "Expected": "000000000000000000000000000000001463ac5e269d286961036db48ae33fb868a28b0dd828c3a66592ff9dc115303bdf3ab78a8e1f5df68ed1f3b4c6c3f2440000000000000000000000000000000012c64ca0ac10ab616fc733f75fe6181814e9c204f9e4eb79487ba49e3a9746b9b7916a1d768f2ec573a4c4e226365f48000000000000000000000000000000000a06b5b745dd92adbe1f4cf30c79ce0c48428b3e3b05af1585c4ca12eb2e763ffff46b55a060913e1f77fc9b0b085c9f0000000000000000000000000000000006271931ce9c8b9cabdc932297f3c87128a5af25a9f77e71ea4e588f1e88686638e89a8e212c92f6472692be2e05fa5e", "Name": "matter_g2_multiexp_55", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000001bf5b74c1ac89d4bab4663943e19128619731e315d2d7b39675f7c43493b338020190a72cc7a6edf0b8838886a7fe6000000000000000000000000000000000713f413bab7919cd57c2de3349394121d6bace3c10df0e41a0ab895433d225b05cdb1587deb93ae6e56ec26a29c39f4000000000000000000000000000000000dfb11c9c0bab7e4d1ee39941d5f6b932ab473567be2329c94bb0b146c46fc1c2cda25dbef8ff9b0066bd4ca3b6da67a0000000000000000000000000000000014e399169243bd619be7f2120b2cae5d19b2f04185aebc7d948007c4d3345a9f45249273b6290c2e86448648868ac552c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c7052000000000000000000000000000000000a67bca1f8a0b386b2a67158e80262f025b225535a294394584118f9a701e31e91b2c7eb8fc7e28538966b967c139dc400000000000000000000000000000000185e8aaeec9b9abb9f0d6f34e2480e9abc30208eb1c6e023d4986d544b356a387c323c9edb5c52f5a2f0bd59cca7df98000000000000000000000000000000001877ce1ca6e8b30df86de688d950755f2708fd6f933c07ae45fad1b3e43337f1a8454ca5d2a80940e8fee98fffe953a700000000000000000000000000000000117a0ac9d27292f967ff5bff2ebed5d2ddd9f453d6aeadd9106eb52b53447974561b621fdc1d973c055f1cdf824c367bccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c00000000000000000000000000000000009fc4e9e816ff495dbfd4f745106fc90c023d95bc64b809801d02dc7cead905177ede5016f537243660e4b7f54a02ea200000000000000000000000000000000180aceb6e9851a11a1e34502897299e7db3e09f4970337612634fd9848d1de2bb3de8ede690ca051a75add5810ff777600000000000000000000000000000000199f3c43d429fe8f73e20f81ea00c4e78294eaaa29fd67563664381db3cee2186b387b880089cf96fb99c2e22c95449d00000000000000000000000000000000040b20ec4e685f104be188d0f15a79f27cf34dd01f813275f6019a9ffac56e234b6c967c80745294d9fa46e0083cdd907ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c3963100000000000000000000000000000000189fba635109ca215bf3a09c3e44ad65f7eaf653e0929aed39042e3b9c8b1132c5fe7cfafddfdd0646514aa1f9e7e1c0000000000000000000000000000000000c28f598c80ac262ec7a0e0d1c867e01ef26f182c5df9ea7f88fdf8bcf3a5d2f06128526b1ce72cead8ab4286a0b8d030000000000000000000000000000000008051be3328df43b79dc9040ef0a0263d474acc0edc023f300cdf7c13088d1bb21b5f37ed81b38dcf8718bf6441605f8000000000000000000000000000000000d2d474723c6c246dc59e683be147b1a6bd6e7d3cf12aff7b636802a99954e7a13c9ea429b19833a985ca5649b1a998f6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf01840397800000000000000000000000000000000058bf424fd68aac77c42a046f78a55729e6b5b3fcaf436d0d98354b426a95904b55cdffdd9a8892c9f56f170ca8811a600000000000000000000000000000000142c1ded08928fd155b89bcfaf9c8194f4569b4cdeb3bc7286f4dd79e822f5db497768220533b71be8c71d121e557020000000000000000000000000000000000a9c753686534bfcc295eba0a617f86d7f9e78d3fe6d52f26cede97a5b1f107210a757a2d89361645856b7b20e89185a000000000000000000000000000000000f745541841cc4b5352f659c2b7cfa8d51b07f91b0cb8c787b4492bb4b94ea27117695416e2806e57c38d7e565b9eac67fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f0000000000000000000000000000000004445d4464b51d6b12f164a49ee3b610f11738d60cfa6e02f8c33b168d9d5db90e6cc558cd12c56069571567d91183a30000000000000000000000000000000009e4b96c2b533a16803a36f8d1f179313b7adbe6c4b90716855474ffb2fbe087df3fc0b4ef14cda7d958efc5c92574ac00000000000000000000000000000000104dff7c859eec61a0ff8e0d831bf9667226d5bdbe298400b4f9e3159a64b1bbc7cb9f4ff9604e3ced40bb0de0455ce300000000000000000000000000000000134bc2461459ed6f0d96aca02b62e3110c2009e1ba7d3258656e9cf97c2a1685faf1f61733ce6ac3af7ef4d73d0b43b1dd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b00000000000000000000000000000000104e5709f8edd71f50eac1770ff1c2b21f5ee8cf5a310fd1201109d1b73cab69913bcfa2d27a8ba16d974e9841586ebd0000000000000000000000000000000003a4bedc6277c61825f6ea1f438c058a1afd494c384689a8479195646888eecc7953b8b8aec849fb5f19a20071261336000000000000000000000000000000000856ee8eafb9b3d25fde7e38da4acec624d1444337b87b0b1a660bf497ff37929b1ef9aed8e1fb0ffc6cacd8f0d1a1a00000000000000000000000000000000011b52192c88264df56de3d7b14372443e25183bb816ea1c0346f15a1f324527ef8531e27aac3112e2a497a0eff0d5485b4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000a696c83010719161b6624aa7756e6e84980518416554ac045a93b63c2561a68ca2ff2fd5b6d2d667822ae4e3b3a2ba2000000000000000000000000000000000fb8fdab4f177b0dee52bb5ba615b1d548130deb87b14d05d427984ec148a7a94efc4674804b3660d0f7aae2b49f7b1e0000000000000000000000000000000004914c0359c8e23a7e431e517cb83e5735cb2876e8b53ad45abf1e9eda06e736378ce03ff75002374d47f1bd45b08e8900000000000000000000000000000000139abe340c2d773cc45cfc75c47ff31b2dcdce27ada3e6d6c0823f37e4e693ca30342fe41eb96dde464d14668eb72c5e457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd100000000000000000000000000000000180345fc01e3fa349c45b1a7fdccde5f9ee70d7d65510e8b4bce654f2541fae7641ad86f9bbc1f02e93e94422433f8b40000000000000000000000000000000006cfe7026cd423be189c5ade8de197aecbc9aefd4cdbbd2aeacda816247ad59ae06a5c49b0e29bf1140f400d46845191000000000000000000000000000000000cc4f240a317ae9ce75b44fae87c92fe9b6de10e1191cdebdcc37ac200957683849d8a957216676db1af51fa0a2a1136000000000000000000000000000000000ba84d595661e5d9bdf9d268a3cc575fbb6b0d469b58b3e43f80694c78f4e9e501c4a4f9c42ee4518ed7189a1c36ca0c19f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000fdf7e2372b01b5d926a18ddd06b4573248c02d7debf944312dc06f76ba08a7be460c451d296b71e9e81cf0956b974b80000000000000000000000000000000018326d0e1bfb4a62ab6f772b47ed7188035a62141e6b2eccf53a299028902a172771e8e46c0b1ac4833ab12045922b3600000000000000000000000000000000072107574145c6afdfc7d618f2dba2b8bb01d92007dafd476e4ca62e6053e5e9f2e34243ec2dd16ffdbe3488b925a0f000000000000000000000000000000000070e8491a835ae96087013b0f8da267a7ca5b0a600d71b8c76fee35f41d8b5c1ad82c5170b0e8d1cacfc7b7b13938e96f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000018f4da37ff63f66d68c875def8c758d9a5adcdc408f0c12b3a60ee4a285e6702b1d5b9326c61f443dc71ae83c7bd21e80000000000000000000000000000000013a665e430141cff62c25577798473a645d20321490bae7689de6ea223a434c7d3b16ad004b24a82e2c62879b2408cf90000000000000000000000000000000011b0108562f53bd47d9f8ada54166854bf758ef3769ca1c3b7b006fec8707107fef0b6c7e59feb727646b74c27ec699600000000000000000000000000000000028799b52107d8965066e2f629b30c0edb490a0f4d0b6cdfff89a9f7763afbe6217bd42c2059042397b6c0443465fdc050aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000c42cb42e389f32926ef09584516249ae332641b573ed29bc0884feda08d35c1bdc6c3d4a69fa15105de95010c6cc24600000000000000000000000000000000006c57fbf93c7959c562e0f3ef59966c1640c706fd18a6b539dfd711b0ad79643642038954bc866d42d1c04be375b95a00000000000000000000000000000000039ca3ad23b71693e02af36a4abe6ccd0dd4f4aa709f74d900b9fd015a2eaed55bdc2bc0749c995783a7615971e8a1f50000000000000000000000000000000009a08596b29da34466c8a7f46b805f1b6f2e48bbba614d728562981d3d4884de9a3c1980d398eadcf69e90c851d48526d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad43400000000000000000000000000000000123dde5bb9b7ca11da9e08a9489cf07d147492be8041a5ad0b70715147e21d6017a58af23c47d77885a7830cfbbe5e0d0000000000000000000000000000000001527cec3c393d03e74ee8a7b1d6a8b6398945cd284b59a93fade9839863f0af591c287e89b3b45e6048f2f9b518208e0000000000000000000000000000000017ac3a2d9458bbd5f38d584b0fe4b35f3a452e22161564a7582465d2068b3ba4dc5e1e24a996596b1fb553d641996a4e000000000000000000000000000000000ee5ed5610a78dee181750e35a8ab91c001446f04124930c2ed85de74c6167009af45a6cbc3c59c4915334d7853ee12f85c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae0000000000000000000000000000000014d60c1d436e4486f35ec85bf2655ba6b752a36c86fd9088c0ce46363e75abd636052f876986fa0f4a59152998c0e4a800000000000000000000000000000000083328e38373f1de1049deaba78f568db818b1dc38d981ae92b968134d369ccc399bc3bd55c841755beb484cbbd60f4b000000000000000000000000000000001788850a5508d81df9af1f087356bf8e63b3c8a4e209403c4de7b3adda07684a08f9de6f1f8fd8dd4b2bb9b75be329cf000000000000000000000000000000001506a37d222173f0098f56b7c443e04ffe08b376e1563344e7bf22b1c9df0a1292f70ba51cbe554843fb93a7f535a4aabacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000d5f153952defdea9309269bc996a7714deab12e7644f8f8344140fe53034de538aae6c3af7b06687684edcd2c5dd19e0000000000000000000000000000000002da67345153c87ca65012b8703acbe777900953abaedca4770fd893275948d150ca3d6694d58bbbc9e62904448a8d2c0000000000000000000000000000000006e8c95d22f01fd9d56178d754f0892f46166282a27e6b02826478cd39119636e811c03fd835c714a59bd2f7da5ce5e1000000000000000000000000000000000b5ab6233d8dff50648d89cd65793640c06ea784d00aff329e882ae04fb466506cce3fb6c381b4eacef8b5305953f7b6473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae000000000000000000000000000000000595edc440a5c94506a79f3b3fee818256d7c4185be40c1953b46765b2f925ed16a476b07a267570c727592dfc4a0d8d00000000000000000000000000000000079ad05473fca57f26fd068ed659e4aa4919847dd96e683e7d4b3a731cc9ae0562a693abeea4fd550e644b43b553118500000000000000000000000000000000176a9751dbfe727a442797551254cf904862c4d590892e019a54b72f6a5a124d268777b82e19d557690ccfb81cbe949d00000000000000000000000000000000164ab74c150cd151b70fdd7d63d0404214fc9cdafba3bc642aa798b1c301c287ff6d05ee7b3a3ce997072b8189d54aa62774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a", "Expected": "0000000000000000000000000000000017d73e29f1d555a10272043ac0900e80883c185ff7d087ee7f5a3b762213e658a42d1b4fdd435d1acb9d5587fa7e8243000000000000000000000000000000000ddc440795d0e4308577fe8439d43418641538711972c9744dfc8a4c206c193aa17958404bc387c7c2fa30bc678937f7000000000000000000000000000000000d7e43c0f99adcb02db99974e7615b4ca0de72117792ea515bb04c4bc8680a3fdb0afcf6a3bdfe16bf54c1d7336aa185000000000000000000000000000000000bcec1d7fc9f2210be80e90631810987801fdf60890ce197db041b6a62682fd7e181c6110956c5f5e9c196049e39100f", "Name": "matter_g2_multiexp_56", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015425cbb7075a97dfa9409d7b014127396056dee6d4bb63ea285309fd91280fb691f9cb9572b544b332324f6cb3b1276000000000000000000000000000000000c5b9634e6748d5819396051322d9b7e0377554613a7fd8dc0c71cfb7886dc0ac29add7265af84087a9df5ae3799ae30000000000000000000000000000000000534226ad7324ed5600b5438b659c7b1e96f27ee1d77163f2d3073418f7ded5c613ca4b1a686764ecc43ce3388e0c32600000000000000000000000000000000198267e2bd474dc0415f47f5c87a11fe0945a91cc0bfc37d504ac53f9b9b0d087cd5dbc9b03972be03d4b3f9d2123945d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f02667214000000000000000000000000000000000389a084d95445af6e0afaef21d3676794e45986b9520035111ccdbac4ddc1b23974a686a900616f878f3a06eec90db500000000000000000000000000000000064c75d1129753b5f399c1a5166a0f6a8f427d65ec2fd84d0c7339218e0a396681797bab68b33653ffb9820a6005fa7500000000000000000000000000000000147e199e8c08b9af38cb457b623d0fff32242b11e695f2adc0f136c5596db313b03c2466fb58e37c94704152e5c8f9dd000000000000000000000000000000000e8fe5436baf3470a19891b85d15486d1269e1b13098d837b0a510e71b0e6260700ea85f0bc6476217cc73615370cf003e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000a39a2032858a57ccbfb940741f4ae21b318a56d5567cc0088ed52dddf1e0d5de60bd2da9b675212a9a28ec17fca7c0600000000000000000000000000000000039e2a4bb1b417f8a94b02cad60a3e1c4c4bc5a86a23def7cfaecbfd97d89a5104e0cd13870c9fbd010dfec3ad9b1df9000000000000000000000000000000000bc29c5623f9f18ec2af5bc651a65d89554705a349923ee15a9bfb82c114246b404a1dc1c24d65c8749e7c9cf62d963a0000000000000000000000000000000001496d76f7b8583a64c1627151589af876a2f5e7677611ea15f14606538f6052c56e9fc3ed145c313acea69a51547fb6145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f0000000000000000000000000000000015f83b2f998691e504aa740b4db38f5b0236ece3bc1ca933b79999d55b737bfec51e590c2127d57625a9b7c2960c06280000000000000000000000000000000001b7b117f5d722e320b7e90307ac1423aec5e30c29602d314bac9e5272ad3990d31999bf3f516ac78b2be0e16c0375d8000000000000000000000000000000000fa7992cd7fb679eb5f9f9a9febe9c3cf41a717c8f6fffbab5748572098407174f09457e13468165f1c7275d52f6c84b000000000000000000000000000000000737e95f62aacd12f8aebc288c5cfe052f34c4d16e7b44df4497d9a713b77485fb0efc09aef11c7b86eec4d0cfd9b03ecf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed000000000000000000000000000000000004d48d72ad4e77954ec6a5a62299f0472bc52b556cf3857019f8efdd694758f13029f9d6832ed672cc210f32033da8d0000000000000000000000000000000009b2394755d0319741d131b012ba0ece7e2044def20ae73fe73bcc276af9d807ad75be79202963f9a5c512a6ca53197800000000000000000000000000000000128f856fc4790d9fa68cd2a3c152d675453dd81dd64f0ab084c6dabce456f78c2bab0e7f315439b34f86e8fa61a33ffd00000000000000000000000000000000173dbb908ed617ffffb6aeb212cfe6c03f7ee51c84134fde67de2ad9561a897e28a0efa66257ae0c21ebcee3fe4fa68cd9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000bb7b84476d4b17f4ada0b6f50d34dfaecd611356862895c8d2fee6707c4aedbf565560d4207e43c179c5cd33cbb739000000000000000000000000000000000112d8b10c775218d318090dfcef55a903953f7466c50417125ec0b2c20a24fb50bd172331c0377d4f47aec99bd87a3fc000000000000000000000000000000000cf4e4b3c600053f45f350c8860e47621f50f3849872a91ab115f71a2b04657991217e2f0844b296d3a6bc33ee66e6a80000000000000000000000000000000008f625da164bc9d96be3e78df63bd1633a2951dbea0b98e359c6317abe6ac5799c4bb00bbc2c5d02048539e753019a6241733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced1575867000000000000000000000000000000000028db057ab9421eefd1fd481c91153b5c1ceb0f2dacb0097298cac986f036572c6ab0c8709325b3bc25bd494bb46c55400000000000000000000000000000000024be09301c9be4f726fbf7796e8336c50897e8534614c25f65c37bcfc6e724d530c2782bf483668fd08e91ad09484af00000000000000000000000000000000037bfdaa11660111ce0a9c3e18b5da74c004cb44882b1aea4173e18d3a17f04fefa3b319afaf4af9dbf3d4b9ddb2c3a00000000000000000000000000000000008f2138bf621237a286229fe762968a224358b030f6c20db58043c13727b516097b42d47781bd0f0df2b155197ca3946b21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000b4e2b058d6e77cf95be093375233e5c9c8ee0cb2a3aa93172c08faea111df81b9721a506180b7b45bdde4b58b0b7368000000000000000000000000000000000f7025cc33424a7c11eef47baef888535d938d50c0f40eb83ae86791834770e5dd95b30aebdd2c13eda3447d5730ce3b00000000000000000000000000000000088270ef05480ef8aac5c284358d8e06c3482c26279734b8513000019924cefeb396ae79f5d9bd863bdd9b22e3ac3c54000000000000000000000000000000000df75afafb138fb06bfd905c87035bc5d18c45a29267c3965131083d7e0112e10556d7693d424172a53e8d3120f0cf2aeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000122fdd3c83c01c7cbe71f54d783181860e7dcf8406e3966e910f4d0ccddae3a245d6b1f94b1182d1917fd63960cd75d400000000000000000000000000000000043592e5797cc1409d6d42dacad628448799b24320acbda83f6ea9d232968efd021058f540e3bd73a7f95761efbb5fc400000000000000000000000000000000025b5a8577ec1064b5c557415a50e84c2302df97eb65860f979e5b1e261f47c0f305461681beb07e521cf03f0e21fd030000000000000000000000000000000017e86f3ffe72bcb71d46661a1537918d52e886e362d78ed756140a6b5083a4eebb5280b9eeb8a25251dec43a5cf509b13d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000122f26b4561d1f79a70bd0e401f25d50891c0fa0320579ef21aeed7c191fe1c75403a09260c3872cf74b798eb1587ebe00000000000000000000000000000000039a261d9f48b9eab6e89046f333ac328cea287993166057e9b99fa8a7d7eb3e7c34ecbb353b7427b235084f47f45d1100000000000000000000000000000000015d5e297317684bd0169c795d9dcd209452d024ef9a450c41beb0f6c7e6dc5fa0f3ae24c7cf2d7eef97bdc51788188d000000000000000000000000000000001487564f0e9d3e0d2d30ec9930a00f10093e29f2f195344f567960be323ca21231efd8528108dbee4d5ae4de3930ddedf6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda4000000000000000000000000000000001965ac3a520c1ac39b86832ecbe226ae0474b76659076ccbb550a0daf41c40d424ceda084dd991f22cc53779085828430000000000000000000000000000000002e970a4248823049bb4339d21583fdce9540ec103d6e9530b89e39ea875b1c333f7f5f859be39baad34b374055baa770000000000000000000000000000000003460eafb3e54ec03fd5cc1d460e1359b97f5543e6231d61614c1225ab7545fae079ac8e65668b83d022031a7a54746b000000000000000000000000000000000321394863e7c70df3934d874613b7c9d6c331e59a599be593c82edb7a26eff9bee8e4befbf122240d2deb2d527bd38c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e10870000000000000000000000000000000001a5eebe200ec041476457f8585cb4ccdda936cca4977d7701c44e0d4fc5d9c206682a23348013a055117028c16914400000000000000000000000000000000003519bd1dea70245e521988336eb41870599a877380c0a9eb19301f9b2caf963eb559070e23eaeefa4de0173bb1fbd8a00000000000000000000000000000000125707f5a8e26b28968dab97ef4654c315b0a118c20935e38a5a526d9ac0a0e18355d8c9f3f58c082de98691957e2d5e0000000000000000000000000000000010b58dd683f73a16d8bd5557b35b7003a761bdf7d90ef576de8acd420bc74f5219fe7f9d35667feeb3ddf1d568b56bf1f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f00000000000000000000000000000000018267d8b83ca59d4efce7ee3d73f7b984f09556ea4fa5cff5997a1eeeaeb8bdc9185176d77ad0f4d86f2e429f4015350000000000000000000000000000000014114344d6b7c976cdaf2418d7f72c120c2fddcc65c3ead067482e7073e2a3a239af19f862ad247e3181b13f5236d1040000000000000000000000000000000015db961a093b248e83deea0ceeebfc3dd57c7cf8b48cd627c5c566a4f9bea30ff0ef9cab9287a0f520a72b02d9092a0c0000000000000000000000000000000015159439fbfb91d1e24af611563aee3eb498fde666a1014a9f645037995d72dca0ed5569da7ecd084208b7c228e8a2b2ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a313000000000000000000000000000000000e1780b32a7b17464cf514efc4bdb02283af396ffcf6d1ae023e07fae02becdcc3c467f89f8edc9173a71aad27b200da000000000000000000000000000000000c3e7fd95dd823338bdf3d82fd46c265a3f794d4065d83873b1aca66da5f80c5962c9dcf537fc315d024d8cab7bed89d000000000000000000000000000000000e4eb722080e24f54fac7eed4b94e7b1eedb081c3edd7aaf5433d00829929d8bdef940aedbdd7dfb0376b3ad5544d9cf00000000000000000000000000000000158c1ff057f7ffe6492097e339cc4ce56bbefd39658ad55e08d5407619d1cbea7c83b977a1583ee48897a5e9c0d9ce3e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000138e8bc8cfaecba9fd1322a3c1682c9fc1286d78e5b6718da00acc69f811fe9f94c9f0dc9d80e9002c0022c6dfcf156a00000000000000000000000000000000021da679a068b2f5f473ceed588f07adc7f485003f7d2286a18c07b09b835881f4ab94c7d4ec742c33a7cf01801116fe0000000000000000000000000000000018a62c2f4a02b73f5a91f503b53332304afc9cd8769f236259789277599a203b8b304b38993835a87d7cc970ad514d2400000000000000000000000000000000179396865f859386df7c1b8fa84c4ee71c14daf695fc0841c293618e6f8c87fb56b924f3f91a273b969e8635d7f90985d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd710020000000000000000000000000000000004a8cb437297722c0c1a9471ff083ce60ec40c908af4ebb570c87133df705e725e3209152bcff26a0d6e4602030610d3000000000000000000000000000000001832e55a9e703d727156e4677ef4f82b86c6764123c3ed1dd94ae3b46d7eed459114993968eaf8e21cf24c59d042f41d000000000000000000000000000000000f606d5ee57b188636334ad60057cec4008ace88f14ea06324edaecb26da627670b44b6ac57b9fa2717d03096010785300000000000000000000000000000000145bf70f90a9d98f56ed38b3506556a48a1340ca6161806d055d7a1382eed54e294564de7fdbf525b0012de3d25ab5c838f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0", "Expected": "0000000000000000000000000000000018ca453b9d832f029ac8c7c70df846be97b530e6e42de3ba6943a7d0dc00296942f88eba6a9cc3352900ff124efaf7d90000000000000000000000000000000002e4514102aa3f772f2659ae9f1e2a91c7fb749ea590a3cea2c1a2e0f7236f71e182374cf7ebd2fa086dd921c29013910000000000000000000000000000000007c025696cdbf403494c5fc7f9a10ad0c549f84d1e06c5c4bb22f7a039486909c540776224bcdaaeb3880ae9d745dbe5000000000000000000000000000000000b5b5b70fae8b3953ee6661a0f4a1be25596839482d78710e584d3bcd93dff2b0bf4c8b20974744667e25fd8353cec0a", "Name": "matter_g2_multiexp_57", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001265e90c564693db716f17d1a8815a8449e43b5a2d5446ca65160d864718cdfd413d5aa024e7581421c7222c29eb452b00000000000000000000000000000000133a6558baa53a2b8d239198e1dcd81af1ee46d55137177be467a99edf282edcd47b7861a3c822f9bd0df2e86aeb5dc2000000000000000000000000000000000d8287564bcedb1e57c3d74b0d484a9b475ce3f5b0322bda0e980de8891e2e8663abda99744b58032b8d7d3adddbac9500000000000000000000000000000000013cc35410d7fe07eac96abd2b35ff656e17b6b1eba2bd1d75ce5c87c5e76755ef9c2cce70f05cdec15d1bc44bf902d4cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a00000000000000000000000000000000193f0cd6b4051cfd89f358cf6643528f0f042ae30ba3627d297b4fa2c2936426a9c1b65145b8192f65dfaad1f2fbc358000000000000000000000000000000000a92ca8943e64a391aa39126f093f2b530f556c1e3ea1b55bef1c264909dc93d260eec6420fb7a4e4a45f932d57951500000000000000000000000000000000005c7dc5832f744089d5fe034bc93e0bcca042ddd1b221cdd5958be86214831906ddbf82508dd91dccee467fd1625dd740000000000000000000000000000000011b11b3d24f44bcafbcb9baf62cef3f18b56ded696b73577375dae8108dcfb663d437e4cd9e44b7e6bf49741e058f8cb9a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000073872ce0d74ea368df132897617aa8f941b67cf3fb395ca6c2f5bb2c551f17d68b0c6ef11e742206d6559796f06426c00000000000000000000000000000000156cc28eece7bed943c8410a44af112edd8576807e25701093eac0c9726f93da68a19c1d7b294f3ae6c84e32e7c2d5ba00000000000000000000000000000000050fe5987d5fa678be3d34c50fa6c5296f883e65ac3201c333b97ec0de00dee6187d2790c357a3f8822a174a534539a900000000000000000000000000000000177fee6e2d3909c0536acdbbdfc716f6ca19b6bfee7920a78ac9725c85114c69cd13152467e72270e35006b3c6caee8c74e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e000000000000000000000000000000000dd147bec9e0d1727c9d7597dea4a5b6b15c0a603dd1b586835580468148a502289fcc38194b2fccdcd8fdf0d8ec1904000000000000000000000000000000000186501fa4f3a20e80bf297e8ef1885b7d157617701839a3b524d61f35b2eb843ff0af13e253bbdef653a83e07a5871e000000000000000000000000000000000023eda2ed9d34aa253c8bf2f3b66b3c0c2551cc0e74f43dde2e429d9dea113a62572d245b44708bed79d662d9cba487000000000000000000000000000000001041cdaeb244803556e9b20db95f2a66830cbe47a68aea262865da50ab15ba658116657625318fe46fef393eeb6f3e2ec23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000c27b1feeeb38068ee52b0fa440af2e3bcfd16601c8af983d259f2d15316b513ac3e89069bc141f02b934f2e474253ba00000000000000000000000000000000183f966cdb28f344ccae4cfda63ba6a6f29d00ab942ae7db7572cc09305e4f80c11305527b8ba38c40aae5f23165cf9400000000000000000000000000000000049cf59bbd6c26ab3e25b3cb94878271c73c0b4436573d612311feceed0f1668f4d79aad92360c1c97d60b540239ae630000000000000000000000000000000015f35eb8e4c40cb1297f7128d99b109ca75944c1943abe9158813432145a4a2a5663b55dbabfa48bfd9dd01907e1e8d3972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a3264000000000000000000000000000000000e5d9a671862733804f517dc9cae2190ef0005f26394e3161fbe771b9a486368871f4b1f10f405e45048362f437238260000000000000000000000000000000008100c6f96ae7af5fc86d9d91fbbefcc1bf5873dacaba9c3adf1b2833dd529d87f303a55e5d4098153377effd0f8114500000000000000000000000000000000010e4863a9b037d4ae6dff827a34be04c7f1627670b40e5cafb1fbca2fbf56af9ea6b24548db58e3119db64553d18cf200000000000000000000000000000000036a298ad5e8b32041a18e3f6c5847eaef20a5b63ddece41bd7dc4c4a54deb9c6d7002e6621aa01d78d64ec9991f68fbdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000f62c0bad83c41887bf1ebd2644cef0577d793c2f3d67cbe43974f460a4afaf2e412fbf9ec97404e5e882ca0b23bd1a400000000000000000000000000000000191562ec9ace63ad2aae1f7fa977b9e0606e1da9775a978b2caafada4f6b3d9104562f2055fe037cd06df6093123a08e00000000000000000000000000000000156702c3feef1baf5ba202a25b9dfd5c1fc620e837501b0c5bcb85ec8b6e3e92bad1fc842bd1a0dac363e4bdf0fac87c0000000000000000000000000000000013a4b7e869ed9bdbf9671a5d8ca9145a2e97b6885d2a93b33f378e649e0e576be65bfe849119381057337315363bab2f4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd330000000000000000000000000000000000a4b273438168494f0db235f535bf31893bb70f4119dc4741aa3c5e63e93b9a8bc001faaca10e37f36e130ef53853900000000000000000000000000000000010936551b148e16249dd934fcc83dee55279495c2a70d46dfc45945a69549657c3dd7cce00d8136e28d64b0c800344cd00000000000000000000000000000000115c053ac0b68573c3abd5f047b8fcd897e3d514945c5fe6efebf1921563d0079eadf32f7428ecb703d9163bc7811ebf00000000000000000000000000000000162e86af01daf552589b62be849e6176d74fa5da9b214a5cf2285802dbc44f346eaee5cc3d93a085740f74cf7e1b17e1dc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b000000000000000000000000000000000d77cb5045f7d4578621c76bf5b3db076661c72174508279280de3e92f0aa57057ab50180f0f908561a87d412636d964000000000000000000000000000000001853f9cdccf5e6e4b87231b153ea5257f52ff10dcb24cbaaaa95426d0231dbb355f9c47475d125ec1079b9bf26b23b560000000000000000000000000000000000fab825e06c2329a19de853a05c4bc65f16fa047eadba8e79607bb31b84ed6541b00f7f14b15687d67cb4cae0ef9c600000000000000000000000000000000005deaebb5f31a62fc0bc1af13da63d0af3c716df8c9bf00f1e831af5882b88974c49e8d35db2545747c85ac35156bb668af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b0000000000000000000000000000000016faa5e91048badedcb33e83684d2670051c82b7a1d0ead0e28f4dddccb141a8ed1fa7606e4b6a3a893c55344263eb4400000000000000000000000000000000019b2c8758abe5d339afade4ad0c1d44d651f185f8a0030b81b136d5972510b353d43cef616ce04827d56255419831a400000000000000000000000000000000124b1e87f343a890fd690e384cd156da57f4f0fc5b1ca99c73bb0571332ec4c12d3ebe955e3ae792efadc1d5c0c67a410000000000000000000000000000000014cef10e4a9a41bf117aacd2fca5f1364a46b0c4aa0723a369fc6ede09dc76dcd8cb67fdf87ac49bd4bd9981a2e589647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b80000000000000000000000000000000002862fd5f38154dd452f65de0d3c1d54403cdd2a397ef416fb92e570913c543d3368a95fa114fcf48c3bb4b68895ba33000000000000000000000000000000000e7185443e5dbb656fcb9ed100949f8f7052ee2cdcba4f5c687a65a1b45bf66ede5c60b0c04845b9a870e004f8af8450000000000000000000000000000000001817be6d13cf2a67225b2eaf073e9f1614f3bd32cf5572766ace4a91f6b6be56f498b989f1c3dd3dbc9a819c029431dc0000000000000000000000000000000001cf41fe428b088a17b8ea93a653677705d5c024db530b8300752c6b100f2abe4c46dfc24afdaa2b3d53cd8ce0df1b6a70cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f8520000000000000000000000000000000002eed272430ca3176988272e6157a18df7151bbfed5b90979752a02619ef467af8083208dcc9c7d926490b1283baa21f000000000000000000000000000000000a644f6137bde232c3a909b742d30bba096ef88b711ef100144276d0944487f9ebe8331483978a47c07d3a42c441310900000000000000000000000000000000042c67cdc10efa8301ae95d6d4f21cf152f04b235bad2dc5a61724cba64083f690b3158676ee6ef10f52dcc7061f7c7d0000000000000000000000000000000007018d0aed5abb744cb998f84140331fb2cef8d9e09c76176def48a85370c6247c2ac6fc726eea891b2041ad5edca7f0244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f00000000000000000000000000000000141c0edc966b7c845d4e68272c6a71f8ffb7fd8d56b7cabcd556a98422f830d7a81d123d701ce1479e84047328ac1f3100000000000000000000000000000000105c1164d721b6dfb05b6b69955b2f25db0e9fdb58600a3229dd516076087aaec05b837ade68bd2a19917eee7b9a22bb000000000000000000000000000000000da3dd97e693948fd6955ae52d493b3a2d2896dd4ad00a0b549d4d392e81593472e4f9435a8b7977f3d58e324c5b9af800000000000000000000000000000000068c531ddb26a2299cc584b5bbfb0235fd774a2447134c06e7de8b94993804958bbf1ee80728cc6db647e8a244462372ad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf3000000000000000000000000000000001881432f4742dbe41bf774930413c98d49a781a48d6c64ee1a18f3076bc6c0e1214f92d5bc84ac65ee1c586c437d697300000000000000000000000000000000067e0a95f3eb826f3efeedc1882ecfa30b8b96c92f626aa324f4044ee74531fbfd50a221b1b0e0182d759d149d51427d00000000000000000000000000000000173f5be7098b756ea84f030e374973feb4f8811118ea6673db1db75ec6909303e571ec5a1d55a6bddf32fc80480cf103000000000000000000000000000000000f28540976a6ddb277df5951fe58e7310861af837cf31fe31c24f7b979f72ef1549372e7ea1ced15b655d24293dade7854b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c600000000000000000000000000000000188f296e218719bb9cabefd4f33d5728a1d280bc59c3d826a0f3b5338f92e6544a4cf36f1a493458e0adb246c01a415a0000000000000000000000000000000007dc8e4222c7ba78190a8e72ec7e6980e2581f51a8d6c41669b6fc9e16d50a2bf4d422af73398e76b2f39705eaf8a6da000000000000000000000000000000000b25a44523323301cc01b50d58726768c2cf61e691203dd34a0ce8d58fe4f72c1c33abfb2a56e0425fa9b7e2fe48e870000000000000000000000000000000000c6f11ea269d9061d2f462ac37401def1b2b28c47b84344d04d1f026add3237d99a586e3fcbae347a4ecb5646c8c569fae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e49000000000000000000000000000000000c41a02e937f8cacc0be5d9f2d9fff0d6d4302fd252f32145974206463854b3a7d09b3b147cdf2d7536e970dc13613ab0000000000000000000000000000000005f9367f4e31f7e4d6e21664ac13d55f501f5368c1ca77fc439db60e1846861e6c4c3c44909469f88e02cd973499992300000000000000000000000000000000131fe6df7fff97f132bfcba1d2599a862c1feb514a05b4b7b0bccf49e00aaad043edae9346bf726e2eee498dbadf2067000000000000000000000000000000000e59044f0950a741da3881282697f4a1a522b026e493f6009227da4c0a963de622d5e421c30e0023f4118c9a036274f859b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4", "Expected": "00000000000000000000000000000000144433ad3afca0a9581e7e87220a4944e26ef2eef6b887ce77d2a2559ced058e7349b36efa66c492cc75b014b3448ef9000000000000000000000000000000000267b90e45d7001edae01fb198d16dd37c43cadcd2ca87bd7cd1f0f65a95148144f5ddfe75d344eb4573c1376aa2728600000000000000000000000000000000050ade28b09b0394b08d128c089808021e4c65dac49d9fb45efb93792a4faf210230b650fc3ce810fb8d11947e9af5060000000000000000000000000000000003b1d7dd7c6d944d16724fd1bbfe0f53b6b50a70e133dc5998c82b51f817f489bfe1e0c361be36fa41f5af7c1577f2ea", "Name": "matter_g2_multiexp_58", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a081f037738b0d812da43a907e7c624e331108ffb72104d82725b9c14dec8449f5ba0e8c1a3f1379cad2c3e7aa99f70000000000000000000000000000000000937fb5d8b3c258b7b28555fb59620f114816f0fad46818a5f100bf7dc3332a03d285eda18e31e4047cb2606bc53b20c000000000000000000000000000000001574e355b7570043bf36ecd52f9c4d9ff556146d81a1e9d088444805db9b3b678fb55774865ad34d21022afea2c154590000000000000000000000000000000009f70a5cc658cdab280ed65e13aaa319049b9534a222217a08168047ee2491f25a9d2620c7343a6426bc54a0700bdb4fa53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b460000000000000000000000000000000015a76e89c8938b8a27e4857aaae8c942371b6979605adf774827e9438ef739428fc53b65d32e4e152cbc6a4de42b8bf30000000000000000000000000000000019494030ae0507eeff20b69b4913596c1b9ea6927157945c8295e273707013ef1f2cd08c058f6b469a6c99ad73acc28700000000000000000000000000000000122ea7ac21a27ca7c4b00207538bf561f688429999332c45de7545046acbd6d9e96d31f5f6a00595eeb212918a28d2920000000000000000000000000000000018b023e7da67cb8d9159746bf700f9e151fa60ba8f5a28b3739de005822929cd28c49b9dbb4ca8a10729dd24771730ff4d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000007811c759634904765029e955c3deca648fba6a9da6433b50a6d2086a59e65811d52d41ed8ff2e9bd63a4c0828bc702c00000000000000000000000000000000182c86cddf5e20697462c829f41c7b49e7976880311b01ed4d12d7174340799f19db0f295263a2617182bfd1b49e0d1b0000000000000000000000000000000011824bc20bd1b27876b4f48aa8fe3063f826b6b2c3dd777fb8999a25d9139f218f6f288955274884ce96ef2dc6d34d120000000000000000000000000000000000dd310d5e141e4eb13380db828caf74f62878959b6b2df998bebf9306965f723fcd4dae7c25bf2f79ece3e8e9b92de61b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c740000000000000000000000000000000005667d8c4f8dc3f4aa0021d1026a1d0dd0bc3576c49339262e84d20198fffe33a389d28ab1d782e9d19af761a2f097b40000000000000000000000000000000002803d5ad6393d7072e149f1f2ebf70cd8961ba3bbefd648916a8ac5a5eb893b71bb6015e201dc241537ad5890024239000000000000000000000000000000000122e1d0e0859b04143f23c4d2d2ffec09ca2ce5eaa9429dd0c047032d180bcdb10c106071d9f9701c006e5eb8ef88130000000000000000000000000000000008347a7bdb3b4f381b58ed3a128134c09563b345380ec948943e738347de5b5737540b57c28d00b9d060c60942446617ee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a5000000000000000000000000000000000b26aaa46a279c482fb395ddb84d5b4c9c70102c336cd565ca9eecf62cb96f59f634adf46af748826590fe65beea752b0000000000000000000000000000000012cc63256a9f73f450e86ee38c54ea78baa5bf87d3bc01320f7fbd85bf11e19f75d787b9b12b8f2c7634368a9023de880000000000000000000000000000000006392fe611835f6fd50229725d71d435f704f78cabd1b5569e1c5a89d4b11f911f0e34ec034369f972a80eb407938b97000000000000000000000000000000000f4ff2d6a991fde9093000d7bd9cecb289383d259346d83bc9bf5389d4c39c82a0e1d7deb84b90ef370e0a19fce28d2b3e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000193752c40fa0f466f7c8bd26658f133d0283d2ac3b02eadd27b3e9681329307f91a1512fbc53e537f9e1025a3d68a7ca000000000000000000000000000000001106d751c9e1637f00e51e0be856405e6b69421d81bb30b9b8718cbc9cfdc36c80d2848bab0d5246da84f10b478fe48e000000000000000000000000000000000827a83f28678c4e39c4963e95c2404a70691885788e5457e149c0c45d4e8c74eef55223ed15cd75fad9f7209a6ecaee00000000000000000000000000000000072667f02b781c8e0a75d0ed8f3d55e668ddcc8c61937c80653e240c3a744c961055c782ca41b15211c0f1e1ba800bf5ab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001419629aaf0baf779feca264d0d9846b987506125b0049ebc8b307c4e3ffe00da1284a94a012bfd60456a4a937b2e0e000000000000000000000000000000000119a801bd0a5a1c1b25cebbbcccc7d2bed9baa4995483f4ae94121a8c6cd0c3f90a26234f51590d66cc38b8bef9020d3000000000000000000000000000000001125bd15fd9814ddd15be0997a6961b6f1c05ce7944514371f10c8e5bde271c4b936d6537d91ebed740fbefe6b281a0d000000000000000000000000000000000982a2904a524b1fafc50d540506b8fb07c3b4978310bf3cf53ce570b1b05e746981bcfc06d59a78d170573b09347f3fc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000b1da333e508ec6b0329747fef35cb926d922091d4a45eab7cb5358f20496c66e17e46874ed9600cf4252432c29aeb07000000000000000000000000000000000c757daad8f3ed7dfd64782548eedfe904f7ef3bcc11eefc4781fb37159d07825a4c9f3fdf9cb3d8f3944277bf25f88c0000000000000000000000000000000011160e21503d6fd61a2ca0212a7d48317186f259a987a17cc3eb04a6d9251736e4a66b739a8f3095684b7d91ce6f79730000000000000000000000000000000007440ec0f9197352a3148f9bb3d3dba9b1d5add903e48b50ef3f6879859b22ea0e31b46ea4ce566930d8853520abdd14ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba00000000000000000000000000000000070ffa4d522df8b9f62aaf36132bb1b857e177280a7b6d3af6bfc79b73ad3848241df18ca7f8993ae3d67005ead9264d000000000000000000000000000000000e32b65bf035bcb11f86c60a334622d2367797d0226761b58a7db8c7324fc4bb498a558eec509c2326fbd0e7bb8d3d19000000000000000000000000000000000dd291a760393c6e962818986727e5ca5d46544dc47eb49dd828c6f74caf0599e88c4293881714c425b0697944faa861000000000000000000000000000000000f7ead0be081467f3371ab92c249cea73dedfefcb6aa16a162c06e30605e104844c3dd194b4a89ad5230f596bef64f19e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ca545b53836899e507880329799e4c1a1acc17275f5d71d87b9e41ccd7a090da854f9936254448c988ec772a813bb6e0000000000000000000000000000000016c9b03fd01394560497d6a03add63c034f96744d96a13a4ec92d28719018d1eba1465e4332e53f37f2aec4d93d4ab7f0000000000000000000000000000000007019f5201dce326d5a6a1ebecf3fe50e22335593bc9d3e62256351c591f0a1a577d916055d79c0b4abe191b6b8011fe0000000000000000000000000000000017acbe72fe30c386e463f3e9b35a474b902f6712b30af88ef340e6fc6ec0fe2e606c7e26432c2a4de33a12e35ce41868f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000057f856ae648279f2b6dd17584e1388e4dfdc9e870db48ee6ef5f58389ccd4ba17e074b79ae12b728c59e2f91bac5709000000000000000000000000000000000e0f39f4beddbf05fd700458448067b52c11e963b22603f10d697d6b6286b1449b1663e032bf7bea48f2051d8ded923f000000000000000000000000000000000022cfadc1dc399ef5f12afe1349d9274cd595a9ab6ef7ffdd68f8bd2d170a4a783ce0a7303878d809a16bb8073d79860000000000000000000000000000000007e301565124eb66d59a70897f2ac356e7b0c1bfd4e3b57e508ba0cb5c9c881f9de86b91fd5133aa2977c8e81138d66971927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000000edf3fdbfb03bc07871079aa4aade538a97e1619b54d0692a7f5f73d7fbc8abbf680ea3a99325e03c0501ef174deedd1000000000000000000000000000000000b8c1b5d3c926d7da6e0583f67d981af5286a04429e857b0aa4b1120604f9c8c93f04e763da169137416dc9ec4839a910000000000000000000000000000000006ca2aa4c7109f043da9cd90bc801404685db802eb8bc925d9d098e7af3d9f95ca490790b2b1c77995c050aaebb935db0000000000000000000000000000000001f40a2090b63f94f93e8b61b5ba1ac62a37548342ad81a9bd99ce8339435a7d7477c3b9cee9b531a1ecdc85a72041555ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000021464dded318cfa86db1e4329f302bbeca7095d910c4260799cd2a60ebb20e60152868e67a48b86f44000f267d11c33000000000000000000000000000000000ae45fa46fc8e043c3df99bc0d87ffc5867208fde0eaeda782230341a8624b101346f35fa24e1dd67ab200f5d6fbc8a7000000000000000000000000000000000795b9afedbb128a46c1eb25c52a71375903adf7d3520535372d9af5023dadb1dfefdcc0cb546e9d218890123252946d000000000000000000000000000000001852511855bb368cec51c54d95b430259f05dba6bae53b5c42d69f31371c30cb611037fbd81393a896cbdb6240114549d37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000113b883c6bc41b0673145bfeccda414af45efe5710f436977712e7227f38911cbae851dbe03928f38e310033458eed72000000000000000000000000000000000853e32773ef1f95a3936aacbca50cdd5eed3d08dc467d7ee834487e445fbdaeddb0df394bd0c91fdb06d2883c4dadd60000000000000000000000000000000013a7f9cdebb2ec37fad172d31a717f4b538a8ee74432c5a5e6410460eaaa3b5f24d223b76bde4277097e93087b7136330000000000000000000000000000000003d6f141b56e1e2e400fe821524017cd972678a7d64f660c313e6a8910b72b5ac04328d45945077aa2946931c8dbd11706d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b40000000000000000000000000000000016adbeb3530f6b451d870b2d8292a01143986cd9890c79a64764383575771b8608ea61beb2de87bc034d3b8a085958be000000000000000000000000000000001125d7cf83239e4341c286fe0c8739e7013b234814b26a079ffbffa329ee4705da81fd12f34f49d821690a11b8f83c5e0000000000000000000000000000000005873dc5c0baf0f3297d884ac7b652c749abd0405b96ba60fe396efa179a79fa55be76924b0690c9a528c605ad4f9e120000000000000000000000000000000000fceec23f479c72e0fea0d10d3394d7121bf1673250cf1ebe72eca60af82f232fbee342e2c8705434394d4e519fbb40f56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af0000000000000000000000000000000005ec966cfa28e105f3496f977a2f046fb206a190fce1a6062df0fa1946f274cde9f6fa8a71089af8cc2fbc2b60746cf40000000000000000000000000000000013c77ab66fa92a2411391d366a331a40accd120db1c6a656bdd92858826fcbded296293c13ee189ea3f34635de56732c00000000000000000000000000000000162795b6feaf6a63e6ea2d34f2bff2a4985ad26463b8fac69f8525eb0a005bd377fe7ff4aae820d361592d2d88f98f5c00000000000000000000000000000000044c9d5d3bc0d99693f5a0605ed467cca8b5dc7c7093294d14015b59bfd8ac6bd479b73ed52fd30d8bd891ed971912c571e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c", "Expected": "000000000000000000000000000000000c3bed2f51a60f9afa6655853ec2f0e9d46bdc1277bfedffc468d9f36cfc7ad9e70365fecc84a5a40d863dcaadabf22a0000000000000000000000000000000008c5894a4f93b02fa1deda8b556798fb7d71f53046ccc305588bfc00b68bdfc34b3f0bf154ce7cb50c9536ad45e65f300000000000000000000000000000000003699501ebb9698e98dc998fcdac54dff895457d2e4e0a0e2d65d275b5798dc016e921bf1f65fec0f284a563aee66ca70000000000000000000000000000000010389c73de7f6d860c972c1f09dd24137c898e92935c45c10565ef3da3406cf521647ef80688f6e799eef4879ca9a6e8", "Name": "matter_g2_multiexp_59", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000114b9c33bd09899c684e81a5a4e620eefa4e620c01c391a4df5caa75be462ec7ab027a9ae2c31d6643c48e3d75b6ced6000000000000000000000000000000001925084d2a1f537329e23c77b8a820c385ec5e12e4a145888882ec611e99b05b789d79bcab48326db4424309c24d1688000000000000000000000000000000000a1dc78c25cd16211a38bd0c70d24c84da1b83adb219e1b9c06fe6a6669d6e0281a155b4cec32d32751fff653aeef1990000000000000000000000000000000001daa74f19cce1086a87232464ba903938465da5e3e1f9ddc05a4b4dc13f1026e1b07af7254d515d2ad6960ea62dca1f77f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da0000000000000000000000000000000018810adf0cc793c21726e9a27b7c558aa16b81af73f22629c478d293208a107fbfed4511d9cbcc25fbc2826bf004e7dc000000000000000000000000000000000356b25cbc7cf65107438125c930dff24b7786cbd7eb744d7f27967619d5cc02799451ac8814782eaf9aa331e6f8dbe7000000000000000000000000000000001164ab32ddbeb11c2c8baf7f311ffb01bcc367395bc7ecbe5642d344a8e879c74a554b3f9e4b6ed7db4ea0f872cf96740000000000000000000000000000000017704b1dfb111807d1f5d90c370a5b2968008a5ee9fd72262b6543c93fa168285c04931198f5195f1abca648722ebdc5630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb0000000000000000000000000000000000a336a04a8fd8e18dd9a582da897016983d9beb0fdbcea6c88b7c0640620be52bff32afbe700599e3c08669c457b760000000000000000000000000000000001765fe4faeeb13fc2c007682c031ea7ff2899090e16a9a11959c5c3ae7881a1dd2c6d2b7f5f708a92349a2b0de4b92d5000000000000000000000000000000000e7c57db660133ebeadc2cb2054ab4ed16355466932685d4d11038e1e1f47b0349b68bc4e918dd48ef8e1c5d7cc53f7800000000000000000000000000000000169b629ddd7add588b91d9866a750570dec58662e43409031a5e25f1b2913c5c5a7a7cf666953c99835431f091ab1b140969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a51620000000000000000000000000000000017d03e9855f3bbee719a15208ae24324ebf1879972ac134b027c9e03444a5736863bc55604158e81b38c7fd78ba4bee7000000000000000000000000000000000468f7c5478cc0faab7098dbcc455bf18525b56272c2d02cc1febc1825579a613edc6b455764ffc71c903a0704224a4c00000000000000000000000000000000067104ba5366e7e11bd4d516565d9cdd93d4390f2af3c1ef2ea3b1e84ee8e5c0e0fd8ac11ec9d2553e4cc13b277d473e0000000000000000000000000000000012e10495ba15b29c669cb9683b2fc7a45fe7ddba743b4a39677fbf85aa738480eb9da967eee69b02ef14137e102e240eddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000078f8c17427847ddaa1665d206866231a5f36d3a7b4e8fa13910161566163006b5aa5d9696f423d0c44195de65326f21000000000000000000000000000000001613c465b65940f43c61b5e3c93313ae49d92728518d9cdfc57b49d6924479b70e281e724e04fa5f165b5999f1c1ed3100000000000000000000000000000000031741b6830c16d730619457d42767a51037fb4118e00bfd6cfcd8baea35ae76a5159bf1f4639fc2951f0b57446110e70000000000000000000000000000000011a618ffbafe4bad0a435d04084233495e5f7fbeaeb66d0d49a8177f562329b52a5ed4fdc680b791f273a7b0d3d4b349191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000043c09eea638e524661c60ae3704fd1c18c46443ae134a0ab7b9a98cd398377febd9026c28b3e1e50de98766aaf0083600000000000000000000000000000000105918aa1476cf52f91b9ddb7c23ac18af3bd5269dbafc369713687010720affed6b12af9414cecd521cf0c7f5416c350000000000000000000000000000000019ab4a3eca904a15782f560bbbc8819dc09275f1f6d7c3b8e98aa0a96ec33dcb528284636b0f42ad0d503489d17161ff000000000000000000000000000000000a2abada18e79c548d5829991a65491ebcfe0e1a2c89a1e05f06a0ecd197797c5ffea0ae90b61f54c6b3fc844e0eb3ddaa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f848000000000000000000000000000000000d457cb2c77acc8ba4b19ade0c724a2b6b0966ecfbbec8cbea745439b9bb7f3dde2febf9fcd6c5e6139fd7175e57b1720000000000000000000000000000000003154466283addb0d0b5d86a9633f8300960cbe8bf6a1405a3a040472542e9da63fd4f79a43d641a47c2b69a31298d3c0000000000000000000000000000000006599794823797f8ccea9daf0459b9d26e0d207f5fb95383c6b61eba38516b272e8ae6ddff2a9fa791e69c0eb25f3e470000000000000000000000000000000018be316bbe0416ad7deced1486d4e31490f5dc7e379c17542b7d3e9dc77bbae9c992e657c884db320cd51c2141a4abd2049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf10000000000000000000000000000000007ba1ec5293d169b88ca4d2d92eacd51f0b8cffdb403632ea8ffdebc37f3997baf736771231335d12717cb45b51be31a0000000000000000000000000000000013505cc24222fb2ba9e25f5f3497653462f5b10bdd0dc88f9b16d5643a99ddd4a7749dfa6b566f41cd2da7c2b1ae93d2000000000000000000000000000000001465fdced698ca76d5faaa7e4faf1260cd5c4fd2939b16d3593e3588c92de3d003540ec989be9632fdba4ecae889ef180000000000000000000000000000000013a20cecd5e8f161ac70e40b8e9ca4c23e2b267690a3abea941c293b03acbbe4fc68a1e7b6d35b79ac46f65edde73a3e0502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f000000000000000000000000000000001936436783f02f3a5307bfc0bd8c0a00ed8013508a440d040ed4f45b37a4e89986102964a328e93fabde6d9dc7ca424900000000000000000000000000000000000f16408b869303181b4b4877b554353b26a7b4750b711f3c41cc4b6682b2113cc772cf9bfcd0cf60e59ef29a5d0814000000000000000000000000000000000d5880e2ef94663ead736687ee725f7ce98fdc594230c1ac9e8345d39754bd616e261076aa5362776a6026129bff105c0000000000000000000000000000000006865ce3cdb5081e86535beb990d95ec3d75f67c7e881306607e4876c42714d627f8d548849aece4382d1c8f2b693bdc9787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef523713750000000000000000000000000000000016d941b6a0dc023fa2699c836b74e16c31b4cd51538f73fbb271d163519d4de1cb0f6ec2f8efde22c74ffb532c576b16000000000000000000000000000000000d10a7bfe9541a7b22d455f1b68cfe2422a83a070d93476aa0844670f02aecb36e9f41b9d66e8e9d0d67c0ba85c99f44000000000000000000000000000000000d7873f96d45fa8c9ba9cb4913a7b01c8e38876b6bb2a05506d23df0491bcffb42983ef663db85bc3cf755f476291a79000000000000000000000000000000000c22fdb83f9991c85b3577d1ed5a171f28460d79dbc6167b0c30b200235c512f999066eb1fa449115aab55128f8f2dde10b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000010faf9cb9d0fcb487c9e86a2d2123105baa8691d82ebae8f5bb7d5ae7b7d8154837120eea86dfcd35ea5482a7ebf7f8a0000000000000000000000000000000014e40640eb6e8e38651a2eac05165f6cf5e0178b3711f34828766ff9db951e1348f0cdc652a78840dc24ada8b1c835c600000000000000000000000000000000129db7482ec62873591018a8399a8c5e4bf00e8bd9dd78dfa3d0b4cd1d93ce5ec7531e56d58b7a1cb3e58f062f6895ee000000000000000000000000000000000d8db3b54b6e71497faed107b31f5e44f328780cf01c62cb5ca00f99f10385ebb22a367cc89505640d1106a9ceec98c4072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000011ae9bc3ce04df2add17e57f260a72f88f19a1e44b0b074cccb7fd547035038d19e5f2228db46843343a69823decda370000000000000000000000000000000015ea64b6147ef76212bb5223d6d5ab9ca866799365683720866d8ce1117f60bd552a8e9981c095894258ca3c1bb5150500000000000000000000000000000000173bd5cb455b80b78951b15180fa7f8fb4725c1a12e5c53df1b9b31b45a29083e66c7116741d9aa93448c81b5e6014610000000000000000000000000000000007eba059855ab058c2066c643ef5268c864d09ec9962537d65a1686322c374eb5ab8eba4c4260ad0919dc18b4289a694f3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d6000000000000000000000000000000001598471460ae082c2e2568602c99923193c913b9e803cbb7a4503ceff369e8c4bb3a19ad245c08192e12a2e9b3e75c4e0000000000000000000000000000000013b289bec9d97c529382388f7037749c10a64f915746d23d8f37e15db9dcb173b3a6d00bf45e67b8c70959472148321d00000000000000000000000000000000094a99f9b031a51b7d54f7b8865621b204c85d23fd66fe8ce007f0b852f8b5b895010745b2fc469abb670e38fbc41e50000000000000000000000000000000000e36daddab2134f65696ede36c50f90f9a1c56165e09243cd56fd3d9902d3c78cd85e7028f6dd466f6a8655da62ecefd393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca17700000000000000000000000000000000050a818ce247367e8b57673d205d6bff8c650bcab7bf794dd32494669eff865fd4e05d7b4d35eb579eb475a3a0320ff80000000000000000000000000000000017ae5d612bdd46e1351dd1367c08c16ceb002a29832eba75e48d4c82e364f17c58525ee653a0940955b874da6a5bcfcf000000000000000000000000000000000eb2075367b42a0b3dfa30799ce1ab327eb583316d15b8cae21b716e6c7fd8cab96c67bc39e353f5e842e74995356c070000000000000000000000000000000018ca4b533da1baab37f05afc3ae0afe976e4f4530401d2f97176f5c73de3eaa75b8a34e8c6c0543ca0a08aeed28e478bdb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000b3f4bfec920018663bb39c5520491da5c538f82138f03390c768e088bbb2880287196af937f1f70e215edd49d1872ea000000000000000000000000000000000037e7607a60cf235d8e4ecbe69d378dc02f0a8e40b7f23745e15a73fdcfc971cc8707d55a8c5b91d9a5f42c2f49c455000000000000000000000000000000000467df75c2703ccff1a01fa5bdebde210b61b5f3fa33e76e55be5dc953f4758c3a2c499cbd42b256ff5a2005949d9bbf00000000000000000000000000000000010d574c69050ce9e909dc23a76e9a2106870e8d8ce2a0e30d42cbfeea56ce3167535a9af1d453d4d8e6a450eff870638e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf24000000000000000000000000000000000907fb825f247c85d93fca36dcede9c22a409fa82fcf540593e8247c17875a1385fe009f0ff43853c404f6c96e2809ce0000000000000000000000000000000012bff10bd4162207870f6363342f2541804adc6a4e3f7b8be51d361be34def7a85fb39357c85a4e8df670fe39233bed00000000000000000000000000000000014f7e61ccd52bbf6d050c9d506751e03c8771b320872179a9f0161ac5736edc13bc133bda6239abba1ae09bd6c16f0c3000000000000000000000000000000000ca78624563584f8929d72668da70218a2da12b42c4b894108e6b103201372554fdd6b3bbbf2d94a9d0cf4053eb07d460940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c", "Expected": "000000000000000000000000000000000de8e87899b294575392d523ff6e153a7c2038302ac74574bfae7fb222558f2b4c9556be1bc2757b83ebc180ae710187000000000000000000000000000000001881c7688debe3ff795788c90397c3fe3d6d9f56da9221778d7b12f5a54d8c0a00e1a8d4bb9c0b1d578dff862516b5dc0000000000000000000000000000000014cdfdffbb956a20d8521ccdb214adab14975d22ffbac107b2c15f42e97bb823c6a3945a5b299d3226e2044e64f8d1ed000000000000000000000000000000000eb769b301cb7c0c976623badda4db8ccb18dc7322472b5fdb969393d5d82b3ce94bfa59dae06ece424bfcb88e24207a", "Name": "matter_g2_multiexp_60", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000164227fbb787b2d47ceea93faf1cf7890f48107ffae3628192235aa57658d9a2861db13fec0e58c347571c2ab0cd11ea0000000000000000000000000000000015478417b6758826b1d6fe0c562d43451e289dd50de31ef365ec70faf961ebb65b510c4788b6c7da2dda9cf56d3c8a74000000000000000000000000000000000f9e50d802ca8cbf80caec6489fbb24a2761db1245d9f7e820e6747bdd0855902ff211c427c00157ed9b1bffdf39eea900000000000000000000000000000000128f69ef5dbea5f80dbb9558a25f133b9ad77492250e0654f8fa5b55266f2fd26826a5c373afcd74990ebf768d6d8fd20f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000005f7cfb31492dacae51caf4036d99d917fa13b0d2353bbce4e6547ea744b3a49b162deac2f107149ebc2f79e74828f720000000000000000000000000000000015ed4627efa9b318cbb52f518b734327f5d1cfbb097adc6184c5034620504181a298ac7e52759586dae2e107f121a9b600000000000000000000000000000000023e832638849599d9d7854d3ae18648e67e8938ebf606a7c86c3a7ea21cab8d4dd5d9cda5c482e05d351ea3ccd854710000000000000000000000000000000001849665396bc36d0301f4c9adbce81fd2f2d0c7f89925487d91a25c6bd0730ce31678694a319666cf42162608ef15a834680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba20000000000000000000000000000000012c9b607e29e35f260f3c4617b4217d5dbc6953eaeffaaa903710195e080d593972e7794897eb176aae3539401a483b10000000000000000000000000000000019cdae8d1d9035d1fc4b4db09e7da3c20d3b8777523155d407cc6565a71a6c951eca609d328ddbb165c2b5a3e6b081da0000000000000000000000000000000009c4629b67c1c50e5fcf316136bc645e9e62ffadac8495c084f97e32b0a3990b3b1019261f78de576ff7ffc89e36e2af00000000000000000000000000000000070a49e8892c5b523f5914e2341dde63127b694eef556de6dcff603da109a53b342363d9a854dda3d2833e25afd5b57eefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad0220000000000000000000000000000000018d3c9259f70312c803dd6bac6488541f92482f7eb61ead71fa42bd5e2cca9338218d62835051bd308799beeed3b422b0000000000000000000000000000000005e0da6859601b6ada82b1826a455a846f8b4e54d9f22c3c639835a8a89e17ea2d76e2f49fb151f519de3e9adb78f0590000000000000000000000000000000010113d2fdc1e8ce0027b651cee6f9f6832b531d843db3ef7bf209aa00018715c1c42c68a82c53247a267929ea3c9363f000000000000000000000000000000000e7d1152af6448aca78aa7983013395f0dfc298848d86def6f017780e9cb144bbb21540a14a4d47b61d7a9b8c62376fc2c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000014125c81d4d7a8ea18004d798311f0d80c41c8e3a08366f686145e867192bbb13244f9f77217559cae72a150faba12a6000000000000000000000000000000000fdcaaf79c0607ebe9c8ca309d29d32284f3567a18dbbd23da9d96bad7269395ec2445d153711df4c883e8e7f7b02ab2000000000000000000000000000000000d34dd6636ef18b14f011fbeb62d33ec4358166f96f38a54c36b8797b51c1bedafa43d9f51fa4afcc2acc0cdd991997f00000000000000000000000000000000017337fab49d545caba55b763c23ce9bb3d3cc475f5ca37a15322e94c37825fc800cc7ee67bdcac66f9b5c22b03bf6558b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc930000000000000000000000000000000013434d32deb96edafc9a0e855281970b7c748c92b3472b34cc758dc3c17c4e6fdcf3190c910fa54a0259ef8bec75a3b300000000000000000000000000000000137df92ec14dd2fc02c0ec15a4e63547492154b4d4809e25f3ebbf24fe84255babfd6949770ba61637cc67e8ff299a2b0000000000000000000000000000000012fb20ef106e8cf3c79173e15dcdddb216c25a4de6797e411fd11d5632aef1304b36f8135c915c8c38caa2d778788f060000000000000000000000000000000014ef5cbe5711a815b9ff845e9201745f4117149b54ea3c6d1606060a192d513aa8ffe73425e37a42537773796b6fac8f202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000000b1581a5def94e95e565bfd402cb84f2f21c181639c047d8f91044da84bb7854f5cb4eb3a6cdeb66569d99410ca3ec6c000000000000000000000000000000000d8029828f4ca245cafa7f396c25592ef08f6768e1a5b806450be6ca5b548cfb212d8c4787c3f15fe922f466dbe518c0000000000000000000000000000000000f51e01a044b6da437e3850349476437e4ff8b94fa190387099b17e6462040918cb2eba3b10d6044ff2123242005bd6f000000000000000000000000000000000991201229a856f88348381e1f2e282f0487e7daf1e5a4ac3854e66fa3d1303e3c20eb9eca605859e7d46dcfdd7615cc8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a00000000000000000000000000000000011f1bff5df413ade311b0bc3b46c4ecb11e386b886b71226987f14bc1a3a4b986412c2bfe8a4618ad5d70afacf4a3b4000000000000000000000000000000001972f49fa8b36d11d9c9d4ed6197261506b892ce6dfa932b87e686cb197560dfb8718aa413c38ee1bb771a5618c17224000000000000000000000000000000000e563bd240f5e18b518a792750c00aa5dfbea1f79b80a71369238ef15df9885d341d6901fb9168a2e74249f036e9a688000000000000000000000000000000000670e59ebf6e30b458ea505075840ed5348563efd536c31003d8d0bafdacfec7ba1ed401c616a3bab431a0fa71bb6188ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a7300000000000000000000000000000000071319574a93739586eda876ffd3be5d982e6fa04f5667873dfabfab83ddf603513394e0dbb9f418e725b02d2dc7b876000000000000000000000000000000000c6a8e0261da2ab499bf9a639a6e261e8c479f3f2b2d12992b41a3267e034c25373d4da4645626e6343e867466bf3626000000000000000000000000000000000045a0312dd5fccdd19edb65e24d5ba50e44689a9748ed9ec208320bd9eddf8d606b9340cd34ebf983e69a65c242fed900000000000000000000000000000000090b3dbebc7dd49e9f764e99c43b5915b67bdebd00d22c80e36e08873e5c5186bcd082dbce94f4f230b237d60cab7107b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f60000000000000000000000000000000007b04063dc315025b8545cef11be6b601fb4ae02597d75979b4946f3872764ffdbfd309f5ab3b36fe47b810f8320c1b40000000000000000000000000000000009361927d02192433a8d3c3d7871d76c6d88361774913067d16b68625aaa60f5a4ca19b6fd4140a5a11f92dec57d783e0000000000000000000000000000000012501f19b73fc6ddb4d194895e5cc2b89ca84defb7ae94f3170f25417965102fc195f38dfb7a2d88aa4b24e4a2fcaa4300000000000000000000000000000000141d0a0be60c32247f6cb0e0114251ac68c90fd43651d58c3108c728601ad6efc27c27a331a2f086d55aed54b3585fd1b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e47200000000000000000000000000000000043f74a82ebfbbcf4abf3fd02eaa4483108a3446c9cf041bc67f5078d1774308ddcb3f918d7999d1e2c0876177cab6790000000000000000000000000000000000da7d4fa72dabb314ad8f68b61fcfa38627d1d7719bc07767f596671c58cca16e005d36e42413d03da3c643eb46b1eb0000000000000000000000000000000019f3f8f1a4008f9db1b604373d3566ae7c14a9147f80597a31839b83f0f8dcdfd829f7fa933fef3499b671867c3121fc0000000000000000000000000000000018bba4bfcf7629fcfa47935e36462cef4fa3751c7affa2ee2cb2fe3e3532d46ca1d247393ea190fb3f48077270d6a8b22810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b89363890000000000000000000000000000000005a1e0e3a023f67aa7ab0109814f130a05c8c739036b98c70c8a8ddc1828d2cc4e2fcd16de4ef038a7373d15c78e81f10000000000000000000000000000000019e2bb467409b3dfae0b06244b4140de7f75cb105ab897d1ffb999c6b53bf3b60a3d11354815621c5d9f07962a237ffe0000000000000000000000000000000012e745499d5ed626b4762b57923bbfae7f1209408e7ecb8813a545c4ece0ec7c48a4015e0e264b47fa08fa82c39d3a110000000000000000000000000000000008acfd3c2a2e17be41a70ebbd1ca2cff2eda8a359e0969a389ab0a6fa51db5601b386dd035b26232be08d704a02033a7175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b646000000000000000000000000000000000fb464af51161f9c2758acc09d16754d4d8ac52a37baf2fb6ccd3bca3058bd3cd204de6c8a0bfcce8822f16ecfcd0601000000000000000000000000000000001819075eaa6d9e3f0568ecc2e507370f938a65169cea1ecc40c9cb4d02c83d7964254602e3d041ba0f93c24369fdf3940000000000000000000000000000000016c179832739a8129d2ef184f4d1231d24bc8d4093670a63d73771983152ec322b6a8c954565d61c2af76c4f6ef5e8a2000000000000000000000000000000000f6623578a4fa45614f4b74768adf65a753a35dacc84af005fa4d7328d733a09f12f709a7bb7f89060f60d4fac85780ae7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000189724a2a0723e7727d224ced126e4288f4743f6855b035722f2aa36cf2f0a6fc23f6835c25222b670c15248884451b0000000000000000000000000000000009a57d85140f31ca58e38b4a99c4ef103f0a4af0d5546d416134fa8adce6ecca6588c3c56ba06b2f59015acc1a081099000000000000000000000000000000000dfc67b7644851c3e928ea33aaa0f745a18983edb7488b148736e81ec0c62345c11e3f0dfce729d893dce27ea249860e000000000000000000000000000000001712009a81e06a85a225a46fac056b139c8da05e6b72074ee4079316e490a06f51c62241e380909b86239d867d631be16aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea5000000000000000000000000000000000976746ae4d9325d5e8300b57ce99650f28055b5e020700ee5f124fa76ef3bdb9923101c3a1f46b6985b8203b4e8c60600000000000000000000000000000000057310c3b6cff6c849938f533b401b0cbe10b6ff3736c79a968009b2c0b90708b6b9a98b8e594cce09c579a64ead846d000000000000000000000000000000000d39511e47f33e310332178b8a0210e76e4d4c7408ff5c2374f5e7bde8335525e03897cb3e2bdfe59bb76b21cc6411df0000000000000000000000000000000010c46a621b7fb2e7ceab8943b3371475d3d6f132fb658b8c6bf299888711f1b344ebd4a5793ffe6a7a7eec8c66c80303985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000011ffff38891ee56cb1fc062d02f6c9993100f991a556445b5ee1b1b0d56d8e64bc6eea4d7f69a6b6dc55ce7d8b4ba300000000000000000000000000000000000d6cdd95d1ab2a11ab424d7aa596cc7e5de025c57217da0da143887d7dccd6fda0addae7c2fd9e0996bdd0d23128e807000000000000000000000000000000000499b3e69214fdb4db7dbecd619ef9c6b5c8343c808e4953f593cc89adba02b5cbc56a5e7a3046c6023c5cf305e54e85000000000000000000000000000000000d267e21606c16479065e47da8e3c058cb59f55a1316a87117a73dbb067ec26f406eba6a40b30ecb00f506bfd3c32f4da3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010", "Expected": "000000000000000000000000000000000650fe9f3cb3620e0bf1654a7f1dee503b79fe2218739bad608dba9f1e5330f325b4fb7c340f118eb10dd0776fbfe63c000000000000000000000000000000000bcbf1c6a684dea5ad6c1a540b3525cbc64c7c431f37213bc8b08c8d8915a331c07bc899d3a2ea72a9a4bb2c539cf56b0000000000000000000000000000000008fca1c364333f558c7284afa1be486e84bb035b049a2108b0df99395149de83549de153a784e4df2b0134317c85292b0000000000000000000000000000000002784cc1d11667bbd0759bca35a16a1baf49a21765c6c2c3bcdd4fc9697ef20f1274be5caa0f820d37e843bc38c68957", "Name": "matter_g2_multiexp_61", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000051646993c3aba532988d7baa07eaabeb8366853436b8b19c0fe3e14ed45fdc65448d749adf745291ab5ee62d4e824880000000000000000000000000000000002cec01290d8e51ccf751183dcad20bac20b8231804a2b6f87f886aacb61d31b14f2335629e97af0ae0546a17a4cca49000000000000000000000000000000000762afa7b94ed580fd07d5141a8e1299c6ec439bbfc6c1a4d695d9aba4ab5d6dec93dc4de47096d72e5ad87d879eea190000000000000000000000000000000014769208ce8a9682c8e0340f68a0290a7782c2b04e3c13027f0b23966eada2ffb2156f6e20539738535fa0ef097f78d6709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000acc4e3ccc3574285c19d2545839d1da9db6770b078aa399262b7c91a7c41fb4c83fe7dd0aad19f4e3eb2b56273f664f0000000000000000000000000000000017851c99881677b89956fcdf1b8c5ca5dd0997d810f3fb89f7378dbf7964926cfde315f8722531d6d715b4932179eeb40000000000000000000000000000000005e374a4c7118a76e59cdaadebb1c4e635b4dd18665010249f3bc78d559455d27d547856573e264c98ba39f6f3abea69000000000000000000000000000000000a532979cfd5263c774f629027f7624799dd0f9d6a77f675d790a85fccccad6e93c00ff2e5536b8e9a92443af14611e69ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e00000000000000000000000000000000056f109801b7a4a36fcadbee7219c06ac74e4a3f7b81616076c33ba2a71d7ca0776b596fb25d29992fa26d416272a4b4000000000000000000000000000000000c02d7e6ec50b778a7ff36fbe5751ba32beb1c2024b17bd99b46239e6dd5a708d2fc689e8e8924902e0d80287cdbd6e90000000000000000000000000000000016f18df97f48aba4d1b64e71eb894904d02ee7f6ba425e58f38a08542319e2498cb0dada8dbbb81bb398c9c924ae44270000000000000000000000000000000017dce98b335f536909ce01647aaabb918942ba2468d9a07c5516cfd347e1baa02029d39de1b2602932630e4819f2f00f391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000005b23d6f76b8bd4f334e91771383856794d1dc65b365fbc0c94f21fff049761d7379f0d512c42ce13f878e0661712d100000000000000000000000000000000009dcf70c16f524ff540f132b35074cec6ed7dcc1f319432a0dd09b3ded0778ec9ad0f05d67ecf3ebb7947951fc4b25d000000000000000000000000000000001075fb15240d532a9543dc59cb0098cbd03da77c3bf85a0ef8be1560958f8ab57d3777fab5836ba98d67c721a4a8cd460000000000000000000000000000000003511525fcf6fe224eb87b13999d2548b6b8bb8069fd354f298a025b04a33f48be72d8e82a99b9aa34ce5ccdc1f1a59c94f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000019039c69d52a66330d2d8572a1308bd88159f0383c041ee7605d0aa86f1d0fe3e884d0a2ad9c72405149b5fd204ec3db000000000000000000000000000000000942163eca08672af3827dbd876b9c1adeefcd5ae74a2768fb55f1e8b342aefbf76bc6546853a2b33e26fa866e60a4e9000000000000000000000000000000000c60c6bd103ba5bb5323b5107373cd8d706038bf5ec2b367a43bab72411523bea35985b974c756184c346626ab2622d30000000000000000000000000000000016c4a2fc8a9b3c54f65cd150c80a3bf70ae8dbacdcd37128514b4a881239023e427f0b0c8984ce219207c458bb380da970f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000003e3df9a8ce220be05f15904a3321a6805ab68bbd539479be56b2a870c3d61234e9cda8190bdc89f48e7f0dd9374e1d800000000000000000000000000000000040446db3ec43e3e67dce62efd741a4157e8ea2597a143f7d6273b66c7045daf31f72397b4b9d374328520893157c1f1000000000000000000000000000000000c3a7dde5b02df5f7c1e750a9ee5314a580cc6ed53d326a9157b507ebd6c2da314c37a7f1837f7fcff7e8754ab603b7b0000000000000000000000000000000005e617ca4eced853f8f2e9fdefef810c97eb27d5c8bd06c5b4ea50c03761c01e8adddfe27d2d72eed8cb25ea7514a4aabd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c00000000000000000000000000000000104ccaee210aa8196010a6478702a54cb7ba49c80a98ecbf5c0920408ff8b4a7568212bfbf3561b6a7790520bb73bd42000000000000000000000000000000000870ddd51dcc76c8a97ac4b4f23819df48dc8a8798df0450d7a45d273f830c908541dcaab7b066bcd668b289c846ea000000000000000000000000000000000012fdae32b020a346ad5edc3bab360fb5ba55004ef3dfe5f437e841b5dd7284ddb3880051956c8068e49a3fd165143ac50000000000000000000000000000000019081bf768dae314fbecec408d687df5b6ecb32ec24b41f9febd583c05693f80345e6b9d81322ddc72616c1cc39a86811a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001316b5ce5bcc168d76d2c862230ce604d02cd3d242c51c250bc6b6fe5c380c9e83fe7041049f2272481ab38f44648f4700000000000000000000000000000000079acfc2b9629da9c9f3394874e64aa00527de21e726f02db180f86cc0b9a97138c2c567832e287635721ca40469e00c000000000000000000000000000000000e11807dcd4ac69fdcea71e3e6a93dafc27afedf12c2998dbbb2e4f33e37ea736df73af791eae69bff84f3bb212bab47000000000000000000000000000000000e834a34fb63d9df68d683a26d79ecf8ff67066586e5f760d4468ad196c66d4ebf8605ebfbb7bde201f47b35cfde3a5d8ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd000000000000000000000000000000000990f115519d2125d47b925b613edc3303110e9040fa705211e0d772edb2e0f7f88ce521d1738a5f65c9d158e9d360c2000000000000000000000000000000000bb951a16decf9be8381d0c88726b53d90bb32cd8aeff962d48e43863e4eab1839bd80d7434c7eb808bbc0e32e92a4290000000000000000000000000000000013dbd5bdb7caaecc42ffd81f14be0ff3d8fa228ff121ed4f2f3ad5961fbce617d7cbc8133fd49e03caa62f7d1567541b00000000000000000000000000000000195fd9b85e19d0e3e1c93bab0380cad6f6f3bdbdcbf5c6ec32b7de7972421d0065cf0b265f6250c02eada67e95284bce26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000009ca977266277bdeb985750df47353a6b81c5f0c473eb3369d25a01df67610bebf66a6de5727a465131404025e90441a00000000000000000000000000000000054410a13287ecf4aa18f543916fcd65b15cd5d54617433217b0a2b91a79fea764b511b3b270de3e8985e8f6a2fd8c380000000000000000000000000000000009a9802a03a7c9fb63c1eb13972cd42ea2df614a0972b914c4015c2e8630af319d12fc8108b4c88db9508a9a77d9e57d00000000000000000000000000000000094d83483bca296b20b7bee124f538ae9c659a84541f5c9d9fd22e98251d2b48051ac55ebe07bcc9d2e9109f526d60a6c470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb380000000000000000000000000000000003f2d93ddb6d5983fd5521c1d1726addf662af0945aee54788855037f47a013d2fe595231792a05e1259c5e5a8c553a900000000000000000000000000000000004f4f4e7df5dee975fb440b5a217c27d9d1eb83a5ae280a2b147896f6bb864abe04459c17ef56d784d3c4a0b7ad3f3900000000000000000000000000000000069da36057aaa89cda458af4ee27fd9ec969c8f7612cbb153da0e010d67bfdddadb2941cfbdba8c43019a9f1aaf9c296000000000000000000000000000000001545b8325a80176ea148a3d9301debd7046f33a1b419b4ed01916a3d0a072037fd617d96e0bad32b208983ac3be7dda4e53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c9269000000000000000000000000000000001536a81b203df2640bbe7e695b5fde186021d21685f24c25966cf11dde554d49bcefca64f16697509a9ca86e58b75eff0000000000000000000000000000000014348a2bd4907cf081f2f7bc944a98d3fac671abde029995377df190f7f60319b8de1698b99be39c821328e32a449c760000000000000000000000000000000000e18d4da3823addb2a6cef8336c83f99f390e23d7129365d57035d4363aac7e9c4da9f8000f086f7d2206666f990dac000000000000000000000000000000000d6ba54e2af9afa57ff4536a35e9b61c8d8fb3d431b653a0c66a2a4b8f11d9b5c45389f894d64485233d4183895921f3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f62000000000000000000000000000000000d88d5719e07e2332c54ba41f330c7763d2b2b7c4140d19b8b0972fae6ef902415de5f2abcc2342fce24d3ed8ffe156300000000000000000000000000000000163aa2c768eca58194fb76822deffc37cefe04ceb70aba38a51f507be7cd64c0755abdc2e49e7db234cd5d68575c2d7a000000000000000000000000000000000e443d9953468b8cea4eca4f5968e214888e2b95bc20ece39483ac551d4e180c0b0a41c4668c8ddaf761a0ac03fbcad3000000000000000000000000000000000691930530ce86a1354d73cb21ee32d968e6d89b12e5a09a7991c7d27dec302348af7f49c3e0de91e1a1838aa11651e795f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000d7c6f9d4aa794f34596bb9af4d62363462d9804898ebd7c7db7544be1f46b4bde488ec59004adaa0cbe40aef525ce3f000000000000000000000000000000001094629b1428c4c284b7a64d0623e10ca0c4d395bccbfaad89d1a737a3887c10b714541f2681c33e674c3b99a36b7a450000000000000000000000000000000000d6812fad9c5ea365a64ebd3150238349d88b76d041ccaa7e637fdfa6c715d9d6dc3d3315cb95fd6919fe419d028783000000000000000000000000000000000eee5cb772ce02fe2a4883008f17570aebb902ad7c40b4024a5b24ff75b3aaa2b54ace6fb4601b1c62837a20204194dd740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b0000000000000000000000000000000001026d21e075fb8921dd849c98252a565d39ca9f5a62a825e7e3e77ab5be6620e76e45047e51350c48d9a4cf98a1222a9000000000000000000000000000000000f6459a8287bb2da77404a515dd7a35f46a4aa49ef72cd2cdefbc5e5242872df5f7b7aeae6848d59afa1dd142ae7caca0000000000000000000000000000000011e3545151d4e0b034b950cd2f1a3fc2d29e9d53250ade2482b7ea6075dacf7e8e777afa1e8e612b45028205235265970000000000000000000000000000000017a869d75144ece603c04d39cb56a487895cc882fec613f40f6a66601bdbbbb7748ec755553257d654d1558b1104a981f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000011602a23c9b5cc091a700114e5d3557bd4857c4fc44cb8628ef327ddeeb728927347438f123e2011f9cfda9b6dfc42e4000000000000000000000000000000000c4fad264ca95827e9cbb9783e36cb0b683fcc33038d47bc7ab6b65998770325588e5b910e811cf7d61fce13c3378d6700000000000000000000000000000000009b4711aa67e84434cabc289a78fae48ea86641a162d48b79bbcbfd56237705dd2d1e9ba3a18d737eec29eb8e940e58000000000000000000000000000000001160fc9e2a488ad9385140bb62ab48ee613c2284208cf2f92912e1b973ff81a5d3de338d9aa6881cbe437907890258fc8390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9", "Expected": "000000000000000000000000000000000cd0d8c746ecc8d92fcf2232793282d7e0e17e0ec27ee851487eb7788f590db3487296061075f36c24f67cd4c4bbf36f0000000000000000000000000000000010c5e1d05070c27f19c228813051c6a830254542eb71469664c842695b219670dba8ddff858e2d147019847866f01084000000000000000000000000000000001799ca7d8f2637da761622b793a3ed3317d50b902a1cabefdfc776b0d0ef88b707b8a5c36786d5ede3d8a381de4e069d00000000000000000000000000000000129881a3b56e0014bf1dac4775f509f309c33406f2cf22df9a0ccd15c87ea48a868d4437303923127bf580b8d6ed0a8f", "Name": "matter_g2_multiexp_62", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000d087c1b98f8c67c1bbc4389f21d9dab02faf46ee4223c609e7b9eb399132ae168bc12847c580f58edbb9255dca3b000000000000000000000000000000000065ded24bda39d2b830639fa511bce8dc770eb95e349d6874ce63b3355d23c1da3ee9771ad44e57c6c661b7453076fe7000000000000000000000000000000000fa3b2ef40a7c3d41f0c3a5f86afec252c6ce89bd1bf1f2192026e22fa256365360589c788753033658b1ba151797feb00000000000000000000000000000000105040ff4dc2bc435c2a82e1174e2ee0b94043d69074f01e8ed013da8c431f33c94a438a93b06774411780cdb72abbc8414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d00000000000000000000000000000000129c8c1db08ccd0dadd59b04df67a91fb6547d97ce23e59aa57cd3d38458e6baaa67285800809856e7e264d812e584390000000000000000000000000000000004a0be934248b4e142fc51745233b6d0ab2c46f53a8f9d4c84981e5eacff146ee6227de289c713e4ce24a4341572c9d70000000000000000000000000000000005916d14a8592af57a40418b10376e8e20f70929d2ba568c1fb70e343a1dfcf3e63c791cb639bec49c50aebd2f816fdf0000000000000000000000000000000018682c66a461a69b11d7c32f7aca07749e05a23fc46547bac121752aef64e9bb98a274d15a14faa93af8f284790acb9b99eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef9420000000000000000000000000000000001552053742eb89ae3d0b95be919c84e53919c898ada92d3eaf05605a19ac910091fc08a65e9764f3108877c837d478c00000000000000000000000000000000118e5d22f6df0e6bc7447177ce06659f94315478385372046b649fa6d39fefeeb492e6623e0160bc47233f4d3143e326000000000000000000000000000000000dd02c30cfdea5abd3550a9f28b546d82d5b3043f012de622d892062945847748ba820555fb811fb3382791ec43ce1f700000000000000000000000000000000050373898b396d9a641e2f2ed832c7619515fd9070852b891b4ce0b5bb5ea8b5e24248297d53e9db7cb946e76c4433fa49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee0000000000000000000000000000000006b465f4b9d60a3a14e119c54a7c35172bd648c86a7cf331e80ba849fc87b9dcd48410e3c9a07b634e83fc7dd71e5b9f000000000000000000000000000000000283ad9c77f549042f79c47b8a69e72164f0ee77aee50c20519d2b89029c63ea86dde2744cd21eb5d37e896c3abbdf56000000000000000000000000000000001668b08a87787928afe92d941240e503da07b646a34cf82ed09d4c2f4d479aa24358c8475eebd9bcfaa6bae17c430cfd00000000000000000000000000000000150e5b28bd901f7a2a9af44bfd6b78cc84900dc05e334de306f9a45f1e67708adddf4dcede8150a39670054f97a643436e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d00000000000000000000000000000000060d75764a92e30e80e7c1a6df1482585f4de901bbc36dd9d8978a76c12c739f85a9ba16741d0b19ed480fe2dc331e5b000000000000000000000000000000000024fd15c9e5b8872d2e9dae9ae96102bfb0e31d15e92a24316818862dd8ca7a6fef271d499fed5e0db6dfebc4c72e0200000000000000000000000000000000058cda551e1fcd701c6a3880b276a2f7536a26aa366a6425a1c42cf31eec678551f489a27f23ed5dbc76f19b0fbfae43000000000000000000000000000000001152e2cfdb584295563af8120c523a9f4c01cf72da64fcbe0a90a284d693a3089f299bc760166be062cf9f8efb6a951ad3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000116fc24e980b2e7ad6bf17bcd7c4f06e654bbf766ea0238a66d738bf3c2d41c8c63bd52f81553cca5fea91f5f9b74a2c0000000000000000000000000000000001078f19ecf785a5e0d3e764b7d6ea47b2d077b5eb222f4e6a9451f134ff0d77a0b9a3b53caf599705d131e3b17b6ca9000000000000000000000000000000000e44c07f00a1f198583a8ffca43da45d8e54e1f2a85bee7afff6c1c733b5d0b5712961c4b6d344869a8e4de3b34218e000000000000000000000000000000000083c78b3568cdf808b75d9ee2b03b98cd516bb16ca8cc35757f53f12119747bf6b5b0605bdffb2f079cbc69e99ee0bad6de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000087ded6945bd6fae7a0aebb1ea68d3cd34588035531a6cb00fcf1b83e06f7ec21cd3486580165c1364027b43e238e34d00000000000000000000000000000000005a2fe8a9871273bb60cc7ebef44a361300a1033f3f0230a731f5723fca124ec9d305cfde45802482a45942154398cd00000000000000000000000000000000121eb94a41f9e133adf082ef651272c178d780a1c31ba8797f60a208ad36b4c703c9b6c08be845f8844dd14d6406734d000000000000000000000000000000000e5e3da7c91ab4cca1c9286020aab9795e64e667d55a5a700241f9589aa3519639f168d040a0027ac057f334a9f740aba28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f00000000000000000000000000000000041f9117b426938acb40c905bbcba443c043bb55cf9b876edfa2ca051b6354124f0fa54d6a88ea172c3f5c10c6d921b3000000000000000000000000000000001828dc0b9533274db6afc802b2fadaacf57f28126094b6b9038ed5f6bbae0112c873fe5eed15bc49b970461abc2f5c3200000000000000000000000000000000107df6da02f106ae47718959aeba7b4fb4a8f0e2651560e2f2266a62566e13a5af86430b8800543f5eb6b1e96be79c69000000000000000000000000000000001628fd4a598813133de75cd7c96ff3711b6bc826806b96d07e5a89cd549592f0f51c84aa9ee0642cffae5630ca1ebae1a3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c000000000000000000000000000000000e8fff44163cd9c2a4e148eef3cbbee19ab8f648da1a8d438be27d2b0bcab393fb7d49e096d9a7abed3d8f82c11c4e03000000000000000000000000000000001274335d8bde3d14924f8d7ba18fea82bbc85427892f18fb741c8ecc5f2d6d7bee74c68058164c55db3cb8da8597bfe40000000000000000000000000000000010c7fc728c094e47569f0e75446c399d20a1239b511e34d8d6193dd32df607dfaa4377a1825b3892a9f74ff4efa0d9df00000000000000000000000000000000067d904122a6581b5d5a60acfe8156dcb6c10ed083840e506487b5dd9117927663e0ad883fb91b4914778ae082de0a7eb90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d000000000000000000000000000000000a909706e3ce45c86f2c30de5e820c8c9eefef207e530fd504511827f5e6422714d3f4224afa6bbba22ffca533d647390000000000000000000000000000000013ff61472ddc0d70207692648087c283763ede668ae380b0b9d6ae6593498b0adc9d4e4fcc73b5cce250e7563f7577de000000000000000000000000000000000a81db69eca785373c4dcbafd8635b23a9f41265e91152f309fb2945622937e65b5c17656abf8aff042a1fd1e5e50341000000000000000000000000000000000c66269c3ccd9e91766d1a640789bde6de752d08ffe3b2955df8dad3d2a0b6cea9013af235cbfbccee8271a7242e310614f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb51800000000000000000000000000000000181f3f4a16696980bd0eb9bd10ff1084ffe90bcb65f12f505b25f0a26dc1d4e16987d486b2c0b117fd6f2e356b83a5250000000000000000000000000000000010d7be6788da3ec56c87acee68ea8a03e7d467f816060207bb163dfcf8a4e7721651bf2bb23d5bc390d50fb1ee6625a900000000000000000000000000000000196c1ac817493f51d9ca891b55fa65ad5192df83cdb63eb1a634ad54e2d627f7feaa68780418f5354e6cc09cdf2f6c5800000000000000000000000000000000190f36690b8d36f2e295b9625f23afef9d9babe87c1ba0303f60c6d44ec952ba6bf8356469cff9d952f8e26bdb86ca06e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a1000000000000000000000000000000000f77df606f0611856c449c58393f4ee7a6225a5bee667382a48f59dfc747736a895d598f90ab26002dd0ed3a5a8f5a200000000000000000000000000000000012aa50d0ec440884fc6c2f7a0e8db8a5e79160f0c482209ae1a1aca2b9dfedfec6d6ea09252a373ea57905130220a4820000000000000000000000000000000004773f46165cdb19cae49cc42663316df39586c62be5b827535f138e1fca8dcf62ba42ab60ac6dcec85e8496f32b9eda0000000000000000000000000000000010c91923c2c7b3eb2cd9aaf0455c0eb035e38e5352d218b07ea23f50040ea58fd548b373c1bee9113d3d44fcb25f6ba08c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e0389300000000000000000000000000000000044b95fd5f0e049abfdc2adc699646afa5b0f64464779efacce85a5279477697090615933069992bf30036c6ac70dfe50000000000000000000000000000000002778e7dacc5566354c24ea1144613a5ce8a38eb56d53d230ca145ce83d5ed88596afe243df22cba10f423e64a7c103a0000000000000000000000000000000017e87cd2752d8674c373c557ab2b922e02620a070aacf6f5b3d3d07ca35d89ed2666da7246b800717c0e4763dc35f5f6000000000000000000000000000000000a3ed312e5f309eafaed486629d953970cb73f839bf30f506c2f393df4c283f299d6c643ae6c229430d919e8aeae8bd839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc3652000000000000000000000000000000001082a0edac6267151c8ef11fac7614b74cf58b39b72fb71e4d66467ed4fb3264b177c691e569230f2a13a64b4a48c6fc000000000000000000000000000000000073a8d5f96ee580741bee1f82cacb6139d962fec34c44c648c8fcd0322796429bbaef083a11b4c8fa376d4c00cd79c00000000000000000000000000000000008d41e51dc2822e0f14b992511de799fe4db3783a05ddc1026a53faa89af000075ba5aa830ceb7551e51f0fff144c1360000000000000000000000000000000006bc4bf0bdf350af417160d06e8aebf2dde02c9b50be39b0c4dcb3a045f9e04f1f041f6de10328e287df6121247dd4e9c395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000802e7b71127a15a279a629e89f194b51d19c4f329efd8ecf9fe69d340dd06068c8467da6ab39be25c194077d3ce2428000000000000000000000000000000000250172c787afe866b428748be8359d8e0bad161832abc108c850362c5839237483fb38678d77c94696260508907726a000000000000000000000000000000000d46223c1666f314f9a1e32a94f83d8150755d71252e19af91a3b460ab0ade2db2364d8c6217cb422095f0d9a1ed648a0000000000000000000000000000000002fc2849014717d1c07935efe601325e1842ed333897222f6de322dac8b50bf4d9859eed8880a34676af0d0e3277639053ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e10000000000000000000000000000000004151d78d65b0c9eb26822e20d90ace8fac209a1f08f62ce722ae3effd7fcc476f4c0179e71b09fc181db96fb2ea4eec0000000000000000000000000000000013d17ef429483be98411947ca0771ce671fc38e27bd0aa4abcfd5ddf1af9e138404d86f4c2ed74702f80a573638d92f500000000000000000000000000000000178f2a7eb43b9f88acfa892b5868d7f7c5787a399c1c566de39ecedbfe88357fd5256ec57e1ba12e9784382c14331756000000000000000000000000000000000253a391373974beef746c4397654a30a68992fe9163f9518ff0ed9b7be37b858ac60c95259ab894bb6acfd123333b7fbadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa", "Expected": "000000000000000000000000000000000710bfc39e92b0b9d15ee9bdb4959daa3a78f66aeae29eaeb50a0aa0460f3ff703c86eec8903011b4b61a0dea725ab08000000000000000000000000000000000856fe7a074d37786237cc14ff1bc53c735ee8133b231dd3fc63dfa0dbd1979304bcc7b55cd1bb66fd7529e15d15db5800000000000000000000000000000000014757f1fbfd4fa7935ebfe65e150519d6eb4f4831890df4b236dda98804b79862fb6699b587c3e568fd6de1e582409900000000000000000000000000000000000f7b54e4961dab9e94b1c4b897177dfa74be9937694a38207ddc9d6290dae1d5e122cfe4c8c31d853db3783999a7f0", "Name": "matter_g2_multiexp_63", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003bfd2535c6d8ffb44670bd02b5aa6f050f5cfae7266fc3225865bc3f34320820eaeaa952f80da51671f6d97b3df9d4f00000000000000000000000000000000026c1adc0ffc3fef9ccf018ff9a647ef5c69c5133fb4a6566cdcbd3180d9ee784f34d667edb1dd54ae292253b45576b4000000000000000000000000000000000ee90fb541becf96b4728f1859aee5ae74e30ba9193b90569b66b0e1d087eb81f30c21774298cb06e7dbee8f8aafb1930000000000000000000000000000000000a4361867bca952446f64c273735764e141eef43d156d6cbb6e68dbf3bc940e935f7bf3a77b57fca4bbc74bda2f26532c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000004bdef85b0da28e0531734016e5953256c75c3620937736cf65de5f05b8beff294677668047a3b74f0f135b846a95bd6000000000000000000000000000000000b754df2aef855b4a0eb6f6aa03115ee8f38a31fc852381deef2b59bf23e2c885ae166030ccadd5673bacc35482f81e9000000000000000000000000000000000f1d760ac6dfb65b39c999211d4e4c3623c3fb8ea59cdcf926249a07285a8e4da1890327fed20ff07f12359f6d9035980000000000000000000000000000000009f2698239c8b452748126ffd83abec768edffb83dfa3dc7943fd499c8980e2d9aad76dc38b336a4a63eccf5c4150ce0b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c000000000000000000000000000000000cd724c51fd56528dfa688df46f71bbfc9144ff98958b559fca8fd05eda01c38c28630ee19579012b9913a393264cd90000000000000000000000000000000000aa1e55f2b6d9385ec6a9cbafcdbad157f7ebc06b2e30e2380ac54e71db5259cb919e17042d6ba6e045f1358aef276ea0000000000000000000000000000000010181ce9ffe235b6b271d570b3c2d6e1be60c53b4a98ef5e8d7d00b463e5bbc9d8d96dda881e58746090983d6f8edd35000000000000000000000000000000000333deb8b14f499319ad675f482fecd80f9a69ba369425decd441cd2ff5c3c77f11075f61bb1d90d0be850ff657d6b7cccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000001581a5440fe892ee6eece5fc2227fe072dfbc440e0620a1e5fb505ff0b16d9e6033d83c83576b4b6ff87a807dc81b88400000000000000000000000000000000099b070a0d7497f33c1c478ac424d5564fa645d836a3d572d98782f08713d8e425b571433fee928475688db2b3a9a04c0000000000000000000000000000000011e1cbaa09a6361aff9e199e21bc52e98dfacc49ed83e732d4b4f2503b3bfdf85d029dead4412b6f3d7ea447e20d669b0000000000000000000000000000000005503e151d620e9a5a142e4f7940ed88375e7efc1109214141c191e9f38a32a40d3a92d6094584e763e0cf13cbb54bcc99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000007c780f119bbccfd658f3f1b69ce9c56b1f5269bded713b6827d97d32b2a6deadcc02c410138d984d977527f3609cc2c00000000000000000000000000000000095aebacfa33928a916ca7b0ceac699c71620781b35cb2f3b254bdbd1544b728a2ec1fb35416ed7a8a3a630bc07ff8720000000000000000000000000000000012194abf7e411f4961b6f8a1e2ad052c27624ded863d7a9132d9c7ecd3b4074ef0060cd86adb73056323f4227ba5fa9e0000000000000000000000000000000002fde2be9ac1e8265f258a09eec85a70112ef1eadc3a91429c9206555933e2b89aaf7493fb833e33e5d61be28a12a1c2338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d000000000000000000000000000000001586c65405e810e1d5b59304bb4555ca43c04a593671ec64d5ed2d2e626b1f8a89f48a4b21d38fb49909b8c614209a460000000000000000000000000000000014528cdf994e774b8fd54090cb45b68098c1ad9a351bc1f36a9393f3b4364f5beaf58fff6e5f8b21a85b67bc427c0e920000000000000000000000000000000000b48d8713aee51d80c79109fb8b4e0c6e32e25a7ca24dd3e7700f8f3195730375208b241b2c722af3c2295a1704cbb3000000000000000000000000000000001913cf6328429cf2966a48117dc74db0d45be7800f93cfbebf597fb48a8bdcae4fae2df7835f9536481f67261755da2a1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a0000000000000000000000000000000017e45a481449f167fd579accc896ac65aff6f1f7392df47d006b404de3cb7ebf6cb59d0913438f3a51e55a0ae3d446c9000000000000000000000000000000000cf4b7db343bea29af6e244a71880538b41b826bfd1d06a21512d00ce58f5d7500ab1ed77b446b1e3782df736bf3dbb6000000000000000000000000000000000525d08e134779ca7614784818876514e14b65e799b7832f61a63601fc491c8b9cb25430547f961cc1c22100170a2065000000000000000000000000000000000450cc2156c4716d0343f32aca82fd2d0712389b1aa984b31d51edc2aa0545c88ff52e470b15eb6b2c22e30f79864dc85202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000004147c105ee8b4db68482b9d7f6a716ea1474b6c62efc41b9444ed1ef9e92e2b7010a1c1ecc59038ac37b385074a6bce0000000000000000000000000000000018a600a85c5c38be835d2e91a35cce4b59e5f5ac3b735fc007bf5498062beca9befc9c8ead58f9f21f6e08266b149d800000000000000000000000000000000012a476fcb81ab66e3101de2364cb609b17e06eabdff5246bf736eb9d5c87fddd404e8867578262f07a05731b04069164000000000000000000000000000000000c54a888678c28766ad17a18507e4bf5dc57dd394eb6e9b69abaf15e645cf4779bf6ccf4314d2756584647cf27af089ba2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f9722000000000000000000000000000000000e809152c44cebdd8b40f0d22d57c3b31f29700e0cbc3e69f660bf7270e59093d84bf7ac358be7e45e799a75cf9c13df000000000000000000000000000000000c6c61f98bd4e3b7095fc7f1196baa98139087df00fae2a795e76544ca47e453f75929cab07c11cd3595de6ecbbbaff000000000000000000000000000000000171c70446c19fec3c152741925c8db28ab0d140720cb6a6c45e9bc66c012a421d12271889ea43fe1524944ff572fe6850000000000000000000000000000000006e4baa09b4660c69cace151e60320b771e56e7460b01442bfcf26823c17779034ac241b9365dbbfade770d2056eeecd892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf000000000000000000000000000000001917a23350e94963e3a7488ac1dafefe9ab11856d405eff39d655e31ba808f02954b63e822613d3c6e5f358be04be4a4000000000000000000000000000000001620211b06288c16aa02f4404192e9f57a048e900f0ec5db9b478475f13b142f924c6de720031b3fc12cf869b422af470000000000000000000000000000000011e8ded9ad57e46713e7ac0044ee4edec12689cdfb98838a74adf1a35244e3d9a4a34c81323b089c10422abf26b044e70000000000000000000000000000000006f85c7478cec590fe3355a8d6e9557c5be084c161e090c72f1281be4ee56f36aa1e3c9c844eb45d9e295c15c4cd903efdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000d555d9f23de97318dafb257cf444952bdd3e844e9ed5ce193c10b76f5179f0c6851f93af1553b128f34d3a7e75339f3000000000000000000000000000000000132704571a12a58f629dab48f1a3956392b40f801c2b3757c15f7be46ef1d9115d89920c460c0e2bb062b3cc1aaed7400000000000000000000000000000000152829eaef900fd2f19d6fdbb8f7eb3b02df35d218b494d075219b69016256e572eb7f555f6fbdbe17c59a666d190055000000000000000000000000000000000fe5c67c949b7c89a867301528f0ab24b04d31d6f18f575c475ab5a6098f7187eef20a9ed6e810684da9afd8de96ded6660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000128bf3cbb5208d84dff719ced229921a889c9a4d02f5a508187662f03852531fb8be1f4c2aa9ef01de7720c352dbd19d00000000000000000000000000000000158d89a44b8fcf9ca8c96a8e516e130ae8af19ed71c2b8487ae300c3cdb546e248728bc58fd9cfef21107e0dabf44fc20000000000000000000000000000000012b70b42c8af4551267a94a795fe18e8d054291225438adaa33fe2edafa87742fc3709abcc7bada5d26e3a14649cb47f0000000000000000000000000000000015a853160b7666ea7d64aacd931314497ac7068a4b8bfe3a7deed85df2bb8dba277716a9d1ee50c56b2970016ada509d1ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af840900000000000000000000000000000000087c7bf08e085e19f0cb301d2e36478357e835620b1cde6e132c237ff6fc63e6fc16a8753550d50fb93a0a1741302cf9000000000000000000000000000000000615299ccefe4da879e5f4b01d6b6ef8358bb59ed8a2b365ec72003c16486d3266243db81f48855d81b6a25440bb861a0000000000000000000000000000000001498fd20640f39dbc03a474f4514e5e283256ac19468077af1c9ddaa40759dcf93afe256de1e49be6469fa106394193000000000000000000000000000000000cba50fc4919a29be2f4e74c261487dbf855db1856e8d5d008cc3f4ee5eb3babfdfaff878adae49b96db99d424bc4dab2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000001369dd82ed013474581ca1ab2d2133341d7c1d52065060d72b8317e899e79e9077bcefe6c76c3c7f67e54f76dd3c246c000000000000000000000000000000000405aa84d3ceb02bf8eae989a9cd65afa15451443af6f3cf5e70f5cd7bb8d413c57ac3893a7e8b888ae93a92dcfa2b20000000000000000000000000000000000378d003988f3c6c16d3b12ef47a4a49e2d3d2c7c67e384bcd510939581770aed92e06291ed3b7c742769f0d1ef740c100000000000000000000000000000000048bfa6550711a17d52f48377821baae6f3de6ad99ccfeb8302466047dfddee8005240cdc65b3ab11ed85b11f128624957f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d0000000000000000000000000000000013370ab697da0ff0a0efa8ebc7589b465374c983c13daee7b5451e8b299933eb5a4d255ffe4aa46782ae0916fd3990230000000000000000000000000000000002ee77be6e0b6fd260ad660a96100bf3259329faf2ff9796102928e70cd52c2bda8d0d1da1d484d7b023d3d59725d12b0000000000000000000000000000000014482fee88e02e61b847c08e61d7ae6fca2d993bbb69bf1653138150d5d7fed09cd5cd4097cb4b6368ea8023383477cf0000000000000000000000000000000009d0380d0d6fa39c9e242b9a67336d86445551658bc29fbd594239a76d7741ba388450caa244fb186afc36d35c8740e93017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f000000000000000000000000000000001537d4a47247af8f60f77d309666056c412ce089f3f011457e894f74fa4ad5168baafd36ed3294f5f61cc9cd8f87554500000000000000000000000000000000119e43382a846c8945e58dc7723a0f24b24d9cd487d436a156156a6da97795cf3f4ce382d21435695949b5137a2bf1d3000000000000000000000000000000000be5fd015998bd6043f124048c82e4d848e1b8c87442d0021390cba41c294de17648a47dacc06268606ba73cc95ae6e70000000000000000000000000000000000e05a3dbbf3da8320c40d51ac44c6380d56ecb460b0e7094819aa6af4d7c70d1541d4bc1fc5afd453b165f3d48d09a708bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd", "Expected": "000000000000000000000000000000000b00b5c14685ddd17ee99c74598e6bfae5bb1c103f8ebfaec3a620ba57312f3093f9ad5eac820d81096dfece90e72ef8000000000000000000000000000000000dd81552160d449cd787ac27c76685ea0dc993a9fcf8ab182f1ff5d8a484a47c14c1c1a785285b44336c7f6fc0732a0c0000000000000000000000000000000003008b6d97a12868554d294faa26e2ebe2920add650f841adfbf0ee89af72fc4da5dc23b45b7ff191a58c17971b50ae50000000000000000000000000000000013f438d927f35b04bee8fc55693d5c97229c8548ff9de39fae6e26c26f89623d3b0c810b9be8dcf0445910e8eac5c58b", "Name": "matter_g2_multiexp_64", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e1f825b71cd9edcb231c178e160e37bea70108b369afb248edc7c6a59114c22e843fb5541e0f26c77a2b589ea88fb3d000000000000000000000000000000000d65d777e91920b17400955a4afecf82f67cd13f3e7c5d9c2076c4a4d8f7f26383d22d9977dfd0987f219a625c8a621200000000000000000000000000000000045716092850318c343f0dc5337df1a72f8c74dd729831d12103b46127c9180fb50cece34986a94fee6119e72d16a55e00000000000000000000000000000000083fac698ce800786719d1f6063c87d9f728da03cea2545b4ad8831f6c24bfff73e80f2c2fff1532f6d1fea60e7d438ccc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d00000000000000000000000000000000171d60b76698d4d3f14b4eacbdce9fa66b8c3cc7ecfb989439330fbf0d051d95f3007c389113346e614f5ec8cd170a2100000000000000000000000000000000151a96beb250bdeca3cdad1b07322040bf1cf2105dfa854bb24fa76c8abc25ef4fb924ff995da641244f9daccff2ff970000000000000000000000000000000007e5818778a8331cdcd1432b46abf1efcdf7e4aa8907fd42d5e7d14b57dbfc48125246b57587755ee1571be8b52d2c57000000000000000000000000000000000693eb562e22fa8ca4a655b76e43b50fe487ca1d65cc3867eaf793e50496f0b4658bd92199104c2ab92e4ac53c44db6f57b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000001059d23ec6e472937d80829256db506d2d2deb37d4b750a980568cd5b0db085358a4d610d59009b64db1a9225f9f6f5300000000000000000000000000000000053d9ffc47672f1058856aa08e51aebd469111dcd129ed542454d6401e7893323f8a9c63641f499cd8617c7389518f8e0000000000000000000000000000000002b9b30a5e37b18af4bb02ae8cdd56f6a87820716ea1522a174a0d99c3716295ad0ff2daf663697cb56bc6053c9dba610000000000000000000000000000000019d3230c0bdc228fa0cfd5e0d8bb88be959e70e59d931d9f9e3683d5e65d8ba0d121fcea329b23c5905b80dac34de33b03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000015a619addc75f425596f9a51c6cf2259087cf32afe9b1f07e346a2f4e1f8caa001dc10098d1287b89837f426d073982d000000000000000000000000000000001660598fcd3ab6a55423138ee72a4ca7b57277f6ce140f9f992dd9934bcda78513516df0d309a0e8ea151b2742dceddd0000000000000000000000000000000004cce7d84e0763fbbb54376833ddd7408afe3f741bc2b7e42fef3789a005134cc5540981a15a9f256e0e541ac58ff3b10000000000000000000000000000000019c20a0064f89d37548e06d63d8ff4fbf3584d5bcc2fc2757339b7c89db6d5da76d43b31da7364259187ed602e79bf4f976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f000000000000000000000000000000000d7ec5a27ac44daeebab7658011624c441e45924cce97d5bda354f1daf9362f5bce2ddf57151fa07f78740a7db170e8300000000000000000000000000000000121ee325f4252ae5cdd3e3495f36492d68d9dbe13249039d1185760e6e48a789744b2a9946a3d6478a64b378f76b0de300000000000000000000000000000000014c6c5b98c1e214f78b82f1b3be4c32c5013934b1231fec942b5591d3f0440bf63b1505cfbb7a8fa78a85ba58fd4aa90000000000000000000000000000000016aaea3bd0ae91b9d18ff89a40ae27b68d74f3a227383138ed737d59c19ac578da03df83f04c8d962cb9d6f84a15302f3aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c8300000000000000000000000000000000188e5aed425a768f89f5ce09b2cc909b28c6a0165787c8e3750fca8e8162128ecf62ef0ff853d206d23bc076335008e70000000000000000000000000000000001cfd330da0d1b5b92b6533cf5a8b6b70bd93daec4373f28d669f5e970a947fd813ab1d1272b61afbd2748922b87c8c300000000000000000000000000000000002aec750fd085c99c3b9c3af62b6deddd85e49eba0293e6e8160b26a3945af546a760b8f8f85120d6a51d22313cd33800000000000000000000000000000000162a109abce2edef753ca6351aaa9cecdeac20919681c672dbb183b5b26649e885ff081b9d3687f802dbe20fda43462af9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000f7ad6a1dd9f8cf52bef02ae1e82b0d20dcacfaa5c169a485bf8becec8b51373fae851ca29e64385f0b7024eb0bcf9270000000000000000000000000000000010412a7a710f842fe836414e2729d0ff2e145709d8f7b5e3964af3e0ae267ac53dac3db1e6d2b7f7671ec34b18c844a10000000000000000000000000000000002d3b96fab0e3b8fe44e316fcc5e35f06dab83f2c531a777e162f7521cdd5767ad0b6f877f876f73d2ff663d9b71f462000000000000000000000000000000000c09a98bf623e82a4d2d4b63fb867fab5d3bb1f85a0669c4c11cebaeb357c0717a0f246a9ce4064b7351dcf1e77cdbd393be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000114270d35ebff55c0341776086d893513595aca3b200ab98c8b586029b19a360a04f2e77e90d382174296443ab8531d10000000000000000000000000000000008b88849c3cda9a23d37ec9f4700904edb24be95fbbe6d9e20ced0d52208b597d44bb9269830a1ac5cda35d0c0a03c9e000000000000000000000000000000001144466b13427c10ad7679567067dc47c671107064fbb9bad287924c9bdee653c395dc2654caa5b3013ade932fddd5e50000000000000000000000000000000008e14e3cff3bb57f0d87680a0c09d745c7272bd3c216ff9fde7c03df2caffc27e0bfd9f99912855c156a787200752c125d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd30000000000000000000000000000000015cda76d42de9fa86f900a5180ff016155f31b9276c617ef664202848d2efd2876d412402516c0c3d26d49f71d894acf000000000000000000000000000000001307fa2b963fc19583b7e4ef2e9dddbe93e2505e8f4f00ec52db26ab411002136c1f646b1cda71e19480c767906a6d03000000000000000000000000000000000ba87b08173c841a2bfbe424584d4685c39bdd0f83f278f9fbafa8111102aa3acfad5aabbe032c7123631fb8b454255b0000000000000000000000000000000016c525c1dc247fdf34344168b7cc245579585fdbdd6fd783cbe60b727cd11ee97b87a86647f78dda207c98e65c2ee7e6854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b24000000000000000000000000000000000403887fd4429f44f8da7f17ca072f867e88ac046922ebe3e1e6c4f9d8e174399e7648aca924a557dbf7b29c540db33f000000000000000000000000000000000522324700fb6b2c43eb5b39e0da94cb60e234369543f530ea47f4aa510ec0fd79cdf4dd3ae046e21d78b9c0e35107900000000000000000000000000000000015e946b90984257ffe3814dcc3ef065fed1504f0790f3564c8bfad4e97cffdb61c0d73bb0b1dbe78c4266c773abd56b500000000000000000000000000000000078f604630074ebedbd836c463f3879cd5d4a2c947da0e47740ec369112f4fedd787ae59bea69aab61b91f05d92061036f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000f722bfebd55f75f3bbd0a55492499c3a3f637ead0e54270042fcc88853df5bc5f11a3677efa26d31c28368e00c8713700000000000000000000000000000000182618bc8a4b3f6556d79848f90efd6883df90806a8358cb6852bde465a27a70644ac5d5040d4f64ec355763f1a384990000000000000000000000000000000015f717739a1cbb2eab30e7b1bd9b25f57ad56f36016b59128ea1f2089f2d1dd0128b455b1b0e9e3b320f68a38a1bdfac000000000000000000000000000000000b855788d6b6a7748aa923dea3163fe525a7b43f4619c1eff3f9219ec3d98ceaf34b97bfd19aa6f91f7fcff728728978191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e200000000000000000000000000000000120048ace47bc1ab3fdc07713b91a9223fe0fffdcbeaabc8a61351d756f936e18177f672c5a4db7b9dc29bad16bb7c4c00000000000000000000000000000000101275492a6e843306f2927b6ab540d7a5ee925bdab40103b4ddd885e444e6a6ec2d6e99c061284a1967797d8a2e9e700000000000000000000000000000000002c12f17a5dd2c56aed0d308367f37510f83c94a4482e5f632161dd0517dc2d4f46a90bbc13034c63dbd04fe4c616e320000000000000000000000000000000000e4b9089155ce2178f26b058f4bfef57b73aafb83b0b78138a01890a167709f79100a1e4d797c5849473eb3486cffa4b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000018180eee7e72b6a4bc2e60555236da335fe05fcbe2b3cca4937e73a550aeae6274122ba84ace78eff84d323b4196f58400000000000000000000000000000000147659347e0fac7a16c92950ea5fd115416072f339d7de3cc0f00ef369f5122ff050d8515effacc825c807f7e19650e10000000000000000000000000000000017bdbcae7f63052af9a7d8bd71dc98b6eca7ecf5eee7632959fe56ed51278099690c534ec33be4ace4612b0f516794aa000000000000000000000000000000000d6fa233be4d6d783bf973cca3740cbaf0f719827d7f9310f38d1dd9d1c1f125cdfca6d12fbf6a8e8104f79bf30b00647d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000082f3beaafa575e86be53b4fe7b93835b00759f921933402282e5bb0e643a812e0e4b676ad51ff2c6f5332d777641cc3000000000000000000000000000000001760b87bc4d2c13122fd7acc6d629c9f9db9bc9a2c49634aaf33e258ceb3106bc2755b227c6660a1df1d92c60067cf5a0000000000000000000000000000000016a819d7109c9a12199eb98537a730908a693767cdb35a69b4c7329761939afea766f0b91ae405e273227330761a53dc0000000000000000000000000000000009d14d7138440349e83f5ded46d18b886ef3cd63e0e5bfa0a8b50985142b21a4733813ec347e40cabe28e6ec1e068c24a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000001676d7f489219b56c198f8494e156fc0672ae28dab20021b7a6018436c7c0f107efd2493ddc2a1cfb3ad490ef146348300000000000000000000000000000000001106e89fc098ce7bd8bead5d7f6432bc54501370ae6544f34cfd996b3b610f9cfc7ad366751ae1211b848aad7d93d30000000000000000000000000000000011f8f0bd037365b5427e76d57b018c1c644034b28d06c8f68c59bff45eb4a2c4d761d066d96c13f7e73dfd80c81704a0000000000000000000000000000000000fc826b5957613f35bfa36d3ce088dfbbd06c8f2e88056a22a9f35db561e06fa0378ccff29ba8b81cc12c7a504f8c704c0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc0000000000000000000000000000000014f0f64acb0d9638a68278099abf5b5da3aa087792bef15192cfe3689b69b7ec1aefbfa14e659358b5410d98d2eedac50000000000000000000000000000000015ca79c92e98cf8314a2f6319520e1eb7d4656ca6e51278710cefd9c768a25691fc58e983aaf858d3c8d0ed73e2beec300000000000000000000000000000000007a5192f1dc906693568291f163e9632c53e1f418a87cd25656064adffbf31863680468f3ea451fbd22ac990dc870b3000000000000000000000000000000000131d2e3f6956da8941e8340259b8a15aee9fc6f23573f9a348ee9a51bbca1308dc54e7b4675357e3a9c5971be3a5c16af833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94", "Expected": "00000000000000000000000000000000163da4bf7e159e05eec90318a8ddad4a59fb02d7ae2fe18795d38c3ccaf797188fa16577e6a421ccfb12ba1ed573c4e6000000000000000000000000000000001256654eef3352b09e0027277aec042519d99eb2567fce2cfa50a0c251c12d3593604739128171bfc13b3bfd1ce8f9e8000000000000000000000000000000000b8a46123bc863bed525f97166bcb77504eeeb66d2db207eb8342a3d18f7f5a99910fae3e6423c6e84e437a2c4b24363000000000000000000000000000000000b73cf08023c8572f48c132add67dda7a15def638a01b198361b9d21a4634ba76ceed9819b37c12e24f148d255483856", "Name": "matter_g2_multiexp_65", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003113ba8b216d933fe0c81f23f75942c0065d21d8f009d73b1f698281408874e33dd2750fd4298367b81827cf6fdad34000000000000000000000000000000000b8a921e840fc665a786d826f83ca5a9c8f00e7c802bce5473a7d1ebf63e8bb6cf5c4b426153508d874064d1f1dade09000000000000000000000000000000001492ab584088d23d3b0d1283904f9a8f29f9efe47950c6e9ffb9db2123f3f9820b906d672fc7f97f0bd38b8fc0ef44520000000000000000000000000000000010d321c2538f92aad4631af44ae39e63dc06becd2460f0cee0e526328d167fd6cfbcf4edfaafe32d13b5fe66c009533bb16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000ae75d01481a51294003041afc4802326ab878a3a75eafcda43cf873cc65e300d28aa986fb82a2d1d649e5be00f956820000000000000000000000000000000017640eeef8982250f88a4d187dcabfcc9adc3ee9194dbc3c04c741690fce5bc7cb07cd0b7c3497191d9ed8558fd0d24c0000000000000000000000000000000007527fd8dacb81b8d1abc746688db6a47211fa71556155d38361921c4bdb2a9e9921a3a540bcf55c6dd751b84c04a1040000000000000000000000000000000008de9109ba354d7426a5313d66cd747a54df347f0f86a3c0f99e9e4b68fc79641fcf98ab39fa23ef6f1a781c48f53f76cf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000017331b8367f07756e789f7edce4d22f6886656fed78ddacef6987a2751dd3d5d49011a050e7b2a3e11fc8d90266c9d710000000000000000000000000000000016959c303e11f23392f95c1402d1d1ad7f38343c711e96f18d03f832f76e3e81de789a6eaff797ae51079b13571334d40000000000000000000000000000000004266fd13db1ca80196a91263c79d1583b717fb61fd9ce5113e4cd94c59e605152b244e10e364b468c5a561c6fa9715800000000000000000000000000000000026f67cb263be83f3163856f091e9346651c29d4634e242da53b22eb6e66018d235b0f30f8833310dff9f3020e5bd3811cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f42129000000000000000000000000000000000114d8babd11c81ca2b8a7e193afbe0a8fce426b83996bae6f77201870e51c9355c319dd86b985272f73e0804c0f53700000000000000000000000000000000016f5ea7610891d0e72975816c08e6e25a75c7c42500655f26efdfb384241bbc825358a21caff347d00c8b2391501d15400000000000000000000000000000000199c8c74a79ee90c3606906bbb8cc163c214259e4d0127cee3283bcd9c1ebe4090ca7d7b180201910d3f6f51566d3bdc00000000000000000000000000000000032c785165ad4c1a2846e15318bd7cf5b42ce8b675cb18fcc4232e28701f225f1ea384b276e7a38b2c9e2e8b112f1911d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf0000000000000000000000000000000000842c65006caed9b53add048a2eea89e1b4584e1deb4365e3dcf8b9ecb02f337bccbe5d6929ef8c20461847f171fd4d600000000000000000000000000000000100dc23e6c1c6f6756419a9bad3133bba052f408a424c5239b8528ad4429a2bce64b72f1463625f7599ce43865581e9600000000000000000000000000000000125b4d71333274a16e52829ad5eaaecdda5c206063473dedec5a8ff4424def70e6f650926948dd2158b403f985a3421b0000000000000000000000000000000006a031e3c002702837e4ad28250b85cd94d42cf7b0d765b980fab95edded7636d13bdef1be63e66682c4e297d0cb2b0302e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000003f7091a25da7d5afe6fa6b254604a1abe7a0c6ea11cc1a4167f5f648aa973d888383bc7e987b620d23e688868d318360000000000000000000000000000000016637f888efc3e057227cbefecb3037aebf8e330c3a794e51d691e3bc064237b98351beb746868aef977a83d1fe163ac00000000000000000000000000000000126d2884487984f851d1bd7d61bdb803321f263918e88e0677831563bacc9f5207358d1e9c76a5a25a66f0294f459e3900000000000000000000000000000000125c61b387a4462fa3bb2f06a4cfbd7df082d20cb23ee974aece2ec9a3b0c084d13a7ea83725a05d9f31b8033d2888ef9b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e0000000000000000000000000000000006cf2bc7c691c4f8a64d0aa1ca3760d715b3188a2dd299ab09c723315acba8b0b4bbee819ba06cc564f0c875a63a415b000000000000000000000000000000000bded3d695e471f30f9d723f55826eda112eb0e3fbfb9a377cfa07d6233ed84108b92a79bb491a2971e9afdf83db8e9a0000000000000000000000000000000009b0e9928cb267508d4f9444c6ac3dc6f64f49a70c82c0bcaf4022e97854e5d9ec2612a2cd4d67642dc0451583bcb24d00000000000000000000000000000000009347dcfebe93a2f7674ad02ac48794e7cbffb04dd85b0c8c192fc85cfb9cef40fd11def6f63ae9a923960424eac6a02f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa040000000000000000000000000000000013e8215c7bbdca445555c9fa0ae44e1905703334bade3294fc047ec262b9e4903880d52851967339eeadd666200b25ae0000000000000000000000000000000003b0bf4498103ac03601a8594b154b59a2a93d663f98ff8dbd2c85a1902e572a9456c629a12651aa87a1262102e1c770000000000000000000000000000000000e8bfd7d3fa0f773e6bcfd0d43a5c436862d1cb6a4ed715093c6782cd94699090c4bde597f65768e963fd0f8644e09b300000000000000000000000000000000064dab4d0d0c6b94c58b067337f2fac7d0d922cc822562b6bc941a794d96aac5ddb83d1d5844440d21d0a72a69303b8b2d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000098dd9a20f84fc26e78993a9de4d519aa2f8d343fbee501af945e5943e88425d29beb7ae54481b04175a07bf69b260a30000000000000000000000000000000007ef43e7a56e4e7d532420e152ce566d9055eadb4ef13d5698c49da905a4977fa8a7d3f51c8f5275582e1647984be61e0000000000000000000000000000000003755ee4432ea90f2197c7cc2e191dbbf7950c52a2c1b723f26d2aaf7a38c1b97efa29a312fed599f1199cf186400adc00000000000000000000000000000000150edc463f0a55fc70c2ffdd1f73a3abbdae459eb16adf79e96d18849ca638e6f41c6805b73755968be5cb110d81faa4efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d8000000000000000000000000000000000f7149602cbb3e5f2c5f8edfe59fc0fb8e1f03f89ea192bfe3990d87ccd28d4a80d7cd3003a8cfd669e1b6ff7e3cc5890000000000000000000000000000000006ffbc965bd06de07d8c0a9db8db5ab82d5f11afa1ad8eb92ed4453489f5899cc8c46ff02743956bed81229f64cf6efc00000000000000000000000000000000164cd3271ace4809eadeb1c0f769094272f3b66968690339bdb5da92e920cdc80c9d577ae4fa5b6426a5a6f46fba80bd00000000000000000000000000000000098f0a14a511ff424847d2b4d1b80a049b1f05ecd40af96b7a81def54486e4969011c122ca7dca3444029daeae2ecfc79848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000018409c0d0f37f4932cca87e24eb4d55e75dc98f938420ce036d43689fbdbbd839dc608b21d12a8af1d0a780aeac6617400000000000000000000000000000000109f2294669422a4946f926b1f106c2887893a042e3bf900559429c7fa484da4909216c8dcf826871534981021256741000000000000000000000000000000000a1ded19846e603b958d0bdcc9b554beca784b017d2a35ba117890fd0dbf729428bcd9823c7a378706220377c82a215c0000000000000000000000000000000000eafc89e30e4fc0544497e27674ec5b37ec0849fb382e608e09d0c1c94cb78bcb96ef4ea48e374aad1038881706fbcfcbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000e02cb3e099792ae7508321ce7afa323fd499de90c4006621ef5ce1054d0c934ae058a97ff8aeae0c88709c4d8ed0adf000000000000000000000000000000000e19318f5890320f17d5243adb4683a97e3e9763102c4fc93e3c3e3d24f4f61e0500be916c249dab00094b4ab048fe99000000000000000000000000000000000989faafcf6156472368b282313e076613cfe7ff135eb131b49e58932cbfafecf6585009d1f17ff8941d7f871be23e9e000000000000000000000000000000001167419d097ae8b96993b2e67da79b658adde1e12e43c71f27835845c7077f385612158d3e59fe2cb32b9418463e672679eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d63000000000000000000000000000000001579b7d03d3d2c8a280e8ca113bcc98afa6a2705a5d228d92807a85cd5a1ee97510f632293a478c3fe0bd383f4b69cdd00000000000000000000000000000000107cc2e6bd02251bfd565b4b848adaa84babe9d4f083e827ceae6bacd9c9c221f0dbbef53278175bf27ebfe5949fcf8c00000000000000000000000000000000018d187c566690e4edd8d8abe5e0a448e352f622c96680378051228b6d081a4914aa51383326aedf45e351612ad6c5d000000000000000000000000000000000197427117a52f82aa6e931ecb0c5ffeec7f73ee8f44c5816935d26c06cc8285200ff9240d98cc244708e00669460f98b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000019bd947df5a437a7f1ca2340bec628f2783cc1760dbc4a97ae10093aedd9f64e25ba79d9f4ce678f4fec91a3b1eef2d7000000000000000000000000000000000770e0c39988c9d8eca076464a3e10e274b06b1d2f6230e6dbd8dd59dd9c062f8958c6870c44ff196341bb9f65b8db38000000000000000000000000000000000a1833ef19e2b8e31577e5cd26e0a7fa46a5d25355d8b3dc0605f53714a60423556f3bcf17649745695f68f26570de0b000000000000000000000000000000000f449aed4120f3bef05506f2463f4546c7ea67b9e9110d3942dc256400d063dcc571305b1d4cd2bc3f18cf25319286e8eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000c1f9688ea64165f894e85b21761a9b2bfce891070103119ae71ff7acd164a57b0e054319631180c22f19eab8607f5b40000000000000000000000000000000005ba18dafcd3552af464acd469b133896e90c9ccd7e3bfc6e05db883f3c6aa1cc4610ec47f6354f6a7cff4385c56d2b3000000000000000000000000000000000fefbd9d78f48683b378d2d6311bf7ffaccaf7aa73a0bb4ce019a0c1d2e1673e52c724bf3a782729ec23d258043efff5000000000000000000000000000000000ea47ebbe3e858c5fcbf5b0cc9017d6ea23bda36e235d2aecbec827fdd2e4b042d1108d5f645b6dcdd786304e6bbf81b52b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000a313e1bf72d9a176bbad609631192c779e94c293463507edcd1c38bee8f33cfe6104d7169457ad5ffd9f045fce1cadf000000000000000000000000000000000af8db18938c51742b351fffddd74bf1137092ecb50a7e749391bacc9c1a19c7b9cf235b52ed577e7855d4ec1fadd940000000000000000000000000000000000febaa128de79274ef11d3e6378809d5b319796c653604723693c335eda175014b645604271429e3d449e756c85bcf6f0000000000000000000000000000000006adb29cc4ba053fea56d07225d2f7735651c0046f5cbe4a350dcc20431ed9457651d46a5d23d946959cadfc5500b7eae83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384", "Expected": "0000000000000000000000000000000019a5b588aff8853adcfa959afc5135807d00196a75acb3536ad4fc9a9df3803d919a2de7cbe9ff762913225577ebdbf6000000000000000000000000000000000ac8bde939ba2f164795804d96dfa8d3a1c4d9e4eafb000cfccd956c24f4d594b30bbf961917f625c86270cbe164cc5b0000000000000000000000000000000002de09fdf52aec0b91bbe99fe2eb9043b19975c6fd503815264ce030dd5e5444f0f4275ac9a07a49de775335d52ea3c40000000000000000000000000000000012457bb55876c482e5b907c765b476dfe6ebfe8e588cb7f630e58f78942bfca57e6c0d5d7b0ce80e48960e297863d212", "Name": "matter_g2_multiexp_66", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008f3f1f04fb80a23d348e3e25dac1d732265fd4a71ab8dad3718d268e49c79578e8e1ad1720e70357439e57df0791d64000000000000000000000000000000000fa4c15c76e395fa706a55d1909ede2163274a68b3e7afb8d2e0bb176f60c06f5a921c9ace35bd311bd79ae86340ba5000000000000000000000000000000000173633369e00c8c5528bd5ccf95c6af8b012e5a31941c134ad4541099c7c33c5ffd29a5a31e18be720f7ae85132cd6cf000000000000000000000000000000000800f5eaf7c8b1dd2787305ecc637a0bba8eac807a7b449410e48aed3dae2b4645b8459fcdd477fd92fa5ac6291b800ea4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b000000000000000000000000000000000ea8057b2d609ac2130b21e0b4a41f0aca20ee7751f55d816ea42cfa4612b67c3c556b01b0bb1c5912a74c50a420407f0000000000000000000000000000000007fbccf8ce8d1a92756fe80b15c7d9342af4e166d3c1c7e35ea2fac34851cfd983633270c877224749365720fbcea54a0000000000000000000000000000000000885e173b73118721d28fd26f3a9c562bfbb878ce71091d7ae4b37c1f2625777d67955a2b7458af71077db7557171f2000000000000000000000000000000001754edbfc3f2af94c92e6754d6bb096bbc4b39bb1128dc6bba8b4d4d9fac6649598be90b06b9d5db44c4e77c0cd1537cbd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000008a1a751b5f9a08e2bf5b2a58f62f0af6b8773f88e50f658ed220c0134e83c7031a288eb50a8a35016d2362b431d809d000000000000000000000000000000000d7f04d4a6c36cb3d105dc3915cd5d57f56692132681b3abca4b00e078c700931848e34ea1b7ec663f3886ff766fef41000000000000000000000000000000000a06c3ac81d6d0466e1ef21115150d04c8bd6dc3e4078e46eab213203c3226bb0c6500ae4fda591d6b8a791de598edb90000000000000000000000000000000014d849ddba2fa79b6a7107efeb46e9b6231d65384c69ed183acfb922d55b790d4fc7546afadc190b76f7da00103ef565efbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000014dfcb5fdb38cf09c1ecb284dd4f2de0c3d70f90d7c167a442d84e9a29bf43be62cd319b2dafdb6ead2c6596443a00090000000000000000000000000000000006220fc05c53f48e7e4104422b0660ab67fd88a695a201366de570f0ac0ad30421d5e37a1575e6b5ba35f45b441b297200000000000000000000000000000000077cb8ec1cb83c4974f6452ce0de630afc82e283eeb55d3b7e9969bb44bcf0404deae617393f82ac228b836c3cb6f95a000000000000000000000000000000000e2bdf539eb45a125112836008effd104e881aca397457004fbda4a40d152817801bd259434481f0509ab1838cdd1fd060d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000a09843630131cc6feeeee8aa8214408235655e4733badd6fe20c5cf1e45f6a61a5216e0cde937799437962706d3bfe2000000000000000000000000000000000ff518501614ed4a199ca9e9aad4e8efb8e9cffa9b4fa683093a49cef4669198a7893db998d5777f2cc8f4bb130c84360000000000000000000000000000000010ea66fb5224f4508ec100cdb611be133c4895a8de1b4c475b097494ff0f1ecdc1bf8fe467c630233cac2ddc07935fcf0000000000000000000000000000000009d22c0a45c82b0a19beb94eda0b93cbbe1f2e5f2d61279e1e1c93ba073cb766f5637195e6964a4814e588e44bb03f03386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee80000000000000000000000000000000015ca795fc7f0d169ba8abdafb1dee80b67e7dc616e824959f84c61284d6b2e0e8b9f99b414f5bd96d0e59b66ee706fd800000000000000000000000000000000042f473d1fa228961aad526efd003461935954abaae347dd6c9bc7fcd68b5f5138e57ab2a160cb19d1983089b58b51ab00000000000000000000000000000000188eb160cb968b4b048ce14bb72be27c228df1a6c014fa7dbec09a30aed8c71e8da59d3d5f8073b6a7d70d94c0e59dda000000000000000000000000000000000d467e6b05f033f3923667a82d5b489a5c90c99c5f68078aec700fc67a83d9bb4c09f3f00b9fc2cfd62bb098f885fe295ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d0000000000000000000000000000000003bec45d94f3073b2ca54d6332d36fdb8f5c801d9f70ccf6e3666b66ee06c0fdfd741f74cde1997aa205fb0318c9c4760000000000000000000000000000000014009b777b660264eedb35ec2e13ea586aa9438c47b3fbfd095ea3d8688a89c85bb4052bbd3edd450c19acea6372d0070000000000000000000000000000000017f26d3cfcb40fd6b4f3f1acb6d47a9b54c232aee484c7a8992a3d1accea794dc384fccefb0418d43e1fa7b399bdacaa00000000000000000000000000000000153c6cafbff3c53114c96d8caeee2880dc063d7db5edf5f14157117387f368c76b739553542bf6a9bc4ace3694de885a92837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c0000000000000000000000000000000005d2481438c03493efc9f1e8e9ae6ab05b7430f7fb82e108aada0e886b14d769969d54b17b31e5bbb63d40836748f541000000000000000000000000000000000971deac599b2161a4baf1178feb81fd4798ad5cb063b1a0cbee7cc33b8fcec6c3f43d1d46d9ed45555187db636af99e000000000000000000000000000000000222acaf8df647744859e04104a5fcd546949feff6244e192a9031fc838f368aa465a3799779c637ef0087183f30731d000000000000000000000000000000000b8e8f1889816f89401b070db687aae47f7264c9be192a8d6e485ee71a5a688070d57ad8928d09d9a4925f1050e2c69e127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000b8a5b0dd422469a8d6d7603e9f3179f443ef3fab0016afd94e93e2ea9e84b332da4b59f23a5257b99460efdf7d2aca7000000000000000000000000000000000c28e7068769c3a79bb8d92c3b89eca5d6eb42e3e18c2a7154f43a671f8670f878c4b110990c2e2b163ba4d1155319fe0000000000000000000000000000000001804302246fd07d86f4bb23f610af38deba8e324cdedbe5e61cf0941281cda8fb5dc211fbc0ce6fddf30aefa9563a0500000000000000000000000000000000015813fe0d6bbcfdc8e7e40b6141db21e1b490d846ffe82eeb3edcd9a024315193259612155b0179a4971e205738af74ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac614890000000000000000000000000000000011ac1ea4dad0f650fe0844ac3ab9434ebac6eb70a5f77c8f9c892cb4cb06639a15c63a9b820ef8f7a720040ae5b9e49500000000000000000000000000000000117da7999552e7886a25a939ada0944cdb15b5c468e9d1c3bf5b6af920e470bd648d24f3cb7f91e670f57a52cd65f7b3000000000000000000000000000000000a24147ef5f2b8ad888899c1db8df0a601eca9d76f1b934b1627e7eef3efe542f51205b96b5c00916688579ece81336900000000000000000000000000000000151863d964b12287ae4278c905341124985410f1ad6a72bd5c62230b7d8b0cddbea0c62cb2a7147afb5bfb03348be53363da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f0000000000000000000000000000000001d32ea5faa6303c530790146df7cd5cdee93c0933b4cbc1c2b8030bf0a8d2600dba1907df1756152625cfccf8cc7fa90000000000000000000000000000000017b05f549751d090f42ce8a3ac5d959cf988ecdc485f51734d52c40a3e22a097917345978209fa74a0a05be0a66e5c6d000000000000000000000000000000001481fab7750380626b174602d9fcbc97555c516f4410193d2849443cf25ec22840e4fd00b225f98d81b38619e8844ce90000000000000000000000000000000001d56434066551c5bfbaf8c9007874abe57a6f78de9355a297bc117f2bc5e6e3f44b248037f400f7caf83fece0c00ba0ef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e000000000000000000000000000000001667f1400973598ad3f56c2e49dcb5b556cc38ee3e5801ac4943f3c4554205d8fa69831e582a084aae1ef584feb0a1880000000000000000000000000000000003f0bb26ea548e498f05a5bbda8b8e536613f10e7165607ab77565b193f794664c8ab0a5ae2368d7483b77bc1173d14500000000000000000000000000000000176d8d294b4d975629c6a89bd6d45f9c3924a621259ab43d33a3d5aa1f423b68e3cef96dc103494bbb9036436c170f5600000000000000000000000000000000002f8ed87c584e69de59cdde02b6de9816c31a6efbebafb6ad9cecaf266f5bb9c8880f062dbc9235c91c668bae5051f4bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f00000000000000000000000000000000085fee95b859c52e44fcb2900a9aa590b1a5c2f981a388d6ad7b81ffbfe033f648c4a84e2119cb0484e178ebd3e220d100000000000000000000000000000000171e6ca074aa97981d2c2ab000a8bd12cbd5f5d574cb83158a6ed734e8f9b7aa4b74aaa43b7aae31b3f4fd3d82fd30ea00000000000000000000000000000000004fe6099a52fb491a0624a8d787d95617f6c64d16d20d1b3769f60d4721f7af66d7e3e905b3e08b2946ef7bff4806ed0000000000000000000000000000000004d3d1a56af91377ae6b00e192ad64fce6dd43a37592fa8706c9344b3d96b1f930e03be85a5ead3007f9016255d2df7570363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a00000000000000000000000000000000155830eff04ec2f4dfca4f73403e408a68830bc031555433fd38ab3ce1035b5f882bcd6032aba69ecc43625546b4a3a8000000000000000000000000000000000ed5b698b1ae23769cf5b6dc2e39f8500fd8a881eb43452d67c6b84ef9f0b3c7d81db1909b646e92412acc7365923a940000000000000000000000000000000009f28ec2f949cddee9bbe2fac12c2c029f4e472afa1ea56d0edfeacdeb9f43a4a43b79ccdfbe8957b4cc16bbcac1857d000000000000000000000000000000001474b435131301db9e232ddf54569ba99bc476200ceefc15e4aaaf1a574c1de8bd2d63c8280e23127a7a036acae223b1997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e8800000000000000000000000000000000168475854829d47356d9a8dc13a94e8d169771ea0070d9ef45e666d5378dd676d603c2eb57a3cda072c11e0926b02d650000000000000000000000000000000008b493a9f4c19831341782fe6285db2f7e8250d72952351ddcfcae6f22a2ec0935e29d396ba32f72dfa4067d0e7ce7cb000000000000000000000000000000000d9e72e22f2a1522babc5f2e8dc7857ee690f60f7843ffe15a080d56bf63db86f124cac039cbfa16fc8ace4d6268a1180000000000000000000000000000000008f3db1f6c0e5e7b3bb27abd34bd877cc3c373c681a3abc88eaa91636924ee477ba5032801dda091dbc51936a90c84685ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000004e34bff7e9e3ede02df950aa0e8c5f4c5f85cd3be89d211e957a7de95b8e321cc11400c3dd5b2ba0d1a3008462cebe7000000000000000000000000000000000fc1047097f01fd2079e6357ed379ba39107ec41ed6c6dc17fa6248d52be2b1cc2593c9735a6cb48e6d6e0434028f755000000000000000000000000000000001896fc5e990aeb416cf21ccc73f02c41d019d0a2679bd533d0811b7c16ad3ad3a6988170fb2db030b5fa7c3e4df5acf4000000000000000000000000000000000b70e14ce1b54d7913b9f3782b2b8ff249967a6b871dfac7f54f959954febb2783cf20e20d1710e5526ef8aeafecb3d603c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26", "Expected": "0000000000000000000000000000000008056d4dfcb593c10a877cc8a4accbf58f360256b76876ed2b33a07be3110f8e295ef459dd6fb10d12bd02a8276351f50000000000000000000000000000000005686da1a0da89074c6b13fe9913f5cd49e0ecfea46e06493510625f1393ba4cc2e13f023fbc7ec2e130bf9a4f7483ef0000000000000000000000000000000010cd660001f65876db5b2cb1a56d85171d4cbf037f3bfb0e01bf4430c479237cde5b6cce5839a4fb22b406846e757868000000000000000000000000000000000809d7711211d37df76cd1cf71001cbf02c76df67c83e4eccea3e05b11d196b5d52ad7c3d0a00d9f0ef5b018717fc3eb", "Name": "matter_g2_multiexp_67", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005d6cf50e3add0e5ac3016b394ec363d6145ed66ef56b07bcd33c90600e83b4277558695222062e02d1e2b0693858e73000000000000000000000000000000000de8caaa810d4ac39258e3d1656bf7f2fb7853a5963ecb989346abe90d5d35d3662f6e283cec7bc386a6a8638ac395ed000000000000000000000000000000001849ef86eec16b0612f214c5ed52c0d50a90bd65b623402879f2654fc578ab680d49af9afdeff546702304597a20f1fc00000000000000000000000000000000168707730c4e74eaa4e85e48e7239b9ba3e8cb74c24b7126a685da0fcc963b9f9180e252adf7d8c521deb1a2ce0099582849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd0000000000000000000000000000000007d4fed2fbd9e9dd19e0af5c52637b2cd337e0bfbcef0384f182a56189a7e7304b9d2144266ffa79044be90cb7ede1b6000000000000000000000000000000000baabe8c23a10cfe85494c693d1b09fc8e43ef5f233052d5b6294dae14b4ff9e5ec240a1c00a16a9ddc27cf7b53bcc7c0000000000000000000000000000000001c595f193229da9acff04ef67ca444b0cec75db5b2c1921502e37eebdd2bb43ef47290fc6f1980abc75ef4c50034df00000000000000000000000000000000010fe7f3110ed3a240366ad7ba31d56ab993468dae2dc1b667a46c7759baa37b865d02834e14280a2ccc062af5bb2b7d6e6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba69900000000000000000000000000000000150350d8a771c79268606d6a5e1c147dc9d92e63fdc60b20be688bd52eac697aa5d90fe1b7b91321b2af87c47ac0d5060000000000000000000000000000000000fba8f4da448b8f2bbd99014bee2f9c581f2a974bb0b54f41a84a7fb359e9dbf88ba59a705504140284d486241e94e80000000000000000000000000000000003bb92d6a603bd93f8e987071a7385de68d10cfbde389eaf01ba6480caf1ad8aea03c84d1889b7d5b5c5f72e62a2d75a00000000000000000000000000000000193342a9f15109367030724946342e564507b26971caf75190e0b209e429a948d8b21ca16041a01010b68222db66a16b8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b000000000000000000000000000000000bc4a46eea57231cc64758560e3032a8ad8f1907b3cebd7a8faeb98c4216cb8a0c8fee09929ecefc4bee7955f4e799ba0000000000000000000000000000000009f9486257ae3f94a2ca48eb203e2ef44ecf866ddec7824e1a4bb3b89b320c38b3c46de8202480628c53c415433383a3000000000000000000000000000000000d8e2b5d0825b11344d16dbb2cc614c6b84eb1cb43f70d70e272123867b731775b429aacde611318b2700aa567a84c7a0000000000000000000000000000000007f720929287a70873e9f2f2031b66693eaa6e604668219aa5aff3f50e720b34c5fa3f5c66eced5c3e86e8b34a81b984c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e4000000000000000000000000000000001537ed68e203e56f31498efa314322694ebd74cd1dcc3145d534299fbdadd4256f20b9f74b895931a60753bde6ff9030000000000000000000000000000000000935c6ae847aa7f47bf427988665e5e18a32aa869e196cf9d5bac1349c650219a8d20e01bd8d49bc7e4bb8d464aee84300000000000000000000000000000000013e0661d7254428861cc3ed47c3fc9daae8b86db35d1c64f8ced3bc18a89202825f13163ff94ac0ebf046a0a99727e200000000000000000000000000000000039a6b0b2cb91e460d50eaf9600c29fd4f82a81c283ba4fbd9a7d103efdaeb1e82947f5cc1a7a1112ae6344c51119201650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd500000000000000000000000000000000071a6b0267806f2b9e0ba493960fe0e43f135c739a54c8daf5ef9ee348a281f19876f80c0dcea59dfe9457b49809c12a0000000000000000000000000000000009ac83690c30a4afd78f94b2493674668da4efc84007d2a08fc78bba271ed1f43e2a9e5909149bf0811c44dbe07c52f9000000000000000000000000000000000f5d523612fdb2e7dcf5da56720057dff6b0b80707cf5924d146c0c072edc0635c73fb04256e06c7c9355cfe77a7af0700000000000000000000000000000000168431fc569869ebba9b4a72371e3df232545b5fb95778baf3d9105930d9a89b4cb7eba430e9162a5589c7465e54ca3ac067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ce836522b983fe3ef6a502a0de4c599fad8a36a60d914218d5d2cc4d56d69eed8d27b2d50899639d1a0ea9dc7597f900000000000000000000000000000000014110ac048ac4c20e53f2214df8c06d77f0b3150077d027691cacd3715d4630a387d5819ef58eb1bce2e8669be330a3100000000000000000000000000000000178e5cb42f56df2f1b255a028a00df96c02eab0a79aa0ff3e9772fbe3eb62174728259b3a15e356e6d9666eb65fd6b7e00000000000000000000000000000000045197f136649b61d6e0e7b9a56674e769e2d26716ee7a63fd2b83b767a9ae96694e9cf81375d0377a1b27ea6dffaebbb448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd0000000000000000000000000000000016a6a58301c243b0c59d6934bd926d6440b87b49f004f411ab0fdd924480175052f63f594c18007359055dc776e7f2d300000000000000000000000000000000176db4845cad46a13d9dd0f4077cd22b3458f64084c7325e9885f8ca341ce3ccd4f634f41efd6a70f16e1f0c9ae103a900000000000000000000000000000000068ba68f652c4f072a64d56618f93a1e148274b1b835433be878c06e11f65ff45b7cba0f67fbe80327abace68396da7e00000000000000000000000000000000047a699487964c98453207c98cc91c980c1ed37dc26e17748e6ee88e5f4c0ce424d87c82ca6db2264dc8aa9e437a5f25441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee35000000000000000000000000000000000bf7e4a9751d4e3baa7ce9906f4378764e5384136944f6d3f3074dce66ed017759783c64fc381f0dd7512d6f6e55b4aa00000000000000000000000000000000006ae2a4fda156818cb5ea6120edf7ea39370eeecc3f306890f47a6dcfaffccbb69fd21f33fe491b7065838b277ad2b2000000000000000000000000000000000d3ce00c2f5febfeb232dbbb74fb0405bab86474d1d9c545c93b65c7892bdd58aa56225641074ec9b428efd9063085d00000000000000000000000000000000002552a8c1848fbefd6b039d6c4bd47c34dc34ab307163c4f6d337946f1d1b41aff2f7e37f5fd94012f0ebd21f97d18a83020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c00000000000000000000000000000000105320cccd67b6ea78e96e66425a10a6911d2d348fac3231af583146273609fcd7fd27a19d4614fbdf05bcca0f92b927000000000000000000000000000000001204229ee1f66fb5a5dcc4ee978327e35d703ea310901be9c100af824e39d24a028ef8fce42370e5d734df02a26c145e000000000000000000000000000000000dd21f31f116681c1810bc36141cc18096cc113faee7db2c189abb7a746e398e272fa0cc61286aea0a5ec4008c8d03b60000000000000000000000000000000007911297718e98588844b9022c825bc4b37f2af30e1fc2d9cfb58b4500dffc8e9949afddd051e971fe78d4e1e7ad1b4a82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000760787190048e6ec8bc3bfc368f010e2f8aadd53164693a62b0d7207575bb2597bcec4bb382c57fe9053e90fe2f7159000000000000000000000000000000000ec525abbf13da64a8093c5d3fb800440f4c1fe798bcc71eb97bf2e0aa9e8be4b08afd2313f9143260058132d2607141000000000000000000000000000000000aa12c902084eb843daf7b351989bbab7a86acb62eb54eff0c7599bacaf44653c9fbf53f47f6ca72d22ea1671842eca800000000000000000000000000000000082f330d9a693f2bb9386fe5274aa79ac73a17688821f3c705120fb2aa76903627786a8614053f21a93e0aeb555de64e338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b0000000000000000000000000000000002ce7f08b8d5052d8bd07090744ca067700eaa1db61dad3e5086661850337bcab485c15fdd36c309a9e5169fd2a2b55e00000000000000000000000000000000073fa834cb4dc4ae120e738059749bfbd86b9e64fd71b1d372dcec8474f3341137ce8cb97a38955e9081f9bd5e07ab830000000000000000000000000000000001568df6806d8c3cfc9231802ebe5edc5d505198747a0adc24d0ac59f28d32b7b379d1f2c6b8352389057c7465692ded0000000000000000000000000000000004fb4b08a4fbfe197e924be3f7213a769a2bcd24109ae69a32a197b6212c5f50dbe8f46f5ab6044a4c779cd3e09d13bdd29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee75021000000000000000000000000000000000c07ff656904a47b0c7bf77540abc47cc6eee3e76b6ff0983151de9468ce3a860c427f3d5d489d096264159ab0567cd20000000000000000000000000000000008cce094ae1d9fff246a0e76cd67dbf9808c94554372fc4aed4879487ef240e45047dc201dd8bbccb613feb9c4623a0b0000000000000000000000000000000008a25297940a1bca1267fdce450b0cf43105eb4a21ab14562116039bc8379b1a3f58a7c117e9ba735bdec40f772465300000000000000000000000000000000000ae17a9b1fc3b0b7803ef48cb26643e8e78ef133f94bff5f87739182e662e2641e72383efab1f3ec58fa20fc816d56c675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb250000000000000000000000000000000002357e5a04b0dbd7f9a1709bce9b7afa12b10c7274b440b4dc3bf51a801d483804b1b4b9a096c3205a0e2aa7c0100c6e0000000000000000000000000000000002ff20af67f126c80293e44bb3c9ac74a94586a2de4146588c7ee8503530398eabc30f7e89322727739618087fa55de50000000000000000000000000000000013c6d06ce509fd557946479f2768f62474e6db04b2c92c5cfa86c023f79d05a387bd4c9aa618888476d4ecc93ba0995e00000000000000000000000000000000000fa477870c952f7506b879b17fb0a1c31771ee832ce0ab21a513fdd91b7a2a78a03d297c55558b834e255462b15520544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff700000000000000000000000000000000181336b8fdc03c02e23cd06ac975855caa2bbc1fe78a2fc7a9d0963c90a1f1f9330d50b88bf2526db6132d336ea5b8e6000000000000000000000000000000000f2d94d3fde2c0f67dae5a6ac12f713ccce2621303762e01961843eb9924d1d3c732b4c977d8cd0e5668adcd7dbf7dcc0000000000000000000000000000000005ac9ecab11c3368c75b0d396889dc34bd43ccf550d817c1dcdc7143c15d5c0e241add37328a7bd8556fde87d75d67fb000000000000000000000000000000000184704eeebead43f85b32d7f3efb9b9469f3ae10b73a2f034bd33e6e66da0bc36597d8e29ef5585443a655e24ffb68fbb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a00000000000000000000000000000000046751743f8f747e378738c265c1df3a368cd9570a2bd7636991045974c34039161fb0eddc6b813003e0908915b402170000000000000000000000000000000003341bea6cb81fc5e7baefd386a518d17a6f752c0e1ace5a9580a1b1649f5501c7b4639ba0cdbc33808d78b025a31f190000000000000000000000000000000016e3b9e8e189df73574a00a721440379589a7a6df09eca9a790e04c729400323b2110f63d547d83664c35227bd15b5760000000000000000000000000000000005ebd94e4640344e99e7e0f1619c6288665c985b90d99921ee61bbfce921265c4881a7e1034bcd840a665bae44467f5a94a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc", "Expected": "000000000000000000000000000000000d993522760839abc960e99d62dca1021b52ddc8147929c4a064ec72570ffb3793205598cefab8490446453fb6da231600000000000000000000000000000000105db1e83fdff735d06d34574f962e70d84e2c1ceef4d8a8f14c2673633d7dbc7b97ba6dce9013f06fcfb134ffa2ef98000000000000000000000000000000000363be663cb0d36b8eb076df283b075ab9e568e460be804f197c51cf7ef611d8783ced304407d4c2540f1a4a04c18467000000000000000000000000000000000ab2c00473a2267682ecb356422aeafc893fab96a3bd27ae58d9b0786624c8fde446cf68bf8a003d9449702e345b1ace", "Name": "matter_g2_multiexp_68", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000a575d896b06c5ebd7459a70b9321cd0de082dce7dc0ce7e39581751d01b7db810bca80f39f521df0bf70ef642bd66a000000000000000000000000000000000ab497a9590deef40f6fdc0d4db2ae7b6ad9ab59f112a5a0671b48581f1f2b6a71602c73784ca6c0effce66a0a9c6500000000000000000000000000000000000af3812439e44981c91633f73d1a92298ca1ed426c98cfbdb50643cee36affd5fd02886349aa608f4b8a27452a51a96500000000000000000000000000000000013126db8b642d33dd988b745b07084ef86a228767f7e8bd45aac830dbce4136ca5febca5fda9644d3292203e27439d9f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c277000000000000000000000000000000000cc12f75fe5e6d6f082f9977dcce64c7858f3b6378112e7e083caf0c4b33b5811d62a1130c595937983905fbde8db1fe000000000000000000000000000000000308b803bcaf4f63affaea0206aa9f4770c21b4d191890602bb4151b80fdb42af0cd9f8dd2b1a3adfe28d0e49712d2290000000000000000000000000000000019f83af5cbee858fcbc9bca0f499222849b9e80dde7ac79b7c46785a484fecf274e0d4326469eca647cb223068a183d8000000000000000000000000000000000d0a8334171571bc63054c032299824523bd2476b1150a67eb17b84bba01d8a65295624202c3874e0302159951734702dcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec088000000000000000000000000000000000141cc01094391887f46391bd49fdedbaaf524cfc94d741cc7c8cf081dd7c425d81ea3e407be48127550012e39d2b0580000000000000000000000000000000014db31972eb242d6c2912b418ddf416fd7911f13aede9194559b05d1c9e12056deaa1e56c155cdbc231b39f4f9aa91ea0000000000000000000000000000000007b361beb6c156b5c8b92b489e6d6c05e32a4376d20ac3e1a54c94e678c88480779bb789c3e1ff7a021aa6d872c98551000000000000000000000000000000000215d270f2d3c5c5b9fa99a873fdc337f4edad6889f7a55556d8ccb5ee86b592453b74a720ef6a907bc342710cfd9cf91d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c670000000000000000000000000000000017f5d31987655f8eaf046d6ea4025444924befa51c319b2bcb02dcdfde4d80a1c48049514e0b580e4bb59dd2fe40bb22000000000000000000000000000000000141ab771c08ad7c592725630aca0b2564de1ed8759eb3afb10a4bf451eb21d25e8d917f49bd5f7a06894baafdebbe790000000000000000000000000000000012dd82703c939cc5e7dd5bc3b924d744f0ef1a95fd0b9e57617e822e3fdda05b2e5a9959ec48cba0da40079da2253cc7000000000000000000000000000000000c53ff34d875fec4c7095af324d15921cd775873a3ba67740b2c123d6d482263b1cf93585dc810d19c68965cdbd9e102310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a16000000000000000000000000000000000a6514331035d42f58abf98b805f159921d8c4c935f88bb5493c580a6ce14a65e243424b41b3a9188e26a7f0c912a378000000000000000000000000000000001351e48b2d3f619887f4e83823dcd9dc15afb2800169ab78a2cd5ebdf25dcb6310f1051894bd2b549e509c55f5286f600000000000000000000000000000000007900972b84b6a76b2e686fa5757e98b8395bfc99da86eca122ce209afb39e8f3b07603cad92623774ed54d637e350d30000000000000000000000000000000002c68c42b3924b89a67764990478e48fc17aad4b5543bd38bcfee34fa1cae7535671f3b885852aecac53a30f28b0d4aa2fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000152d2c0e798d85e4dbf35dab808dd29d724e9b6c7ca7f53ffddfe1aef5976f2d3079eb1d3099e91b37d9fad7f1af5750000000000000000000000000000000015059423ee4e7201aa65e39116a2a49ba715b15e4b9547d18a0efd355de6f5a0159bc9047508bd3649407758d62887f0000000000000000000000000000000000e5a823fdc69f3928b22c542388f982f8131a978b08dde80d44e51d9eaed2ac4a1d5fa7392be6c7edfa33e833da4832c00000000000000000000000000000000044285f4e4ce526f96f9f512c5be754e0b0953744dcc04807ec6f041ba5c6fb9d5d395e93317064d50e61aae26810df0aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef0000000000000000000000000000000019c457e369dbfaa130ee79bd33ca70d00a3797b6cf62126baec0c5d7c3fdcf5ba7f41195276dc412b6862b71560aeb77000000000000000000000000000000001206f67dee6521ede85573bbd5784d675fea42da16010544857d4e2d81b720b6f85f646fa23540880b44a6cde9a39f5d00000000000000000000000000000000142018ecd7c7acd4f4ae288e1c6a66594f1c7f31bdb9bade2b4dc4c6455cdc685b716382c54d67373831a19100185e850000000000000000000000000000000013b0b57463a3e4cbe063c0d4f4e998cbeb132a41c2877106ee60e83d4ef7d339a5432d30a3c149a42dfb1da9d61f34030227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa80000000000000000000000000000000008a463003900194e45fc2610fb461fde538b17c4fd516919000d423f5a1b582342ab9ec20d8eb6fda8fffc6a898e46420000000000000000000000000000000010eef0f7bf73e35dd75fb924bd9759c09aded9cce46b05e5d3c5eb3e93e5d5032ecc459e2220aa529d2f773c4b8b8c180000000000000000000000000000000002a0247f82a25468ee74da555218cdbb6405871f7097c24e89db3f3eab59b91ce48ac06e8eab2c049346436c846226a3000000000000000000000000000000001895b58a50c025e46a2cd0c59d5437f6eee75fac949adb7ee12d455c96206a33ec9ac17d5088fb773618fec131981ab6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc000000000000000000000000000000000cb0f13b0680c2f7de522a59f4e46fe1d4af3a64cd3ab97a2523ad3c3dc42f5e6760e06cf48e4db22ee64c5ed8273dd90000000000000000000000000000000016517038ecd2799d787c5b6ee93079c93f78de4a96449bc82699ddd6eebcedaa1d02981ab47c529652cc21663f1a665100000000000000000000000000000000067ae1dc093d4aa2ddd8b7127dc60745ce9c462a066106b099a7a07525597c72e4920bf64c2ea8a3fef3de51c703de8b0000000000000000000000000000000016374f51023e2448eee7c64115d85794996fadf4f76fd4266c45093c266f35be09e861d07ff194f3d15e310385705f0e1a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec0000000000000000000000000000000017479396aeac06bd624a47e75b066d6daf5a37dbe515650cdf3e16be21e7d3a1f52a695c1c06382589eb7fc869c7d9250000000000000000000000000000000015c31ff36ed4eaec4d3927e62c111d062236e19fe6514236e6e3f7ff05ee96e3e4c084fcafcd21049a81faa1f84b7e7c000000000000000000000000000000000341b440e6c6273515fa7940d2f77018169bf6362b70a7b0cd6d66cd332ccc30e3ac48f7581edf47ebd137253a9c1369000000000000000000000000000000000cf424de046252efea9320b32b79bdab58e0e04f2916b4e8ef475da7b8ab85d8d5fc793a45ec6e6c035b6331a895d3efb206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000007c9111a85a6acb851e9cbdadf182096b720913ba3fb357dc2cbf2b8e796e9a8044b6df3ccadb740c73a16c3780c640b00000000000000000000000000000000059543a955c84a197d23cac22e15d82363c881026e41c57ee924da2a8c044f3021b29918d1db7926ddc2fc7a662ee7ab000000000000000000000000000000001355d8bcbea65a50c9b6ab59881e48e8e5f5592cee6aa69d5d01b033a84057cb6e74d911769bd2ab5f9722328aa204640000000000000000000000000000000011232571c95d0cbadf8e70454c851974efa4b326370249238db159a1224cc6d34eaad690e1840ad887a875b667ac1f193a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e00000000000000000000000000000000088b7cbecf91721e01e5e4a08ea3b261febb58cdae3056d9316c3840b3e5720a289739568bec7b899f4b1f4f5372013b00000000000000000000000000000000001f8835d4b0e3b957e46b718b6bcd81acdb50ab85f10bb70c6343a23970efbe72bef89dbcb24d66e6a6be3eb55665a200000000000000000000000000000000046500afd292a31bb5a4a9bd7b5bd0fe608bb1265351edea69162e61f1623cf58e34e8e1a8ec58ca166e8203c86f84c00000000000000000000000000000000005d6cc367ff9c88fc8b6c35383f147b4f9e3eb21268a5a7405794441d449b3e1b44c8f66e30783e5f6c3567adf0d80171231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa0000000000000000000000000000000015bab57412cc5c7ee0147b0d2511b7836a14a82df06b4eb2b1baab102840ed04cad81da6e920ee000751e0727091c1460000000000000000000000000000000002f725e61e82980e6164cae7a2e30a36dd7245402f4933697607640d53fab2d5db57698be33a0c9b5dda14aa846db7c90000000000000000000000000000000007fdc589448887f6986efd817c63954d350511401333cb0df89214317dec0a82b06259ae9263f260fc7f21f98ad2630f000000000000000000000000000000001324e3bb46a1c69fc550fa8f2ae2d0ea74bc2d7159bed03c13a9d232233449e271ad1c3922dac5d84aae52606f77dcc0393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000010adc26d73007e3b1cc58684fbdd7d197550658b4c66c702e9cd0f4e481f23a26c94c6798cdd9763110eefdca3d802050000000000000000000000000000000009138258ad1bdf6f9cdfb943fa32b42c4f1d834be536ed365d00126227c78b0df2776610fe5cf66a937cca3e0b088861000000000000000000000000000000001991db3a35bd2cd72377cd459502a84315422bed92890af906fefcc0acc4515fe7cacee1e4f360ba24efb23292482b8f000000000000000000000000000000000d10dfb682ae7a78b23b37b081efba32ff2011fcdae7b0f8a794a6ec33d71f5d6055f93e3b68a37086ab190d7d9bd7aed412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000182795b905320ee69281de833f37e040a3295e23be05ea7ae4563bd49d8b1fb02e95782c5c19645244633951cc29c5c900000000000000000000000000000000053368ee1412723b5c6465ee5ebddcfc00812e0e12e940f8485f44bce475c8897b324eaf7e66c0351ce9a6c92758c337000000000000000000000000000000000279f26c1e76e5f5d0fe1240c0956cd6025f6520ec303feb383b69525ebb6b2f199808a578a91368c3881a4044f37be50000000000000000000000000000000000ba4012c24dfe1038ec4b4565e1b321bbfc174cb197f0b0914bf1c126bdac9f423845f6742129670b7f3dfeaaa62df45b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f10000000000000000000000000000000016c27a3a950fc4857fc775441947f7ac02af9b3df6422874507b11f7b005c61d7d6a4a115d3759fcbd64633a8ad95611000000000000000000000000000000000e92954034df4f15450c32be31d4e146c4b0014a2b81e2afe755df79aa962afb05ca4d03577f15980fc6d8a34f2cc50200000000000000000000000000000000032db3e3c3617c16ceb1c8fae83e806744ca40cffb56bf9b79997cf48c55e5fea89db43b368cd922cd7ce30dd3984d82000000000000000000000000000000000d153fadc3854be49b2376ffcf4e5a46b9dfb4f54e580986767db13127e2d4d10e465f1ca932d79ca90f1971ddc0993dab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5", "Expected": "000000000000000000000000000000000e1968e131e25d3f911284c369eb63aaf264ee82f4d9bd978b7d02470feab68ae82aed8509ffba875798a292f012c9180000000000000000000000000000000011488365570d9bff018ce6aa15e3d7e078368f09873ed5e0b827d1c25ef369d6571899c8df38a3df3135d0db814c87a700000000000000000000000000000000161973f4949bd72b9f010f017398778e0d7f0c8f77e12a87db1851af516b4540e3f4df314381f295c4d167fd9ac091a6000000000000000000000000000000000ae16f0a4a597159195aa47862585839485615095e538b745c1081ca73f202115a438d279dfa45bd3aef8d4043ec67c6", "Name": "matter_g2_multiexp_69", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000046eea8e5af344dc8600ba7e506e923f6c356f7ecb3b78bb3805c4561e808c1f570e122c4fc5a1fbe433b48ce0c15d510000000000000000000000000000000006f1ab405a46c825e104bc963d2b2f573f0d345bd2b08a952d8793c0297dce267a754b802ded4db478399cfa88e7e255000000000000000000000000000000000a5fc4a09019ac9649c07b623d2cbcd9f0cbb89d28c01b170b62544d8da8ba3f236ca3172ac754175a3db85d9b846cfd000000000000000000000000000000000f7580110db2549742f69bbc2850e4ab35a6e415bcd1b06220b9b009c1f4c99152289eedbcba2aa653f38f6b8460386b3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e00000000000000000000000000000000026907ccf4d501265cfe67bc1c0b06840e9dd94a614c873d676b5416457d98a1dd744322887f1f1f86176b11a27d2830000000000000000000000000000000000cb08e541a5b32fdf51acb28ec64d3ea542c7bd75179fa3f74e9588156815bda9d027dcf5597d714aa001b2dd8a9553c00000000000000000000000000000000103ac1c03c16706d5936f216a6445577c96acd3a00a3d8a9c2c66e6ce568dd84a4c4db187a5fbde24e6ce60e037f53a90000000000000000000000000000000001da5cedccc02d0f8d1dd7e4d81c3ec47d432e81e941ea1452b112eaf40748a6634957c90f32fb0385dc5d642bf65acdb3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000a0b8dd68918b58ca6b113e938f8a00b2595351777aaf32dfbf703ef3884f02c798f1b5bb78cfac32f196c1fe88aecaa00000000000000000000000000000000121a4104e374566f8d582f75a3c9b70f09628f116b7ab22679ee13a1691b0b0bdb0d737833fb606c746fafee5859f1ee0000000000000000000000000000000000b8bc89d718572ebdd6e3100769f2571cabdd79ef5ca9a4b9bbaa432b1a4dd752f9af9d2a9b1f1f32d76d4ec2d1636500000000000000000000000000000000129f1d760a12eb1a75fec1d2ca438189c933e87095b9fbf9a0371d64eb205d8f0932fde9ee2ab9f36f8b6e5d4b5dd31021953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b825000000000000000000000000000000001499e5481ceeefcd2ff672df24e8987fb60872ed106c496178d71c68e9078409a80016e1f9727ed0d5922c93e821dcc80000000000000000000000000000000007bfb606c005c7da6b4ce2d974f9fdb2e3710c8f51f18257ced7663cc341ff81fe2e46308a2b62b13408965949a6f08800000000000000000000000000000000003fbd951e860e3a4724b667427fd9916ca4ba511a0dcac7b1125b14d8a4f4da82ddc0b0edab8ea50e911b0fcb5c200a000000000000000000000000000000000b43195a5f0263307e85408ae4eb046e06ddb1295a490ac4e0e654324de53d0dd023b8cc159d86b861dfcfdf7ebeee4a505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000000743bed2c17bea1ebddf750da504fe120f457cd3b1754c9413757cc48f7aef07eb4fa0572cb853cb72d68427e875456000000000000000000000000000000000102ddfe3dee27186a9484f74b3cb3aa366a79f0d2e36063af6e484f6a459e9168d7a4a6969bb720ec694a52db7ab34b40000000000000000000000000000000009bdf5b86aba4845adf9187ccf9c74b1fcabaa05764e41fcce4b38356b4a0ace8e7b16abfc7f7b96b785ad47fbf8e90f000000000000000000000000000000001934fa903b71d234c4341b2f49f8177334142e7c401553dad38e66a2c157fcdf7637165058955b7798a59051846dfb8cc4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f000000000000000000000000000000000ddde953f59b8591a83b0cdfce780ec23d052037c26d60cef36522d0f984f907315d7b41c8be9a9632f2b88e0ce950ce000000000000000000000000000000000b8d7bdd94a994901a434e6ea5d03ea45dcdb859e560833d8ea0bd9d20c7db9c16b2427eac27d8f1eb640b7d28a530fe0000000000000000000000000000000017b5b3a3097a74d9c1f1b23783723235b6148023b6b060234dd9e2f6fd05e38668167136c999d91249963e224f9bbcbd00000000000000000000000000000000133da0c217c31ca052800315aa8a3b934fc1f179e6247801904bcea1e28dec0b65632ab2690bcca3606bb1461aeb147b99762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb0000000000000000000000000000000012778a6fe79b1f2b768432df036543cade95504bb7735ff547969faaa8db84e3588046a074838c9a551a4fb48f4a66140000000000000000000000000000000013288a3413d7e7edebd118463d5eea9f9ae2e10f51965480f9b5c244b05775d04079a1dc75ba0885aaa9e2e4bae1ac750000000000000000000000000000000005b766ad112b8d69f1a28079688942ea146f8f31616611909f539a57c58ec5e857da9fce415d683c1c6dcb5e74da9d17000000000000000000000000000000000907e5c3c83d3f12a68d6bf812e310f5a04f1417094301fab7d4f41007b9d01fc1bfbf739dceddef756417367ed5b1d0298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000090be6ce5ed09e45a6fd9ea3a9223fe43a835141c1c29d6b386e085846869f9c5798b80c3bddec8bc15171906dd417dc0000000000000000000000000000000019bdf67eb16f2708ca55fd20af8deca66e2ae270b2f2f9736fcf49dbdf7cee034cc956f6fb799f0e87c12f283a11448e00000000000000000000000000000000124a69c723cbd366d52919a72dfceb7e4cd9ca5b5cef1784bfad3f125b11d810328ea1c849602536af500261aa684f5b000000000000000000000000000000000bbf05318ffd81495efa4f4c271c8b1c669041a6446501788f49b8739a934f09de9d976fe7300b0ae861be567d35c992177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e5730000000000000000000000000000000010833a3e7329ad40c1a8cef296b015f6ac6542c612038ce00f13a99f673783cb7eeb14796485c168d21cc169065d051c000000000000000000000000000000000d3b1416b23453b893c92a6c7850cdc0e4a395459140391b1dce11055da10fb68f318c5561e1c12d991a28f3f544a5230000000000000000000000000000000014721dc58eada80f2d0574fb4e2c1c94c45fbd90c2d2fd666fd618a96f4736a5ecf34cab34fcbdcb19b6cf7b44098922000000000000000000000000000000001905d34029bf84617a956d1edae090853dc1b622f560c5289251447ab6bcea5700bdd80d6ffb2dc12fdf3b0267e74543cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a99000000000000000000000000000000000b4d380f4f4eb976e6121b933be8418c536f85994491b0b93695d50473615e41547ead326bab795d4d59524a61d607cb00000000000000000000000000000000104b7f4058c9b355d38908d715c311a53169b42d2434de0876f1c4ffce1c39603c4876b33fe3076528be15fe42849d3e0000000000000000000000000000000017e2fd647e7739366ebb606e8a326daa5c03cd2b726cc4cec7747cb3468419f1907126d7cba98bbbc659478ce3afee7700000000000000000000000000000000183be0a976dbb3b5385b544c194e111729c7a8d5aa98eba3fe1c0a5b69b5fe6e5d0164e96398cbc61eba5b86d91b3c94e03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f50300000000000000000000000000000000023bc7eab817fcb9982cdac242cb6cc0ee1779bcefaecf144dbe57d5ae2b2ebfe9088f39f416a56de4b4dc04d4bbce7a000000000000000000000000000000001318e728c271746905788dd8f5ab22a3a10edce3fa063438e54ebadba22c29e461b2ed78a95a8f26a65b47022291b8df0000000000000000000000000000000010aab000b9c5de56623f18861b343ffa80da5ed4ae0d7767b7ed791bf3dd507fe7286447b6a07ea0fa12c19f2e4d8e8d000000000000000000000000000000000770e2909b5795a08d98dc66389655b1718e70b93c5bc6d805c3945cb5fc0092a5b390e6497b550988c28c58b6e016a3804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f1000000000000000000000000000000001259a4e36f5bce7d5f97184948d57fccd458cf7f2ae0c9e174f537bece01d744fef544447959cb73a678fe2c378ce3c900000000000000000000000000000000131aa575b2b94232e06879fa1f6f145a0bf5dd12456b698f731a72bc587e6def5054b3b2afb6dbbfc34fa5249dc673860000000000000000000000000000000011d64b923596c316b097a0752043efad8b61fbe068c58bec7a6766d9bc90ed965b3419dde3b96679426f72184adb8931000000000000000000000000000000001653af784cbad5a804e3f72716bb51e0c733014d587952c47395f953828566cbd7da811a3da1d48681998d569db00a7bed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db00000000000000000000000000000000077a9ab830f7683b7fb46676df09f72d773b65286c5f5ea86623306e5de51e63851c18d192c4c3b20af582bb7f017ff70000000000000000000000000000000016dc185f4158e249939541d35ae8230fd749988b9174c40c40b8c932aec625a7e94beaef9a07f492445d4675a01b7453000000000000000000000000000000000c107a895bfb45d33136db6251c76dc0461a235fa5d1ba7a5d216bfebe15691261b46c9816315c146becc328acb6b8c7000000000000000000000000000000001151cba240678efe61e3a36e169e314b3610e9d4df6650507f53ccf635d8f1277a80d86baa85a2d4c7e2af73934a7299e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd506400000000000000000000000000000000090a1ee6c611980e0421b72a122cb39257dc38d1e74ee41b809ad76e440fe307cf45e79afddd8d40b94382d48cdd4c450000000000000000000000000000000010f2e6e610eec7b7c2b95c1510af1af342ac19fd3b01dddf81b8961ead2cc57a8eca36c2f5747238eded5914e484c52e000000000000000000000000000000000acce0789cfff975b09d687ef79535c536f3b799157d3ff731915ea5b323ddd9f6f4750dc8e00a879d4e516bce8cb3e40000000000000000000000000000000008d8203dd13aee7363f6b10a9e1ae9b713bbc8b8fb2c56f05fa71e8d69ea571384d150e8fd01e855b1b0054fe7967a052f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000b332430c518d7dcd120b346440e5b6b48900b5c3656d84840823a96e5bf002816d583a989898cad9e09ba978ebc58a40000000000000000000000000000000004197b43877b833de7f69cc1a43ad8d6d3544cd10d42336d4b19a187f31337a37b10cbf48e72b77e4d8e1a1da68e5e4c0000000000000000000000000000000008887d5dd08f45034584f40a2a68254baf2104f9d6a4c2637ef79c5ff2503c246f7adc36559758a0c07533b66c3637d40000000000000000000000000000000009343819dec1d4569683de4596621c19785d5ed14ba13e57d94b1b1a108aa62cc8c55c58dfa18c06883ce50cc1364b95e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d7305370000000000000000000000000000000003fc7f9a0804e7f1664f8cd3ca67b70ba128529a611c24214fd09674072a6b8d652ccd37bf5d4611424688213a41cb3100000000000000000000000000000000137a869cd7bde696035bd9353662e0d37d2aa0731ae55357df3bc43536b9210f360324cbb3670362cf9ef607b1919bca00000000000000000000000000000000045d9d39c04e257fcd912c54e57c86d2d4304e6a7cb95a83d2bff07964d0a5dd8b4e42bdb91a8b245e512395e6749f1f00000000000000000000000000000000120e5e4b04b8a744757812fc331e7c98b35624faa1cbabfc1470e4c0804248bfb0c53a484107a677a7d3f0d2b533e7530cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5", "Expected": "0000000000000000000000000000000002bed414afe9c7a630441e7b163280be10e502cf877e94b6521d14baca0087c5dcdfa39ff4a51c8376d99855e1e6f36a000000000000000000000000000000000dcd54727a7729408e682c6e213005687ed51fa7935c522312793fc58cdb273eec9c61cd8b056a26619fc8dc006b066800000000000000000000000000000000137286f4086763e6ccd5ee82d3bda712b26814a17c6a71006a3e6dbdd919e469bd0e744bcdb2074679e78a1e7d56ee7d0000000000000000000000000000000012d75de1310199c0e556d61d6c0395b406afba0f13bfb37486c05d66b446809e8b1a024e8fd2c55f1b82cf2aed99a5e1", "Name": "matter_g2_multiexp_70", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008b83142b22f6d6496cad0dea23c71355e7c5d98659580b5ee6e97eaccb9fbe523f7e3b925abbca3a38f67426f3fb35f00000000000000000000000000000000035f655a1b2d22ea21cf0081e78d7140bad08c4e66dd45230a113ff3b7a77e39f0f1a72991f85e2b00ff58b27d5cb54900000000000000000000000000000000105d04e38243ef1ad2f734a3c97e91506c5a7c5d95e9b8771b7fded8908f1be933a81a5769044b633d501c0df7b5d7fd000000000000000000000000000000000e670ae4af94d0df34a7f2d7cfbfcefa6eebcf2a6b2dc5b82068b023fe02ce8a279e1bb96d905ad4f2ffbd8214e47d702063b046a71c2674e35466657a85d8e02253b42517b033619e31a536659172120000000000000000000000000000000009051f1e636309016c5433cc7eb019c7dbb75b3a4a5b27f6927de08fdd9577e8eb9e12919157ed35bfd6607be7fc4de5000000000000000000000000000000001953b7a33695ede6d0792eba85567aa5052b8a58c1bdc94ee82b5001893c6b996d3e8f7af8b8effd6cf50656d8b85554000000000000000000000000000000000a2f769f00679b610bbe212c2f8045e7579a96dc6bff80899eb7715aabb1afe79421ad5000f2c7b85d4e0904e335ddfa000000000000000000000000000000000ec962a3d00fac14d05774adc49bbabaf46ae78325083c0020587fb85eb234387aaf6506f503fa988df8e9ecafb4a59992fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000006823056a4da801cae430fb9e3a8663fc8f46bb6c180b743b7f9c7c7e3287f3feb1aad4be0e98409c74ff58004f8732e0000000000000000000000000000000015f7a3f692d55252fa5af5ec952f581b796d54089f13971fce2ef9062173664816dd9f37174294ed78681d8c8c5a9cd800000000000000000000000000000000154743c76f7de590a31cb96d46a0ec0fa88008b7d6684bd8f6fdaec70722afff7b6e88c1f0fb048714fb1072d30780e60000000000000000000000000000000006f3191946d0e7c1307a1a0d1ea9a26db195ec98ad88f9b8f08a03a3d48bbff1fa53ffc920f7db5ebd4c65911392bb834484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df8000000000000000000000000000000000355018079cd02dfcca15fbd2934a8e47c5ee89e679663488499ddd4abdaba7679fb1c9d2102317cf2798c47aff1ceec000000000000000000000000000000000c417d489a224fbba9999300eb65a23749194bf5302fdfaa33ff7daeb8d896e387e56600233038d5c5eb59f644a99b6a000000000000000000000000000000000f5a62e9d711293d4373bec1bc2637802938eb789c828939e6c42f10062ec171ac6110261165bd179206d649713f6fe3000000000000000000000000000000000b11f9fd0ef8dcac2e21ef09846ffe9f5a624ec246e31393b39082a47354fc9523dbd247f0059b6cc740d7a387b137f0fae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000fbb5521cdb9c3a69d58e5c9cd7e4a50bf5469bda2603f5119f3209669eb3e374d700f851b0c7ac5ee3cc9de79e6a7ec00000000000000000000000000000000131ccc37581e64f6f9fdf675b9b63ceb67d9d5844bf512166f39b5bb09d8e031437c06b0ca01caae7ad6d8c9bbb9fd67000000000000000000000000000000000531cb0557fa18ef054dbff2e7e994f1af08aaea7557602a26fd6ff539ab3c0a73f1fe841177012dabed4a1223ffb5a7000000000000000000000000000000000a180e7a345d2b635be92888934608e8b6c17384c48c560f4cb9809ff995f8e70d83cd4cf0e96c458fc414e1275d2a993168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a200000000000000000000000000000000125c83184f63dee35ffd2c0c7dad9010cd6a9735675099f24b465554ab3db727ee76b5b7ea603ead78795d33e37689a400000000000000000000000000000000141bdf7e270dcd356993327cdb5dabe38a5c5a9b53470d9a4aafc041c46fe8bc841089e337469bddab5d4f7fd3d6ccbd000000000000000000000000000000000f9613f6d05f38e3073f14d0c2557101a4864a7d6d0b5a2b931d0613f020adb99a1ab2037a39fea6e99fcfb47929827a00000000000000000000000000000000192d812e05a17d22c60b78c53fabcc55a0eef3656f8e84132faf16686ee18ab4d35767db9a384d42f392c40c7b0fe1c0f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda000000000000000000000000000000000b6e1960e82586de19ffcf29a8c5f16cf2fcf5286bf42febef832767919abddc655a0d1bfa240cac8fdfaed5a1e8f389000000000000000000000000000000000fc1598454caf04414f1930f711d762f0d72f5cdc7a4053c92b916c742b00dd0f107aee111976c1b1218c4577deeb006000000000000000000000000000000000455d6e9e9bb848e0868c9d725edca1f50b279d0acef8c597927eda72763e3702f46b216919ac36b080b4865249fd961000000000000000000000000000000000174463cc7804796b4a6d8ff28d2e8cfd8361b2e38f368de30166cf3c20c474ea0a1e8d94749fc3e6468924a7d1369e62d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d00000000000000000000000000000000144e4b615ddb871bae85484c308423adceb5de387d0c7ffffdd2211b4ea28788eba9bfae96ffc46781e6d6343e2f501b000000000000000000000000000000000046e39cf43fd707ddc4b7ce9a8a22a2aa1e55aa63cae1eb23082f7b4b5dce49f32d2ff887b5108b40f98062c02d5613000000000000000000000000000000000b75b5460db2baca86528569b47209b5ac24930e2545cc6aa08c401a87ef2c4e233de537e5a857e533d0ba0981b24d7c000000000000000000000000000000000018f53b83072fe7daab226c831a89da63a0930ea86e301c97e639d0ee1609e298e2789d1a347bdb4afcd355fffd16d053bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000185b296e9c7209a9abcc3194b46be9a545666527ec9b0634a3e3be579447cb52330174c19e40e1667124552392a7a0c00000000000000000000000000000000158a053c788e5b914fcdcf1aebb4e21cc8bbfbcc20c4d692256b2ae48149f6644e1578f98d58b3e73d9768d0e7df643b000000000000000000000000000000001318ff4150bebd8fa612f4e84f89151d5c56c272969bc1f31a3c1fcbd8ded0e298914e98e1ca48248e9023cd12db0fd300000000000000000000000000000000076555254f382707fdb7419772a4978808a7409f59d1dbb8c9e648372e19c44573f5ce1888a2b570a83afc20e698ee44f944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e431000000000000000000000000000000000f4395e3f2e301ee3e18df3c23cdd142716c7fcfc23caed924f0561795948b0bfbed948a6f7c415ca615ee0ba4d5145c00000000000000000000000000000000176ad308c7fe8c3a1aa350fa82b8f8ec638f77bc703afe1042a6da22e5385cd8473ad789247f205214c9980532b12c7100000000000000000000000000000000092b0ec86c511992c66f320ad46c9d6d7c82df118a9ab2ce1f2c5611ff4e5cdc9193a39c3fc95f18ddf96e139688b00f000000000000000000000000000000000b4f671e334b7f22bd8d89d8c4eb8a52b04bbd4dd1259cc9caa1872093736680618930f3a469b3af4a00cb6e44b573f27de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e100000000000000000000000000000000123b64561ebfe085238220eb1428b3a203acb01846d1e4428f3759db6cff4ed3c1b9d436706f28b77e3b92e2e39ecb41000000000000000000000000000000000ccdf1973693e4b43b6133563986f6c96e2b924895c813f8acdd0f39585e4ee95ef26c0d9d51d6ef88bb62305e51594d000000000000000000000000000000000f51693bd44b12188131ca84801bfee0ca853640c0a8d5b20123c97b369c98299ac04beeb27d75946cc6f45f8a07b5fd000000000000000000000000000000000804c6597810d2c75de94484873a67eae258fcc9577bafa778e13d4814ce099a5684b1cc94e0df5a59acc7b19328fb8bb0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000009b02eea05c78a24adfb0187defb6810116e21894d8782605c1d590f8bdc10723bf71a1e5e5004b181504ac2deb142cb0000000000000000000000000000000015882389195128e20e50ec4f8d278e8b8791e362341be93c475064d640e1f8bb1c92a6c777d666f8644d471409bb9aa90000000000000000000000000000000000d89295f845f989e0fbc6e86e97400b08e39b2968fe6c9a141d1e92ec9c838a3d8e1ada5e44bb08189a5d514ebfc2f5000000000000000000000000000000000dea05d8e6ab50b8f8dd9632337948a60568724d5a03c7914e4a03e2af572dd8153effef1a7d5c2cb27765ef2c17bc5b4aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f8899000000000000000000000000000000000bacd14447ede6af0e92e19b54c4f5b6ebfb94207efec3e9f385a4c84a7d670514ecbc28ab686b383e239ae7f9bd673d000000000000000000000000000000001698bc92d146049174b843dac8c5dadcee12d1d503b2d0e46ee68139dd43d3aa797fd5bd06e2b214cc9ae3647c98394a0000000000000000000000000000000018d20cf6c84446cadfa1a26192a04e16d2b2a053705a89abc51bfbfa35c2b03cd58021ad95a35364ae1e2da5d233208300000000000000000000000000000000113268e360006294fa0203ce58cbfd05d05fb625e1f9474c96c89c0ec1ea80fe834030592c2f1c182ef8a3d5c32caf71adde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca0000000000000000000000000000000001727b6bfa9c601fe84a65c54f556887c4538cb5383a288156fec87420ae7f15da395886e1ac0e10b8fbbae8bf040f4ba0000000000000000000000000000000012127cdf02ada71f28ed036a417971b87fe443b8c65b7739795dc7067082cbc9f06f7bf10c709969281cd072490c06fb00000000000000000000000000000000134f1fa1d277d01e2811c118cf10e2de6324e2ba14efcf717a03c1a10dca0862ebde0f6328839da63d7d85f573e8501f000000000000000000000000000000000d20a036b715d18ac9e2dbe009dd0063a4b13b3ec6fd060a64c4ad2b98e05e069060179530410d154caa575d504c63b7b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000000bd5375e7f98d3972b93420a39fd6c31da86d0d9349ac3774bbef15c2240437cc0761b2f1245e805d2538cbca6f778600000000000000000000000000000000100232139641c8cd5bdaa75b77e1e1c8e33b3f9554e2ae00ec6315b82cc00a6a70d576d744e68938a299ee2b451558250000000000000000000000000000000004224691faacb007bde3e37db6c7486aa5d3b4259a24c8b7653238e7522604ef4ffc1eb3cecf719a1b7f52ff00c34399000000000000000000000000000000001156ceaccfe0396374c6dec5adb39f14b6f08a32b88ef7499756f5cc324a9f1553bf5dc106a97469f2c49be5d563e1100cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e0000000000000000000000000000000000010a549108e77f0ddeacdc795517ccdcb357f909264457cab22fac2b982d10064756d66d0e48af02a59f58eeb1e8ba14b000000000000000000000000000000000c68703ef1c1e93c78faebc5f7ccc69e39046fe8af92e12469e9fd6baee62a2e8cc06fbbb3def81ae5cc57f488fd9c9100000000000000000000000000000000064ffb6aeeed432629242c3843f8cbea5bf7fe78585763926c5c45dc3cb4d1c79b3715506d7cda18c531ef890b22a1f7000000000000000000000000000000000e0eeb69f28a552cc6563f5fdc9919423c4358a2b70ccd56b048c22111454f67107513cda2a5aa0efd2af25dc74a1c47e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7", "Expected": "000000000000000000000000000000000b1913c672760f98fc6d4e96ad1ef200f99add6f233b17291036e187ac6692ab0a29a4083dcf86a532dd06efb3d9b8c6000000000000000000000000000000000323b703abed59a9824f34d97851546a5e78441accea4e3a933b9270f92a9dd1aa056858ebd1739659018a0ca13b96e0000000000000000000000000000000001603cb3ed75c09ae5da6b94eea6017dac0c40b17d9aa8b65b78f2ba17de051bf3f21109d9afb214d260a09391f5526c10000000000000000000000000000000019f3bcdb8f16d9a2bd11e3f9491266780aa9110f447e19f12f7a2d62dc4f2c7b5fa18e13792007f5f361e20656c8ffdb", "Name": "matter_g2_multiexp_71", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b7d06c16c77a57b5ed74d829ad6acd665e73d20f1d9285ebba67b60c0d1571e1b02cabe5dea44499ce6d40a83288aac0000000000000000000000000000000007e6ae768ee3d149c7130022f9883ed51e4fcf68c91327ac1fe85aec8083aa61a37e9afc25d3352e144aaf888f264ab20000000000000000000000000000000016f2423478e0388e8495a898c23d63a0929a2ee7cf848034e4c1adad3460c7070caf47631eb930678d3c85aaba044dae000000000000000000000000000000001587e63cdf50d6e0b6b3d7652ad0a0a2497e70259d73432831781641d3a76db4ac7cff1bef165fd8ba29200d7320e43475888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001181bebe3256dd6ed754b8a1d77ac07e9a341789b3f4a4988599c7c60a36f1e95d3e3cec52c54c0f0abe312ac358c28700000000000000000000000000000000189d224b2904bd45cd1e8fa72570a1e35c94680d03d30292892462664f9d7aca3cc45ecc0773e66a10248df28ba9a9a1000000000000000000000000000000000f654f4c8b02a891e14fccbd5a96228afaaf79ed8306c7c1267715bc934e5f2568ea06de2bcdc2a55ef708689d90108c000000000000000000000000000000000c0a413f16e1aab8b91a87e7027f067ffe7de65097da37d67f604a184c7e7a7af6fe59ced8c03fa32ab036862868b35018ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c495043000000000000000000000000000000000dc972d55b7e68f97191d988ae7be5f5301bce5c654b323d4c17bf6e070f7227c0789ee38af3ccc07b04f0793090c6130000000000000000000000000000000016288c405bb42b4e71d12fd0a798cfccc7d33aba0500f939f5fedbd0e071166169d3072befcc5549cc6963b6dacbef4100000000000000000000000000000000171ea4f6607d6efc875cd9cff203bc62eb83bdc05c07f702143c23ab2770f50f42738f748e6bb3bb5d6f51f40fea1d910000000000000000000000000000000000fb729cc9716bf2e9e30a598ee7c4281163b287422ab66b414da85b0b960102991c24cd023791e4241bda5b0f6ddd3424a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005720bcbc598c4eda697406dbb390c2aaf4bc22c794b4b664e9b85b7c2079b90f7678e69496a4a5cd3b46580b90a7a30000000000000000000000000000000001159788c3edf619cc5e6f77c4aeb4860764d46afac4cdce54cade63155040c631eed65c2fa11b9cdff14847950cddc2e000000000000000000000000000000000d61bf02587e2c61544ae8a98b4c742c26a3d6ca49c6ae1b19a9d69c7f8eca43cefd555c973145566f8332902217cec3000000000000000000000000000000000cc0da96623432a2c170f07a3aad2844c1c2aab9d1bb5d2183928c818e681c66cb3767be372be4ae65fa40bf5483258ce4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a1350000000000000000000000000000000016efffb5d4ecbd01567c1e6099c0f06644d4312c599579b4cb780fccc8a425f3d1263a5f0c957dda4508202a354162f600000000000000000000000000000000115686a37624ffa8272ec7dedb7a632ac568245918ed91be6c9116e0fde290c26b5291e5f57ba6a779750685b0f126ba000000000000000000000000000000001852662b92fb49b2f0359255db8a7a2d20bd37705b7994cef1eb8e318aed70fc37bb7af9fc0c32ab3efa8c0afad640570000000000000000000000000000000017a691c08724ccf0e668f2f4eeda692e9ac21385fea243dc62c37ca73421eaf51c3a60771da3fb3e3cb578de37d2d45d38573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000006fe4276e8f2e23127853eb929ee4e0c6ec4a190d46ac222dceb995c2e1d8fc9f822806a877e6cf87cf579cb2862c25c00000000000000000000000000000000044dc671bcd516cf03ad98ccc55266688856a4e4e5a59d6a6bb93e9ca77c596b5ecd2db6f3cc6377a0086c53ceed5487000000000000000000000000000000000c3ca83688d20519423b2b5547afcccbfaaa088a90523272c7cdc9a9b0397340350f2a5ced2a8153d69c81cd79732bce00000000000000000000000000000000069916c468f22bad174522d7fb94b4b7d2a664743b4689daa5423f470014152387a29425642b50f9e50fb679ddafdafa02257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc1630000000000000000000000000000000001fd452b8685b0806545e09783947551bc5f6446c9d15d079a8968a448a6fd6f8b7e91974b71a4b2c50954be299c4885000000000000000000000000000000000f28bdab0b0fd3e05d08ee2c51f1bc0d30380e3a7aa18d6e04b47018d6a8d2b35a8f06df3866ccb95ffbd9c5333ca94c00000000000000000000000000000000035f3aa1cff72df0bb10f1a6f8414aa2ad0289cd15f56d84061a7cc70562f1f12304c402c388e48dd3f34082aaf79eef00000000000000000000000000000000034730e3ad7a3373b97279a00dc7a053aadd088557e0da61b9aa132c5b402fd9aef73cc45dc1cb7f2076cb2ff27ae2fc76b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c80000000000000000000000000000000009c91d800cb1d52501520b3625dd4c20173684bad8742c7ac4b35c0ce028556b6529de5cb7541e3c146b13f58ccae57800000000000000000000000000000000124259d345bf2f8c16215be4b6b7922f4e2d6b32f91c4b1c4f1d4974918fa9e6fcf10e46f0c0b55e2a7210d1a5336eed00000000000000000000000000000000072e6231244ed14aa0f5de06e2f953371995a567684b00e459113380c1434a8faaab8b28a0280336ae35bf1f90f1d4d10000000000000000000000000000000010289a63e0e5f1f35b7af22137e117a85df27874ba15df39b7c26801c169667a3afe9a14663d7ac0c2956f4eb70cf11fc9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000d0f22a9bcda47ffcd034618c15daebad8160f9ab6b3148f1cacb32e713df2ef19f706f14131f8ab1181b7ef7598e3e4000000000000000000000000000000001680314cd79fec583c8bc0842e1750b1318f94aa7700c6662aabd4c592ca61ad51a6876b686ac3fe3f508cb40192c31c000000000000000000000000000000000a172bd8e49637fd9eb611b590c68bda707931e403db35cde1c10bb74c389ed725aab54dcd7048285352c56c8bc5fd920000000000000000000000000000000012589683ff3f85ecb006c5c435ca7bfd9d5a6fd06eb625bcbcb18577cdef610d912e783f3986c965710269b1ff79ba972467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000009720c2b3a0658a4aba8e76e196a558bd155ff550b3e41bb5b43e7c5946bad803b1de64e342956a11627e7f24f69fef7000000000000000000000000000000000decf2262e8369d6a2b1ce07fdd257abe1c7610084ae2f347640c0cdb98c7cfa732dc609c18b7b6a51b47ebe4b07a586000000000000000000000000000000000e8a0158702ff6d6c3a7ed9fbc774bc329681130840d86ca3f26cf6642cb49e5f14ad95fff1c94151457b1d5a142bb5900000000000000000000000000000000035ae66137629e95539e09ee99b001d5b9a6ede79727d7deedcbeb5acf081cd05ad469ab06c265a5224fd5236db160b62f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae000000000000000000000000000000000413d82d0b02ca706f0266051445c04f3ac594ad82e2f1fb4e8e0cf23a6c1087c29383238ad3677f170e99259e2fe93e00000000000000000000000000000000070af21f84895c0193f0b8174cb20b11f45c845a8d782b1f58182b149362e1368ba076ba702185fc54b5da94c3172f5500000000000000000000000000000000182e124ca29d66f9f6c370f6065f60928b6a8f445a74800d59209219add6cab0d1b79702c31d60e61cf56874a4eb6717000000000000000000000000000000000b94b733f76067a102cce9659292f31f3df2cf2770e3a83c1524536e29d0a84ea5c4883cb4e849830384dc7e157d8715474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000004b2feedd5badbbdff6fd0f33a4bee17b38cc8967fc72206246c349e1017ed0407fe08e0cd9208fa9e4e21eca4cfbc2a000000000000000000000000000000000df0d74d5cc17ea94457c0ee26ef24071700a0fd6bfc762e3ec69b8f1c096887f679e312f07cce8340686eb2716c9a96000000000000000000000000000000001878edbfff2efc5af64aa9a23589a52d63749b7ab2970f256874fe0cc15091c4511050b0a243d421dc6536f19b5977cb0000000000000000000000000000000015951da3b20494a266e4d014d0ec70fef4586c8656baf536a0ea9a48dfa041624e8154989a2fb106189217ca979ddbe8eaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec000000000000000000000000000000000657fdf40c829719db134acd6c2a9ff904681b1869f28512cbe2a64d93e5b62114a73bdc5260ad9a1f24a3ff191b7a3e0000000000000000000000000000000004e77bf63eb9c4741028dffd0591b4f525d533b455d35e51cd86c7884d63419a162b145752bde188d2a622251c087f870000000000000000000000000000000016cf02af01fa6750b4d862f0cdd5a87a79da7c3fbedb0fa356ef2e7419e25b3a2bc8cbfa97463d463d0ab349efaa3f2b000000000000000000000000000000000ea4468fe6a85d36ae990d0ba959ae050756805c4c769c829de475a2990ef1c46de20d5b466543978faae0f6045023e85158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000018d42a2df8ca475be6bdc468a82c313599239b974ec3d27e8b8c534aa4d6b85d4ee9aceb15c38b3bade2bb1706a2c2cc000000000000000000000000000000000124d5dc60527faf48f5e9574308f8a328b410de1cb49f2cc6f76b8a1f2707f2d1a94bcbca0a97bc38f24545a8013b250000000000000000000000000000000018b690b3d1e3b22946a91ace004e1d8f92eb5beb284eb05b52ac5ba003d7bc387540d33d088a02711522e3aef7f74f4300000000000000000000000000000000103080d8bb379d961da06bc4c148cb5b056ae115b3a0e33f0a8c99a7fb7b7ceda35d3902e0733156d354dd0240e4bcabd66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000f4d068354cb5b51e5a86163978386533f8f9b6e388c5e75f7d9ff5e1ab6d1637717d251f2b723b7d683e26a274d610c00000000000000000000000000000000001ec5a0d408c55f247d62ffef172ef26e45c41029f1d04e36f0dbb4fe8af414b0f7fe7ec0cfda66a2855b58592486fc0000000000000000000000000000000000cb1b68045076f457746621cd415d743701bf3ecae8d52dd5582c3e0bfb38e6cf2651a5ebdf521afb1ec5b8066444210000000000000000000000000000000010f5672f813470378fa806abdff90edeb0239b00d85ff23a3fc6798779f46d6b43071d66f7742897a4e53ebf6c7dae719acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df000000000000000000000000000000001422fbaf1bc2908be5900968af61ffa7b3af46e7250e4663ff321f42e2db057bcfb2106c433a9eef8fe20f7138b71d280000000000000000000000000000000002176e68cdb0ada2d7baea437bec8754ea293d14afb85a811f7a5d740d645a53e511b5605445b110174ceb5e6720e736000000000000000000000000000000000a69e992b6f4f7eaad2682cf9ac2e58faee9b3341e852543c2aafbff390ae067a641b2b5693319618fde413fdc64d6c10000000000000000000000000000000009440317af8f5c753b5de4648b06212256a39b7fb03678f1913b0a3d402a50e74e2da5d29c211cdf0b292c132759c36d0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8", "Expected": "000000000000000000000000000000000fa31d16d9625200c13a415fd61b7552646c62fb8db307e92c1ac3d2acc92336765a1db42407ab0f774ccf01291b9ee800000000000000000000000000000000156a77678873dcbe4832b9fc7f516eabc1a10f4a6576cfb15765cdf999a771a6a6d41021897dd783e9beb2db722a6fa2000000000000000000000000000000000ee4599a6ca9642cb4cf38f5f2af23271cc8c5bc6e2cf6bad572b618bff9f8677837104b93ca8942843fd5db5c30dcdf00000000000000000000000000000000138986714a4053618e66a85835d105f4aa2ef38ad18e34b2ee7ae30a4282f7e543c80c94bd12c244506e7fcba25f4c1b", "Name": "matter_g2_multiexp_72", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000083c515ef8509b12ab85ad7d0a816d986bcdefc14778efcb3bf7c2ab61991849f279ae6a9f5342880837c0d0f4a4eba700000000000000000000000000000000020cf5196b5d567fc429cb9ced7b55e4925e18c914caae216a736886a8d886c4bdf6d704bbd0ceebdc1975ef530c665a000000000000000000000000000000000f3d0a217c224434604d63cef559eed3864d2da62ac00d49fab8c2c6e22c688496adc30c8d591e21bc0be404b62083c20000000000000000000000000000000003d0bf7f25bab0bf2c768b44e10a6022650f7d5b7d568d502b9d0b28209ee69b1d952ed848572d3e966e8771c20becc4b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000cc4c4b7eb7e358d4133b65e635fc13b8a92229706a6dc5867171a60a99a8e343045a794c368f1133ae6cd2788c3a7db0000000000000000000000000000000019508aa39fda9c3efced287d2571db97045f8b7b0c7a9c9d51796aa8017fc0e5abb8fc994700dd5c9f755edb518e096600000000000000000000000000000000049f68b0ac142715cfb385161ee70e453f0e24e2e93f3f96c3d69447f3a28b180fe76989427b2e392c7ff939011e04ab0000000000000000000000000000000004903c0f8e0757dfd3f5edb4f54a0e292df15ff70757df7b0b04c99f590a3dd13c6ce7bbabf3e14daf9f3ec60e2379aafee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b23500000000000000000000000000000000041128064ac768664f076116247e0f8a00adaaa824cd6fff33bf524d0c76e61203408ac13b294aa41f5c462cd42d3cec0000000000000000000000000000000005e150c27979ff1cbe307511816be900648957624caed1f08d88347061cd783179c615258fcf3619bc4bfa53d2513c610000000000000000000000000000000009d2b3d97d29386b93d7af014ea8f1cfe2c1db5a9aa0c17e8430b0fcde974a4e7b8b42ef041e9a7b1a8aecb97cefb52e000000000000000000000000000000000d86096ebd88b2cdaf5cda1e9ca6b7f12ed5def629354b0570eb084bc7139cf20bb8ebe4438f87937b8b554e2201344c28728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb960000000000000000000000000000000018d677cd67e96b10b671d2ed9234d7708042ddfe6fb804d2e9371a80ad167004f9d6b92d26b3d3af34ab7caa0e03964e000000000000000000000000000000000e34a6c85187d328eb33c2d5b2ca96b5210d47a779ab810dcc380dcb7e6b3c334ac8fccd7354aa9108136e4f6dd4ea0a0000000000000000000000000000000000ab8f7274ee3fce1511c58661625c766ffb0ac68bdb835a948b09b7510bb573d49000000e3d3cea772bd71d79681e1800000000000000000000000000000000135ca42f2103905748a1c416d82170f7d24b49ff3f859d6cb7493cf89bbae0217529a9edc835be1f9890ce105877af630fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000007884edaacca499491580c8c7194c0d60ac6eba95f7a81f63742451c8ed21a223ca545d5cc1e648b9d2dd05016b4fea20000000000000000000000000000000014c78d5d1a93760096bf6da73bb41631e94d6a1b251ed0be7bda93e4c50568420bd4d49e4a46e5be4bb204cdb6b0ad5000000000000000000000000000000000128a860c23a183c5bdd18b4a1853cb53475f1a893420bdf3271cc4a65a827eba6b92e1f9e8ac0d10c73edec5160c640b000000000000000000000000000000000ac14b2170042ee6561c34f77fca40e1bd2d40d01798417dd954905135ed9b7772e5689e6d4e543d44a4563da8c3ca40c14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d0500000000000000000000000000000000144df2e76821c19167f60630f50c939b66867a82c2a5f807e943676c876aeaa2aef2126bef7fc431f0c7b39e648542fe0000000000000000000000000000000005e463627bb2d22c25520c27c05cdc75e1f2ee3b91e8088399ee42ad13ca217284596e5404b4370995f71fdbf1c1c7860000000000000000000000000000000012323010d6aba1bc6b1d6e7f7e8c7bbc0838564b279d5ae6279f7f7d3cb5d96273e27e7096e9a8540463ad16deb3780e0000000000000000000000000000000019102ac6bb33bd1c5a158a584ce32308b6ee5679dd6d2acdcfa4b9c54674fecad7489d1e39c05b1ded88e4ea93620724d81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c6000000000000000000000000000000000faf210330693663c8a1d1fef78e211ed2542f7ffeddca3e19be3ba77ef211da1b8bb5abcfc96b692d74f8c7df40b0ce00000000000000000000000000000000134153a252fd8ec5d9aec08ba09a94c4416f95ff6f4ccce59bd400474c836af5bfd941f03384ca4bd5c56fbe81d96ea2000000000000000000000000000000000b4532ff1ceab2a3a177cb83a75c16a833a2ff28df447def351134ec4fcd608b2b75b1f8035ba7d40a737087f3e8c1c100000000000000000000000000000000127e3ed13384b69819b34ef8705fe9a66dd01b275f1f74c2c724420546b39c70cb7a8295a6c1ec4075ead4e3312b8b603a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c000000000000000000000000000000001351d0d5d531a63a5f56aaf1d7906b7ad2bfb4e9d823e2659bed4e05e7edc9179a7bbf13405ab5cf410b25c7d476c342000000000000000000000000000000000f0ec96128e058e8bfb6e0df1331887245dee87c4f9721fc7f1d20c20a2feea7a7078a4946803ac093477707598d59b70000000000000000000000000000000009399034e4aed13cbf197d8c4753285effa72fc53493ca316db11b39d5527b009aec6350d579f9dee22cd6d4cabd88ad000000000000000000000000000000000002f41ed0dcfa2437cad7b12a94501266d670ed6956196c438241aeb90474d17214eec5d5217090d28892d95f4e40055af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000ae208978a751f8921c6067ebab4190ac8d3608dbdf50222eec59460095b8ab2abadd97616c240edd0a9c53dd006e38c000000000000000000000000000000000905224b317a1e64d8af075b6db9de46ca4481458ad6bceaf726ba0f63e81e2a0322e79e70a5a82034abf00d47fccc300000000000000000000000000000000007173c3359f0c2e315d11d646a76e6f500c0922401e4bf9f4ccf2f0801a567fa653f287fdbfb878ba0d9ee12e25396ef000000000000000000000000000000000161d4cc71621e5df13d121c77105af195c2adff5fc6b656b0fc1dd6eb2518f474444d8bc526ae16387f23a4ab3f342f6541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c60000000000000000000000000000000000266af9991c393d3b55f9e0f22b0967d47dbc5b0c97947125e220c4bf9f4bc58d32ebc7bfb02b2e329c933ce41d0d8c00000000000000000000000000000000004cf5748aae8dbc1e4778dc85da575de2b6d9d346f5dc5ccbfd82513166384111f5e5f2f1c2f7ae367a22146d1fac027000000000000000000000000000000000095dbe68521b2cf51283a8cfea1f20eb7ae37e6e945c5f879ba4834d20918b74981f9e0eff4543a79ff4eb36d84a9c60000000000000000000000000000000007953cad14379ffd4309cef1ed6a2dbb73a93db0bd3a256753402e525bb62b10aaf22b662bb2c704865690af995e7d284b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000010e99f318111baeb1b4611847fdaea7cbd5e3ae532af667ad2498fb2e97b1eee0297e2811c7ae854b882f616da7733fd000000000000000000000000000000000e56cea75b4c4e4c669a492a6723fd60e351a66dc5c34c46469dc36cb04d2c23cfd4aeaa23d0e9e83d5b78a1b77696ed0000000000000000000000000000000018f838d6a582a52a508cbd6bbbb9cf515e091deb7a640e141dea4018af6593c001dc43a8fe4819a7877d9ecf53d5752000000000000000000000000000000000119aaa2ebcdb6379f7ae972cb709990a3e8254f1025cef308281bf7057295e3099d1f3127f76bd2f9ce0a03ae0de8e8d7e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000008f837c478e874b857f1c939a26a02e13061d50728c10939ffcf5e862cb177993e204590699a28cabc7593056617d433000000000000000000000000000000000432d9e66dc78bb58ab98771e7e8b5fe51835f286b488e2df6c1991fd36c3c537f2ce30abf24f9d4fb13941189972e39000000000000000000000000000000000b202de3708984f44f7d05ccd9e574a2a93a285d5ca262017346580be273c58f13165437dc90d1d4103d3b9eaac536ce000000000000000000000000000000001873e1251d9ae9448de8e7ccb7ca59a21bcc0d07a2819d140c06ec33cbba559ba90647494a7ecdec8b609b58cf7995cbfde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000000084e07b6576c73aaf43c0ef9c5666dc988ed93d1a106b71e4882fc0cfb5e710b91e5d5eff57327f5678f662f4a451d50000000000000000000000000000000008a29751f1653236a48adb5fbc59059c7137d36139574c6af97314bfbcc22f77a4c5162092762a26b5da7887b94f2da6000000000000000000000000000000000a4fd84c4d58cb9e18aeee180fb05f07c3e1d7ed8d09940182e9b4738744fa6faf600b6f720441e0ad6391a4d502ac040000000000000000000000000000000018b356be2aebca82c54988ab2a2ec58751ce7a815f3dd58a2218a638753d4734d38b74ca0e00bbc8681768f5d1a02b646f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000de0f22cf05620a5d4bdcf50ae179f23a9c089fd6eaeb14eca937d9e2480f1782a1c67df76e06191a9b87514daa8bbce000000000000000000000000000000001981cd1f260e7d96e55533b8e29867f37af507b4a58abd69e0ad6af2a55228ab1c82fc2de52deb7b7b7deae2fe621e10000000000000000000000000000000000d22a7a567ec8826391ee711768e612c403e3c16e20947ca5861185c24728b6c7e7756debb333e7acb53d86032d5748900000000000000000000000000000000016fad52e1e86b9e092955cefdf93a10f30db896fb519fd2ca12571d8dc8aa352cf4f8092e0e973d0b0c66df78433251e2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000000484bb2452158bca93dfeeedb40745bc5d9a9ad49afa20e6c29fc9ed1a8fde33ce508cc252ddd05fc486f8ef78738ac0000000000000000000000000000000003c2d6ff6f292b0f0e505fdfdd2940e72bf8c2837da4ec9c74fb593fe3318a9b9a8592524bb5d40f6c38ad871ab7b6150000000000000000000000000000000015f888ae2722713e1b5b02803a5b48d53116c1a4bb1191c9da77ded8c6ab49f1620b0f7c7867957d84503cfd3dca1be7000000000000000000000000000000000fd96baa382cceadc252eaf000d47d8c1e2085e9f274dd9dbb571bf85bba612836e1da2453fd914135842e2750796b54762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001820de289f62058920ac3d4bc60da023ac29c431ee429a10066f305d2b1a333ffaa906404af977cfd3212b53e66726b500000000000000000000000000000000094e448db84421e25cd03be3867125cedc7f77f286f404524757f3c1a9cfa28ab6771293da490a4d75852f515dfe1a6700000000000000000000000000000000097dec124970bc63d8f62f9133157d412f5ad3fd5eebb444568cf0fe2825d6ef6577ad302842f35570c9977638c6a827000000000000000000000000000000000490bdaabf4db27dce906cfacf3160c0fe25959df4af89301cbe6eeb29f72e4c55bb467841ba7d0750a59a32fc8b03d0ffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843", "Expected": "00000000000000000000000000000000193358b283147ed5848559d4d1533734822b0248dd17b2effa80920a853b70e7fb683b08aad6ad4dbb91f964ad1b3bb6000000000000000000000000000000000649be60ba72734db4cc307a2fd8be57857f60660d0c496c0dad73794296552e17cb2eabb3537ce677edaac1c6997341000000000000000000000000000000000f91ce27345e86003c99d133eca50710c0722cb35af2ce442ebd74b46d659e0118be9bebf32111c258e4cb4ab795a2cf000000000000000000000000000000000d76ad65233522b1e079fcfef4dfa80f163682d7984d5062680a5dd4cbccd5044de4705013c6bce2140f7950032f90ec", "Name": "matter_g2_multiexp_73", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013fe4afb94d08ae311b7442de7291a11e733d8e555f2da6f72bf99da780a8f8d357cbf3d8959f6aeaca7bf3f5b5bd10500000000000000000000000000000000025af713b18cbdb5a960371c2dd0317f4bfd0182f4bfd6b88d588b56fadc1a0398412e7e0a786c326aca8779ae384243000000000000000000000000000000000581c277053c15df8eec05c34267f62e63faeefa2d124c2b4b84d2a739ce5484641ce955fbecb901d1e8ca816690189b0000000000000000000000000000000005355dd304b9b60498a3fb1f08e1ba0c98db327365ca9a0365a7f1e5cb56aec43b7fd2b4aa104eac7b1c30b6f53cd422be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000016b5b3a6fdeffe5b9a0244a333ada4444a2e03771f94433832a4617be696e467b4e88ed80b174809dde4242bbb51248b0000000000000000000000000000000003dee846c5b84f89734016e547c63c02e4be07dbbecc86f811e2d8d3245f91205bfc055882565371db532240da1a845900000000000000000000000000000000194d53bbfa962def4da2a9bc7129fb6242a3922fe26cc4e603528ff31393a31d03dfc3463704250ea2ffa973ad175153000000000000000000000000000000000333768faee332d7468119b9e0469bbc7bc98a482562ff2fd9aeb6d9c67daac9c3da1db41c9e12224a2eff2feee51778bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c7260000000000000000000000000000000001910ded86d79f9b043bb79cc4049e0652c13d0fb8db2f070d695124d7a42cc3a2238282fc8a424fcd8d9ecdab4bb6fad000000000000000000000000000000000dc8d6caf97416928d2d58466219f054c6f28f49b2bc04d8a80cd46a308bc95aaca3a8df1914ab0c7da341862fdf47400000000000000000000000000000000004380ca7b1f7ef96295589f78a1683a51bce4b2afe50bd6076ccf5d07d35e6cb2ec7f74fa35097b2c0b9fff3f4797c1100000000000000000000000000000000054f492d7442b1c0d1293277d95efe822faa7d8881b9afde20db58d6267e049b90d0c8828a6c12540f4ba1e7c9ace6d84f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d7284000000000000000000000000000000000bba4761eda87a304a80180c2447a1d5a52f743015ea7c728e70d6a5defe3139c80696f842da3f06586be8d506ca4bc90000000000000000000000000000000019ea930d5733f4a1ace9fa0139d412d65b2886b659770e388894592de0694d38876fcd86d14580f9b92518d5496fd44c0000000000000000000000000000000002bf5d9a36d641d1259c1b30397aeb071b88844c4cf17e3de0984129d7b4d67865157ee2f682e7cf9d968fc07ce43618000000000000000000000000000000000f9a4f29868654abafc7ba935aa22d3d010023ef5112683a037a6c69b9e89374b256b8e1329eb5ad306d9f2063c22c335d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd00000000000000000000000000000000004f194f21373f09f8cb4984169890ad3855e814a4768c84e9fc97dfc181c60114aae534a27d3eb225b2125131c754ee000000000000000000000000000000000e6f88880e9645e35806d193f5d16799d63e2f9edd8ae28df54d19875c61857b0a34819a70ba3e9c31f00b5826b0cdc200000000000000000000000000000000193293c6cfae9ae4b24519fb23469e2f8dc4eda8524ee0b00c7141587b07c8a26a29841d41cafbd24bfbea2034a9c18e0000000000000000000000000000000017433efadfe9873dea9a68177af3d5dec4a13dcf4a710422d52020d4d145e2523ec0b48acc533a1ac7068c08ae6aa28bfeeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000011dc003f7542f6822cb872117fa658638dee2a15429aaa9dd576a7e895bc0a2160bc120558a32aab9e646354233a1afd000000000000000000000000000000000fe9ed8ba572ef7d1176176a31fa92a5ff3dc38b0183ea1e22618e3b3214ee78c53074d4c60b5056901c6f046f8210070000000000000000000000000000000006ef1c20c3bd88bd6787598dcfca52da4e5e0e7c7643af983c709b916e71fd15475da30d763ddba0899b182cbc070ca20000000000000000000000000000000001a38a2e54a44ade572ecde076038f5244f266cd99532024a377829a64c20fb2cfe1633367c74b5990febb08e776bc34edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a60000000000000000000000000000000011ce7b2cba037e5f3ff19b36371d34e287eec807178dad4118c6d43aba68623e182aedbf911a2ae5cf3d0e690ec3ba790000000000000000000000000000000017a617453f391e6e2437d56ee831ba895084f60d1a5f342e19a242b9661c703219d90a157e1b55f005f5059c15c179dd000000000000000000000000000000000746ab134c7f4bc19583a4ea4991c7cec3f651a60582b40c17b2d18cf6e252d93d2f3c2a1a3399be70512ec9eab251de000000000000000000000000000000000698daf214f2de44ebfaa36379862bd9ffb40987dfc8e632f14738c93c8e5c3fc7be9fa9100fb5f7440311cab34fe1897a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000000e4ac65ce62180ac602ad68098ee31cb747886e95a183e4f819d54af99850d70496e6952076084dc7bc2d3f7a273383100000000000000000000000000000000182c718fc9e5cc961426258e82594a5cafc36270af0eb50646d161fcc192c30d40d06647e14a282421638b31f378de940000000000000000000000000000000002bf448ebd27cb6270e1b87087796ca6534ff51ba0962f3290ee1d06dc18ed39fb736ec95632b483f44d3a9d0e45d1d50000000000000000000000000000000018b956acc1300e60b22bb936b2b52e2ae82e256f15f1415263157965179855137715c321d3765c5227dacb63ba2d6225f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000f5372651ffb40bf853f6f8396a7c7483c401b89b67e098ea888fde8d19e7552a006a127af1f3311203434126ffad85800000000000000000000000000000000050d7e89b21c7484cc5831885422fe7aa8e898df85cf7a3a275370623eb9660611610cdb829d3935f0d0955e0ac97506000000000000000000000000000000000f83a3f79f1dd110bdb8521e18a64490d567210801d77fa3c0c6e5cbc7285840da325cab7ab08494c8d516511eb189dd000000000000000000000000000000000f72904131be66380c5a18af4857ada7c15e88572197e100de1cfcc9fdb4306e446f2f330fefcccb41b676f24e3e0bf88031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000009ba6bbf102d390638ceb9259205a1856def2b3a4b5209eb3e4e54074347f71b6c06b70764fe85c8dfc9074067b8d00d000000000000000000000000000000000339c30631229eabc1230240942bdbcfa6e18f23bfbf88b7b8a8fa92f18e35d2f7336f0b819e875ac643b43e6d931e68000000000000000000000000000000000600cfeda6033ff51c3bf9182d22abbfbeb6db46c0fbe15ba82e72fee483744ba5a57ab2eab6f35927b4ba6d2b150063000000000000000000000000000000001530bba4db8a60bb6b7a05f72dbcd23044011d75221d114b839aaa9535400874472f94c849597174322291b5cfec4974cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000c636ac98557e22897fd101dc6c54d87060f460b4cf2c5a88ea14641e2a8a9395492fc5a946eebbba36dbe38f6f5c0c60000000000000000000000000000000007fe3a557aa93f2e9aef4ffc55d39a9172475e6595fd57409df3a7fe3d11558c4d3dea3396ee62f61190add83b85813d0000000000000000000000000000000015b04e0daf4a10541623e7523ac5fbe57dfff9ac17afaf4293c493c1982f3395980ec63046cb1d424c6dec91899202c10000000000000000000000000000000019617b191e9e493751b0a02511a18757330bde56722a72a29a399ace983db7114f84795e2b70bc9d670cc0095220454ed983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000017472b8c1cb3ec528400649fe7c39e3908b16ed69b42d967e4d225b694544e8bc7ce5bec87019db5539f1de39dc6807a0000000000000000000000000000000012b1c4884c37037a94f84c15061df5ca6c05c5a35ad9b37e3ab8e8297c9000e715fd2bdc3f2b485e86c415bf656392a10000000000000000000000000000000002c21af2933029f04b344be76e18ce499def4a0671a97dd9b6a108d0fb23852fcdc56f882be0319978952ef04a207a6a0000000000000000000000000000000015eb31e80fb162d5fa392fada8d43648ef54d4f9ebcb0e9652dd501f55a8875a16a148d42e283ea8bb2c5a38bfcc8843a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a70000000000000000000000000000000015e68e011ed063a9fd9cc8a806d8e3561e4f449526ccb6e5ce983ebc4fc49d61d26dad7db64f56ad5ab0b54fbdb76e61000000000000000000000000000000001617d7387fedcdd772a34b267a44315212d21b798c0fe1e7a9ed3caafb678910d9c9c3bd1fff4a3c8e339d0c90a865b8000000000000000000000000000000000e2b3c9b9cc10f41c4c0129d34c62d526aea47c77ded91a5ca3afa0da1801bba81def3ca66a978ebb2d1f3227ea82a9700000000000000000000000000000000096b6caf7b6f29e91bea370f91c2576c188b08b95f9df6c7df995fc9879c11cdbe2af86809468d472fcac8a89716d1d87ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b80000000000000000000000000000000016daa86ec04f57c72395d96b6ea5d6ba7cf2d9d4a50eb90f7121545f17c1ee16216f4086481d91e59fc5ed8542baeb7e0000000000000000000000000000000017a783d60be67206241e0bcad20e371d86d47d88ba1293b73f32999b0a1646967e5d031a5b28517f035168d7c7d7927800000000000000000000000000000000058f24fbe4e9befd8abe364c961f0ca4d9083260234a939bf6103a3e8f10a8381a9e3d74af7c13f159e5c7dcf456df00000000000000000000000000000000000485c9448fe3a069eb024ec43aaf563a98da09c02c294da2a94a98a95430e25b062e8ff886fb5fca240fba1abf7cee60297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000001230577527a0fde2e8e66b8c4d17594bdab8be1339866819c8890c600b35889d1e3a749fe15fd8182001e30e6420ca6d000000000000000000000000000000000ce03cccfa87229fa8d560884d8c7963276d79ae9873a23d550b4555cc4bda35a242dd2e70cc730b70cdf898609b3d8400000000000000000000000000000000174aab1f142fbb7a45bcdffd64c2d38b99c8919baf9651aa430bcd39613d7565196c18f0f4ee6fe05f5c40ddbcd4a67a0000000000000000000000000000000011dd23f59ca2a033ee5dfa50afb0c7ddeaec6d4f50e1866cca3f061fa03594216f005bc65b2c97ed1109c305e16222671b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b640000000000000000000000000000000014e1cdf4f10b11f47c15d0b6b7dfccb6081d05d116c8149989cce4f1c53dfcd2d0b7443677b03d037710eba813f6f597000000000000000000000000000000000c8415c7d5508010e0db1878ca663d359525b290b2f02c61436e945145a7a4e1b3ff4e27ea1b2c8d3adbe737d8291b14000000000000000000000000000000000e424ece68003cbfaf65a54dba51e7b0942cc53b2fa9794b4deb6aef1dc1ba1719cba285f9a1a59e71a881eebffe2eb9000000000000000000000000000000001404f9a3146b7201b09c5fd678fdbf2111c48130e82cc95012e5aec1df7e64a3b3c727afee4f603e620925686e126c0f5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6", "Expected": "000000000000000000000000000000000e9f6bedba1f6e2a3ff33e0e4b18fbf8e77558bf42e89023df6338b03a648c591486c63c2ecc8ecbbce23b3ff9a7ae6e0000000000000000000000000000000013d2526d83b4495b5af645d5a1af7bd40bd0ebff125e0fa14f10d1c08511dc29643dcfbd25ca0bee5705a56b26c558730000000000000000000000000000000003fa442ab532094d47f1a9111c87deacb15d80ca6e76bfb5f9b9a209bfe196643351d778b0c6d6b274b4799f733abacf000000000000000000000000000000001278d51523d5d9aefc0d3783e745da54f74a88620f2161090a398defdebf82d13d5b5a21a5cd466352ab8685b034fa89", "Name": "matter_g2_multiexp_74", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a497e74635fde8caaa5c9dfe666b1b40732e58b93a72d39c8a60c1f4b262e1f18f62229a30fb8257bf895352ac4d249000000000000000000000000000000000c1b2fcbd7f78d85c73ae55f67110b575750bec353e55761de0ff09a9f8a2d916c336655d8f6a78dfbae13fded5a9c36000000000000000000000000000000000173893333d998dd32cc3e82fd7ad8ce77003192ad2bfa1b1d2b43f9466898313276b922f9fbd8e83e86b67acfd9ad780000000000000000000000000000000004ed01b702bbafc73dc1e6846bc944be297ff08d1dfef397603294c7fe11668cd0670d386a8fa0f0f02c52d47f54a11b34aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a6000000000000000000000000000000000b5ec74a2150dcf5ebe09f39234c4dfec623318889d92b0bc1f197a69650bc48d28a1112306be763176b691c6915dc7c00000000000000000000000000000000028db19af73ffdd0111dabf9c7d6879cc7389320a249f108b41be8b1d4c259d5889dbcbb48b30a288e26cd9926682d1900000000000000000000000000000000172fe526c62f9cae49e6d3284170e6339d5af256441590cae9507c61f987eb495d340500cb761896163cb8ec631434690000000000000000000000000000000013bbfcf9cd3167b47b48af5f5ed7e6d45a5fa38192756c9e140eb89a85c75602814f767c57108cfa2f726e71f31548f808ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de0000000000000000000000000000000000052d7a0f93142b36489cfa21d76c0eb96904a3ddd946a53b8a6730036d88d30336fd8aae3ab29ebf62a48c6e849ca66200000000000000000000000000000000198350abe8cc91bd675f26516d771422c128d5dc0af844c6c1af07bf04a1d3ad9654cbddf2de5b7828d1446c45e7828b00000000000000000000000000000000198f35692d5face8dda4b464ff48d650145242852fe189748783b1a2e48806294368ae0a99481bfe739fb4962f3b86a4000000000000000000000000000000000e3cf2e018a7e0acfee25bc3a82cb282cb377bbd72ce3044dd20e109d948f68720c27aea3d4663ee45b2de6f178a00ac58c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb41500000000000000000000000000000000150e6b03a3052d043da6514bf4ee09baf1a35b2a909473db33ea0bd4c6af7d7aee9a8366c1d08d2adc5998635eb0dfb0000000000000000000000000000000001370c2976b0d36fcb955e797087e6ccffc851d2450cd63833d6cbf52e1fccbbbbf9dc695ee45c7df01c2828051bcd79700000000000000000000000000000000048b5fad2fe0af7ccdf675328d8ff5e63b564d8436d04c55b23b6ab7d2aedbd25d614d1780963fcd03d569bed2085bae00000000000000000000000000000000141f94b4e7ba542707d0c3cb69f8dd79e499602952be2374cead840dc669c5ac57089c5fd60c44291703b872098fa2daae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c000000000000000000000000000000000eec51e0ddb8cf9914304e7766a7418e2854ca71367c1d2b3875c12b7dc5c7cc2fbc136037bb7ff72458027104ed3f270000000000000000000000000000000009fe5e8d1918f9b5865a8b97c2c2cfc8bd750a0ccbe2942070827a09d8e41ca795a86b2262b10462795f833c73e788ec000000000000000000000000000000000b95c9146f3f560ad880ca905b5f297e48905680b4613e91f393f72ddb042f6a6201628fb5f75fc23f2298cde66a6df5000000000000000000000000000000000a29a8fba7644ac96d77ee73a93dae23b03d81a57f6cd8cb4594b23571cc1f658f163081ae50d72e09c6513d1cd2c8bf72022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c012871000000000000000000000000000000001807dd8d2bb40a642fef693739b1df12fc787db0f031306f31970d0f59f0c97c0894afc34b9a9913726a20dcb7d5191200000000000000000000000000000000096fe8bb5e911c1ed9985ac08d864c7020367f4259a0d074973a26cc421a44e8034a7007f6d1639285cf8acb8b2d64a60000000000000000000000000000000014026d43eceb26b9ab5bdd4139d4f94349b273e43f27737f9ad26d23454cdb1d35ea793d21f057359d28328a82d5290b0000000000000000000000000000000003dda2a84bd1f92524a8ede9f5e81f0f64b41b24510f4e0b8146496a776d5b509968f188c12c2d66cf755e5000cddb3b800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc00000000000000000000000000000000011246ad07713d1916c662679ab757c053e33def437d7a976533f0ce80ff6ffc259489c26524ea96898c3747c4127539000000000000000000000000000000000acf66265811a57e47a4c98b40b12a37c6f439550b18215fcf856c167b7218397d7d559f852fb45077945a5074f460be0000000000000000000000000000000009badf2799f1c43a2e3859123aca91e894f86d6298a06a9127249100ba270f2bdc79cf511691bf2d7faa45ffa17490eb00000000000000000000000000000000069438b1d53efcc4277ea7b41cbd28a19f80b5380136f62121e766bd2845e13d5cb40b2f15d508414876ddde491a3830a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c01300000000000000000000000000000000112c4cc34da9e83207b5ea8a9251ac5f004546596f2294b3fd51b77ad8d8e98239d53ec4f527c7280801233175500b1b0000000000000000000000000000000011dd8627748c9a2b08524f88e560cd3944bfd1fa17e1d6e2e9cd025b04f2e3ed35125197136afa2848d24fb5fd19508900000000000000000000000000000000093219f9ffbfdaa60c5965b45a5d5bd923eb5d3971542ac147de3f591a5fbe31b30704a0061a524e2ddd05a45dfcb6a10000000000000000000000000000000006407dffb5580790e250a72dfe68a488431f61f45ec9df279217b8800f0ac1ab585d84e486487d5688735fe5aae75bacebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec73040500000000000000000000000000000000107ede23f8e4f273ac2647fc251008905966dde32339c023f1da3c4d35d483a55b54f4157a303e68e1dd7fa3f3b14c8d000000000000000000000000000000001739327f282812fbcbeccb12e40df049284562d8986b8d4559787e1d5247eb6c83d6b838d099f36d8d0e32da2a7999a10000000000000000000000000000000005e5b6b2baede3ceae776da5adf075c1d774e83d6129ccfe7e835862686bb4064b187cc0be0cbfed37e5cc039f3a3fb6000000000000000000000000000000000249554dcfa53f73ef8f08daabf20c55301f75c8ce095cd794061c55e195221602a54ba54260980bcdb35685e41d0f4ffa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a700000000000000000000000000000000048b7fc5a71787231f1c7ed2134be528fc8d8f77102bda806ccbadf4f9bed79ee94b43c0fd3e5b1d776fe73d786872d1000000000000000000000000000000000152a1f005a64e16949d7249c3b391d5c1e0ded4893d0ce926cc666f0f88b64e8dd6ec4f92ddda18127ec24cad7e40b40000000000000000000000000000000013a2e1e7958a53307adf3beb32a88b7c493df0e37e074c9105da3c09bbaa01fed092fce2b1800790c6e8af3d30ec5a81000000000000000000000000000000000e2d405806764c75122c1b5e410673b28759f26af7489cfa6f35c6c0dd16c508af045009853f3329cda4a67948232bcef0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf2110000000000000000000000000000000000a71d61dbb3ae37230a2dceb54061d5f8c1ce645e20ec39785c229cf79aefe238959b2745e3b50e4b3c20c7a8e2ae27f0000000000000000000000000000000010e82b8dd5faed6bbd5755c4e5a88edbb3511d3f4442d1e44b82cf72a6414bf6558d29e8907b07f71c00f537637605bb000000000000000000000000000000000d8c93f1984b742b5a02777b706970215c7d8eeeb7377cc26c3af9005648c2eaea7f7a3177b6e049b132ef6bb4b188da0000000000000000000000000000000000ff082a252082499d70eaeba6d5514fc8d641404b48b2ecb256eeb40d9c6b68ad5af58556c9dcfc5667621c549b8ee760d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f9000000000000000000000000000000000f4cf354b8de6dd2231448bb235af3c84daac2db49abed345da6ded50eae93982a4f2c27b07ce725a062b07fdd9058fe00000000000000000000000000000000076cf19408f0f0379c7e65a6675b9856782990986f5c6d7002e9c9c74b95ab875924bd7ad5e4812844f6d1f530e58deb0000000000000000000000000000000007acffe32f96f5e56557965e3db8dce87eb7140d93608cc003bf4a43fb261bb7360c576da0b7c4dccdbdd9cc53b5c5f8000000000000000000000000000000000eba1c668fd9323d42d6a82d9f075cec2d278cc57122e25ccd72cf8b5a569552cc6b0e9f88d23b9b7af18f3bfa0cc820006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d60000000000000000000000000000000006157cb6e2dfa2733d4c489ec0334f0303ff1ad410f329cb59f99a5fa3ed2cf84eb7d2f231078ba5db0954badb58425f0000000000000000000000000000000003dfee394f4c140e2cad61e8675b26f91244880d9a0b6798d6111090dc9d080563db5c89b7293dcaadc74ea5849a08aa0000000000000000000000000000000001aa1e0683014d5b6f99f469a0b7beefaf05a7ac0298bd1a3e2da409f6cf856f70bc067610fd705a851cd70054df9562000000000000000000000000000000001571b129f69f3a6717272ff75351fa053f46294f68ba3f859208d6c91ba5eb9a0f2133a5e139d04e38c7f7aa303451768f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be4000000000000000000000000000000000c4e84b7c8e46daea67c8090b27dc28b7867b89b92f56232bfd8ecd9968b865a057957292e79c6dc08162f9e91e6a4b2000000000000000000000000000000000b8d1eadcf3f1de6ee608a4a0ebb7defeeaf4e251bf07717a6a8e50c07223ca32a2ef290f26d0de14b1942e02acba39a000000000000000000000000000000000e901b546a4d3c68e4432f376c97f42ecf0724777956c4ffb1e6ca4fda562e57be788ecfa45ba3afadb439c2ea546ff30000000000000000000000000000000007ffe01da4fbda9fe5d47c3bedb4b92fdd71ad73fa272b071a7a7d1cdce7743a535da7dfe05a43d03368eb97fff54b2d861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000008da0fe413e31ca68f84032f23bdd5399e01eb3b5ae47033c6834a39645d7b5cc2ec937067b91ac6d83035a86fa841f9000000000000000000000000000000000b950b982323f747782d9065dddca5332940058a604829e31560a6bf9b03ec72b09cfb87a1cd244ec694c7cf192c37ac000000000000000000000000000000000f4afddd25eac15d2248c71d76c9aa27323f75141820efeef1ab4f5003141053f138d9a7d1a901961d0f2c210ade27ed000000000000000000000000000000000217b1800c53d53459b00b8e463df1882b2cbafe85043f08093a5414e58ea7fd4dd933c601acfd7c154d0e4ce187468a2dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be55000000000000000000000000000000000046aed1acd19201553bb6a88fd6a6c0525ed44822d2a4ed3bca48a0a2b75e76cfcdced8f342b81ce03ffa72e667b3bf0000000000000000000000000000000009a5adbac43cca3402db016a2138342fae89285ab1fa16d7acaa9c3ee2b4e3df2641f7392355996bef7b1578ce1ef119000000000000000000000000000000000c8ebbcbdf2ac3fbb553a2e589f4b7c259a1621b83b14fd1927f92d9f6cb27e82507d7943ff5930f0c14b9fc38c9857900000000000000000000000000000000105b729f678db31d04ceae0aa37f9cb0b0319c4da9a1a4702a11bfe3a5f2f1f2af09b9cbd5ded5a930e2e65f4279a31699893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23", "Expected": "000000000000000000000000000000000708e9b926f2536731b02b6b75305c549da58e312d9c53701a993624697af2f3469af34dd4634467f8c98a0f721cd9c00000000000000000000000000000000019185b84fc0511a048e3c39bc10334c91dc1052d323a31c8bf325479a2fa3e4228f8260c0e725c2b89d5a0319e6fbed70000000000000000000000000000000013c7c441d5cca81b48d43e908d6a3bf8b5057cf19e4884227cefa9b235103b46edbe01bada06bb9b620ebbd016d537630000000000000000000000000000000000431182c8a1eed66073956fe5798a894be396403c072e766cdc262b719d1779f960f4aebf61c1bcd4d005d3c7413e52", "Name": "matter_g2_multiexp_75", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000199f555aa5c651183f52470e36cde438422f41c9b2d1947510665254b74ba0bb9cdc6e6a1283b0c8f58d8f009eec46900000000000000000000000000000000018f1d8f22f43b4649300aa23ac92a2e8f17e7e3853b912bbc8e90588125c371084cb224c2d54dcecb4946ff6db53cd02000000000000000000000000000000000efed0bcc83a52f0faf9e260815da8d4e5286396081268485aab052a96af8eea0112be6cce1486b10b60551ad6c810780000000000000000000000000000000013a3b1ca3b9b7d50083c10d36997f5f521d4426af8d2905aa5d074ff37e218a0c96c74387485c2dae24c0842b7a74cf0d8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000efc5a5c506e94ad2754e235e2da866d9c46342f14d518f12510c93f13a619f6bfefec50c146d6d6170f190497eff229000000000000000000000000000000000fb91f34356005f38c9804250549554cfe67ce195d5e218e4e1b1a4fb904257bdb68d6dfb013e8e85fb5a4cbdbf0f21a000000000000000000000000000000000f09903db4c41fe3f11c6f0cdb7c31a131033e30f52cb66ba10c2e7da1ed8a225ef280d313630121701f9a490e8a0f5c0000000000000000000000000000000003484f7e8f7d67ce40b4cccef110bc255d91f61a4e1968a9ad37e25058eeaf39e9f1ff89c9b2e515388a7c1b49a84a2c33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f000000000000000000000000000000000444215c3d4a7d62201ea1b69890e2ab90b5f5c6ff56fdc9908634c7489e785521b8dcd7ed409cf09c585cae8414a3250000000000000000000000000000000002d70674251a0c9ba76b8bf3b70547da77cde5592da9204954abd6d8aec82799cc0fa4fcd42139357043fc867b3d0e0d0000000000000000000000000000000018c57fafbad2351a3da695f8b523443e8c763dd7ab875caaa6a494a498cc40b1c0d44488e2dc80d1f0bce00a2c90c67000000000000000000000000000000000125d5a87ee3f558b5e1e7664b0cb95c195bcebd5e43b930fb47d15eee4fd50b3fdd0a401c9bb011c326acc77645440137894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde480000000000000000000000000000000018790123ce8b3b72d626493a16936c47770a9b06ca45b17c6fa5c7759f088cf98de8ce7b3b5d6082e9e42b39acf76f79000000000000000000000000000000000fea86cad8b40f315d8378550f6d3d831149339a8e8dafa77295859ddd2417e8f5c0ae2baad25fcfe00de14f45a537170000000000000000000000000000000014ad78bb2bce966d52b1fe1a273bc07f2f24b354465edef6dbb1e0123c7c3d7550983b3793ff1c7db846e88eddbf33c4000000000000000000000000000000000c0daa6fba40ec59f6b34d413130df5d9137297d1b7b71b83114a6570fef8e7f83d6f5689527164782f92da4b1ea12e8fb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000001186b7c78952e5c32a9393eab07ad4532471595bc2c5d8137c61dd7fe6b6ca3aaba82dc205a559bdc15421a001b7270d0000000000000000000000000000000012d56b6fcec3d6511d2d723601cb8c9faabdcdd12efdd0e2bfd7c9292f2c3bd7f39c6e9aa53e6955727f88ad69c5b4f10000000000000000000000000000000006a5e56e4a42b04c03619c78232104f1f1f39e755058a19354eb230f2f09bf486b2586817aa6b88f27b884957ea0226600000000000000000000000000000000118c8521dd4866df907ecb252d9ce7a489f17d0f240d054a5dbff6c35895ef20b205236aa6e5be6f0825f9df87878ab783c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000092809d18926c20456857826491f55cec17803e9e7d43f22faf4da18ede3bda15e3319539017ab20ed1de2bff490a33f0000000000000000000000000000000018d736b967eca64234f4e0018e5d6c902608e265037d9b8ba42dcc923b84ac62599e153e1c7d00e552ecc5aac57d1a5d000000000000000000000000000000001804aee99219354d4a5c46328f0658a417c85c6bc89af6db29a4911c4b0cad5638fac5ca61cc997fef3450cfb4a6c666000000000000000000000000000000000bf99dc4a400adda5bc89762e9011dae8ada23b284e52e2d49f75f1c75247f6282c95a36f7a72f896ea308131215404bd2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b1000000000000000000000000000000000604f8bde85c0b26894e0de155cf896c911bca47533362a0b59ccdad0dd64108d33af8262d3ca2ca399306723f2482a8000000000000000000000000000000000ec10d3777aa54cd0cfd84b4062092ca3ac840a24e8e8aaad5f4c275e4d45091f838ae522efb1b2a0fa42229157297d300000000000000000000000000000000132cc70638d02186116773b31ec0e571a55c1cd78ec055fc647ab09cf4d3c543e0552d559b3daa4e99cef031e583e61500000000000000000000000000000000194a6a32a269692906b64feef9e4e8cd204e560b98db8c66380758d2123babae871273b4c571a1570a317c13a51d0fe9120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f0000000000000000000000000000000016d05912dfff44912bf34f242ac85eb55bbb8a21625d45496c76d057f518352528c6632d6e8adbbccdd5983d13c26953000000000000000000000000000000000b10aa1402c15fd601ce605ade8f25531ea8f95cf592bf4ed86c4a3aa847dc8aa2369655ce5348da30a897fa8d71ffd800000000000000000000000000000000183f5a2f40da0a0f4598c6b9ea7b99f8cda1d85cec0e6da5365d7eaad1e9a3167bd647e5e654985f395ea72257f61e5d0000000000000000000000000000000014e615e2d5072c1b536ffa607f3a826ce297800b0da329fff397b6327800ecdc879e91f1e3ebc26c18e188e1ca66bfd66af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a000000000000000000000000000000000f3cd275d72a637bcce855e2e20727c6e5a1f15bc8d799231d3a7f61311d4cd2f58cf38448675aee9910c1a3d0b576210000000000000000000000000000000019efca445312f568727948c803d06b8d4e2c5289015740f2626fedbc0047d344aead06ef521ff7e139312fa41d1c107200000000000000000000000000000000141384e1c9f79e38bbb0bc1025c079741b93f56e150df58cf9a61ec27c2877c4188866fa197242965e3feb47a78c68380000000000000000000000000000000010638286faa6c45cf028e8e3d200edcb348560e2e35902927391401b3155240b62a40784db88e02b874e128e3a2132b5f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b000000000000000000000000000000000c7fc4216767ed298206bc142862c138d78726e2d39afa18fe5732616c73a965d95cd2032d4b2f5a4d562be48ba6885a000000000000000000000000000000000928bbbd76b87f58ecc850e1aa4a2be11b15a81786aa7ca8cf0f6cc342db87b66c435f009f88ad97b747400fbcc651e10000000000000000000000000000000019f5ae9f06f2bc27a39bafacc7f3745fcdf8c78c9ae8a3c066ffd704aa4117eba773691ae43387b93e86d2e2de3688700000000000000000000000000000000014360a7ed73c05ef5fe651321f7e839c920bbc1896636143b88357cbf76e15da839bc7e1f1e629768d447c9d313cec8e23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f50000000000000000000000000000000000b9d955f9d28f9485d0bc4a961f0acbf09ee5fef38ccd81a2c73cf87a461ff1bf28d4dd1e0db3ea522299af67bff93b000000000000000000000000000000000889061e71866001b0760f68e20c7c0c033d782e6e6752f11502a0e8b6b70277a985dd13dd83424d1e5cdb9eb96a01c0000000000000000000000000000000000e05a26686667f44de2bef53c36c82f1fdda13dd3f7f8fe1fb026273dc4dfad18241d732ccb757e2b46ed8317dc69fad00000000000000000000000000000000038b55685b02231905dd9a62a709c0f015cf5650b3fa469462b3e9d06e3af8092d998c8e08ee61db1fd5583b0809a38996c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f0000000000000000000000000000000006649a8eabb25fb7793344a0b29325a88294343f6c69612ee9d9002154a49791f6cd7b37b2bec69fa8ce11722e9f8a03000000000000000000000000000000000e10f2f3de16fce9b9817085f0130e1839d9aae949170ec16834732a9b12f589a2b00f17d2fd3416ddd020b7421ca20500000000000000000000000000000000016b51112b3c7c42a8c2a0fa7f286ec05cd07b6cea5675bf1132de99cf42b450b3c2a8f02ec821529a14a2a0fac3a751000000000000000000000000000000000f471ec8b65bde22e003500d1d422dd0d163abb424dd261fac588333755cc5124acde328085d8df852c61e024155564781c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c000000000000000000000000000000000ec162d22b6516c309efb6a4577c5631a5807bebddc5fd1be5446e4a64785d49eed80eba2e89cfefe484ecb8d50440a600000000000000000000000000000000070c252caf6c56018af6b281b829a4fb8dbab850ba0446d233dcd4d87bebac00e3e5070bd41898dd561526498b153199000000000000000000000000000000000a0d76d1205c1f520d82c85bac4473ea7cf5f68022d95b1f04d06062197973001234d86921e70a94e478eea85264f14a0000000000000000000000000000000014c6a07f0d568f2103ccf8f61278e916458820bcb61fd91479b0dee874fe36c063a34bcb14ee434b68681d297637b5bfe44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d3000000000000000000000000000000000aabac129385d145243c3a1f357ccc963ff14867ad039827488128ac639dc62fba82ace66f889b47d8eac39802bc1af900000000000000000000000000000000062bbbe8c72cd6f8626484bac159b7e28c6c8c3261edc6a05a30c308cc9e56db17eb58f62ab755f04a5c87e58c04c7550000000000000000000000000000000011a4a439d18501142350229778f67bbe0c9b948229dcecf70a8b09d1df6c54801a111c603301da2377d4198d09dd51e70000000000000000000000000000000017de3d9bc6fc5f415d04ecec013a635fa200699c496f4d0bdb5cea7d446274dddd0a7f6b06058fde43fc4f1457361558ae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d98000000000000000000000000000000000c86590a02fb5c9568af4e69611f09980cb5a7e040c94ecdbe64e40005783fd3305a5657a5c6bebca7d20ee123a872b4000000000000000000000000000000000bc873a9bc694171d2606f4efa409897e03198a61b1bb16ae90f0d12345d2650d93c46e0c22b717e2f0504b8983515990000000000000000000000000000000001df9160ac3bc54c0121a9c69e9065f4266202f755c961bcb8641d13720b82ebd73eb3804ba44769fb2d75144442f1c400000000000000000000000000000000045e9c8ed2fe1e5c9a2a5bda75dd60f6bb5dcd0a805f68c1f662a5960b025ff29c8e21857d2a61bcd65c747d2a2da8ef6a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e000000000000000000000000000000000f94df8d267339bb4f51b21014ca6d685f7657d0f0bca189e53cf19e0e5e05bfad773c0553daafd80c86f302b1907ba5000000000000000000000000000000000d92905addc028a1dfdad50e909c77662e10e4689e7c8a4a0174a3e1c746b361665b65e17fce02b6c067a5b8d7a6a6f500000000000000000000000000000000183444f0665790c48bd3c07545115a11f82463a092774234e7b33aac1094761f213235895e5e61ac1b0a15603bffe2140000000000000000000000000000000003cc2cbbf181fb023a5f6088d8a9793b17984b3dddc8c3ef1a9f82f8f436002610df60b2d35be212da9945bc8108c0bced3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5", "Expected": "0000000000000000000000000000000011f85691799cb76213068ef4f997af66c349bf707295b969d85fe637d4eabf54f3f29e739152aba5027c1b55317a27210000000000000000000000000000000019627f9570f07f44f326b5b3ee19bc477e92d813be2865e00da93135645e02e6fe5507ac4d50085b02149667794609fd0000000000000000000000000000000018fdc97bf0f88b2348b436d70ac4e28b5ee5ba21e21e94808b8b9e401c0c7d688974fe203ebda0b23abe38018876f4930000000000000000000000000000000019e28c9c936ea5a0b3b41871c3afaaabd53a93902e44a96dcb7651bce7e6143d81cb695fea8b94aa32c09ec030dd9ac4", "Name": "matter_g2_multiexp_76", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000703481cf48efe78fe8dad34184edd1765a1d01846de74a45b43d4721bf1af116c229f969868b0e6e851f22bdfb0451300000000000000000000000000000000063d316d495b1e82380c5b73bd61ce7f2159e7714c50e374e8a91dd56731dbe03a3378bf8afeccaba5fda73b4c2dd166000000000000000000000000000000001012cb2f6578065c93aeb673f447ce95fb42927ef9d12e07968ec04b6a604d785944620043dee5de4de33d59e67d64f20000000000000000000000000000000018cc7cfc360801ecc420d77ee171fb3eac3be0cf26b3f36a6cfb7c6adae7bb74c18071daed8fc56b8fa639ea138267928c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd10000000000000000000000000000000011e0dfc437a65c6fe37bb9e554b5138f68a3c52816807bdf7d98f13cfaf86b37e9669f4e0db1b7865d910a309f16cc200000000000000000000000000000000006f2323e01591a7db1d3c7fa1a2ce4540cbe0396cc55baa3a3e13650a6f6b926a7cde0eebb45d359edd52137152fe360000000000000000000000000000000000066bfec8df4ab5f5f5eb369b34e8e22fe32abfc00ac58b68f2d3841248fe5843d6d29ad012249fb9ee851e40b940dc2000000000000000000000000000000000f4ea977d9249bc05dafb682a863ed17f7fba0a06c4a13cdf5a836748664183272eed96bc4109bc5beff61c5469e221f8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000fa3e3e77112774fd6d6b560ff88cc92ef8d009675d0ed65705398ce727cfe786684da50bcdfaffae97d19bdaddd81c00000000000000000000000000000000019e98284b8b9f53faf3b73902cc322dd80fc330dcaff2a7fceb55db6a4b0f7f667297f5e4650c797ee337985dc6b54310000000000000000000000000000000004e30acf2ba66d842575c8679caec607fd090f0aa2350464f3b6eef22e2b9a1d9d5fabb0f3909f1c19f6b8f27c53b040000000000000000000000000000000000ad76b86e32f84ad74bac68909da0c271571606e071b13bd92e387a8a16a1c4002c5a5e94ecaa1e8d2d6e051e19a45c763016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc100000000000000000000000000000000019bd07479b234bba974ca2f39b317d5f4be33afef66c1d69e53c44cb5e44c679775ba141f82486424110d186561777f00000000000000000000000000000000130002de0d453abe9052a5f70a9d55de74939d1c8e6ad5871a669a867861b1359322eb98539f4a21597d806aeca62d18000000000000000000000000000000000b2f0c649fdb37216c10762f510c3bb4c789dbd29c4f9a8ff39f74ed1a96609c60473a50f5ce3f6535e4af0f2f0a150c000000000000000000000000000000000893b9af710787361a32fbd19c380161c9a214a1bcf3761563424b8546f6068ba650d9caff3e42be63ebf4b6afa2de516c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000019805c0de5e232632228e2772dc79712e3d863bd6fe56932b29ee99870d2ce5eaf90c73632d1dcddc093e9b6b5b0f1d000000000000000000000000000000000405d77f4b3c44f99a956ef375879e62df033aa408127e0fee013b74675a8c7d999c6abd30f459693086bfdb326d67af00000000000000000000000000000000110f2c231998aca3d76e40055a05feb37eba76cdd10106719f2300f57906424d7eb6d9f85115b78b7371ee60e26d02b5000000000000000000000000000000000593a4721a67caa7cbbe1566611a1d48532c68adcdbb67f362c9ec21e08aaddf6b5e09a9a96df9a89bc25f11665f3a36aaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d7100000000000000000000000000000000151758f1921743d116f1c4adfc09cb68b3ff911329e2f6d6bcd04beb9c109568c796f328e1f04381a995fe89aebbc49c000000000000000000000000000000001388c73b1db46bdbe70540c99db46b730e157a23afea97648d73f9d5f7e8b073ed665eed9e9e2500152c87715f1c4d4c000000000000000000000000000000000284ad228867ed14ade5a327ed951ca50c87f0a669e59b7a75d17feb54bc5d685245448a912590179db1e84f1eed1e5b0000000000000000000000000000000017d3da7c167733dd88f1c39315e47cc80c3310cc431989d4cc50ddb22e9fa481c5dc02d94dbf806c4c8da16ba5b24905f6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd490000000000000000000000000000000016064871cb68f748939a839800afbb018fd5836914a2b76c51818e764628a76817c7ea329e6b2f9de653c8162a2a2e0c00000000000000000000000000000000082fa03cda4c617a780caaecd7c859c5251b56b61f70fb3ea8c05b4c11c030adb8a96d715c1325ef3dce9b20e8065b6700000000000000000000000000000000174a245baedb7e1bf1368212620b850151be41ebb00c977d85da499223c207ab6f1a1d94a51aa9e90d07764ec3615b3a000000000000000000000000000000000df5b81cf4b008480775ff3d7644f546a60382e92a98b03deaa4a20f831e69e14a893ffa731c4ae9ee237d747149a9080cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c64700000000000000000000000000000000019d36b8dae5e1083e687743f7494b7f9dd0923024df81e2f83c78743e227ffce588a16630201b9909daa6c9207b5f430000000000000000000000000000000015659059cfee7850e1cf0e49abeef2fe5837cd128742e62de20dc734f1bba343aee1c9f1a59d920a0519995561891fdc00000000000000000000000000000000102b7221257c40d9adabd0db3ec9f6348487187ea1110773fcb2ac5ce210dfed167a4d15e605e9d9e666fd092147a1c7000000000000000000000000000000001402ff9770d27d2d82efa6abe4a181e3c1d944e97a06f670d9e46b24f9900fb4a838b32e17482f25be9b6f3240870c02ea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000001745ea52686f87a39fa42ddb5b0f69368db3757394fa7a1a93eb20c398c26415c8a7edeec7334df5b15345d6174126b0000000000000000000000000000000012b580e6fd228f087c7584cd95826e56d1c074cf16c35286c45d2067a362529d241c1e24fd22cc9727d423551de1a1f700000000000000000000000000000000104b46c42a706c61610f8c0434894c7cb9ef878cd0234f8aec0825cbb8297bed3de349e7f6037dd19a159103ca7753390000000000000000000000000000000010b781b3cbe6f415af15e37be7c60dc6703e6e79618cb3d8d9a5ea3b17c00822aef1eddacad66a646c009dac887bb070caf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea30000000000000000000000000000000004b172c360fca555e65860c7a294960f506b562e012ddebad5803bc3f4b93159c16cedb73f339def9cd1beaa0912c93c000000000000000000000000000000000242e37775a042ccf59e99da667c67fc49e80e54a1b438a74fe306d668059ab4dc7d9e457adb45e1f91b3e6bef0a130f00000000000000000000000000000000186eb83ce3abe66b8760dcc0d375eb783d175b0b2f36cc08793d8a86cf76b7618b826f50c6b02ed586394abe4efec2f1000000000000000000000000000000000bf780324df1cc5de325a796f1fde367eb52dac76c0632915dfcaf01f5acd6ae890dbfc2e505bafeba7fed8fd63018c2ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc000000000000000000000000000000001231b52c8a081add6e5c250caeb9467335933c2ed66826e4ab44561eda9259acf926f22ad0df8e8756aa51279d12bc9600000000000000000000000000000000051c46bb04d3e035d324de681c772e4561cecc6a5bc4ef0a0cea56618e09b3f39f5085e208229e50164bcdcd4abdefd2000000000000000000000000000000000ad7ee610398935a02c3a7139185409d7fd4681ebb74a239e15d1c092ea913016d3f585d8224cb1d109ac111660a94aa000000000000000000000000000000000903bb16efb052b99e9c46f3478b4acf800a173b35b0079d7728fc25c9415c8b05ad520f31e6a3c867245f64355cbc080d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001788efb21597aaac29b7bcb9ad6cecb89267c757cfcd8893c32fb13c0f3e1af7fcccb9573dcffe8d9220292b7861cac90000000000000000000000000000000015f85d3686148ad62d7fecb71920981117cb8759ab249d0ceb45f9e4687914536a1eb16ccd0e185d1352a8d2b4a8ee7a0000000000000000000000000000000015d8ed94c0415ee0f7c9854841bac5821253bb2ed4d86a61f494cbfbd61614983e4279fb17802ca68aba4a0302ec1d8a000000000000000000000000000000000f950a4c8aa18f4605e1252c367dba1e170ad00376a8560c2fccfa7d5487b0d1d5885cec16a0a17d81b5a584d473853f7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b82000000000000000000000000000000000da25ed9154121205ab6843f603a38a6892887d2725f16ff87a5218586c6139188f46da5a42b5e05982468e8115713ce0000000000000000000000000000000013c13ffbed4a60bcb8659013b022012ef3a4400f506d65aff7ffb1bd5a9a5e030a298e417cc1ec8ee7ebc06455dbe61b00000000000000000000000000000000132d83bd141c434326d4772de7f8772c30a6456de7adee7de66a04bece4c0d20bae5526c8eca5af5ef2eebd72c90d54d00000000000000000000000000000000131355c5e359081dc86e0b15c8aedb4f2016b41e8428051f5132258eaf4392fdb63a91452dc56aca20b7ad3263ebc8c92a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab7190000000000000000000000000000000012dbe1327162e4176b4988cec23df0c1b0075d0dc51ea8afbbf98f00891511d9023cf7538c5705d59b6d6ddcc90b101d00000000000000000000000000000000036c12c7f7627b6d6fcba9a303248c38d784a3d1d0ff02e550565efbab68c5116e9a88faaaf09bc72bcc3358e9dad0ee000000000000000000000000000000001578ffb68cf12dc9a5ae6fb5d822324cec9e3f576ce08d45e24fec9203d36a6461c5b8ea6ac50233e8893b07ea6e71e00000000000000000000000000000000015cdb43c82b20b8ab270b942b9e625ada9283962a7ce95eae156aa4355e1123ff87ddb1cc85b2a94bf36102ccbec33fb2bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da9600000000000000000000000000000000017041e16975850e6445c7b4896955eb5eab383ad3c3031aef04e8fdfb65a6d52c9e647330bfbb0f0eab630c9f9ef7a12000000000000000000000000000000000b62757ccfb913ac4264692053f766e142697f598a3fe26e998119b63a3abc7fee03db32a8af36aa21181fe9ea89d12c0000000000000000000000000000000006bbb842a889d7ff3c1eb5e0b16e3a921a11d28a251c488a8a17a29edd93672fd15974a7e972a34c47283c583cf2d29b000000000000000000000000000000000e94e685fb1751f8720b8af79aec7b245ae8daa195f11f485f2c0c5dd68cf39eef848a402ce2342a6b3398cc7879c6010f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed00000000000000000000000000000000100d3fee47ae6c8c7981c8cc615870924fbcb34c2ed817d6862e2e6d0b4612222a4c8332c7d51b58ec59df6832139e1d0000000000000000000000000000000017270fa71c34ec84043ef64c5dfd61614b5b3bd99204f9f70994d71498219818a5f16843c67c668b06aa5ad3a6ba8a0a00000000000000000000000000000000057948c0ebd14664bf33fb282e200fa0e641764a353e8347586465dab0c79ca2caffbdc2c6d60b2d7c8cb6b088bd16fc0000000000000000000000000000000012747eb070f2de18f517648395109bc08b4af3f04d98e23eb6b516199b4eefc5df7d57baec736987139c7b03b573941f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e", "Expected": "00000000000000000000000000000000128c6c0283ea35c10330502d6aa849a909df6b8dd927a24f08072135b8e21e40c495c42e742160363772569189d73ef40000000000000000000000000000000016d78dba1e0feeab46f8cd38681a9c2f6490ecc3a6e79b31caead256611d133090a4eaed9691a87b66dd1c2ee50d5f470000000000000000000000000000000016de93e176c950975abcbc692384996315a98065db6d6a6214472e5a338e291b36abbcdea1b8be6162fe578acd669abf000000000000000000000000000000000d7155e239e9b15ab64a538b0a0bd53936df4ebdc3ec9b0b1d494e4df780bd014425759e9743c9b262cf48cda01e945a", "Name": "matter_g2_multiexp_77", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a653e0c24eee1cdf8e3652809de0cd159f2c541981a4f43936e7d41c0f97ffe2f1e1e0d1032f0970023f1d27241a16a0000000000000000000000000000000012d1d8d2f96db0e5f97be096c961e3b90ef3d88492fb756894979d2e8104791a5b9a43888043ce9e543691f15d2fdb650000000000000000000000000000000006ffb94dc3c2d07830498260ebe4641b2cb64df61cebfffaf2d4ab5b6ba92cd75de209e8d7915ee744c4db5352ff239d0000000000000000000000000000000011f25722cf9db77ef8adb9caa250175e12412e6350b494395a86c31e1f5dee6c89cc6603f1dfd08a70344cdc44aa0c2df3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d80000000000000000000000000000000006177a74e3551770e7d906222590108bae7b97a5dd3bdd2344fc12e7005f2c1a188ab9dffe68f5ffb0cc36294106f15800000000000000000000000000000000041b140c46868767119a6ebb58562570732198854c92bcc070f2a8d9be91282a70c5ab99e75cc9e5064ed628aa5c59de000000000000000000000000000000000f318ee33fccf455e46add44922bb6e99afd4354bbc79d7550f8d12d3de4f75e5ddf4e62624b116f91aaa80a148adaf9000000000000000000000000000000000fe012bf88e152eb62c0c906dccba469abe591687573a59d3debe747b7d895e4b0755f16e67fa9193a2fd338c04d243a4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000b26c6e0106d4efbacf2dd0d15df17209b1306f388f493c096429c031bc4a6a535b64cb02b400433f948fd6004df2fa200000000000000000000000000000000061853cf1a32fdf4c370cd413754ea584d3722a08d58575075a7371e57a7bdef95386ed72f91c4893377f6b551dd6b1d000000000000000000000000000000000ebf17e60718c8563a1029ba035dbbba75e7191b4339d5d33f64bb35f34866081f26f4815e01b02e8330e7b7e9c428cd0000000000000000000000000000000008ce40f92efb5c5be48c814018fbbe45f1be45f5b607a6600cecd50d8f791de7d91939ab61204c2a1337c3f21b2c9d26518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af0700000000000000000000000000000000123ef52cc44f36326b33234ab3348893bc722bac3674e43385b201f372fe4ea3569d69d4d561e26f8ea903e017d7376a0000000000000000000000000000000005b1707ef61ff9acb9e8b4dd6922daaaa2d8a7558cb55b1b9b96eb6d57c23f50a7955763c9b5ef04f52b09be8d55f4b50000000000000000000000000000000015b6e35d14da61e7a7fcbcb0dddaf0071d8d2d89f7179f44851947a2b9b0535d6fa86b5cae9713a73bbed909a4c6deaa0000000000000000000000000000000013463e135b1fd460cf042dcd0226e229d60cc2beccd8a1832df241e65a644159722a14297c0033eb499e5890f0caff1e5561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c386551000000000000000000000000000000000fbdf4a533d355e232723fbc97352fc5d7d3d199934883a61a9ea116830bdf9e40d423256225d9a3458134332ef6e817000000000000000000000000000000001195f0ad227941c5e383c48f546be34762d158e6cee585650b6ee987f7b98e802f678abac6646832b30b6e12e90948cb000000000000000000000000000000001820d5fbb5a62140c6e8cd105a70fc2f1ed84e254c839deadae5eadbb75e1c33a07ad12ee92900f55478e91958a3147a0000000000000000000000000000000013849bdcae33fad27f16e91c6d46b9678a00491e3d70a8db905db4b1d2c6f02a29392b5b77c1472052d6f4d49f14a16737c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e2600000000000000000000000000000000188fe1e394b567d71099fa13b5c8a5891636d83b6b8a08f410b080658a0663deaae4dca1afe8b9023b5e8e573c752c92000000000000000000000000000000000f66c65dab8e1b2912fd5285a4c87821888532f5107075cdfedacc4d7f75c6a74b4828d0b4c3a2c0ed94576654a7047d0000000000000000000000000000000016af44a6df79c8c9b6f1d8aeca24e024c454d7b94c9ed386858dd35c4158cddcad1207f9fc3ac9e3b748c2314f875dac000000000000000000000000000000000315e5e4f78e9fcb93aac78025e95b8bf82ce4c840cf565e0a868b0aac22950d62f7becbf8039a16ca3ea66a7498327d981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de4000000000000000000000000000000000f9a61dd1b3034b8cd7408b0a44c8d02f4fe0e87778d5d34f5e884ccc9e2d51eca6b6060b46b66843e8247b3c794e19d0000000000000000000000000000000005c47fa7799a0fffcafbbe4694dfe8d0f47b60f712d6319e9a56ac459a636460e700e2af80f9c688208978aec7c413af000000000000000000000000000000000ab1c55fe2207865ecf12e372a341c776d24c08dba10702fce1cd2c01eda314852d81d0ccf1c3423c2a12e8960677f060000000000000000000000000000000014f8a1964aa3240d788ea40bb51abc50fae2736a34120ca9585fb2d5bba4e5cfa201c83be1e00ecd1c46fcb2ebb4eb809913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d30000000000000000000000000000000006441fcaf5e68b10e7e511a95e56b9613453ec6468bb126c5eb12f204c9681c69b5c296320f92a6fbb0b848f8ab5fcd1000000000000000000000000000000000141de16aeca0a2f991e9fca4b6ce8fbab3d66ee3ee4dffb0124384a7d4ba51864a53e005fd34516c92ecab33165944a0000000000000000000000000000000008543656b5495bdb726109cd98fa18e405648fa88cbe2e5fea5380b7d0ecb207f0343dc7888b9945e55156977336226b000000000000000000000000000000000b53d4e392f304225b1ef363a3528daca1d3a6ad64ee99d58491863ea432a29cde5edd4f390de45a567cf32112ca5929188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe0000000000000000000000000000000002e0c55a43078df575efb2c99b27c5632dd1c08bf28b6c0558081a78de58e4258d1b57d94ec6fa157add04aee06e7b6e0000000000000000000000000000000006d3f4f0791431a56fb386f4bb8e6744cd19b10bd0f2e65e927371ab488d3735e3b83400ddb25ef9d740a8620821b0ab0000000000000000000000000000000011e9cdfec8a8f8eba0de6809485911711149ca0ebd0cecc033e2e5ddfc195fa7de671a686edd2f56e5f7da7328dfbec000000000000000000000000000000000171f188afd5d9568cc5648aefb65cd715c0293344b9aceac1031f10b4a1e4b9fa2ab11114bd58f28aaa58c10ee0eeac65525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a000000000000000000000000000000001651d9bddf61e5e54f86609c2479513ae84b000ad7defd840d9619a8361922dde81c999d0e95d8a3044c46fe0360c2030000000000000000000000000000000014a68c248808e826a3bb50f3c1c1438483cbb9da8dd67a0c9633a47f733e6aa7deb4a13aaebcd50de6e8e8f00000424a0000000000000000000000000000000010c8a94b9e0ec9965f6c8bd0c4279102ab682a14fc3c22e9640d68f240ccecfead9a2c6e69f7c8ed369cce7e2da50d5000000000000000000000000000000000181493e8137fcfae203e1b45189fb828dc9eb56887c89aaf9aad0380fffada423f0ab48ed068ba4e67a2b01a16abbfe55ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc57000000000000000000000000000000000e3e33fa4d85a35e8707419ca6d4fb6a61ee6b07ce152adfbaf6b5f1d7ccc253b59f91e4545848b3570bfaa804ad9767000000000000000000000000000000000c923a4de074dce3ccc94698bf6445af5847c0e6f22f225c589f744ec83ed0810913af2a6d04bd55200ffc738b31b01200000000000000000000000000000000186961ed1c6039476eb6f13bf1b5f6627b3b017ece57a4a5f33db8ef12347fd507398a421932d3d2a1d009f65d06e42c0000000000000000000000000000000011e10ae0139f95a2f1144810894fb98f6e5e86ce67877b949a2a7134c446dfe53c23dfbfd12919b24975f26eafa249216ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb942800000000000000000000000000000000029265ecf3c81aab289c98d9cdb917749ceef56e2e4d59de2d6c83907f394ddd1cce9d093a20206c2c1c215493c41c49000000000000000000000000000000000986ad139381e4dbabd6beba179600e1c782f436f84a7bd58cdd96a22269f1d937f88f25059214fe2a781ac519aa621d0000000000000000000000000000000019e296d5b17f78b3ffbdaa2ef5228fa9dd65abdf6b2c5b0f99a708c4721797b3b156b8df98a5a879f17f095548555da7000000000000000000000000000000000349677d4719445d5525cd65e2338463d232eb75721ca51c48fe52d0fbd299ddbd6cbc12546f056bf212d5700c3c4100854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000016f5d5eb3fc3ff178843a7d21d3dd628bda120321ae44206d88f07ac001651428e0da95d3f0676e1bbb969a300406ce000000000000000000000000000000000029121c539ef1d7b9888497a362fda2f8402adf10a1bee11b53cf3dfcc6f99d5026bc386f86a2eecd0c276494878104f000000000000000000000000000000001320a402922f2a0bb287464854be6782046dd9dae4c0cd94efcb8ad8e0f37b7889bc97a3c8b4d3b3670a6924c8ee23ec00000000000000000000000000000000101fa8bb2c90b755bfba9cd7a98790b7bea2ede4c806fbd9f2006d10cf87c44172d4ba46ea40fb75afbbaa2abc3b6e9d7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd0000000000000000000000000000000003285cb099b04b6acd333c7ac76c839b6c09388792d5fa1f2af0821e49dfbf40a06803c4cca92512bb76d073129a48a00000000000000000000000000000000005b2fdbb25381b3b67814bf6cc0a4cc17271416d16ee369b723b1711d968c355b755183f0bce519709723250515ba32a0000000000000000000000000000000002c7062ba4f642b95e028a364b0698b801f48af3c336fa09d13d83ec6cff10d210b55b23cad1d999889c83df7d1ab7e10000000000000000000000000000000012cdfdc10bf46097083294259754453e084010f7ee928cf540d44c80aa4f601247223a318700bc24114e7603922d15ae923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f54760000000000000000000000000000000000669d760352e34a407aef8e141fcaa9468257b12ec08ec218f49f0769f3acd5068c6dc9d251a1b2af02a2d091f8ad0000000000000000000000000000000000064a7b4026ee3115cb730e56c4b9bf3e1527dd0f0ac6015f43d30a2f3d8d8c2659cf50247e70ca3c93d7e0a404d9faaa000000000000000000000000000000000979ca2e81663ed61486c1f841c19d83549388d798da72feda82283406d4964bc9991f876a6032382c35b605441ee7da0000000000000000000000000000000008d92cf77b44c516c243f3e6a8a8d3f9d3d7405820ab972338f700de1dd9a66d33b4a70540a30f630aa81fe1cb5bf057e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf900000000000000000000000000000000198831a40fec54a210a63f5e00b132bb1eca6408335b85a75e28be6a111beea3b99d9f2fe5091ab0eba0f082c201c14d000000000000000000000000000000000fe457f8d215f390000efbb7fe7193ba02a2ef78e9bff6539995f01604fdca9fa3c010276afb90215890f5a5df3ae21500000000000000000000000000000000076771823180422495d89c301443a9d1fa141716e5e27205b8cb6b461a3ded7e6f196c3976cd6ad56b2e6ebb6b3a70860000000000000000000000000000000007f666efc677f6f767828e1291bde0ba0ca445ddb2d69d5d2fa090ca49e697ce4e00f55d2b706454be6d68f012d76efbb6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa", "Expected": "000000000000000000000000000000000e1268a5e2f654c4038647a910e6cb4bab1d6ca3562ad4a9ac18444c8b8a3fdfbd35acf37f9203041fd587a5175ce86d0000000000000000000000000000000005e701a8ddd15ecb0231b7625f7868c81467c140b2617e60440a9b348a192e5248b1b3c087545cfb6d173fafe48d32f600000000000000000000000000000000071327f52b1325bb664d32c513fb12afb96dd8da23dd91bc4c3e8ae5f97d6bf673a5d03bb8bdeb6da3144dedac200dbd000000000000000000000000000000001852b86d3ef45aaeb691f454a345ed61104cecf0f444e4d841f2bc0402ea1291ef056eddb3fc3929ef4623f31016c3b5", "Name": "matter_g2_multiexp_78", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fcd3f253d018ef09a7c6e8f36662ab4190867a197e0c42a0b425dfb5fe61d57596ada28dde0b093676ce15d03406d20000000000000000000000000000000000df00598337060d603607f3b8dd16f277ce1882a2e9ced48e1944662323efc29b33c807653f31583a5d2198426019ba70000000000000000000000000000000009876c81a76986435d34c6d44d51cf1016c19ceed2432ef1e68decd64da2e31e42372c1a41a514b0eac0ac103ab6f43800000000000000000000000000000000121cf298ff8f610c64ca4a887c52cbe940333506ef2afecffe266b5b585ff737395e2c64adc23b5bd232250e67c7a62613ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff566000000000000000000000000000000001885d5cdc3e0e0c8cffa7519e6914e5d85962d07633970c4174ae4587853f13970a1f5d7ccba97458b9b5046847ad29800000000000000000000000000000000105b7c0ba96d5ce32d7447351ded3e3f491a0e741e921447b91f22a23b64c2d749055a0593e5b47f0ff7815e1a4c9943000000000000000000000000000000000cb88fc10c94642ae7e1d7275bbfd51a2d40e9b29f3d51a1ceda577beeb131eae4b17418f9f358d47b4b9c9ca4960a3b00000000000000000000000000000000131a3e080b1d4e936d97d255b07b09a6210b5fe6900da87b5cc595a72de2b6ddb01809e2dc63ad460a2926dd8d3b3b2ebcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a00000000000000000000000000000000066b31c0bc4b3b9fe420dc095d551903a2859556d86e210c96480f1d31d449d85ea292e2432babdb71c151c7b215cd6b0000000000000000000000000000000019d79a60793957745077f9233aee7a4f096515eefa7c49473f09bbc73fa0ee13a2a30a08bd7f3bc1d5c412d671fc37ff00000000000000000000000000000000006882160e4fa8ae2c2d48ae389d8f023e2775adb7a815edeba13728b8f6b343c45788c8e9116445e9989e01eb43e1500000000000000000000000000000000000ce53ab2d81ebaf4a85b3e12a6175ad7fb6cfbae207a69a0fe2195ab916fcb582b097f09d9fc565b837925f68855c4b59f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000a30d335c035afe459dc262fb1bd24dc0bafbc08fae0bed47e4e204280eb96595fada9c4332df1218748921bfb1274c7000000000000000000000000000000000e37eb189560211d6fe56faa3b6e710878a21907fdc1a9f8becabca290c24b8831e28ebb48d06bd822300fd09b4d103100000000000000000000000000000000104842b88b9df6a7b8243494eb11eb62c89d1ccbde9f55fe221c2366d6bc9149178f177628c6fe7c7661318640295e570000000000000000000000000000000011df8599d72b85ade11261076e02c036be5dfa3b6fab4ff72ed7413a879c0a0742be6c36a32d0829a4e3171b0341c6a3995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c0000000000000000000000000000000019f6634435be45b099cc739fe5c2dfa01f61fd2d466d5ea464053e2d5acf2e0e9448b1bb7770b5ad426f8a872c5764400000000000000000000000000000000002bbd52efecb10b3bb6f8bd04a5751042d8598cc34e2837184cea2b5953ec125dee871d1f2f57ebc84849e3a7ee5abe2000000000000000000000000000000001962b716342df9c13c21d89ab5b8c4c0ca191440fa709627e0f240a7ba518f4c95adfc5973b6ed0af591bb54bd00937f00000000000000000000000000000000089eec676276c52bfbb2593ef0362c12a5f3c1a0566d5aa862f5f5ba1580f4dadb36c15fdcf0c3910ee14487ff146c8997b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a5000000000000000000000000000000001556d081a489eba4fbb0c20e22b8cab432a9f6ff459ab9b0e7ceacbbd46c8e24a2ee70151b019a1b4bfe47d934afede30000000000000000000000000000000008fdd7391113e8d9865ef48b60acf921b17c50744e6ad62fa24abaae54836b3d59a7441371bdfdcdb251d252a43aed7b000000000000000000000000000000000cc66cdb1fe32beb91b05922f3920060e7a95467381d62f2f036e6268af4128c9516780ea53e873993744ce932b901f100000000000000000000000000000000151f94dec958859ecaeb810c4b1cc7a707d0e1671cd4a1e3c811910bc8b95c6c944167dd280c7fed22f92ce7650beef998115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd178100000000000000000000000000000000004f88568c7ede48d7476175f1d2e7ded4312c24934f0d47794705621f8aa8a5072b86cc41e187f4aeeb49bff17a4c9d000000000000000000000000000000000ca6c579e86a68b4041150fbbc36da744d359028993681c34e66c537eb8a0a0d55aeb9b8da7fecb844104dabeb507805000000000000000000000000000000000fec63c57d3d3ca98cd1735b2f59217e163ca53b07b4fabc4415b98377d87e75f0fcc9b51c99a57ff61ca8d0016a206d000000000000000000000000000000000940e9f93f3ccbe74c7be93236a2c440b213a014ed51cb57fa053495c3d6f6c8edc08ba8e10be26e5faa898162d67fe327370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf500000000000000000000000000000000055e4dd9da22201b5eb64e3b9eff2eab614c48450424491a85c18e05f50659b88e862490edd11ff980b06696b60c35b00000000000000000000000000000000018fab38f58d3d541666bc29b9e94cb3940f1794b2aa851d079b9aaa1cf742b07cd6dc7c985c7e4d7d3fe683bb15d618e000000000000000000000000000000000534de5e1c1181e951b437fd17993e995fd4aa2f6b28fc3612cd4db615de742e12d66c03b9ced538c1c7cde27752c190000000000000000000000000000000000aa8580f1da71f2ae9ec26f3b6466813a40ba5bd3f89ed0d42695d420032540194617fcc2f13e36219fc0cc3886a69c36bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f710000000000000000000000000000000001804ed7677fa3842bdc3eba708bf4fb7f7d4eaf2f1a46193c861595f64196398622df4358b9526f33663138b24fef1310000000000000000000000000000000011fdd7e1d0c5adfbbbaa69ce63c7c54525091289e4dfdfb3de772a8d5a958581cc23933deadcb8856540e2d0dc564dbc0000000000000000000000000000000013fcf17235506fb194e3adaab881c7aba4b87e5aef739e0547b858410e3cdbff0dab1980b1b30a7d03d617179ae545c900000000000000000000000000000000004eed0ca479cc458231ff969ebdd4e33732953e9f5610d78d4753b99c5f8cf73c742387b8e71b9be074fcc67acd71cf6b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d0000000000000000000000000000000001082b7796d35e387df689bcdda6e0316d343dc907822d1a873adea050374962b164ed27cea0e1b834997f8274e4c5438000000000000000000000000000000000b1905979a90c7a61f4ee2cf3a9f4d6ed4c724c9e216981b8ec34fb9b528018d237771ad620020efc2c3cb104df667cd000000000000000000000000000000000752663e72390108288ef4de3c3ea409c74e7051505b12083c41a2e8937eaadbd8cd61f96f7991722226fdd02dd8d252000000000000000000000000000000000f8e4eb7a3c78b8040a115c42b5d2fc69405f8334e948b8553f444dfef29bf3920892da431cd8394cf61f24e356e95694f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce11070000000000000000000000000000000010884516bb9916084709351ed8768c6105fa451e08d5acb233511254ddbf4e72baf9c43b56b4d7dd129a38f5b34ee5f0000000000000000000000000000000000228fc5fffef746419cc69abb17cdc63ded44892b8c5d02f0c72bc8506a61d15a74ec4ea0e1d78f555ddec07f418539500000000000000000000000000000000048a4192c204b7441e871076d91d4f610c347c2d71cf495ffcb2e2ab808a8c1a549eae96e657d756d9a3b94db2892a2f0000000000000000000000000000000017a94d2472df89104ed96e24d166f922bb852b5ad80f80188fce65b08d39cc3ecf94991c6bec5dc12f9337e7c087db2f8b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa3000000000000000000000000000000000f6293fb0e19ec85f43a1a02df9f59ad4fb0e49b16a216ce097b8ec59e781fdf176360d8492e8b77674ae2c0ddb1da70000000000000000000000000000000000e354d09aad68fce6cde40c787ba1e4488999d5b9f3fec25c9994b56bcccaaa746c958bd16ba271485f461b0d4e983200000000000000000000000000000000014fca0851b0bfdf2c69fb346f23b46135d2b7914bb49e297a0c1304d8c2851ff6bd0a0bb364938dd44680fe86cfe12e300000000000000000000000000000000164e23a53103dfa332e5ae09c7c898b95773c20f019d8b794a6b49594040e2e090db6a8047c943885dca95188e89a63b30e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b200000000000000000000000000000000146d37241ce4f71017e4423dd0bf907a12c1364ae9fc6dfe535c25e5e99e03ce157cbba2675829b396a69f92668107280000000000000000000000000000000000d5a992f5357615f436d95fa516212812f6811dd1f1921ba4129e84e3d487b6c97520995d8a65f6771dbba9d150c7ab0000000000000000000000000000000007b01f86574a9cb7eb3b9a19b6040055a5c11b13e7071078d16b9ad71f714ed28ad25db9511964b156ee34db22385cdf00000000000000000000000000000000154c29c6e2b21a75b14159b183e625c98a04be1850b22d314225e94b313619f641ead73130c1d6feb85abd8c9e172f6323a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe00000000000000000000000000000000075be2703b8416fa07a7cb6ae8841dcab1e36b0ea24231dba617a2fed3bebf8d952d31f68c149dd17eed136fe37b01880000000000000000000000000000000001156563f1401b731cc23c4be59e69b0e6a0827df4889cd9ef9e11310f679c1603a0d9c9679c29b8dab75ae51f49bfe3000000000000000000000000000000000663faacfaa92fbc095a5dd6b1f2dd141e248f84eff1716ee71bdffd4d28ef1f4c88828e3457e8ebf0daba1416d2d6070000000000000000000000000000000018f2871f5897aad9ff6ac45a9c0e78be8f312f07af5f1dab2bc4705558070abf367f1782af896288a7754da82bf1a5141ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000001618a284286899f501f46c4761c93b68bc8ab3157144e4013e242e1678cba20a2d978ab53b4b43145dd6062748df541000000000000000000000000000000000c25da737368775e41ddcd9c64cf99a824afacb1d404f1ef46ec7fe4ffd89673648c5207551914e6e0d12c57e7d7682c00000000000000000000000000000000097ff49c4872e2da1f6c24fd6dd4667f0bef4eb30fc197d13e8b66adc425e39841dea011d79e4d775106a19ea1978f4c00000000000000000000000000000000147426b7d9b0bdc2be051d8f6cc4249014e1bbc2369bc32eca94684483f50ced2c07be6a320effddcc1ed5cae455fc92212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000000cf68f7ab056c4689af95b361ee3e3b1c1c48f18b5aa655cce1a2be217010814b3f07dedf6f9a7b835cb13e2afd7136000000000000000000000000000000000dd6d0fb94048dab34410dba4e682f020ed54a655099fbb6f6e94a31511960f0447d7e94143eea88195291b225d11246000000000000000000000000000000001864c6ad3f2f794239a179647d68734e23b3520b79952bda20acf2f5afe1b76bc18e35b852d35a5cf3b02a3ce86f640700000000000000000000000000000000015ea24562d7bc59d813b77b2a4943f9e98842b5a41c0c7026077a02ddfd3d5fecf352d4399f507fb12ada4ac495ddece9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37", "Expected": "00000000000000000000000000000000080f0e50f90e001a442965ba7900997fcc89246742590b99add6d90428d3b61516664654bc8fb423f701e85a342a668100000000000000000000000000000000003fa9e84ddd754047649b7cfcf5bd78852abb298b3bbe6575c4c7dbc2e7595499a9f42f379a2463aa74f29e5c73a9040000000000000000000000000000000009e72d3c418726f6400b8cd8b9b649005f3b25ade40cd6f77a0c3cbdbef461e917d4453c9e07ded45301d21df4ec44db0000000000000000000000000000000015a06cac223217602ccfba4f4586cb184994bf08b324bf977dbb3884c394aed0622da7dcf5712970554d73b18e2733c5", "Name": "matter_g2_multiexp_79", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007340f432a5cd5aff1a1d98c6ea1c94be24de2d15a4e112925586c30979e23a5db93643308d3299e370b1f26bdd09eac00000000000000000000000000000000155027caae88381a60af71b2fa770e58efccfbb7642f5ef6b1591bf77e415eb117ab564aff8d9ebcd576f813b793ad2c000000000000000000000000000000000f604238d1b28f010ce8e45f2fe61d3ea20b902a4debbabcd54ce0ecd44a9540fe2bfe847178656fef0a5fd7e6d012b3000000000000000000000000000000000d7f503ede395dfa5682aadedc98bfe28d3fbfb52f42ecabc9eebc0e0a6616d3671604709f28255f50b62bee641d2711f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000176e1f9eac4dab0d253c0ff41b7600437b53a5ac5278d544a9620648e0bc4dc56aff0bda973fd1338f77fa174d8b13b90000000000000000000000000000000012919a18343cc166e2dfb92ff07bbf838779ef0479985bb85b3b82f9d0632b3f7a19d387f725a21729a77c58dd4d1d1d0000000000000000000000000000000017eb269ed75fe0403021ce70505bb60a711c91c551931605bb2a0773fafa07aeb47cdda382c0aa64f40f5e6e0e6bc77d000000000000000000000000000000000bed8ca999a4691646124a140fcc17dec02b74bd28b599c807abcaaff13bff65aff3892897652cd33b4ba5e4cc0198a9c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f000000000000000000000000000000001344d6902f5fdbb59a4c975847db0191beac284eb17cd92360e59f42cd7796cf2aa282bbd4cb074c4ee10b489ee3f2f60000000000000000000000000000000002158eb3429d0532792532fcceecc404e95a879be68b3685ae94016ca3762438b3320553ab6d5fbda3a0b615a04d996f00000000000000000000000000000000118f6fd8f60edf7088a0b4b49338bfcfc9c38be230460d7516f317b27c07600f504c8cc87acb0c95515c3acdc1b125ce0000000000000000000000000000000014eb422d44ec6931ac9860a6a017a907e8ed76de91bb7557e818dbacb19fb51457a1f45cca91f1d1d75a3567a3375b5cf2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a95853000000000000000000000000000000000f2c72c53fdb1b0cd13a1f20407c64c46e4a0e461778b0e2d48c4f20be7c655c639b38f758fa9199b8395f706df10e7a0000000000000000000000000000000016e6c75cdfbc20c5dbc2dbd1caa66be92911264d407ce3c689ef3ae1dca44dffacb4c0d8a78ac959e47ac5c454f607bc0000000000000000000000000000000011c5d80d52e864b0a46fb48488f497fb85f51ac040c77b1d01336860b972858c0a6e59914112f6cd6c1612c604d26f56000000000000000000000000000000001136aa7eb63d6f85d665d0539975a9a51a9a3f5bd8731910c32130b1ec8b07c39eb42e4f61e7d22bed933d9fce1810581022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b000000000000000000000000000000001306612f5119d33f177b8804443d14d04c8e059e28f63aa10ac6a1b25975327f378d5d24f0236e05849f07e99af93ae20000000000000000000000000000000017340f8887292264d498f84fce4af83573aa6cf1d57d99d364f2b84e1734fa4f9a1e07ddc81a2135ad5f5e0ed2989585000000000000000000000000000000000f65073250019ea69339379aacbeae7520c1ae10c8912ff827b702bdab2e15404cfc939389587364d811054b7d9f2b350000000000000000000000000000000019742f83ba0c9d36aa1d595fcedc3cdfa6c6f08579e66b8956fb32ac03530114ed4266738c57175e7a10313c8dd42deab80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000b2513f906db531d052e8e6f1cb8d7d3c41c7ec3158b370268d1de204ed8fe7618b64ae35029d1718153b5bdb8439dd90000000000000000000000000000000001664c367a2d4170f463c90351cb321608e2a49fca6f3258bf10d32c39747084cf9d2c38d5241888aaad97985cb09a450000000000000000000000000000000014de15b86461cda9f1be69f43a9ceadfe7b7d1548a206f3237d93c7c01ee554c4245fb73827ed0ab72b99a62215faeae000000000000000000000000000000000b25e458522be9fbdde4554b1a0d9af157aeb7d3ec1f89185b193c0429125dafa554d7a531ef9502d443a26112b940b8f397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000006862c0b0e3d7bc4507bea1df82080745aff21b7549b372085776be2f88aedd4cff00ab8258aa21e63340963bd0d937b0000000000000000000000000000000017199c5ec3a2dbc1f1e8d74648cf8da247e35cb07df22629b3845274d29e473819a31bc344f2a2bd6c790530cfcc0126000000000000000000000000000000000e7fd1ff41d86a02014229c5085c886988dfaddcb60f5c7c81063e8289aba846337d61bdde57e276fe6c65bdfb48751f0000000000000000000000000000000010efa6aaf7650edb0c74d30125e36cb67cffd1c7f57932d92ab4aaf36f8d9245d7c75dc2b3bc8f3f328589b16e26230e28e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c5000000000000000000000000000000000935de4b16f5f9c0accee77b5820cf36c24aad9953d40a2409b7e6040f09f85da7d2252843f9f8005316146caae539800000000000000000000000000000000008a8c542111951b32bb0b50f7631f8938d22e298193edffefa3e0f5c861ac8205ea9b865f9420ad74cd22b37c5cb56200000000000000000000000000000000012ddd660879a1f52ae6284e14f2ae6ea381ff3f321458cb76bfa566b04ae19f3793468d0aab652a82671be74332a3b7a0000000000000000000000000000000005eb148c35732f7ababc73861b71fe4ea5e25bcdd675e975fadd0a9e0fc54e175b2e39dcf0323f4a9802a68baecd25df3cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000000bc351eebfd3f3c332268055af1655c8729cea44eaae803607198cf747280adc0d3dedba137828834af3e7179ccff4c3000000000000000000000000000000000d8a6cca17e1c6ceace7c0ab1333ba76ed6c3b114bf99ff80127c6a17eb0585bf6fcce871deb7385e9a8896a21c065ba0000000000000000000000000000000013222db97e31e28946adecda10c9ccc9aa9fce33e0aca51d6483d2f0c5bc3f33994ad516215f8333e22167164ef5459500000000000000000000000000000000144d3707b1898d35c65ae2c89b1570971a9494e8bd23df835f565059554eb7b5cb66a6eec890058316aef43d6c6ff55c8e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c5000000000000000000000000000000000138da93a9a4948d41a6fc6d057a217faf5efad863b45ae8eab311360c033362213edb0ff90bad6c95f60b8e1131336e6000000000000000000000000000000000f41766d9b57b3210d315a2b8f90aabe591c1de6037ec79c0d72a283f0ac3094436bb97b82b7ad12ff4f471a41227bb50000000000000000000000000000000009aa4f5b674782b7adce6bf75ad676480f96a58d68dd7ef8d1fa488cfab794f06e7754e9315430189eed265913db8b300000000000000000000000000000000004e2a4a48f02079c0ed50c1daa91b1216af481a982c7aa64d8ba90449ed886cdeddd0cc08f1f8764f7f8c5988fe677f5674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000019c927bbffd96aeb9342666e1974d30f9dc215e8eca41c24244c63c106331ddad20d64c79faf8c5baa45cd30b561e167000000000000000000000000000000000523f063de96c9b77bfe5c5045a007e155b45dbe68c5f1162884f1d942bb385bd34c2a37e5e67e6dae4a23d600d75d1f000000000000000000000000000000000c221006f5bfc8baf43826258d0588d7c0fc345d68de1add1693bb897959c2cfdbb9c165e82c0c787529cd7be85afbc50000000000000000000000000000000004218e3d52b42a4504611929f94024326f38e78bba2aba105db3ffb4a51f8906b060ce2302e22ded60714d652a234c1f288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b3400000000000000000000000000000000189e5063a36b0edd736bcd9f997f4b08c62d33b27560e2e2b7b40039e7c63b75757f23746e70a330110d975ca683941300000000000000000000000000000000013393485ae494b1f1467cac9a8840c695d619aa1a78c40674038c053f264c1e20481f2005abc7f0545346f5a982d05e0000000000000000000000000000000003f2be501504f4d37e12acdc54b3280671ca0762a063fd3bc04473ed5a051cae3767044c002b7ed1abe88b2143af08750000000000000000000000000000000009d5952af88514996336e1ff19409e3e4eb3079f6dea22f9738f4a331ce842b151e0b842b68cddc10a711afa6d3242b256e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000a79444c673e630f46bbc5a9e06e8c023978a78e3c58d72910a04c3733ad873c0d0de61448076b2fd3764cc17d86d94f00000000000000000000000000000000110cfd215d67d4a091578203855fa0e85feb4dfd0076fbfad20bd092fb91b528a4117850955f5fb6568fc5844e17bbfc0000000000000000000000000000000012ece0577512182c50dbb4a485256e705410108d9ba9c8d57780d49e2e25a0f89ed1fe917797b902aafcb8f7d98fe931000000000000000000000000000000000217cf1dffac7ae162181d43ef12e3e88da4840f1573d7ffa271f64d8d54861099be37b644e96e650dc613975d8a00a4ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd0000000000000000000000000000000010bec428b2865aa7c077c168dc28dc549481c6f8367a5b84cbbad661b0225cf0fda3e840d96c4e4efc36c20d48f23d5d000000000000000000000000000000000ded3a1e9e2eded0a11211a217f9355070361f0a5887a7e19c74edc8768000311cb9dd8513977ecfb45416cda0908cca000000000000000000000000000000000b99ffddc79e825f0b73f2d0229d66e51624d854d00bdee5aa7a884dcafa1888963e2a2149db0f6e40ce3c67941a391000000000000000000000000000000000147618970c71965684bdf0d6cbe1de189bd23bddb2b861c9636efdcb7a96dff27bb1ac70485b562e78485a1e8e56531cb96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000001a402aba8fb28dd37f1be11fca037baa99a6b57188ccab66208a50bb6967dcacd1943cca73e34f6b2e2f72407103a73000000000000000000000000000000000c0bd64d043fa4e3ea566cb84f9139091891231ff500b67e5fd451805f79003f6303352a4f0c236063d60d9088fae88c0000000000000000000000000000000002861fa7d0222711ffcadac86e7b9e7b494f5561c22544bd0876fb6e1b2e680d0f7074c2800312cb233de2412ccbbc8600000000000000000000000000000000015945f0c83e738a17cb1283d08d63ecf12a7272bc62812006ed78254bfc45ca7c42306cb79bb16ed17bea600a4d62b5d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb0000000000000000000000000000000002268793f6872f7715d802c0d96f3b3d850249d8e70aaa97f19793d2c92e7cef384aaac603eb51525c7ceccdd0211fc40000000000000000000000000000000002507d680a2db16746810e966d1ba5547ac98d08c8402aed0859203e6dae0cbd87a9ddcc05119c1ca08fca2fd733882200000000000000000000000000000000192426b6438b2abc7386599afbe09081ed4908fbeb807a65bcb7c6676aa76e5e0c2c87612cd109cb124c73b9c8e0591a0000000000000000000000000000000017f125a2ef5246e7a19e1b2741b31b9224511ffefe63ccfffaef1b7949e88af573e267d6c7617ea97bbaee6d50eef67e1ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5", "Expected": "0000000000000000000000000000000018c2f533f464f9768308a56209711cf9b6653e8d38591d782ae2374905f99f75c0d42c63af4b534056c28599a9da874400000000000000000000000000000000071d4d708f00875545f381e164f77183e14faab599e472b2857c15091254ddaf5c2e9df809875336f92ebcf5b7628da500000000000000000000000000000000099b207cf6ed022289c27393c32d0b83aed6c7b57323e746374c1a8e2ade071a5293168e72f7aab82f6c2e39b97b03830000000000000000000000000000000005dada01b4dfb6a52d998210a67ccedc11d6aca2405e0836280e2f7c8fd7c8dd271c815a2e9ea1dba6f1ab0d6e89d756", "Name": "matter_g2_multiexp_80", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000195d3f440857011bf9b764ff270b8ba1d9d13daf48933e49c12ea20d335b58bcbef1353d9698a7e795b4370ee385f12b000000000000000000000000000000000716c151efc6e611b5b15c749eaf02816a86e267428750741b167404a21116f2025d0d07c447b9c7bee8edcc2c7b76d30000000000000000000000000000000012ba0bf62b35327111d09b402db2b75b2e835cbe581638af2fdd6d06034774533e6501be3de84e7075e4184e11fd81a8000000000000000000000000000000000329b14859d004c146047b03870371f53936e078ddc69294ff1fd6f42cf2a354a921e5f2e5c125c454e20af97dcf769e7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd00000000000000000000000000000000042286dd205ac86fdec3fee779059e2ad59adb62505f7b78606c128244b031c53dc40ebc2f5afdba348892d5ef4c10e7000000000000000000000000000000000f960010d4818846b3a0291c6fe1aa53bf0eafbc0e0968e3ee82324452a7c1a8041c06b4db9cd36a07c119c9fd2f9038000000000000000000000000000000001876da0dca72869708b8ff9ea0b74ad6be25ba82ccc76660246413a04344f2b72e5a7f6fddb58e9dc0bfaa6b33a5fadf000000000000000000000000000000001538ad1673f117493d998941d9356fb9907f70c279bde8ae8813b9c7b371344456f8e67cf02bf3401ee06d55604cadf9c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb1000000000000000000000000000000000d91319b4a5e047ffc8a68e10c34b2b90e7f3f08f9e3ec53ed12bba5f66c168c20c6583ba2016f0137caed834845f7470000000000000000000000000000000018d5542919674d2fc32430175405d806ae4abe3e1236df2188bf4c9ddf66c0974036e28414890212ff8dad244d11e3c700000000000000000000000000000000160b128f1ffeb97edf0e62dff85e3f90fa48567ab777a7937a2c0e4659df180fae4565107c2236a5f2808e42a03a4ab40000000000000000000000000000000003ee74d214ec491331fb9db8243e75570daba9feb587671496cea4b480d80ee162c6294c082203534bee450c384f645e69700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000001951afa33800a366944c43bb42b8c5c8beb9ea2e1cead8b84e0f94af51e4a156d9454c0f08d1b13c692c41cc480fdefb00000000000000000000000000000000077f4543fadad6f2f8ae8d5d98f64965bf9626971e7efef5221cb4d95d51b8764324cf4a11d0ff5330d58df70cb79d92000000000000000000000000000000000417251cd0c1b32505377e51bb30ac8a8a3c059644b9ddb5a058b3c6e1110e1c71ee19f549b15090144dcf4668d0d50100000000000000000000000000000000052133be345adc562238c4ecbaf76ca4159fc11ff563ab393317b03065ab668e7df401831baf7027f0577f5791b1ca3019e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b00000000000000000000000000000000067bd52b7a3193d31a4f1ffb76432c8d4108442616f17056d310fbfee2ffaade9437e2bdb8425cf83233f0c632efc1170000000000000000000000000000000011b045d6eebe1bc8218b696b5e81e78db78eadf1b5d987060c1bdd73aa65666f77e1d6bb6f3d939d64cb3e6bda08994c0000000000000000000000000000000016eb5ea5067413b72632f5300efbe0d01a284b2a59b68d0333c269da9302bf0f0cdc923acb27e51bbbbc1d4086e6b06a000000000000000000000000000000000ff37b8812963d9efaa1e6deb5cfd34eec70620fdb65808739295a819e03ebcc8f501b8194d0b3c72717fc922b785194199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e00000000000000000000000000000000149b5e0df255281c1b518427094cc0903fe89eac9a6dcdc379b8ca30f3696d89824c201601fc4b0795a3c859a82893170000000000000000000000000000000016ee9e7d957f439d078f3c5da98d114a1b5bc4da9c17e117e1f540dcbf83a349bba94def4b87b63247f190e3b5813cb00000000000000000000000000000000005d4f56bea105be4bf1fcaf4f25df30f85968d59e60b1c438c28ea0f480851f5ab9c05a7ca6677e6f12c7dd3ed67c2e0000000000000000000000000000000000dc0e87ca5a8b339b485ff3da2b9854a07e9663c43344dfb5ecf3ea055eadf67405c43013e15367fbaa55f1bd8e222f98159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b000000000000000000000000000000000b0575fe2adc9ad66209cb2191efc2946672e4e81b96d50493d2125d9c83165f0c4d3f714539eecef9de0706cc20da9b000000000000000000000000000000001511649f0cb6b86111d2830812231ad37df5500d7ce1086241591dc3cf40b30f1c53dda3133b2f7fff253c94d5eb98720000000000000000000000000000000005b15e4e32f4f4e46c1560792a9973f6ad63f5176694734f379375f16a08c162a4a820385d3ea6c191bd87fea4f5c8cb00000000000000000000000000000000089218403fef08dcc6e679b49a74557dafed3278d41ff36a9801db091b91de0d46d779a40574fa4a3f2baaa1a14be098ef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a0000000000000000000000000000000001c35a3fdea92b28c9ab4bd9ea592b998853a73be844b9dcb500ed6704bbf3ca4ed4216dc24b50254b6ca75c4ca3e7fc000000000000000000000000000000001815292d2a365dd7f41ecf3f9a89e040bab717241cefb3155a097eb9885d64fa55f5de7023f2ecfd33f483ff304666520000000000000000000000000000000013df522c72805b890aef97864ec6769f569504fca2d6a6beae97f80dc92643f8014daf3dafc0040dd7b985c0d9b2c462000000000000000000000000000000001155ad4373a8304fa6301cf48b4ace135d6a0c08cb06d624f42f88073e43612ced3cc37235422171b43af2b4ebbd5662d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c5000000000000000000000000000000000bb9afedf7417ca31beb96486b024af13c06007585d785efd1e78444daa9bc3c03e1d64b560e8d6a18ccf77a8c3c8d05000000000000000000000000000000001652d3adcf1612e487a9ca198801afb9ec30267148502684c2b91c05ebf6c48e2ce33f9c0a986daab81d5359ec1b503c000000000000000000000000000000000baf3d34bf4a78e3b9dfa637c6392c7f4d7ad0ec315d10748784b5b60221bd9da0f4b75c57c139ac2db329e270d559de0000000000000000000000000000000000c30e553fa2324d552bdbc7d2dc86531340c4894495ee9a38b64f5bb6f92314021a2a00c4bcd8837e55a0ae2676a9b761d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000159a1e4e87c35aaaeacdf21efbf8ed99fd6a2ddd7e990c12407b1417edaf185b8f1df9bafbddfaf3d581b5d97d7718300000000000000000000000000000000012239ef7b1e1009c81098aa4aaad8ee9e003530db5afd49867aec47f46d5e29d44b5e62d80d9e832937a299633e863c80000000000000000000000000000000016af6f74392461a9294d9f848508651ca5c0cb50494ee7c6a334bd770580b924a17beb7824b489e7e101ccd50aa0d5cf000000000000000000000000000000001912a0f54ba4fbecaa55c150ae93455e1db6b238c032fa7992bc8456f183c09b6005dd6398a77ab91cf547919ce7485b03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd000000000000000000000000000000000cf1cf7a09a12f51d10059425042ef8e140718ff11d2f17897a0156034f73ed29496d93b8695cdf609280d319c9bb742000000000000000000000000000000000b2c4d26fa1eb72eed1a24f27229d2675e0c6f91e3a4eba7d34b0fc1bf5a9b4eb49c3492d9586669abaf25a656e1f95d0000000000000000000000000000000012c5c83a03087b2449b71e9037591fa265d710ff6d869bfa18ac37cbdcc93024f673128db3dbad9e3517501af12f2540000000000000000000000000000000000ffe5824245e43953e3d0adcd5fdc1a97ffc87f8c5473fdb0fed57000fd126a9925ba7415c698248c51c1f3e12b270d5e4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b00000000000000000000000000000000036aa888e40882b2d6ac71d66c88543e32b4a0a7c959eec560e3d26114d8aeca63fd87dcbb3171622c989a6c7a204ac60000000000000000000000000000000006a5e552e6d2dc95ab8636a8be16bc79572b47860bb88934bf04c195ec01fd71eb91e45f24c58bc2812ed5fa10c8dd7d0000000000000000000000000000000015fa3ffcbd4e562a4bc29975cf8c1eedf442e37374fc87128e6f68bcdf6e996f6f054e0b8c608e651753de96655b2c100000000000000000000000000000000019bba7c0b170dfc1f8fdbf7a2e09ca0c4027a6aa6930d15dc2772a0f20e5e56f0d11644094dc866595f801ba5552e6c4c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000341f46ec06a8def4f044328bcdaa308798469c767d10e5db34b0ffb6f550421c67c6fab7b63cbc7504e55847cee419e0000000000000000000000000000000006952e5f791c37dfebcfe69cdef196dff66563b29e94927e3ab34365773b93e72251a63af4ff294af88d45fe0899a2c3000000000000000000000000000000000874dfe75b31450e99dea063c090e32d24fbff9b681b64a9dca5f967f82003005b003d17eb869bd3b37d4a412bcb28fb0000000000000000000000000000000014203b69e8af4e25232777f503d5e82d6121256fafdff1b037f65d5aaad0f09ce882151d6bb4705328400f00089dcc7a7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af4000000000000000000000000000000001403fa3f418107e0bf7f3f4bfcf621812d32b1b744ab5a4c37b5cf946a5e5dabd675c2b70bd355590a9883436c5e32dd00000000000000000000000000000000069e006f168bed4439fb46db9ba4f279f72ed608c12a05eed172608693f42cb1f04aaa54191f4b0b35f967bf03d0e63b0000000000000000000000000000000003f9ce029f6fe605802de64701ccdf52bf4aa299400a6e1c36f5a1f9173bc11a38e7628f123fdcae01d2b260f77c577c0000000000000000000000000000000009c9732809f60635115cb479c80457c6cd8dad092111d663c0cda0da1fa71c9bd6795ad013d2efaa4599c8ac5c88e5f26f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000115e08d8e4dff7adcfe46a416625be0ac26ea2d7900f5fed497809a6d46e7faa5b47c52ab3bbeb9fb16d82b549707ed6000000000000000000000000000000000dd1b31446e44f64ea5046dca5174ae854f6bb5d95886fb95aa136d432f1a8c03ef1a5f9320f89c82f764049a7f678a40000000000000000000000000000000014879783c07e6986cd393fa1e0ca8a7e23b2c9efa595229fc0b6a11b9c232ba33e92962a1087fe2ba0532d7b541827900000000000000000000000000000000013dc6e2bdb2801333e7f914b99f30b40125fa1ebd49b141d88a8c090b15ec3250a13812a19c3c0751a4e5ed100a6f0ba12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd6000000000000000000000000000000000a2cceef36ec78dc702b6731dbaf8cea1dc2b41fee1b235673c6941729bc5631e69ff37900479391a4d10b300fbf3eb40000000000000000000000000000000002f4881fd626f4ac434bc1e59716e5e5ee14dcb9adca4d639ebc9d86e323d274ad8ec0a4b1e6ff92e1fe7928d48924b000000000000000000000000000000000174cac80e7bc63989f58759e123513b611e9849b44d43a362f2eb84421ad008f3ae9e9f0f233e49fc8e10c1824ba948200000000000000000000000000000000143641099c8a6c8153dc8ce74debe795dd6c4487e8234f164f9f8dcdea6a53619c04a8fac215421f985557b5b956c20a5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256", "Expected": "0000000000000000000000000000000009807ffe8fa881b235b1181d2d3f147dbe21042524fb0c0b4c90fb122d160c7b895034ab32e20324dfca564ca6e3183c0000000000000000000000000000000010f6da88525da3e86ee56cd5514a436e3ce4128e437a876be130a70c42444a05ac269326c84dca532ca2e546860027c00000000000000000000000000000000011396a7317918841ba171ea46bbddc9bb5a08db7c82b90008c6982b4b79a4dafc151081bbdb7b9fb79784e603e15eb9e00000000000000000000000000000000070b8580f303b83c643a484dd031b780ff4ca2ec805d8c538a0b0c791cc7f8163654f5e5a41776a8681500a6690e24a4", "Name": "matter_g2_multiexp_81", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f38906bd058e4d32403fc3d39fa57bf49c0da65ef42fb129332b91c184185de4f9f0bfe8908a44833ff4ac4d65b88180000000000000000000000000000000014ea6fffa6dc462463c15feace841697698bc521f608ed0d16be5097bf42aefcd1f73182f37b6279f989e9668a8076d1000000000000000000000000000000000f56d296323177ce53c6977fb60e445278e59ed1cf92e3f68c570eb7a9e5f8afbec5e2ef64674bbb54d7016c829f72750000000000000000000000000000000001b29012ff3460cbe4a07bdc65885718f217cf177866823a7cbeae18bda67f65913ea20bb69e0ffb31bd82f19862113dacc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd000000000000000000000000000000000a4126bff91ada057ceb9a75d577120c7ac8c9ba62151602414364cf88a3e12dfac90b5590db3e40c16163177ad4e7520000000000000000000000000000000004a3768d326c4ebcd5ffed89341e8d04f89e674f3f2aded3205a7193e11c20115b3c4d595b959d6e39a03d76f6b5925b0000000000000000000000000000000006e0ae4a9c45bb69c3a1c65e26e4869f2eb18fefe584e4598ba99c0044e8d911145a5db3f57194ceb6201e7eab9a81b20000000000000000000000000000000005be2ba6b147f3f2052c4877c90ca364427c6721ab64dd35e89f14f3179564d8812b9013e3e3db22f69afd739229682b98c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf0000000000000000000000000000000004c36cf955fc81bdba4ea8d2ecf934adaa57fa4073199f77bd0428d3ef80a7d7102179d4a44ef0de887bcb3ae915408900000000000000000000000000000000138bd3ec7a1b6fb65d1df6bc1d2ada35aa52b06729c10b5d45b9bb7cbbdf41677942b99eb9c2d32e3e73da7d5f9cfed40000000000000000000000000000000000b0291ca10245e2f7a963fa07ec62b15f6bf9e7a5a7839840ebcbe538dfecaf2114c7864a16564a5b3c85c15d97fb7a000000000000000000000000000000000b436e912b8a71cf8050d10d59017eca6e494e5440f02d2816924ac9cc2034bedb1cce6eff5c42f3dc57a74cf1b51cc0a0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f0000000000000000000000000000000000c105ac7475ed9517a0b07f25a030a5616952d817f3893181e352907c7cf4ec9f5f3006e37b1da97e9cae4a1213584e20000000000000000000000000000000002c112c18268934823d5946d2322d0faec497d8e18736da91d2af744d90f74136c49370a4b43952152c62820d25e52ee000000000000000000000000000000000fe2818a397d70543e752e7022f12bab10f1b1289cee61a0230d545296ec872e34d8df6edf7ce9980f3c153e6e51d96d000000000000000000000000000000000f479e6a52bfaab3a31aa9a461adbec8a390daa8eb6273f9e425eeed764a6dbad44d12778bb888aa5808df272edde401e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe00000000000000000000000000000000016106cb42ffc41d5b23bc5b06001473bdfe556d375fac6a0cb0a12494e9c02ca2dd6133356846e1759a2c485faf5e890000000000000000000000000000000003cec25b0f5d1db0ead5319d6dd15517657d1fec442facda4335ae0bbeff606fa9caa6a4c00445001180aaeef895d7fd0000000000000000000000000000000016ce3573fbe27a8d23b3ebd22aec989d61fbd0e41a519c5e2f1d650f2ad73adcfc8c840fb12bce83b722a0cc69164e21000000000000000000000000000000001434d13d44fd8dcf776c2a045734dff7c09ded31c9e3a4b5e765cf26fbfea4cbb4ac15c06599012a7f2cd572bfafd78ba32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000019923ffba0d08ebf1bd43393142d61022430356081c18e37804172082c7ace987ece2594f4852e84604a77235c7795e000000000000000000000000000000000123acf9e1a86846ae27d5fc0358afa34fe9d6b68232c9ebf2d47cc169779c4bd24f225ad30886fdf68166adfd9898abf000000000000000000000000000000000a6061d4cef29d1e3535d54a2e36373e2c16f91543f53e1aca94c4abdabc663049673f2327ea8bb574244d7f5c99e981000000000000000000000000000000000b1f3e1d43575a74584ec7a3280f8b7196f9b99b5e911ed33ba6bde1188c82d906f0f8e6fc2b285fefa0ce59116e449524301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000000373d36dd0fac76a0fc46ba5da279ca3be5a1f8d799570004e429256787110d4fb746f65a8527d0ba681a81b9980bd5c00000000000000000000000000000000057933c2b3e482ae026159211c4742264f7e890efbaeb6e14f3bf66c80923289af095dc97b751a117e181ef917d049b000000000000000000000000000000000068816ad2369bb57b3430c657284858d3736c327284e7410b61ed444786bcb34a66db9c16aca583aa9722aa8d7975b440000000000000000000000000000000007fcd7dbc062d28f6ef906f6a455337e517e1d6e6c02c7c0b2b2685b79f56ca3436c1bfa0ab96e4a5eb0c2e2c321c0dc17a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff825695706740000000000000000000000000000000007bb0ab060cc12002e043724c0fd0c8bad30e08b65ba9f2fe5d09d18cac4bb2d50e29ee14590ca7bfc505f3ee3d4f93d000000000000000000000000000000000e680653d29eb5d90f21802f543eac3102a1de6d2a5bc943a53dd9b80bdcaa6951ced2eae5e2a25448b40468f1923ebc000000000000000000000000000000000b7494b494019e3ef36d5c620ac56483fc6b1c8fe5c6f67537b19f56ef01db327812095fdf805d3dfe678a3ed8bb6226000000000000000000000000000000000291e5b98ecaf7aef0374647d28fb9f8785a64d9165de407d062403047da14d4ecd19fad8575070b278608e16b71d387d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a31800000000000000000000000000000000186536e3ae3edd9cc6bc24fda6589ed26e72e06121e97e1ead65b200fa0578c6e53d1154dc7b14e7eccc3a53237685060000000000000000000000000000000012fefaf6c76ae7197b99571e41a19b14846fc4499e8e964ff750e7c3ffef6ab3dc19eeb42c5f6ba44a573bca7a15166b000000000000000000000000000000000a135db813a44a21174cea3a0b34fb49f273877203ccb66bce44b2b58794818d8bc1df27544ecbf780823467e2e4ee6b0000000000000000000000000000000009b08f70cdf4e349e1a73935de9fb2ad9f4feb8cf5f835be78383fda2af94d81af253ebce08cef825764151d5713ae60cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a0000000000000000000000000000000007976df2d47c14374e554401c4d3330bbf6f1e6b8fafcea1e1974af61e8ebf493dc0473d34b30b0b1cbee082550d85c200000000000000000000000000000000177cd64db8334dccb17fb207e467e5b09e891b05df7658d9b439e3cb72bf3e0a70e84f96fb5e448f33c003c279cb38d800000000000000000000000000000000094d739a02b8ea6ff8113019597f41df4728b270770edc5e68b1f5c32775f0c706e3f31c0a82059c1ee150b89097376a0000000000000000000000000000000006ed888aa4bdbee94ec67500e30d654071774fe22464dd5b900fdc17b445754293504b10d044aac8fa0c289f0b2d9dce6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000014a16b856b04ac4b687c79f2b4e1dd6d45db25b382e0ba6687afac648c9b6384cdcfa89812f1a726bb4d1c22ebaa6668000000000000000000000000000000000764088e337df6db30ce8aa23aefd91d9e35be911c9e89ac62a1e06c3d06e28efac256490400fac4490f595cd03c127e000000000000000000000000000000000894856fa1c8488fce182a9c7749f7953e6a73879b6e743fdb8c780275447122f512806fa83d5ad528f8f61598ed01d20000000000000000000000000000000002b33bfd09e0ff452c3336bde08df0102162488bc83c27052447a1e5d16c9c68bc529f96ee3787a26d2009f22a1246342e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b00000000000000000000000000000000133e2d092352d3ecef5b67a09c2be268fcd4fe1f7360a8ce3ef5f33bf689242961a140d9c8afcc1e2fab3ad4e3dba49d00000000000000000000000000000000101eb285f0c462a22406846d82ca6a278520b65132d2008b124f6647a642c221b0c3bbd4a0abe8af7417e7aefb81b5b20000000000000000000000000000000010958cbc317f1186aab69ac24be87647b8013b678b0eabc6270167bdc9c0cefbaf4d9a34dc41524b709f1b881e6bfa34000000000000000000000000000000000d92c47257fd0c4d6baa4c81efe65852840479b9bfda5cc06b253f167069ca7367924c0c67d6497a1e9abcce7d0ce9502eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f1840000000000000000000000000000000014ad0f935ba129b47ecaad63b9dda44e7ef7933f182a0f5226141c8f0ede026ca2f11db7f4924b5c582461688dad6359000000000000000000000000000000001453716381f13bf6ebf8fff2ed7bcb90f7beb44269008af5880a355dd03de5c84c14f5aaf69fda043b422aab0c694784000000000000000000000000000000000e983c9e9b799eccfdb56444d31948067d46adf275d7f39a70aaa8bfd0fe1b83632c23d87f4e993c8191901e9a607217000000000000000000000000000000000267c8b8c5e09b59277736caad12ec6986f206d1c1f48023356d8bc877a594c8bbd98981cec6382bf9bdb9a5fa38275ecaf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000002e28c245e71a7f6206427ee512f3250612785ce29b369682fbf767d06ac08f91de8ac9f82951574cce46cee1aa757720000000000000000000000000000000019b0dc35eacd961e0ca7d54a0e37c4ace37eb0200d5489316f3371412717c57c8f17c1379721f4dd67b3fde24f50d4cd0000000000000000000000000000000013b9741f7a32e5e5b1ae5400e32dd6fcc1fd43b68df54ade57c934720b1289a51deae77b1726e1955b6430f37928e2bf000000000000000000000000000000000693980b347ed7ee6cd93f565c87efb36fb304d7e9ae24e2b9f902bfc962b6c7fbab93287147f5ac892db2a709c9ab42106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000ad6b7cfc6cefa5783093b7d700360b354d0698d27ecefb7d5928ac5bd6c299e4001474d205cf3b85a32c600ddaf1a360000000000000000000000000000000017172c3d5acf59b70b340fc703e9b7801aeb4857ffbe7a9d5daa0f32ad80d1c0ef2f0b3b7d1fd83a757c076872425fc7000000000000000000000000000000001291f55fa7d14b14c578d57178cc707cabcdc4bfb444cecabda271cbfba2ab361947d045ed46d9edbd215fa4c8164e56000000000000000000000000000000000f64ed6c989eec5222239d888d08dfd638a0e35eff2266410dab0498941fcd1683654064107fb7e53b8c02fbe98a25622b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd30000000000000000000000000000000001d25b5bfcedc6d7ff7e9fcf729f858759936235d23ad45b14dfd0229bf3e50fc68799d19ef019b36728285bf7ecd0b4000000000000000000000000000000000326e300ba07935e0233a03ac891f18dc7b5a9ad9a28264136228e9e23e8f2aa31b7f5e5f3cb3354984f57a868a5d00c000000000000000000000000000000000dc92060e3403df3a92b15ba3e437ef0c403fcfc9c3545e544a78874e5d9b5e63b9ba6060c29022fe2594c2e6fbb6a840000000000000000000000000000000006a01e85f59dc45b1501309a350137d71147c30fb70da6b7637a9b1dd884aeb7e554215474784ecd3bef18d15d2c0524dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5", "Expected": "000000000000000000000000000000000b7fc0b44723ff0d1cb7c43e470d4d432fc4bbc7f9d98ddb3d91434a5574956fdf15f898e579236426ea44677998665d00000000000000000000000000000000176586b6f157e408138391e3767d0c1c8457857f4cfae571267ed64ac86ff8a4b61a28b406e1caecffaae6a399f4ec9c000000000000000000000000000000000a420992f850db20d4f7d2ddff33e4dc79bc0c39caee533920c5d03d1c2619d8ced769ac09f664c0921829bd7edb446b0000000000000000000000000000000017e4000f4d03a6707174c3adb74966896bcc0eaabf4ff83cce92a666fbd13b59efa2c767442062b6c8b1a3abd604f0ac", "Name": "matter_g2_multiexp_82", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016ac5146ffc26d1f0c33645931bddfb84756e1c7b03f4838467d1b6701ee9478ae03c910b6b4f5c14135487bd2c14bf8000000000000000000000000000000000e1d082f16e4d5c5f0b6fbe5178aef6f781a6fb165f775cf0cd4dd55f2b498a79edf373007113dcdf6b977a87e1fded2000000000000000000000000000000000bb94be280df1aed761651c0292f88037d172b675bae1933ec12323b947023b437d080aac9e196fe5e06e5c4137284d200000000000000000000000000000000064190f9725bfd5d56c31aef7fd1590c975e2ce549fa6d5c71b254851149e0a0dab0b9de3acfc2d64231c79bc0ebaa37ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000012199d02d3f1bd8c4005878c3302e6a731ea69d69accdd690b4667e847b079563d32e18eb7a440b8005ca936da5e73cf00000000000000000000000000000000125b0dbdb0058639513b007a84d3a3e6302f5d846f22f99a55181f097e200981d9013c00d688a11eb976120f1a5da64200000000000000000000000000000000081e723506635433528fe4a40fe4ecb8a9c3d8cd701c043c0418d149951651e21632cd85f03db33b89efadd69e009ebe000000000000000000000000000000000956af2e67f8ae676abc783c4ec9f85c50ea130410cee8216fe036cd0521b8ea38966288afe7d35c28b30f7ca5c6edc0c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a7730000000000000000000000000000000003e11a4e9dfe82cb495e9e698b16c257ea3f4ecb24749751e7334e0f31fbd6677545e4bf9ff78a82853560f7e7ba2ae2000000000000000000000000000000000caea2c527cb3aeae427e92fc364365b1f55e7128a544009be2ff7a5236d1cf8ffd5a5cefc87820bff5bdf1c6bfa165d00000000000000000000000000000000064a3186774da8bb5d013debf46ccb0d894592c414f32de6f77da47f4d42b0c8a13a2ba4f14b9883d564fd8ff6a4c90200000000000000000000000000000000072f6c48b6a05039e3a4dfc6b73501d6d4ca7e840b119da9c074bd4cd2adf4f2c6e9e6325ebf6f97c3f0b00e6b9bfac6dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b0000000000000000000000000000000019ddf708ab31f6f6f725f0e4f65d11248d3a79af30927a6f2673901fef9819b189502cb952bd4742d2b8e84acebb5196000000000000000000000000000000000d928535c47eafa5da4ce4f91467fc31aff8b86b850e4582a597b334491b14da71763f9aedb15ed32856382069c094ce0000000000000000000000000000000004d6b3545d067aa0768cda9dc3cca0f58eb546345b96f7d6b9355d47770e00286d962a6b3a64ca2ce22fdb4834a4bb6e000000000000000000000000000000000f4ef9366d342b309076299816c1ed9b424b68886a5c69e21e785f97cb0f99ae3a99ff6b5244dab817094449048a7552da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce478000000000000000000000000000000000236c5b4c57ee4facec5d4ff37a478c505217af66e029c3382613442c58875c75cb423789f6703ff3c1c0d80991c9e3a0000000000000000000000000000000016c052de3336002f362d9b0cb386b800860527e0fe81a1a6df0ccde31f3265e6246191b3febd1ea48e9391c44593ab0700000000000000000000000000000000078dcb04ca93c676a9a924e59f924d9d3af872849bc30ca633d4025aecd981ba12e626337635ea77886a45f4da84104f00000000000000000000000000000000027df6394b195222bb8357bd684088e3e2a398f0fb0cb812ca5dcdcd1fa1279cfd03db62e0f8b2800d4b8b48238931656aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab77000000000000000000000000000000000485ce58b387083172102145fdb3e26c6ffca8b35af0e1d84ce9cbd89055be083bddd3da56443924049a056fdb2ef092000000000000000000000000000000000d998b234a69d584c78ed054b1322ceb33f73cafb5b23c1703a9fd609edcabd44f1a642802b9c0b6fde6a6828b50c1200000000000000000000000000000000019235ff13567bd007d77e4dfab139cd57dbb309a3cf6a6198a548c4e6915778094ddf2b05a91f5478169757bf5a56cb300000000000000000000000000000000110f6ea19a7f62bc3e78f4c5c1c6d3efdf1a7f563576e758218b2c363fff8ad8fab0e72431619e4ebc93d2d739fc786c40273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a0000000000000000000000000000000001d45da76e3016c00fe65bb50f7067e4f06364ad8348184831c4932ea0e0f3a170ab5147e4670ee1b16924105b6fdb6f000000000000000000000000000000000b3468206db0613369b2b0750c98da65b660fc07c30cab4e459c311dab683b6b313b99ec0fbe92ba07f8aab43a12a2c9000000000000000000000000000000000f58a57c449a41105837d5e2419a34201cc921ec77408d6c0c7a2eb227be98ec1f6f6eb9fc088daa0d4c78928a1eacda000000000000000000000000000000000ba53b872dcb9fcabf35e673b467523ea77accfc1b38a5f92d7b9d269c28aa00d00b08d70eae6ed4d2e82bdb06008f9ab77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb540000000000000000000000000000000002b82e2b582b247271543117b939fd17ba8bdd617a223873296f7bd75de4790f0d5d8fe523792bc7fb4764d3739669d80000000000000000000000000000000006eb554347efc5f2ee79949bafc012e6d9964ce19459b3867865709d903fe3d11bc617f30f6279a9e62ea104565953600000000000000000000000000000000006a543fe5cfbae629fd3256575e3eb4e0b65864aad6c7f359e169038bf090ed9bd92fef32fe1ac20b2a8c90fbb6081690000000000000000000000000000000013ee42b0693b2f3b9b977fbae5c856e9e4c5e70120b5c29e0a9f898f6d04b7fe351e17b02716a44febcf0a00a9cdd9220be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000001ae7368f84e354e5758554aa9c72ab4b00a644cfb9a4ecba38dc72227d297749bbc98c8f5d6149143b31442359d8013000000000000000000000000000000000abf087f77c79cb8c69e4289fae87b2ed483442daec3851a5ba32c43e342be29433b2deac6dbfa7a787547a7361ed0a00000000000000000000000000000000000fa01cff7aea64b649951a8d85fef0bd475f31e47c706b96ee2753df9987508b5e5456cc49e88ec3aad720a2535f6940000000000000000000000000000000018874d020e2eec0e286dce324b91f15b2a4f293d32956b27524f478983f0e0c5b43df802b60f4f001753f12d449cd821c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000018f7d19362e2cba91023455e115cd90f02aeafcb026349393ca4105e270ab1cf589621b40965fdc9795f66ea0f6a053100000000000000000000000000000000170ce0eb304e0e1047617b709c834b67a8989212e5bf1cbd5a33242be94bb141d5366e636c01a229943bead9a7baf43900000000000000000000000000000000077a17356b3b31faf90f709042938b9e901817f7379b7bd486d18e47d22b0430ba70fb3006e9afa67d7dac71ffaf152400000000000000000000000000000000064aca92c41561e195fa8239800c97d5242ff0f8ce76b0d119063e2ffa09c26e01d23d5728765a59bb9587e885450ad1c6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b3000000000000000000000000000000000876a57dc24ad58416f910ee3ce220630a1297e6bc691c908e6cc16f975b146872d71661bbb869361623c61670627eb0000000000000000000000000000000000760fc65097d215ab9aeb3d5a5153977e1e399e2cc0b0cb9befb0266d98ac13512a0eadaba4e051bf56794621c551ec60000000000000000000000000000000003c8e205e53075a96c14ec26345c75881a0d67c7ce0d62d73c83dc353cd7b555cde52ffc5659ab0db2179a899f0fd694000000000000000000000000000000000d7e8a7fe6b751f7f478698f4f0d30cd0a435a2295a958cabedf4668769819b4cbd4e8b7721eeb5ced3f913156abcaaff3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a21000000000000000000000000000000000180546f697349adb2918129f4d0a979bb114d1b58e5baa6cc221a09d7083469bfaa61f80f1e3a6ccde0da54b24d59db70000000000000000000000000000000004074338380e3d7c0facbbc71d83e78b53191af9ba13ba0cba6015bf4f28e4b0b52ffb34c7867a335848f57b5ce5ef5200000000000000000000000000000000148a800ec38cfc2386497d9aacb4327d5953a6612cd4067ac13fb977046688e80032125d4b0e7cb49913e489796a50ea00000000000000000000000000000000132438d18d942e6dd3f69d117abf83c2fa18418e5145cc43b3cb8d18c873935e41279a9e13596f2863be7aeae9b73d172809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c0000000000000000000000000000000018b3102ce91af86cd10162d3a43e488a0d7b7807dfb9624c3cae76f342e86f8ef1200444a57e2ed7f819828357a6dfe80000000000000000000000000000000017137b470f3c8d1a03e7252e18f4466c9ff809408cbb2043d6b226ae2746d890b267ce3255114b2e073eb66e93c55eb200000000000000000000000000000000054dc1c981c9166d0bd3a54064c33f15ab856b240770ed44adaf9f32d4429babcd0baf2c5b8a1ff80728e9c63e806cd3000000000000000000000000000000001897595f836342ab54bc2e1b72f433bfe3b5bc989727de48575abe89386aaad9b1549af3ca55f39feec14355b29dc9e33521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac68000000000000000000000000000000000467f1a3093c72aba4c2d9e8171057cf88146eb32f38db0761a5ab2027f2213c89e12c67a338b4b342a73384109988d2000000000000000000000000000000000ab26c871d140c9c4e0512afe9fb576409ffdcb95417f8c6cdc0d964011dfb1e745045766bbbc08ff7dbd6935934bba300000000000000000000000000000000183488902b886200e63465098be87a905810b2e8ebe0364316da798e423dbb267743a0d2e3d93303623fb17df0e74ce30000000000000000000000000000000012c7e79f9ba36cc47762139d191e6625c850a03d5b6e0648032d1669575704c91e48a9ae432bb3553ec66e86e082de689c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000012ef4988956e026a79e5e904ad3d7ca56793321d62cad46de3cbde8570be5f0ac86d386216152b37053741fe342de7c60000000000000000000000000000000014ff7804312754d23b251a42aea65207695d4df65cac4f87fc96cb920843c022f24cd27731224db751cfb621886249540000000000000000000000000000000006ea693105a1b2afc79dbf75504c256c519f927ea0d79ddd1997a49638a67151dc81b84473208e8078cf71d456f2de0c00000000000000000000000000000000122d367c147c91517679432d3c7b56f2d529d70040109f803b89a04fd8540a6c565354ae420e1bd4ad4ff61427332629dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd989664000000000000000000000000000000000156e7f8f1412cec315eb76f10c92143157313b8eda0677a6c0236de5fd27e5660ec3eb7369f1604082c59e1aa5f94dd900000000000000000000000000000000018ca9f505a88ed2bf595fa9b55d2356748770af16b35bd5db448990b7d41c3aac53aa490791f7ac09d2f5a087f938f70000000000000000000000000000000017c76ca9ddfcc26b028928364ee35829c6e57fda40773a6bc0c259a1b3cdea715c664d7bd0340192aaf7dec7ad20a2ed00000000000000000000000000000000082a255966c4f9d0ad6bd3d88b136cb2cfca09ed6ae378c914c28ff3338a2cd466cafd839f3fff4a30b33ee56e684f4e00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0", "Expected": "00000000000000000000000000000000075c71e21ce327a97024c8ab5fcbef4fff76260a4f8c8489167166c4a85d25096c617cceef73097a4bb956be3eae8b780000000000000000000000000000000016270f3ac86c0ec43b9472499c4d845eab488a34ad9e2148c72cbb1db13623c5dbbc8327c47ce596521bd1f54f119a660000000000000000000000000000000007ad4914ceda9fbc161121c818bd05953836a581dcdc78bebcd82ef548671c899581681c908a337618a445f77c6b7cf400000000000000000000000000000000173f401cb78024e844adcc88fcf0e52d32de134f6300216ea0da7747752ae3ddf4d181b8d266b53d1b699921f9871425", "Name": "matter_g2_multiexp_83", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f40eea638a3d4fec417701c936d61c5b233c9d6b3e94ba9addd0fa0b20adf9f8e07c6b629977445c7750acfd18001c0000000000000000000000000000000005f1ca1ca9cf67c33e3ff174a65adfde2db62e74edf30b5b0156d6b9dc86dd619ad8863c055096685d611ac015ba884f0000000000000000000000000000000001683dc67710880b8af76b464291c17fb0ee4eff3f648ac0772f4a777025c8cda0342d8f5aae3123da7fac57b965685900000000000000000000000000000000143d919ce2cf00838b10fc65374e770bec3db8ecb17d2db08b6a10ac38657bb109f54c1b3040b661c3914ded6f7eb80fa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000000616299341f2921adf083d1190c212a7941bc0d9fee50b05b265f2e8c339fc3dd9f94607631f485e394f5a7d71ae73d00000000000000000000000000000000006b2f12e22369e8aff45b6c05a2bb72a706dc46a5d1393aaa9e5a7931ccff33a5df2967189114c3fc5dbf69d080e39dc000000000000000000000000000000000981e1b119d04343e075a80dfc189000b4cfb4e321575817aece6009e6b3a6233d1409e8e584f0ac9caaed1f43e40d7d0000000000000000000000000000000001ce4693e8c14032c35497e0f9a586a4541d8a1a68ad014b0850753a04215be2bb60cd7c2fa9be4f4f09a562d7b29f3892073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000012d634764207dd7a0201703f855365f7750291c810ff292b3e8dee682d7d8eebd6d6f3b3dc8b0c9e25bd2860e031311b0000000000000000000000000000000000eb0859d79fcdef546026fcd380f5c936e64a5665d73f56d92c03dfb50c534a00c857c86ec43275ce69cccc0b53137f000000000000000000000000000000000131bf000fd117ef722b33a1cebd28899fb012e1113f767d0ed46fdad82a32e4327b883fbe29abba1bb7ba3ecc1cbab2000000000000000000000000000000000e24ef1e44029366ae1daf06524d8beacb2b99f60f419cc2ec1a49013b79fb7a4781dbd37785f32ec67c0a28d61a3cea2117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f500000000000000000000000000000000067b6eda41cb8da47a424a02a142e2b98b9c69e7023cf616040993f41507798882194229cb6572806e82e9e5eb837b37000000000000000000000000000000000e38693cddf130d3645fd60ade780db84fa700e5bfb74ebea49cc95ab001bde442f363b4e4c61f683b3e67f1ec8c2af80000000000000000000000000000000006d593005cbccc55c5e336e19aded70da65a7fe42b6a85070e491a4ae54e18ac213556a91d5d62786b6d4d1305525a76000000000000000000000000000000000ff86216f5388114dc06deffa7b52a273b22fe0bc8d50804b491fac83e13915c2dd1b8c2779a46b5c313c4e1c05eb2979087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000262cf4727703fb227bd7fce6cd3f25c1897011ab892e79fa47446711d6867ca82b9b95f129f7ca24dcb60ac75173d4700000000000000000000000000000000136b5a304807e029d0a77b2ed505ee5c920248242f0f95aa07e9bc2e13d35f6f67451d028dc19d26095b55cdc2fae4fc000000000000000000000000000000000b511b2e19da7bfeb183f0aec91bc7db3e7c913f1c282e12d5d2f422a49e7fa78a5f35656dc9c980324717a5ad386dc30000000000000000000000000000000012eae443aae59fdf907bcfe3ee4366e252bb57e268fd569d742456f348429f009f67bf92f9dadd401104ccd2549cecc8255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000016a827938d8b98e3446445ce23f48a7ba0d634de826dd1ee3c71467eb57bd4c24e0d1b4190f47bd841183baceaa2593e0000000000000000000000000000000011d360e0c18b45ace82eaee902475127d8f18aa4a2ec2081a453f1c85ffe3c59c0f7016f966574a7c51bc14f1935568400000000000000000000000000000000186b5d452c6dcc1ddb4f47b07e01b6d64644f6d01cba8498c3059cc494a68bd25eef35cae05885b9f2689683e65161410000000000000000000000000000000000ff826e5a62affbfd6d2062bd329fcb561f287046870b8be461767759cb0d5f1ac904ecd1f136c5ccd784bc11088233e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c4000000000000000000000000000000001010e75c52ed0acebe30fc588961c849b7b6298bb8d859f9a9401737c467921c5e3cda101cd4e38e4318233d12b6c7b9000000000000000000000000000000001884db518fbe4d621403ce00521878c0d419d8cf476a1dfda59b7d3c7af2bd91058bbbf54ac0c5cf9a217beb78e3f98e0000000000000000000000000000000001272cf0ad917738bba052e88baf88347d60f63f5b875d604cf0531c1ba7d43e868bc70a682b7274067106f611f08ae60000000000000000000000000000000006e3236f6a66bd37af4be230d4edda6eaaed661f206ca4852d3004b5f358f184d80be6af81c62e5bc8c88e7a1072fe21fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000004fd1e2fd0d28db08224fa7e880abb8c48dfd0e488df4d2ae5f6649f448193acbe943baf22af4b12fd763e3e4ddaa08d0000000000000000000000000000000008df68f276f356ade28500eeae3b755c9af9b5acac5f5f60827b5b2044b2405129b00e5271baf9a80847d3b720026b3a0000000000000000000000000000000005e683d1556f513e6d093704405f312687c3b9e2de3b2840fff32e88186c89b18d1ac558d960b1196594730a9bc107480000000000000000000000000000000018161f8d23c394d10ba576fb0ceee530ebb95a670f2589d84c0646f693086ecb7ed80e556f3ed9434d7fa488430ccf430e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000017860708943449c2227c0f50cf1274652dd32e999d5f9b1a8d672feedde15e9f1af484a7b9462a62dd745bb6d3c7295a00000000000000000000000000000000064f8cb707494f82ffb6374641817a466af65f5c7d83cc2964e6cb8efd021e0c40934a3ffbb0d91bf8a7a616dbe8d220000000000000000000000000000000000eb37cc9d56fa0dbf050b557aaeec76f9f6d0a6c448ea298af78004e41ecd8a1df8fe8640e77cb76b593ee17658326ff00000000000000000000000000000000092ab597967544fda640b145edcb3ae6c3f027c2111dbc282ebdd48eb93287ae4729cb30e45c1c8999b3a45b099dbf0ca49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f00000000000000000000000000000000124fa4d48ffc5732fb21d465b559e995891fef98370a1eb73c9264988f75caa93fc134fde7f93c794582ba5cbf6bc685000000000000000000000000000000000b71d012abc1558e49831f053757518643ae04f79234fa92023db9c5483bbd872d24eb87a78960f12930094c4f8fb70e000000000000000000000000000000000651cf0016efea086d98e5bda8e1959e20e4947e302eeb021d196897cffde3e2c28f783521b2a28b8de1ad1a131f5e67000000000000000000000000000000000555ff8a930cc11d320afc3e0635a6f93da1487a5764d56636be4e5803d740a73d84666f6141ed5ee6b778a463823fbeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a7000000000000000000000000000000000974e769869719f0ee30895df837cff50d47382461c557abc4b8806b04776f401b76a5e630a6ccbd3484980d03ff58d300000000000000000000000000000000098157f0190e6bacbf34c20310f6471166750ea1b235e46a5fae313f90dddc799f21548088322910bb0fd7e41beb23450000000000000000000000000000000007f00d7d18719db9d91e2c32f51083b42c4fcb43c38087f86879ad6bc99600d4c395586187d26d041ff49dbbe517fca2000000000000000000000000000000000510cea4a7463bc5882d0cc25fa967a0b02072627bd57f9a5863fe5255953732846d4907fa301789bf02af9c1b25211c53961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000bf264c0b7bf68c595b89453ebbd7fe2e64f4ae2c7268ad51f4578c35d48040277f3dac9021997af02e492039348efaa00000000000000000000000000000000083a4fea41cb1e02e5002259f5f7b335c81e15cca93cbc884dc1b08ee981c55f2dd3c0db1a35ac9907435edd7f0ba625000000000000000000000000000000001468e508a02ed7b61f752ac38313345338d2b2d018f719f391c0f3fa1dd1602d9476f3d8829720d17021a459a2732e96000000000000000000000000000000000629edb2530c38ead8717b289c08036c12630cd8c9ae875111749ed893b8cbce40bcaeaf13df4044147bb665ecc2319ea04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001635830ebf227be126e13c634a84f3649d498e0999ad2dc73b9c7360db120dc2216addfe18c00676ed185efa1e789d8c000000000000000000000000000000000471e3cfca449bde0ba2b1e2a5b63d53badcb34da3251313190a35daf694d70ba385976d1f875242386fc74ae0173d18000000000000000000000000000000000986cf3f1eef587bcc70f66f25c60f353e6b15bd105fde9254487e9b522159658d0fc6b6a8a3ea38c27865f1ea4d76490000000000000000000000000000000015a2eccb9c10bc273cb712ee04bef01a11e486bc6a4d220a0f653582af6ba1bac0b5108250626ddf126f16f4015c9d2cb688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e22527000000000000000000000000000000001213cbd035615f09189171b3e22630d72df2df93fa8c14427bb00c34f5b55bc8d1b1a59404bed6549b582537a397eaab00000000000000000000000000000000161072d8ebec2841f0f34cb38a3e1b2094a597640a34178ee951e5c993646ecfc3a4c0dd753e7e76f3a6da5a091f9f7100000000000000000000000000000000077e9c95b6c6f726902392c3a16b5cc71cd9d4cec58c00eadca6091e45bc095e53006ce8ac8827565e867531013821950000000000000000000000000000000018cdf909bd9f38e57ee24c0f51a5f9f703eb3d190dfbf75be00969e9e8f8fee331cf32d93c3a956d12f374f8752c2c79cf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b6000000000000000000000000000000000cbc27995eaeef2bef14919d48a008a0b0467856f8a6659d6e68e47a2d9d41d217c5913aa1d67911325dbd4fc38e36eb0000000000000000000000000000000010639740654bad5c4ec93f2496f4dc54a7642bc92ed03372ad4edc5fedcdfcf37158d3f02279d4e15078e9d5a7f8b5df000000000000000000000000000000000155ff4d6dfa031b0cc2f57df41c1e1b1c81bf5a5cc1e3aa93920e93c2e2e7a71b56ac410a87855400025badf6dae8e60000000000000000000000000000000018e637da048e7e84b9d1654113978fb148a54d86e1d011d7f5a86cd4f1e5bc15abc5b67d00129f53c0c021cf933f399c528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f3650000000000000000000000000000000015d8f6e47b8f07b3e07ae0952a7c8f79519ce2828e3e26b284b8f2fae7432b337de17089b5c32f0081ec6c6916f2f53f0000000000000000000000000000000010ecfcdb02cff772db667266cb3f99f1dc28004ffcadca7a9c64b3b5853c09b7793ca0aadb155257bd64fa7bccb390450000000000000000000000000000000011096a52f3272955947304ba037e8b3fce6b2f07f2352c08d5932f4d2306ca511a74dc044d0f0e1e260ff40b0fac5e0e00000000000000000000000000000000130facbe0c1c6d077e9dcab647a44b049a1aba3df500bf27d1c268f71a59635e702c9ee1bdd68fbfcff7ae5b3e6bd83bc32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1", "Expected": "000000000000000000000000000000000b47d58802579e662f34908a4060becd40434e4934ff58790df2a69a759223ca29f42e658ab475cb92bd9c46566811c7000000000000000000000000000000000091d3a4c58a669d3bf0377abfe28d1817168b2a86375928d95df3459c83334669a59aba95ab2b9957d5ded0bd8925910000000000000000000000000000000005aa9c3fe0067338675099ee32f93bc8a5e9ead94b120dfa391651da40cf1ef5ff79d193b0b14b5926f10660aca6c11500000000000000000000000000000000058200992b111461f4d737533301734a5c3731c9f2e7b55e18887ebff4d5b74dbbfd23773606f54cd6a930b85b89aabd", "Name": "matter_g2_multiexp_84", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bc3eba5594666cec699a47fa13ee74722a80b4e5a1b28553de37c3163f2fdb8b801e35a19f6d99cd207d1642c0d4ad400000000000000000000000000000000104b334afeab36961824611f0fb58bcac55c9d36604ba73e9c8809085c019bd967cae7147c766df6565ddfcc0cd5fc9700000000000000000000000000000000026c3a6a4ace8638bf8ba7434b59c3aebd4bb274cbdcb898ec6a547b215f32d10395f3bb85a9eaecff5ef6d5ef2b50e000000000000000000000000000000000065bc419f9496b5e81ce72a13fc1bdce4738c2e3faacd80676be31db65ba3e7941ea75e370b6d6c0e7b2cdcce80a2fa14f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe000000000000000000000000000000000e7f1f5ead0f212439b4c47599581982712d2e6ba056f36cb04033ff5eebd81b5b41b874a78aeaa98562899418ab04c900000000000000000000000000000000095e45da9a4b2578cedd13af71e289d0067ecca1f09c014a294e0b250d1e8243ff98a9761030ac855a9d897cfe9fafdd00000000000000000000000000000000030b44b150d1337a3ed6a77f7b6332d7c8103da1aef0d445ff7467b4863e4c830fb782a81d01a6bf97e8d52bf333e78d0000000000000000000000000000000013bb76800375a45b847a96ef6edff3fc3c30e3d45bb4afe04230107f6a1802794e1dc23431797bc5e79e0d5ac6357eee4b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000602e0bd3d34415ddd517a73acaed5750dcfd68b633d51003edf79a169ad7a3ca2541d7a131c317c957a9597a753b5080000000000000000000000000000000007a964539081fff51e0ec24bb71257f6a1c513fb0047aad84b80180b22133246a1f62958ead75e4b2a68f973d17f1230000000000000000000000000000000000f48fe0f5b5a95e48bde4d8be1b2f352d748c1201b064bc88309a709b285f81260d4535d3e1dc7f1d6ee23ead35abd9f00000000000000000000000000000000135b480fc8a72248f7a4898fffc6c18b7f2f5b1af5cc3846610c299c8da252fb71190d9f761e037c6b47595bab6a03e56765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f0000000000000000000000000000000004773cd2884e52c2551c1ea0abb78fa17caacfffe13f18b75588484b8bfe781e623075bdf827fc82c6ed7e1d4d62081d0000000000000000000000000000000007e6023fc0e409bfc7d0b7ca65fa0e8d88bf1b4c202a8d1f0e1c3479b0963a646d16795fc5a128a54e624357050fed4000000000000000000000000000000000039f6eaaf99bcc9f4d8fb994a040af0d29c37960e9015d4e48466a9e554da30975c5534e76a1f08a55ed8ce7375b70100000000000000000000000000000000003d2b097d4afdde83a01cf2b4f9d12f77c8e92a8cadc225d40f974ccf385ae65bed1972a365d55e24231d58abed4395a2eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa00000000000000000000000000000000047b8c550310ae246e43b513d39e507f1dace7bcb543a49ae0854a397f62c408ae3632c94d172669ef3e013781796ecf000000000000000000000000000000001592914e260afaca80c0a240426c2828239ad5e256a707530f49cd65e9da2e4bb14a7d6d5978f52c04130a0d434cf4ca0000000000000000000000000000000006c0b8448ad87350db130373778d414deb738d3be97fba25c816826f59e3e926f44956c2e2056b7d769278cf56cf6fe0000000000000000000000000000000000a42d716fd83071bfa014a9b7af6c164d494f0347aed953bd2c1c97ade087a8bbea9f53c507fc0b22d520f28cc5d480cf8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000018026c9f6c86219d0be88955ce0afc3cb637b1c3a531aa2722c56816d368688181ef2fedf1525daec6d9b1651b71f27c000000000000000000000000000000000b40b15bb0621209bf9e33ebc27a7502d90fd3af62a1bb8f54a874a14c105df34ae34a43fc3805c1e4817ba30c048ac7000000000000000000000000000000000465262367e30ccc24632d39bf3af9cb160e97049d855176f665a185c138d5c529d11e53e56c65506e3e30be7b48c6730000000000000000000000000000000009485991319a311052d883b45911be12cf7648b5ca104ffe77594472f7047c803b8e9fb753b98645e630b9913bbc947e28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fcc0aca0d873cb8733ff7e2ea02b3736b737821af2db06ee6508e161f6159f9d944372c513a03cc4c9e30a707dca0930000000000000000000000000000000015c3774f4e0b30c9532beaa2f7f9b777f8d46bfd3888d6835f4a5a046153a98062efb17f78807fa17b3a995ce720c0b900000000000000000000000000000000083d48e01d2fb58244861a74a1261063f7d20b412c8a44f9945fbe373cb4b9a7ffd4c4ba4054ece0abddb6c14c013ceb00000000000000000000000000000000133c4976454b7be427c4c2ed437bc2e882854d2ddce42d2f97cd3fab1fcf60c3272aaa123a0cbecce1a774946bb7a8a0dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000f6ae7de1dba3b3030b208f61d182013231c4666f134b007b52d36bceb6f3cd77577be7b11abc097cf9618d351d61e270000000000000000000000000000000005803904e3e640e51900805f930638ddd8b86cc1bd50cbd32a142e10d85044cc52ff769bf1b40dcfb7269c913d00b01e000000000000000000000000000000000e6997b1f8bb649c56de5c4bf9968d19712abd22fb7dabee19e0aebd1b13adcd3e8b202975b4edc917d93adf087fb539000000000000000000000000000000000a32384fe03280962c5f575b47192e5ef3111fbbb0a01bda2db1e9733471f11eac0a37df8ae1a891de311770c482c06b0adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000531fad86551ac6dee15fbd62cb13f38d8d5c89d23a031b9977f110efcf16501534757bc5b93f0250ff02d6cfdf2009a000000000000000000000000000000000e6d78343049a68514271fc785de053ed7f50a7774b87f264c42e03e6f8f86285477f8cc57ae066ef0fde237c8d1ddb30000000000000000000000000000000013e313484da4d6b85634c5306444bdbe45d7db823616d72821eb64a2bb5f352a4f7e4273fb6557039fa563ce1b091bea0000000000000000000000000000000009a40a984be66c3442fc8946cc42eca722187dd819be9ab34a9c3b4b0de7de3d5f126c175fe84c51a6f09e18623214f9345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000008a76db551280cd43d4608e9fc629a021675bfdf9bc5a021546b92f3734acff1e97928850716b94d15b7dbcc4a1e0aee0000000000000000000000000000000000b2262872c268782e8f27ee8fefe0827d45131555e755c0a65a7c8b4185269bd621412b653348d7c1111d681f38d946000000000000000000000000000000000dabcf0f847045e01ef70ceaa32455f4c962e4657b840f97a1cff7cf5073cbf4ca8ea75a4887076f155e27e8d7406c95000000000000000000000000000000000a9c0ed94170eddfc485d9f1a770a8b493d4a59bd7156d6cd4b95b55bffa1b597ae9d6fbe529dc0833634d75906a4aba5ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c00000000000000000000000000000000170ac69c2bf9b48715f445524cab902b18ce6dea7b258481cc59986ae61c8fcb6708b1457be299a6e2f6f34dfd936fdb00000000000000000000000000000000107e855593b6f3bd2982a65167ecead47039065c9ae6e1bf963f81d441f0ebb411eec4b3ed1cff73044f68a4c114806a00000000000000000000000000000000063b470d158ebb4828e875c3dd0ca29a4fd2cd2af356233885a871cb5b77402090f29709c6d6a78f612c8ca4df2f4119000000000000000000000000000000000db75a60fa0b425b8cd2c955e21846ce3c407cb3f96c472cb412498143cc60212de0dfd0bf4de53ae3b345232180b4ad853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef38000000000000000000000000000000000389e79154f627463a7966252deab10b5e809b0c2a9e90989c56d4076b834e2081ddae1c02a9e01b71d96b772766fc680000000000000000000000000000000009109473c7aa614334fde410951a69ac45967f7550890e01b05279b6dff394775dac51d583ae0aa82edda18ecc5e66240000000000000000000000000000000019dd51ec6783c1618a7f12298e38cc75d4fa32fc31438f67eb15419a2f0e9d4b5f70ea59b69e531c868475cada519569000000000000000000000000000000001121c7a6cbbb54d5e30a11a73c158237dedac46385aa15d93592a30fb64fcd94a674cc77afd21a611f704734337905596dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d000000000000000000000000000000000178670fb06f5eb8a4f182913f46f66147deb3f9f634d620ed55da2ccc88895e75f76f55b979e1ba3c3db29710050c7bd0000000000000000000000000000000011adec68ef139716ee081db7122e911ec5a6e1fd7f681a96a713dddc2b742b6e7cf7485b8f45e7ebdec8b1174c02eaf100000000000000000000000000000000089dac9a47cbdfead8536d6cfe8b94d316123bd92ddf30091e16711ff4651c4e2d8dcaf6c72bc159d7de9fd832c6f5be000000000000000000000000000000000c40b871930f0c6826a943a229112f8bf9a3b7d7e07139e1a7d99f97601b6ca8cf3638e0265743dd732cee17fadf996721d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000001040c4cd3c28a752295b115fd80c8ef0e538e1a3906e0d326e46585d633140bd6b8231f50d50c8e7a9018a625c4bdc530000000000000000000000000000000008b966d9433bfc3bede4ddb005cd0c256a168437c31b8ecc83e6fefa6f4b1f2bfd057c78f82bb76279b74a2f7de493b5000000000000000000000000000000000c0f75db7a17e4b712666b16c31b10bb935e7127eb9a0e59e35ec54814a9de9012210ff1862aef5f765d4f7f673c4962000000000000000000000000000000001015e63589a8b56aa643a79c5a433dcd8f4933a10edc9921bcaa7098af435f7879a40868e25d1ca6f7852800df29c2eb3d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000000fab22ab380043b01d312004057488ffc958168f8fe4d9c86af622030121e14a46c4308d711d5fa9a414b9ef75d51ba300000000000000000000000000000000047c738fe5272e695f421ed463ce0d6308e05c23b6bd0973df9b55ca96d89c0771a45d53b4d17f30d8cf08edbf94490c0000000000000000000000000000000017bcb3ed735e5a302f76002ae82f4ac74889fa0e966f0fb611fa6a6a09440bc923f447eb6aebe47eef917753b7427efe000000000000000000000000000000000b189d5b64578eb53ad850c826082265e506ab620a9ab9684cc2a53718f26befc35e9431af012306a6190f144a9632bf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000049fff545ac239696c995eacc560580a0328af07376f5ec819902e30d5e7e40d5fe07295c4ccf54d5c06134370373c1b000000000000000000000000000000000bff448d5ab544a8cae0cacd216a6b6d48f0abe1b4bc946d95c1a8c4ae44bf049c3b572675a5e20c1b4188fa27a867a70000000000000000000000000000000011dbc52baa00712f66def2fa8fc77bcb07431d3285774e2517dcca65e611f07aac265856cdef0c1637def44c382230fe00000000000000000000000000000000090af0898dd578123c65d1f818c3f33866e4acea19aeafbb31bd8da029ed1daa2d7ab3b22147eb32a09021f7a78fdf2acc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3", "Expected": "000000000000000000000000000000000d52fcbe9f1776477a9d2149ca55e0651fe9d098a67209ce2e7d772d4901ff2c70be432b53dc94886651865a81ba8c620000000000000000000000000000000006b54871379e2be969f86c72cda9acab9bc99f73de987f17ab8b25c63c55ffa2cff61b87e8c30d9f712afb62a2b9cfcb0000000000000000000000000000000005652612b19c38650d1babd4772722ae2c560e2914f2e246725cea86dbe1275a981a592eb55077ee4b7c6090e84d2ed3000000000000000000000000000000000ee37a6d42ce69aa67cdcacb19efc230c6c34969a2e081ac77e8f9d45128a6e8fff923c7647a0f168fee18342bc6d845", "Name": "matter_g2_multiexp_85", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f114e56d10dba7945d125fe1ab7871d9510771548d8388a2aec8a481de92572645b73631f9a60285c3eebcacb3bc0f5000000000000000000000000000000000667d3f31955df11e4e7896a1856fbd4e573f1cfc906b3953b5806a5d01dcdb96009d9f148156a3828e822435f722c5e000000000000000000000000000000000d7740ae776eb4766999f5671315c8965ccc84ff71757e361fbbb55babeefb96265c97df8892acdd6a9166641f656e62000000000000000000000000000000000166529d1a76ad784557384cb971728dba298baacc2f2a39ee36516bc7a761e9a7c29e385cf5784efb9f6e60e998b01e864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000133e0b08430d9318d98bcf58b3d8f51c7b717fab56fe25f434bf521f830c7d4247d87d3df910490be2ad38adaa8eec26000000000000000000000000000000000e15afaee4f1ce6290ddfbc13cb887e540efc3fd8150dfbf3a5e7c759ccb8f334ba26953c7bbc43b5234b857159f6722000000000000000000000000000000000e4cc685524d42ea5e435afec7b3d7d025e93ea06407a28c246a39dee8ae77514a0bb2d5031f7367d658027299762bea0000000000000000000000000000000001b231237f7b0538d51adfa4ff92bf313507996cf5255f191875970ed4d946cffa8620b44045f4bcfd8f89baadd331fd93064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f53900000000000000000000000000000000084128f1848b2b244e4812eccba01287b9d07e85450459c8c42b01180bdd843058d9926f39e2fb5f610651a00233e31f000000000000000000000000000000000055ee70765f2cccac966dc08abd4bba0d004b379a2c6bf188f300f5d413f84e77ca1d462219bfb820d7f585b914a52f0000000000000000000000000000000002dd8f1d1cd85a5e6ac793f7e1e3cff887204aa4a5fed92f2088c06eae95842ab2c04d30d56f4b0fcfe61379e8e7c6940000000000000000000000000000000013e318f8b6f4165a8096c76ada440154901de42d69c38e66d9df4ffe5476666ecf7068e7163f29f04972682c43f3b0fd5e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000003415c8bab713aa18d3f0d54e0101ba36793e6e9dd3471f8eed9a15e00d8312732a9ce88b5f0c30207aed92eb173ac680000000000000000000000000000000008a7e145e9576be8ba2fd980fb1735a2b73d1bf5f3e108878b721b6ed8378b5e0f03ecac179a6d148541096ba483b40200000000000000000000000000000000029e5554752db8bb87d58275268f24ccfcf3e0923744d57473d54a72e2cccb847eaa8f3bf638833a934c43930fbf30990000000000000000000000000000000000e0f2ead2697110a132c4ce1643b97fc652dd0660deadaa4e0c45e7ebfa64cb6a6fbbaac7c4e2b725beeadf6881ae5893f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000014325fcc087aa108f152b42759cbc02cfa24e7e7cb995c78ccaa9a283ec2029c08cd747d599e0685d365ee99eeafca880000000000000000000000000000000011da603d3a1128329af19e596ebeaa4bad034c59581e9fa2e42a0260032f84654bf5ce22ee32c34eed7515d7fb0fade0000000000000000000000000000000000189cdb5b934cc1ec7ea0cf4b8158a1416712bb59c1650e6d244de33bebfffd3691b499b3ff8255b1b513deba709f7d3000000000000000000000000000000000e7ab2b279d0d5933df25d8fc4faeb8ca907e7bb8588e618b92737fcb6959380abc205118d2e3fc128b89a2ead5ca906145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000895b6777e677732c74cfa82d5348c4c8ddd63ce10347836f5140b9a64dfe631804ea3be8e20bd4438f5e7fa14a121d80000000000000000000000000000000002422cc4781f007f732239ff9eedc126777d6ca0f0365dd90bab6b68c9e3d02ce726726a6d30d7d51a1f0b45aec1854100000000000000000000000000000000048af8a79663aefaff77a934f0af3a09ba02077c13a794ddb88e5c679ce348b3ab0fa217954ce1422f4e212d1383ebdc000000000000000000000000000000001190fec6c510b0b16e1505f737b25dc2401e9fc2c95bca92aa5d6e93b284b766bfed93a80b137e5fcb339983a86acd41ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000014fbf4d005f43563fb7408d1f20f672c8983120c66462ba9156b64a287e66960fecb41ca129b6b14466a5a0de91b81c50000000000000000000000000000000004fb283724950174d60f64af7bc8a7d059431332c8f17769df33f6607d72633aae3a8d595cb8d5af3f8909297844b3a0000000000000000000000000000000000e187476a19280ad9f33a55c50f37f765e343f92938e247ec9fe099c7f3df65e24af14885539bfcf3efe3bde9f2700ce000000000000000000000000000000000f086e6b9e845fe3b0c5100f82bc8aeaed166bed9fa4d34bc03ed86342a997101c508a4c096c4f67cb5791cc1a1fdb8187bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000018dbe48c54347635d4b6bc17ff5ba390a73925f1b180d2c516eafc0936aa9bddaf7317cc0c211fb2a7f7bb096369a45d0000000000000000000000000000000015544c177a4b8018ed60c2639b43236957c2d995fb0f32523654584b0bf052e0930366a93406e1ec5c6d2edb955e811d000000000000000000000000000000000802d2cdbc5e15b25c77ded4bdba087f1d5760e6ebf9549a37f3314b1e88d3d6f58da9d8c6e9ef85028a271b83dd6242000000000000000000000000000000001577bfeaf213ca8b0983cb178e9634dd18f74baf02f6ca31b2e3b287d80a32d4cf11afc71df09ca5bb0bc8e60fc7ffa968cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000007c059044ce0c15bc527b19ce85cade8b1d5a9cc6dd304ce9a3c461e631e17c4feec52a0ab5cfab6a2270c75f73df86e00000000000000000000000000000000076344286cedc8c180e3bd762f12ac08f0ecc51293b9f9b8e7c0056ceba1bbb6fab4ee39cf559fdbd601db6c3d201199000000000000000000000000000000000bf6e708d0a4fd85c7566804e19f21f7a00bcc3bd7135f6639ad30aafef2ed1e72c84c8995b0e59738c2bf1e4040621b0000000000000000000000000000000018ff3d0ade15b690b6e306adaa5c10796b78ed7f8a984f637271cccfd39fd17c1e8288a11b051ca94de2a9bd04fa96d7a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c500000000000000000000000000000000025cb808922f6deb0bed979b80a675d9324cf25c53de373534d771afd919a182af9aa1dc26a2d0284887121bf4d6b6470000000000000000000000000000000018970aa4f456c1b203817322df2e222516bce67ff9ace069599061c6229596e506c0286171f3551302e45b7d3b69a39f000000000000000000000000000000000a57d0da60f03fd4a5664546f9809c771ab6188aca5102c31f26b09950cadc26b0275417ddd9c4f4cf29794b739733cf0000000000000000000000000000000004ebf2bd93d7921d8bd97ee71cadf91145e064a33651da2604ed6fc8e08b1b8305005f12fd4e6b68b7b6a3b5cf123b1324c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b000000000000000000000000000000000c8ff29d0333e3f38fd8af91ecdca49e54ea5dced71b60d693b1bbade99ae668e4f994f7a5417a08a8ddafa410d437f300000000000000000000000000000000078ac1d0898a9e6cae29fe6b50e435e5f543d0ee233346728c46d659c4338295f27b42fc4b2851ad5035feab2bea8871000000000000000000000000000000000b3a566d2ef4467f21c27e4a3dec99a26c304b32ba1fcce8276a8518383a7de44de5b4011ba738dbb8761e67e36115560000000000000000000000000000000015a0aab8c3d51fc3fc8aa35dcd07f8a08188976883f9d3ccc87ee148525f2115ca46726a2e3c550167c169977b216d6217ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a00000000000000000000000000000000115589e8e1440edcfe72c008f6e9cdf13fb7baaf70aee16166e7f32f4651db784f4c5cac15d91ee13001169fa777f0d00000000000000000000000000000000000f86710678b01c8f648bab2289e8f90648d9470cb13d5145ade526696d22508a4a59164290586c2c000dfc55b4a20350000000000000000000000000000000019b300961b40b0d9fe6e292e9357d04f0483ab3a8cc6f8f522153c51d22de8e96a812adf720d13ff7d05d1e68264638a000000000000000000000000000000000a80b61ab051ce413ec838167fce393f88c8a25f403bdf07cb60391fb15306a5271a7042d36f7c46b5978106a7b5293c4615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000567c33d22805319418cb1ea7eca6205a6c44f1f881c03e37bf3c66a1baa5153473cc73b8c25d497b0b0057ceb0395960000000000000000000000000000000014d7a2bfeea6a746e709f6108eb32581ba38a617e4450b3567c77a992988d91f4da31b209286f8e9fd0d7b8628aa6c4e000000000000000000000000000000000ae6c9fbf0e06f2e38e91699cd21596ba90f92f6022a4f3c7c8a6557b7e1331283bd4d7a7d31d77d9d7cf70a2945ea1600000000000000000000000000000000066b8132c73e1da8ae7fec9169770a188b686f223fd0306441356040bc9070f34a47fe1bb8c94de9fd7606c18b1d2b1dd38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f7000000000000000000000000000000001460040d0a19c37fb0736ebdac0324d8a38c94a73fc5f602b7ea5b7255be9d4b6ffc22fea5043d948420e9ae3476f56a000000000000000000000000000000000b37c0078ab8babcefa8874c6cd1c5184d713b976852d087ed84337073fab3054899859d0fac2f4351bb75ee0e534fa70000000000000000000000000000000004150f3b98e6166d9d6b0388342042dd8eff9b8e1239f479330b64c5b316f98fc7bb401b737efb87e1f6663ca4efa26700000000000000000000000000000000043e6131c1ff621fd6f8caf0939487a927550343e24425ada33cf622de757e6e75c9affff9f04373a954557181641617364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c74590000000000000000000000000000000009fa1754bbc957d2a8317a2eed859457073571379cc7c6d65bc6a0b5829f8142db77654eb98a2bb0cfa5223a27d756cd000000000000000000000000000000000cfe8b8fbbff7507d3d74f4f550b4c85e19b8929d3728a462e12b4008c79014103153c69ed8dc6b743e1b6fb4720bad00000000000000000000000000000000017ca0c08c320c12502a1dbc841425694bde68b7806eddbb40702e58ed26c7e112f9a821a6c67afed174f51896ec2287300000000000000000000000000000000014d08df9cf825b07a387642ac9959e8cd15ea8e752231a3047fa30816acb1ecb79f1755484af9a98b993f50128c2bf5031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f000000000000000000000000000000000273b64e867a9111e257c9b32484655e4d7e676ec50f174d9ebc9fc4262c037b176ada941dd8c1abf645e275dde04f4a0000000000000000000000000000000008a63b9604e96a5034d92e3790411f3112c2c7cdaa056f9f1bdfc0b164c37fc9f58dbb566337132cd1626f9ca2618f800000000000000000000000000000000006a661167c9fb6c26bfe0a3902f309fa683fd22729bfcb433756182e7e1a406bf44ae1d13ef0228534881daa339394e400000000000000000000000000000000193c6c5ec200d225c43c6e37cfd15e16e49b7d87e5515bb7b4c918903966f4f6ae0d42af6b98f6efdedc9b0301fa1c0f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f", "Expected": "000000000000000000000000000000001403c7e3059135ebcf5e752011fdfaf66e348135314f3f4239b066e1c6192ffcaf89bad4228fcc2be19a64f4f5386f5e000000000000000000000000000000000aadbd8d0e53d5b409f7fa508089337bcf36212a3f613b37a95757793dd6b0ca99d1b3578ad8020d46e29c9c4197ea070000000000000000000000000000000019e43bb32f92ed187fc32d9dbe24a486e38316a3cec0fd7f7c19b313af43a10fd63738b78e609e04a083de6761d53a90000000000000000000000000000000001490da7d36ff16304b27f6e57412975497e9f3a6d35cb162464bcf69fe141d34ae27a33afc75a2802eb120e90d4897bb", "Name": "matter_g2_multiexp_86", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000038ee0c2c409d8832437ea450ed705589c82791b8319fd0ba6fb4d302d3c5b73ea0521a0253716e5810f03fca2e9dc720000000000000000000000000000000018c9d748aa685bf6e11e6e4b6ad2290ceff59c8837a088b41a08983fb2c5ef077adb0730b298c5df9aa02a820a19a4bd00000000000000000000000000000000015d248426e362ad2489c0c6a567d80b22d54d6a79e198198a771fae4c4e97eb317da9feba8eaafc9460ef45b1a5e5690000000000000000000000000000000005a2342412801cb37911a04d7ee3b1e5d3dce2a06e0658d59f2ddcaa9ba32804a1ddbe8f4d00f4436aad1346ed1ea5344aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b90000000000000000000000000000000019d49748f05458cb9b316e433b0d341e23bb5aaa724b824bd147596761c11efe8f4940eae09e302e563e14e96b814f4a0000000000000000000000000000000018011e7ee4988da168adbcf81cd14a9232edacc06bbfef0fc78dc0f96b5ac86ea67be8661442b5ef60e3889f3137182200000000000000000000000000000000175a2ae3bdade6551b23656c16884ba0fd4247df4ba7471cf81022d7e224b23490db153c8289f95467ddf9671f8b6cf90000000000000000000000000000000013c58c0f55c46bced98faf3865e3b6a836252f252e97b6d2a799b574dc569f09ce33082880a4d0c3b8a2c7c0d4c30eae22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000ee45d5689a8ea6132d5ace000699a157c1cea3c0c98b38d504153d64fcaf1702ac7a1cb0889539d6b15489fef415aef000000000000000000000000000000000b320e0cdedbdc1fc5733488e6d2aece6386a030adc36b0a69dc3809827319947049f3861c2edc859797d30a3689322b00000000000000000000000000000000194096079b3a1d6ab1080dc71bf6d5734bc7b5e7f30bbb0f9b95c9495a6bc4adf76e198fc66accbbbaac215a8932d8c5000000000000000000000000000000000ec07be0cfa9b3d3a64c016471d9e6d25228b46dcaca6e197be00b9ca5087162c35f1d6326a3cf83f568cb06da8c5220895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f6832000000000000000000000000000000000a7f3a3fcf2e7b0ada6d4fce179bdf229454002f1271a39d5e99daae72da549c6ccfc7c574f35bb9784100675c30b1120000000000000000000000000000000000fad14ab095fa09bea919ada313727e7aa5aa06a1cc7746d006e3eaf70f79c5e4001a8a8de03540b45e0598b22710e00000000000000000000000000000000015345ade62c5691690c181da09d8f39c1ead42046987b8c7c975d40690a286a816f8cca519731d0ca23349c54b30d8570000000000000000000000000000000019f0a32361bb6ecd8b1d87c2e15d31c0e0cf995eac9facd5eca123c0799c465f156b0142d98e0f315e9b3595974a7b824c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000017af46e78904915e348734d2450fc6e1938bcf002989f855082e3b4ff3366d81ee8d28293609c3c3b11568668b1305f80000000000000000000000000000000018b0b3859763c2654fc00792a5193b7317fa5051bcfd15ea42be2fda0f43adf322219f34e54b2446ef73a4562151f9a70000000000000000000000000000000015c23509a1b324c649ff878d004ab5f253d041670ef172ec4dabec7a525d5ddb8f9f62f383e3f71b0e9c98532e247d560000000000000000000000000000000003a38564a55fdbe05b047281fa153f736edbf48c901749005473255333590f967171a6fc88751eaf57a5335bbfb6ebe86d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa00000000000000000000000000000000100322c4a92c136437714a6586c82a6842027ee218bf1fdfffaf95ce47c9c8b6c8f61115b092dff81ff2e645d0a7a4340000000000000000000000000000000013a91ed8629acb5e770683015c3c248255d673d4b2e6c96334d1c80326d1a8b4b655c81175e4a914a45fb37c1f178bd10000000000000000000000000000000019075c2eea3f64f42be82fdb8f83f2c68c08e858702a0225d869143c0b017b76a7a40d809116ffbdff6700b288f5ca3b000000000000000000000000000000000598ee9ba9d56400b59c7f5977aef1e179855a37179fbfe97b95f19137b6034568e5c7f616943b4aca804272955d42334cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b138400000000000000000000000000000000025a526b137aaab5ac1b5f8179a18b06feb7c905b4a843cd55e31b7464c2b6d432b569e9bfc3222511c18255102aba5b00000000000000000000000000000000090c20c9f78a242e52daa339d5cc1c3f35aff7ab802a3e4366597db8b6ca43d30fa0fe8d9484e49fa4fd0bf5509f19e6000000000000000000000000000000000e928b2173e32e5fc9c373a2a6f126e1a3a472c01a5e87677be0d29907022b9a7dbec3340cfc89e67377ce472c2d5d4c00000000000000000000000000000000147b4eaa2dcee39b918b7cdf24483b29466120677e5d42b51353a9b2fa207bd911d9b391142a13a212d0ab38adcbe10796111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c00000000000000000000000000000000007790cde9ff8af2d7597d33909f00963eafa228817de1ebf4233ef0831202700b99641318186aec80ac913a1b1143eb0000000000000000000000000000000009d42ea1386d8b019dcd26068ab156f399c35b7d492722a20da0c915f7abe44ba688d9486f4bbb44268542c5a49168930000000000000000000000000000000010611f233bc1c4af0a14e1d1b945c91c077ec3dda592e2f852e2de41e09331664e1a92f9a0b7416c50327bc943a17b9e00000000000000000000000000000000048614243262dd070a754f40652b96a03326fc51273dddabed85df0654890ff38e0da7abb8190e4ebefdd6f78a5fec509d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d0000000000000000000000000000000008451d24fdc873c61db44e57372d43c35a2a8098255f9aad3a6b244913b86bff6444042e391685b1244f009c5ccde935000000000000000000000000000000001177c2da9972a2b96afaf866f97dc149482fbaaa93e194803c09c8334c2c7025e08cad4f7898959a57b07a545ecf76ad0000000000000000000000000000000016f40426cbd1f0f4ca5ae1dfa4c3960a6fbd51a1b5b24ff5d03fb9911e908406a0ecf4f20a78a280d24dc9bdd1c0799b00000000000000000000000000000000194a8c55f549da1842cc3173f3eb7bfd70df26b43a3059a3590992e34fb19b2caac4149f64d442965e166225b9013e2b11ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d3000000000000000000000000000000000a97664c1d7624cae0e969c728a84130fe260581305435ff8ec701cdc51a73977f58c891ecee637eb6b7c972069ebbb80000000000000000000000000000000003f4ed6a9e9f4229f0fb35394bbc10da9adbf4985d4453da64eb312ec88cb15bdc189a3b5df1af3107a36fc001ec92ad000000000000000000000000000000000ac552c5f6170a70563fcdca8e0c6a7c6135af2f9d5ae6f60a2c459d1be4cf76ebcdf9bcd891db8a1e2fc905a23a97b4000000000000000000000000000000001734a46c99e776d1ed4b807f5b313562e0989ad5c67dbcb961c134f8b7b7601c23308839569dc224bdf7c370c4498303b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf440000000000000000000000000000000000a523182c886671435ccc75cbc78293274802c6142465acb31a1809e43b1d656ed9c808068de167b1ab126ed0f73a4490000000000000000000000000000000007c4616080b5a002fea3589d54c7510884a3ece705d27dee315851746b1ee748e8a08d3516d8c6afe1c0482b960a9c62000000000000000000000000000000000dd1bd9b4b9c140aeb97887a0266bfb5696813fea034b78bb7d0cf1cca15b5bb0ed92a97841c8d8cc614f7721b8b7e040000000000000000000000000000000012a41a8941b6f0e4c87f8188718f9bc75305d41d6f4441eb9682473340fce0bbb463e1b922d3af8daea32b8a8ac9c3b4a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf241169119000000000000000000000000000000001763ab2b361681955735ae00b69f26e06469391af993c8dc6f2e1dffb52ca01e49d58d6e2249e7433ccfb5ddaf8fead40000000000000000000000000000000003858f3bb01b2393aa4d4d7889bdeb0bb9bcde0dcb9b39c4ffe0fcd0b865baaff75b676c715be275929ff4303c416e0800000000000000000000000000000000086d64bd1302b0b3a620b87ac29cac3d9e606513ec8b47898cd852bf552c1364291aaa842616b92c8936e076e59451bd000000000000000000000000000000000967c9f59c15ed02c9b2da6e76fb0bf3d445ba849010afb7f9c994b1ef6a05ad577570d4adad043796eb90e51537ce5187fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c58945000000000000000000000000000000000e6b0da7b406bcac2dbb90fbf430fda6442cc2860ce633ab84404dfbb426949d55ecd72992da1a2e8e1ce229b599232c000000000000000000000000000000000fbe3a345ffc8fb85cedc4b8dedf9d952c41b4ff6f1c7ff4cf91b2276621969d905aa9aae5fc89bc516f96b9bd1bb3c10000000000000000000000000000000018c2a7fcc35099c41bb851ff66abb047e2af9cf4fa9fc45f030124ea2c7efd26e594abbfc7a7f258c8081a3a80d15105000000000000000000000000000000000a27cd33c2121c9c542e27b52a13275ef7e81dc0c6ece883b65e71d2bc3e7246f95aef7c6b41eace382a1400568cf298c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000158bd8e6198d22b52efb7f3b945668666e1190a4a8e70307ba5c1b737316a8f8568092f219f683c0f53f56f25745d4e600000000000000000000000000000000097e64e4553371c81a9bf553ddd9719f59b329284eca0d76f023d603c29a034d123ab777cf173c5f2bbc66412d69d4ce000000000000000000000000000000001298cd5501e136a06ad4fcf87a75c0c7b96c73e844863b74bf6aa581a0ea98c2b1f608c668743a3e37ad5ca2074af9340000000000000000000000000000000017ff9f1336d7f2152f17daddde9d3e1679cab8120ed2c0288b0908d4e2099a08c9bc6f79425f004ea3ac4d684abff6dcf29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b4989911700000000000000000000000000000000115baaab8f0331894da531ab557bb454e2003010ba1dc1d96e3d983d49b1312585c6d4c43d85dc074b23b2fb28c8a1d6000000000000000000000000000000000db1621b721c8a54ece26a355b190af5f3e1dc1b43e0827a1912ace651cbad4b980e77a4c3566aa809157229b234c808000000000000000000000000000000000c594e0ed3f7ee55886e251deef9732aea3de11f094ec53907a843b755add8fa5d00779a66621e615ba7772ee821c4030000000000000000000000000000000004e80aeff6c4b85188903b4d2dcac4f94f7cb4285a38f94b0becb556d83dce8735d1db5810b409d45a8dd1b9a6dde29c125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000371a74468ce2ad90e19b7fe3f57159dffb1b0422b32ad693b2fe6c45c5d371b97a90054095da887019d25c1ee8197800000000000000000000000000000000010575e1ec9a3e609ca086ef8bca679c4548482d9e0da2e51878158ac8e5b29d824c31ad7ff642041e748efc50c2514e000000000000000000000000000000000ef36130380f1e84b2f462b5f970abb8535431b79813015261015c6d7e74f038b47504de01794840d93fbbb4b386e17500000000000000000000000000000000018419e85fc2d75f007d1e0e02c1975332e03d42c3b41c50c3538c3625e702161cdcf8913babd2995aea7566ff15abf2bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6", "Expected": "00000000000000000000000000000000125406a942ae0119575453beb4c093d2696d3bea7bc031d7a586439197f848e1d5a82b925b4e96138a3460eecf198ffa000000000000000000000000000000000befcee6bd1412c54674a3d519dd2813b87b18f2ab3375a731197e9f539f8f8fff634f15647e7fea3c65b93594343c2000000000000000000000000000000000011e4d432ee6babd502a9cbbb5cf4839dc6da6176b6bb0ba51d99a3587465f5f3f83f4d4cf2c7e6187de93b859ca61d800000000000000000000000000000000168509010b867aa198fc294a5879ce14a51503c1d0e8fbc02ec08cf62afbd357ceac24b633bd0fa99f83dda92e10724b", "Name": "matter_g2_multiexp_87", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000070a0d060c6e9bad0d1bb15417578daaa8b7a5c984c4947dba10fd874d93fd1e3994337c87799d78a674087678d9168f00000000000000000000000000000000128985b69d5d6ea0ad0b19eba7c2b430f5242a7e89626c66fb83b58ca7cb65a479de4b2fca6886cf55b8cfb52394102a000000000000000000000000000000000bb0bced708571662af042d18956b5b7d797b61aba70823618682287deebe69bf1f9a94ca4059e0570e25a39e60b9a8b00000000000000000000000000000000193f0793324dc78c40f356dde030b632feeb1609a1bd75ce88f0d313a0864dbf1f5e92826870866ab9b3c98cd1c12aa508c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000000a61a310f90a5ffde617b78f784b2e699cd77e7c3e7c483a2ccb768f94d68e59a2a4521410c22ef6f21ba589ec3abdcc000000000000000000000000000000000e6568c83e0f7e459b27a28e5bf954983c5dee478a009c244da16041e710ddc67479cdb3da6f47e7203fedb8f765b2490000000000000000000000000000000001c5cf6b948b85a1c426fe932cd87605f1fbf6c932756eb1bfb43beaf012bec4612d8dd0840efd4cba3f5394beb65112000000000000000000000000000000000e02d5bc20c40d7cc2165a21ab37c6e4eb71322c01a43f2085f93b5b02bcabcd668dab90323db0f9288737d757997631a0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000165287d72eca1ecda5fe16a555245b0a34a04beaf9177466bfd88bbc675442d206e70f7a2063b6ed0e15e9406232f5ea0000000000000000000000000000000004c0608bd7e01e65a15716b0c505111a3abb0abac3efb846e05e8db59c063950dcee052f04d1c4e9e492bc6740fafe6d000000000000000000000000000000000de897f7ebaf9089f7e198ee41e1efd7d84fbec7327799b9293a489965cd36159442eb0dc1f79f6b1f122f592b013bb30000000000000000000000000000000009774586dc359e5d20486f00dcea6ff93948c5a8b74058645d1048fe46ae3330dd56d85204d328f43f15e674020f353ec252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000438ee51a560aa419ad6ae45e1014c38b7c43f1f6a512bccc2d4f10a35838369b71799fab4b6a754fd938c1a1b874fc0000000000000000000000000000000000c1491c85965c0b74d08f5866ca727fd30bf641a6ada0ab8363ff01916c37d10b1b7eccff79b396c587d9beca2c826c0000000000000000000000000000000001452f254ceae9626443265ba31a1a750a425f2a7789e69cde16b70eb319c744a6221e74a9e2881c6bafea161d29638df0000000000000000000000000000000011bd6a1bbded174e9cb95d74492f7b07a755339a6c40f2a1a76debccc0f3a32c7017ca4e6679fb2c038c751f19986f526d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b00000000000000000000000000000000139157c34aaf70cbfaa82be655281b085e37d6406df4cf8e291b221394e91d9e3cf04d431f15436064d0bfc8cbe13701000000000000000000000000000000000353fcf6e587e71e59d8f05d4085961d37b1f62694dd5c7f40efb5875b90459dd66c4d2d6c01a40834307ae9e82c2e08000000000000000000000000000000000a4975c9872fd167d0ff4cc80a6ce179b1e6e1eb21c8de80321451b1deffe68d8a13db26218f14935b64af25d63644c10000000000000000000000000000000001e8a2824f21cda745a24844ac0336994fb18e30608ac61201a932c0a5a58f1acd56cbd9353bfab4944efcf2859ad5915c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b1973000000000000000000000000000000000537d7a9d7d9dc451cba4d50630caed32e182cbbd95212577b8c2855c327530e447a4f3d73c7d63fa3ad5111254c9ed90000000000000000000000000000000006984b32955fac4ad3c0d181c81b98534ebaddc316d51a40baa1028bacd6a93a20d4bd6cad6a0f8cf7ade96bcd4d68dd000000000000000000000000000000000720c392a663884ad4d8daeb7279ac41717ea602108c76519da13a45a77d2acafee842828f5ccfcd786bf7ea88afd01600000000000000000000000000000000081f1d3e37ebaacc11671bfe1670ed65ece2aee0e3b5d746a8d618b44bd4b7dea905eb8e958bc026a092b2bd5a7b87cb11009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a00000000000000000000000000000000005af33731879a574f39dca99c5c1b9517eda13121221be77a0c1bac82fbf29b37889c15a9d32531a3f6bf9137ce82dc000000000000000000000000000000000c62939f00d70a07a85804cd97fd34b9764565bdba225cdd7549729ceb9735bf4d09a80ec3055c483e1e24b66c41e403000000000000000000000000000000000e415677988c9d4656e59f77c608926c83028f91bf4c0634120b5f774ba07180b98141ffdf727cf9d0fc7a4cb52f4393000000000000000000000000000000000c9c37eaca857151a0c4a49b079f2f061e6a8ebb77e11eb32b29227529562f8dc8e2646e25469491eec5a07b11943f203e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf00000000000000000000000000000000015113f8f9100cd18427ff48038e1070fd835fce6c0812b7bafa679ac733c80bef56492ec3ca08c1117bd0edf19cb26f000000000000000000000000000000000789cd90c0be1de5d0b359c030d4b9d8aef93951e26870e37c375b9e7879cf277971a05babd319a3a6ac53f00f3254e40000000000000000000000000000000019b1cb91c9a1b1ee49c3837339778806bf0c093f171c92c9931ad43e35fc61cc08dafaf55b7b9e0f49dac28a12bcf92d00000000000000000000000000000000066c7864631333226f191e313436453e59f48f91d42e68874fa4da45eeda1f6f7f6342204e64e124d5ecd861f02ef4f00444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000129d887d694be0ef2f84c343a9aebd0a2aaf19a4e78586470351ffaf0b1309593363bd9c6e7fe39a6e59445d935414ef000000000000000000000000000000000596d7061c2399b6a9be7d4d495e58c0377b18db1e45cf3eb431d10cb8b15ae42548a86a26086d57b1a71cb5857d7917000000000000000000000000000000000cce7181fc87dfe1bb493043279a5d93cb2d980eed38dab2ace8c9fb335c2890447434d80df6e7c95729933ada7b9d8f000000000000000000000000000000000f0e1274ff70bc6d3f1d0d5b251ae528ed94aa3a1b9bbdb260892bfaa6213892071b8a6407abe26105b2f81df90569492035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc7360000000000000000000000000000000001192050735b114c19eb2bb9aa01f04d1fd9bed4df877113a14f7fbc9c31acc10db3ed0e0d15d8433e7408bc237c985b9000000000000000000000000000000000a8a66cda780790311b56836fe69479c7b94dbc6c82ed5886887dbb539a40390ebb2683c04078ed105e639a2ed8732a1000000000000000000000000000000001678ddff677b99011c73e0c9875b5b2ba063170f4d565d261b4c6d3263ccce0334b5bbb7ee08692568037fa96782e48b000000000000000000000000000000000ae15f79ad7f790f8ceaf7709f4b5da71642da0c1f7c442eeaeb165c7dacd8a4892fdfc8447a03a7c56e12513499e43c4bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e8260000000000000000000000000000000007fcb5ea5358074d06b64c5f46454e682dd9ac2127374c83f3ac5ad46bc5fd2fff7c5a80ffc669a1c159ee8c9a01bd37000000000000000000000000000000001010ada1bd493d6282ac2d3582480f50074a02fdf412c63e93c5857974626ff464150c20bdf23a87692bfe69a075eeb300000000000000000000000000000000086bb5664a8738f02af5517aec4c6db47653a6d76bd4b5e37ba4d8b27a7819e82e6a4c7ba4f8377e06a5878e7c0bffbc000000000000000000000000000000000be1463ab76e468e47e1711c158dc9bb10d1278f5cc676cff937f60ba457061bacdad7b8d3286f40219963b147cce4bd369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c3329d1e1c76b0bcc7ca3766b2cc5ec8169690f45e0ea3e37b7173bfd6c884921c7523ff25391a85b47d5de395ca63b00000000000000000000000000000000081ff066c008d5a4c893a636d24e9752c6a06666dcbf80082167610e73a32d70aae3e58c88ffaa27f05260b86b11f72a000000000000000000000000000000001178e88c652d257888cda1c0b65ee2c0636184194fef9e6ae3791a85417c43a31fe75893773ff3e7b4d4cda9eafa8de40000000000000000000000000000000019657ec4604ab5e8812237a28e5ff320a0d728c60c541142ffd87fec2c703665638e5eebc33e308d5582cd043d08d788ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e120000000000000000000000000000000010789200f69d8acc70f108145804b62b521a30a04176c449f52bedff5975ad7b273aaf4a32f8461ced8e92b2229e2cef000000000000000000000000000000001178c36174cdb783b5b09d419ae4a154512bf9ce07368521d1576b2f1bf39f98be29bf533bad16ba9d96aae621612aa70000000000000000000000000000000002580f2115d1814667b6178b6bffca6a4d992eb66e9601c0d21e32a5f3b69e3f85e1205c877b2dc2696a0e872c5bbc6c0000000000000000000000000000000002c94d7ff016d57bd5f589971344c6499577bc2234e18e6c8dfd7d27a205442a4236ac54fe279d1bbca76467530140b42cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca36100000000000000000000000000000000108b6fef7396ef71b46339d421726f83b08320599d66da18234011720d2b524d24075a255d2771f1ae904958c50a9046000000000000000000000000000000000723d5045b65c0887da1bb01d874714ac86d21441119a93a1d5758957215f399f5ef1cbc00558db01b295bf0cc988cab000000000000000000000000000000000994914a3df9d3094dab0c0c41a45315dce5968a99e6171fc609ac9e50bee5ccac771efaa04067467e95709bd924973f000000000000000000000000000000000ac746602f804f52e9a485c30412adf92eb9af3f6daa8f23b974339a0ffa6f5aa1b70a80a9f19cde2a69a4b7251ecf5d60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff790000000000000000000000000000000000a2ffeaff148dc5f70fcf53e7e8d7b6100cd6e7df5b3fa4aa33bced243f15b4f77f48d25f74366a693404b6ed7d3075000000000000000000000000000000000f3e1b34ac8fde4caedf3d8c3e24db02de3f91487db300f09c779e7e4e96ae55229288abd946abcc3a8adaf18a0c89e000000000000000000000000000000000166a68c5191dd7f9d44eade2ef1a9b522dc062bba9c55e2ff03aef400e5d2765a12816b4ba51e10bc21e06113c8ddc5100000000000000000000000000000000109c00de20f7e827375c1841348e684fdb248fad116e9643dbda8be2bd06b71db264e9f2c40dec2092e7d518540a6d82652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000014ddb61173359514226c150a3343576b04fb1b06fabd8fe2f921fb3b90baf5513447c107f6d2f96c8b03274bfe451dca0000000000000000000000000000000001d1064860f6c4d62a282147308e80ceb0c5dd62f39b3232a231b1b287e497df31cbc5a3905a7687eb2f24447e50a395000000000000000000000000000000000859611bb3962955f92bff861e03d07bab7fe1f69e90c6bc7928be8d1758c9194ff7a52b16472d04564607b742543eaf0000000000000000000000000000000008a3e8396901a205a071aad06ba9812207171f33775eb358de4232826a5f0ff50ec3e137b1344b583849e8a5b424b46676a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20", "Expected": "0000000000000000000000000000000008c9db83241e7f3ae6c2eac8fdcff5f2d35318e24c3b4130e9bb7048a3b84a52fa3f222a8190121d2a5b8835bf911bb200000000000000000000000000000000002db79cbcbabf41bd8c715e024f4687bc0d058d76b8dbe58ffdb80918212ab6e9b35256fde583c0fe903c34a4c41ba70000000000000000000000000000000019f37d05f5c9e65c6f004e1aef03ff0e1899f0739c9cc4e9038e18f9d45678388454d144495b2cd993eb3691bf3e96f5000000000000000000000000000000000d8e0d7715ed71291729bf480f5fee7ae04264015732677488472bedc0dbacf8b35eef7adcce196e3bba9cac0991be81", "Name": "matter_g2_multiexp_88", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000064a134260b753af73df3764ab662e3b1bd624c8f3248e9bcf7676d8fb0825ab85ea33387d4641c81fb8ba3757e0870a000000000000000000000000000000000d67eff1936a395cd3f808ed7fc89f8b6a227c4849a6941d4bf762af6e41ae41c8114aeccc2565ba01fd902df530df1e000000000000000000000000000000000110ca2339832e7a9468844b94b3ced0c9216654bef1c8a5cf66385a99d5d452f978bbb7fe15fb477f56753488fc909b00000000000000000000000000000000173210b548d1b98b926539049996713f53108cd2911105235c1d5258360d5620d330951db67219ffaa304a67fd6219f39f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f000000000000000000000000000000000f7c3795ac3d511f93a3d85e65261e4c09cd316787f74ced6e472a3993b7b5b0ce5a7c91d99559a8e0791f712cb4e1700000000000000000000000000000000018eacb2c5fa9221881c6311256a69c7616748deb3235c61cc11412860450151a25e3d6a220bb23e0b3e3325044fba68300000000000000000000000000000000121827286873ad31f58cb3889fd01cb7d0f91ff1c241295f6ef2dd0e8aa8638b63a7e6061efc2e7ca1d3579b4868f0460000000000000000000000000000000003a57315175d70880b2b53c67d61831ab066b08d7ac68637364ab1c1f3efad96d42a3cf5189c45012c1f73a1b97bdb4c1deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f10800000000000000000000000000000000180648e5d0bf727101417f515cb9578bdde3e9f6c4176d516454ea7c32c1712610cc8bbed303bd1afd48f580ec11b77c000000000000000000000000000000000d6ffa9b85d69b67abb77f5c8bd776eae82d1cb055d2dcdea31ac66b1825014ec7f7a2aea320ef9f6897c9aac8c0706900000000000000000000000000000000073214fedbade28cc60ecfa4e1fe2fbc05f3d71528aca315312d50214f680956bb9e0fc12783843b00b3f4f0f52efe2700000000000000000000000000000000128f87e7da7b53f28944aeb26ef0f6c99d84038af51a1d242501ec84b5a6a8593ef1a0f6b523478d9fa12e36c2fdbe694b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000001443980d7450af1e19949fb328776cb7238a9b26240cddc565aa9d52c5592083b1533e8103dc07eac80e4bd830f209f000000000000000000000000000000000afdbea7f1cec534c03d3269d50017372f7ccbcba9f096fdb2754af4d6b4956decbab2b0afb69f97a03beeb20b4ccc31000000000000000000000000000000000a83dfa3197dc65097601457a97d0df7710e001e90657b150e289515609f13997b454167a7589ef218893309460139f300000000000000000000000000000000029c362244510c342358130f877de947acad5a379295f3149d5c713274316e06a169501f889e4b9cbf86f10b9521c1bb124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010a83f13a185c70ca3f724dd84efcfa3ec463d7c05360056f8b5304864b20025b0a82c9d542ba08b645e2334f176472d000000000000000000000000000000000848a6a18bcf64d083e118190805d68f7ffea8b5a66e0807b9cd3733d31ffa5cc25dbfa6ada604646dcd8dfa622e08a30000000000000000000000000000000009962205c0ba43e5101fc3d5353f429a57a97bcb84baa0942a7e7facdfb0d032b9307aed8bd2ac9094a2e5b1460db7140000000000000000000000000000000019b1012661a10d31a4a73d0cb31f7eec0e7be729a42baf560c1e90a9124fe8d5fe31ecbb6d4954dba7d943a7af773eaa5a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000e7f02c1d2ceae60f314f51374b338c329f2eaa82553c3fc1643c7f1910ca24e277f3d658f552a47f780d4d9e0ac5e030000000000000000000000000000000014b6b56afc4afed5199191ec13dbeedd797f14ed493c25658a9658f031ac8d43de12e6a8c4b1671c9e5ef78da1a55e2600000000000000000000000000000000194d8a50618ff55ba3fa5602d41cbbeadc01a348ad1484c5e9aee5fb7241fcd9018f436e3c6c6dc64beaa241513a6c8300000000000000000000000000000000052681eac4bd59e160b67ebb27582a6d3ad5286d652787a0e160026607acfbfc5b9f38b9b171375079d052cb242b87fe8c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab87000000000000000000000000000000000d96ce83d917204e674ad9f5e5728651f5f23df25236b0fe769be48adf482ed8c36ad9c9abb6efa3719bd35324bd700800000000000000000000000000000000107f55ab0e5b60dbcc0632c345a9e93818014d7657b264031709275744e1c6722ec63aa209e655878a57704ca6cb3bc10000000000000000000000000000000018d97fba324431fa28b8845d94f62fc9eacc0253134b923908f06889d375405b51610ac21a75bdfb27e3533dd4debc22000000000000000000000000000000001667856804a5471238ffd64bf3bf266ce3a2351ebc68265674bc86ce6faa8dd50a3dfa00c647fb4265951b3a9607ab99f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff000000000000000000000000000000000fde2fd0349e7a47a9b6858014d551aea569ef9802629bd9520e303ef0487c9d2d399682ac16ce6fa03adb6f4b478fa5000000000000000000000000000000001858ae58920dd0abd8ad94d2f9f946c53e050fe89c61f62fccad37e17f8723a4fbecb6b1be1e3cb853f045d0dca8e53e00000000000000000000000000000000093615a7f9d12e92c90706a47abe9620c4db41e95e42e478949745d6b73e021422e40b969e9e34263778c8a4d4907445000000000000000000000000000000001006ae7963b1e1c4d8c2c85175aca958758fb380019825b09ca3f728b5356254ae4fc670aa29812320b921b48a069df622d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000000ac407b75ea77789748e7607b5d6edb1d891875aeef2802715ddc393818fc8cbe82cde9f96377e3ac60107ddcda7e6610000000000000000000000000000000006e63e49356c38b816736d1d7c360ceaaba875c53c98ec68cb825962531855dc6410a125b914b0ad99f6f4327f5450890000000000000000000000000000000018ffb4ac95b8ffde112c8bdbf07a1c97b1d30a42dd4a97c82617698617ceb169e8702437ff6082a2ae387b462cd86256000000000000000000000000000000000497c4b3788c4d6c9b4cd8b3d3569ac4b4332b2f76c5f03f112e089bb79d33152b2469f7ad3eadb8b954775aab73f47de816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000003e16f2f5a2fe15fa02b6217aed7dc688dd2670c09c02791cafeccfceb7d99ce826bccf213f6a7c6064687519f9283de00000000000000000000000000000000095e6638ac74815dc451b3ec85a6a8cc18643b541e8be99052ff6dad39c971f2e8bee976ab2ed5e1cdacf92816249ded000000000000000000000000000000000f2703c08b1d707fb6de215de80b53ffbf2ac48f3dd059d2a952b1031189248fad27beec5c8591ac93625a08e3420f0200000000000000000000000000000000024ae36412ba6f2fdeb0777b892f1ed7bab0527879d93f7b71b62f437f5c1ad1f04a5a7380ae5990a455f11870c7208304f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea0000000000000000000000000000000000f7ab1908c6d4b152835f950b604b55fdda7eb55c6b90c05e98626ba7cd014683bd3e219fd0d5983e9dcfaaa5d389e560000000000000000000000000000000010b285c2884dbdd540d6dfeca704e00839337f12d2267f6a3fc731fa0f724cde19e268782b4b9c2e11ec3aef9a72a6ed0000000000000000000000000000000014a40cc55570e8f45369bd9dc622e05f03989bce6a98a0d87f4fa7add67eee3e2ad9a297615dde05e64203e86153ec230000000000000000000000000000000007f2b6a092adc595e4857e821579801301396321d4a20bccb3296a031d74a62bd79ea4ea094d2e545943138d2fc930fb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d200000000000000000000000000000000108834a685455dc0be10aaf54607a06100673140b012ef23a16d3df204a81dd8505d62ca3e0278a2581abc59e0fbc421000000000000000000000000000000000bca7130de9896e8d6858022f24308af7ca66fb4c91f38b30f717c5491996ef4cdb01f4d38a730f9ba9ca5af5ad1de7700000000000000000000000000000000007d60ded107a06114afaf741dc8826f9e14bac6014eba26089c4e31a73b0f30c4b6e22533ac0db7e73621cecf753590000000000000000000000000000000000b538213a703f7a0bbcffb4aa8ce25ba2a538bf599d3c0251f5e8acddfd596c9912d4cf9a1bd8d3ec070713328ca992205edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000cdf0b9bc829cd8537918d665e5bf344d309678d01ee80c71a6d6efb45ee8a7beca35bb5ee046e0a3fac76e1771520ff00000000000000000000000000000000014e5be9dca2f8ee4da18e5ec9c4caa891dd78acc47f553af584308c72988435b85ad21b14abf8421bdb9e25164d568f000000000000000000000000000000000accdde22a1c479e47a17b8da6f1d2b7f780ac278c68a68090e5402977d897bd734f5af8164118d613f480c1f65e5d8e00000000000000000000000000000000029614458afdf6b572bea02a0af987d178c43650ca1c80a297b1d31e259aabd3e2a2c8e4b2c044466924dd6e5e3483e6f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b0000000000000000000000000000000004e45cc43d4d10ed878e18df156062c799a687b8e6beedad9fa6f66ad855cd053af6918e234ff9a43561da7e67f3dee10000000000000000000000000000000009c9ae47a76c199c93c38e7213c8d6c030cfca709714c703839b9ae9b65207e83486f9c8c16373e2b37756f3fd4355fd0000000000000000000000000000000001594ce9c2e229491b22317452938115747515ce62a0d49f4dd12667f5b3e7b541b3775c9b1363cc185a539b9f7596330000000000000000000000000000000016bf68e05e32168c69ad67331d7bc88a6d130fe8aed3e42eddfeb1d92add266eb69487b246a3ca961ea6ac0a35f8da78059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b41900000000000000000000000000000000080d165c57354f87008eb97610d4a596f180e48ed3190779591a0f7e07278f8d2fa6cd21d1b10e6347f11bd9731fdfed0000000000000000000000000000000008d5a1e66ec76743ca366be80fd1cbd5efc9112dbcfa84ce6c44e8df03140ca5f07d4bafc6c6ce5f2f190ede55fe8718000000000000000000000000000000000d0e1d2e5ef384a4fb314fdce54ab7895f895b3bc669acffd48e92c6320024d4f371f42071fceea550c8cf68615b00960000000000000000000000000000000010beae4ffbb68cf6e5d0683dc0629411ee14563f84788d50b1c8755b0b06092cc0f0ef7b55a39d51945b5178e374f8e047b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface00000000000000000000000000000000161486d422462460923bd98834f0cc270982087697747fe40eb9153a7923d48eda191e4e7a75964f18f1df9365901a360000000000000000000000000000000017ab168a4ec81c8db4a74d529670fe6332b3870004f696f3a143cd1a62abd747d94afac9485e5dc19b0f4262dd379c990000000000000000000000000000000001e9cc85f03039ea53253f0fa2420012171fe39ed8696ddfbed57b80b73476171e59631388d75fe43aafde52aa14a64100000000000000000000000000000000109a5d5449002f4bdca44c0bd141175d5ca1cee449302f0314fcb5f282f022a7a3cef77f4e9fb515107e797726ff51d767d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c", "Expected": "000000000000000000000000000000000aaa5de171664fcb45439b17a024806ff7e07d02294e0592ca74752a5b66f3365d1b49d6893b3bac3b8b0d10d026e48d000000000000000000000000000000000418354ce1820ecf848321a07ce22117303e5a15169a9cbfd141fb4797de8871d84d577e86270a9cbfe31c088ceed0250000000000000000000000000000000016884caa03ea641e0660a790975d77c5bb03568f873800d0559b69e3e0afcc10ddf031bb5c25c46f136f0791bbd3cc8f0000000000000000000000000000000002bdf659df76cbaaec030448e8f4bbd6b424037a8dfd7c4b8ccaa2224b0852c168f49c6f45c04f23abc85b8df21953ce", "Name": "matter_g2_multiexp_89", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000062bad6816308f1c8c6941980caf71929a4006083dd29827902ffc92ebd9b14f1ef662f3a0125b1e74dabd039f9106400000000000000000000000000000000118e4ae76e2c321a5b89eb19b58f58f44e80dcbc7bd6d619579da40e1156aab32fe81df8eeb1bd047f96d65aed8b3b6a000000000000000000000000000000000c8c93e1beeb4efe52a96e5d5612338721e3e487c13c18b02475f9ccd8fafc2c95101aed291951f2031bee5216dba26f0000000000000000000000000000000016fba44e9aa39a12ae27e3c36de1f14e3f37ffb0ceaf5fed2a0d9815eab02c5aae91b254812a8f3a2e3654cec01a341caaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c0000000000000000000000000000000013716488daf8586719c52fcec80d35f17d4c595b66c7f2138244f3c8cea69b819778bfb50e49ca1d092e57c51674fca00000000000000000000000000000000019cee25c4731bf48602ceab23b5fc4f764993443e3622107b4c33b29c23d1b5916380431b7ecd94a0ce99811fe6dadba000000000000000000000000000000000562b28b245b7c1ee531a320fa0f4e12d7c171c7e3932ffda6cfebb123fa7f5993e5ed5e7b7d295405e5031b339994bf00000000000000000000000000000000180c4a8158a26d34123c870bc694382352a8e4de712b650d3e45e6baa16d6950ec15d3a4e032c1d1ae8fea18faa6f3d8db48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f20000000000000000000000000000000008472d40e0505d6b8b92500e8e9711112048611fcdcca2377481ae86a7f6da1571f179183301e2194a42dac3873a3ba5000000000000000000000000000000000e2c5b61c050a8a12298f76b5f15383e72b90b001fa26889b67a24bb374b63c1e00979b05450e44ed63e72042af6d46e000000000000000000000000000000000e8723eace9c7a72b3e6097afc9bcadde61462e2ee03fcd5ad1b1c0dcf39f437f80530c2a1c5e6ecdaac14e8715f02e30000000000000000000000000000000002e21e0f451d035a5257fb09e9ed17b27f0994e6d85ddaf8d33153628adb194c97db17656351c029be4d3125bd29dc22ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f40000000000000000000000000000000000dce49634595869d7858e95a301bcff8112eb73dca8a22042137456d6d4887998a541489ff09f8e006176e6beee4e300000000000000000000000000000000010835f7336dc49e62706da4ef21d8e3173629b16742c317c1b397d4f17ced40a56520ea63557d7ac7f251568f4eb3a220000000000000000000000000000000017446ebe659a4510a362ee3b406b636bea8f381503e51ac21031c7cc92acd23046d62c2f32cda01b680c0f107142ae7d0000000000000000000000000000000006ef82deabd8983ebe4255d8e06f4a1b3585c057b2a1ca3c3e1cf04b582b65792e9980e3a1735a8ad58b053b16ca03d036d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f46000000000000000000000000000000000cd8e7422ee179a0499178c3848cc4fbc87fc25c8c882f036a03cd9d3f273f7f2bf71bd3c9cf5e30c42b1ee6e90b36fb0000000000000000000000000000000005005a471d77a35e922b6d6a45b13a90947c2b31d8e7a2e4b6388265b039ce23ed958495dbf904186bef60fd547b941c0000000000000000000000000000000006c337380065eb8a5f63cb20fc61a9eec4ccf0e23c4e0f231a5bc4d765271b9c5697bbde692b4828ae22ea12423ad932000000000000000000000000000000000f7a0080cbe72a6e6473f66ed729f58683a80815a1748e52f7b67a6bf2846b7df8e7dd8599f87fe63706e9823bfe00d21a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000c94834474ac91547546d7d179b2091e33c8812c1b582ff186e69b63011177283a74b549aa342a7f3882ee82ad8ecc03000000000000000000000000000000000d72c4308e9ae695acedb9413445bf6a40d59ca78bd4f74ddbc1bcd8508cfb521bfcca99c98dad8022d3d1ccdd98bca9000000000000000000000000000000001487d006830d00d84a567c5d031019035443fae4791a05253f91249b32a4b3e7b3ce7eae885b8caeaea411a90b3445e0000000000000000000000000000000000d94f17aa100503f605732a48e4f55c394a8df1421a3d7c78bc85f4cb7a53744eadcf76e1620fc54204b123d6071cd3bad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e900000000000000000000000000000000101cfa8d9c7522277f2bb4bae6c09e8b93a876c749c91c61784feeb105be61c2479375abdaa81deafc2fe754ed6cd9da00000000000000000000000000000000089ebbdd489ff670a70218f5aaca78d4e7ade483c7f20de4a84d39217be8f560fbf7bbe36f3f8b8361ba16d17ce609d200000000000000000000000000000000094f094372b2315fabc219099200e7b9e2f3a2f6fef2ede6f83c82f44792da03aaad06b8cd06dc3f140746bee2a45706000000000000000000000000000000000cde6cf9a3a7018b2b1c0c26b5850820080c7e4b56e615d577a78565431c93de78348d2851d5ad9f120ddaa9ff3da31b8fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000059bb8e5dc5f0cd31cf674ea78b80b67b8a8a753e51284a2ab37d3f29459250d904e70ed00481b73556970a7f5424e5900000000000000000000000000000000043c6a53c413bfa2f4bb14ef296afd97ce801a37fe63d11a842f8d66160794c1a651d70f4c836af2c73cb1bc58c706460000000000000000000000000000000003e7b67da1513656f7b08fc5a77682477349ac57e53687c82b6d98772b5f929a2b06b0c7e14481d522aa94fa3a6e1cde00000000000000000000000000000000109e07928216eaea36fbb20a38711e73fdc26e18a6967b54f308b10116a5c8af0c8411406ef6ab1050b61c23bb746b0a3023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e000000000000000000000000000000000166f26d3d26cd48e498578900a8c830ce9b80f162c4b430749651b945d9f60ae6a26306ad7711a1f9d3428946074912d00000000000000000000000000000000165f1bc59c9c36d12754097ea83e9a63fb4ae5d1b93a1b9239a6f338cddf4a9b30415d58076852288c6a467ce9b6b9eb00000000000000000000000000000000198e73619cb93fa6a2bc700cd400519d11a7d3d6d945ffac9754a6faf37da8596b49b7a3a4f2cd899ec9c84f1e79b7ed000000000000000000000000000000000a4740820d60034d37bb85e3e622783852779d36d6e61f81a7eabcd094993dd7d81900277550bb4299d550d2805466aa8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000001714857b0ee07b94ea928ff57aae9fe003c0c85d8564456955d14fc8d4ae14a7c9bc303983af3e2999c6db2d000ea51d0000000000000000000000000000000016512cb60aa372cf5098ad514291d8168ed31bd755861dbd9ef020252c01379d343a9c058839cdec8d14f2fb9da0db80000000000000000000000000000000000af74d8ac711b6590e7041e80ca40dd4db659e42b950bdd68c56d676de654c1a47867bfe6483dfe1971eb7c1d1a70bd10000000000000000000000000000000019e56ca1ef3fffa9e131fc5bc93100577b062cf9b2acd234c79e5e54aa799a389f30002b4bd683edec5fb100f1800d66415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c00000000000000000000000000000000019d1d1e1e2dd4ab86df81a8246c902a573d1fd1598050663342e411a1d1b3c8849473c689afcc8e0ce5e51a9dc9c3b6200000000000000000000000000000000190d7c923bdd6336fe3e0509563b2eb6067354d8807f66e6052e97d5997464b9f07f29f3022f78779a5c4ac155a703ce00000000000000000000000000000000128591bb699c18a7b9e6e4e894654853f6a68233dfe8c744b42e057711b8d0efb3a98bab6aaa40ae7675d9200a8427d600000000000000000000000000000000045e0560e0936b16d1e055d3d3f4e0fb42d129546abddebeb78e871d1442f4796d939929d354b0326b95e50fd5208fa9ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a97400000000000000000000000000000000092373dfd7d4375d6bcffa415e5b36a31499e881a80be32400105a6d56b34d64f4fed09f12640a43289a710f034b71e6000000000000000000000000000000000fa75d6510b3b58a32635a7a6cb4b9255aa7af46905cafc893f29b7866e12565765bcde498dbe87df3d1dd53ab5628320000000000000000000000000000000010dfd3456cb6a8bc853b390380a13f045ab43abd289fd05e7f98839477dea1fb1fbe38ca4f5bdd6691446ac0219e453000000000000000000000000000000000112567397f3fda84db6042817a99aeccd0c46a11fd3ba44e2600deafaaab7014dba98cdcadf81b97272fb7f275ee8a4e039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff30000000000000000000000000000000007b397f093e69874d2bd3592489d93c80d0191b157e71d08a6ebe73063f77e7c5e084a24b34da2aa6354b1815a694185000000000000000000000000000000000fcede3a39dd5f905d072dafdb6f56d85726f6f362f91f079fcd47a8c1d3bdcf199d64edf17e3db1dfc96a3e59f69bfe0000000000000000000000000000000010cfa13c84e750d8af8bbb88bd6d16adf3bc7b532447c2e6accb359a5576be08c1b25f336047fb8e01a4d7f9080d0392000000000000000000000000000000000ca0e88b5c2035bcd3a65e8bf1aa219cf428b6f80617040ae02a0ed41559804844df373ac61a85899bec83e5a6243ed42c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd055500000000000000000000000000000000021f3b793680e0e3127fa53034e9fcf286f5279cd167ac1e8ba051c440aa265ec6d28fcc2f6d3bad126180efd4503fe900000000000000000000000000000000182b429f27996ee070ed27e7015bd70191b814bd02ca6558a9be81d6898161aa525197c1672ae75da92729f2fae9fa3c000000000000000000000000000000000a20b3922e07da4ef6696de85754eabf1f58f7f5d37accb6cde4f62066e789bc64bc8ad6ac827b8c955acc858b03d053000000000000000000000000000000000814faebd3b60fa1a8fb86b3cb57d36b9c85d4b28e97a2251e6bc1fed1ccb18f17664321f38f3723cf8b09a2161c6aeaab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000159f9d329f929a65e41c7a0d4c05e11db61ca7d6d82f8b92a780bac66568694656f4c845a730861fde9a313fa49bdf0e000000000000000000000000000000000d556bdc8dc959b00f74209dff27023c5521d387a40bf20ae2a98f3f55318eddd347bf1e9d856f43a4b5fcd26c3567ad0000000000000000000000000000000009b4b0cedf477ef1e0f99627bdd7a7afeb9e29afbac553a516fab479913b23a9be5e0b38994215a9e23849bb664201ee0000000000000000000000000000000010899f4dc55ac5d1f56a7b8d55ce7f6a5e0a8647bf1ef6e9050f00c5fcac9f679f138018b9aa611be73d3bdc0af2056e297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000000226c8a6b27437972ce29c2ed7e5cca4b6691e3a5dbbe713b5d309ff2f4cbb95e8f1571314444d65ff5fbc3281f9354f000000000000000000000000000000000282a49d0c560d873676967700c1062013a2d4beee96a09af7e14436fda4e3d2a32ab8ee4e591decec39a811ddff130400000000000000000000000000000000167bfe499f1f4609e67134e12ad91aadc37bdabd0055ecf7f96162c39a02a86e62a7b3d39f514f63edd82d04beb1958a00000000000000000000000000000000191673ea5470e4704e361f5ead1c56371d6aee3035d92d9e1b96fd119c4f877cde6451411e441fb45aa9fcb90fe4c66ba87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99", "Expected": "000000000000000000000000000000001488532d83fddf0bfd69b32f965790b3fe4cd9f64e8d17e78189c346518c91e69db2f0b742cdd5804b3db3777dd931230000000000000000000000000000000016205c470c6371d73b012a14d519bf214ff10de458605097da1b798977bd938727c5be19a10f4f492f301d2ab6c38ed000000000000000000000000000000000142cc08f61d3c9bd4c7bfd0b7a0b8693af6120898fcaff49a7fb5abdaf1d15bf70eb033d6ff09a75995547e6856c595f00000000000000000000000000000000164b2807e19135ca3b66bac9aceb371165c930ae063f3cb5a06efb8985a1e0c39023d8f01df517713796083e8c2cceb7", "Name": "matter_g2_multiexp_90", + "Gas": 293920, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000023bec14deefcc20a90e439bc16912e90191dc7142234b1870e4e8d70c56f695d5cd30a68930ff9b007bdcae8ca90d870000000000000000000000000000000000053a6e226f3bd82150e08ec3690f36616d5ab745b36a9990baac7ad3429a41bc60c7f7000ceda4cc9298b10043639e000000000000000000000000000000000b81b331589ac332093928faa60d6819d3b5559d32d37d2cc13c78aafa1cc34e32d317695c1c4b4979baa1865ced90150000000000000000000000000000000010dbac5e52f9a046ab88aa36b3c5f6952720e174bf8f1732e886e66e5803aab63642185aa24ea08c991edaf8375bcadd9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000001875ef3f90df03d49ce6cede2c791b4d8503b75acff2dcb1c7c88026394dfe11481da72de4ff58ee9a98e75577b6398c000000000000000000000000000000000c8ee603d1404e64ea3ff08c70b3dbffd318736ae95f9a96ca07ddaa449818e6c5a17b2970f572f53c90be893e5c323b000000000000000000000000000000000f31af63c68481f527092b261d29d5c2daa95873b68899c28ac7753d95a64f455ebabedfe6e72246e494cc5fa2a9bd040000000000000000000000000000000009fd06bc51d4dc51de9fad6d1eb763809cdb5ccdba8e0427859d878904bdf295983b318f311856728078e7cbbecb0c5b64be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a67690000000000000000000000000000000003ce75ecf6b605ce73f4e215b1aad4799f91e624daf0deae3a273968490bdbdbd0250686ee91a1c24c2e2f2b6024fa49000000000000000000000000000000000e4d9b65d71b7593310fb5145677d170663c0ca29636f7b7c50ec1988bd2d2f1c41d542d4cd8fa23fad94bd6a84aef5b000000000000000000000000000000000fa4accea53a6362651f6c6ad2a68d20b5f549f8eb961718e0c14cd05249a121e442a6a588eafc83d6a43d8baa66882400000000000000000000000000000000121e325406767852620ddc45677495fe3e0851fd3c70922896a3e92033347d2fe8d07f4db8f26b8127ec39d619d596030c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da0000000000000000000000000000000003a0ac3ac37932b71672b9c48bdbd368d64c11f57ccb952f633bcd10ec19134c65fb2cbad655d773a90cbec2d9232b3b0000000000000000000000000000000007553c470bd8f38a48490dadea29df81ad901ecaaf1eab35b1f497bb58acce77b883e03e78702930dda72e2277139a2b00000000000000000000000000000000044973913824b3326b72e62ccbabd8c9f1b5dc81b423d0dca37b6f33972d993a681c326730717036bc6f0286da9177430000000000000000000000000000000017b0407d2864cfb39dbb0a5fa8deb4ed4a690a4042153e829f51c56bd0f2953a440d8305a318e6d6f67970d473753021a2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a000000000000000000000000000000000b7478dda7053590ed013b7c23431a21626e748c3843e2332bde0bd3890ecea95b6104bac420a8be5f3dd9b075203616000000000000000000000000000000000e6dea641181cf796f62b196652f952ee2a26ba998cce1cfe9d65ae49198d10badffa561e2bd818eb2a7f350c122fa820000000000000000000000000000000003c79917ad5a9c7f046b34e5491ed015695aecb00760f3009dde4cfbf88ad1c03e44117fcb6cdbd5ecaa8df8760a3da100000000000000000000000000000000034e22ddbdeb9dea46c71ca2144ffcc8356c1a525c5ada69a6d5e5c1786aaaf0cf532e31a2f78371e04a72e8222ed4c7e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c5890000000000000000000000000000000017964112272360a38d3bddf89da922ab50be076bf71a094fc8afde109d3817cc2db633e6408f5716b76d70e30ae00c0d0000000000000000000000000000000009bed28bbf43846ab97b92aab9ce094b077bbc59db648dbb469f21842058ef20318a1a8c18045b3de555bd8c76132ff0000000000000000000000000000000001297110789c7aecb0fec577f6f4a4de14608d9aa26a8de68289adea7f6b53b766b840d315152ea346f8c10b2d2729e730000000000000000000000000000000002b551c6a7846b96c6895e55ec435397af70eb435dc1c562ac71a44c36936c2c6d3e6a1e3545513516513391aedaf9ca76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000003d463ee4d177d78849fdecba52b7e83ca90d54177ed39e82b4e80c17994a6a2bfd9c46edc0ddb256f8955428f30eca0000000000000000000000000000000011dd976dfeb8ecb7d7f5cd10c235131709fb16d8a827e83d7084266c2504cd1f5276ae3333bc7fbb4ebab48c0d97a9930000000000000000000000000000000005fd19477fffc246f5991603b48085d95256b273631bcfc16f19c6980a3ba01ac098061faa149b475bfce37d586464b800000000000000000000000000000000103ac3dd682aee109dd7fbf60b50c28cf7e37642f05b424773a06f6cfaf7e9fb01d5074ade97ef6cb0ace2e1fe07d54c7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d0000000000000000000000000000000003208ce7f51a96dee053cbaa66fbdb921c2c3b42ead78b39b4f1df7ab49f05cb88d0f4ac18de5839749416eba5535d4b0000000000000000000000000000000001ff7f9db52aaa0fddc8e96a67b99353b92d7032f59d200bf69da3b446d08435d2ddaeb93584d3b68a1934566187922b0000000000000000000000000000000005f05ccfa5704652cecfb42979c538823fb9d11a00222a963d00f1a4b9a040a0222dcf45baad40c6574d85e5617dbbea0000000000000000000000000000000018637b8c3ef111f6ad4538464c250d780e7f081802bdf720f4c925154f4667c5d50cdbc4dbb7d0b2747b97d2ba2280bfd35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f000000000000000000000000000000000eb400becfa5521b824a4288885fe46642c31576238e94f95e9b4bcbf62845ee9d9ee122f87d36fbe668f0e605fa2ce00000000000000000000000000000000003c8cbdeea0d09590e1719ddffa0a116723f0fe85585583f3f271ead66fbc2107873181915cc41eed3ec6e2c5669e9d3000000000000000000000000000000000e61c0768561517405952c6462f1c5df95be272251d8a7060624b62f9be310cef64436eb2c4c04e8352d7b75fea1756200000000000000000000000000000000036cd74a8efa8a1fce7587f07d5c2a6c4b7ef161b0faae037c9bbe63bd0c92b83e514c8c1bae4a5d9866c0889b1b914f3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e8680000000000000000000000000000000013019d0fc8b93da2c79e473d713d94af33eaffda65a7a49d0cbae9f5259b8323e6f29b83da9608ba7d6ec004fb0710eb000000000000000000000000000000001505d30bf8f7c51994d896d91e8e2259782e2b49bda834015477f18c29e64da4d31f8b96edd080267b77a9539afca06a000000000000000000000000000000000eba929531615d9c0f59c4b33c1fc34b81e9c77cd8c6887099d850b3e39326d7caee1feeb101222f22bea1e9853d06ea0000000000000000000000000000000019d88f62cae047ddf2cefe497495f890d9ab8499e56f72488af65095e992427bf821f63555a67b0afb00d6fb441080a010325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c11382000000000000000000000000000000000b7c8f3d0c56b3b7d96c0a24fea3394551a186f87acbbbbce41d1313b23762945bae2e911725da4211614b456b508c0500000000000000000000000000000000125316f64bdd0c5bcd26a0e5bcfc3139045b3a44c8a8dd1cebbfaeb83b963c5a5abd4a5961465cff261c0e49189278d800000000000000000000000000000000095a327f488b901fe7dcc9f9ce6f4f25876bb09b053b64e9f4de9506a0fb95fc0cd443473c2cc5436750581d39b8e51f0000000000000000000000000000000015d406b31c791ae2d25ce462304c0bcf341686d7967c9dbb6734bc28b02123b1730d0a673fa8071dd90950d9411a2b3909545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb3000000000000000000000000000000000c7029af9422246d0a30784431d6bf9eca09481589438fe9a6d2fe1d5e526ec3d176a3d550204aadb85353d99bfe3ce50000000000000000000000000000000014a0dcb26c40693ad19a1edccda05055a27ca24544e933d01dfb964571071f94c94233f81e1ead0925d24e6d3df2c21500000000000000000000000000000000147a55ebd83c746128ba9c7ac57be125ca5c95f80f891e2c5893caa779484bdc1f9c3b3ccc4223b2343ba939251f7fdc00000000000000000000000000000000125622a040d8b157432ad81b8a83a9b1f0920b92680bbb65050b4862b89017b3bfaf81a3402ccb383265ba7200ce677feef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c810000000000000000000000000000000013b394fd7a0f3d94e5fe4cf5cce3627d425ec848912395565b3e61ffe89e56be799c4779d3b9a0222ecc6538ca3346e40000000000000000000000000000000014ac1a87b333caed0f557fa5692d1138a8c1e92d1f9acdc9f357e2a46f27513dea42f367b046d389dc831610be4fbcf40000000000000000000000000000000011fa243a0aa8b0c01c7636387d60021afe6efc223b7deb69d030651c369643188b9dd5e08d6d031d71dd11eca1e825ac0000000000000000000000000000000015bf8fd7fe438407db7f1b0b586b2c285777c5b6dbef9e45b46cc0a50dc831f32a70e7d4316d4869bc769ff6de58ac30c8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e80000000000000000000000000000000005ea57c269c9d43d3f17a83df04c95ea7e7bd85aad1dc2dd285ccdbd52bfe707a1d2476417e848ab119e62fea30520af000000000000000000000000000000000b99768ffbe95e315b244bf996cf34f8ac356664adda5aa7f4ff8d513b2eb5934b8ffe0fd9af94bc9b934e0a8bbd51ba0000000000000000000000000000000003b02c259df189370dd2700c5cccfc8b212a4b332a083adf9771503f5bd0c9ef040590320fe4a86c555a4ea87531268100000000000000000000000000000000003ebb1e610bd055d037a410cce3ae06aa654950aee0210ed0ee79f7a332be7342e308347d7b17a146a8b4c623029e08a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000015c4da691b5e6242af870e06b29bcde467b4644f01080eca60a28c7f941590192be30e6a4270a36dc8959b80235600aa00000000000000000000000000000000080f3d3d5c35ee24179f51ad854a37ac4ff867a2736a0e3e8f3312ac98c7016beea6ffe2bad1dd4842d6ec77995ff97600000000000000000000000000000000130c29dc633aaefc831b0bccb13fde1212fdce8cdd17beaaf1d06e74ef5b1b69bcc219c8d63f054690af1b6dc7c0d647000000000000000000000000000000000767290aaa1ed4c1dfa5603d976df0715b417599445ca577ded7d99e685118bbec71443fe1d9a65e0f23436353df152cdcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a00000000000000000000000000000000133e997857f47f8d6278b8ad86f4692ba0dec9da336f2726704db593af368dda7aefc0b218ce1674f415e0d9e2dee5c60000000000000000000000000000000018db87da1272bd386f7d8b5245dc2de30e82739723b680dedd36f4ac4cf5042bcbada1e1bb307ba444431d73a4248f9c0000000000000000000000000000000006580be3e67c7a615408aaf9c95c0956678af0e2b1f536f1e69588193387f8a05b03d5e1060ca60c4fec9eaf3e72d39900000000000000000000000000000000050bd9879ef9eea147678f552cedacaee84562e6561b3b7338fa8f9d514099291c3f2a3723fdb22c88f1c9243d411ccba6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce", "Expected": "000000000000000000000000000000000d8f69d90c871c08ae09e7b3e62e36514fd056c41fb596fec2fc9ce8509ab4f6675d7e85aa6b4b3197f5ab781f6f2e490000000000000000000000000000000011c4bd3cd156c34065e408efcaa5e13ad23d114458b71c2a6345f4aaf82af76cd4362db7ba9ee7e1e92ce72e242f570a000000000000000000000000000000000712dbbf20e9b24d20511d01717a3783608386408a258c2261fcdad5fbcab36c6bd21473c3d93ef8518975256c65a945000000000000000000000000000000000d13747be82153aea8076fd7813ecd7f60a214c31e88e25b14dee5cdb9336599e40b136d9ae6deb85606d35406b2675d", "Name": "matter_g2_multiexp_91", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017da08f2faa32570d95b9efd2d2fe358faec1ffe304750dca1dc3a273be3427c70904d58864f76afa19b0fe33ab1535f0000000000000000000000000000000017de677b713202f23baecef2b0618da140af624e56b876f2d7a20cd437c3868ea00ff6cd9c8908c1ef323ad294edd9670000000000000000000000000000000011d50aad957c54868aed6d848b2e67094b129282cc2df56c41d6ffe976d02ee83a592c33370d3715588a074db503b3e8000000000000000000000000000000000b8aeb019d120959b21627c1dcfdfb67ade22a948fe433172994d4a34084ac9e1c11333a9c663c87acf50962e21c728e92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b70000000000000000000000000000000001312ebeee36fff8152324a3ed24c37eee50b3099619a33c7a6316470ae722548b4b9e0f0640453caf53f374dba504830000000000000000000000000000000005ea81d2e5d9edeb3ed6c200b75beb731c31ad666e6e37db72ffd0265378bffc2724047c7c0c6e3f1598345fd390e9270000000000000000000000000000000017617a836beb12e637c5bbadd4fbf1ca2f5cc3280814ff5cbb5890b31cf2d2faee9e3ea8134af97ad4feace50aa194140000000000000000000000000000000002606deb5d57dce5b3d2e5f7ccec3ad036992beae238673641ad6042479ec3cf83bcc0fd03b7dacb9b4bb6c181ea9cc8c193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000001203ef36896bfad2a2841689a964328fe4ce3d83798671630d0c8876e67ceda03d99555aac46d984f1d3bc38ffc134c50000000000000000000000000000000013e7461c256c8ff9144b17f8cc2e270aa94b64be62588280baca2ae6b6efc4d32b3800eb84da62561e0e96d5f0387a3f0000000000000000000000000000000009454b6a810647350cf0b364eb1c2b719670af45bdba9d7d1a534e23d4e810c3ef4d9318532e46fd104a83bb10159a30000000000000000000000000000000001034546c4288f642daeccf5b56beed2ca2d946bb4391d056df9c6fd6771048903fa330ec16d59d05540cd715333c4bc73dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae6000000000000000000000000000000000d7782404dc6721f52648fc6969db33a9aa209f8baf5faa9678437c76c9e1635fa6d22d94aedefc90112223bb81ce33f0000000000000000000000000000000001e442e548d3045d1589817d0b57dfcd66fc64ff978186f784bd576faf57607170d49364a72189328c9837c9a2d8b0a0000000000000000000000000000000000da2b207bb7720aeca2e6ea02b65076770b960d4b7a96ed941a7f409757b952031a472384298acc3948bdc485088501c00000000000000000000000000000000048f85bc05ed78c692138f27c3541ced11b6b0ec158b43d133c3450a905416682fbb8c83dea06a06d294c48289ddb829e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000017134f787c920bc15cf2228a186dfa1d10194087f28b6dd8f03e1c86226928f0eb1c27020a5cc74d94b50c4b4e36b8020000000000000000000000000000000012fa1fdcbaa81c4cc1e37447cae51beb29e55bb19b91e2b575afa3754589ee0151cd9e83573edaaefd341f381d34f4f8000000000000000000000000000000000ecafd00cc87a773a13909512466ed11288c842716e1ca5c37a4d9a4cd7585136c86f32140fdf02e2997a6e19e3d76a200000000000000000000000000000000104cf007ea863dbd473d7dbab6f55e74062b18986e9bc09bcfdc9c23e4bff8683f73aa998a5cce59ded10499d18a0ecc4dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d72000000000000000000000000000000000ffffc98e55f4ba9a642c40678d625690464bea39d085dbc9c99b4c36ea8bff5154eae3c315e1dec29aa669840accf290000000000000000000000000000000000a3df9595167048c52b8170596d4127968194aef7fbaea4594a27c6af05c54bb772928a7749d74311038d1c115e91b2000000000000000000000000000000000b317a3abd808e94a7197e0d3b2515a147774f78d0cd7d36e1156da28a26e33bfa76d75c6e3ae346f9ace050c9911cc6000000000000000000000000000000000fb5fbcc2f74fc30ae7e32143f219db7dfe5db6ecb09cedad8f087b6df56bf9693c8b7d78aace064e7c31785f6869541795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a95680000000000000000000000000000000011e4b907a72f34af899a6c4de211af5fbe0265e5bf24d406798de53ecea273d5df4f4953d13fd7c9dc3bb0f0c143e3e4000000000000000000000000000000001623de5e87b6e1ee920e1b7d979fb9c431c12abb47b93876f9ddfaf28a7b673c18be634f96b813f7e0574c55b628a8790000000000000000000000000000000018ba994b02dad759ee79301b42ea20d7545844c0ea4bff2f95dc9420194cc4196fff12cc09bc0cef03cb7ba868c273700000000000000000000000000000000004b3527c8d148bd9e6006bd298ff8d7fe320748dd3f6d23449e874fc0c2f58d933c1e038a74f60fb6032cce41a3dbf5725b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f083800000000000000000000000000000000129641af11fa92056236ef135843b2189d46d870381261d5781a5fd6f2c5cc1861ebb2e801f19f3adf2216609a9e196f0000000000000000000000000000000007b4007c55e47f6bf3aa420ad75fd191ffe0fe824fd30c3f1961a8168922476fdb3869822704999b044feead470e3b8f00000000000000000000000000000000174209113e2d8c363b04f49487176dc6d9eb4ecc0b22daa7ecaa5548d038b3b7c23ebda4f1b6845425cee13493385302000000000000000000000000000000000a58c80a02b7f93db01d2f8e0005839625e6c4f121f3d69115f435526a7f7cb53177caab4db86273bc2d2f0474235f31df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e000000000000000000000000000000001738a03b46a8ca3f3d1f4f4447497c59f114005400f06813b24ff462ebc6f27c1c3c788b5f83f65958cadb34fddd08f40000000000000000000000000000000004dcfff2bc9ca0282016f38df484655cce7b872b1ff047351ae6b903e05f457d7fefae93104f9dfb549980394dfad2760000000000000000000000000000000017cd89434225dba07be137a73892faf0258b3fb19e6c8cec412fcda912c0613f2a925ad50ae485187020a371ff2dbc59000000000000000000000000000000000f1f9f87d3401e7b3b59331a89d9535adc973f869b81bfd8892a37117d8597ebab2800c966e623469792f4ae2a8eb232959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000000bdcb1d2a782541ff7884dde4167ba060fbd4b117944ae69aa2ff685b9bd7d475f45adce0c9f92695b4f4ecdd48cb9b50000000000000000000000000000000012a55432678043888bb9e7e47efb17700b3e702e389d0f58dd454224a02da3f190b2fef4c9d3e2074c7bef813fb56fb0000000000000000000000000000000000efa51ba64f1e7a1a269dc083179a222afac916778a967098582f55a41394bff3747f8d024261959f6d399f44a40d0fe000000000000000000000000000000000845dd0974c5789a85c3cb09ea441f2c433f0606928ee1b177eb851530d6e6b620b4fdcaffb8f75623435dff99b3ad9526ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d700000000000000000000000000000000180beba92bdb95c7803fca0407e29929ee64e03d61cad96ea0e6c469c5a888cc5ca5eb20983b3418a8da6596a5f1b2ba000000000000000000000000000000001322f7356eb3069fe20063f4be22c44426162dc8fc117e4e382bc4e33bdf3d971ef662fffc1d58ce187c33a43a4c853e000000000000000000000000000000001601a0aadaba846f11ba5c9f48e13bda1007ffdc1b8bbc9e85e83e569e9ee17a1e9e780a50ce617e6c780b8155675f2100000000000000000000000000000000105b2c213aa43ead42d9cfdf1d6c0559c25b4b86af43d4493bd75b76986d0d4f1d9b3bf9e3922b5c08a37a1629cab7d8f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c20000000000000000000000000000000017bc70346765b7160a0a5e556805c7944304acbecde06cadba474c51f05f22445c3d943674cc8215f973cdf11b9ea2e9000000000000000000000000000000000bfdbe202619a1d95359941c249b25462d3ecf09fabb878943a8a37cb9eb94abd7e6399f8d82f90ffcf904f4466cc5b1000000000000000000000000000000000f048db8530a288fef10a5ef9bb3cdd9f3d3b0ef4824609efad96bdf52d7c3b10ef628fa04f8b6513485e55f653f4b990000000000000000000000000000000004ec35f59287eadb1738bb50b0e2ad9d280bedfdb0a201e72594bfc4322ade0b7ffd6b532ebc7796cfc71f88a194bef4b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb30000000000000000000000000000000014424c77af7ace8ebf66f556cf219919712d96d24438466ad620221ce1ae9b2cd75b9c526e25df7fbf3c9250583757f500000000000000000000000000000000198aa00723781714152b3494b76ea3ee043b363b3fa81806cdf7e440b4cea907f226a3c038fb95c932710dc9aad4c9dd000000000000000000000000000000001360e4c775f6fa5e987231dce25ec67f61429ca9fd8160c3074383c30a8c0d7ff068b1d1215b2c0cc87129d9c9aecbc9000000000000000000000000000000001280ee6160800c4b0f82d5c2775238b4b223d8a0ac9a8f8013f138d554ba31c9fedb30e0eb5c330da17f5785b2717422ad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c1000000000000000000000000000000000f1f84251204d9f9328f79a45d15b311984df0715579633a82b5a9f680f6645cbe748b0fa64b9ce1e696e20a5645d6d300000000000000000000000000000000156901506e502a09917f76d825614824dfbc34d019ed53c2ec5395b51512da512b27541bc53331444eac2f618ffd5357000000000000000000000000000000000ea8736a97a33112bea9d07b729e973e3a942422f1d2b24c30e96637b535ccfc10cb5930bb59ed90bef604453df8772100000000000000000000000000000000187378477f60e3eaa225e89d8532bd95babd4a5c51729cca800d364b61575704992639dc5035138664e8e074ed0820033c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e5281c1c9210269a7f5ccd02cd5a7d3648b56d9ca6a4ee50beadf151c2601e0291fe7f1b89b694500e6c636d4e445c4000000000000000000000000000000000d5d5399f49697e46013558dfff544383b25f3b60681ba5fa2c5e6edfd3924267d0992abe65cbd5109ba8a1c6eadc7e30000000000000000000000000000000012a2104aa92871dd8e41ae1ae6dc18ceb7d0f361a5a4fc67936454b8866b8aec1602dd596459cccf6d9e1319ec3299d4000000000000000000000000000000000268795f6f9892f5b476c3a534673538647300203a51a8ff60b530094608b5fdf16297f02ab7ba41d6fe556885f064a4ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff7000000000000000000000000000000000a5585961328c52e0fefff16e66e3367e34339dac1a20cbc5e89b78804b8bc265e6e3fec1da6a62cd8a46be2f08a6d960000000000000000000000000000000016fbbd698784beec5a636332c0b20fdcb68fd3015cc6d18b541346a5e6af76613e6fcb14c888a2b8133c0f4132fc079300000000000000000000000000000000041805e0adf2a32153b89d1131226cf0ebd77cde3116a168e792ae8b88ba2edcb1fe7275658a384251b805d282ee039c00000000000000000000000000000000024213e4a8504cbae4875617b9b78473e7842ff72415ceacfaaf2e8b415f9f7e411989bada8101be72f9295dfbddfa3f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af", "Expected": "0000000000000000000000000000000003c4f051d528166f256d9356aa9cb885db5680c51990d9474a948848888fb82a9b86daa7a2273725ac8ec564ebbf15db00000000000000000000000000000000010a6c4c7067f511ca8f1b66bf9ffcbb275c7575540909262f7c4332c3d75b2f6d2f3ad2848c0d455410afb1cd60c835000000000000000000000000000000000ee5e582554b3930c5670d4e3542bf32e8b871849d7859eafc077bb2b533e936d462f614057f9fc09c4010afab501c1f0000000000000000000000000000000017fdbcaa065d301adb94a60dd20dbae71512d369fc82c556ea0dff66843be768be942e060752591c6eb0718985d8e313", "Name": "matter_g2_multiexp_92", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001353960aff58d45691c5378a0676a8e837260f5819cbbac9cd75c8cc4c6f1e17b9dc9843eabc0b1dfb27ff7631e4e52d000000000000000000000000000000000d6279a43d3526c035e88b0b640b04d42ea573ed07323aaac1d9d5570a8be64782682892415ba2be5cbb13f56e3a44db000000000000000000000000000000001250fd14fd003f88eb6e0e80e9f2ebe204475fc6c06cd10fa45608a17b7039afe0326474ff80c357f86c2825cdf7a16d00000000000000000000000000000000186cd91cfc8ae625e302946f2b393ea67e1107c0bfe938f5f36d28879fa0c0780c847aac77d0310d43211152c1d5f5d314d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d98000000000000000000000000000000000e55a98e8b1e59600e86cabb5e8db8ee622009b1618ff0df3e93fb55b80985bf2a8ed060aeaba53773274d4186934f75000000000000000000000000000000000bb7215fc43f465f51fc8265477fc8c79493966f040e02f0eacc4ebcb3414b84fd94ded822bd24dd5ad5720f12bb8313000000000000000000000000000000000b23328e15cda8a576ea352b5dd7ce382ec781deca6c23f646e42f0cf63e28669539579ea51e3c0afebbb58e1e8e3243000000000000000000000000000000001716019236169bdb4af7bf7d7ce0aeee7900b74023acbb16f6965c2abcf28917bf88d0f9d5bc26a81710496f7821fd4682cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa8626000000000000000000000000000000001718a4d6f5e78524d8df23d2c589abf04e3567d2176539a30b9f73c6251de573caa60c2881f3da99c48d48e9aacd7402000000000000000000000000000000000ce0e35721379077e6eb3b572f7f7718bbf775b116521c14acbd3ff19549c75d50bf70ce84326cbc3f9e5e53605d8ecd0000000000000000000000000000000007cb3305ef0d2cd7de4dceaf25d2eff44d4f437e065f6b244bf1b0611c891626eafc4b759d55b45d76e94b85852df1de0000000000000000000000000000000011cb56d2ed32a46bd951836f8e0f92d3824a4cddf011eecf1e2d92d81bff407a04abdfcffd60ccecda6e9443b328d51eea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f982490000000000000000000000000000000011ba705da23100f853882dd166d81ee1d7621550d156b14f7c2123e2681887ec3724626061db68b2c63987325b27d6230000000000000000000000000000000014271414fe078a80587269398afd127ce34c8dc2a4851f76613b81dc99d766d75c703949c1093b04d66a301a79d89bc30000000000000000000000000000000011b7935ff284b0f812b5da5b28ed338dc4c21ebbc7fee04db834732b11fd76092db0e8d80368255b0f1205129081e9af00000000000000000000000000000000104ff0ad2e3db08d3b4890b2e54f29e456e627cefc3a4f07c1109b764dae4142480e3e5312ada43fec9ba96ce587e8a4e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d000000000000000000000000000000000c60749ef36d63960022f3127d0ab4e12acf05ba1e1a136dec89be388b9d7144c1d78c04df658727763dbaa9725bd8b90000000000000000000000000000000019932b1c205a765bc9de0cc136999deb153222a9dd9e9ec3660fb6daef56242d08791d440888e69ca0da2bbe0fcb7d79000000000000000000000000000000001764790d12f5ff79ee4f2c9fadd5dfb1cf47db70b9e86018bbdbffd1be18df193c7dfa71533afa381053a77e02719c6400000000000000000000000000000000044b2b0211cbb407281ab2abc4725c2cd791b313bab8779954a2461ce445cdae60d4a9efad9f90f80e66b1438514e0f0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc00000000000000000000000000000000175cf9e7eead650e7ae4fd657bc288b6b6392773bf1bbea48e17172a5019637fbb2bc0a3d0d1e3b8054564935c908db200000000000000000000000000000000136da2a625cf72403d0861b9cd947cdad12b1f1e6cdefc4aab6756536425285a7953a1b892df40ec12ac3430fec889cd000000000000000000000000000000000c2d10c6d71cff4e1deba1984bfd17166571e64659ac91b64c343cdf587c29d52a2266c00a57c01feddb1df6439d21d1000000000000000000000000000000000384a782fb31278f49c840bb8f0552ac2734ef36bb3d115be7df20333aa747c92db990f7e879399235d122fdba0eed76eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000a9a821cc63e7c9857b0f39f7444a1e00a422f7cd5d0575c26bc5c6b98313abfde51e3f6d5f4c817193bdf391344e5ba0000000000000000000000000000000010daa8c7194a75cea757b6ae4eee85006eda459ff2cf155b1b5f19c3ad341972f72e28b781c4878e8919c7e5abe9a1d5000000000000000000000000000000001154d5d5764aa2b8818a9dc5dce30ba2197a86d0bdc7dee3e600462e295cc3a69dfbf8db34acf138e7a1f16b62a45717000000000000000000000000000000000b4243a09b05a958d78ba8ae25fd3fa85d520b95e56f1dff44e556b221a075f8dd3370313886d9dbfc56a75697454d72acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a9000000000000000000000000000000001820f953fd22b71ce00bbe9e9b78fcf5fb28bcb925f6b5dbf5711e00470ed7fd2f38d7291d40514ab4258807f29150270000000000000000000000000000000007b737b56a2ba33f76bcf66c0b26fb44d5f79879273f6ab21ecbfe6a5744da289464ca2b46c55edaadfe3210b907f3f7000000000000000000000000000000001735d1b39c5369bbf886c5063a96dd12b85e56fd9d8ff9d84520918e1dfeccb62bbbe1c2ab440ccecd0fe66f6ec55853000000000000000000000000000000000e591b7709bf00bb2a87e9edb95720de19adc41a42378cf9ebb930c6d3f5993a1d7b6320040d5c69908685d978be8f980cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d2000000000000000000000000000000001732ac410b2a7d10110bbf7709dc6fdc91ce742f8cb9b2c3ba37ba5f0934f8622c675753a26d04a176e24a630d090d81000000000000000000000000000000001111a52da6aca10cf40127fa8ab7683505305e0d474eed28a5e1735ee6877aa00c1bd598420876f2154b814660f3fe7600000000000000000000000000000000098c6d19c2ff42c2c57a4924693325de1a91135e3474ec699b70439d034469e72e844a5511e23dff3948a66cc2a2165300000000000000000000000000000000175fb79e5e54963cdbb133f38dccea2d1abc3cdf005c17e8f2de6dba9b9dbdeff7719983aa9ddb602f0cf966fdd430e0b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a05440000000000000000000000000000000004e88468d35d72dba6b3e4b9ca216b75b5d20c447064a48bee6a6ddf994b1e22fd6ee8abd60c627622daffcda219645a0000000000000000000000000000000015eb2ae16e3310b4c4ff557f0615519c13f29109d9863418fdfbe6309b5bac4463456df8ebb0b6d9022e294cc16265ea000000000000000000000000000000001288ffe0ffdb96708558d914bc412758770d048c4d50523e2b134f8468d11a57da97e42bea303ab7137e2d26c0b3b8f30000000000000000000000000000000003ce563b63c50b09a80b71a1a82995238a9de31aaf189c6d29307924b6f0990854507b7dc1644f689c5abcf931dd5a3c825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a30000000000000000000000000000000009e39ce653485caf699ae1d1d9cf2b8c5ea85b80ea042279e57f0beb81056159e49f73d67e7b1f9ece9f9ece7dcd2cf50000000000000000000000000000000008d6492cc335660c54e4a34b29b337b5800f1ef992d124524c799c04c852ccd3cfc01bf39515cb8b96151753147e8c49000000000000000000000000000000000ca779d87aaa3a6552f9f1a10b0d2e635be90022326db04e6072f326b919ee55d4124b9268f55751dc0f18172bd327ae00000000000000000000000000000000112eea543d6609d0acfaeb7be98be609f03304f50c3814ee8a010283146e6b5dbf170c7314598cac06efb9ced1ac2930307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd0000000000000000000000000000000006624dd7f6eb043da41a36a15752f370eeb3cb2e6bd88b337b370fe0660c5ba8fe64f62e112f91d2524e9324f3a049fb000000000000000000000000000000000415b964484c9246385cf95461ab955ed0390e20209ed405d84fa8c8af9fa7ab39ce89049691a63c61b12bbf6aa2a4e80000000000000000000000000000000014411d7b2db7c9ee78ea14c6a315df3d90827b511db2e2423d660176384d8f8afd284879b22f5aeed73afb2eca4be52200000000000000000000000000000000105bfb471340e76f28901edbdbfe2ba246a8824b501ae2d4a73cffd2690181347c1e6530804614e88e2bb13a8edef8f4bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d0000000000000000000000000000000002b17f4b0b0231be229d87f075998435560ce9046a8b0e8f15e3a9f07cd52f3316f6d8c00d6a872362e7066715cf990e0000000000000000000000000000000003110eb232154f8a06834e2ddd33c0207ea552f439a6127b652bc261158209a00654e50341d333cd1b206a915fe0691d0000000000000000000000000000000007940e209c8934c185e4392f12fc0afe3d234dd1ef3f92df18d76be8fc42bdcdd6d1ea8d5bb6f07b3f3caecbeb5ef27f00000000000000000000000000000000012ec903a8442f68c03300ab02ddd08ec935d97bec9050d26a5e276584592df3ab87d596f90768d2c0918099b28963be58aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000010fdc16bff0fea02b325c672fe06297e0669094e2710d0baf3838f3e234c3f776bb3fd41b967c9ebbc72a6bc6eca70850000000000000000000000000000000009d64ce322e39d5b2d0872760a61a831877c450b1cfac6cacec52d4070b0f179dce90afbdefdaa8466f6a6e2e83ee8da000000000000000000000000000000000cddca46f3b24e05b76e61b4584bc716ca7036afdd914731a61347e453a26d07549e9808e553ee056bd47e53c75eac8f000000000000000000000000000000000451cccaebe1a188d3eaadd40090ca594f071c8b6d0e0d82f5b2d43fa784f8437e4226104c4cfdb24ece1ed75375aa616810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff0000000000000000000000000000000005832ad778dca8dfcfbe741dcf311024d76341d5920b6830cb75893a112c9d86719583d1dfa7287281fb73fe21650c3500000000000000000000000000000000044feb86b4816e45ffb98e9a670fcb039fd9d8844a2c7ff9b7752f20e619195fe6ab1148f30afa393936d3605fa4c8da0000000000000000000000000000000018db9365370a8c703364ba6d9c48b3512da46cc603a43c3fb91c0a8ee59777d7cf9ac646c3e4274bd950d7de92ebce840000000000000000000000000000000017bd82310e251701cafbf8c4dc5b9e6c88085b0df287b6dde7887e1f64f2d9487a25b31abe07aec7d99a75baa5983195c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d470000000000000000000000000000000002c1df194f01dcb503dcc8a283f059b82d141274c8f37cdb6441aa33f84f16dd288d566752a93ca23d26ef5834c0658c000000000000000000000000000000001700fa4459dd4e609453284f4f7dab479342675a87c1cb42b601908296557f39256f1597ed3b9ec38ad0a40a2c728f0d00000000000000000000000000000000135ed4f475eb99397cf204f971215a0303316a3ed8b62b303b4bf756ff753410b7fe263c4e97fd4c4b399c319ff3ad98000000000000000000000000000000000a487e179bf1b73627af9d7d2b43bc0e43127a8fbfeaea7ce958ddd53ecb27741eda187745e3917f1cbb60adf0286f5413a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc", "Expected": "000000000000000000000000000000001327c57e16f03fbf652bbacd16cf574113860eb87b8f2f6e498dc5dcc4f2fa63859d922d88ccd6683d503d0962db5336000000000000000000000000000000000cb06948c539cbf686f6936b6a1ebef2e148d98c531da36272e0334afca5c2b16a52da542a0fdbc3bf764eb877f5778a0000000000000000000000000000000003acddfb5bc4fd5579d3f592977365840be4d3cff96434e5ff4f01ea798e4401930a1f5d91f8de3ff98504dce398c2ef000000000000000000000000000000000a5a332805f704613eb085d6639f99667d0d9247cae34eabcfa399eed551f24c5d5cb05d6458530ae270b1be682e71f4", "Name": "matter_g2_multiexp_93", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b1b06a76e5bdcb6c1c2f1952b49e1820a9d8557229fbff8740269a0b819b91cfd0de20db0afd76a2cb0fbc5fac12ec5000000000000000000000000000000000347654df2084082efd32cba2b270f66b0ed30fa8713b27949fc9d270ee8eaa9b9a7896d7a52dfd8faa3e0cd112a24e0000000000000000000000000000000000bf5b7a2c0c8bc223ab334bd1df5d9fd4bc0c635379ed2b32da13f6178e07217bb88a4bc2eae0b975f2e566f657d23aa0000000000000000000000000000000017042f8585a07304995853270b1b03bb08484104f7498a12bc865f2a0e37e662fc4b0331b94ee5690efe74056567000bdedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d2000000000000000000000000000000000fcbc73d0628537eae417f8efc67af0a4c9c375d82406086bdff669911fe1307576333c389f189f49677cbbfe2ee98730000000000000000000000000000000019d552b85b1445660ca49518d202afdc67b0eb5be02c8d3482dc1b12e5d40a4ff95a49ce47809e4d6644d04aeb67b3c2000000000000000000000000000000000ed536c0f19f592180291bbce59a72ce5e516199dcbd4fbba736cae2edbe3cfb860ead0325dcc8f8d9be1ac126dc6cda000000000000000000000000000000000f5d4f0c0ae3e76b1c41edbbebcf1ff17c7cefd41e7ef8f75dfc10170834d05820149d5f721a8c6460cd0181571fca97db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d6207f6684f8d2f083c963551bbf0a674ba40e691a34ebe6164ff80ba9bab2cc23024a896d7b906fb74c95016a9adfb00000000000000000000000000000000145855e7d610b50cde39db8995b127145d68fc9bea3f075f65b7793acbb14bbb313a1a39bd96fbea6641baae02612b000000000000000000000000000000000005b533ee83cf72f0e4d9c9ddcc6b91f4364e50a106becf766987c490d559d0f733839ecc706bbc9c2c75b243814068a3000000000000000000000000000000000cd8fba13b9ba7557c7577da183bf50810fb14eec7380e3b3d4f2fed62bb36f2b5ff288736bed0578fb6f47fb6d22ac86e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca4000000000000000000000000000000000fd6a466f2eb12f6337ae9f9b847ac1481820013142af1a474229c5f5f5e1c0bb2d9678c19c7a3a1aa22cfc7b5052e0e0000000000000000000000000000000002a0340f5a0caf5c66719f7d546972bb4b89147989280542787d281901ff036b7c69d41418c21c43127c0158593aa5cb000000000000000000000000000000000deeee37ef96f26a4907e1a8a8f3f030dc09102799bd0c6dbeb1d208a0c86a423d0da6313e0be03c026da5614a6a576b0000000000000000000000000000000007220475449add59b3cc6570701528dcbdedacb9a3d39674ad4aef4d94114f24d2bff32f40b25af97ba883905ea6838a27a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000009d7023ebb73df81455f74cb2708c14ccecacd49521a0cf67ecb6edc8756e286ede59eed54d89eee5f77f178ea8fdee900000000000000000000000000000000002ad48fc3192634e7b01604678473e286afb0efe67a4377bb885d38b59ea00202241fb28c93232ce7c9a3dabb136a53000000000000000000000000000000001934664f2bfffb254f0415d6769f4e2ac710ee88cd822bf5da5df3a2541f887e4155dbb7e8056efb2a0370d6f9173e3b0000000000000000000000000000000019df518e1ebafe95adf683279729a3298fc8d7eb39c9a3dfe4b6665153f970e243e50dfb16fb87b3be54192f69766659446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000001870048d360f397877321904563d35bfd0817ce464e0078e9605a4744e2723f49f9cb21dd3d6f37f1f9aff5a6a99bc530000000000000000000000000000000000e29dd0da13ac451d013d4a38408827cb0e739772e1f250d31e4192ddc13d651ab576ed6b8f4ee44e928fa663244999000000000000000000000000000000001646183099579322e0115ab0b3bd6c814e216ae6b2b80206354925565b7bcd97bc12668b7f3530a95409456ac99bf01200000000000000000000000000000000092f6f594ad0d92c9c64f78c819c44320e6bb5dc1dc8fbe58acc7ce3c101e49a74ae6d50b1a668a3b7436dc445e3da345f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f0000000000000000000000000000000000428ff447de18dcc11b2c5c679bc2efd125464f589013c6964ea6cab33d9b7cbcce3a5d6177bf43114ee256f23fefa10000000000000000000000000000000000d1ded695e88dae6dfa702375959831f4bda688fc0faa289dcfb90a07f3a7963f2c9070958561909a2051a852cc15e1000000000000000000000000000000000c39bf1d11fc5693167890246c81133faee93a8639f459429757965e0b62e372153ce53c61f2c539247dbe7747b27d1c000000000000000000000000000000000e84ecb6dd9cbd4133c22350f07a976ae13dcbe4c6ae09ccb023f2118fa2dec68c20ba2266f9b571bbe30dde97480e0a94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000015e040fc8753f06ed1112cc06e2cb7142a4fc984834f01faae718c17cde782d5953547857ca9aeee1c4a7d91df060d330000000000000000000000000000000006789ac15d719a7159b650b757f7d3cf58fca02d3b8f3685478ad5e5b1dca0508dea7a8203ece97c7c6d32b2f194458d000000000000000000000000000000001824d75634043cac3fd17ff0bb141daf7010f70b5941d8f75f1ae076713afaa7e0a0a25fc71038baf1b1255d64c914c6000000000000000000000000000000000a2f71bf85af6392a8a070596e30225bec9e3dc12c70e8df7c545bd6bbcee56799db2c9a8d2504c4f90ecf6a5e18abc9b677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000008bd859ff1f22d682f86e1a0e3bdf3a332ae78d64814720687a3de44c9bdd7506d2696b4daf81a94d33f64983967fdc2000000000000000000000000000000000d7b4b958e0087f8edf18a4370ff98700764c126808d5c52afd3e71ee326c766c1e5712dfa351cf5b3c518e52133ce780000000000000000000000000000000013a145331bdd9c93e63edbabb9f6c541a7c4dccb1705f07eb353a0407074a76022a8e5f5f2535b41ecf6474649e257bf000000000000000000000000000000000a12e461b7439bff0dddb560dba21ec53ce88f71fd3dc10723f3d8742ed63a1ab725f7e9619ca1ccb729564dfbdb1be7f5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000001430a8184c5055008a06ea22ca9c997d1a24ddce7e374937c32ed1e487c80537b238a589b5e50b86fa194666bd3410e80000000000000000000000000000000005c78c94f457bdda242deab79524bd2beac82bb1cb427dcb2872b56d1f46d11fc9d69ba132004958fabc5da7d6d103fc000000000000000000000000000000000e985e8ca038b5dadc9fcaf22699e75cad9d2effa47fe7d4c579ee056b1e34ccc540372111a665041062fc6c39e05d170000000000000000000000000000000018c865243534fbde740de0ffbdeab0d38ee878c20f5d84c0226d1f2b14ed3359f5b5b909808b6b3789bfcab3be75c4cdfa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000c5b10541ec34dc0a8b8e42d9d6fd6f4f71e1fe56b5afa323f4ade35c0170b5e224a66771326d9edbddf2bd38c6c68ce0000000000000000000000000000000019cf33c19936f7489a1bbc095d0f5c6ddc1f43bccf7e8d1b30fb8e8cd1ef747b483b9a8e9faf21cba7cb17fbee887ad70000000000000000000000000000000010e83916faa7bc9de9feb8a7f34ac6f2aced06a771b662cbce846107245edb9c07632782300e838957788a8d88c8253c00000000000000000000000000000000066127bed5ac9f2871500fdd68a03ade57c35449d4b4186b9fac7c89e91b4ebf2f2a02e94d0b578aaf60b32017f147a493d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000000cb5a734a28b44f04d39ffae049fe8b63b138411661ca6dba00c72cadd47b50ad4b71e858e817561682d6ca378ebbe870000000000000000000000000000000000baf4d689baa09aaf763ae7e142b801223c8ff58f2b541ee4c44ab2460fb8f6dfc1e9f61a8d73aeb92d7d08c281cf410000000000000000000000000000000008a0c736f19bd0005c9d25f88565b1355e53fa3403021577de536712ec986567184f4dd626127ee80dd03cdf9044b2ba00000000000000000000000000000000063ffb7a3b4e057a9ffe233296c11fb462136fc4b187be6f9e36f9e6d335a3d673ef8b9ae6f60c146a075a1789f389cf3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d06000000000000000000000000000000000c33d89595d039722222b9b9ee7ff1a0dae896a8de97f202d3aca00bd81d0169f14676efc4b051bbd339dce862d8b60b000000000000000000000000000000001109a24dc6f70bea47e040b24df395bf561cf5f1ee79e90c9b0480fff0795677483a85e6f2e9ded4f36ca849ff39d6f60000000000000000000000000000000009c7878f3a4e4e3149b72149a7da91bf527c4d7c94b15ba80b02e0e50b02a2c482ecae9f458a881c87e669986514f6d70000000000000000000000000000000004284448e42187c128578b801f76d421fc508cfee9360a7203a91d6f9cc7ccb6ed3211fc5df9e15f14aea98bc298b2f95143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000271ec1a3f8e3364ba8e101b49c0bb17e2b7c7f27a4aa4d4db5c07203195050f30c1a05d33c524a84b1a2f0ce31a587200000000000000000000000000000000082ce9d1da5d7f192c537b2bd617b36b65f88b308fe1ff85e47c64b62dc62324458493d1cd1da9f5fe308d27545fb6510000000000000000000000000000000000b30356b59eb04258096d0c3f357fb04471583cfe6a060de5279bf2cff4413678c1716ba87d0b6de6b6e79a96ec26030000000000000000000000000000000003c02470a14211fef14d754f6f71efb33a06a76e099093a5b9512f907ff819e1e0e15f14995febe48852007bb5c380bd0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000172df3290c3c5044d590eea59980d02e02d4fc6fe7948168492362de8f0a85df0c3d09d8cd8b206cc4d1608311ef4c130000000000000000000000000000000010e4d14065315a0d9e48204e47955ee9652b08318251a7836f32e6fc015d4856444172de44b3b88efa1b54dad346e9b1000000000000000000000000000000001549b9c85cb2fc2c7495d7ef6aa1452e58937baf58717037069e6bc6d72ced3a163f800991cd26510e71aa64c44f66170000000000000000000000000000000007814c2f1734fcc8cbf9fcba06b936c86d0452a2370f8c9480b97105e42f9babfe0869cecda7e15500e9d8d868290201b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b0000000000000000000000000000000014f849e9749a5ff6b7b10daac7f5934be5f783d49c8593367c4243664e01b1d3552e878802d7dfee823e0122e9fd46f90000000000000000000000000000000000d0b32d7904dbf08269ca3c6ae3fe582501f55e32337ae361fe4a58dada560db54205e56a399aed33bce8758a05ebcb000000000000000000000000000000000cb21440baba44c3cc6943c8cfa2fe544a652f06423d3de06c2ff734ebbb544da07ba8982b3009b6c4857b73ceca570100000000000000000000000000000000174ef591975fdaa0e3cb05bbb4140abcb38f685ce4de77c95e2cec1911985557b77d9229940b8c9157ccf9fb553e8e0d98df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af", "Expected": "0000000000000000000000000000000006da1222c7ae02843ff289931fcfcb315f621972f45e4fb4160b8bf48cd8102d50fb53d2c699afd36892d91f5e608784000000000000000000000000000000000523048c5de2d0139965c976d8c3328666e99c249104712719e992334442245e955cd6b14a1e3d666220617d78edcc630000000000000000000000000000000009f669d4e7d89fa8d999d8d5a6323da9445583344276bd6a29494a91174aeeb29132926a893d5a0eeee9c3048ebc0dd200000000000000000000000000000000099ee1c33d6f09a8d063393d2a8debeaba93027e31f7b23c5170b6747f56bd6e6494de966dc280dd67a38d39ae35a336", "Name": "matter_g2_multiexp_94", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b46cd281a09b85d977e88cb2251cc61cf13181522cf1c344b114783c4fa22d08f2d008faea3dfee8ea7c56aa35ee39a0000000000000000000000000000000012b530bd34f5b31161355436c7dc444d41d634b0ac3292c0096558618005fe82d189e4b208a48240dfdb4d50ad44dc620000000000000000000000000000000014d334e7835d4bcee2698ca4f643f9692f28d8090ebb8ed8f67c60639c64eb116067e5e8a6d04a0c88c765c608d57ef1000000000000000000000000000000000578cb4e243b4b20c99afefcdc0e0a9e308ab4bec6d655a8c4981a711515c807067472a6fca30060d776213b8ed98d74e49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c8900000000000000000000000000000000046ad1212696acdbb686543d284d7cf2e1e8e8c10af76c6ba51d55f060c006dbab25d3a789c71c428f5bdde9aafbf6d5000000000000000000000000000000000a6a880d52fed6a45bdc61d9ee78d8fe472e76ccbe155bddd0e2a967f4d116bb9f2dd4c62cc6f7224b835c8060213ecd000000000000000000000000000000000786544589eda15edc433edcbaa46d4953da72473f04169ea64dc114b99f0a58181d41dce1fcaf7f3109f66aef02e53900000000000000000000000000000000030759c3bdeafc94fc8fc0b03ddcd96869459bf54ace74582aa06c179323ef076aef89c09ce8e7bf9109ab2e8c8fb0be79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f3953000000000000000000000000000000000e6addfe0db96a7377fcab1fb92183fd7d7f13ec003fdfe0740bcc8cf03d8cc602d5d808b4bc874f34944a65b249997a0000000000000000000000000000000014a4337107e716113d8ba0fc7f75e85edd1c132e2b3dadb3f9cdec1440f261513646525314b5c0de6fd372472aafe877000000000000000000000000000000000d472ee0484ed831f8ddf7ad86faef5443df8b943c6fd4c3f94c8d52d9eed6fbb53107170a60f25be52219ca4816788f00000000000000000000000000000000035d06ffc452c65a31f80c3f8a0c1e2d15e32d993ec06c50499bc0fb8f669acd3d2182ba23d942489ea922baf61dd49cd2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b000000000000000000000000000000000ef1f5f6b3041939557368d613279043d1aceaf5fee3ed90b3b756ad409d700fb41e62b3758c8c2d325db7a37f339c610000000000000000000000000000000004d66040a8e055399bacb6a1e762b698afbfabf789caeb957fb7a3dccb01d7dff5414e90f5a14961c4e980b298f834ec0000000000000000000000000000000006efe9e66078000c26d375e87ffaca643aae9cd3f8337f5718e0e268b74f4b7838f7661dc0ce60f557e162a21ff467160000000000000000000000000000000014ab782a3b2c06af7e9c2f28f1604cbfa8a676a874853bf38195780751d306936cefd1cc38c2192cb756e28793d2abb3e9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d300000000000000000000000000000000057270788a199a894b37a526a26bc4d293780d365a6b66247e7417884d543dd752ef7c89f2f4b38f4b51e6f9d86b45ad0000000000000000000000000000000000b59fedd6798487ec09d226a7406b27f04f7983075b4659ca6a78c6bb8aa83828fafdc6488518e2cba6fa4193de938c000000000000000000000000000000001105c18d92b4192833302814ee9b176831e57fb64b703ab3c2d3f440ab302c8fdf7ddc81933d3b1adaad16038dd6dc1f00000000000000000000000000000000020509b08e6ed980df29da649051c7095edcd4eed4ce95cd797da430cd09062a110bae21b6f73daff2053fc0289041fac818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000007be348ccf6a76827d3b9b33e7a89378c133c9b226e47dcb205ee061423ee6e1b838bc262a7befae7c15aa385ced00bf000000000000000000000000000000000689787c19192ad55b9c6c260a5ec3aa203ef71f0b746eebf10f82526c4fadaa8570936d7049c1a46e7f3cdc455a63a6000000000000000000000000000000000306965b09678d481aa4c754d56a0bb4565f16f7523cd0b404fbd39dfc3b6ed483f5239fa30f13aa3e87918ca039d5ee0000000000000000000000000000000000a2586143f9610a96eb0ef86593988770db5ed49663eab72f8c368b9388bdfbcd02fc6bee09f4fe055813d140ca0fa89ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c49645000000000000000000000000000000000018f46dfdde786a88e582ff6addbecb4f58e12c2625e3d6440f2e5b5781beaa95cad6f63b7d132e84700e7bd344fe3200000000000000000000000000000000185a4fc339a95a50551d53c18bb0dc3b74e9c164729c2b0d919392f7aad2be3ebae3b8f676ab81ea05233b3039918ab50000000000000000000000000000000015395b020a9d0bb336066c1347dd91c557b6ae7b8817cd8a2cba9e5bb149ca3401d661227c26d52a9be234faea894c8a00000000000000000000000000000000103d9d7e33a0767554e13b57dc756981488a3c7dfcc026ea84b35b0af21193e301226cb5a4760962707d19a95841be9296acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000018359aad8af59cdda484232b885d1b14956ec04b5584684b13a64d97b8310c283e5d66637dd75de405f5f4bc65a6879a000000000000000000000000000000000849fd55e4f3d4dfc643dfede6356826eef21290b84f7e8e226deabbc84273d95f7be5479e9656dc907ec367a7ebf8f60000000000000000000000000000000006ee01b54eb7834b4de53f821ad46f467cadffce6df09751b728d0952bfe615253d7ad173892a52c6181810a815bd90600000000000000000000000000000000161472d45b56dd9fd276fc607f2eef84c5c843ea05799e732d7eb6dce96c632335949e1b3a06815e410e919f4cdc3fb360bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf00000000000000000000000000000000107ef91cf3a3068c4e5644676f7bc7c5f9ecc361524bf3fe2ebfc606f22f8f83b38c0d4bae89f3cdff6119cc27fedf820000000000000000000000000000000006a7f7cad2fa9db8824e4e30da7158f7737d2536554b904ed835c37add0341c07c5220db0f9801da2587a456300c7b75000000000000000000000000000000000f6dc3adda42dbccb1d1e3fea8918f5572e8b26ba3011429e754edd28559b731853761d33777f4e767094f80e63d417700000000000000000000000000000000107d93537a79173ba9367732fa3a28113ec37e053cdf31ce6970dedfa8a9b4cd55238289be9a6f40319e3dfedd132f95537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa98302150000000000000000000000000000000019dc19a1663bb05ebfc0b7cc23ea9e07376de413f77e15a685a3f11fc19bf0ddf38d5671e2a5e6e31624cbcd47a19cf60000000000000000000000000000000019e78aae57f327fbe8ce794afc22bddde08ff9bc9ad3527601cb1fd5dc0b8ed8fdf3b210f86760954b48bf61d74162220000000000000000000000000000000013954a533bf871e99f4a7d81a8b9931c480ce7fc47260c3708c590ade42e6b7bb887d4d24aa18642d010a8170cf85d34000000000000000000000000000000000a561d3f64ba31a6d45ffcf1bcac95f8f665133a1e962e31351ec78e369042bd3afb0c43d12b3087168c1142107241f31a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a20000000000000000000000000000000007149094366e29537b0ad7239ce04bf49f253e4b746b9fe440dbf9b425bfff21064fce66e286e08c87dd83e22a3b499b00000000000000000000000000000000045ead132e0d03c842656cfc82a45c8b4a3b0cee7a5d071c5f235791ff7b5ead071b2c529b446a15aa8837aafc11222d00000000000000000000000000000000013159458f2123698ad4e7d41da47ad7d5083b928839e346a32f2307ee69f643ca11335d50e47d328b0079f1873cc7e800000000000000000000000000000000167edcf807ee723ba70e352367705448047c6b5223fe703381af6bb103cbb24da739ed005b14fab5699fbae6574505a7c7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000feb6f6f85903b3c8e4d6ec2ff234775f12727fdf7c35eade09c9773b004270f659b00248338f0b749d6715778f1f4d90000000000000000000000000000000003300794df19b9e472e8b869a2762c07a9251cdb96b508dfecdbd62fc3c3843b37118d216a64519bc3bdb71e40f9bd700000000000000000000000000000000005fa144135a5d6cf1c73055750ab6582b4c6d368566172b75902b1fc7a6f5de2a251ca7efc7ac6cc6c0bded14df02b700000000000000000000000000000000004239a7bfdefbe78116a588810328024b1bcebaf8f28f09387dcab66dcf2b02c94002df09d12db369fef9dd960783c0b84f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d00000000000000000000000000000000007c8a24005575a3098c12ffa65095bfe227ee59e5e978a7ccab7a9a72391fea61690648c102ce24af723945bbcafece0000000000000000000000000000000000323d57bec7dfbb4614c8c3b286860fbadbf71901fa006149053ea614dafd56b1f3d6a86fa55bf1cbdfe8af4ff08dec000000000000000000000000000000001180b2b0b9c4c12f6d06eec07bbf6f5a220722015fe5365d1c4ca9e58ac9c8f67964d8230152d7a2220575c756bdf8b0000000000000000000000000000000001969a364c447f07d0820586bade587ccc816e50696aa0c5ea4f1daf6cd577769a890b44caa013d93e7f21f5ea269aa85c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea0000000000000000000000000000000008a51c01c3bbed13d42a4da626a8b89e2811db1d83d7de3332b36881ad14a5c8668ece4f5ed2b71204810457aa3d75cb000000000000000000000000000000000658a56aaf627e3f776d3f03caa2c00425bf197c6fa20c92f563f48260109a8f935d0d1638f5039486ce0c0100834fcd00000000000000000000000000000000126d1964f2d964c290cd7364e175ca4a855149e5c4ba488829a436b09ee5e21f6c964e439739f15317873088726bd51f000000000000000000000000000000001803186f88833393bd853970ca4fe414a43b7a619ded1f9c830444b4d43a94e9146146e2284d690436b395bf1e3fad15a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000002dc3756c7f4bb47559cd720a3acf4159290d7413e0498877d1fe321cbcb7cdda90b6c8b4ef8e27b2642b82ab9b3174d000000000000000000000000000000000c7490f1ccfdd91aa37a3044d265cb0612bfd9c065c370adb813b2d96f02d44041e79921d1b8935dcdb8c83ea4460ef30000000000000000000000000000000007beb34bfb9ba9b6fb590c7e830400888095d1958b252d187c184de91f165e12599d66345341292fdcb662deadcded030000000000000000000000000000000001ce203d58bebe1eb5b7cbc6038f75b2f7534bce9f50e7e4c91d6cc5ac1bb68d9fd8ce99206c5ec92bcabb71672c6ac195a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000765acace3e238e51bdaa08c0f6d737c9de55b5ce9ac3523335f0d35bfab6f4e7e2944b8aa4ee031ae9d39d4db96e9ac000000000000000000000000000000000b0fd488a6f9e92c4bdb5e82b52a0035f9a0aed7f69ec65303632017669f34d11552f849326e4dd204d58f50f3ad124800000000000000000000000000000000033991f66588b5e39eb78c7cbf62a74bbde2fa1b7c96164cb58040f0887c485b372e0ef4def9d38da9c6f5c4df2d59a700000000000000000000000000000000187d41fa7905739078d2c2f8775394f830d20352a9d91e97568c6929412f356009239bc9e1da3a8c766e89d09893b5b5319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40", "Expected": "000000000000000000000000000000000dedf92894c567ee656051a7f02384edc7206152af6d3c5f662ca02559a3cc349c6b034c6fadceeccf652a396dbec6c900000000000000000000000000000000089deb173bda620678247a7218408594efff7ab0cebbf627b93ed37e553cf944e09232b92afe2f5f31d29bb9ae442c26000000000000000000000000000000000178bc39b2ca8b032d3cde53d2da3f8797026d78c76c51381b377c79992f027cf55ba4e182773c99c99ea6293a948e5c00000000000000000000000000000000195d9cb91537e77e7a4be4370b982b6d36190342ef6ebc2250a9cc8ef6ef45211736ce1f41da899178c1adcc4927a9ba", "Name": "matter_g2_multiexp_95", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004638ececd7f626a069b1bc3ad9d0f7cc71e5f0c1b11711fbfee1f81466b574f7a11de8161e55eb574ab367f56b9d6480000000000000000000000000000000013ef4f403f139771afe7e97815d3b3777818a3054d02125d3a25138e504c8c2c6696061572322aa19ace9ffd8e3ff308000000000000000000000000000000001910f776582f5acbaea626d2378e9da133b63afa087f25c2cfbcd1e7b34f6a237f2e9adccc303f9d5efe22496ee2ab75000000000000000000000000000000001963bd62098614c4ca2fc2a9e2d679c2b74bf9d322d34377cc63c3b8e7f8a4eb7d6d440d081e044606402fb3f51b0cee2a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000001935ecdf4f21cc6b510321b4ed2663e339954cd7399b9d67f1d9e2ea7fb9bfff8531f83ab59d3f0546393dc289ab2dd7000000000000000000000000000000000f390b86fb4cd4c1a072a83e1d1198a57a650fa6e94b69d983b693c592bc0c8fcd9a46c6883adacc4c7e2546dcd079fe00000000000000000000000000000000136beae11ea54ae26a8d69015ea7793675625b2013dbeb081a5ed877832849d67ac709b81fcc4fb322b262ec3776c0c00000000000000000000000000000000011f1df574f63f679b6464df463b58948fd337a4b3f159229ba0313cc040303345e75a3a2b0ed0dedfbefa89d8331d074f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b0000000000000000000000000000000001510378fdcde4027999edc99d49bfb46423ceb0d740829e310f8a381a7632fd0d6b6aa3533c2702a5ca76d386ac0145000000000000000000000000000000000bae237bfcc061552ca07eb14300cf557c974611885aa6894f7933f7dc7a0a1cc3b5587bbe1ea5fa604e3cee1db5f7c9000000000000000000000000000000001743207a1814990c798dc3de272a02b1b194a485bf09faea382dedd957861dc15cbf981f9906cda50cce2899785b9a6300000000000000000000000000000000106f902004c66c80437392e92cad73bed6b73010bdc7b6a75de4c01f0b6fbe5b8d1f47378279bfa42b3af05120854ced07f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e400000000000000000000000000000000123362e41268f7821fcd2294b032c6a51c6d80506eb052ab6132267fa248a1a60c3e4eb2e75a1674bee1c9d46d82b9180000000000000000000000000000000006296670461ca67081cd76528446867e1a4905f88742d0ed8d1f7baf86e0a5e5ee86c8b0eeef07c14dd821dee0143bea00000000000000000000000000000000058bff9544e4e02c063158a52a68c93c7544e8157d37159dbb99b51b09e3d8f5b307bfe63a10fa409e20a35219ac244a000000000000000000000000000000000b135edfbf53187004d0977db94eeabf426ad7bea84ad76c6ac771fa186a073d430af76d717070e3c4057a7a2da095984e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb605000000000000000000000000000000000133d3a223112dc5665e78fba8fc8d040d133858d984e66d2382d5e629f9369dd127e93c7a4da77fad98a0520ebedfeba000000000000000000000000000000000e88515db391bcdeeed2a9f64d27387af0391bf832164fba79100b560d8150debbd703c140dae3ba9b1ec35c1f45670600000000000000000000000000000000042583722c69a19f413392c6a2b75c8ca969be85eb951056d7e1d94e046dba49c346d5774009b8463a40b0576ccc1a6e000000000000000000000000000000000ee61a9eb6ad497c57405a44d798868e22b4fd5b8c480e9938cfdd3f1817eaaf331a9988368680158c59c2801add0a7a27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f228900000000000000000000000000000000020352de9b4e8ea1acc8589bb22e23dfd0ae3a80de9e21bdb3f6391dd05a012e635de9e1f5f450bf4aab05728c054f8a000000000000000000000000000000001733593b94ec800bb59ed97dacbefda5ad882a8023346bdff8f471c5613c67247e27d72cb4ed8cdaa0f236018dd2128a0000000000000000000000000000000011f272a3b25bc519fc3e229211b846042031e22fbed22ecd0d1a4ef1d05feacf105772d71157e3d7293575aca257cd5f00000000000000000000000000000000153b4b4d7d65f7bd13d20fee4812f04706c96cd1a0d27b7e139c47299805e0ac86e8941aa38d90331c78a61c2dd56aa3c2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc00000000000000000000000000000000143e1f6dd9397f0e89a46c6ec995bf6c87ec8a72b309f050dc5b3134e00e2a16327767cb0573ca5ea9776215a5815df500000000000000000000000000000000186cb3af2cdb4562bf2d0c180079547cfb345cc3943fd7f9203fabbdc1547079cb9ed854f9b1a47f513e318cd409df83000000000000000000000000000000000c8c9197fa5a1e66b371a653c5d18c01fed8d17a8aa92d89b2cbd954b9fd2931fa61abb6676e4851dc9481732c6195610000000000000000000000000000000009026b259e840cb5264f6aad6ebbb09661f5b6d980389817309aef99e4e0cb228d3a7a06e6c25bdb1aeafe5acdc44441911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000007bceb74ba86c07d0fca20e4febd3b12b1fe9f786c9a5da0531550244f40261d7ce728498fbfcfe16cc235db6ed42e11000000000000000000000000000000000883104ffcc0d040d70bda04dcf67c1197c39e200d4d9daf5f3c185638a13dffe3dcac94fce4175187dad867e8d2b78c000000000000000000000000000000001404e48e86f199486db7d40076cc8dc4e2aa2c1b6d4bed8f027512e2c71817905b26ff4f0551f9c08a2a7a27b2075b6c000000000000000000000000000000000b789a6addb98ea43c0f9e85831a75b8ee1977936c17929fb45d4c06b4f1ec33b9b41e32b52cde542c9e4b64d27c686cfd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b000000000000000000000000000000000654e7f3985bf90dd1e3169382690fdc0f804eb6384ce407a060f539804fe6e0451094abaa0dad611c15d3ee52f31a92000000000000000000000000000000000deeec957d58a2246ff8f7b7448f5198647576c16c1717369ad155ae36d5a6bdb42c8d6a1f0a095891fb0890b6203f950000000000000000000000000000000013a01a6ca4c296f59cfa4a5f5399d28af76ffcb8b218c861d5e6dc603e140f730f632028c8da46c823d87bff5ca703280000000000000000000000000000000003698f659e86b96613ca74a480c81e749bce4b74324976c1d241a0911d078926fb2adfcc3f901a7a015a02f525ddbb808118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c0000000000000000000000000000000016ce72e1798ffd84b52ac664a184c6cf5ce1ca2aa263c9d056355cf610517e9c7bf7f057c342f6e3ae801b84c2082c0f0000000000000000000000000000000010992af1438eec10881b5e2e3fa3b1e91b6b5313ea58dcc0cd2159f8ba6ce5912d81b38956929620e04b3596f6835a6f0000000000000000000000000000000014315dbebd532d0c835e8e85a02c0814574cf040a20c18d06573718223c8ea15b7ea69f0cf342dd09037258398ba4bef00000000000000000000000000000000136d13a83e72525b2d4af54d14d5e21d8bd9bed18543836b02ae0a7e51d433c93aa1943e85f978a8a9ff4454d8c5d120c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce7730000000000000000000000000000000002c1bf7dc75006c2941b89a2de52fdcdd1b4fbda5b14fe3fc165915b90fd9d93cdb8105898ef59d5b374707f0afaccd600000000000000000000000000000000049a16efcf81de84e443666bf990f6aad2145f9c9c2c61a752e256e8f447dfb27b462e4553544971807f909a666af12f0000000000000000000000000000000000aa4702fb69d791ef958826753d3f74f61c7a591ab94bb6c1bd5d82d94c5877121ecfc1e769d0d16ebe491b775ad96e0000000000000000000000000000000008cd7f2562eca6c53a37382fcdb04be53998f45c2241bfebac3d1fb08d8e1d4df3182f2bd63861d0de72d58072356ccc982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b000000000000000000000000000000000a6b30c4ddb692ae33c903693cdba00ba48efa48e90b9cec9dc747004e57a8d5a05b5522634fc0de306d38c28390dbf4000000000000000000000000000000000601341b3c4057767a910bf30dd16324ad7abcb55b7e98e73584f26d7f87d8a8d24ff2113c12ceb3077bf65e0912b2360000000000000000000000000000000019dc9c50f613470abdb5c763c0272e88e34ed38e617d6757f4e70d05b8ec9f67a023b4ec1363e7e60e38cff64e18f0510000000000000000000000000000000013fb1858f7efeec5fd03d9f7f4513e3e9103c340eee7bfe48ed3cd3dd073b96add9450a17f12e161f1d44669b1b2f813000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000d6c135bfe0fc7af93571a69b7c37ba691f051d69582cf159cbeb0bd59b48342172a82a3eec2e3d440805934e1574f2a0000000000000000000000000000000001b04e56cb3bb221caedd3582943f89a33b955f624f9e473941f1dd987f2898339142a654d11d87bf8bd2fd0fe0d4c1a000000000000000000000000000000000f185fd420b761a1e38d542558b0beffba916f369b37296fdd8878a7c3d2ac9d3ab1d8e45ad799f0d81bb439b5e5058100000000000000000000000000000000002d10ce460c414fa1094ef2b7de8f1ce024b6d086d10067be0fed4e45dc25c8e50bef361d39a2743be1e1ea4fb7e2ef773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb2000000000000000000000000000000001972ea36bae504d7047639ce6e0e6c3b16afe89fa3d6c6b33c910c8d4b70782d8165912e5bfbe8bd84f78f9f23f7f956000000000000000000000000000000000ae55c4fc1c01f1bbdb060191e8551a7ba5ebd3dbadd138202090d7dc6765fd1ef5fe8204ae76a8bcfa03ee5985a35280000000000000000000000000000000018ebed295805e0fc14f1c7b0e6ee12ca48cb7177c1d367a613e0d6cfaaac5128fefce0e8f38d4e2f11ae0d327be466a400000000000000000000000000000000157068d89fe48e77e0f62e3b5b0293423f35c5f4ffb9e0577f5aa49e91cea6bd312a0e65ec08af9c1f53de6499409c8d759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e00000000000000000000000000000000120e4a8935c08032dbfc19a43e2a770b12b05cf1dc229e12f683f0d7f604bc13666bf318fcc38038b618ef83c9448b870000000000000000000000000000000004e3f851be46bd85f37c8b1d84507f4ed63ea76bc305cc26a6f4cfa2135d5affcd3b319d9f57619e21c964c6246fc3f600000000000000000000000000000000138733f352029373b19e1c40d5958a04257e2b344770e1bbb8f377bcfb1c7225ae7a8b0b0e57795ec06a08e13c90d7de00000000000000000000000000000000093e85783c556a017829e28bc42b607b1035890fb9743bf0e279df4dd8a695c1dd07a76a213087c3a8a7e614b29b7a1ecce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f0525302938000000000000000000000000000000000be1ae00f9ba0a2e57f94728508e0029b1bafd52c91ce718ba41790a3541117d1a9f846d68440978cdef016c3b9ae422000000000000000000000000000000001947683154204c9fc93e3aeac17b417453a24d01804e8acbf6f67947f5962dce875f49d05e6ae65384602828784f852c000000000000000000000000000000000859dc1c00b49cd1292cdc65c6aa4b11e27637b949c7db508930c557ee3ce00f98f9cd3dd0f6d73a646d176a91d75c070000000000000000000000000000000015a7a6984b5f42aadebba1e1f4682aaf1a2d01c9ce2afab7fed2269373467787bb1361b493dcdd862180e9159ec2ad5785e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f", "Expected": "00000000000000000000000000000000047cc33d9decfd059724bbb014fb9cd95de187e2dd29cf4a9bf9ad06d415e2cacb5a4e49266c13e0c38f2563d1a21d6a0000000000000000000000000000000011c79d93fa870d541e79ad4037c20b85d3cec053587f9df60dc11e7dc2727d79059f75bef76474c09fe61ed10b317cad0000000000000000000000000000000003df3f0db20c5ffea4dc9f4d9333d76141447bba50da18e521e89aae1e63750c71b392570d79e597967dfc9952e903c60000000000000000000000000000000014e83ea645b1019ac2dfafe370f616af0c5baeabe217ac1f9ecf78877717475b25911b339557422526a8163434439923", "Name": "matter_g2_multiexp_96", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cd1d25f285c2073175ecad5bba4987cc52012eadc7b19dbaa20fa82d7a6cfb8a52f33469b6308d921eb4b3b23f7022d000000000000000000000000000000001707b67a23d9212d30c06f26f0040c38389b185057e80236d2c828a8d9ade4f72eee1d6eccd78e4f4d71e2c28ee9539e0000000000000000000000000000000008e5c04effd14d915b9afc2083afa2b6d4008cfa0e47144a41982d8b5a8e77922a2609384e2c5d18c871ae24a7d505b7000000000000000000000000000000000f414acb056fff2cd6d9b408adb6eb7f34c8f66a66ee93945a3381d46c2d181613047ca6d4067614c190da444223cab685431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c0000000000000000000000000000000008ea841aced2d0b8dd688947648a8ff18d0f6f03f63ee1c331f126dd4fc0da3d386535156b80902bdc1f65add7769cd70000000000000000000000000000000017a32ad2763d99c38c954f62466e78c0332e48875e15afbbe9c78376f1bab12346c73a573738353e2162d3928091dede0000000000000000000000000000000010ea738884dbfe5bc35d031bde9aa4109b1fca529502e236aebb5ad0bf71dd2f3db250d924415b0bfca56519f8ca5d290000000000000000000000000000000013699e29cc1871f51a469898be8b3c732b5cf7860286e655e65bd8176832804d17b48d0ff85eb023360db78162581297b6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa00000000000000000000000000000000004d504e62b2825651381ae862fd33407309851d5291591cd0f541fd092800d709ede00a9134e65ee752eaceb0a344b50000000000000000000000000000000016481efba290c37aa4ecbf940c76ee5df199b0c1f90fddebd2db28120bb5a14dd9f4a067b6d4889aeb683cca0f6ab337000000000000000000000000000000001400c89942cc63417ca4cb05c9d81dda3251e5611c27fc7727c3e803170672f103bff26f7216a0b646533aac3171488d0000000000000000000000000000000019889641be9db08880543ff476b9d4c72167092548ba49a3f3ace4518d3874f4f7993ae7b8cec90f092f144ec9d66c1a54dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000001927fe80adc6dbb581349c603103ad8831e645d9275af8669939b83829182cc6e2a30df2fdeda6d3aa2e2a6126e00ba3000000000000000000000000000000000b6d7934d5ca1098a85a0c60acca075220105e221b802b1be97c2967820bffc2937fc3278ed0f26905c60d44d5fd8dc000000000000000000000000000000000057acc1379f23c0d1d37427d400eb1b0a89f3736c83d3ffd797ae279e01e2acddd84082f13f3c8b8f1bf7c275702a9c700000000000000000000000000000000038dbcd7e08d34c553850a52336991a7d48968e98057e930790d78b5c6368eb2fe01571b60c4aefb653ec04122953d56eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a00000000000000000000000000000000014052031b88af979b7edb06c99c2e46bd9df2c862c7e1b71321754841fad67fc3242b51141e49ad86b61344aec913f40000000000000000000000000000000014806a86d078ee9bdde99257b67f50dc2ddf9bbf01dde931742ee6f739aa986cfdca06cd32d23d86f2c14c3b09033d29000000000000000000000000000000000e0561e795d35ceb8bd9e3b276406ec1f697a38ada25d1dbe08715a28bdd3d6ce6e0aac01f7dfc7c2b403850ab213b4700000000000000000000000000000000146a65209b09487e00e356e3b29952280ebc6a06343c4ce14efa0c6281bc2482698bd02295bc35125275ff5f5bb867dfb273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab8500000000000000000000000000000000021592cd7f4cf9cab3be53561c889c9ee865961aad51339f6393dd6a0b7dcc8a7c48b753c947b15cd3add01abd3d76d8000000000000000000000000000000000f9e1a80bad58055a8577700c177889c4d702de04343c1202eaed9485a76493158547b20bcb552b66c42a0c86df809ed0000000000000000000000000000000013908dcff1945cf06f038e3caac9a7fbb3a6466ca18627e93468a875759a2b5599a96834ff21fcd6bfbba82b79536b9a0000000000000000000000000000000001b6354665109c5a64613c3bd7d805b3a34098708f3d41c7b77db031ac6fa0b2d4e2f2f70c84ac78687b0c0f9bf334771342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f0000000000000000000000000000000019394063202186e141dcebce7b8f0f267ba6057a0f993bb1cbe22a5bc528323823bfd1597a87017d478186a18f09a47800000000000000000000000000000000148437bcc43d432d70b47dadac8e738616c97d38d0f84dc132599626612f7bba74988bb23ae47ac15e6f70c059d607ed00000000000000000000000000000000180851594710f4bb5be7ae0104a383081c50f59e4e048614660ab5a4e2661e171510f5b112d8cf97a6af27d56d137c860000000000000000000000000000000000599f3f82f29b493ffe9ee3a8363b9a599a5ef3c9c5c680d4e003f4ac5a7de0562cba8e2a4c6da7d07cbe86c3f7bfb85b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee82398125000000000000000000000000000000000cfdce7997601afbe484901893a1b5fc0b83e8d238d41d2f889a58fd4d884df1c667a000b53b587df2c42ad46aa2c3e0000000000000000000000000000000000c50bf3e06400cb10494cd09bd89f3c96ff49c9f74dd5325f9489ed6be13b59bd7b0b2351411ac854d430405b8a2a3de0000000000000000000000000000000001db313a34ca4073e4fa2287e234ac32bc579742de22e5218f7873b922f5804894826e6054925a394f125fce850f33ef000000000000000000000000000000000e0627a66d286e8d4d3654b32fc5f552a7ca12f0bd47eb6dee0dde22ee48165247c067a0f4c3d422bf3562d38a3c0cf1249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d170000000000000000000000000000000005285ba39f5bd981fce2fdf853706d70992acab2dc6d4c4198144fab397392a60d631056b580d0d98f3f350414ce554e0000000000000000000000000000000013bddbc1180f155872376fcdfaff2fb12d3d9645b81bd1475a5323ea855cea820ed7eb693791caa9bd3fa5c66036439700000000000000000000000000000000125644d32df397def58dff875d7e3f14166e765ed49a3991f45b38d74db3985fc7f5052058d85594c8b97afcf850e11b000000000000000000000000000000000fca4662eb1b39f576ee820385fba88ddd2fc01fcfb9d9f874453ad725cd5defb357be028fae97ce71bc5ac26d11c1bac014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e8950000000000000000000000000000000015b97d7c74c8ec102083b41d7ce5490466e1c0e261b5ea5c756d3f9ae79dd2d8ec6eb5075cfb76dfcf7bfdd80442f7d10000000000000000000000000000000016812f845faf96b8b69ac7a6af3c8947aa25877199e3c12552527706a17b768bbea259ea61ea82c4624a96cbcdf4040d00000000000000000000000000000000123ad55e5cb5ac5bdd3ca0a5afa7c3f8e4b98ad91a205f073fb546fe799ffc57b3c1c3a6209547ffc6ef05fd24be6f5d00000000000000000000000000000000017719f31946aedabe0e9d88ef3f90eb6ceda884f5e3d2ece368373785b2d8bf0f9677731803b25accfcb6cb716e0aa4ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000000497e3480d58027c780f47cc35a121ee0cd76c4e84d9a2f9002c04a1c286be990167a0138049ad70467132818f48ec9000000000000000000000000000000000ec0ddf938553105400f70989140ca322d996f48ffb1b35641ca36a6ba9ac1daac1603c100822f80cf62ec3bfb442158000000000000000000000000000000000a0b6ebee28a792df46d2f727af812c15fc91a471e0d8e34b25b26048f3b9606d8375b5b268c40fb04ef8f098e1d03340000000000000000000000000000000017843dd19bfabbd0cfa41fb58e70a8900397d17ccea783087ece90962560f5cf090e8d9eaa873a6a6ebac45219ea97a68b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000b27c82d71f7e4aab9a68596669596df3f62071e921e131ba4d9e59d8d81d370e077e93a4a6a43e059661227f40b38c800000000000000000000000000000000093004917ceb2fb4a1b33960ff74943d520f86e83aa02b9a6c85e4b9a489e9331863cd30cb6ad6f099d03289b4ada5520000000000000000000000000000000016f04e35186c7deaf730708e1678089bf3e73c1164bca24bf8f70c4f6cccd5bbb34bbb5dc313ee428aec4ac9c638a01a00000000000000000000000000000000011052348cec9dc3e85e01abcca5a652461f08a9f5d72b3fc27140a6a571137f0065ed7ccf9ec8cebe314ad9a214d5ed94ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000007083dfb0738d58ba8933a1f60283e5da8bf90af5aae4053ca573ee7223d3b80e4bdc30b4a831ce6af9f52f393e9742600000000000000000000000000000000130c627b7d3a527c94cfeba9f514e75eac047e1b6088c082229a8c95d0765a0898ce1e45694ca2c7935bb8e41e44e8b10000000000000000000000000000000009610645b074e652a08f2b89dbe594afa3009d795ef211f7c036a56274b1e1bd69a035c4f356b6b21f69b9cec2bf7c32000000000000000000000000000000001020f3cdef468af700269aa1e9d928e71b8c521f23586c9b0155314f0073da7de04ca41ececd5edcc052af72c05f0e4bb1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000123540047f0768b0af841aa4aeceaf3dac31ea832daed86c8cbd1d33ed0282c6f697d5881f9022af032e90ff82efb6bc000000000000000000000000000000000113daffbe413075f5f4f6fb42f37b6e9d5e5822aa24d6f865792f63e6078584246bcf8b17117385db1d6233974f6ed9000000000000000000000000000000001067b46fd221b6995d25d4bf0adb088e0554d858d4e5d9d6b59e1ae2a7d57188d559b0208918a8944aedd62b1ebd4f4700000000000000000000000000000000087dae77e483d5c0baa37b9b96dad5ca92b5869fa253bffad24dd8747446f7ce60858b52438e58233210d86f470f765fe670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af8500000000000000000000000000000000039d09a094d655c139cb9714aa258d9548473162548048b0f07c9317a41a7e5dbaa5aca156992c8a509d4071d9ae4394000000000000000000000000000000000f0273a38b1b9d006efa43c15a53f026587a676912d0275968608519e97994ea9c6a147e377f68b1738ebeaa178f9c1000000000000000000000000000000000132cd92417578d2e46884f1c1a1080b1916c8c8404d2533a4de02bf8575c80ce7e8097c2ddd1f95737355521c0ec21ce0000000000000000000000000000000019adbf09a268a3ed8eff936d25fbe8af2874e44d2580c7941dc14fb89c5da963b468a7088c4a763eac89f4d15deaaf5e54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000756dced467ea32c3c425590b7690a45e250e464ac6927ad3f5d2d8d2826961b8dd7572db609375c8d06cc3b9bc3a157000000000000000000000000000000000b79b4eecbaf1d0f8a89f9ef8fc144b3aff38148ae260da7c20e9dd3866d946585df7ed12c8b7005e7b0e1387c9db41d000000000000000000000000000000000afc403b008b70e19f17b1ef37c9c396577a585b6c34b23d09621b891efc00ef9460c3f4b5f3e851ef63620dc06c824300000000000000000000000000000000024e06f3f3b18c026a166c41f75d7bcf699480f5b6811463c27606c9ec1232fd249a46235b7f5b5a2ed3b53231b333150563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d", "Expected": "0000000000000000000000000000000004f2480d0b7e84a996965b76055f6879aab5bab26656e4e5ca7220adc17f14b5946047484327bbc5952d9f2ffa5f92af0000000000000000000000000000000002f7260c55c500b54df4391a55eb4adefa7d19bcbec82a107fc0d222c898b97360a679a02ab3023ce2ebdcffd125ddf30000000000000000000000000000000002cddfa94c8f6b3f29c2fe018b1f8679d0e98c8c2656f86f327b9cbcba574cc52643ab423b458994a03347460deef6570000000000000000000000000000000014eb4c84f71ef5935e707a11a92ba34780677ac7eb198e160585ad92aa5c1ea21e3a77be940fe344e7e170680e2a8d53", "Name": "matter_g2_multiexp_97", + "Gas": 293920, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c2c4c039047d297049afd0e8f0375bd4294d628d3a22078d93b684b737e8c4b6ad3ee544ecbeaad6b3c75d8d217f3a20000000000000000000000000000000004c77a2c0943c6f997ce2e785461f8ec253c47273ded4e1af43ae882766ef8c168e66d831abc2b3b3a0849bbc210cbd40000000000000000000000000000000004456a6c267a5cc6b7d9a9f573270855186a1b621cfdc465fe71ddb4d614565d9d36b13985b31396587919226843c6230000000000000000000000000000000009487cdd8a0cf7f40e9087fd3121cb480730f4302339d25fa12128033239662ed65579a59b837bf1bc5fa87db15b15335b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e990000000000000000000000000000000000ba43205e8392168824f77bac344d60c1a9a0b14ab55538c3bfea4a64984cf381a2f61c64f1ba1bcfd8a7973e43f6e80000000000000000000000000000000000e95e5ac415c3e3e7c9feb6e7a2af3e8189afca06ae1fe54bbeb31783810860921ab3c76a475fb227b3c8299e3f1caf00000000000000000000000000000000001e3cb2106a23e77a126013087751c4d2a419a51beedc3a33faa6c933bedb3d34ee9c6450c583642426edb352e04da98000000000000000000000000000000000ab5af4c98aca1fc3fa55355351d12f3bc639662bf8b5b772152988d676b00ef39f767237a2fa3be936e83d1dd77da86a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000012f3d927316ba638bd6294f7dd2f3f166d20285ee1662ae4dc145835704a17127078343d26042a5c397bfef31754186200000000000000000000000000000000162893d6252361c340057bcac31986458b8b55a8a4283f5a06ce1730098f9838dad1bca264374e7261bb9d08c177c1820000000000000000000000000000000017264aead0ec41a079827296f3d32c12adfca7cb6c674070d54087438d57b6ccca4822b2337263e60075d469b4ce0ccc000000000000000000000000000000000480cae035bd3bf1b4a4a766bcd5f188833e9587e1aff0e1f10e36ebbf2f3ae76bc0946e7c336efc3ee00bf42e7efbb9677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb607191000000000000000000000000000000000d13375356b1518e37a13b43b7d192eb74bd69636f91c570c41a741a8763c03caf8d13c7364f57c867a4a3983e88060f000000000000000000000000000000000f6f78dffb404faab88ac7373e0c765209c0af80514d438e18393bfcfeb60d9a5e13158d399f43162033571ee4a75dbc0000000000000000000000000000000010c379860638ccf3b6cb8479aa38881b0004197e3e367a1d5ef7c7fcf075689d283b87022e2825b5c789ac6a448467320000000000000000000000000000000002dc392872cf2fcd8e196f10c1ded175300070e4e38aa58c89c81e1aa5faa08d770a5ad90a8295a890551f9329a13cee53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac0000000000000000000000000000000010383a21acda7c8f3f3be980bff2d57fa0a5b2dc424164dd2ce53c0b20ca399d6227913b7b550462180b01c231e4813200000000000000000000000000000000078aec90354721f0a31e1377b3652bcb1f388ab36f1866c955f3ea8dfe6ac2c25bc4cea14f54aee71595c2c1bd2dc4910000000000000000000000000000000007dfeec77213d952c183452b98ad936e8854608d950c0c1451262cdc7d6de5aed0db07a8d74b3e8f674967cb4839c4d00000000000000000000000000000000015c09e4ed2ea76d10d196f7a733ccc272b94dc436d6bb5fafad2fae4a96372c2c6f1325d1554746814ae292d8e6b1e3634c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d500000000000000000000000000000000138656fa091cc6613b1fcff04a3efb4f9c393985b2c78fa838eecbbbb8b6dafd88d9c72441f9bc735649480b5187acac000000000000000000000000000000000a35cec4819ca3321917cea5aa589db8cf61882fd1135031dc41a8207a8e71d326312799291b160a646148c382ed086b0000000000000000000000000000000005b6e4c02c9c54630c96271073513cac3a42d47a7272f62a21c7ad4c85c19b60b70d04719626cf4273f6c5691719931700000000000000000000000000000000166a20da734a47d7e28cce8f0c2d679fa6c738a7a1ca9089dc67ba2b1c92a83b024b8991f131e7e8802a617153de4554bebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000a4432a544deda931b1f62759320ada2963062e722bc1b748c9bd0d026ffae10f228be36ea0ab076358924f4c06b6feb0000000000000000000000000000000000e955b1b1b28d2044b6be371c58bc85097c77145b239e913bb0729757518c465d9e69338066f7496aa6a2038ea604f900000000000000000000000000000000017ca2a7d52c3a82ab8abf9fc1bc187389b6e4904e161541008e5b3ba0981870e01060d1272a6d59bfcfb294c942403f000000000000000000000000000000001870649a50e0978185551f213eefd9305d33e92b3f8c39752b6ebe18ae86ad97f92acef05971dceee3b3729becea18168b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c95960000000000000000000000000000000013713effa20d5039ced751ebafe1516f062f11ee05ffad37281cfee9d7a49ab14c065709832f6674bfbf2c9f379bc9c9000000000000000000000000000000000295f7ef148430209b48c292b024474f05036edfdee082c56aea05a62f1fba3ee7a540955423f78614c8385da8ef60040000000000000000000000000000000007408c97321b6d7c27e5e442a9e35b054e743c34d845874aeb1ccf4e903ca7803ed7fb1288327865f9e0ff0a388e92b400000000000000000000000000000000081808d03722a2d48846a693059c2662dee614f181dc406825544d30a6adc0f9d84a712eff80bddd4a27a036e4bf7359d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca000000000000000000000000000000000880b646a674723c15b240ff56d2031e5db724251b1402a68df8b26261ffc9fb60a81abf165c6832137dc7a7293142d200000000000000000000000000000000172354b62bfb8d388b5a984411414738302725a508e8abeacdcb46454371d5e9cf762028fb65921d5c3dd8c67d42a981000000000000000000000000000000000a1af459bc3122dcef78359e468f4094d609ae3da09ca5aa6efb71a7494dafa2373a3906bac1f324d98b3eaa982a27d500000000000000000000000000000000092ac3b47253c7f090df076914cdc08a715faf153e8e365392b4859fca1db14d3f7fb998c97de9ad99b7d0b357252f086330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b80000000000000000000000000000000003b23eff722c078a781771d8b75d519e7a062ca3e4252ecca877845920158fb20d79a9ce449d9087426b113da0091826000000000000000000000000000000000c9026e8d3fee6282492393db504a2c41db19d8fbb83260624b05ba4107d6cb2c90d645a3c16862b27cc3fcce9bf89840000000000000000000000000000000018b8648d0a42285d474f809519696df9e1ad5c35d8e848ad74fbee37425aee8844a8be8cb4d3331670ee294ddb9a290200000000000000000000000000000000068cad37ee8578f4b502ac2ef4371a10e5432e57fe20d0cb074dc427831872113d3514a0b199d813b796b8357fa2a3dbc2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe3740000000000000000000000000000000013200f0aea4c60937be47213b6149b0ae76767f3559e0519f774af4a5d9431e2dd7ea74b42cc3ceb28ccf0d2f01116f30000000000000000000000000000000001c5bff08fd16ecb68f21289a3e7b9a2ec5da1357d604710a18e78ab780f8ef0343d5d9ee7f7988a009329b17e498beb00000000000000000000000000000000125453772eb9d1335ce4dbcc8f2ab8426fe89a0e49fec51d4e96718a38570aa82dbef452368141be2df260fb131c50b2000000000000000000000000000000000432cdd445519775b9914a986a0941cc829b4a15cd361df9ae7129547b24f7a6a15cd8fe9393fa1551db2d761a208b8ec9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000396cb6d7b44f92b716ed02985d351b4e8cd1bbb95f239e4f29d7379428470be395e2faeb8e3a910007aaa490d3c336d0000000000000000000000000000000000ad0c0623fdf50c2b504777554dbab3cde1b9705e976561873d7c22b81f49c7654a7c76e558fad1518ed73a0d3c3570000000000000000000000000000000001241d5bed68e02a2ddeb3ccbe109a161abe81edd7affb72182c5163851211c4763e6aecf766053b61ce575de893985f800000000000000000000000000000000183696d2a48feef6088f4e9f75a5055e8c54b3813658b593958490ddd4245ac495a8ff966861b20f26047f07fa8609a0c2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a000000000000000000000000000000000fe0ce41aa9e7cb2bcb4e01721b7b1d99fca4e9b7c4df09bec00bd346fc57c25118ba70d5333b7f3eef2659c64520a470000000000000000000000000000000005c932e09c62b7ddaf3f5c420c60740befa7cdff5bb812e0f089c45098d71b57004b7a207f0cdd34daaa3282cf6e9f7e000000000000000000000000000000001874200ead9776c1ecd6a54a57e5d0f9577910a4b3afb9b051622f658fe3ef6cc5070af60e7ef910562720e9716158d6000000000000000000000000000000000c2c657e58e400a67e59deee8c28234ff4688e781a2f6f2f0d0b186a5e4012695a522dfa0770cfd543f55939a05e20b09fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000f0b561e5860321249b9ff434c604d26c3275824fc4ab9c1ce5c5858605ddaafae83ae27e523bf6006932f6c7f33d0a7000000000000000000000000000000000b47aab85bbd909599aa85c5eda363b67790ac6729fd8b1f4f53f66dd796cf2fa3496407b1bfaea4dc8eae53519054e70000000000000000000000000000000000cab1ebd23bc05c53bc9e8481c469eac3ee1b140af545bebed10a8fe50698d2ed883219881929207c0addf2f687198d0000000000000000000000000000000007742de55b799950e6f786f4eef45d0fb67e0475272ad68a183135b70047abab6c2ed51ede16c39be7b986df334e9e75d4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000923861332988bc843a65ec5dd4637f9dca8a15e71b82c780fe60d768213d118d8948ab554e30bb9253e900a9b7d87f200000000000000000000000000000000132b1faef49e7966a05783ba526e71134bfb577b13116548352da37e91e617d7c72ed2645e672ebbc517e079247dfb0e00000000000000000000000000000000000a46a8893a194ebfe077afd05fb25d4680f1e4991a3ec29475fa5651d086d20b38136155a65a4c70af31de5a78af59000000000000000000000000000000001344eb957594028b4228cbdb8efb03cc7cf49ec43b2ca5481eae1df6f2df3d5be9a7c4e4e78f8c39be546e29a83c92f49644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000012d7111303563a6358e5ce9155d7a153b5781062c2f6b919efc67ddfb4c61ef03be8828ca6339397b84763a5f8a7e8330000000000000000000000000000000010a2a0ea9973728d3fb1b5906ee84b2635c687c11398ebf605cad30216df3b7b4e3ee1653d4b323a690e6ba614ebec30000000000000000000000000000000000b93d5de37b892d4de9407a820c73ecfd6cd9fa565db82e7e8c14c8406823f705ff0adf6bd6add5ddc5f72c91e52e840000000000000000000000000000000000dcb320ceba5436df8f099c5a77f34376c96d830f5e8ab80667d156d89f6bf8998c148ef9a53847ed395871ab86f6d280df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d", "Expected": "000000000000000000000000000000000ff3e299e6b9fc488d6db991cf9d226d330846e87c4a5cd3e5d4ac955bc2c3c2d1ee5ff230098b48f594d256495f489800000000000000000000000000000000097fdb8fc95d64c7070d8c71e0fd2a933d1e1ad3fefa230f079bc5743828317cd1b0c6d4253ba9c3c6ec6b12d53afa700000000000000000000000000000000002448bbb179d82eaa453cd5452752b9a083b52694cc65c5d9b47f72eff118d7aa06b0fba18eafe93d2937d7399c001af0000000000000000000000000000000009dec4c0aff2a46b07855c87e08832811633709ddfbbc30dbb7e78b3de43bd95e383baa6ff4c58188f0c7643e0ea5a40", "Name": "matter_g2_multiexp_98", + "Gas": 293920, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008afabec8a9985cbbc6246825785654c1d2eb7da5a01f76c4af4d0096b9baed3c33dbe492d14a6f9e762f06eb3d198f800000000000000000000000000000000027c592315dee4bcc892acc6f41a6eff5219c308253f7cd715d0e4a32c03c6d0d0e8568e146e9e799ac3025486c77fc30000000000000000000000000000000015b4ee27a3aa518a1ec1b447bb8f9128301c85b7176296d68dad3339b1dee78715b2f031a7fb6ba376145c97ceafeef60000000000000000000000000000000004b7e30ec7cc024ced863ce511cef3cabe954a4e5843dd636d776645a44225a36ed7e153ab5bf5d18f23c6444751875c8a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a83300000000000000000000000000000000119949d36d8d8e2bc1c26ded5f5fb01225a980a28b934ed3862480dc9297a3758e0f08ccaab3a09b5e5c0e4215e3246c0000000000000000000000000000000004a82dc22316ee6af39d937b662d1f1f2dc855c2ca8f33ec3274d833e87d594633fc7fab247911e0f46564397910d6ce00000000000000000000000000000000196900a09d8504ed960d41f4a8a2cde2e5dac61b008d3f6eb47e86d7b2ce6fcdc0f85157e3ab1571094d9fdaa75d0d500000000000000000000000000000000010c52ef9407eb4ec57844aebbcc3ea5000b1940d035dcc2a873327affaaabdd79e3560cbd29c63ce04f6279056d6eed1bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000000e43655ae05dc6cfa93113dc26cea895d1c5bc73f20454c7b441dbc5ac80035b290514b13b31b41931ea5336d8d9a6a7000000000000000000000000000000001199a873958c63147e6b82625dfea15ce90dd41ceb4e315f67221eb874ef32c6a2953412e7e981659c72239a7a72bfe6000000000000000000000000000000001845af5936b4d7487ffe59137ba2f86daea3770cf37fd560969ee48243389941a1072205e049ddaa06c0ac56b7edc8930000000000000000000000000000000003cc831177f24614f93a118b896434105f05a277051a852fb9973a775fc54f779c2a1f3d64c457e5231dc22d6aef606b58b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe400000000000000000000000000000000005e63584bc85ba58615985f6a466afe05268e545e0062cd7214e0b6fc8b87537c745b754cd9a1144948bc88b3c43acd000000000000000000000000000000000635b6a49090ccede3ed2ef203f0ed164783e3df4d9a7d93319515cb9230bd841b61a097f39e30175793b3e934d8e426000000000000000000000000000000001861e65f47a9da1584c45bc79a66045d86bc1709c2d1cf6cd2930a9fcc8c4efaa6536b5015be8d54789e8f574f93f9f70000000000000000000000000000000009290ce63d55eb436794acf11be9d896f03e7608a1bc8528f61ec9473f054bc9fbbda1072440e58e2f6ba080a01180173b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe0000000000000000000000000000000018feed9500bff884d2bb58554da2180c68267b6d3a45c2c7cee4c3f8524252d3faaa5eff971bf40123587e669fe66bbb000000000000000000000000000000001441bd3b58b4a4a87c2459f873c0692f5977b775af984bab46dd76cb9f775d2faebcb77b2854c9f1faa33f6c5de61c6a00000000000000000000000000000000123a890c3362c77e5b5cf9846d9c9e43fb3242d5a831e640ad080993fa0547854c8d11cc22f7f7b426528bf1154d2300000000000000000000000000000000000ff4a59ea98d13cfd353ae61e18d3c7018688f755561e6a1da5f09acc4277e8d49645087115acc64f992ea778a11f39bb049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc0000000000000000000000000000000006b019d005141e82393a2ca04469d1f6fd7b9456001ffef4c34eff6b2e91df58e99fd07944f52b108bd41ab6c4d6bbf200000000000000000000000000000000109ae87042029856befff0c916db5437e1e058a96f2970d8816b3becc93a1a50d6d336d5451303715f3e272147a36caa0000000000000000000000000000000000fc381b8dc9dc02d34db13e34732a10d0dfcf676c224a05a3bffd888b0af7c415b38af0b6afe6b464ffca42947c6ee5000000000000000000000000000000000087040d09c39ccd06c9ecc360fa02147a32e8036ad6e4b6bdf5b3883722a4e5a887dd022d53706d2585fe558696be6656197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d970330000000000000000000000000000000000e35c27b29df0fa9298bb9ab6a38b3450782223e2115d79152f9baa924d762d583b3ebe88e42f33028814ec78e5b319d00000000000000000000000000000000190c65667627a16f0af0ac7f23af0803bca810f3986b906b7b4f126d98473d52badf45e90e2e45bb390242fa8c40135100000000000000000000000000000000103f0283a5673c16bcc0f74f259c2eb077061947da04e467dfebf62aa005491e32b85cb73418b624a30dbaa01672921e000000000000000000000000000000000465466955c908607191faf15f0768dce42488c488eb4a065977f21ac7484766bc0abf23961ea2ba46dcc04956abf6c7721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000082346e352e845a54cd4267f93b85b2c8623d4650e00c1c56082b73ee31f63588d2c117d3cdecc0378fbbf8956b082040000000000000000000000000000000001a7f43c2bb19cb32345c43c950536f8e85815b86364f278f6ec8169eca80917c2b8fc08d59b20cf55f25dc468e7bd7f00000000000000000000000000000000085a5cb020df10f9b4c7afc01b1d11700579dec1e85e766507def2e6cf5b714174f7be9cce3b18533a5ebfeec2b4e481000000000000000000000000000000001836d7506d1cc984fb777b8ee935d6f5b110644f59e96ff44d8329336d59a3e1d2b53a05d35e97f634baa4fdc11a6cd8461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e000000000000000000000000000000000c1c59828ec6257a02679cff0bee0d665d449d2a158bc6d877e84cc0fe2161c297dde09b778d5e1249c515833e483004000000000000000000000000000000000f5e82589bfb7781e4110f1486752b00cbdf96cdf4191d75053c6d6d646e1c989add011361031a11559e156d64139fbf0000000000000000000000000000000015053afa7fb2b4e4b70f3c8a570fef8288fdc22dd951b6ba8a40b6087b9ab04ede21f0ddfa84d6d18914041bcf244c110000000000000000000000000000000003f399800cba51ab35624d866831ab6506392cb3acf549787153ffaf08cc451acea46c7a612821dd96c45f8b75133d88569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d000000000000000000000000000000000e26242c8f73116079369ef4265f624abd4377e4e3485c28197663de9de9f5618c3b6ee602ff6bebd1c242aef7295b2200000000000000000000000000000000066ceb3ea6067220bd28fa1164237782859d27c1d3087a42b4d09bcc343611e4ed2be014a27f5b394c67643dc00f57cf00000000000000000000000000000000157f9d30de52110ea7a2a35ddfe67d9fad7223c5e3307e797dd0df3621520a421958a2835205e3c4777923f47d47e5310000000000000000000000000000000016ebb41beb85b9489a6d5482f8a3330a5c5c5e5718e8efb8b67362f9d8e9c313e9e563275ba38c207c5bf3d89c406ea62061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000005d1c9109b5b7409f94ae3f7dd9e8ae4908a9b378fea4ea284cbd33d1e59b605577b63892aaa8ec14d415f34e22fec520000000000000000000000000000000005afed05e62599f20f7eca019f41d770c630cf6359cb5601464be821691fba5205c16e7b580e6881047214f938e5104b00000000000000000000000000000000105637a2aa4725d8e080dec3b731a111ea4c94b79f898dfd51f645501ef0c8d68ea8e80fde28ff96e927e44306ebbb1d00000000000000000000000000000000080cfeea754474ceb37973234d5dc3269f8ca99bd862d4d2d1a602321fc709945a3209e5ff2cc962cfa6d03017c9a1354129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000137762ea5c80033aaf17570451b15a062feedde810f11ebdbe9a79a3275dc12613e0505835c122bd5f9afea7dba84203000000000000000000000000000000000d89c04e45e60769a63fcd73df2a138c457bb549195f2c4eebb3be1ea46149f286756795be8328b5b886f497d8167b34000000000000000000000000000000000be43d515083c8c10f467618685a43d4d5f6457204bacd278445943a9f44f7189b561a0e1bc59d2757fcfab2e3f93a4a0000000000000000000000000000000011a52583227c6dcdc1784d3633fd584612a9f3bbc1922477396dcd5af84413e5e9382a34a71b3a72491ea09fab2fc6bf366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e8346729000000000000000000000000000000000073acefe33525dd2d5204cce72371ed82c7e4b58d1b4e7f4b4994f9c58b02d9d6206fefb3552446b6b355e860ace43c0000000000000000000000000000000007344eaeaae71e17930e769e02bcb4f44ddf3d040ffa0b081f25901cc125a37a58a6a5d13e7b0ba493802ccdaa054e29000000000000000000000000000000000a65fec6ad29ec3eee9ddc7ded2297f49d03ff18a255f1e6d29d2a67c20713f319d79d513af0c58ae3cddfd1f6240ff50000000000000000000000000000000019d5f00d9e2b271f4e9ac779a096386f08ae124f77fb8183405d48ea7f16e685805442dc67a392aefc643ea95b4f1fcfd997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f50000000000000000000000000000000018230bf1a873aa04855af1426da30f1b3ef4b64eec613b9f660222e3827b325c318baea031b463c7e9f775165d22ec8f00000000000000000000000000000000017faafa1294fac53e1de8cae9601acc62d76a5f01a39ce49d65f3f5d2cd5cca33eb90bb4116b3ea36f912ae2b81b6cf000000000000000000000000000000000fc3ef5ea59849a87fcd45500989f1744cb5570ee88e34a952cec32cea2eb5900b64d8d0d04ef5c51e8fdcccd46412490000000000000000000000000000000001c53aa8aaae8422fa4fddc86cacdefa89c37592c8e67e472a23627514623a90901a619af79e93561a0dc65215837274e881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a3371000000000000000000000000000000000e3db6885c2db9244548e11b8c49b73f85e4104b413f54308497262fdff1957495859830114528a22c45d39a554ba82700000000000000000000000000000000181b1bfe2d9a1c563e73356d73f4ed3e7061a79c610bc97c911ab1a0213d123c9f83ed6706e862087a796ce14c5cf53d0000000000000000000000000000000013f5fdceddce771588869b945bd6025e5ce485fe78a362356720b474b83998f27e535cfd8d33ee51cfc68e5d514f915c0000000000000000000000000000000007e8fd7ba457a3cefd50c641847425cf2262deb1d6945a0bd740eadf38dcaa616edc48c3912508d663349f089b8b56fadcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e650000000000000000000000000000000004b20b0408da7b704694b47607928a655077015f2174fe01bac9a0b3a61dae087b0b593f58d2947d8d84f75bbfb327c900000000000000000000000000000000106d623b2007c5d7128e03e540325ba763e992a651e2e5c78936f82ee2ff72d89a1a914345486cd0a04440c75beb190b000000000000000000000000000000001847348e5ef429cfdf1ba4d265d8c5ebcbec3d5dd4611ba36e2754fbd3d327273bf2eb7b7ba4b3888d059dc87f034739000000000000000000000000000000000bcb0a9dfe5189bc965e9721407b4cb3ed4171510aa4d4e5d5f0823a1c2827643e1278f9c0ee960c54ef8f6c208eee7b334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638", "Expected": "000000000000000000000000000000001205b70b29ee04212589f8a70a71e004f517d3354e714c1b4fe42cf93faf1a8ed40dbc1b5089ddb53bb052c9cb74c0e8000000000000000000000000000000000f619082734dd9de653b61cf2fb927199f228637db70797bd2a21fdd48b6ecd4c4f712097037534024880a436fdd63680000000000000000000000000000000000592eca560be6ae256abe1796f7ec394a8085c127437f6590c8d41501a482c61456392cb320b9e801044dcba7802df9000000000000000000000000000000000a6d20b8009708ca01a274aed6dece732c5eed5aae5e4c2f3793b5fa1f8cb8c95037ce387bda2e7476e9c493507c7fbc", "Name": "matter_g2_multiexp_99", + "Gas": 293920, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsMapG1.json b/core/vm/testdata/precompiles/blsMapG1.json index 568f18a7fb..07903a3125 100644 --- a/core/vm/testdata/precompiles/blsMapG1.json +++ b/core/vm/testdata/precompiles/blsMapG1.json @@ -3,600 +3,700 @@ "Input": "0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350", "Expected": "000000000000000000000000000000000d7721bcdb7ce1047557776eb2659a444166dc6dd55c7ca6e240e21ae9aa18f529f04ac31d861b54faf3307692545db700000000000000000000000000000000108286acbdf4384f67659a8abe89e712a504cb3ce1cba07a716869025d60d499a00d1da8cdc92958918c222ea93d87f0", "Name": "matter_fp_to_g1_0", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641", "Expected": "00000000000000000000000000000000191ba6e4c4dafa22c03d41b050fe8782629337641be21e0397dc2553eb8588318a21d30647182782dee7f62a22fd020c000000000000000000000000000000000a721510a67277eabed3f153bd91df0074e1cbd37ef65b85226b1ce4fb5346d943cf21c388f0c5edbc753888254c760a", "Name": "matter_fp_to_g1_1", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed", "Expected": "000000000000000000000000000000001658c31c0db44b5f029dba56786776358f184341458577b94d3a53c877af84ffbb1a13cc47d228a76abb4b67912991850000000000000000000000000000000018cf1f27eab0a1a66f28a227bd624b7d1286af8f85562c3f03950879dd3b8b4b72e74b034223c6fd93de7cd1ade367cb", "Name": "matter_fp_to_g1_2", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2", "Expected": "000000000000000000000000000000001672a8831d3e8bf9441972969e56b338594c5c0ede7bdba5b4113ac31ccb848dc2a2c4e23c0b9ec88bfe7165f472b427000000000000000000000000000000000a86e65037cccb5281389512673068d6f91606923629905e895f630059cf87fb37e716494db288958316c6a50de65ca1", "Name": "matter_fp_to_g1_3", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de2", "Expected": "0000000000000000000000000000000019adfbc918cb74abc6fa0664dfe60697b233f0663665d2cc133478db4d6c9a41309ff09f9af9240037a7332bc42ffe3a000000000000000000000000000000000d31ffd63837cdf1cf2a7b3fe23a9d86c08f3a7c44ac4fa52d21b8c235f0d45f85c036d80bab332034635845deb31467", "Name": "matter_fp_to_g1_4", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799", "Expected": "00000000000000000000000000000000192a005eb944f391251402ac3d31c30f0b2d77987ed9928d244f492f96c1a0a06a7cd0be4bb3dfe3c484ab8ac5279a09000000000000000000000000000000000b99b9e7f0b51a2e0d12272fd0d9ae65294dfd34d45f30fe446a25b225316ef467b02acc3b6a578e054e612434096d7c", "Name": "matter_fp_to_g1_5", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c", "Expected": "0000000000000000000000000000000019dbf865a67157efe65fa7171279049864bf6c280d3c3462e93425bbf25f9cbad6c27885d7927b5cdca642df48ceccd2000000000000000000000000000000001606be1ef7aaf56349e5179b01b89e172e463bb3446792d5210452905fcde42522f9719b9e7ddeb8cc3f227eacd55947", "Name": "matter_fp_to_g1_6", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60", "Expected": "0000000000000000000000000000000017d476fdf0be6b09206dc83cce64c603a6b911f051e9191a2473a1bc6b1dd2c6e9bc4d262edc936f62911460f0b648a70000000000000000000000000000000016f824bb325ff7f485a8e9d116f4a56ea71ecd2c11b2a4d119c208cf323bc62bf1e9fc609230c571e7830a956e140e47", "Name": "matter_fp_to_g1_7", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f157", "Expected": "00000000000000000000000000000000145726f8479d7390e7a21cd31dfee0e6203115e72d04c5a735feb2cb688ff74944bff2b1af1b6368b4d095143662a1300000000000000000000000000000000002fd68d51753faa242bee10148c0e473f4110fc7b67848dfbab7d7105090648534854ba75890e099cb738d1dce604ea4", "Name": "matter_fp_to_g1_8", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac", "Expected": "000000000000000000000000000000000671b0f33b0f1ea3386e6876452989416c7171e283c4b0c375e840ea05e7fda22aa03899b50e59e9ca5a87039b2e732800000000000000000000000000000000031bf8caad5ce6a0d94f14693da0d551dd4bfd2c2163c8e8d5a448956153f63ce2ab72f03b97b560d67933887e83be1b", "Name": "matter_fp_to_g1_9", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c8", "Expected": "0000000000000000000000000000000007d67999ac2fe6ab93591646905f23aead0d37ca43573ab02dc16c2b199086f788a8a1de6b10aef4f4d772b2e12e72ad0000000000000000000000000000000003700b150ebf60cacbb2b7bcf969b70edb57b34b5c772cdf68d42dc9f1513631799b9b9041c5e94595ea848e195aa730", "Name": "matter_fp_to_g1_10", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3", "Expected": "0000000000000000000000000000000006cf4af50766ec08696c9bc0d9617c1f0fcb0ea1bcb576179cd4537d9d31b373bf8e3c5f5fde2c21e44917cf1f51ff0a00000000000000000000000000000000050a9f7d8490ba2b6e49762cf2bfce557e39edb51ef03128b64267fd3c6b996e95d73b26cf1965d427e3445b1ee4d133", "Name": "matter_fp_to_g1_11", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e", "Expected": "0000000000000000000000000000000009fbbc6ba7ec2315dc18aadda7b2e53180b904c5f1cbdca1b2f42ed9c6675da7beb4007ab6639520c4736bbf2ee3f04500000000000000000000000000000000113f0bc737b2f3a141121ef236cbaff2f34502aa13e121b857baf327f7be66be97867fc6f752555835fdd01610e30c77", "Name": "matter_fp_to_g1_12", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be", "Expected": "00000000000000000000000000000000047dd479fe99840150e73e4a8fa6be74a9b7d743e21cf33e9d7a9fd8700feeccd5111fb037eb3b15b79d5737ec4c7f0c00000000000000000000000000000000000ba7f57ce062eb9c67d84eee64d64d250a18454bd63dc5a136f5341214079eb9269eea7c4e0d836dd8be63a8a45c04", "Name": "matter_fp_to_g1_13", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a", "Expected": "000000000000000000000000000000000e8cf94e68b03d1f6a3d4eac7898f143324d08f7544aa9f952947e9008d2c14e46236667173266d82f5e41887c6f614200000000000000000000000000000000089a1ada37f30b1f6e3a6613705992e9708d0437611f1de72a9f696ea5efea6793f285bd5badbdc20af64df8ba95c79e", "Name": "matter_fp_to_g1_14", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3", "Expected": "0000000000000000000000000000000011e4919deb9eefd13dd0ba5184003ce34ff6c2bd8920dc49b936917a7b6aaf1c7541780b5d0e380e6c808f093a877eaa000000000000000000000000000000000152dbb758aa5f60b8d0703eb30680857abee717114b8cc5f6466e70856f19c76a88ec6c536e7a679c177986bf636e6a", "Name": "matter_fp_to_g1_15", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e", "Expected": "000000000000000000000000000000000750f69c43c56df2c8524b4ead9f6cb3ec16d3a6ec913254e585b0d8518e53c18e0e93dd4594adb926c51820de6001c10000000000000000000000000000000011f5c985ed12f72b6ec7e222dc8d93da520ac65476c716e231e7142cd3aca49b25dbd716a8f587006e4a2af31c37956e", "Name": "matter_fp_to_g1_16", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad", "Expected": "0000000000000000000000000000000012652effba341826ee7bc3108404f5fcac84776c6f5fef5d440454b59f04afc2cc87f243265248445c7c2bfc14493ece000000000000000000000000000000000c0fd215b7c012da4532c882d7d7f83ebf133d58acaf8b5123c1211aae5929c6726410631c7f9347456448df643c9ed8", "Name": "matter_fp_to_g1_17", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6", "Expected": "000000000000000000000000000000000c444b07e9ee5dc366c63ba30f1b17087bc4c548963caafacf223f4bf5b5bad1f9b51433bd1942978f3f5e5696d5056f000000000000000000000000000000000453941626954845d89821df34efc6f81660684b08f03fc42da54119d10f1f95357ba75a0962961f1487df45b0c534ac", "Name": "matter_fp_to_g1_18", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20", "Expected": "0000000000000000000000000000000013d5fcd7e4a0b1d7d8c7b242b46968519521ff8bc4b990a56ece26053d4bf884afd24a00670911f943522e06fe4f87d1000000000000000000000000000000000aab46534de37b5c6d206959a1023ad4f20ed5966bc3fd1750c1758ed806f077444ac70e9943b4e8debaecf208817a5d", "Name": "matter_fp_to_g1_19", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab1", "Expected": "000000000000000000000000000000001440f44e3964de59be03a6c69affbb3b44ffcf4ec4976361ac49c31a23f9f154f91750533ff2425d5e8fcde0974a91d50000000000000000000000000000000002031eb89620736dea022880e5188145f080537b1aec183db70bf307029be21a167fb6456bd1a47a75626280f78442a2", "Name": "matter_fp_to_g1_20", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175", "Expected": "000000000000000000000000000000000a2d7297376216582c3938c2aab0a26494da7d9df45e1af7b4f826f064467a939ad99134be4c9b804b5bf273e082c4c2000000000000000000000000000000000b0a4da7cc585be1be6c091006fe831edb6f6eadbe3ef611041efa3d14f442c9768feb2958efa161e0adf5c321d7d522", "Name": "matter_fp_to_g1_21", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d23502", "Expected": "000000000000000000000000000000001314d7faac7b4d5003baa10cc432108d6bb7f80bb13991f6ac45fd7a772f31cd43345ea100b05f2ad73e3bf583e7e7b2000000000000000000000000000000000eefa97eaf2143a991343a8823d8b362f77d8370421bd13a9a6cc4988544feb0cafd3a797a28d27f4f8d361cb7f49ed4", "Name": "matter_fp_to_g1_22", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3", "Expected": "0000000000000000000000000000000003ee4f3d29cd9f29a2e559a86d8204a1d65598e7788d585b145022de2c19022b122c1f10423d3bec769545d656726f5e000000000000000000000000000000001803f26af468740849a2737a42e53098b48c0709415247023aedb111c96043e3b13de300213e5196cc3b678f8be0696f", "Name": "matter_fp_to_g1_23", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d9900", "Expected": "000000000000000000000000000000001800b9766f3e621ad7a8d1870ce16c8cd054c87d7fb100120a38c3368cf1879859645874b23297957fef6cd8f9112bf800000000000000000000000000000000091a8b69a1f4eb883a25af2a3a0d1e384ef7a9ba4e8ff8811ad356781c79f631ea20fcd0590e94b9c1841e6add2b848b", "Name": "matter_fp_to_g1_24", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844", "Expected": "0000000000000000000000000000000005fe170feabac3805c3eaace41fdaab2c9ae7fe609ba609f4ebce2d24c0d704d847efd510acd8abe5aeff2eb24e781b80000000000000000000000000000000003262879ff5c9831ebdd0de9df478923fee72a8829378f40cfec310a41110ad22faa759276e3b9e015c86c94c3594e0a", "Name": "matter_fp_to_g1_25", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08", "Expected": "000000000000000000000000000000000e4110b2efc984c4d7affcbcf5cbbf919c55f948ac7412dc120d30774924d6020a2292f27b8e716c2b5045a561f2b14300000000000000000000000000000000194649f6906daa0394fbc1d45355e17d62f6c22a9e772bd7fa5149e29ab2ac6060d83dc5d70fad75bf3f2c7917b641e1", "Name": "matter_fp_to_g1_26", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2", "Expected": "0000000000000000000000000000000013af2a5f26d1f51da0d80fe7c62369ebbec235faf4565e62ba475e6f58418183efc8b9906196ffda72539506243e0482000000000000000000000000000000000774f3096c99bb826792cfd9243d8cbb1bab54fccc3a6347daea74ff1c8aebafdd971b7bfbea5b9a0bce243372caad6a", "Name": "matter_fp_to_g1_27", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb8750", "Expected": "00000000000000000000000000000000107c66e91d518789be416606058cfa8e9df478fa097241fc109d065005ae927d83563b72410e5b207d1556c2ee4dd67b00000000000000000000000000000000148c208e55e834c4e4fe20c02f517c21030f60c74b1a3bcf70bb2311cfb9b7548837b9187910bb7e8d1faa40ca8d6d92", "Name": "matter_fp_to_g1_28", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75", "Expected": "000000000000000000000000000000000fa72de55fc2229c0176120fac3e0a64c4498bcc7b67ca40b92d47a76a9db87ba498b72f06345c61d59a3d37c51153a300000000000000000000000000000000001f0e176d0987b8ceb7ca0e5ebb491bab0be17282cace8e03d52c986483026180082f86196fe512ac6bac58ec4cd024", "Name": "matter_fp_to_g1_29", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be1222", "Expected": "000000000000000000000000000000001655eedb905670d10d2f979962e864d68e9491aea41d073a6119e5bc0ae74216383501a48343d7099b93601f8b67c00c000000000000000000000000000000000842846147959f0f81efc6e8f515a9c59456637740bc15b2d335e0de45890cdd814ca7057c5d3e49e48e5a250c5dad25", "Name": "matter_fp_to_g1_30", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760", "Expected": "000000000000000000000000000000001098de70e8748daba7bbad52ce344619d3b5374821c1f932a18666ea0a591b24ece05004546cd519ba4d78c9747c57cb0000000000000000000000000000000005f537b6a394458ad51c2e677b2d52974a714bcf6a7474e748ad7f1b28738b6b874b6f49bdf19479bce4ff6c6a47de1a", "Name": "matter_fp_to_g1_31", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2", "Expected": "000000000000000000000000000000000a27f7fde0c79210f4b6cf59c97ac773c9766fdab289225c97f6cf42179385cf18f47f14b7e481df7c19418c79dfaaba000000000000000000000000000000000874f21294205152df3a4fab2ced482d325274886d8105b61668074dc8fc90240a715c62b2a2864901ca7a30f12e76a3", "Name": "matter_fp_to_g1_32", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091", "Expected": "000000000000000000000000000000000a6fd7355965c9514dc7237efd262fb9dfd8025ca2c56165e22675e615095887760ecfed4a2080cd5a2b8041ff26578e0000000000000000000000000000000019b1e02c9258fe62160d92eba8640ffd79b3bffb8ca4d602ca6c059239047c5563049758911d0e6034a25ec5094b1f33", "Name": "matter_fp_to_g1_33", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18", "Expected": "00000000000000000000000000000000013e4cd06b8ba7b5efb70feaa03550bfa45c7c2c79033c92b819257b2ddce28d501cc836a5ec81bf210bed671bfa66f100000000000000000000000000000000165d806d235d41f21e961795ec3da4f1b0334ba6e71ce384445bfda9e5d89e448d00253ec9f8d49825a230b25ffb2848", "Name": "matter_fp_to_g1_34", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1", "Expected": "00000000000000000000000000000000067c19b7c3dcf8b43d6e83dbda7406f5f88b06cfa0d7d145201164a1f06cb5549545ab28fd1ea8c1d5a662cced00822a00000000000000000000000000000000013aab7ac4ebce4686ad8a05e4eb2f60ebdf03c4f4ca0111bb1cd3dd5fa7558f1cf0dec394d0b616cf557f3811bc2104", "Name": "matter_fp_to_g1_35", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f", "Expected": "000000000000000000000000000000001673e66a7e558d533be5b855df7c3bdc58f1fb0a3b268b84b4fc25a3a8a211c4c9c8d884fc62f00eccbadbc96dadd7230000000000000000000000000000000016265b691fd43045567ab4fc7e7efa63c8430c8130761b128f0ba7bf381a7cb81bf05aea2526b50ff6e48a87c8ee9cf6", "Name": "matter_fp_to_g1_36", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192", "Expected": "000000000000000000000000000000000b36ad42aeacfa47d77f045de527d5bd4fa5fcf25ca3caca99e3e7980e283278e013611d1bc7694bb0b1b86d8589730700000000000000000000000000000000136290ed913b8669f522e16103ff42733a57c1026f966facf4a2d385b0bd52668925d748760975ca5a132d00deddf675", "Name": "matter_fp_to_g1_37", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a77", "Expected": "00000000000000000000000000000000076fef7b61f4c687246991d6f735d6f89c953476ffc193bacc1f3cf9573ed47bfbf6dcfbb3da1ec1bb764a9cc9b1c26b0000000000000000000000000000000012b6bb88e8acd6cd0ef1929a79bf4d8b10ec3fd575fe460686921fe94aa3a472cbc7aea543ee6284c368f5ef2c33ebc0", "Name": "matter_fp_to_g1_38", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510", "Expected": "000000000000000000000000000000000bbc25f7788b0031f1487ef154e877c5ae277a80d56b3a24a39c3ee94eb7df81a47bbff233c1baaf700829919e5254690000000000000000000000000000000019fd9d1237b508d06d7b2ff807c15c3ab36e6eab7e5b9f145bb2c0f2ce8ec96ca3a24932076abfb74eca85744eee4044", "Name": "matter_fp_to_g1_39", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b80", "Expected": "0000000000000000000000000000000015eca2e3d36d619601b0f40b01add7a708bbe59d04d5dfbf12d6e473e252505cec0cf7ea1c420000d49221d5e1ba6b91000000000000000000000000000000000cc6045184317aaf2bb8a904755bf48df9e6754e3a864037ebe0218eb3cd1c0a54e50b95f9e6d318799a72fac8d4e262", "Name": "matter_fp_to_g1_40", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5", "Expected": "000000000000000000000000000000000ef0a6307d4a3e92570cad673ca5212780902de416e81d15638ba654951f442e852b53255d7bc4d4e71098924d69f5a600000000000000000000000000000000156abf6f096326c75710300578f0cd946536e16bbf80034c6dbfe454565a501c268135118745989e5274ca2431ca5155", "Name": "matter_fp_to_g1_41", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072", "Expected": "000000000000000000000000000000000dc94ea6018ffc5838cb7cb00df9625c0c09701bbf19edddb735a3659b385bdd09e9a7d6e869720b727ec59ff3956d9b0000000000000000000000000000000000a20ea6360179bb6608bcbe4879df186916ee71b3ff7a1dd0fd137a0e9dfb135bfda2c66d1cf8d358d69934012a1a1e", "Name": "matter_fp_to_g1_42", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03", "Expected": "000000000000000000000000000000000b4e11f70679333c064d06180df6b54dd1df20ea216415ecb9b704bf4b206141fd841770ab77de4ab2400a076cf9dd04000000000000000000000000000000000ad8c02345e141396401221bb36a2ca21096e89aa76fca4121066da74f2f54b3e2c4049483d9855b7f3159ef448c120c", "Name": "matter_fp_to_g1_43", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb", "Expected": "00000000000000000000000000000000136cd8012cebf1639a396f331f73f0da6c114927559cc595f01bad1a18046ae8364858fa262ae04ae3f3b7d13db55a86000000000000000000000000000000000393a915629ccaa9ea06be749f3053dfd07061cfa24bc0aead12622c7d14c085e2994178bfec98b3f8867ac5b4b7a05e", "Name": "matter_fp_to_g1_44", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e", "Expected": "0000000000000000000000000000000019008e485a0a9c2f73a79bfe31782a17952edebca308bbc9f90e2ae15525bd501268a1c38c669de0b4e4fcaf1194591b0000000000000000000000000000000009c35254702eb7e3213fcbab62946ba79b7375cc320ee1733d8bf5729d378d1a98fb27d870e27c13626c35cb00a6bcbc", "Name": "matter_fp_to_g1_45", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e9", "Expected": "000000000000000000000000000000000059a3315f8b6e75c45e32843b4ff2401c41e1f6716a5909894cfdc71a49253d2cb04ec416d204bf0bdda051ace606260000000000000000000000000000000019cee852aa9fe28e1da49dfbfa7901220616f464ba447480c2421fd6d3a5a818c778510a04cb6557d27f7ef9a78f2fb8", "Name": "matter_fp_to_g1_46", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe", "Expected": "0000000000000000000000000000000001d157c963811725ad533539f17acd16ac3aa22917ecb2198d83a3ba396955f2c9654c02fd42e3d4ee6156cd148e9c270000000000000000000000000000000008fd299ddabfe525075f548a31ffc990a3626aba0369bd0accd0e1968204c8e1085c6b287b370808609178ec8ace2d0a", "Name": "matter_fp_to_g1_47", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf48", "Expected": "000000000000000000000000000000000ee6b51c5eb4dd9c27a61bc2f3480d799cc4fb88414630adb3961508c7067bb186682194af406f811296228c068e6415000000000000000000000000000000000b878c207bc4b61e827ee09a7825fb216a63ddbc4ef0522b8a944bcb673ca368996c31e6513504c5deb5325ef4df0459", "Name": "matter_fp_to_g1_48", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909", "Expected": "000000000000000000000000000000000658d61bbb2273e8969269dc16e16be93ef82be0668c3a164097a1c0816bb4aa94e5f70ed8d96bd15d9acb602d70f8ee0000000000000000000000000000000008f696d49a5c6f3dc971699a5837f7b3a20e222d9559d899eade367ce684b60153dfb75a9a8b81d7359a93069e2d7d7d", "Name": "matter_fp_to_g1_49", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d5", "Expected": "000000000000000000000000000000001313f4cc65865c367cb5c1c96cf30c7e993207e9ef4b2fce9db01181b1192520f01a0428668bb9d33eb857d9435939df0000000000000000000000000000000006b5e883fc24585de3b0a0b83cc1742050e578cb57e89b385e245da0dd2832852c3fa5f31ccf55e6744e9cae6c2f705f", "Name": "matter_fp_to_g1_50", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5", "Expected": "000000000000000000000000000000000f3fdb57966f9ffd0e20b9ad3bfb4fcade56468aa598cacfe388cd3b647d5966350586daa4493de23703a1debc82e48900000000000000000000000000000000044ff5ce3b9bed637709f9105bec0d86b4f0ea2dd86c9c3b1324637cd4c0fe5a4a965021c51279fc03592414e7968d23", "Name": "matter_fp_to_g1_51", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0", "Expected": "000000000000000000000000000000001288807e8f49323b39c5d592b97f19cf76f2f642dc4fa704004789d28452ce7a02a45f3f83a8d9875480d380e76df09400000000000000000000000000000000123b15dc7f166cb7c2c106cfd2f7c321a9bea9e3bdd118058c4745b6666a0df2a7c7fea16887a4c85faf860fe48a3787", "Name": "matter_fp_to_g1_52", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a", "Expected": "000000000000000000000000000000000adadcf2f074679ef3523c10674260b0e40106cca8d94b05f83e2b27d8da8c00dea4215a30275ea5e1a8fd0beb45dfb30000000000000000000000000000000003c2d436e545163abbb18ff7c8e6db1e55c733c75f9594c695c66656690e88995f9f266c2620e99075d3b78805e3ad41", "Name": "matter_fp_to_g1_53", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f8", "Expected": "0000000000000000000000000000000016cf855c1ea449c47236065ffe53a4c6afdadc08f1eaa26a8f79ea92a7a119b26dea1dfdab4db9b02b3dcad2c077338600000000000000000000000000000000071924c7d4e6aa5234dc921d288dcad3e49b44d2f455d207f3641f4b5b5c809b84c04945df08e785b3d99eda1807611c", "Name": "matter_fp_to_g1_54", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa", "Expected": "0000000000000000000000000000000016d80d4689e959233f05a3266628e233b747705bf6d6236771d5e697da03a0daa2dfa88aa5a3a5b97bc4517c467e94510000000000000000000000000000000003bc451286fec0e7a01d29ffae4986a2a3371d4aab875547cac05f759f5a52b8cbf84798b5b3d664a8692b212d4e974d", "Name": "matter_fp_to_g1_55", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe", "Expected": "0000000000000000000000000000000017297cdec2f6a54cb11c1fdac799f252c72dad52ead6c29de61d64e56ea0e0a1d3a60284029323e35d38a4a25f82fcd60000000000000000000000000000000009beaeaf3ce2c9bfbfe5e04ceaee87460d760c4c16caa7b37767e16b8e97cf08bdb6d30472b3027f66803dec1ce40eee", "Name": "matter_fp_to_g1_56", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13", "Expected": "0000000000000000000000000000000015a163067e8039be1c365804887dfbb78a7a699f0308c8e26519bf1c86fbe6acffaa26f0e5a2a380d1c704fe84d3bba60000000000000000000000000000000013f94e107625aca9c4346102dd5f09d51e445fd44ea67f171048e8f9965ce3496e759610c078404d41add90a358af482", "Name": "matter_fp_to_g1_57", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce73", "Expected": "000000000000000000000000000000000b0667e2b7c0fa318c5c0e66425f8cbb8217bec845bfe56997cdb9d0d915131b81e82419a4533eb573ffe103077f35c90000000000000000000000000000000018074b6e0cf144fff9da02a4b5785d21762952d4ed23b1430d6165974f49521b73eaf98973f7967ffb35cee92a2b5269", "Name": "matter_fp_to_g1_58", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b", "Expected": "000000000000000000000000000000000b5e953227f4f5e2070482cde7fded231bb0d4649a626d356cab2bfcba6c1588ef38c62cb2c550719091206727715dec00000000000000000000000000000000095f29eab98321d334f22b4db0c30a0604c5c385fd222a71399763f5c815e04226d9d06b460b9e3b44d1ec127d20315d", "Name": "matter_fp_to_g1_59", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc", "Expected": "0000000000000000000000000000000017946ce626cd11556f85d15b85044fdab0456e24b5e331886be860bf55411a03886738aed9b19d52e91a94ea5cc5f040000000000000000000000000000000000cbe613ecf3c8ca8a5f0617c64647a609ce6e8fd40ae42f69a928f4ba78f7038254689bac2dcde7a464a03d5e26e34ce", "Name": "matter_fp_to_g1_60", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82", "Expected": "0000000000000000000000000000000003b425300fc1885f2e932a469a8137bbf9df9560279a5ba87a13e7d4a461489bd8005054f14fad881e06aa46e4333d920000000000000000000000000000000011dcec636ef785d348fcbf9c59a82080b8f2c02d7ab954bc17af1c163a5383a36dd3948ac9110c6afb363ccfde2b6682", "Name": "matter_fp_to_g1_61", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df", "Expected": "000000000000000000000000000000000e545a87fb19f7943e18c75f7a173d18ef8129b200222bf6a2ba5a93a92c47ba7accecc4f089c42d6c6bb2425bd1786e0000000000000000000000000000000008c005ef6e5b25e84a8251add6112db49637c2b955af8cd65d029f8e17abfc660794b474689a00b5d2784163a9a0c241", "Name": "matter_fp_to_g1_62", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb", "Expected": "0000000000000000000000000000000004cedd2deb72d9168ab5704e21d9a5d85b65ae1510a628515753e85425286d9825dac99922be4a19870700956a65ece9000000000000000000000000000000000f5b0efbb2b327e294246fe862ac01dcedc7e728b938edb9c4a6128740b7d192cf8ad877b869207fb6d1453d85db895a", "Name": "matter_fp_to_g1_63", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de74", "Expected": "00000000000000000000000000000000031841f58b82f7e44aa03f474f18360128aa5699e748e4e2fda1c29d3cf165dc3542b90f09e415e92d73a162af38ad52000000000000000000000000000000000028cbb3ff58cf28f6dc876c2c1cb147bd6af85f3baabe253e9a1dd69687b3a46d4604d2d92d08310ecd7c90723bc7c2", "Name": "matter_fp_to_g1_64", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5", "Expected": "000000000000000000000000000000000e378bf9d1d65cf3a39dc2b3cd2dca8954270006abe048cc29183c5e7c1cf464b21a548679fdf5af8a31e198b69ded53000000000000000000000000000000000865c90b45eba1979e433f71c93c7b3b8e90d3d12a3c2153ab7c420f507bbf91edb593d3beb3899e76d41674b5ca33d6", "Name": "matter_fp_to_g1_65", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276", "Expected": "000000000000000000000000000000000844679db6a74e2a1f7c342771616c446c5e240e40e1f994fcba49f8ab22a7fe06b6909f50ea3c49a8fbebaf2b22b0a000000000000000000000000000000000090afa19255f7b71630c466d6b180b2100f8ea6b7ee2085973e409af8027859b61e0c46b639120ef6f3ee1555aed2f94", "Name": "matter_fp_to_g1_66", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7", "Expected": "0000000000000000000000000000000009ffb2b0054536d714944c6c96f8c1ea902e7109d4917a54ec551d811ab15042f843e158a9e4decab9761cb10e7c3e24000000000000000000000000000000000a6c7a862b951aa9f8c2d1e8ba30af8b7909e9721a06479d186e46ffae3ba09f5f52561c7c4c34d121be1304650cfc6a", "Name": "matter_fp_to_g1_67", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031", "Expected": "0000000000000000000000000000000001f9035574fac4ddc3f114a79938105d95ad4947588028b60e2926a8e0fd78710434edb2ab6b761fec43e458e19f0e200000000000000000000000000000000001e86d391172978aadc652b1c5d28dbb26a5357d1deb522bc280a270cc63cc18284e5b05033cd7ce1a6eb962a5b7e268", "Name": "matter_fp_to_g1_68", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb", "Expected": "0000000000000000000000000000000002583631492e3e0bf080a5f67334f7a2907c707a678bf63d53badb3ed90305a6eae895f7842a5d44a2110585d412ed860000000000000000000000000000000018719d22fc604567689870d5a5b043ee7234927b1e878dce88be212a8b0981e64f3cf9e03dea94439f504c846c6e42f9", "Name": "matter_fp_to_g1_69", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5", "Expected": "000000000000000000000000000000000fc1acd8490dee632c51e67356601295291b107087efc2483c1e1a41fedcff244114608c49f6911a4249a59a891264140000000000000000000000000000000019c402eaa9ddd6ff3c72a7d3bbc736cc867b437dbf56c9941ffdb2e0cd60bdb7ccbecef3d62aad22e97c1d96a328e8db", "Name": "matter_fp_to_g1_70", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2", "Expected": "000000000000000000000000000000001785abb82ace5d8024c97b3480fa69a65f5ed48fd3f5416f068690f8f79295d13929d01922c562277f65293abf5d739a000000000000000000000000000000001076dbc521375a1431b24f7d03902491b80b1856cbfd3e759b520927fc559e705801460afaba6991b032d59739c25059", "Name": "matter_fp_to_g1_71", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b299", "Expected": "000000000000000000000000000000000bf99b7aa1dd96f57974fd79d5823d1f379bc0e32ce416e6f89a499b82727081aa78529dcc76257d1d699b9979ee23f900000000000000000000000000000000067044e8b0cf455974850859bf76bca780f1908beb06a64a7ee8db2ed54703431c354cc3d7576fde0b45611a2f49f862", "Name": "matter_fp_to_g1_72", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf", "Expected": "000000000000000000000000000000000d102c354adf7380053c8b0c11a5c15b046516a87b3e98d1f909bdaff06eebfd9b0c457ec3741833da262f77d411cc500000000000000000000000000000000012cfcd6910ac046ab8c0b448edca5847d0f8cc2a4633fe42edd223ea1b73ec451de8d75cc3d37dfb741ee35259b34449", "Name": "matter_fp_to_g1_73", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec433292", "Expected": "0000000000000000000000000000000013c11f82052df6294da64b16551e689c439d2d27922bef2a067bc49eb4718a392693570f3b3e58158dc0f5bc3a5b8f73000000000000000000000000000000001517ee24f199913c184181561823d7c3506caa09d93d506c7773f9f615169df444c9f09b518e840735c259ec02488670", "Name": "matter_fp_to_g1_74", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036", "Expected": "000000000000000000000000000000000a546a1f4d65a37d7d60468c18f72152473feeed100119b4518f4c778a7a37a23e8c60ee04cc0b39d5a1eb8c908856870000000000000000000000000000000009c5766d9c88dca87768c0aff4160ff0fdc3aa67dde3eafcca030eb295a6736e95e415f3f5a443f2545c7fbd01f97964", "Name": "matter_fp_to_g1_75", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d7", "Expected": "000000000000000000000000000000000a197b81c0950b1b802128a01e3b620fb2134115a0d1aa2946a82fd22e91f172785d19017fca385863ee1643bcd332b80000000000000000000000000000000011fba5b82b0b2726bbe7a6157ec9103d0b5a480066ce5ab7120294930b81c04cf6d0fb8b979d17c3e262bd1268bdf1aa", "Name": "matter_fp_to_g1_76", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e", "Expected": "000000000000000000000000000000001644dd543ee92960effec90347ffe5f06d6b087f13c6bd73dca93c9e16818d25ffafe3610260cd43ce9909e2ac2e2884000000000000000000000000000000001893436c9dc44500be831076b375d0feccfad2a126110fbcfb77acfb95d6dd6c6615b4b795c007ece6ea0c31915b8e32", "Name": "matter_fp_to_g1_77", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b6", "Expected": "0000000000000000000000000000000001aded655b8ba2739b820b894eefd7e60d11889d7321fdae5ddff5dce11551af24acea3f501044562237fe5df53305df0000000000000000000000000000000010f4f3f415891ba4dfb21307798329aac5baea98cdb44354d4263e1ee6436f613a3accf06802ce2c2782e8a15738bc63", "Name": "matter_fp_to_g1_78", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee", "Expected": "000000000000000000000000000000000be0b580d0f12faa809d589ba59c5810c18f74b025e6dd4dc49c83b6a39423c5cf82b0dbb1d750e1801e37a5291692fa0000000000000000000000000000000010891c5bfece55dabcd223518167c5b0663f65c001ed051735635b417cbcf2484a057522e1c3417e43c82095b0cbb855", "Name": "matter_fp_to_g1_79", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770", "Expected": "00000000000000000000000000000000152454ae7fed9c971cfd72ed054f44124d71542f9ada5a90f1601114289c93fb490a1c5d99b3e8c70fc44fd10322173f0000000000000000000000000000000017bf9499bdc15ae5091daf41812c74535ca31b56520e420edf9e5aa90795ce5db5fa42a06dfcbc7438e954db83f09b75", "Name": "matter_fp_to_g1_80", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38", "Expected": "000000000000000000000000000000001633516081b91621b786a09389e89b274c2d9ec616db5028b009ed5c0a1ab47695a0b95c53a45112144613a4af08e6ea0000000000000000000000000000000014b09586f75c939fd62c3d667ab6263367f8961ad4597f1b92d792e8ef79a469137dfba5ec0a6354d5bfe3a84130bc65", "Name": "matter_fp_to_g1_81", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e6", "Expected": "0000000000000000000000000000000000f0474d596ed86a0d664885f9c981228fdc352755d52dd7e979a85fdb1b6dad106d8bc0a1eac04b510829b7da496686000000000000000000000000000000000a72f532897f912eeea707bfd6d183a73786c7b2e2c80a01f3abe7b959467d6ea63093c16d6465382a7808d5f0edd92f", "Name": "matter_fp_to_g1_82", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d", "Expected": "00000000000000000000000000000000139ae959f9b0cc2d900e748220c4bfa7dbe22926d8ecb9a10e7d713fa0a6e147fa3463e06b791a5e604c66110b77f7530000000000000000000000000000000013f8d09915f77f4a18854dc2451cf39d7ff502a8184d3b4c59ad3317d62940e903d68836751172ec0b4a796db003b373", "Name": "matter_fp_to_g1_83", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b", "Expected": "0000000000000000000000000000000015d065191ab63df2175f821cf62a4b948a6b2389512c7e94e1fa3c99506af624810ee17de2c183ebd69b4dc485ae264b000000000000000000000000000000000fa8cfd94bbfa6d504497866c1e0d9e84717fbf0468a164e3b8ca46348789e2b7f08ac5e8aa2e7205062f3d5083dc5fa", "Name": "matter_fp_to_g1_84", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7", "Expected": "0000000000000000000000000000000012978a0da7162aa1e8b32cb6ec0eebf2c2e62350cab4534358c6bf80299dda9281e16ee40313e7c52c804b2f4de7f1870000000000000000000000000000000009dfbafc8e40d71a789a52d5f8b80e7c8510c58bc0774cfa84211a9c1417d75d5c7b06d7aa9fe052ad9c1f30c922705e", "Name": "matter_fp_to_g1_85", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d", "Expected": "000000000000000000000000000000000de98869442b759a382d0f6ca45eb60424eb9aee2efdac83086cb6dd374120941343eb314756113e084f943cb60d91470000000000000000000000000000000019dacc8180e6dd09ac4bb97114d2ecadb04bd2aef6e5f0993742c5270267e42d052d436c99ba61f6c0fd1fd2cd51d172", "Name": "matter_fp_to_g1_86", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1", "Expected": "00000000000000000000000000000000123313e3cc006c4b95938f5eca903604ac9272c7a0c79cd932407b70635d7ca5de9297496c27406f180d5edebbb54c7e0000000000000000000000000000000002164460e59cc8788c96e235a6faa7fadb7e6ee9f6b0b95292992973ff54a92147dc7ae8e8f217515b6185875bd0bd7d", "Name": "matter_fp_to_g1_87", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a", "Expected": "000000000000000000000000000000000393a1b2395447b2e2838c2f49493c185424c4848f888616f16a95552671ff28b5ef223bf34299005f22a8df6efd68290000000000000000000000000000000012b1fe46279922e92d356355752ae0c2f28fc55de39ebfbd317a6c1c507d973f88c6282468571a1efc20c10314ac72f3", "Name": "matter_fp_to_g1_88", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122", "Expected": "000000000000000000000000000000000739f563b42648cde5befaf44317468982eb9d2fceee7d2efff1755be973cfc2beda829268246d09cd29fc3aa91f0b8a0000000000000000000000000000000014fe0b03ac5e0e03acd7811270d65742a3345bed7a4790d5f40097dd34050d0043104b65fd4691c251f03e67525d41b5", "Name": "matter_fp_to_g1_89", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be", "Expected": "00000000000000000000000000000000128e92c9c10fb9b065fe2c2dcfe365e98aa54eaeb3fae987306c7f0a227171ae0b3464d01a54a8d6b144ff60c45088a00000000000000000000000000000000001beaace4e23c9a31e1e9eb8596b3b05b9d72553f44c61627654757080171b05c900fe1b638193a69058e8d66cff1aa6", "Name": "matter_fp_to_g1_90", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f", "Expected": "000000000000000000000000000000000a6099ebb3a1101206bbd21149cf22af2371106bd34671c1cbd4f2e19311fd100bcb56a6d9d77bd834f972e55e0fb75e0000000000000000000000000000000001db77a2045e54b0ac4b3d61190684b4eec9c4ea415e5c820992b70d6ee2e086c02892228c4465c8494f939cc0b7b5ee", "Name": "matter_fp_to_g1_91", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a", "Expected": "000000000000000000000000000000001101cd37b61247a9859bb09ccf9eb416643f86b7109bb45d6827fbf424956c9a16b2a19c5e198551c43aa1934ad8ed0e000000000000000000000000000000000da562fcb2e3cba853de6d245a1ea0cfc3ac120b316a5f4f7072cc35a6634027409ad08c5d591a6688b24cdc4562cddb", "Name": "matter_fp_to_g1_92", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb", "Expected": "000000000000000000000000000000000323c3aa4b20691af32696c449668fb6da6a0c2e8eb176fb8fcd8aeebc9b5a3bffc57b28dd35e374811d420419fb0fd30000000000000000000000000000000019516a092385d8c917b46a742f086c51e2648c7e9a709ebeb5a0f8bc29c9aabf99972aa3a218582f37d91f9758a5ddb2", "Name": "matter_fp_to_g1_93", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c9436", "Expected": "000000000000000000000000000000000d0351d8557d21c2dd3b1be77bb01df804ebb9e2d7e80910264ff94861cdc0a4deedc1231c61b7503c5d653e31fe10850000000000000000000000000000000005858ee487860d1ba04cfdcedebda235616c2d271ed50f89d6cf2852ea7e10ac825dacd8b00071684858a12459d1705c", "Name": "matter_fp_to_g1_94", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba", "Expected": "00000000000000000000000000000000176c50c2fcf1bcbe03a1a1ed2eb120f94ad4fcea34a59607ea595bc2b37cb92f87641191b65d4b5d57f5491ce6576a670000000000000000000000000000000000e177361e09975c98849faf8e24086f75a48df0f257ea47b659cc2a142a57ad1f64416f6dee5cbc4e57f780dadd1cf2", "Name": "matter_fp_to_g1_95", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c29", "Expected": "000000000000000000000000000000000dcce000aae744f8b3b6754af57a36786d887d7f9857654f93edbcb6c4416ccfea5e859acc82860b5f706087e87cdc07000000000000000000000000000000001847c32c839668a38669fdbabb512df15cde2b28ca336b0e158d1fd57f74638d86ba40ff68f0a50cead7021e86c5271d", "Name": "matter_fp_to_g1_96", + "Gas": 5500, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4", "Expected": "00000000000000000000000000000000062a58846d39dd1fdbd34a7117797f2200d814b2a8eac9479885762565a979e93b5313575bff5ada3211eeed0a3f4ddc000000000000000000000000000000000548a24e7af2b38c4d16d8dfc8fb2d7e7669051e2643c44aee113f20d31f4853cef84e2dec20095c273680cca278331c", "Name": "matter_fp_to_g1_97", + "Gas": 5500, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa1", "Expected": "00000000000000000000000000000000107f01e4fb6430e34128e3335872cf40df2b498a63e048d46158190cb627e37833d2238dd72681037ce376384736b43e0000000000000000000000000000000000e1812299403efe0f8d111d97a4b7e7b8aa1f4ec58f9935b1367d81a847fb42cf756154448f9172118123679a41a280", "Name": "matter_fp_to_g1_98", + "Gas": 5500, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79", "Expected": "00000000000000000000000000000000188dc9e5ddf48977f33aeb6e505518269bf67fb624fa86b79741d842e75a6fa1be0911c2caa9e55571b6e55a3c0c0b9e00000000000000000000000000000000193e8b7c7e78daf104a59d7b39401a65355fa874bd34e91688580941e99a863367efc68fe871e38e07423090e93919c9", "Name": "matter_fp_to_g1_99", + "Gas": 5500, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsMapG2.json b/core/vm/testdata/precompiles/blsMapG2.json index 2f5625d2b1..f30eef6564 100644 --- a/core/vm/testdata/precompiles/blsMapG2.json +++ b/core/vm/testdata/precompiles/blsMapG2.json @@ -3,600 +3,700 @@ "Input": "0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641", "Expected": "000000000000000000000000000000000d029393d3a13ff5b26fe52bd8953768946c5510f9441f1136f1e938957882db6adbd7504177ee49281ecccba596f2bf000000000000000000000000000000001993f668fb1ae603aefbb1323000033fcb3b65d8ed3bf09c84c61e27704b745f540299a1872cd697ae45a5afd780f1d600000000000000000000000000000000079cb41060ef7a128d286c9ef8638689a49ca19da8672ea5c47b6ba6dbde193ee835d3b87a76a689966037c07159c10d0000000000000000000000000000000017c688ae9a8b59a7069c27f2d58dd2196cb414f4fb89da8510518a1142ab19d158badd1c3bad03408fafb1669903cd6c", "Name": "matter_fp2_to_g2_0", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2", "Expected": "000000000000000000000000000000000a2bca68ca23f3f03c678140d87465b5b336dbd50926d1219fcc0def162280765fe1093c117d52483d3d8cdc7ab76529000000000000000000000000000000000fe83e3a958d6038569da6132bfa19f0e3dae3bee0d8a60e7cc33e4d7084a9e8c32fe31ec6e617277e2e450699eba1f80000000000000000000000000000000005602683f0ef231cc0b7c8c695765d7933f4efa7503ed9f2aa3c774284eabcdd32fd287b6a3539c9749f2e15b58f5cd50000000000000000000000000000000000b4f17de0db6e9d081723b613b23864c1eeae91b7cbda40ecd24823022aee7fc4068adc41947b97e17009fad9d0d4de", "Name": "matter_fp2_to_g2_1", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de200000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799", "Expected": "00000000000000000000000000000000184af3f8a359dd35dddd3dfcc6f5b55ed327907ed573378289209569244e3c9c02bdf278eb567186f8b64de380c115360000000000000000000000000000000012f5ba8e520c4730ac1fb75dabbfdc0181855e5ba2968a8c0ba36a47ab86ac45d19aa3d55f15a601e120be1f75eefe240000000000000000000000000000000004e313db704b103c2c1e3a58f8e95a470e7199081eb086e9524583131714c4a3db551fd51a3f2314a19a658e7b1765380000000000000000000000000000000004040eab7416a1703b0d103120506f1de2b26b0f48c7a0ea63dca4d9ad1c478ae03b5d7bfd51f4cd6f8cea26212c4edf", "Name": "matter_fp2_to_g2_2", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60", "Expected": "0000000000000000000000000000000017fc341e495bf4ef5da4c159a28320aca97ca28fe3a0441242cf506b0f89bb52f5b5d8c6e038d229ffe67d00151912f00000000000000000000000000000000007666300b7be3d904ae3d19019f7be5cf5ba6161b969c1a78aff639a24387d8fdcc4d0e3cd81ba6f063ebf2d859370f20000000000000000000000000000000007cc705dbfb5c0418beb1cfbd864fa0631bd60eccfdb16b5d55b6ef3558e2ec87dac3b45294dcf04a064d6d1eba5a6eb00000000000000000000000000000000052cb9c982e6b05c1d2ab4eed1d8082f96426b55615ebc6a53bdc320ccad0aad044395ed641b3176b554f19e62d46b73", "Name": "matter_fp2_to_g2_3", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f1570000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac", "Expected": "0000000000000000000000000000000015ad9155037e03898cb3b706f7105e39d413ff3a5abb65812b8d21d003cab8fbb607d3938ccd6a774bc8debfa30f42760000000000000000000000000000000019d6382bb2d78180a8998a0536d67412d00ec0ef65f4cbce01340b8d6e781c0ff790296f8cada28966b147c69e02f366000000000000000000000000000000001290c2c205b748069d0875a89ca74a3b05ad8218ed46a1570696932302983c090d96e17e0b828a666fdfc3b72cd348bc000000000000000000000000000000000114f2f7ffaa9f90b547e86c863a5d3585819a78b095848dfa39576a10874a905488687b73e613f3d426510f5d1d1ce1", "Name": "matter_fp2_to_g2_4", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c80000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3", "Expected": "0000000000000000000000000000000012084a53cde353a46af17cd2fb02c477e47b874d8ff58025b5015837759032ff98013dc5bf01253bb964f035183c9071000000000000000000000000000000001659272ab7e3a070a5c7b25a5d3402f7371ed67e58cac8438df41c39c1acd95ac5886b030384bf537d7c4bb8ddb2c538000000000000000000000000000000000852ddcc37a09a0a8f62dfbd1ba5064c1f6afacc9a279a4d998bed643eec5a0d96d6bad95701a04f52c83e8f87f48d5d00000000000000000000000000000000097a399370875398028d42bde8cf4e9641730af7a2971e2f59c95938120603a239c65030ded4323c955f7fd24bebf31b", "Name": "matter_fp2_to_g2_5", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be", "Expected": "0000000000000000000000000000000006f8191123f1e8f6a05e4e663fa763c8a0ade5de3c7cd38ec1c82e1c85f123ab51fffcebd677afec8e9adecd8d11263d0000000000000000000000000000000004fcd825bc55d044eb70e0bdd5ea2ac58ec1487e903b431c57a640c756265a382581b8450fb15dc649cf22a8539088220000000000000000000000000000000015259f83d76490bb868bb88c2a2c3e07a326bd3e97fc2f552adf85722a360a443d720c328076e35224328e09494746e0000000000000000000000000000000000f76b0b960a1343b4267f5aff44901fd6796a778b1a87666b95b773edd0e7ffb6656d4f0cc3b9b38bc6c0ed20cfce153", "Name": "matter_fp2_to_g2_6", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3", "Expected": "0000000000000000000000000000000018bf5f93dbc2c37479b819f8edccd687c4d3c4dd04f8c73762fd89d0c003674e3b2ed749d23e775f925279b3112689f80000000000000000000000000000000008a033b197aa8ea2213dbd7ed478d98c25dc6e9f91b9924f3c14124da26a67bb196926e02da89b746f2a67b14ad226070000000000000000000000000000000006f7824bdc9c53212609512858278f79d9b094165ff178e3da8776e24311bebbd9deb29f366d4c7693a15c34df118403000000000000000000000000000000000edde25fc24b9ec58b3c317aa3ae48dd5fecdf6397ed9636ea042722d264db0b1a89a15a1e16e892755730ef52796527", "Name": "matter_fp2_to_g2_7", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad", "Expected": "0000000000000000000000000000000002d28025f4b798083aec3ca9a91a051ce27a374b115c944932026b4fe0dcf68b335d5e47212f800c241c2d42fd219635000000000000000000000000000000001742fb6ef8e9a5a7572b0d3fa4ae8ae56c9c6f4daa20d0b88212c40511c6f6b5ee98314a2d1cbe4bbbec907495a1ade8000000000000000000000000000000000d700a511a58c1b8f11153669cb21d88512dfdacbabe38e402431b4f7ba374b5f9a88614da2d56799d39324e9d19e27a000000000000000000000000000000000c6068bc7a43d614b8f1132b13e04f66d2fb5ac0c5bc8501b754a0bcf4f382db92b0994c4999e104c9d1111ef91d5edc", "Name": "matter_fp2_to_g2_8", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20", "Expected": "000000000000000000000000000000001350c68434a9b02392e60540a3985bae8daf9a170b30336ac73afae6f892c7ae8f5f1cadfb2780d6e5961ebf91cd69ee0000000000000000000000000000000000c20bd286fc1886b9b28dfa40d1a27395cf76a8b73946849ea0a7b5e12530de13c16acef8fe2a2c247ea65ca023eed70000000000000000000000000000000002d8ffd0235fb60fa573662034d46260e0c96396537b2a9d486dd03bdd13c5a1efd2d3cb9849ed11c4376b665f378226000000000000000000000000000000000d90ca1b73a6a9566832f9f19d8530a3b12f22bef853fc44088559b923ca108cebf4291e0d7de8f25c7429d455f5ae46", "Name": "matter_fp2_to_g2_9", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab10000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175", "Expected": "0000000000000000000000000000000013f223602e8d12c3bb51cd393f6f59beb5c55fe80c3fc8fb0bc90eca533d9b7981563a30ebd727ab6cf0111fa2d3099d000000000000000000000000000000000962b0585c681894cb701f17ec06c0c240899db574c02d82d85ed4dabd4b8654c29b84c71d2921986fc2abc542a3ed9f0000000000000000000000000000000000f0e79245e645a6e3fb88b9103ede3e6ecdd7e45d61b5755d7a8d100d80719746af58bb23d3068cee7389b2acf17f8b0000000000000000000000000000000017fa0aac84c58283f34b9bf713cde98c175b38e92503c08205350822d778f3dd5bed8051e185c495831a628aa89335c7", "Name": "matter_fp2_to_g2_10", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d235020000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3", "Expected": "000000000000000000000000000000001062c97c214b86518660c5e1c33a4e48923ae89ab7d8bc5c798e631de16fc1f104aa957d3e7915aee8551e24aaafc8e6000000000000000000000000000000000e42b785f17f25b87a0dc558a8d57b19d8f41767c3b4fd70c147e95443aff2d9a743003da41d578a2b56d7dc748cf59500000000000000000000000000000000111fd38cd2f5f681bb37f6239a5eea820ce3f01023c685f8e7e244fe9aa9dcbd18f0e50705faa5d8d66b28af9f371c630000000000000000000000000000000004726d3e452f6fcb180ce1d50bbee3a23f7949b635a058f12de1cf5abda19c042168feea53211dbed0bfca489a020930", "Name": "matter_fp2_to_g2_11", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d99000000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844", "Expected": "000000000000000000000000000000001211bb8d3bf65b60efc7237ffecddb4e7e2f0dd36e2a704dfc9f4972897addff1a57182f8e0a0ac08c9af2c98eaa4c560000000000000000000000000000000007e9877280aad45a3b1453b6771ab509e4f53937cc6da73d3add50aff94869b27f49218fb479fe19a6176b9aadd36e35000000000000000000000000000000000ff915801695a281f6642751be77155a813847ae0237d77d2edf836aebac02b659b98d49842d4d10e82d9d146e63a3da000000000000000000000000000000000fae1c8c01a2dd94f17c660353d158ff6f3eed4e6375f1e414ade9d6fd040a48e3ff0d558c882e92e74bd6ef4ab06168", "Name": "matter_fp2_to_g2_12", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2", "Expected": "000000000000000000000000000000000dd00d9f31cb5148048125668286c1790cb7294e740df978ac0bdaa6e1c4ba139a04f5770b194c9bcfb123d9b40b6acb00000000000000000000000000000000085d5f4cb831720fa13cef25464a1ba7af33abcc4079d2c5736a219ad9649ebb5dbb8687a2d3952390866587d7088f72000000000000000000000000000000000de377d773e40e1c76e218b969297d15f7819c525ce39aee5114e8405bd7361116682cf9d673574d415a7016b23b567d0000000000000000000000000000000018db26c2097f72b8788ef5aad2d7aa400627e224924afea1ac7c7a6b5cff4a55255e218572614519a536eaaf0f65533c", "Name": "matter_fp2_to_g2_13", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb87500000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75", "Expected": "000000000000000000000000000000001853b4c4e6fcdbed29c5d3aa4a9f6d447adc512f66a32fdef06c6ad316c42eb3ca47ffe6f21318ad610d0a68673d7bc300000000000000000000000000000000123d15c37fa8b1a95229e28500c9a767e6286b780138dcff2714bf1f8242f39bebb7d86e2811551914719ca90fb5615f000000000000000000000000000000000537498c2ec64b2ba58aa0a858b69990cac544d5cac29abdf6a42ae9c04061f83580b79c2a6104ebc55939d9a2bc5ae2000000000000000000000000000000000b348c19aad3b67c690512f372d995555ee38bffcdaf33bb827160d6929d2ce598523880f6136f11e1d6482a654cb016", "Name": "matter_fp2_to_g2_14", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be122200000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760", "Expected": "000000000000000000000000000000000994e7b6ccafc996f672c42ab491105ffe1482e65aeb456de2213b531889773ad4d5e6ea1687d6a1f13e74878766f11e000000000000000000000000000000000b89030486a1d622c97970ee7da6189ac341b9cafbb4081463f579ab8b4b049c6e6c8b63157455770a79108424a14f24000000000000000000000000000000000ded43800a991f8c37282d803a39941d3bfbfbdc56dbf7500ef3d16750b27dcb1ad93f89714395fd3dffe318c1771375000000000000000000000000000000001994144b032e1f8c4d688754eef82cdba0018ac47030fcb77e8fd920e0b0336255d2cc8376c03e1074f91269cd2519d1", "Name": "matter_fp2_to_g2_15", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091", "Expected": "00000000000000000000000000000000195d99406baadc7d8740962cbbf4bc1f22b08eafb52f3cb3c588b6cb3cd89d16cb7b8d388563289f5b5ea466128525c80000000000000000000000000000000004809f70463633595dd763d658354df4f9b409911e1a0328fdaf486d76ffb410d7c6cfcc2d48fd6757d5c2a4834f81fd000000000000000000000000000000000654f8475562098a2cb27ce224674a383283cde35173e1c16b141998b641ac9ee663d766f045451a7f6d600973f0ec520000000000000000000000000000000013bac451a44982c7b1aaac7522dab598cb79b9a3dab77f4d5a4c1c97c154451499979af1f86ced8ce2099bccd400420d", "Name": "matter_fp2_to_g2_16", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1", "Expected": "0000000000000000000000000000000016ea88d0bce32981f489438df1bc14e7ade7a45d449ee1ac1a041c1204460cf53ae5c0e111914d8af9e6b3b7fa394484000000000000000000000000000000000db571ca6a55bc8285421553a373048f7877ecb9683d52acf07d48e1026795993e4e7177490921bc6fe1e63d69c2de3c0000000000000000000000000000000011602919de1df6cc0dd36a59c84ebb8e209056534e336f5074c9ae5323f8a03b123dc6354cf85301d838b16518ab64390000000000000000000000000000000004407d30fbd632fd493055bd4d8cbed337767a2ac534411a3eabec570ba41d2ad28ef37512a7da3611ad60b6536b3f07", "Name": "matter_fp2_to_g2_17", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192", "Expected": "000000000000000000000000000000000c1e7b188697aa9a053f14e2d907f2c61a59e0b0c72f9cce30faf81dc714a50113500ca9bc3af6657a5d214f52c90616000000000000000000000000000000001544c35d712eaf79d8dd5a22fbab72f8a6843728898412a7f305b205f8a50e03c6c462b87b3ac165e9e6428e0a44a74a00000000000000000000000000000000029ebafd90a1a887669fd0ace762a66bca2bf0a216333b0ac97dedb6bff3dda2bca1e3d0ed5fa9081c2887fe6a8e24cf000000000000000000000000000000000e1a01ca93ed268e0291a937483f7f8e252c91f9bd8bde55271b0c97fcbbb9219009514217dd8bd7e0267f44e9927a93", "Name": "matter_fp2_to_g2_18", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a7700000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510", "Expected": "0000000000000000000000000000000004e8ad9838e7e269cddf0ae5c8f0f57e7467e0b6f2b9e37e7c4bcae965e9582dc46c9c50aa01f5dc761bf2f1ad311eec0000000000000000000000000000000011b1438ccc668900914578c3ec6e1334d0823861c892608817498fe2e538deec73e0034a6e8ba9790f63fdd95af3714a0000000000000000000000000000000005b4c88196425d3ecd22bfc0cb1a95488493f85bb74f50315f0ffcdd57ad2de23c137cd6d2f6f6dca8af2e3f7bb0539c0000000000000000000000000000000017066344a0f345ecf6a2ba66c37ccbce26a3f551524f74636d4c4812bf5adfabffb0645b898b10c332e94e5f2ae2d1c2", "Name": "matter_fp2_to_g2_19", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b8000000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5", "Expected": "0000000000000000000000000000000010d393bf893d589c578df58f4d0098ad3cd10d3a1d0f112f51b132a369e68c0284a6b70a5673383ae24a27a9043b16cf0000000000000000000000000000000003402afb77b187b45906d9cce348976ed88c758d75b9962a53352a6c3ee37751a9928097c0d68c6f8a315def4ca875200000000000000000000000000000000019b98631e53a3ffda3fb9165ef7236dad5c0c8d57c3315617cbd3ce77430bd89b9e1d88a019042cae0075594514a5e67000000000000000000000000000000001783bf1c9b0ec44c9191dab01ef5bda0cb2f533dbcd3aeac2b7c6720dbc8e3f770a215ec8ea2035129711ce4b448ba87", "Name": "matter_fp2_to_g2_20", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03", "Expected": "00000000000000000000000000000000086ac901098212acd091d9c4d42a1318c3b343480f1130d6e52128d61df9e19fb61ef1ff35de0ef60062cd99202910ff0000000000000000000000000000000019109b7292f1a420f09a56dce9694cb4944808a2ce9f1964cbb6ffd14a710c35abe81300090ffcd9e95f33e0de9f879a0000000000000000000000000000000012660c4e114a215390c6f6eabc4bd6e3d062ee28d0c87e24351c7d43195253cb7b5bcfed2b4abb2fdeb3ac04ee228997000000000000000000000000000000000e56d35a7e40a86ffd2088c81488265ecc4468d6cf02d563c91611cdf8b4333cf66ef50b993fe651b1792d2b242cff94", "Name": "matter_fp2_to_g2_21", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e", "Expected": "00000000000000000000000000000000071d3e796fb15d63c2d5cf68f59f11792b0b580b85c8839a02fad96664f14735ede2edfd5ba5b64045b366904f54ab600000000000000000000000000000000013fd1ea38d32772458622731b9e2d9d749f2b747443f7e47ef5e041531b56f86d1775d42a548b2bb201228f49ec9f46800000000000000000000000000000000099c2bd996c8c5ee37de971e8b75a0bdd4f69299778ee3d216973c9dbba97c7a93e40b209d390024bc4b5e82560a1a83000000000000000000000000000000000c4922ed9af845467440b78efa3a53ba904f29adf66e8ac437c8bb6624b5e5ba0772a5639b45fe167b1fb9283747c50f", "Name": "matter_fp2_to_g2_22", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e900000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe", "Expected": "000000000000000000000000000000000e413d72fdc3db6fc79ef26ae8b37fe5c4356a80b3598513b5173b3406ffb54708b8794dae158060a1accbe956a39ff30000000000000000000000000000000019ba9dfa74fd241a55a3b47c9f37c6ebd1e8b51f46197881abb64b7f57c0e2d8f18edee35bb9da03702c0dc5cc8749f700000000000000000000000000000000183525156fbc80cc67d6cd15fd2ddf7fb0528656ec1d31b4c275ef101dbb635424abbff1154a3ee04346ac53148fb1f70000000000000000000000000000000011da0dcd666d01180902d8a7fd7d2fbb39f9c7587540451045956108a8579d7c116385a81627dad9d4cb8cfe68927b6d", "Name": "matter_fp2_to_g2_23", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf480000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909", "Expected": "000000000000000000000000000000001932acb1fd0708edf13c293007a035991bdfbfe0089b61c261258e8c5c10d82a5318b2af221b372f0f3f43c391421582000000000000000000000000000000000973650743f0ec8e2acca33f2ef230ee7a05635d14099cdce913ad8678458ec0dde5c5a941097af2ee0c8ffb937d09fd000000000000000000000000000000000bdaf319044101ee9aa27b3accd36a5ecaf8b80deda4548377ddeb97283537be3f7199ad3c190ed23cdb44abb8786a080000000000000000000000000000000006c448827e3fe4f274bfa55a66bc76c5b01e29ac6a8dbebd801855ba4e93bcbd03292ccf804f07f21481260c135b827b", "Name": "matter_fp2_to_g2_24", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d50000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5", "Expected": "0000000000000000000000000000000004aee050b0ea07118d76f835218b77b39854f5ababc4e2a29d7c8cc7c18a69c30bb22437049a051d049c8a84f7868ad40000000000000000000000000000000003b1b809d5046054924c3814d26fd5fbdc59e03e5505813bab73bc212b0f5bc0d3fc34478311c5e1ac70fd16a01c52800000000000000000000000000000000002249a026af0b49f4659eca2c23dc790fb36a7b2996188828a17d5852003f1420f11699062932835cfe6543d454521e30000000000000000000000000000000008217aea2221f8748cd81cd37777605a95a63aba36a6ddad72c1e1ac57b24d79ff9d9c4ed71a6e3ac8a378129d5475ad", "Name": "matter_fp2_to_g2_25", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a", "Expected": "000000000000000000000000000000001274f676bcc05e54fa4b0cce234870ba97a0b1626543d6a9f09afebd5a752769000df404e4d434ebfd561f8335f36d0d0000000000000000000000000000000002877c9438fa319dd1a00f381834e8f3d3cdebf4e1f7690cb82559a2e978bedfd2455be020d0353aa56d435c0174b5b10000000000000000000000000000000009487cc9c7a09be901673cb1bd9a51f45e5d2ed30c90cbdd3e2b294c8f866f68da55533b78152e9ef6de30c345fde5b7000000000000000000000000000000000a3a8d4aabdb260203898655745cb695e6dc90c6e7bf0248784f8aa2340390fd5d8f1c6a98eb1990eb97c2a7f103e3fe", "Name": "matter_fp2_to_g2_26", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f80000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa", "Expected": "000000000000000000000000000000000ef415dfc1e47f39e9632ed21c9c2bfcc1959299710dcd7935a757e3756a42c8f6c627c720fd62f9c486a8e88a64c76d00000000000000000000000000000000088079108fe7d9ac93590c045be0d41396f3204d83793c4e862c5360ddb3268a63f704a9d14323943fc85874cdadaff1000000000000000000000000000000000cce908e8dbb7ec35820f2db5ae1174e0f675b21ae416fc89a7f242df3ee98764022744842999f65132229156d2627370000000000000000000000000000000011e0e2f8513d0a71b48599139a9a29c8eca090c5b02292baba58e07b1d3898fe158cdeb3bbe8edb4a805e695e896984a", "Name": "matter_fp2_to_g2_27", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13", "Expected": "0000000000000000000000000000000007c6f133647745c312695439f1d8c251e941bad6e988cfe324ec7c959a9e0fb50618984429ff1841d4286922a26873170000000000000000000000000000000008edb220f77ed17fa1f4757a42ec66ad808c1acc25c4b9311be4c09703d547f648d9dd7c8109ffa89d01a35c69ec2685000000000000000000000000000000001595cc05b04f557ed569b19d64c09f4d82e6617437571fddd72a672d07ad94bfbaaed906b3a7e3db519159ec8d0a8c4400000000000000000000000000000000041157d4f40bfcef680af0143ccdd0c4bdd25e598a470dae844d887c398bc498edad715fd7383421fc78758cc9b00326", "Name": "matter_fp2_to_g2_28", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce7300000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b", "Expected": "000000000000000000000000000000000f983607a6d8a5c3b8a577cbd5d81ad2ae936e714199e3f4095cf280b8fd6d3699acf4d2ef251a571dd1ef4ba6d838bc00000000000000000000000000000000048c12f8b95f9537e56479b1bc43a121e4edfb6477fcb090a5ea60c5f4d01071776dd0264b0250902448f62800f4d2ea000000000000000000000000000000001644ba272d7003d0077991ccb4569638de0dcc48fd2e8e9a41cee1d2200aee1a849f2d620f60beeb06b08c31cd4eeacc0000000000000000000000000000000018892d773f7e48247215484ca0c8d996833c43a5291b0380c97607c86f4ab2784e692673a1da012ac4fec2713d156a49", "Name": "matter_fp2_to_g2_29", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82", "Expected": "000000000000000000000000000000000a06ea8e644d2d762520ad956d41ac2086a588450bc34f6d070b86fdfd73cd0734341a751d823935a009b7517770f86e00000000000000000000000000000000140ef0d6a0482537da7db8d775ac3c4a93b16c15fbe4602b5b1843ce757aada5f7776a74151d0bcf760f7284d4ffe56c000000000000000000000000000000000873c90f56a2b99da2f0a1528b8e376a5912f9cd81a159379ad70b7c10e6ebb7fea0a90d65543d968a34ebd539372e89000000000000000000000000000000000b05ff57079386e4e18e73cbff5f7b0efa329ef7355f083e8be258922203240dbb8926f7d11c22ab4c16d1df4bcbb600", "Name": "matter_fp2_to_g2_30", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb", "Expected": "000000000000000000000000000000000e9d6f9e83a2584f2cdacc4711085bd251e060f8c87ff7538ce474d663c6f23361c88971c9da589586e754ed69699c820000000000000000000000000000000003fa90cc1dd81b815704e15c0448bd0e8e8d0cd7ad51237a25d4b8a0f78f532b18ec30a108930b7407b7486aad9824de0000000000000000000000000000000000cb97bce1f75b1df5a4b52745014eb632d2d2230e52a9767e3dfd76754e98252ca81ce274b92a2947f6a65fedbaa3e400000000000000000000000000000000090edabb37f411fae1764792083c8c7412fb470833a9f7399fb312c58687d4afbdc622ecf9d74cdfa3ea87382adcdd5f", "Name": "matter_fp2_to_g2_31", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de740000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5", "Expected": "000000000000000000000000000000000cd234fcc729a4206233e46875a557027cb52c96322386b56d6e50d95dd9d23b6f8936ddc6f8475b1076a855c1ae23510000000000000000000000000000000010a774120f607bf9ad2d7bc498536cc9d35cefe384f88a2439a75f1a4f6a9e4b4253daff0d2c91b5915ee0e9a99b4582000000000000000000000000000000001496e7181495114abc0314f580c16038a04a8dab43b5564d518dba5f5e48112ce9daca4b16b6ad51c3af54ec9ce915d20000000000000000000000000000000002c61691a96a2120663c726d7fba3ed37524b58c92a024c15fccc659d1d2cdce077ba233a0d4419a6f237ee4e09abf52", "Name": "matter_fp2_to_g2_32", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7", "Expected": "00000000000000000000000000000000011cd454f16209b0b7040c744291f2df465ebc786946ce3cde77fe4d4bcc4b60a51573c45b8bb2d209da69107613764b0000000000000000000000000000000018a026f29fc2f81e82015ef8610b4396f2e3514ab1a213356953804d585c5cd6a3c5cffbf70d63d9dfca50129021f0e60000000000000000000000000000000015bdcc8c139e636b05ba7376c1ced4a183eb465df53b1996f4ddc8cbf42cdff4ae2bbc2d24831a8ec8b1134cff4444ee0000000000000000000000000000000017671fc3995babcd2c0a1d2a71c417fea84e29df67fa1096fe6d3ec77c45b64fb8da6ed08a57726ab314fb860899961d", "Name": "matter_fp2_to_g2_33", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb", "Expected": "00000000000000000000000000000000156d8823c37c81d8f03c0b2e61a2342aab6e6c9db36cadc9eb741e085de711e9fda08ca78f21753c4fdd8cec059b6c2800000000000000000000000000000000064d4fc2584c78f1e92f808d4457070b0470eb8de9d558885bba8b03efd8d8e195e4923d8e3382481a0ecee905371ae10000000000000000000000000000000008f1dc4d2ba12e7e3e1b0ef3855df4dbf29468bc99d5cb29fa3058a535af2ba038396bccaa238bba6d538498565c2809000000000000000000000000000000000fc9839b6ee876f7846b5086d487360b8faf133b6f5bd2dbc92a7fe2261b91b15aef8d90c227cd5f8ec05e32d807e022", "Name": "matter_fp2_to_g2_34", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2", "Expected": "000000000000000000000000000000000d40f1c25dd57e36ed305276d4505cb250d2d9da0d5b954fe5e396b2c17a5399613243216586cedb19340e80f898873800000000000000000000000000000000063367c4a622fc925319fc6d119d8592f40f126ae05eed86ee5e4f6707b1d234c747e698c40f292dcb82ac5fe74ea80c00000000000000000000000000000000199ddbb5d4b6cd0fb9225a72c53f4596cf2597de63da56f4a9a18be8321a982de17367b0f3d794fa799657dd8ca10c5f000000000000000000000000000000000f1ed84e4fd958547d40cd2dbf16e2da4cb6d0d02763441067221890ae27ea1f689c26c900b695464ededf083667146d", "Name": "matter_fp2_to_g2_35", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b2990000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf", "Expected": "000000000000000000000000000000000ee446310185ce76e31c13e4ca6c43166d971d9b9c539c7d0e8dd8ebbbdd9249922cb674bf6ad6840c203a5e208911fc00000000000000000000000000000000037344752896cff03bc39a9d09757a83c15fbd90f8bc1d8d58dca9b23bc00fa2b0f3f0bd7c9ed857d285825d40afde450000000000000000000000000000000003ef77f0220d1caa7538ecaef1ae2924ac1a180f11004034fc118aeac464fe1ce684b5fc90dae3370e3f79619889f3d7000000000000000000000000000000000fdfa434e7bedec071a1a333088d06299f55735f085a1e907a1c71c312bbb8d27ffa7de7ac69d421ebd675c4afd37594", "Name": "matter_fp2_to_g2_36", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec43329200000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036", "Expected": "0000000000000000000000000000000000ac465de3832452edcead434729be73be90785158617b5ec3ad53b12653e43721eda7de6742dc51d4d4bb58a291999f00000000000000000000000000000000147c39a5c162afa1f8eef400cfa1bdbe5436bc59d93973f50384022962f828ac934a4f88ab7c3d505b0bc3bb002f5efe00000000000000000000000000000000141bcdad53845a7eb2ec08189a55445059dad24ae5d39fedce869791aa28459f05a6cdf9575676cc6f3dd7d6faf077240000000000000000000000000000000010e9f539a9ced860661472f53147d0347927f065ec09bc32e00c5bc157b07f8b41b05aa4e0eedd1f73c7a287b2d0e5ab", "Name": "matter_fp2_to_g2_37", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d700000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e", "Expected": "0000000000000000000000000000000016479eca30f48bfdaba4c8afca63ddbf59fe3367b2d3c17d15a5869dd2956fc67ebde964530926598cdcb62cfc993d32000000000000000000000000000000000650b4fd24ffbb953ccdb1b112799149d29e2377ee233b9ac97f4db432da63c98b8aad751f6060d04fe1f9262b75fca50000000000000000000000000000000004568dc0b9b430596f2fa59291ea6f923d552683ab9ab93000788145cd7c468c5576efd981c9ecee2ee0c16eca1ecdbe00000000000000000000000000000000154af1490463930d6b8261aa1d066eeda6d65b742cb53c65348e5cd766d86982a1489ad191d1b126233f193d24823b9c", "Name": "matter_fp2_to_g2_38", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b60000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee", "Expected": "0000000000000000000000000000000000df692ca763a74877352af3609c8cdbc184eb71bd35fd86334cb88543637b40b3adbb5802dcd7b88f4d722b566aba7700000000000000000000000000000000181495e709d1617f2d912f43487ad3920ac5f8e47395ec4b58bcf0b2d986c674a0c7838830a039bfb5bb59cd2fee2f5c000000000000000000000000000000000d20b482dd8aad583bd5d08ba9c61b3e954f022d48f9f4f62ddc9f5015ac71dab7d206b1d8b885d5e605519bd33d93a20000000000000000000000000000000010d3deccb9364ee386eb35c7117bab373a76d024627b8a031f96465d5f75b029fa992e29ad4a170c4473cd1df585429b", "Name": "matter_fp2_to_g2_39", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38", "Expected": "000000000000000000000000000000000a007c914ed40c7f2719fc70def0d4752cbaa775cedae9365c5afb61a5e1a2854f9e1ce19af9fc85bfbfd2c33f5bf095000000000000000000000000000000000d85b0d173c25c2915fee429d2468a9eae01ba43c0f1a661f2ef83c1acd726865c00c40ccbc3aae306f93074e5e7858e000000000000000000000000000000000b3df302ec532c8100c121c9a3455392c713ec60de1f9572b040b0966f8ffb888e8cd768dcf6d63d4835a52d13a730c0000000000000000000000000000000001123c43dda8717d03fbc02fa53c4b1c9a931db6b274162cfb02ef5eec602bd8161dedc37c7f6217c8e82236f06e49e2e", "Name": "matter_fp2_to_g2_40", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e60000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d", "Expected": "0000000000000000000000000000000016d8d9b1b59a22fd830f88b9850576488f75672a87ccb766e52da77f187a8e66071130c7e71f86675f8379b2a8802c4b000000000000000000000000000000000aa4ca84aa23f01ec536ffa25c4b7a6c822f588bc75a4a72ed9237c0588ab892c8474a0f23afc7ff0dbc3b08f8e35b60000000000000000000000000000000001425e759e2537d9e5f0f356ff1d38128eff3a771fa661a839f7a8d0f548347438574ef7d592cd4273ef9b7269c9c5d7f0000000000000000000000000000000012cf1c67d1ce244ae22eec0bf4a400a0f356b9dd075d87a6e61941933872d7c0e42c1d238b2c1704d2cdb2df75169f39", "Name": "matter_fp2_to_g2_41", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7", "Expected": "0000000000000000000000000000000017f6e2743cb30fb93816d0dc802c24509315363c3652b0244e1395cb9200efb4d7b9fa7642e8d165d28a00740f1a83be000000000000000000000000000000001483644fffd3989ac98cea71843e87b8e446a3d497630419afe99b3f1729a831fa6a49bf763b0c410cfc5390ac4ac1db0000000000000000000000000000000018ad20ae5012266d771b2c86f891f498c2e90a7df19561be240319edc1fbfb316948fb3f8a6b0e3720676b076eb372e10000000000000000000000000000000012f404211899d8fc1221ab5b82db9042ad37e63348871e5ac6cdbddacda0a564888f89d22712069b6096b58c5935edd2", "Name": "matter_fp2_to_g2_42", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1", "Expected": "000000000000000000000000000000000d4d78f992f12aefb0e3a6b18fbe2411108327a9befe4a822618fecca4def3169972b4f1fb254cc4656a676529d554ad00000000000000000000000000000000145ef33250240a5c9434d4b2cf2404d9e7cc51b55e482ebc6a8aed85caa21ed00623b3cb2d76ce2d96b2f346d395dfc40000000000000000000000000000000011af2ee2514c58078da335c0273cd18b98d1ac6f0e67890677403f71b0e06863fc72611c0cfba39ac894ae500edbdbae00000000000000000000000000000000186863e7c24cbeb45f7a66b5dddc9b57c7e22c5139aa6bdb82e77cd8182bb8d2fb7bddd7d3516b5422f92e08d02606b5", "Name": "matter_fp2_to_g2_43", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122", "Expected": "0000000000000000000000000000000002af4a301e90c71eb375110e7fe23f8f05e2ede86b1a9b240e8d1d4d70e96f1dc3640fca7ebbcde9918deb91f3592de600000000000000000000000000000000058b5f36cfb6b0adb14b397dee4c3769c7446426eb5719aef4965cde2dcb70e6f2fa60101a5f03517c0040093453d092000000000000000000000000000000000f77b560469cd42c5cf3458ae13020c6678af3cddf9bc559372d12bc5d6b930795e1eb09f27cfdb8215f39fb2a11b30c0000000000000000000000000000000003308985946c742af7bd7d29abc2517ff1d225607b5f11fc66695cefabd8f25e294ebdb7339949d6bc4d98db19533966", "Name": "matter_fp2_to_g2_44", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f", "Expected": "00000000000000000000000000000000093c936d57135b25900bd5dd55cd579aa8b85b9c1b5e8dac6196c4450b624734d9bfc3fda499cedf2e877d79f2da650b000000000000000000000000000000001832306d3ac1c1c61bdaa73c9b6e9c2ccb484c3baa1de6a217a2884c72b72618e864f75fcc2dfaca358181ecbd3347980000000000000000000000000000000002b2e5ff1ee02657fa88c7d6f23cd4c0465152a9daad8479b4b68c97930acb22e4e2eb0011ec4062b8ec46991a7cc630000000000000000000000000000000000712543547e9d24cc78d1c2e3fbe0b51222185f4c6e513256d1ee066ba50beee20321bfd60462e2587c375a0e9395715", "Name": "matter_fp2_to_g2_45", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb", "Expected": "000000000000000000000000000000000f52e2f8dff9a93b2985d5c2b8b980e4869af53ce55aa48bc1c9295e557e3b5ff78896e5e6342c2d535d18b11950bf390000000000000000000000000000000013d36cf2805d350c5b748e639d20e592deb4c5bcde99a94fb539dc56d48a862151b925314f21dce4c9130b32e44f54060000000000000000000000000000000017728f485d881b861f626c9de8b3df7d807b266de6cf8dfcba262f40a6248fb5e6506d11e88f460f0b5f1a1907ae5f3e000000000000000000000000000000000c0ab998f63f861c82106dc3ed5ea11a16e98139e8686f8442047a1cf9ac48c3d34b5129263767830144e9a13d4a1f44", "Name": "matter_fp2_to_g2_46", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c94360000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba", "Expected": "0000000000000000000000000000000003463d887c4d0aaa21acaa308d77f2c7e13d10157efa9ec3fb1586a8db5ff1a9e807c91c86afc4df34c9fcf06e8561d700000000000000000000000000000000128a81efb9f30ed811ea3163c71b6a46ba2cbdbd3a9f93cb8d0f518747cc860431c6e93bdcdf36d00f83838965da4b50000000000000000000000000000000001777802b7c41111b38da3fd8092c280b4925827b2c1592f779a4ddca71f8268858855c413fd5c0057a652155261d75ba000000000000000000000000000000000c88b522d6dc2000cfbb7052e141ddfe15c6cd7fddc970edc4afc36fc59e7f8e31415706a8121e8e84348be0b50d0d88", "Name": "matter_fp2_to_g2_47", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c2900000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4", "Expected": "0000000000000000000000000000000014be96cfc0dbe09155ac8d8233b71ed584153e279b2b2be88471eb653aa4913fd2c33947547c61f7fd8bedbb552a8b1b00000000000000000000000000000000146b9a0011260e2646920894cf405bdebb101db12da7849b30868655fb5f972113cdf2fc322cc246d3dbd9f20b98fe2f00000000000000000000000000000000104bc20e104da5173dcff3e195f80960819a0d64e922bb484c2739c4b7c22535f7faeb1c85188aa853277740b389eac90000000000000000000000000000000019f5aec599f9ec286aefe48eedca3f929ac6c758c231182b92dc965d6ac1f3db53d93f57d733ca8425a5dde070b0dfa8", "Name": "matter_fp2_to_g2_48", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa10000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79", "Expected": "0000000000000000000000000000000004ca0149527817b4df0f08acabd4e8c6329c0d1bd9f2e8211cbea25d69b84009ef158c770f948fd67e4609ccadc938680000000000000000000000000000000004101b351e2a9d34042291f38a289d8575872104bcf76f60bf888c60cca5101c34c247da30f7a8db4f0cf2f32abd302c00000000000000000000000000000000167e668de3207ddc60b8a5d5d246bf2f63ceae3bcbc4309e73eebf4d4234c2785bb13e4d5d8fff9c5f205e4fb942a2f6000000000000000000000000000000000491b965ed005065abdac53e3065781f2fd23f6159debc64f01c9f62073c651da33c05ed84617efcb5ffe08ce05e3b2c", "Name": "matter_fp2_to_g2_49", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f2dd27e3f0ab503a8752c0802ee14c655271e8cfbc734905b4331fb4e70cdfe291ff71053fbaf91680b1dd108f458f000000000000000000000000000000000c62014b7694a3e81370761e0adcc32430547a1bbe33746637e7762dc24f8d04b4bb955f17ca901659482c622d777642", "Expected": "000000000000000000000000000000001541320fb6f8a8c3c67278a7ad05ae7927d3555ad562bc8addb54c6693c51fb1c7355d2e74ff10f6bc3eb182d8f5b88b00000000000000000000000000000000172b65b110935b116ee683c8680ef0a660afdee43b9b8fce08ef3a70b352f8710c06b820348c338fb903a165cc5376da000000000000000000000000000000000df529b0e274e2e8993dd89ffef487aff23d31f502a19dd7d383de08fc77f1308a59ac5bf7cc899e81d377b2422187850000000000000000000000000000000010b40c9063d174b358637ab710d15c80d9230a1b3a056cfac4d583ad8c5b79c3d9bf22a1b0a4e0f629cd09ff7586f886", "Name": "matter_fp2_to_g2_50", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014d1491a45b4b0914a6cb2e4dc7de9d0962f5c175cd571057cae1e17d2c943954d119690ea14f5815f858d277a9ad828000000000000000000000000000000001650771e0f7b33d235f229b7d49a7a5a0f00f78e5f4abaa70f39ec452370198a8532b5873e41f17c449f9c565e6adea5", "Expected": "000000000000000000000000000000000978ff68d94d33703488298658cf2c1b6034d3d8d21c175d71a0545bc2f99eaaf131f061f3e4f55622668e686e691f53000000000000000000000000000000001124804b252f8187178435761897d00c43cf67b588ca69f97c20b0ffad3ed94acc2c0f85f900713dd6ee9f38e5ca94490000000000000000000000000000000010ca2a8ce71b9a096c132c4a060a17365475b6556d4fc6284266ae787e217b3ceaa3a32bdf751375eaf6ab49800132fd000000000000000000000000000000000a43b435b116d9480497f6b2e1bb377550cb1a7ad59e4214bffacd517afc6b7bf91112fe57b17a02a86876ea07361bca", "Name": "matter_fp2_to_g2_51", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aeb244909654b3e1df7cbeccf297223be57c2f514474edf0740dff48dcd5898b6e49eb65c787aa56ef79778249f4e07000000000000000000000000000000001007c89a66dab07f54313db8682f9e829baea229b030b4514d9c93686747207939c50a198e83ac2cf50315e02642a24f", "Expected": "000000000000000000000000000000000c3d87b1b78fab65cfc853304c682b39b6ec2b4ed005e9108f69daee5aecbd586c9818c37cdee865ba53eab9302320ce00000000000000000000000000000000062a7203cd2fd04a957cac8b6b6bb51e635ed7165c547ace10f93a32b7f37747a2e63d5767d966684409a6c748d4ee6c000000000000000000000000000000000526b44af8157dd68725aa8743684e020c1e385af7413c9dcebb320568663d18b6f29edea26f2628358852b794ffcc8e00000000000000000000000000000000098126f486ff55c21f64421e85b09a1b54f42d3499dc0e198db6f3bf7dd8476cad97c02b5b366e5ea20d8f83cc223f7c", "Name": "matter_fp2_to_g2_52", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000398d86b5206bae4ceef0bcc6335b1f6bf5d17863ef3a5e8463aaa69d9f73f8227263964659d4b770d6d9813f9399b9d00000000000000000000000000000000096bd18be1176e16a0d80e60f7d7ec9d3b6162f683440e3cde70082a73605da3783c8a058bf76d7e25056f5cd95c31ed", "Expected": "000000000000000000000000000000000f3e76e7d1cadfaad08d16457b02d89c40c157225eec7916d306faca8dbda008f41792888c647dff1acb4d4ba3b43c4900000000000000000000000000000000132bf730456e2afe745a58cdee689e37223292bf682d5b7dafa7df99e40d385559d0b3161bdda0bf5173c43ee46412dd00000000000000000000000000000000141b36ff6890e35db0054358bc0731b3aa0efac1a247a51daeff3515746456216975f44769174a4be41c109d35e4be33000000000000000000000000000000000ca401ee1addff8fe87b600e057ae34ba297886f92c5be8a8c00b360ada71831e31bc4ea1c309c7da31cb28d1011ecad", "Name": "matter_fp2_to_g2_53", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004ca5cb60c32edfa385baa911ccb7fd1f383824c22b945944b0f3f7011db8c123efd8fa70e4fe699d40c6716021f0151000000000000000000000000000000001339adb0dd8d83574c2008f0a7ed001b0808d2fb639b5e57e1d293884247d5c66c948ecc60caeea7bf440a3a44ed296d", "Expected": "0000000000000000000000000000000009d0af77517b654ad97de3ee1dbf69ec1eee901facd0f8c39b4af393d0e63957292a7529b461f7fa58909acad32ba3a2000000000000000000000000000000000fda17cd878ec0f8c294daec1bd1d56c63e875b002a81c9c41146dbb564bab6e4eae2717c9fd718af1ba816a1526e8fa0000000000000000000000000000000017563b7ff22b50b6d9e24b1e0d89ca5c72e68d4d3cc24cce36856191111d087c3dfb392070462dc7850ef5a1422931c600000000000000000000000000000000020001fcff638504055ba35230b360e6d3cb5777b959c194d6f9b038b58d3ead0b82b28bb215378abd85d357b85ea260", "Name": "matter_fp2_to_g2_54", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000089211892a61202b1ad3a85aab9f08f8d028f3e3deb16c1de4d62c1a403fa63c6dbbdf8cec37f0a9d6f346b1c7ee179d0000000000000000000000000000000012a9fc2070b326f4d7e64804b3a2e977f4bb36b6a4afcf27252af757d8535e8172a99dc909fad5a3ff8df23d6d6c5948", "Expected": "0000000000000000000000000000000000d51c77c2443f00d965c0d7ec9b5a8a8003c2a77b0ffce3e47bcb55420e8690a9c2ba9235b62a4b351d79d216a3aad40000000000000000000000000000000013cd46e3ee6cbb3bfb771ee30b5f5faf0a64a9efa1f8fc57024c83ad07a9b25e513f211ea604cfdf319dc42bf4c067d300000000000000000000000000000000009fbe1fffc67220067c948e0c80de23795e045fbe8031c9010eaa69356ffd8e5741cfe12731ec13aa236630f1b1dab4000000000000000000000000000000000e5ecdf808d10d47f041e4b078e79b32520ce9623b50059a3bd8b59daebf9103c31425659ecbaebfb2384d1c2f1b400d", "Name": "matter_fp2_to_g2_55", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b37365748fdb21fcb46f94edf86c586f17d0e042c4683e68c6cb83e7c0ed2c30ed260c15af2c9dce77bb705debfa7590000000000000000000000000000000010d7c02c6c1ba3cf6ac09a05dfe043905e1a7eb32b67f2e8a5dfe82eaca66ef46cce43aaadeff58ca85345dd0d3bf3cb", "Expected": "000000000000000000000000000000000f3e4d2559261829c0f4816f8b571170de1f74d75d74997cba56fdad42932db73504691f9e001f5b4604705a8c1a38e40000000000000000000000000000000018c72136bc7d3050ee693270668e706ebf70f990e447ecc6153a10625cccc9deaf5ae82d2a656b1376bf33b1c1fdc2c9000000000000000000000000000000001754f2725bfa76e92a74ad5b520ec2aa82a1f86e8623a054ebba489adfc9e71d1f14d4692ff9fdd8acc3d768b67e1b7000000000000000000000000000000000096f1373434a8822569cba0679dbd2abf619bd9a8c73e54e078688d4e2615d45431ac8cf3da5e15a83fe77d14b339e49", "Name": "matter_fp2_to_g2_56", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000aeee59421c8ee65f8070b9d036e6bacb39dd2537d02960a3a57da4f0985cc7b27784d60fc1613f5a83c34d2250395c1000000000000000000000000000000001715ddcbaed0a05b38b1c820724405a713cc0215a4c497892f00746c0f9af28b440a3686178d9bfcd41944a224311306", "Expected": "0000000000000000000000000000000018d515b8c99f541c7dd448c3564c1909b84517b662d6a2d1176d3bf5e70abc0a2995c73ae3f1614bfed2f64229e173e80000000000000000000000000000000012126ab671420933cc4fa9206311200cc5241ca3eec54f5d97a426a72642bdde32a65c79735446779cd1744d112d544100000000000000000000000000000000190d836312ffb0d6bf493f4c942263922659abec46ac4de639efc311753148b445509f808c2fd813729b1bd96e0e663f0000000000000000000000000000000006494f9a451460ac658ec17710bef79d59b6e0fca049804c0954c5fc472bbef520f75d34408ccc62cf2da3deeb79acc2", "Name": "matter_fp2_to_g2_57", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ca4b3e1a8351057ba4a2ffaf0cdf1c3c0717ccfe26433f6c40e2cc29e32ed884f63d25979580fb555a5a86c9147bcb00000000000000000000000000000000010c1db593af38aa14ca9dd588f54b219ff1fc9edd25b3d16c595662ffa7939879244326b14d978e0dfdd25e37776964c", "Expected": "00000000000000000000000000000000173fa567aa952bfaa9a60b8232a185475cbb36761ebef49ea5fce900a06043d0e2c1b6024e40eadc9f4bf04b077201450000000000000000000000000000000010fdc32ff84f79fe39351cee1ed6b67dbcf2956020e2518d5bb5b367b61f86f1bce36f75516d9551d74cc3a567e6c2be0000000000000000000000000000000007abdff8a8967eccc4de6b4ce142173841c0e8399f5a67dcf0f7b5e5b4133391b44bf4d41d3ae3426839b19aa4c5d40c000000000000000000000000000000000c99f160062566418c09f10eb80f005f2c8c12825435f354f1d65bec0322e9b8ee968c009a84ba792a7ee7334b32bb3d", "Name": "matter_fp2_to_g2_58", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017cd94e7e672f0dba9a3c1db742d87cb18b9401e8311c4badc24f811a8f40c27942da6485807701c1a12da58076c756b0000000000000000000000000000000012f6de4ac9883e78f9d658cede4c70b44bac6b4c9734cbf24298ddf0df0cf54164aca245d8e313be4aca66ba3cab5d70", "Expected": "0000000000000000000000000000000019dc92f1da66d0855ebc8e7a2ddec623a2f843a97c7385364a631671be7ee3387a0f98940b5a51c8d9e23eb27e3133b00000000000000000000000000000000008493903c5c68b2847869b8c3b0fa9b8ba15bf1f11a40a29e6e82942e2910901044254cc8e8c3c3bf56e1f1b6dab7e86000000000000000000000000000000000bd3c1e302a191094059a6493e59a11ab05a49faf333f36f7680ec9b1043e59dfd7f0fabe9f334b97cd638dbb8bb664b00000000000000000000000000000000141c9b07ff33b6ab55b320dda6be54320082f0057c446236cf3d3a51e674c26a5241f2c702d9989adbae9045942eeab6", "Name": "matter_fp2_to_g2_59", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001b2843d9852feae3145b242cd0999877c07785bc72cc2626f388dca32edfb112bb90f9aefd6953eb15a0babe748573d000000000000000000000000000000000a69bfe809a67ee853cb96b5a7a72798748cda56936e5664d509001544539730f57a7541ecd2396c9225818b9dbfa3c6", "Expected": "000000000000000000000000000000000d0922466c358cfd756727e134b5e64d211244587e4eea036f0959e78570dce3ee264c703cc356cde20637c7560369340000000000000000000000000000000011a66d618f79fb662ac2b2d3b50750a5567e36d7092dfcc72d8f340c04df75ecc0ce4a01b410ea775dc548b8dc66c3d8000000000000000000000000000000000cc49cf4be5e2df6b43054092afa2d6acd66f5a43ef0667f6a2d660beb7fec70558ce02d7acbcd090df91fe833326718000000000000000000000000000000001270b0519db083f903a3dbe0b1b1bd5ce0b0059ea2c2c50335dd80b4bf154fc23a3de1ea753b0e279145254d8e5bd045", "Name": "matter_fp2_to_g2_60", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc7", "Expected": "0000000000000000000000000000000007826398b4ec35ab58ba9fda5c15ada2a41d3854677172ef6a4a54087b64d0f73fc875ad62236eb7fdcbd94f14c8895b0000000000000000000000000000000016b14fa92de5f6e43988829ea2f851746efd6680b0ea1283264f803c8ffbe85a343bdd42225caefd1b94b8b311d2f4950000000000000000000000000000000018797093ff82bc10e6db60b1da50b9a60da01d67673e9bee8c7af2bfa2d57f409f7b06f53944938e5c73b049c2d3c6500000000000000000000000000000000000c66dcc3d30f35c21b8a9369c8f6de28af404e8b30d3c9a7f09c461b0272ba6d5a29e716012536dbeac1d9672af8427", "Name": "matter_fp2_to_g2_61", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e6fcc48312831b910e52aebbf19869b3b32f05db8310b68905bb244ab784df5732db2e72183de5d231d803d92aacff9000000000000000000000000000000000f61f9e52fe3afc2a6bf12e420cebf83bc55a449d6a167779e5b6ba3281c51d790a045643aa75f2516eaf6ae2a816ac4", "Expected": "00000000000000000000000000000000191aacce60a1a83f2c453fe196bbe5839a3a1178b147580435f7de8a2b0b4f65b3e280ac7a67570aba0fdbce6c11ad9700000000000000000000000000000000075ddd6b256f53a6ae6758a5158508540aa99b78ca069378f0ae3f5621ec24b9acff1f9b61d378334a63682a33fb0561000000000000000000000000000000000b06e11c9f858446fcc90c69d05cc26c33bafed0feda19adbd838c9c24bbf567b673110a1b248d0ee97fc682e561298e0000000000000000000000000000000018c75dc203493e12e1523af50f85ed648130ce5d3e9757f713850c867cc95c7acbb66c9733dc4f53d6a0e64bfaad5832", "Name": "matter_fp2_to_g2_62", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018efc6d366d79a09b7d56c81c17c2eec2ef7395fdb5991f41273329cdcf4537d342bddd83c3994a40d5c18f6afa054c600000000000000000000000000000000127021ce28627a9d6a492720f728acef3b38c12b951f70a932c7fc0ce3f5b6c80783351cec55d7d1bc4ab964bb4913b2", "Expected": "0000000000000000000000000000000012931f51430bea6e96f8ec456ce6b3c9e058b0bd3bbfbfe8b6e84fd6110c3bbbe0001018064e8981797f9c93713a0e4400000000000000000000000000000000196b6093dd2276098853ef2bfac84f0cad06b67a12484e98915dcc756310b818d8136954de1b602eb825ab29a143cf4b0000000000000000000000000000000008284beaa877b25374571dccb218c401cd905b351dd96700853f01920e409d11c4e440e90dc175cdf0fa807cb9d1e93a00000000000000000000000000000000063c6c238485c291fbb60bd2824154a9e23dea374292966d271ae94875391b7ceeee813e3fb9504223bb86f0ea3b6cb4", "Name": "matter_fp2_to_g2_63", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a0277228ab4e880c12f957a6fcdfe49e2155083f3f93d3f00c68622415cd1f5bae183b7df9e08328a8139392772cdc6000000000000000000000000000000000de0ab426e56029790a5ff72f34da97e11c028dc5d31e448c49ede004102804d2bcc36d509640247a4c8bfdf5104a781", "Expected": "0000000000000000000000000000000000f7bd0705cc4ea96ca38314cb85963044164b83a506ffeaea6e5eb8f7c4967cab1f1658f33b5435191427aaf9605bbb0000000000000000000000000000000007a93e2a5c118aff6ceaf2370ddad52a82854946ae595d384ee0b2b4935a574ba758736d84b0ae792f998ec6a707dfbe00000000000000000000000000000000090936add00fe5c7556610b28ecb4466ffc37b95b5cab43e072a585920b3cbe70faad01ef75d1dcb4f7d00d900bd99600000000000000000000000000000000006ae82539c68b7af3143e23229fe320924472c2b3e15a2e27e94cba674d30f083dce94706da094435c53285a43f89e56", "Name": "matter_fp2_to_g2_64", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000170b243c5aa49a0134bf3d6494cc1e55a1c6ebefc6117eca3b343a48ef0a2b077c443ec5b9201b198df015a38e66b7910000000000000000000000000000000019a8ac8a3be1d45318801bb0a654963b312540d24aafec46bb7661cebeec27b0b345275fd53c041d02b1ebfa27fc3854", "Expected": "00000000000000000000000000000000024c1b869fc13191b71d7159a07e869f1b13c11c73231b82e9bd0a7b4c32d7b376fb73d54f7231dd4974713179f235140000000000000000000000000000000012b9f95af661e8452aa5026302a7c28695307f75e9e4e32365caf378ed394fcecc831a3c47b443172188f4d18338fa75000000000000000000000000000000000f52675fb4d112d1d39ff953a253b22dfa0b73d972e756ea7fb673bf87aa992883c5baf32be6f50f880b03dcb740f06c0000000000000000000000000000000008b57726e17c873e12834dc291cff6bd95307f50e7b1d0caebd8c1eeb6eff4acc0520b135bc8e35a257133b7dc640db2", "Name": "matter_fp2_to_g2_65", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000fbbd5a10eeb2f358f2b167f1985d4084c4b12accb1520d780ef1c52f6fa80e97aaf190e7a7b241ef96fe8289fc0a9600000000000000000000000000000000155687114e7aa786ba27aeada830fc705aed069c4e3a07e88d7f33923319f416ff3caf6533cbb36e5bbb1b93a191bfd0", "Expected": "00000000000000000000000000000000061938df3365bf910884ccbd74d3cea7c30416bddc1a9b65e7723c15d89aa657da36a45fe10ed50bfa0c2769bb98aa2b0000000000000000000000000000000007b3981054255715826cf8f247210521ac681305aad3928b69804117fc143c5101383eab7017127c8452a79003a857d60000000000000000000000000000000004c745113480fd87212ed3ff30ba43c8716b32e62c1f0091bde53bd4a8fa8fe6bbcf0904144f4791ed1bf12dffa1f17a000000000000000000000000000000001237ba297c7f69e5e240846a12d86c8276a9a6ceb4af977edadc7ebfba3ad3f4ecc0b875da0ea578c83fc3b91f9f31a5", "Name": "matter_fp2_to_g2_66", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000115edef357ccc3432226d9bad796a42b1a278d9c8adfdddc5a0f8a36d32ea1787183877f8b7dfab71424cdd10b63441a0000000000000000000000000000000014b369ce61abe60d942346e049644b95a0fda96316446b2fe7ee427641af52fdd2a654bf125ff6c8c7f3dec98f6cbfb9", "Expected": "000000000000000000000000000000000a0cc3e328b4cfd01afe53dbf971ad78fc74d951050d76210e4c84438109622f0531747e762e185e3d7ecb9faa7c3255000000000000000000000000000000000622ad6092caa727d069b8921f4124d5996f3019705a908ef95d23092c5bb148873a22c227aa25ebee361d4184cc38a10000000000000000000000000000000002938d2ff50cffaab8c056c2844c50013f5bcdbb4f91b3f823836edabb39ba17ed1b8b5862301efad04bd2f5d5bf599b00000000000000000000000000000000072e96136afebbf8c06a37cf9b20c85ef8cb3f7f99d5c71b05a187c193711e5b76f52863c7ef080a1b64b2120ab2ed84", "Name": "matter_fp2_to_g2_67", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d22b7b36ac66b10adb4570f8e7521ed76de2df2a7b94b2d0b9ee4514cdff6fa7c74854d16e7e70f054a91df93c7ebaf0000000000000000000000000000000016867c9cba66dd9f1d0332d31c4e46f8e393eeeeb19af7e6e01effb29ad999b3086b599ee4b371de557d4fafd5da3794", "Expected": "00000000000000000000000000000000142ceeefa9fceb903b25d4dc68f6755833d7529752db0f125f7f65f2b7aeea8c90e599ac409576e82f7b9d6f83c43aa0000000000000000000000000000000001664acd89b482aed04ef40bd4d1ff9f39c80d7738771e2b3ca731af01aa230d865869cb05d83992e94ad99549fd0b8550000000000000000000000000000000013d6ace9b492c014d9a7504b5abe442e3bba13b1ada454aa53177990ec44f616e091f1382d36db87b7e794c11570a9bf00000000000000000000000000000000081b7a8a2906435f8a9242f573225ea62c5429e903bebda9fe9973a18ed2682185d72aaa6584b9848d1cc45ac907dd27", "Name": "matter_fp2_to_g2_68", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000db9258e1257e659e60bf8569ea90c8247a53a1d1eb958481623447a38d0f1f1686c3e40c8f15bd06cf5be9c02485452000000000000000000000000000000000517c87f3df032ff08d960f524939e66f7fa69b4168b0f2507baf7d7231a70dc5690a02d317b26f219365ac3255bee78", "Expected": "000000000000000000000000000000001182e4230f0c360c07913349f89f8436c01841c9615348a0d7057336c7483342024b0369ae52f39d4582f9885f552b5d000000000000000000000000000000000d15433ed130163a85f8ba87468c906aba88ef8610fcc1a8d6b3308cda29907acca351fd7fb19799184f1ad91c751b5e00000000000000000000000000000000111089005c4c5370863b0ea6b629197a865f978f71becb741f50f9b4e49b13162ca63c29aa26287faa9c923f57f4ad4c000000000000000000000000000000000dce405ed2a79ad433123105ad01a26ee85d1ba4e5f3b4e0339fea787058c06e9a6b10f5ec8f6eeb85b211e18b6ea076", "Name": "matter_fp2_to_g2_69", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000b6573c743989fc8613d4ea09c2e500ce965b50cf0c8975ff703116464082efff4b42828c8337809f6938d7cdd3f66e000000000000000000000000000000000896d316629c80ce6e5f240535863b9e064728665c0815f39b21675c236f6995e7dfff1e4aec9ad05861e2122469ea56", "Expected": "000000000000000000000000000000001694cb615d2994a903a13645ad44a63395320f286503902b6009e7c795dc8f024260e0c45bedd864edc9fcb9d1ca6bc1000000000000000000000000000000000f20538af015bd6d213f90fb1a1ebde4d9e2ab2defaf80d791a1f70af2ca7ea1598d43e9eef1cc982f468cf15d223c9d00000000000000000000000000000000046c62bec4c6876a67f5fe68107d677db8fa4d59ac0cb7afe6e706864c6e94744bedac6b34a68e8ebf89c231307b86d3000000000000000000000000000000001839f3b8a6dd8fe8028247670fe5b491bb43ea8fda53116dca87f97da96573a5e701a703fb5fa7bca457ef88a827e061", "Name": "matter_fp2_to_g2_70", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011fd2ccf6883b78fe19cfe7beded503cdbe1cd5dc9ee452aa6b329d2237c2529df6774334b132cfeaa616f20335f88680000000000000000000000000000000009eacceef036ec500e7676f54af703016fac93d69ed19c8943b64ffed2db498b19cd255a0a3437b568eade0f497c7b17", "Expected": "0000000000000000000000000000000009d8725eb8757828a94969ebf40545a62835686897d4504a66484a3078b2f15e39fe918d8dc01bc7560dcb005a7a0dbb000000000000000000000000000000000954a6cc9b2dedca1cf280f72fd0625184b8f83b78ee1ffcaf0f9178ce97900d759e4a74b914c3ddc32f84c3f4c3a8d60000000000000000000000000000000014121b83d2a06390ce7359e570e1593d5ff097cb0e44c38bc74171fbd8a8da0dfffcc2bcb95fb2d80a55933f696a86cb0000000000000000000000000000000016f71d24256de70618a02b0f016c6f31a21d2cc42855886ba30176584a028c2e12367be19b834bf41356cdab21223314", "Name": "matter_fp2_to_g2_71", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004a851380536054f6b69ef7581b57dfd753d1e6201069bd1218ae5766aada087b4b08f65d43b3ce0215640e8d34633310000000000000000000000000000000013579671b64f2d9a2c3ac2737cf95c2148acce3dcecb3db6d019730010c50d1c0504ba4ed42d93771ba296b0b07487d7", "Expected": "000000000000000000000000000000000cd47f0982904ccaf4f3cdaa37091a08e67a5f04af09033b864631300bb6c2aacbad105eca6ddf68a643976fb555d3d80000000000000000000000000000000012332ddb0e91f0ef9e085f21634c6d69576e60d3d24732a0c91a560906791f60f79d09ac0ebf448bd39f047b1dd428450000000000000000000000000000000000a756a869b3cbc5624f0e08019170beda35fd2642a79108b284a503942f8267b75868636302e5a12b4f1505331b15f9000000000000000000000000000000000f60724f6c8200edff41f3299ca003e9ea03b97b01a3e8c63763bdf67b9f7677331a7144915312458c40d041be97b3c8", "Name": "matter_fp2_to_g2_72", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000021dc1dedded9b0dd90afa9ab7fa8f9c33930fe4ae68185ea4cce9ed97ce4cc9ff93f96377b11f8d42b02e759a10b06200000000000000000000000000000000034c963fda3bb80043d6d7887661ad59b3c31c88c958b451a8e11684770c132205db6655ad7cbd604ecc3225b0c128b0", "Expected": "00000000000000000000000000000000095cd509e53f10b1ee18b2120e2d18f0905a202a992a9c62480beb6588275fc8b5b151e6abf17a12b6d9cd03a8b37a59000000000000000000000000000000001723bf1a3d79935eb4b39f7feaa1e05cd8f3e7a32e2c406625053d8d8fde33eefec231ee00adb00b0acac16a83dc77fb0000000000000000000000000000000004af528e886dad3f9fa7232605936bc22a6a22622828367791920ec9d31cdb2f290e37f5fc79efaeaf96c86b3f6e39220000000000000000000000000000000015bada14a84fdb09b77397cd2e27836f9f88854924af0cafc6f9125d32be848c8325a3eee1a26de8be8eb80b601f1ad5", "Name": "matter_fp2_to_g2_73", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003e8d1be04f8dbe5c7e1c7553cde8355ae16d26c819dea92fb543cbd9fe9e726359e1e4be0483a7720343f34c6a3fb9200000000000000000000000000000000062bc5fdae812802bdea09e4130c3d9bf80c7518138b116a4b6a302c155b97226a6ccc8a3ace18744e7adece08781f72", "Expected": "000000000000000000000000000000000d8f14042f36bb377655b63dbc37c50e0eb5775d4e4399972a6758cdfa9751cb4b733745ed1a47fe5f2cc434efc5af81000000000000000000000000000000001384016829d028f823e6d062898c042a461bca13ae4627c983d9b5c9e8b4ffff7eb25daa1c52b39e309b9c1e7e4f2e920000000000000000000000000000000004f7904d491a0c2018b1361a9cfec4fc829e607402859fd9b9ded60adcee51e9b522d302f9064130a4eed1327f49bb4f000000000000000000000000000000000ef4fe949fca569b31fc57ae7d0166ea53318c5712311076e052c2967144116f5490fdf56f26adf64aa01beb4f6cd214", "Name": "matter_fp2_to_g2_74", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000014b922157b19ed9debd9ae95cd9435f7980b8d4ea33fd29f99d5e7fb1a96f6d99ae13f7f86239c4bc286c3927d3522a000000000000000000000000000000000f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1", "Expected": "000000000000000000000000000000000f20b3a6505784681331208b573d3a241706692db71b5daf4e9c80adb1fa9bb87023d7ba7f9c65158653c735dee9dfdd000000000000000000000000000000000f7f357407ca6cc5c5fae4b84509d71b2f4de9af226cb4038b4820c0541d4999b7396608efd2f322a00a768129f9800400000000000000000000000000000000138dcc1b9d978adb5eee6356980cec5d18cfbfbf18cf6fd14f4119a563f473f5027af06342e84ea858223ed63d1a16af00000000000000000000000000000000012b63f0d2e8ea361d55aa617a99e066b5feef3af1930b83d2a48b527e0ef304ceadf7cba1415db80c54fdcbbcf66d14", "Name": "matter_fp2_to_g2_75", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a4d00000000000000000000000000000000051c6f0ce621e8e27e5017628690fb68f0fea27d67726a0a77b0caf9f524936e123ff096168ff2079b9990c07fa80354", "Expected": "0000000000000000000000000000000015ff2aa94f802d8f9c60ddcb43aee598239cf3ab7f90f8289a487b673f6065f8d9bc92bd4cd28df4a7b0d3bb78fad243000000000000000000000000000000000884b5d4ca3c8abea737cfca05878528890b6cee9bbac0bf027df5d4e0add431829caddf4c1e001818581ce08686eeed0000000000000000000000000000000019b91a7738fde9760240b335457955e963030848e85717858f22dc33ba5a4721156cfdd7341aa86d10d268e2fc9a1d26000000000000000000000000000000000af85e60161795906f3cf705f5e8cb8c15083a90836eac78445c6bc27ffbfc8c2df3009b436989b46b271dd8d1dbc282", "Name": "matter_fp2_to_g2_76", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000094e958d9b7dac39fa4f0143a333b2ccee09046cd23e6a1c0712470a3c2e61d2f8b85aeca37350f71d7ec75aea2b3b6b00000000000000000000000000000000080743cdb5e359e8b8ad3485d53ea286669ad66d841945296edf80dde77b20a158e2c1529dfc33a1fbecf177d75a0c69", "Expected": "0000000000000000000000000000000001bd1fe6a6c373cfdc2bfd488b0c942492b77d55b2560824edef3a91c711ee336bc1366690be40949d04edd39ad48a7500000000000000000000000000000000161476946a5687113c74a34284f49b0658e323fae57aba88b039eae584d6ef28adca669fb083a2fe8f0ef664eb5b957d0000000000000000000000000000000007aead870ae09a04cf9c9fa49d0888f7010782cdc5a0ade4c1340ff15d99cb39b7412d66d4147b95601fcf5a39c39bca00000000000000000000000000000000095cce83dbfec12973e27627bfb2d93fa9a027a2c2af4259a0879d6bda055d74559fc93fb3b4f6b0088f702af29a7643", "Name": "matter_fp2_to_g2_77", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000dec04526dbf7666d2c29db5de1ef0b3da85380a171d871a57ae3df364d2754fceabf9d4d2a3da1ecd94e377abc78430000000000000000000000000000000000d19875fe988ffbd0cf1e9bfefc7019579962ffa3a585ee232615e4a5fce0a07bce0537b203ea00010a90ec05d5b8de7", "Expected": "00000000000000000000000000000000133cdf684c3ff1cdaf07ff787b57a66c215eef06acc2aec4d726a086480e7b2a5dead2cb357d99e298df32d4c6f5029b0000000000000000000000000000000019cd65b830fb17880f40e104ed63a7d49b0fbad8eead7502f18f1b9f85f3f6ba6c275b8a242effc61a7a5d770a4fdaa700000000000000000000000000000000039aeacd163862e476b17a22c76042d7896a04f158489ae71afdd35d27106a3ec276baf5c08e3eed4b3f0a79c3c458d200000000000000000000000000000000125a9bd770c1fea2155a581211bd71d55eb1966645cc892a05d32cf1e4e5b23278ea2fb1336bba7f2c887debe4a93b52", "Name": "matter_fp2_to_g2_78", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000016dd03f0df71b183e42cc29c665f18d57637b65f05df85aed9a0f7b8aa37f7913f6606c10f24a7a8c570f485905583a00000000000000000000000000000000161e62d8be678a114fd4a99a6caeb9481f5eaef145571152fe8a6ed77a34c06a1b6ff66044102d19a94abcaaeb254e73", "Expected": "0000000000000000000000000000000007843268081f61ad2b3f6653336a99086381bb4da4c23b7d59b9c7827f2d4c196d136508c8a1f3d2f939e8c9799b95e10000000000000000000000000000000000e2c57ad95f762115d8230320810a4ea9978e26ca17decd6af4c112789608967a92fafe3fb3e79539d75d1c0bae97740000000000000000000000000000000010951c9839db9dd6ca5ef95bd1b1b9cf60bfd97cf88129fca23b24f19c9d5c71486dffb762e92f23d2a9e9d462556f620000000000000000000000000000000013d35c17b3763fc5db46ac8c44aef996f3f876c49f5278b7c97e844f23ac49f2d50b3af080322d30ead873af7b4257e1", "Name": "matter_fp2_to_g2_79", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000036efffcb0c6f42109bf9b8b7421e32fa3f855373345341e6000eccaca135ef3b2e1c0151bddbd46ae92185acb847d74000000000000000000000000000000000edbd7a40f3e688eaff5e29800159b8d799df07e81f65d59011e84329b4689a28a15ce11537fb560df705be26bf14b1e", "Expected": "0000000000000000000000000000000001aa1919a50b5bad62b839d672d5a11ad345fcc61f75eccc42990e113deb8a486423d1b27e7c81536d8a5799986b9408000000000000000000000000000000001879295d2f7bb3923ec61c063ee4f96d7d7cf7786259e2f4cbc3ccffe7e114af264b3527a5e06dcfad50ec1e2a9c1ae0000000000000000000000000000000001042632662e406c95f3fd44a6d956e526907147e7e6d4219c1c4b28a31e479974d00d4ad6e683f6a834d3d4a20830f4b000000000000000000000000000000000a29ea98ec25e7827bcb349ccdb2a57926809f3cce44d5ff6cd636460278c8103b0db78fa580e9edd4ecd0bdb21018ff", "Name": "matter_fp2_to_g2_80", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000974c7d17cbf91947ad435b30ad2b639671a43d67da6a4edc7f8bdc11fe817d4d42f687dd642a2be89c81bc36e8df592000000000000000000000000000000000efeeb85860877abdabae35672a77ca9d2cf0ed18ed209fb905b591a841c900ed06d2c32c56bed5f7efd469d369b05b8", "Expected": "000000000000000000000000000000000c67498c6751cc27d871b8711c4739398c501a5bfb688d7e1a73dc7db5c47c3e28b633078cb83745bf5b0d5d2dde3ce2000000000000000000000000000000000c205c03305422bd44082715b90e0a0ec178003d6f5e14a0d13bb0f2c38f2270816b884b4870b75db44ab080f88a35e2000000000000000000000000000000000257f378935772d326710ec6efeb22f8c9b6b549c8a4c0205b75740047d750d73da4e71aaa8ff33b9bd8ab7621b08e62000000000000000000000000000000000c386a15f09c849be9f449a59e1332a1e7f16a9394c8de198c01399a05b0f963921c4c57d49916407ae0d202af8da32a", "Name": "matter_fp2_to_g2_81", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015333364f4d0d173ef35e447fc189b9d65ef71b9fc4ecba25fb6c8c1bfe8467f26bb9c55ef10bb34125d714b94aa1df1000000000000000000000000000000000cbba9d8ac191032f03c0746f13108962130c9e2c01d47f01174a4c4d3daa7631268f7dcc08dfda317bd249fb6e73e8a", "Expected": "000000000000000000000000000000000864da537fd94a9ff1bdae733f01e145dc97a894733d0811cd67c2648ba61d0b187241f9ec69d8c011f514894a05a608000000000000000000000000000000000a53ea4ff9c0ff71541ee21127a33daff2b39e74301946a86e51dc7834717e7d8784cf92fa5845bc0613b6b869003f58000000000000000000000000000000000582f5a1fcef3067dfcdfabc6af33871114538abcb02fcad761cb496020c7b423fc52f0075916f160fbe03574df97ea4000000000000000000000000000000001244ede8ba0dc09aacdc5d9f886e59bf963a25885dbbe2c3d1f611bfae82debc556ec4c94f0606492c7b8c7bf976ec34", "Name": "matter_fp2_to_g2_82", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000781e980c167c982c2fc8d0baa3907bc5499eafca675ae20a10b25063c9088fd06f6769df505e5900bcaf99e266c052c00000000000000000000000000000000183c12798438ea92db75d5bf76cf29d320fab3653e4131989205f2817aebcb1b13f161864c084fd13a11459d7d5ccd92", "Expected": "0000000000000000000000000000000016c334aec0e19934665596f0ae37eb398f1d6f0d0c9f08189f1ccc219230395124a9da03858bdba13ec5366da54228af000000000000000000000000000000000b156ea34ae7b5c252dd90997f1c693773a463c26935a69bcc0599b95bde9e6aa31649c48b6ee4ec1f0a56b19273a5170000000000000000000000000000000014b2d69e02418844effcbc0d564b2721deae2872cd1f27f61d544fc0ebd5cadc77c6777ec944ef0500db181a5443618e0000000000000000000000000000000004f0d48a25c1eb81233f385af17ab6abf554e1285b669eeb5e884c64d5815fd5fa1350bb361997cf2e317f7c5e9cd19a", "Name": "matter_fp2_to_g2_83", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000879133a3c0e50c90abf1a6ac75bbeca1121c865ef39e9224ddb160eb725e0850a34aaf9014e42174966773e40e4c99a0000000000000000000000000000000004c66f8f5bd462cb27e9f5e1d93e322bd97582b9e81d07b2877103420262e4cfe6d0e3bc07f9f160701fd754793eae33", "Expected": "0000000000000000000000000000000003c0d6b721cee4e5fdc6a02095674a58075f81b1d28163f81d5b258c82634297009e6bfc8193969e23e196cf7a99ad6c0000000000000000000000000000000013229818411c8e55e50a63df6983150c1d5ead828711131d9c81841850ed76e4712954d3225eb6d7fffd3cb9924f7497000000000000000000000000000000000f42d6e4d5a28dbfda87c806cb0b1bbabb745e63e655c3c6be50411da4dcdc745ae50f71d56e88db8454d40375e325810000000000000000000000000000000000f663ab791b48f76d358e66e8cd8fa40848dff2bbec758ce1d7b3fe02d1f6b3f123cef644d4fd86d6a77b8155feae58", "Name": "matter_fp2_to_g2_84", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a7e855324ef471b8fefb31967cec84d57953407ba555b672fa59364b303030cb02b6c77933cc63fcd1b8c107b263554000000000000000000000000000000000b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e7524", "Expected": "00000000000000000000000000000000197865f685e78a8842fa79ddc728d507e9f31b31666d1952a46f6422c97c83fba3087be70e3bb588260556014523f74000000000000000000000000000000000131f5d85ad3beaabd129d5a5675d90ea911ebd02cddb5ddc7a8be28c33061430d684d123d5c516785d21ebf756c99195000000000000000000000000000000000c7a14948f3aa29f845e5ca9877db9f0477af376eaeb45324c21e6f99e738aeec96b89af4df942bffbabbf50172d8e5b000000000000000000000000000000000ed4aea3cb585b0d36972f9ad6943172ca7375b44d1d6e80e0bf97a0b25d74deca4d35ce865c8747f1c7a2771a37c667", "Name": "matter_fp2_to_g2_85", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001706830efca18d3e75ea0f1ca8af23a816017ceeb045694cdbad6d3d9aa5a9ddb123f5097a226a217166de3a82038393000000000000000000000000000000000402132ac383a2fcb17fe73398273ef0c2f2d0d6edabc78f31080d3ecbf7c249ffeef28bb8b37a6ef2a7d726c070dc41", "Expected": "000000000000000000000000000000000a795c2affaaecab6cd2cfd6c8fab6e35cdd646e9cfa7b5e02400ef4abf839a69924ea80152eca7810a5041d1bf58ee800000000000000000000000000000000121426bb945d6f6b385c98a5247b7dadaebd3375dd8b2bff7aa77fddfbe603de89e77baf0e8f36a924c707c53d29a1450000000000000000000000000000000007a6fcb486634186f001c8b99874f0a07a37f1ff4b30599d2f570f1bb4ff290b816547f6ce8b3c1ed33e57630a1d57ab000000000000000000000000000000000fa65924a8f17414eb7dcc54f2a4134568484e91533dd21fd33cbcc37a920f2804516a64f1986e9d887ca189179d07c8", "Name": "matter_fp2_to_g2_86", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024beda2b950efcee233435f0c748e33aa928f54ff29d3db217d7e32b1aac5f4ed11705da4fb8fd38481382486e4aef7000000000000000000000000000000000c85283ad6e35a72d07b74775df1a4660113d50b51426451f454a575adf9cbf9d7be3f521649f6c367c4f4c74b67ff6b", "Expected": "00000000000000000000000000000000049d9ac43e31faa3d02f8255d207b82e4b27e8a9a61ba45fc4f9ad8048e5f89b58d25d98253aabe29334e0dc09d1cd6b000000000000000000000000000000001544f90a0baea38b48d89bcb337cf5a80faaa79334733b7e6126f55358a7e498aeb61419065b9434cab9d10fe8e7fd9f00000000000000000000000000000000139bdd668462a1b5d3ef1299d47aa91ed141ccbeba5b08a8ee31b023aa78c16514a97ba08abf5c8bb1abbd85b3fe87350000000000000000000000000000000005c7dbb8a22403a96aee634cfc67ee6f1069cd61a1e1831e8faa9d7e1aa5e4f7623f51f2e5b739f7fcf3b4ba77c82ff1", "Name": "matter_fp2_to_g2_87", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000cb18f477abe58af92116101c3f52ad4f6074ed92a08c3adcc6660b555df9cff09dd8b34e032ed81e868a62bda50378d0000000000000000000000000000000013c4ab1558dc250c3b5d0f0fae3db62b8df969bb41e9ecc24c10e1e51cb399f1368bed7375a9b9ad9c7653c868eecfe3", "Expected": "000000000000000000000000000000000b8b8bf2b25c2386e5f3be4bdb387d8005cf055e68ab9a5606f17dbedc4fbd7a11314fd646d08bbd6e394485d4f56f5f00000000000000000000000000000000173a45d766682f82ec2d69aed1d80ede2477c276ddaa8fb97f5f4d0515b2c2e370c615cd81c1e361f95db855c9b1b6e200000000000000000000000000000000115868a9187a0465a9309054e865ef224ec3c88a5eafbcc25f9a912ee3b19084757a90b72a4038ba71b10f59fe2f93100000000000000000000000000000000006c5476eb8aa1a471d289af52c7d1df55f6bb1ad53d7eaba6bdc2a97fcb24ec480f9d8e12079d366f2213194c861f016", "Name": "matter_fp2_to_g2_88", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000188f650fdc51b970d16c0637ad5e97aade93c7f1398751439484ec6cc56613814908e51cfa7f68da9d980bb9dac47a400000000000000000000000000000000081834f86f1310135a2cb03265f37d9b7c9459bb149bc54b5a61319a7cde36c6a2a0fb49f8f1fb9d80f07b84f799119f", "Expected": "0000000000000000000000000000000016e8fea4d09831146fc35bcad28e441f2c02e4d17838e04dc7cf909b2133297a13f07ee927722f3d78e36721d6848e3400000000000000000000000000000000114dee8b3a47269e9ada05ee015a874d1cbdfff4acdf5310642f829efd08f78dd6110e1c7a514e7d76aff52046f4ed140000000000000000000000000000000017b9d23f7a865a3ca61197d841fd9195805a9e883d79dc7d36e82f504e6689ade0e84c70a5c5c516fac3e3c643942e160000000000000000000000000000000001ab82b2a0986dec3211507b8adca351829b0a13f25e281f98f54d9e0e32280ea4c638dcb74280eb747a0d9af43b6b74", "Name": "matter_fp2_to_g2_89", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006f66eb49f95f51ec90df86254580f0ae57862bdd8b5f2759ace63c5f14f8c5a762207f744bb82a8962f8c4fa410dfdb0000000000000000000000000000000004e02a80628c30ce336eab890fa37a227f77357a60be72cb87cc2b7442d2163d395fdc59350624ca1322bfe8619a2efd", "Expected": "0000000000000000000000000000000006bc2ae646a603a1f4524b445cdeb99914e4ed19cd0676d511764b828bfe126e81cad2cb566655f04de1a302c14d70bc00000000000000000000000000000000023bd509aabfa41385e90cd4b1cbbfa45d066c4defab56993aaa386dc5b7707b1a3a7d444b8bd295a30d0b8f4bdc572e0000000000000000000000000000000006f82e60e18cc958375cce6f465db461ff46ed9d15cfcc01a3aff455d54c77ebba5a654c2ec788b6ed8ac53c39defdd3000000000000000000000000000000000896fbe6492c4c297f8b6d60295a7f2565734d69eea67b2675211a203fec043f0d181b1348bea425a068b7bc12676ed0", "Name": "matter_fp2_to_g2_90", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001451bcd19495cea3a19393b77760f688fbf17b107dc131c88cbb503eee2a804e2978d6e8a4720d144083d28be73371d70000000000000000000000000000000017db715e8680a0e82e18b513f2c9c7ea136cefe8add71aac6baba146e3e33a498d025c7e0808ced306c915eb02900c61", "Expected": "0000000000000000000000000000000008604a06a198c3e11458de920176842221667d024f9c155892485a37ff56252be1dc629a6fd580fa41f5e598a23f3651000000000000000000000000000000000e008eed25eafeaa67f27e89e1f81b469724a4b00f08dc4ae672aa1587b19dc615330e3fce0fbd98d7526bc2c4afe69e0000000000000000000000000000000015bc1e4ea5ae2a7fde6d5e5c3e58f6ff5df5bcb125ab402f10edd09087bde39fa27dfcdce7d04fd18ce399729e155fae0000000000000000000000000000000006684e9be8bf9fa4badda842a1d8840f0820d9a797e482c64f4004a18cd63986f19abfc93f6bf068d38eb1e491cabbe6", "Name": "matter_fp2_to_g2_91", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf77", "Expected": "000000000000000000000000000000000f15c3d0b40735babb2e38a2471773faa16b2fa307c3a573ef4cfa5a5559574b2d26cf88b19dee204b77f6e11a1b927c000000000000000000000000000000000d224445f3d31d381bb29c4fdc8130174f5bcb957f451c92f4a652cc3d2b5df985017133a944849b5228a88f99bec771000000000000000000000000000000001338b48bc1fa229f251bcd4828654baec9d149f090b19596ad3b444eacc7bc583f97d9cfc40d5611fdcf89cc9a88e33b000000000000000000000000000000000c30dd2aa51f6577d57175edb3ccc1b324717bc195eb0073c1dff4e5b0d77cf5e41ec233527b3936994e86303f91b172", "Name": "matter_fp2_to_g2_92", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003379bc10acda5ed1014e2bba1e30cf83b72fe69259eb35476a031b8a898e0183bc32ee853a85fb3d738424208fc880900000000000000000000000000000000175a2e5a44ed62744fbbab9581ea7283470bff12436dfc414ad80b4070f895de3786022cbaed55bdbbc4f68db7460548", "Expected": "000000000000000000000000000000001735e1f2fe905839fd6534c95b95322f8cc86a4c482f1ad7691b9b9bb8f55015b4faaa1f243786aa33b5874817cd09c80000000000000000000000000000000013f1a27931ac513145f2601e009cf637ba4bdb18a7604f89534fa3ec8488f5b6eab9963c5d753fdd34cbe7d2f8eb8a5900000000000000000000000000000000092d8f800e7a4bf6f9a25ddd7f64fc403db53b1695ae59c15f229458f347a8e7c2ebc415af2d3849282b670c5cf6f8600000000000000000000000000000000019d22d694e559c55db63521e7b60a1a2342c3cce868d70951e5ed32ec0f5efaeab0e78b21359110f6e769776b745938a", "Name": "matter_fp2_to_g2_93", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b384a9db472c38a5d246da56059b7630f002b5f4663abce7c5f6e896222a1ca1ac02883a1ec95a4ef09bcfab7d0652a000000000000000000000000000000000de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc", "Expected": "000000000000000000000000000000000d7dc499e5213120b3ccc173c83d3c15dde9e13ef57238cad84889243b35c8e69eea2ac7ef7560051dcd7402b46b733e00000000000000000000000000000000063ad31c17eb17d39cb4b33e45a0b0e951becc11b685b10cb45cff268b6dca40b780f7e1532be91903372c413a11b5be00000000000000000000000000000000140da959456cbd34e041409350d6106ff65ce6dd2ac3149f04959b16eb83dd0456ca11e5990daf4a1e5c23d3f30a6c4b00000000000000000000000000000000195d07ab127d49baf89fcf5eea1f5e4cffea1a577a5c864c0e637fbdfa10182adc1d5d4ebb871949300193e45ae0fbdd", "Name": "matter_fp2_to_g2_94", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014df33e7d3ef2c339b958fee667097ccf146556261f7db4b0b0a3c29897b73a0ca249866cff1461488012bc16df43b0d00000000000000000000000000000000099dda253a43b8cfac580306267d9dfeb2c129ac1818fee43c6df5e582f5fa726ba73e1a2ef6a9e011a387c393529678", "Expected": "0000000000000000000000000000000013ec1ef25b303fe2f10a0bbe9bd77a4b2a055e176c2870c99e63b4baf2b313a835459263351dfbc25c22ea32946d8956000000000000000000000000000000000cb1c3292a2e0c9b1c1ff43cbf7595f39c00fd413b54782681fe75a6f5f231d13912f8d598dd8aaae8159de083dccd8e0000000000000000000000000000000005385f2d4bb6d94d67b2a3bacd3aae31da282707672252c0ab1a12fc85d8e9b9eb75454eb145937542099b860f9d6dce000000000000000000000000000000000e59506f7733a38a7e1da4ea5958de4755b52a9307ba2e5813131b33b86f0e401f97594d9674ff1667068a1ec3c9b145", "Name": "matter_fp2_to_g2_95", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000011c89c8d7e83155a308b2e517a23f05a4a55353332292b44b0a891b6f730fd126bd0b97eb87f0fbdb6c82779791d022f000000000000000000000000000000000da6f02450955bf26e236ec63aaf80a018ac34fd8784bb24a22a1fc5e8bd686244a923009a10cb38b1422534d0997afd", "Expected": "000000000000000000000000000000000f4392a41fb3e58dea97b97fd22e2fe6436c3f9bbcd944585a76a5f1a8f98ea4ee21639208d765b6c3a7d08f8cd3f3f00000000000000000000000000000000002c3d62794996dbb881b665eece98926f41a42c21539125fda6070d9f69e29e0557c886b42e4bcd97b14134d6e9d1d710000000000000000000000000000000004b93f315822aa1be8250c2e736727d390ae3a862c4c7dda452817f70f01c73e6f344df1b0f05f03bd574edecc70902e000000000000000000000000000000000731403981fd6243d00c23d0a42a759016f7907548847743f18421f51b1e72cea92f0c5580328babd4ae3e15bc9c56de", "Name": "matter_fp2_to_g2_96", + "Gas": 110000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015bb227b5c9ccfb8390edcd158b04a69a88a3b99a10ae90e548182751a16448df25493061afde2c9790a0e75e6f409a20000000000000000000000000000000001d7b609155bf3939192eee9642032e6fb10f57d53916674c60211a37b4a7662759899a9569e2dc730febd23f747a7a3", "Expected": "000000000000000000000000000000000b35c6294b70336217eb9334ff1f1bde9d892d109e947de7f4f5681b3830ed00ad1b89ccd7cbad88ce1586449140697d00000000000000000000000000000000032691e5f4597c06496e9e37907041ddcadd18ca8ce64a8b400b1e2e8d63acce5533231edb66b69807fa2dc026c1d2be000000000000000000000000000000000773ccd132cb215cd98aa17d7fc432e0577b08d8faaa35199000d46fdeeb954e8652566384fa0cc5bcd1724942f7075b00000000000000000000000000000000112e951db3694944fc82fb980547cd8b7f2e5ec6fd2051b6aff2573797bd6a28437848ea0627054af1960ad1de0981e5", "Name": "matter_fp2_to_g2_97", + "Gas": 110000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000017599d71686e817cf58b78dd7586d5b359999b32b0dec2d67e33fb6388411418ecfaa2670a2cc9dce3dadaed0fb3364000000000000000000000000000000001773995b540be9ffbfd276a92c0494e4eae296d094f9f7eca975cf4f73ae05e92bd64ea71ac47bba534044f4072a6591", "Expected": "0000000000000000000000000000000018f2eace212eacabd44ff01d886543410ef72b4d27f8d25cb080dbe4b1d4b2b4e57e4dd40723d15789d9b5104b088d9b00000000000000000000000000000000098e9e9b302876ce85ba486609fd028f357314149ce8b530778e6de586ab057fe59648d8c8ae80fe619c4c605b90784a0000000000000000000000000000000016d20a8ca43d37518c8a0f47566ba61a7aade9ea2cdd4a0907ff0ed862c6b7c64815d50397eebec262a05c6010cfaa790000000000000000000000000000000005a70c2fce25acdc4a95fc2bdedb007d71f24b0b5714fa14910ef590215d25442e91a66b6bfea5f7777f0c6d202eff32", "Name": "matter_fp2_to_g2_98", + "Gas": 110000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9", "Expected": "0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa274526850000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e245782100000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7", "Name": "matter_fp2_to_g2_99", + "Gas": 110000, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/blsPairing.json b/core/vm/testdata/precompiles/blsPairing.json index b0e4ded53a..138b13944a 100644 --- a/core/vm/testdata/precompiles/blsPairing.json +++ b/core/vm/testdata/precompiles/blsPairing.json @@ -3,600 +3,700 @@ "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e8490000000000000000000000000000000006e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb9090000000000000000000000000000000017d81038f7d60bee9110d9c0d6d1102fe2d998c957f28e31ec284cc04134df8e47e8f82ff3af2e60a6d9688a4563477c00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "bls_pairing_e(2*G1,3*G2)=e(6*G1,G2)", + "Gas": 161000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e8490000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e200000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_pairing_e(2*G1,3*G2)=e(5*G1,G2)", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a9000000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf100000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab160000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb541170000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a70000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb3000000000000000000000000000000000029ea93c2f1eb48b195815571ea0148198ff1b19462618cab08d037646b592ecab5a66b4bc660ffd02d1b996ca377da000000000000000000000000000000000bb319a4550c981ee89e3c7e6dcc434283454847792807940f72fd2dbf3625b092e0a0c03e581fd9bd9cf74f95ccef15000000000000000000000000000000000abb072b8d9011e81c9f5b23ba86fdb6399c878aa4eadee45fb2486afe594dffc53be643598a23e5428894a36f5ac3ce0000000000000000000000000000000005d04aa0b644faae17d4c76a14aa680c69fdfc6b59fee3ef45641f566165fced60cbbda4ca096e132bb6f58ab4516686000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436000000000000000000000000000000001065f2a2d29a997343765f239c99a018490eced40ac42fc93217dfe20d8b43ee2215f65166aff483b3dc042c5a43b196000000000000000000000000000000000766e4c66f4a442ff1f61a7a4d197d2b47dd226d0e7822a9b065108cfc643cd3f3d5ae59ed2ce4cde13fd9260bb5b7cc0000000000000000000000000000000012251cc6abbabeb7bbe1fdd63eaee10832a748fff24f7e3fdccaea87facb6e99f2e0407a38f27f90450a471b873104620000000000000000000000000000000011181e08c8fba91271adfee9d31681f8412ab7a3f754f7ba4709024c0ad2287e32dd455d71a296b4838072a8ab9d96f2000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f53580000000000000000000000000000000005a7445f55add1ed5c143424ceef3d594280e316c9441a8e68c3ad97377141d015bf878bdfcf0df9fbcd0529f4e8100800000000000000000000000000000000192b52ba08ed509fc84d5775a7182498fd1ff80941d673c53470c9c9f1192f9c0057d68a1dfee0c68fe5df3625cc43bf000000000000000000000000000000000d3fcaf2f727e0eb32c65da9b910dc681b948dda874d0db6f6ed3f063430fbf073385a9a14c2dd78568726124e2b3ea8000000000000000000000000000000001943ce22cdb2387bd5796950dc95d1ace4012ab9bb4afb46223760230c1709e075f1ae76d6b3f2e947ba6b16d458ccd1000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000a29e38da2d42fd4712052800c7c8dd6e94fd9f506e946068aaac799d60b94c2d7515769ffdd32ea95d3910330ec47de000000000000000000000000000000000c60dae92451206390e30b5daa7151d63624dee496753c87dd54eadc92dc9602081fae02a1a53bac97e984a571923a5d00000000000000000000000000000000085f4fda4c72328895f20c683cb49603a37ff2c43d62f66602506dad5b8d1daebfbac7a7db3f50ccf4dfff277deb105c0000000000000000000000000000000005674d005457e0fe1f0fd978d63996c5f3d29f9149ee4eb04c464742dd329ccaef5e5f6b896d986ddfc9f1b2a3aec13100000000000000000000000000000000071bc66d6e2d244afc4a5ce4da1dce3d0c22c303ba61310fdf57843bbd97763ef496833dfa99d14be084bb1a039bb2da0000000000000000000000000000000012c22e047b0af8e2f4bf3bd3633ef0f8264004ca8ea5677a468857a1762f815235a479e53f4ad4741ffda3fb855021c900000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "bls_pairing_10paircheckstrue", + "Gas": 345000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a9000000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf100000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab160000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb541170000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a70000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb3000000000000000000000000000000000029ea93c2f1eb48b195815571ea0148198ff1b19462618cab08d037646b592ecab5a66b4bc660ffd02d1b996ca377da000000000000000000000000000000000bb319a4550c981ee89e3c7e6dcc434283454847792807940f72fd2dbf3625b092e0a0c03e581fd9bd9cf74f95ccef15000000000000000000000000000000000abb072b8d9011e81c9f5b23ba86fdb6399c878aa4eadee45fb2486afe594dffc53be643598a23e5428894a36f5ac3ce0000000000000000000000000000000005d04aa0b644faae17d4c76a14aa680c69fdfc6b59fee3ef45641f566165fced60cbbda4ca096e132bb6f58ab4516686000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436000000000000000000000000000000001065f2a2d29a997343765f239c99a018490eced40ac42fc93217dfe20d8b43ee2215f65166aff483b3dc042c5a43b196000000000000000000000000000000000766e4c66f4a442ff1f61a7a4d197d2b47dd226d0e7822a9b065108cfc643cd3f3d5ae59ed2ce4cde13fd9260bb5b7cc0000000000000000000000000000000012251cc6abbabeb7bbe1fdd63eaee10832a748fff24f7e3fdccaea87facb6e99f2e0407a38f27f90450a471b873104620000000000000000000000000000000011181e08c8fba91271adfee9d31681f8412ab7a3f754f7ba4709024c0ad2287e32dd455d71a296b4838072a8ab9d96f2000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f53580000000000000000000000000000000005a7445f55add1ed5c143424ceef3d594280e316c9441a8e68c3ad97377141d015bf878bdfcf0df9fbcd0529f4e8100800000000000000000000000000000000192b52ba08ed509fc84d5775a7182498fd1ff80941d673c53470c9c9f1192f9c0057d68a1dfee0c68fe5df3625cc43bf000000000000000000000000000000000d3fcaf2f727e0eb32c65da9b910dc681b948dda874d0db6f6ed3f063430fbf073385a9a14c2dd78568726124e2b3ea8000000000000000000000000000000001943ce22cdb2387bd5796950dc95d1ace4012ab9bb4afb46223760230c1709e075f1ae76d6b3f2e947ba6b16d458ccd1000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000a29e38da2d42fd4712052800c7c8dd6e94fd9f506e946068aaac799d60b94c2d7515769ffdd32ea95d3910330ec47de000000000000000000000000000000000c60dae92451206390e30b5daa7151d63624dee496753c87dd54eadc92dc9602081fae02a1a53bac97e984a571923a5d00000000000000000000000000000000085f4fda4c72328895f20c683cb49603a37ff2c43d62f66602506dad5b8d1daebfbac7a7db3f50ccf4dfff277deb105c0000000000000000000000000000000005674d005457e0fe1f0fd978d63996c5f3d29f9149ee4eb04c464742dd329ccaef5e5f6b896d986ddfc9f1b2a3aec13100000000000000000000000000000000071bc66d6e2d244afc4a5ce4da1dce3d0c22c303ba61310fdf57843bbd97763ef496833dfa99d14be084bb1a039bb2da0000000000000000000000000000000012c22e047b0af8e2f4bf3bd3633ef0f8264004ca8ea5677a468857a1762f815235a479e53f4ad4741ffda3fb855021c900000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "bls_pairing_10pairchecksfalse", + "Gas": 345000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_0", + "Gas": 138000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed200000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_1", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_2", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_3", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_4", + "Gas": 138000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f9000000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_5", + "Gas": 138000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_6", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_7", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_8", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_9", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac94300000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_10", + "Gas": 138000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e0000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_11", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_12", + "Gas": 138000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_13", + "Gas": 138000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_14", + "Gas": 138000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_15", + "Gas": 138000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_16", + "Gas": 161000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f900000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a6700000000000000000000000000000000018c690fc5571f69793ec3c82915ac9513726ec891312f4f11dd3ba0ee95cc98b50d925099b0642e58a106e8456bbcbed0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_17", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae70000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e88000000000000000000000000000000001129c94e0c06f51f1f808a62e42a5449dd159289c14a09c4cc382c91bcfe878b6f3555767c310de1b1c275eb3d17bcf5000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_18", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a79157800000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000f46a7dee0cb471ddef3a50670a5bfc443b8455877f1ff602b21faff1eff07fc6bf27930ca2159f01479359548339560000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_19", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b090000000000000000000000000000000011dbce34af6cb14d3e4d49f2482e1b058609f25dca79e2ca48e289ace9d1c8db177b7bc35daad79aa5fdac77728bd5fc0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_20", + "Gas": 161000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd6700000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000108248cdc6f503c7bad30eac875d92a75feccbdbe7b5394f8557a92bb12a796430a2c60ca23c6ecd259e5b01e53891820000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_21", + "Gas": 161000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed30250000000000000000000000000000000006f1fe4d98675ffc62d4d5dd0af9968faca4d0ef425d56fa31b80aea892820e270f6da17aec9eb83c6cc9f84289ecbff000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_22", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff32000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e705800000000000000000000000000000000091ce9e6c4bab3ad3d275cf5888387646c3d9bb53ace7bd0c118fce3e44fcd96241b58e5af53978272f56d04b7d7ab79000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_23", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000b4322c2fb5d5dd2c65b45f74ca75002c97444d8d4e5680d0369d32d180c93b294e30ef42f21ac274f77ad89633ab1b9000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_24", + "Gas": 161000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c200000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b100000000000000000000000000000000081162fe2e65a642993ba283df9bc8d60bfe495b2dc5623f0467c87bc7570980be2654c8cc8838fb362c677f3a3cb1e900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_25", + "Gas": 161000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e272839000000000000000000000000000000001324e51f295ea95adedc9730dac2e1ab6d85838840e3955103d76677ce9a7359215f8d954286950bed1be3bbfebabeda0000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_26", + "Gas": 161000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b00000000000000000000000000000000132d85236d655190323279a01acc8cbc6fb736d951e3999589f0559cb61ea93e2e1c526ed08ef08046c3d7a40d7cfdaf00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_27", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da0000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a60000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000a4ec4acf91be994fe45f08dbeb2bbd053275861d11a486693fd6e5bfa3736134396f6b1c3ad146642f2e972ba609d0b000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_28", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b592947030000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe9760000000000000000000000000000000011dc30871a7a9b33e2882f6b24ccd192aad215edfc6c6bd9acd064dff4a43f41b4c212068875e896daf127547bbeca58000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_29", + "Gas": 161000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000dd1137e592119c134103b9e29e4f14b48387a767d4effae44f807e5b579e7f9c2f60105495f070d0a6ab2410f62844d00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_30", + "Gas": 161000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad62420000000000000000000000000000000016ad3b981f689f51f46e3e5e166986e4e71655dcc1e928327751ffdcfff8934caec5cc37327b3320202c4efbcfda6ae3000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_31", + "Gas": 161000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000cbcd06a1c576af16d0d77ff8bcc3669a486d044cc7b85db03661a92f4c5c44a28d028521dfcfc292d8ecd05aed6ab940000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f00000000000000000000000000000000153ba4ab4fecc724c843b8f78db2db1943e91051b8cb9be2eb7e610a570f1f5925b7981334951b505cce1a3992ff05c9000000000000000000000000000000000c1e79925e9ebfd99e5d11489c56a994e0f855a759f0652cc9bb5151877cfea5c37896f56b949167b9cd2226f14333dd", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_32", + "Gas": 184000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c81292925400000000000000000000000000000000065d3d4be1589a6f00c85c208c44916a35349a096b09219704b4c31861ef58e6b4ea5be57a175b429e482b1038718d6c000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000b8bf51e6509e81b70ff44d6e0df8f9f7bc8e33126e9f1b5a8419414878a2209c05df56cf590c8e33f484587f4a1f6950000000000000000000000000000000001c07a85ece4a1c5072fe722fb264bd1d3aaabf9db9809d5a6cb3fe17157d8fd1bfa730a26e34c87279ea81abe141fe6", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_33", + "Gas": 184000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda00000000000000000000000000000000055caff20f9f3f2ea27c64caeb6bb1880f326c64eb6ed6ee7d15da7bfeb16518f76a75f061cd347f7322e0cec634f0fc000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000000197b17c5b695db49c7c8b6fd6f314da7cfdfc4dbe61c76475839c89064870fad5104234c09aee7bafc1660263b6959e0000000000000000000000000000000019e514b65a358640ea90cd3cf547d591f5ff1a13ad99c568ef70c558cbec26dd1e582cc92d849fcfce9749068d361372", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_34", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000003c0c619be1382199bee84862a0ac6bf4c891d22f722b6af5bfef0edd1ed8c7e9af5efb5d3fc546801f3e019329ae4e80000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa5200000000000000000000000000000000173bdb0d0a79f1f2607867ddbf2581b4c1cc20fc0bced60ed8756aa4e79cb87c47fa722b1c8fdbe99a8208fc532327b600000000000000000000000000000000172f37eac49dd7f09adf602ebe562e5d0bd52ee2419fc08dc7fda241c0319b3f43b3ec79ab5aac246a783f13e268d4bb", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_35", + "Gas": 184000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a030000000000000000000000000000000014c22dcacf2e21ff447c94d81067c626b1217e58b7dc98aacab2ea3fc00b1c5e66f660d19f1c69b16571e49d13c8e1d0000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec35000000000000000000000000000000000125ffac343f97afc413ae80d40a309dfe461fe6b20eb84f8eec3a2718ec2c9f1273bcba2fa1ca59fdc924e471173d68000000000000000000000000000000000ebfaf78e267fd93f0e35e0d19feae9db125660a3dde99b028be77c6fac0116a4808aab02a29063c3c0bc4476924d04f", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_36", + "Gas": 184000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000010338047b7c67c122ffb13466935623ef2338b32bbf5452f78f7abe9a13a16824c11f5520c9dac256b9d257da88d92a30000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000a96371995c5333949b5e9869599fcb67222c2e44447d133e9b3e18a9e9e14a92ee03dcba86832cde7d35b35a88bcd240000000000000000000000000000000003a912710b425cc477c43ff93684249649732b1e99270bba725e990559169b505e8411fba174b6a15f90d5b3962f5399", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_37", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000008418a39124b40643dddcd6afe1dbdf930303bca65226c2fee1b95de6e080e25451f8b4f2b2b7c4633e1de6a5a7d47cb000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c000000000000000000000000000000001535bce9acb48b6ebc4d3dbb7c236d7cc57d656210e42e9379d4f528fc0ba59bf868503d3bb8e5cd3dafdd881ec56d5c00000000000000000000000000000000037033062d13644c88c317f1c58c18e0d9b4facbbe0701ac8bbdf3c7f0c37b14034c7882d5112a94bea39dfdbfc518e7", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_38", + "Gas": 184000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000ada13f88a645bc6082c6321e0cf2b7ac45c633fe2f0cb36aeb187fe2e50e7510df2a86b98979e8551636e94488c8ce000000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000002f98cb2305518737e92ee72e2c48d0dbdbbb1f2dc63b9d02d1a8c27dd1ba5a071dc72a8dcc7813b5757bc244357f6630000000000000000000000000000000017775ae72405e39e2db32d5b9db6637b7bbb9799e614bd783f0b785c3f2ee815367e67b15cc037fec8252735f36c28cf", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_39", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a3700000000000000000000000000000000162e49ebd1b50c7837336509e48ace0e7856f00ec45a76b96d1dd88eea300a8118357cafabf32ee2d06b601def523e4800000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b6000000000000000000000000000000001575d39e959d14b96f261265417ca949a248c3d46e2abb093541c103dadf58cd5b21e28c79f17b376070799492457358000000000000000000000000000000001240585d5f4c28467bccb5193e4aad78ea3b1d8dfb4716a3310fb5215a478aac3f05a8ed478486c9e703a59b9c32bfe5", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_40", + "Gas": 184000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000efe47bf2b11dd10608a309c8aaefdbcbc2bb5e6adceef375371cface8f79668e2b7c2ce9990063a3b53e419e80e2a79000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba00000000000000000000000000000000088ea99f17bb06ba20c5c67aeb8fbbd19b2270986ee7c6517209679e6f84f5d43fa22daf6264c993f1d048d016e3b39e0000000000000000000000000000000008af6330f5638c0a9f339f4e8d841b955e322766457112039b2a852b37d3bc45efb67aeb216a09a8940f5e4e1a771da8", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_41", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c1872290000000000000000000000000000000001099fe889d8f5ddcad09328997c7c3098ef4b4d74ab1d9f6fcbc33a03cafb59c7b28931da67950d1389fbcedca3fb5bb00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e00000000000000000000000000000000006b5813a1c1f934e8e564a7cad93dc7f57de7a9592aedeb116fa4ed6bc6452bbc0da23be10adfea4ad4fa82969e7a150000000000000000000000000000000008e760ad89fd250a9d5041ec81e51b8b66f5265037e7237f7c4a08bb83e7799f352c54c37cf70a6c61bb95bfbf8a616e", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_42", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a9000000000000000000000000000000001750d2e78525453f113b76f18abf2334de9755c03786fbc9233cda2364d57ed493f4fe6c2b565f4d82ff8113e9b63c4d0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000018446c6ba126e030ed071f87189cbd618627419c82065d26044759f6e4c7257f45021dfad1dcb34dd06b4e391329a61f00000000000000000000000000000000136b60cd7658a5d135d4bc38edff042570c7824245ed9f7a6414e9e7ab3840a99700fb620e809891b66003340db29e64", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_43", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb0000000000000000000000000000000001a28b7856a22db6e79ac4165e60addbb7dfe1f19d815032bc68fea905bd0d7709c2dafc65fe51493c964de678a30d32000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb877660000000000000000000000000000000006a5d436046e0ac1975e4d24bb3e3f35c1ba3801f37705505cdeb6a86c58bf8068b43462a55155799fe2d2cc60c398b900000000000000000000000000000000191fde77c7c2b397a950f0542d2edd183a5e9404e516146697a755561ab2a9705f970b491e4c0003657d864258f391ec", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_44", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e270000000000000000000000000000000007c17aaf82c2aa6bf01695157bcd0c2b34734dfbd572b0abe79c8dd3eef7ce6eb9c5e7de55b36ddf87f05e55ba9ac28b00000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df700000000000000000000000000000000073b61e6c2de0969138ce3671582c9b58305c5abefb54cfe13eb3284c2be04d26c906c717fd29aaf60b485e18de8e4fb0000000000000000000000000000000006297267dd3b6f3de2329e837302427ab6235b3ad4a9f8c6bb45795852d3c3c61fe75747bbc78043102fc3f646f5d1f9", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_45", + "Gas": 184000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000017147eda83f35d0b6c8894317da5b2e991818479674d7dd1aef6bfaebacbb61ad4b2a17ce7e799939f8c2004af4799530000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c74390000000000000000000000000000000009e2fdaeb5f35c5aeb9aaca231439c45ac022875d55575cbf25c15cb6177c6b67416ad22fa7e7cb1924d4c2501f98bd80000000000000000000000000000000012dcaeaa2e415f46b579d9e325d7d7c3cd94083d25fe38c872f1907bbb741aff660d28bb663edd502444e11d2d60d8f0", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_46", + "Gas": 184000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f10000000000000000000000000000000016c1c9ca735535f801c58a9e35a80ce122d20abb327b44db4dea31b899982c4e136a2430c51cf3a31adc5611621f9dde000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000b49e02d9fb238a258f3a4307b6a2f64912b7fa91712b5639de24e90c09f9797654e0f7e2d31e968c040b867de03cd370000000000000000000000000000000005c56a16431ba175ad81260faeac87d8238f86b2828b0e74dbb0b296b34745ac17e6b684a25a16240183619c96b986bd", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_47", + "Gas": 184000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf20000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf2", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_48", + "Gas": 207000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e500000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e5", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_49", + "Gas": 207000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_50", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae850000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae85", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_51", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_52", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_53", + "Gas": 207000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_54", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c50000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c5", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_55", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e790000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e79", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_56", + "Gas": 207000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_57", + "Gas": 207000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_58", + "Gas": 207000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_59", + "Gas": 207000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_60", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa4820000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa482", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_61", + "Gas": 207000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_62", + "Gas": 207000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_63", + "Gas": 207000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_64", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_65", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_66", + "Gas": 230000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_67", + "Gas": 230000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_68", + "Gas": 230000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_69", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_70", + "Gas": 230000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_71", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_72", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_73", + "Gas": 230000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_74", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_75", + "Gas": 230000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_76", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_77", + "Gas": 230000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_78", + "Gas": 230000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "matter_pairing_79", + "Gas": 230000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb963", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_80", + "Gas": 299000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_81", + "Gas": 299000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_82", + "Gas": 299000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_83", + "Gas": 299000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_84", + "Gas": 299000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_85", + "Gas": 299000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_86", + "Gas": 299000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_87", + "Gas": 299000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_88", + "Gas": 299000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_89", + "Gas": 299000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e6", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_90", + "Gas": 299000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_91", + "Gas": 299000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_92", + "Gas": 299000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_93", + "Gas": 299000, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a7", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_94", + "Gas": 299000, "NoBenchmark": false }, { "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "matter_pairing_95", + "Gas": 299000, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/bn256Add.json b/core/vm/testdata/precompiles/bn256Add.json index e211547c69..b6fcd550e3 100644 --- a/core/vm/testdata/precompiles/bn256Add.json +++ b/core/vm/testdata/precompiles/bn256Add.json @@ -3,71 +3,83 @@ "Input": "18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7", "Expected": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915", "Name": "chfast1", + "Gas": 150, "NoBenchmark": false }, { "Input": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c91518b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266", "Expected": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204", "Name": "chfast2", + "Gas": 150, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "cdetrio1", + "Gas": 150, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "cdetrio2", + "Gas": 150, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "cdetrio3", + "Gas": 150, "NoBenchmark": false }, { "Input": "", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "cdetrio4", + "Gas": 150, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "cdetrio5", + "Gas": 150, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", "Name": "cdetrio6", + "Gas": 150, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", "Name": "cdetrio7", + "Gas": 150, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", "Name": "cdetrio8", + "Gas": 150, "NoBenchmark": false }, { "Input": "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Gas": 150, "Name": "cdetrio9", "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + "Gas": 150, "Name": "cdetrio10", "NoBenchmark": false }, @@ -75,24 +87,28 @@ "Input": "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", "Expected": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4", "Name": "cdetrio11", + "Gas": 150, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4", "Name": "cdetrio12", + "Gas": 150, "NoBenchmark": false }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", "Expected": "15bf2bb17880144b5d1cd2b1f46eff9d617bffd1ca57c37fb5a49bd84e53cf66049c797f9ce0d17083deb32b5e36f2ea2a212ee036598dd7624c168993d1355f", "Name": "cdetrio13", + "Gas": 150, "NoBenchmark": false }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "Name": "cdetrio14", + "Gas": 150, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/bn256Pairing.json b/core/vm/testdata/precompiles/bn256Pairing.json index 9474b1aa88..3fbed6b87c 100644 --- a/core/vm/testdata/precompiles/bn256Pairing.json +++ b/core/vm/testdata/precompiles/bn256Pairing.json @@ -3,84 +3,98 @@ "Input": "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "jeff1", + "Gas": 113000, "NoBenchmark": false }, { "Input": "2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc0203d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db841213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f06967a1237ebfeca9aaae0d6d0bab8e28c198c5a339ef8a2407e31cdac516db922160fa257a5fd5b280642ff47b65eca77e626cb685c84fa6d3b6882a283ddd1198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "jeff2", + "Gas": 113000, "NoBenchmark": false }, { "Input": "0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd216da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb314a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee245901b9e027bd5cfc2cb5db82d4dc9677ac795ec500ecd47deee3b5da006d6d049b811d7511c78158de484232fc68daf8a45cf217d1c2fae693ff5871e8752d73b21198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "jeff3", + "Gas": 113000, "NoBenchmark": false }, { "Input": "2f2ea0b3da1e8ef11914acf8b2e1b32d99df51f5f4f206fc6b947eae860eddb6068134ddb33dc888ef446b648d72338684d678d2eb2371c61a50734d78da4b7225f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb122acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf6806d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb186bac5188a98c45e6016873d107f5cd131f3a3e339d0375e58bd6219347b008122ae2b09e539e152ec5364e7e2204b03d11d3caa038bfc7cd499f8176aacbee1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd415794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f211b88da1679b0b64a63b7e0e7bfe52aae524f73a55be7fe70c7e9bfc94b4cf0da1213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "jeff4", + "Gas": 147000, "NoBenchmark": false }, { "Input": "20a754d2071d4d53903e3b31a7e98ad6882d58aec240ef981fdf0a9d22c5926a29c853fcea789887315916bbeb89ca37edb355b4f980c9a12a94f30deeed30211213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f1abb4a25eb9379ae96c84fff9f0540abcfc0a0d11aeda02d4f37e4baf74cb0c11073b3ff2cdbb38755f8691ea59e9606696b3ff278acfc098fa8226470d03869217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac290a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a98552fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d70f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2198a1f162a73261f112401aa2db79c7dab1533c9935c77290a6ce3b191f2318d198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "jeff5", + "Gas": 147000, "NoBenchmark": false }, { "Input": "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c103188585e2364128fe25c70558f1560f4f9350baf3959e603cc91486e110936198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "jeff6", + "Gas": 113000, "NoBenchmark": false }, { "Input": "", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "empty_data", + "Gas": 45000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "one_point", + "Gas": 79000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "two_point_match_2", + "Gas": 113000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "two_point_match_3", + "Gas": 113000, "NoBenchmark": false }, { "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "two_point_match_4", + "Gas": 113000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "ten_point_match_1", + "Gas": 385000, "NoBenchmark": false }, { "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "ten_point_match_2", + "Gas": 385000, "NoBenchmark": false }, { "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "ten_point_match_3", + "Gas": 113000, "NoBenchmark": false } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/bn256ScalarMul.json b/core/vm/testdata/precompiles/bn256ScalarMul.json index eae26a860f..2a28f6304b 100644 --- a/core/vm/testdata/precompiles/bn256ScalarMul.json +++ b/core/vm/testdata/precompiles/bn256ScalarMul.json @@ -3,108 +3,126 @@ "Input": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2", "Expected": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc", "Name": "chfast1", + "Gas": 6000, "NoBenchmark": false }, { "Input": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46", "Expected": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e", "Name": "chfast2", + "Gas": 6000, "NoBenchmark": false }, { "Input": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3", "Expected": "14789d0d4a730b354403b5fac948113739e276c23e0258d8596ee72f9cd9d3230af18a63153e0ec25ff9f2951dd3fa90ed0197bfef6e2a1a62b5095b9d2b4a27", "Name": "chfast3", + "Gas": 6000, "NoBenchmark": false }, { "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "Expected": "2cde5879ba6f13c0b5aa4ef627f159a3347df9722efce88a9afbb20b763b4c411aa7e43076f6aee272755a7f9b84832e71559ba0d2e0b17d5f9f01755e5b0d11", "Name": "cdetrio1", + "Gas": 6000, "NoBenchmark": false }, { "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f630644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", "Expected": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3163511ddc1c3f25d396745388200081287b3fd1472d8339d5fecb2eae0830451", "Name": "cdetrio2", + "Gas": 6000, "NoBenchmark": true }, { "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000100000000000000000000000000000000", "Expected": "1051acb0700ec6d42a88215852d582efbaef31529b6fcbc3277b5c1b300f5cf0135b2394bb45ab04b8bd7611bd2dfe1de6a4e6e2ccea1ea1955f577cd66af85b", "Name": "cdetrio3", + "Gas": 6000, "NoBenchmark": true }, { "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000009", "Expected": "1dbad7d39dbc56379f78fac1bca147dc8e66de1b9d183c7b167351bfe0aeab742cd757d51289cd8dbd0acf9e673ad67d0f0a89f912af47ed1be53664f5692575", "Name": "cdetrio4", + "Gas": 6000, "NoBenchmark": true }, { "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000001", "Expected": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6", "Name": "cdetrio5", + "Gas": 6000, "NoBenchmark": true }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "Expected": "29e587aadd7c06722aabba753017c093f70ba7eb1f1c0104ec0564e7e3e21f6022b1143f6a41008e7755c71c3d00b6b915d386de21783ef590486d8afa8453b1", "Name": "cdetrio6", + "Gas": 6000, "NoBenchmark": false }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", "Expected": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb", "Name": "cdetrio7", + "Gas": 6000, "NoBenchmark": true }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000100000000000000000000000000000000", "Expected": "221a3577763877920d0d14a91cd59b9479f83b87a653bb41f82a3f6f120cea7c2752c7f64cdd7f0e494bff7b60419f242210f2026ed2ec70f89f78a4c56a1f15", "Name": "cdetrio8", + "Gas": 6000, "NoBenchmark": true }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000009", "Expected": "228e687a379ba154554040f8821f4e41ee2be287c201aa9c3bc02c9dd12f1e691e0fd6ee672d04cfd924ed8fdc7ba5f2d06c53c1edc30f65f2af5a5b97f0a76a", "Name": "cdetrio9", + "Gas": 6000, "NoBenchmark": true }, { "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000001", "Expected": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c", "Name": "cdetrio10", + "Gas": 6000, "NoBenchmark": true }, { "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "Expected": "00a1a234d08efaa2616607e31eca1980128b00b415c845ff25bba3afcb81dc00242077290ed33906aeb8e42fd98c41bcb9057ba03421af3f2d08cfc441186024", "Name": "cdetrio11", + "Gas": 6000, "NoBenchmark": false }, { "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d9830644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000", "Expected": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b8692929ee761a352600f54921df9bf472e66217e7bb0cee9032e00acc86b3c8bfaf", "Name": "cdetrio12", + "Gas": 6000, "NoBenchmark": true }, { "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000100000000000000000000000000000000", "Expected": "1071b63011e8c222c5a771dfa03c2e11aac9666dd097f2c620852c3951a4376a2f46fe2f73e1cf310a168d56baa5575a8319389d7bfa6b29ee2d908305791434", "Name": "cdetrio13", + "Gas": 6000, "NoBenchmark": true }, { "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000009", "Expected": "19f75b9dd68c080a688774a6213f131e3052bd353a304a189d7a2ee367e3c2582612f545fb9fc89fde80fd81c68fc7dcb27fea5fc124eeda69433cf5c46d2d7f", "Name": "cdetrio14", + "Gas": 6000, "NoBenchmark": true }, { "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000001", "Expected": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98", "Name": "cdetrio15", + "Gas": 6000, "NoBenchmark": true } ] \ No newline at end of file diff --git a/core/vm/testdata/precompiles/ecRecover.json b/core/vm/testdata/precompiles/ecRecover.json index ed07ac3ad9..4911d6157e 100644 --- a/core/vm/testdata/precompiles/ecRecover.json +++ b/core/vm/testdata/precompiles/ecRecover.json @@ -2,30 +2,35 @@ { "Input": "a8b53bdf3306a35a7103ab5504a0c9b492295564b6202b1942a84ef300107281000000000000000000000000000000000000000000000000000000000000001b307835653165303366353363653138623737326363623030393366663731663366353366356337356237346463623331613835616138623838393262346538621122334455667788991011121314151617181920212223242526272829303132", "Expected": "", + "Gas": 3000, "Name": "CallEcrecoverUnrecoverableKey", "NoBenchmark": false }, { "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", "Expected": "000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "Gas": 3000, "Name": "ValidKey", "NoBenchmark": false }, { "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c100000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", "Expected": "", + "Gas": 3000, "Name": "InvalidHighV-bits-1", "NoBenchmark": false }, { "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000001000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", "Expected": "", + "Gas": 3000, "Name": "InvalidHighV-bits-2", "NoBenchmark": false }, { "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000001000000000000000000000011c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549", "Expected": "", + "Gas": 3000, "Name": "InvalidHighV-bits-3", "NoBenchmark": false } diff --git a/core/vm/testdata/precompiles/modexp.json b/core/vm/testdata/precompiles/modexp.json index fe70a7f817..4550eb9138 100644 --- a/core/vm/testdata/precompiles/modexp.json +++ b/core/vm/testdata/precompiles/modexp.json @@ -3,102 +3,119 @@ "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", "Expected": "0000000000000000000000000000000000000000000000000000000000000001", "Name": "eip_example1", + "Gas": 13056, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", "Expected": "0000000000000000000000000000000000000000000000000000000000000000", "Name": "eip_example2", + "Gas": 13056, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", "Name": "nagydani-1-square", + "Gas": 204, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", "Name": "nagydani-1-qube", + "Gas": 204, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", "Name": "nagydani-1-pow0x10001", + "Gas": 3276, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", "Name": "nagydani-2-square", + "Gas": 665, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", "Name": "nagydani-2-qube", + "Gas": 665, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", "Name": "nagydani-2-pow0x10001", + "Gas": 10649, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", "Name": "nagydani-3-square", + "Gas": 1894, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", "Name": "nagydani-3-qube", + "Gas": 1894, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", "Name": "nagydani-3-pow0x10001", + "Gas": 30310, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", "Name": "nagydani-4-square", + "Gas": 5580, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", "Name": "nagydani-4-qube", + "Gas": 5580, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", "Name": "nagydani-4-pow0x10001", + "Gas": 89292, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", "Name": "nagydani-5-square", + "Gas": 17868, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", "Name": "nagydani-5-qube", + "Gas": 17868, "NoBenchmark": false }, { "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", "Name": "nagydani-5-pow0x10001", + "Gas": 285900, "NoBenchmark": false } ] \ No newline at end of file From 4ea9737de6ec616ee9bc948b59e454cc211ca580 Mon Sep 17 00:00:00 2001 From: Shude Li Date: Thu, 10 Sep 2020 20:21:51 +0800 Subject: [PATCH 015/948] go.mod: remove golang.org/x/sync (#21541) --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index 7e8d0d1b8c..3da9a262cf 100755 --- a/go.mod +++ b/go.mod @@ -59,7 +59,6 @@ require ( github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 From 116763952498d9c1b852fa94542df1b3f03e2812 Mon Sep 17 00:00:00 2001 From: Dan Sosedoff Date: Thu, 10 Sep 2020 07:24:21 -0500 Subject: [PATCH 016/948] ethclient: add BlockNumber method (#21500) This adds a new client method BlockNumber to fetch the most recent block number of the chain. --- ethclient/ethclient.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index bc0305fc22..a964bd0f8d 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -88,6 +88,13 @@ func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl return ec.getBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), true) } +// BlockNumber returns the most recent block number +func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) { + var result hexutil.Uint64 + err := ec.c.CallContext(ctx, &result, "eth_getBlockNumber", nil) + return uint64(result), err +} + type rpcBlock struct { Hash common.Hash `json:"hash"` Transactions []rpcTransaction `json:"transactions"` From d7f02b448a929d6c9e4934ebbee87355336aeeb1 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Thu, 10 Sep 2020 13:14:19 +0000 Subject: [PATCH 017/948] cmd/geth: print warning when whisper config is present in toml (#21544) * cmd/geth: print warning when whisper config is present in toml * Update cmd/geth/config.go Co-authored-by: Martin Holst Swende --- cmd/geth/config.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 2c15a4c832..6b51843aa4 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -73,8 +73,19 @@ type ethstatsConfig struct { URL string `toml:",omitempty"` } +// whisper has been deprecated, but clients out there might still have [Shh] +// in their config, which will crash. Cut them some slack by keeping the +// config, and displaying a message that those config switches are ineffectual. +// To be removed circa Q1 2021 -- @gballet. +type whisperDeprecatedConfig struct { + MaxMessageSize uint32 `toml:",omitempty"` + MinimumAcceptedPOW float64 `toml:",omitempty"` + RestrictConnectionBetweenLightClients bool `toml:",omitempty"` +} + type gethConfig struct { Eth eth.Config + Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig } @@ -117,6 +128,10 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } + + if cfg.Shh != (whisperDeprecatedConfig{}) { + log.Warn("Deprecated whisper config detected. Whisper has been moved to github.com/ethereum/whisper") + } } // Apply flags. From 7cf56d6f064869cb62b1673f9ee437020c595391 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 10 Sep 2020 19:27:42 +0200 Subject: [PATCH 018/948] miner: use channels instead of atomics in update loop (#21536) This PR changes several different things: - Adds test cases for the miner loop - Stops the worker if it wasn't already stopped in worker.Close() - Uses channels instead of atomics in the miner.update() loop Co-authored-by: Felix Lange --- miner/miner.go | 57 +++++++-------- miner/miner_test.go | 170 ++++++++++++++++++++++++++++++++++++++++++++ miner/worker.go | 1 + 3 files changed, 196 insertions(+), 32 deletions(-) create mode 100644 miner/miner_test.go diff --git a/miner/miner.go b/miner/miner.go index 5249118cae..ec5b4cc085 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -20,7 +20,6 @@ package miner import ( "fmt" "math/big" - "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" @@ -61,19 +60,19 @@ type Miner struct { eth Backend engine consensus.Engine exitCh chan struct{} - - canStart int32 // can start indicates whether we can start the mining operation - shouldStart int32 // should start indicates whether we should start after sync + startCh chan common.Address + stopCh chan struct{} } func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool) *Miner { miner := &Miner{ - eth: eth, - mux: mux, - engine: engine, - exitCh: make(chan struct{}), - worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true), - canStart: 1, + eth: eth, + mux: mux, + engine: engine, + exitCh: make(chan struct{}), + startCh: make(chan common.Address), + stopCh: make(chan struct{}), + worker: newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, true), } go miner.update() @@ -88,6 +87,7 @@ func (miner *Miner) update() { events := miner.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{}) defer events.Unsubscribe() + shouldStart := false for { select { case ev := <-events.Chan(): @@ -96,47 +96,40 @@ func (miner *Miner) update() { } switch ev.Data.(type) { case downloader.StartEvent: - atomic.StoreInt32(&miner.canStart, 0) - if miner.Mining() { - miner.Stop() - atomic.StoreInt32(&miner.shouldStart, 1) + wasMining := miner.Mining() + miner.worker.stop() + if wasMining { + // Resume mining after sync was finished + shouldStart = true log.Info("Mining aborted due to sync") } case downloader.DoneEvent, downloader.FailedEvent: - shouldStart := atomic.LoadInt32(&miner.shouldStart) == 1 - - atomic.StoreInt32(&miner.canStart, 1) - atomic.StoreInt32(&miner.shouldStart, 0) if shouldStart { - miner.Start(miner.coinbase) + miner.SetEtherbase(miner.coinbase) + miner.worker.start() } - // stop immediately and ignore all further pending events - return } + case addr := <-miner.startCh: + miner.SetEtherbase(addr) + miner.worker.start() + case <-miner.stopCh: + miner.worker.stop() case <-miner.exitCh: + miner.worker.close() return } } } func (miner *Miner) Start(coinbase common.Address) { - atomic.StoreInt32(&miner.shouldStart, 1) - miner.SetEtherbase(coinbase) - - if atomic.LoadInt32(&miner.canStart) == 0 { - log.Info("Network syncing, will start miner afterwards") - return - } - miner.worker.start() + miner.startCh <- coinbase } func (miner *Miner) Stop() { - miner.worker.stop() - atomic.StoreInt32(&miner.shouldStart, 0) + miner.stopCh <- struct{}{} } func (miner *Miner) Close() { - miner.worker.close() close(miner.exitCh) } diff --git a/miner/miner_test.go b/miner/miner_test.go new file mode 100644 index 0000000000..447892b198 --- /dev/null +++ b/miner/miner_test.go @@ -0,0 +1,170 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package miner implements Ethereum block creation and mining. +package miner + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +type mockBackend struct { + bc *core.BlockChain + txPool *core.TxPool +} + +func NewMockBackend(bc *core.BlockChain, txPool *core.TxPool) *mockBackend { + return &mockBackend{ + bc: bc, + txPool: txPool, + } +} + +func (m *mockBackend) BlockChain() *core.BlockChain { + return m.bc +} + +func (m *mockBackend) TxPool() *core.TxPool { + return m.txPool +} + +type testBlockChain struct { + statedb *state.StateDB + gasLimit uint64 + chainHeadFeed *event.Feed +} + +func (bc *testBlockChain) CurrentBlock() *types.Block { + return types.NewBlock(&types.Header{ + GasLimit: bc.gasLimit, + }, nil, nil, nil, new(trie.Trie)) +} + +func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { + return bc.CurrentBlock() +} + +func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { + return bc.statedb, nil +} + +func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { + return bc.chainHeadFeed.Subscribe(ch) +} + +func TestMiner(t *testing.T) { + miner, mux := createMiner(t) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Start the downloader + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + // Stop the downloader and wait for the update loop to run + mux.Post(downloader.DoneEvent{}) + waitForMiningState(t, miner, true) + // Start the downloader and wait for the update loop to run + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + // Stop the downloader and wait for the update loop to run + mux.Post(downloader.FailedEvent{}) + waitForMiningState(t, miner, true) +} + +func TestStartStopMiner(t *testing.T) { + miner, _ := createMiner(t) + waitForMiningState(t, miner, false) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + miner.Stop() + waitForMiningState(t, miner, false) +} + +func TestCloseMiner(t *testing.T) { + miner, _ := createMiner(t) + waitForMiningState(t, miner, false) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Terminate the miner and wait for the update loop to run + miner.Close() + waitForMiningState(t, miner, false) +} + +// waitForMiningState waits until either +// * the desired mining state was reached +// * a timeout was reached which fails the test +func waitForMiningState(t *testing.T, m *Miner, mining bool) { + t.Helper() + + var state bool + for i := 0; i < 100; i++ { + if state = m.Mining(); state == mining { + return + } + time.Sleep(10 * time.Millisecond) + } + t.Fatalf("Mining() == %t, want %t", state, mining) +} + +func createMiner(t *testing.T) (*Miner, *event.TypeMux) { + // Create Ethash config + config := Config{ + Etherbase: common.HexToAddress("123456789"), + } + // Create chainConfig + memdb := memorydb.New() + chainDB := rawdb.NewDatabase(memdb) + genesis := core.DeveloperGenesisBlock(15, common.HexToAddress("12345")) + chainConfig, _, err := core.SetupGenesisBlock(chainDB, genesis) + if err != nil { + t.Fatalf("can't create new chain config: %v", err) + } + // Create event Mux + mux := new(event.TypeMux) + // Create consensus engine + engine := ethash.New(ethash.Config{}, []string{}, false) + engine.SetThreads(-1) + // Create isLocalBlock + isLocalBlock := func(block *types.Block) bool { + return true + } + // Create Ethereum backend + limit := uint64(1000) + bc, err := core.NewBlockChain(chainDB, new(core.CacheConfig), chainConfig, engine, vm.Config{}, isLocalBlock, &limit) + if err != nil { + t.Fatalf("can't create new chain %v", err) + } + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} + + pool := core.NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + backend := NewMockBackend(bc, pool) + // Create Miner + return New(backend, &config, chainConfig, mux, engine, isLocalBlock), mux +} diff --git a/miner/worker.go b/miner/worker.go index f042fd8e33..16f4c1c313 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -303,6 +303,7 @@ func (w *worker) isRunning() bool { // close terminates all background threads maintained by the worker. // Note the worker does not support being closed multiple times. func (w *worker) close() { + atomic.StoreInt32(&w.running, 0) close(w.exitCh) } From df219e23df94008a168f1c7cc539c41320527c39 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 11 Sep 2020 18:17:09 +0200 Subject: [PATCH 019/948] miner: fix regression, add test for starting while download (#21547) Fixes a regression introduced in #21536 --- miner/miner.go | 11 +++++++++-- miner/miner_test.go | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index ec5b4cc085..8cbd70b424 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -88,6 +88,7 @@ func (miner *Miner) update() { defer events.Unsubscribe() shouldStart := false + canStart := true for { select { case ev := <-events.Chan(): @@ -98,21 +99,27 @@ func (miner *Miner) update() { case downloader.StartEvent: wasMining := miner.Mining() miner.worker.stop() + canStart = false if wasMining { // Resume mining after sync was finished shouldStart = true log.Info("Mining aborted due to sync") } case downloader.DoneEvent, downloader.FailedEvent: + canStart = true if shouldStart { miner.SetEtherbase(miner.coinbase) miner.worker.start() } } case addr := <-miner.startCh: - miner.SetEtherbase(addr) - miner.worker.start() + if canStart { + miner.SetEtherbase(addr) + miner.worker.start() + } + shouldStart = true case <-miner.stopCh: + shouldStart = false miner.worker.stop() case <-miner.exitCh: miner.worker.close() diff --git a/miner/miner_test.go b/miner/miner_test.go index 447892b198..2ed03a2397 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -97,6 +97,19 @@ func TestMiner(t *testing.T) { waitForMiningState(t, miner, true) } +func TestStartWhileDownload(t *testing.T) { + miner, mux := createMiner(t) + waitForMiningState(t, miner, false) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Stop the downloader and wait for the update loop to run + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + // Starting the miner after the downloader should not work + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, false) +} + func TestStartStopMiner(t *testing.T) { miner, _ := createMiner(t) waitForMiningState(t, miner, false) From 4e2641319be2fd33a8cd61f0258456b7cef07957 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 11 Sep 2020 18:35:38 +0000 Subject: [PATCH 020/948] p2p/discover: fix typo in comments (#21554) --- p2p/discover/table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 56ab9368a5..d08f8a6c69 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -377,7 +377,7 @@ func (tab *Table) nextRevalidateTime() time.Duration { } // copyLiveNodes adds nodes from the table to the database if they have been in the table -// longer then minTableTime. +// longer than seedMinTableTime. func (tab *Table) copyLiveNodes() { tab.mutex.Lock() defer tab.mutex.Unlock() From a99ac5335c631c72fb0d1142a121520d7cf9a950 Mon Sep 17 00:00:00 2001 From: Shude Li Date: Mon, 14 Sep 2020 04:25:15 +0800 Subject: [PATCH 021/948] Dockerfile: unexpose port 8547 as GraphQL was merged into HTTP endpoint (#21556) --- Dockerfile | 2 +- Dockerfile.alltools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0705361f5b..d86b776611 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,5 +12,5 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ -EXPOSE 8545 8546 8547 30303 30303/udp +EXPOSE 8545 8546 30303 30303/udp ENTRYPOINT ["geth"] diff --git a/Dockerfile.alltools b/Dockerfile.alltools index e2604232cf..715213c5de 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -12,4 +12,4 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ -EXPOSE 8545 8546 8547 30303 30303/udp +EXPOSE 8545 8546 30303 30303/udp From 4eb92969109184c3ccec426d5190a2c183509f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 14 Sep 2020 14:01:18 +0200 Subject: [PATCH 022/948] p2p/nodestate: ensure correct callback order (#21436) This PR adds an extra guarantee to NodeStateMachine: it ensures that all immediate effects of a certain change are processed before any subsequent effects of any of the immediate effects on the same node. In the original version, if a cascaded change caused a subscription callback to be called multiple times for the same node then these calls might have happened in a wrong chronological order. For example: - a subscription to flag0 changes flag1 and flag2 - a subscription to flag1 changes flag3 - a subscription to flag1, flag2 and flag3 was called in the following order: [flag1] -> [flag1, flag3] [] -> [flag1] [flag1, flag3] -> [flag1, flag2, flag3] This happened because the tree of changes was traversed in a "depth-first order". Now it is traversed in a "breadth-first order"; each node has a FIFO queue for pending callbacks and each triggered subscription callback is added to the end of the list. The already existing guarantees are retained; no SetState or SetField returns until the callback queue of the node is empty again. Just like before, it is the responsibility of the state machine design to ensure that infinite state loops are not possible. Multiple changes affecting the same node can still happen simultaneously; in this case the changes can be interleaved in the FIFO of the node but the correct order is still guaranteed. A new unit test is also added to verify callback order in the above scenario. --- les/serverpool.go | 83 ++++---- p2p/nodestate/nodestate.go | 344 +++++++++++++++++++++----------- p2p/nodestate/nodestate_test.go | 56 ++++-- 3 files changed, 317 insertions(+), 166 deletions(-) diff --git a/les/serverpool.go b/les/serverpool.go index aff7743241..9bfa0bd725 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -166,7 +166,7 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, d if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() { // dial timeout, no connection s.setRedialWait(n, dialCost, dialWaitStep) - s.ns.SetState(n, nodestate.Flags{}, sfDialing, 0) + s.ns.SetStateSub(n, nodestate.Flags{}, sfDialing, 0) } }) @@ -193,10 +193,10 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod if rand.Intn(maxQueryFails*2) < int(fails) { // skip pre-negotiation with increasing chance, max 50% // this ensures that the client can operate even if UDP is not working at all - s.ns.SetState(n, sfCanDial, nodestate.Flags{}, time.Second*10) + s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) // set canDial before resetting queried so that FillSet will not read more // candidates unnecessarily - s.ns.SetState(n, nodestate.Flags{}, sfQueried, 0) + s.ns.SetStateSub(n, nodestate.Flags{}, sfQueried, 0) return } go func() { @@ -206,12 +206,15 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod } else { atomic.StoreUint32(&s.queryFails, 0) } - if q == 1 { - s.ns.SetState(n, sfCanDial, nodestate.Flags{}, time.Second*10) - } else { - s.setRedialWait(n, queryCost, queryWaitStep) - } - s.ns.SetState(n, nodestate.Flags{}, sfQueried, 0) + s.ns.Operation(func() { + // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait + if q == 1 { + s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) + } else { + s.setRedialWait(n, queryCost, queryWaitStep) + } + s.ns.SetStateSub(n, nodestate.Flags{}, sfQueried, 0) + }) }() } }) @@ -240,18 +243,20 @@ func (s *serverPool) start() { } } unixTime := s.unixTime() - s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - s.calculateWeight(node) - if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime { - wait := n.redialWaitEnd - unixTime - lastWait := n.redialWaitEnd - n.redialWaitStart - if wait > lastWait { - // if the time until expiration is larger than the last suggested - // waiting time then the system clock was probably adjusted - wait = lastWait + s.ns.Operation(func() { + s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + s.calculateWeight(node) + if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime { + wait := n.redialWaitEnd - unixTime + lastWait := n.redialWaitEnd - n.redialWaitStart + if wait > lastWait { + // if the time until expiration is larger than the last suggested + // waiting time then the system clock was probably adjusted + wait = lastWait + } + s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second) } - s.ns.SetState(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second) - } + }) }) } @@ -261,9 +266,11 @@ func (s *serverPool) stop() { if s.fillSet != nil { s.fillSet.Close() } - s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) { - // recalculate weight of connected nodes in order to update hasValue flag if necessary - s.calculateWeight(n) + s.ns.Operation(func() { + s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) { + // recalculate weight of connected nodes in order to update hasValue flag if necessary + s.calculateWeight(n) + }) }) s.ns.Stop() } @@ -279,9 +286,11 @@ func (s *serverPool) registerPeer(p *serverPeer) { // unregisterPeer implements serverPeerSubscriber func (s *serverPool) unregisterPeer(p *serverPeer) { - s.setRedialWait(p.Node(), dialCost, dialWaitStep) - s.ns.SetState(p.Node(), nodestate.Flags{}, sfConnected, 0) - s.ns.SetField(p.Node(), sfiConnectedStats, nil) + s.ns.Operation(func() { + s.setRedialWait(p.Node(), dialCost, dialWaitStep) + s.ns.SetStateSub(p.Node(), nodestate.Flags{}, sfConnected, 0) + s.ns.SetFieldSub(p.Node(), sfiConnectedStats, nil) + }) s.vt.Unregister(p.ID()) p.setValueTracker(nil, nil) } @@ -380,14 +389,16 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl // updateWeight calculates the node weight and updates the nodeWeight field and the // hasValue flag. It also saves the node state if necessary. +// Note: this function should run inside a NodeStateMachine operation func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost)) if weight >= nodeWeightThreshold { - s.ns.SetState(node, sfHasValue, nodestate.Flags{}, 0) - s.ns.SetField(node, sfiNodeWeight, weight) + s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0) + s.ns.SetFieldSub(node, sfiNodeWeight, weight) } else { - s.ns.SetState(node, nodestate.Flags{}, sfHasValue, 0) - s.ns.SetField(node, sfiNodeWeight, nil) + s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) + s.ns.SetFieldSub(node, sfiNodeWeight, nil) + s.ns.SetFieldSub(node, sfiNodeHistory, nil) } s.ns.Persist(node) // saved if node history or hasValue changed } @@ -400,6 +411,7 @@ func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDia // a significant amount of service value again its waiting time is quickly reduced or reset // to the minimum. // Note: node weight is also recalculated and updated by this function. +// Note 2: this function should run inside a NodeStateMachine operation func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) sessionValue, totalValue := s.serviceValue(node) @@ -450,21 +462,22 @@ func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep if wait < waitThreshold { n.redialWaitStart = unixTime n.redialWaitEnd = unixTime + int64(nextTimeout) - s.ns.SetField(node, sfiNodeHistory, n) - s.ns.SetState(node, sfRedialWait, nodestate.Flags{}, wait) + s.ns.SetFieldSub(node, sfiNodeHistory, n) + s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, wait) s.updateWeight(node, totalValue, totalDialCost) } else { // discard known node statistics if waiting time is very long because the node // hasn't been responsive for a very long time - s.ns.SetField(node, sfiNodeHistory, nil) - s.ns.SetField(node, sfiNodeWeight, nil) - s.ns.SetState(node, nodestate.Flags{}, sfHasValue, 0) + s.ns.SetFieldSub(node, sfiNodeHistory, nil) + s.ns.SetFieldSub(node, sfiNodeWeight, nil) + s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) } } // calculateWeight calculates and sets the node weight without altering the node history. // This function should be called during startup and shutdown only, otherwise setRedialWait // will keep the weights updated as the underlying statistics are adjusted. +// Note: this function should run inside a NodeStateMachine operation func (s *serverPool) calculateWeight(node *enode.Node) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) _, totalValue := s.serviceValue(node) diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index 7091281aea..ab28b47a15 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -32,34 +32,46 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +var ( + ErrInvalidField = errors.New("invalid field type") + ErrClosed = errors.New("already closed") +) + type ( - // NodeStateMachine connects different system components operating on subsets of - // network nodes. Node states are represented by 64 bit vectors with each bit assigned - // to a state flag. Each state flag has a descriptor structure and the mapping is - // created automatically. It is possible to subscribe to subsets of state flags and - // receive a callback if one of the nodes has a relevant state flag changed. - // Callbacks can also modify further flags of the same node or other nodes. State - // updates only return after all immediate effects throughout the system have happened - // (deadlocks should be avoided by design of the implemented state logic). The caller - // can also add timeouts assigned to a certain node and a subset of state flags. - // If the timeout elapses, the flags are reset. If all relevant flags are reset then - // the timer is dropped. State flags with no timeout are persisted in the database - // if the flag descriptor enables saving. If a node has no state flags set at any - // moment then it is discarded. - // - // Extra node fields can also be registered so system components can also store more - // complex state for each node that is relevant to them, without creating a custom - // peer set. Fields can be shared across multiple components if they all know the - // field ID. Subscription to fields is also possible. Persistent fields should have - // an encoder and a decoder function. + // NodeStateMachine implements a network node-related event subscription system. + // It can assign binary state flags and fields of arbitrary type to each node and allows + // subscriptions to flag/field changes which can also modify further flags and fields, + // potentially triggering further subscriptions. An operation includes an initial change + // and all resulting subsequent changes and always ends in a consistent global state. + // It is initiated by a "top level" SetState/SetField call that blocks (also blocking other + // top-level functions) until the operation is finished. Callbacks making further changes + // should use the non-blocking SetStateSub/SetFieldSub functions. The tree of events + // resulting from the initial changes is traversed in a breadth-first order, ensuring for + // each subscription callback that all other callbacks caused by the same change triggering + // the current callback are processed before anything is triggered by the changes made in the + // current callback. In practice this logic ensures that all subscriptions "see" events in + // the logical order, callbacks are never called concurrently and "back and forth" effects + // are also possible. The state machine design should ensure that infinite event cycles + // cannot happen. + // The caller can also add timeouts assigned to a certain node and a subset of state flags. + // If the timeout elapses, the flags are reset. If all relevant flags are reset then the timer + // is dropped. State flags with no timeout are persisted in the database if the flag + // descriptor enables saving. If a node has no state flags set at any moment then it is discarded. + // Note: in order to avoid mutex deadlocks the callbacks should never lock a mutex that + // might be locked when the top level SetState/SetField functions are called. If a function + // potentially performs state/field changes then it is recommended to mention this fact in the + // function description, along with whether it should run inside an operation callback. NodeStateMachine struct { - started, stopped bool + started, closed bool lock sync.Mutex clock mclock.Clock db ethdb.KeyValueStore dbNodeKey []byte nodes map[enode.ID]*nodeInfo offlineCallbackList []offlineCallback + opFlag bool // an operation has started + opWait *sync.Cond // signaled when the operation ends + opPending []func() // pending callback list of the current operation // Registered state flags or fields. Modifications are allowed // only when the node state machine has not been started. @@ -128,11 +140,12 @@ type ( // nodeInfo contains node state, fields and state timeouts nodeInfo struct { - node *enode.Node - state bitMask - timeouts []*nodeStateTimeout - fields []interface{} - db, dirty bool + node *enode.Node + state bitMask + timeouts []*nodeStateTimeout + fields []interface{} + fieldCount int + db, dirty bool } nodeInfoEnc struct { @@ -158,7 +171,7 @@ type ( } offlineCallback struct { - node *enode.Node + node *nodeInfo state bitMask fields []interface{} } @@ -319,10 +332,11 @@ func NewNodeStateMachine(db ethdb.KeyValueStore, dbKey []byte, clock mclock.Cloc nodes: make(map[enode.ID]*nodeInfo), fields: make([]*fieldInfo, len(setup.fields)), } + ns.opWait = sync.NewCond(&ns.lock) stateNameMap := make(map[string]int) for index, flag := range setup.flags { if _, ok := stateNameMap[flag.name]; ok { - panic("Node state flag name collision") + panic("Node state flag name collision: " + flag.name) } stateNameMap[flag.name] = index if flag.persistent { @@ -332,7 +346,7 @@ func NewNodeStateMachine(db ethdb.KeyValueStore, dbKey []byte, clock mclock.Cloc fieldNameMap := make(map[string]int) for index, field := range setup.fields { if _, ok := fieldNameMap[field.name]; ok { - panic("Node field name collision") + panic("Node field name collision: " + field.name) } ns.fields[index] = &fieldInfo{fieldDefinition: field} fieldNameMap[field.name] = index @@ -357,10 +371,12 @@ func (ns *NodeStateMachine) fieldIndex(field Field) int { } // SubscribeState adds a node state subscription. The callback is called while the state -// machine mutex is not held and it is allowed to make further state updates. All immediate -// changes throughout the system are processed in the same thread/goroutine. It is the -// responsibility of the implemented state logic to avoid deadlocks caused by the callbacks, -// infinite toggling of flags or hazardous/non-deterministic state changes. +// machine mutex is not held and it is allowed to make further state updates using the +// non-blocking SetStateSub/SetFieldSub functions. All callbacks of an operation are running +// from the thread/goroutine of the initial caller and parallel operations are not permitted. +// Therefore the callback is never called concurrently. It is the responsibility of the +// implemented state logic to avoid deadlocks and to reach a stable state in a finite amount +// of steps. // State subscriptions should be installed before loading the node database or making the // first state update. func (ns *NodeStateMachine) SubscribeState(flags Flags, callback StateCallback) { @@ -408,26 +424,33 @@ func (ns *NodeStateMachine) Start() { if ns.db != nil { ns.loadFromDb() } - ns.lock.Unlock() + + ns.opStart() ns.offlineCallbacks(true) + ns.opFinish() + ns.lock.Unlock() } // Stop stops the state machine and saves its state if a database was supplied func (ns *NodeStateMachine) Stop() { ns.lock.Lock() + defer ns.lock.Unlock() + + ns.checkStarted() + if !ns.opStart() { + panic("already closed") + } for _, node := range ns.nodes { fields := make([]interface{}, len(node.fields)) copy(fields, node.fields) - ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node.node, node.state, fields}) + ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields}) } - ns.stopped = true if ns.db != nil { ns.saveToDb() - ns.lock.Unlock() - } else { - ns.lock.Unlock() } ns.offlineCallbacks(false) + ns.closed = true + ns.opFinish() } // loadFromDb loads persisted node states from the database @@ -477,6 +500,7 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { if decode := ns.fields[i].decode; decode != nil { if field, err := decode(encField); err == nil { node.fields[i] = field + node.fieldCount++ } else { log.Error("Failed to decode node field", "id", id, "field name", ns.fields[i].name, "error", err) return @@ -491,7 +515,7 @@ func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) { node.state = enc.State fields := make([]interface{}, len(node.fields)) copy(fields, node.fields) - ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node.node, node.state, fields}) + ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields}) log.Debug("Loaded node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup}) } @@ -505,15 +529,6 @@ func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error { for _, t := range node.timeouts { storedState &= ^t.mask } - if storedState == 0 { - if node.db { - node.db = false - ns.deleteNode(id) - } - node.dirty = false - return nil - } - enc := nodeInfoEnc{ Enr: *node.node.Record(), Version: ns.setup.Version, @@ -537,6 +552,14 @@ func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error { enc.Fields[i] = blob lastIndex = i } + if storedState == 0 && lastIndex == -1 { + if node.db { + node.db = false + ns.deleteNode(id) + } + node.dirty = false + return nil + } enc.Fields = enc.Fields[:lastIndex+1] data, err := rlp.EncodeToBytes(&enc) if err != nil { @@ -596,23 +619,36 @@ func (ns *NodeStateMachine) Persist(n *enode.Node) error { return nil } -// SetState updates the given node state flags and processes all resulting callbacks. -// It only returns after all subsequent immediate changes (including those changed by the -// callbacks) have been processed. If a flag with a timeout is set again, the operation -// removes or replaces the existing timeout. -func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) { +// SetState updates the given node state flags and blocks until the operation is finished. +// If a flag with a timeout is set again, the operation removes or replaces the existing timeout. +func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) error { ns.lock.Lock() - ns.checkStarted() - if ns.stopped { - ns.lock.Unlock() - return + defer ns.lock.Unlock() + + if !ns.opStart() { + return ErrClosed } + ns.setState(n, setFlags, resetFlags, timeout) + ns.opFinish() + return nil +} +// SetStateSub updates the given node state flags without blocking (should be called +// from a subscription/operation callback). +func (ns *NodeStateMachine) SetStateSub(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) { + ns.lock.Lock() + defer ns.lock.Unlock() + + ns.opCheck() + ns.setState(n, setFlags, resetFlags, timeout) +} + +func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) { + ns.checkStarted() set, reset := ns.stateMask(setFlags), ns.stateMask(resetFlags) id, node := ns.updateEnode(n) if node == nil { if set == 0 { - ns.lock.Unlock() return } node = ns.newNode(n) @@ -627,16 +663,14 @@ func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, // even they are not existent(it's noop). ns.removeTimeouts(node, set|reset) - // Register the timeout callback if the new state is not empty - // and timeout itself is required. - if timeout != 0 && newState != 0 { + // Register the timeout callback if required + if timeout != 0 && set != 0 { ns.addTimeout(n, set, timeout) } if newState == oldState { - ns.lock.Unlock() return } - if newState == 0 { + if newState == 0 && node.fieldCount == 0 { delete(ns.nodes, id) if node.db { ns.deleteNode(id) @@ -646,68 +680,118 @@ func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, node.dirty = true } } - ns.lock.Unlock() - // call state update subscription callbacks without holding the mutex - for _, sub := range ns.stateSubs { - if changed&sub.mask != 0 { - sub.callback(n, Flags{mask: oldState & sub.mask, setup: ns.setup}, Flags{mask: newState & sub.mask, setup: ns.setup}) - } - } - if newState == 0 { - // call field subscriptions for discarded fields - for i, v := range node.fields { - if v != nil { - f := ns.fields[i] - if len(f.subs) > 0 { - for _, cb := range f.subs { - cb(n, Flags{setup: ns.setup}, v, nil) - } - } + callback := func() { + for _, sub := range ns.stateSubs { + if changed&sub.mask != 0 { + sub.callback(n, Flags{mask: oldState & sub.mask, setup: ns.setup}, Flags{mask: newState & sub.mask, setup: ns.setup}) } } } + ns.opPending = append(ns.opPending, callback) +} + +// opCheck checks whether an operation is active +func (ns *NodeStateMachine) opCheck() { + if !ns.opFlag { + panic("Operation has not started") + } +} + +// opStart waits until other operations are finished and starts a new one +func (ns *NodeStateMachine) opStart() bool { + for ns.opFlag { + ns.opWait.Wait() + } + if ns.closed { + return false + } + ns.opFlag = true + return true +} + +// opFinish finishes the current operation by running all pending callbacks. +// Callbacks resulting from a state/field change performed in a previous callback are always +// put at the end of the pending list and therefore processed after all callbacks resulting +// from the previous state/field change. +func (ns *NodeStateMachine) opFinish() { + for len(ns.opPending) != 0 { + list := ns.opPending + ns.lock.Unlock() + for _, cb := range list { + cb() + } + ns.lock.Lock() + ns.opPending = ns.opPending[len(list):] + } + ns.opPending = nil + ns.opFlag = false + ns.opWait.Signal() +} + +// Operation calls the given function as an operation callback. This allows the caller +// to start an operation with multiple initial changes. The same rules apply as for +// subscription callbacks. +func (ns *NodeStateMachine) Operation(fn func()) error { + ns.lock.Lock() + started := ns.opStart() + ns.lock.Unlock() + if !started { + return ErrClosed + } + fn() + ns.lock.Lock() + ns.opFinish() + ns.lock.Unlock() + return nil } // offlineCallbacks calls state update callbacks at startup or shutdown func (ns *NodeStateMachine) offlineCallbacks(start bool) { for _, cb := range ns.offlineCallbackList { - for _, sub := range ns.stateSubs { - offState := offlineState & sub.mask - onState := cb.state & sub.mask - if offState != onState { + cb := cb + callback := func() { + for _, sub := range ns.stateSubs { + offState := offlineState & sub.mask + onState := cb.state & sub.mask + if offState == onState { + continue + } if start { - sub.callback(cb.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup}) + sub.callback(cb.node.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup}) } else { - sub.callback(cb.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup}) + sub.callback(cb.node.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup}) } } - } - for i, f := range cb.fields { - if f != nil && ns.fields[i].subs != nil { + for i, f := range cb.fields { + if f == nil || ns.fields[i].subs == nil { + continue + } for _, fsub := range ns.fields[i].subs { if start { - fsub(cb.node, Flags{mask: offlineState, setup: ns.setup}, nil, f) + fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, nil, f) } else { - fsub(cb.node, Flags{mask: offlineState, setup: ns.setup}, f, nil) + fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, f, nil) } } } } + ns.opPending = append(ns.opPending, callback) } ns.offlineCallbackList = nil } // AddTimeout adds a node state timeout associated to the given state flag(s). // After the specified time interval, the relevant states will be reset. -func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time.Duration) { +func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time.Duration) error { ns.lock.Lock() defer ns.lock.Unlock() ns.checkStarted() - if ns.stopped { - return + if ns.closed { + return ErrClosed } ns.addTimeout(n, ns.stateMask(flags), timeout) + return nil } // addTimeout adds a node state timeout associated to the given state flag(s). @@ -756,13 +840,15 @@ func (ns *NodeStateMachine) removeTimeouts(node *nodeInfo, mask bitMask) { } } -// GetField retrieves the given field of the given node +// GetField retrieves the given field of the given node. Note that when used in a +// subscription callback the result can be out of sync with the state change represented +// by the callback parameters so extra safety checks might be necessary. func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} { ns.lock.Lock() defer ns.lock.Unlock() ns.checkStarted() - if ns.stopped { + if ns.closed { return nil } if _, node := ns.updateEnode(n); node != nil { @@ -771,48 +857,80 @@ func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} { return nil } -// SetField sets the given field of the given node +// SetField sets the given field of the given node and blocks until the operation is finished func (ns *NodeStateMachine) SetField(n *enode.Node, field Field, value interface{}) error { ns.lock.Lock() - ns.checkStarted() - if ns.stopped { - ns.lock.Unlock() - return nil + defer ns.lock.Unlock() + + if !ns.opStart() { + return ErrClosed } - _, node := ns.updateEnode(n) + err := ns.setField(n, field, value) + ns.opFinish() + return err +} + +// SetFieldSub sets the given field of the given node without blocking (should be called +// from a subscription/operation callback). +func (ns *NodeStateMachine) SetFieldSub(n *enode.Node, field Field, value interface{}) error { + ns.lock.Lock() + defer ns.lock.Unlock() + + ns.opCheck() + return ns.setField(n, field, value) +} + +func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface{}) error { + ns.checkStarted() + id, node := ns.updateEnode(n) if node == nil { - ns.lock.Unlock() - return nil + if value == nil { + return nil + } + node = ns.newNode(n) + ns.nodes[id] = node } fieldIndex := ns.fieldIndex(field) f := ns.fields[fieldIndex] if value != nil && reflect.TypeOf(value) != f.ftype { log.Error("Invalid field type", "type", reflect.TypeOf(value), "required", f.ftype) - ns.lock.Unlock() - return errors.New("invalid field type") + return ErrInvalidField } oldValue := node.fields[fieldIndex] if value == oldValue { - ns.lock.Unlock() return nil } + if oldValue != nil { + node.fieldCount-- + } + if value != nil { + node.fieldCount++ + } node.fields[fieldIndex] = value - if f.encode != nil { - node.dirty = true + if node.state == 0 && node.fieldCount == 0 { + delete(ns.nodes, id) + if node.db { + ns.deleteNode(id) + } + } else { + if f.encode != nil { + node.dirty = true + } } - state := node.state - ns.lock.Unlock() - if len(f.subs) > 0 { + callback := func() { for _, cb := range f.subs { cb(n, Flags{mask: state, setup: ns.setup}, oldValue, value) } } + ns.opPending = append(ns.opPending, callback) return nil } // ForEach calls the callback for each node having all of the required and none of the -// disabled flags set +// disabled flags set. +// Note that this callback is not an operation callback but ForEach can be called from an +// Operation callback or Operation can also be called from a ForEach callback if necessary. func (ns *NodeStateMachine) ForEach(requireFlags, disableFlags Flags, cb func(n *enode.Node, state Flags)) { ns.lock.Lock() ns.checkStarted() diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go index f6ff3ffc07..5f99a3da74 100644 --- a/p2p/nodestate/nodestate_test.go +++ b/p2p/nodestate/nodestate_test.go @@ -147,8 +147,13 @@ func TestSetField(t *testing.T) { // Set field before setting state ns.SetField(testNode(1), fields[0], "hello world") field := ns.GetField(testNode(1), fields[0]) + if field == nil { + t.Fatalf("Field should be set before setting states") + } + ns.SetField(testNode(1), fields[0], nil) + field = ns.GetField(testNode(1), fields[0]) if field != nil { - t.Fatalf("Field shouldn't be set before setting states") + t.Fatalf("Field should be unset") } // Set field after setting state ns.SetState(testNode(1), flags[0], Flags{}, 0) @@ -169,23 +174,6 @@ func TestSetField(t *testing.T) { } } -func TestUnsetField(t *testing.T) { - mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{} - - s, flags, fields := testSetup([]bool{false}, []reflect.Type{reflect.TypeOf("")}) - ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) - - ns.Start() - - ns.SetState(testNode(1), flags[0], Flags{}, time.Second) - ns.SetField(testNode(1), fields[0], "hello world") - - ns.SetState(testNode(1), Flags{}, flags[0], 0) - if field := ns.GetField(testNode(1), fields[0]); field != nil { - t.Fatalf("Field should be unset") - } -} - func TestSetState(t *testing.T) { mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{} @@ -339,6 +327,7 @@ func TestFieldSub(t *testing.T) { ns2.Start() check(s.OfflineFlag(), nil, uint64(100)) ns2.SetState(testNode(1), Flags{}, flags[0], 0) + ns2.SetField(testNode(1), fields[0], nil) check(Flags{}, uint64(100), nil) ns2.Stop() } @@ -387,3 +376,34 @@ func TestDuplicatedFlags(t *testing.T) { clock.Run(2 * time.Second) check(flags[0], Flags{}, true) } + +func TestCallbackOrder(t *testing.T) { + mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{} + + s, flags, _ := testSetup([]bool{false, false, false, false}, nil) + ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s) + + ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { + if newState.Equals(flags[0]) { + ns.SetStateSub(n, flags[1], Flags{}, 0) + ns.SetStateSub(n, flags[2], Flags{}, 0) + } + }) + ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) { + if newState.Equals(flags[1]) { + ns.SetStateSub(n, flags[3], Flags{}, 0) + } + }) + lastState := Flags{} + ns.SubscribeState(MergeFlags(flags[1], flags[2], flags[3]), func(n *enode.Node, oldState, newState Flags) { + if !oldState.Equals(lastState) { + t.Fatalf("Wrong callback order") + } + lastState = newState + }) + + ns.Start() + defer ns.Stop() + + ns.SetState(testNode(1), flags[0], Flags{}, 0) +} From 71c37d82adaa2b69ea98ce0c5505489d6b711c1e Mon Sep 17 00:00:00 2001 From: Julian Koh Date: Mon, 14 Sep 2020 13:57:28 +0100 Subject: [PATCH 023/948] js/tracers: make calltracer report value in selfdestructs (#21549) --- eth/tracers/internal/tracers/call_tracer.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/eth/tracers/internal/tracers/call_tracer.js b/eth/tracers/internal/tracers/call_tracer.js index 352c309b49..3ca7377738 100644 --- a/eth/tracers/internal/tracers/call_tracer.js +++ b/eth/tracers/internal/tracers/call_tracer.js @@ -61,7 +61,14 @@ if (this.callstack[left-1].calls === undefined) { this.callstack[left-1].calls = []; } - this.callstack[left-1].calls.push({type: op}); + this.callstack[left-1].calls.push({ + type: op, + from: toHex(log.contract.getAddress()), + to: toHex(toAddress(log.stack.peek(0).toString(16))), + gasIn: log.getGas(), + gasCost: log.getCost(), + value: '0x' + db.getBalance(log.contract.getAddress()).toString(16) + }); return } // If a new method invocation is being done, add to the call stack From f7112cc182ec9ec43ff56d4ff3c84d2518aa30ff Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 14 Sep 2020 19:23:01 +0200 Subject: [PATCH 024/948] rlp: add SplitUint64 (#21563) This can be useful when working with raw RLP data. --- rlp/raw.go | 26 ++++++++++++++++++++++++++ rlp/raw_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/rlp/raw.go b/rlp/raw.go index 2b3f328f66..c2a8517f62 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -57,6 +57,32 @@ func SplitString(b []byte) (content, rest []byte, err error) { return content, rest, nil } +// SplitUint64 decodes an integer at the beginning of b. +// It also returns the remaining data after the integer in 'rest'. +func SplitUint64(b []byte) (x uint64, rest []byte, err error) { + content, rest, err := SplitString(b) + if err != nil { + return 0, b, err + } + switch { + case len(content) == 0: + return 0, rest, nil + case len(content) == 1: + if content[0] == 0 { + return 0, b, ErrCanonInt + } + return uint64(content[0]), rest, nil + case len(content) > 8: + return 0, b, errUintOverflow + default: + x, err = readSize(content, byte(len(content))) + if err != nil { + return 0, b, ErrCanonInt + } + return x, rest, nil + } +} + // SplitList splits b into the content of a list and any remaining // bytes after the list. func SplitList(b []byte) (content, rest []byte, err error) { diff --git a/rlp/raw_test.go b/rlp/raw_test.go index 2aad042100..cdae4ff088 100644 --- a/rlp/raw_test.go +++ b/rlp/raw_test.go @@ -71,6 +71,49 @@ func TestSplitTypes(t *testing.T) { } } +func TestSplitUint64(t *testing.T) { + tests := []struct { + input string + val uint64 + rest string + err error + }{ + {"01", 1, "", nil}, + {"7FFF", 0x7F, "FF", nil}, + {"80FF", 0, "FF", nil}, + {"81FAFF", 0xFA, "FF", nil}, + {"82FAFAFF", 0xFAFA, "FF", nil}, + {"83FAFAFAFF", 0xFAFAFA, "FF", nil}, + {"84FAFAFAFAFF", 0xFAFAFAFA, "FF", nil}, + {"85FAFAFAFAFAFF", 0xFAFAFAFAFA, "FF", nil}, + {"86FAFAFAFAFAFAFF", 0xFAFAFAFAFAFA, "FF", nil}, + {"87FAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFA, "FF", nil}, + {"88FAFAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFAFA, "FF", nil}, + + // errors + {"", 0, "", io.ErrUnexpectedEOF}, + {"00", 0, "00", ErrCanonInt}, + {"81", 0, "81", ErrValueTooLarge}, + {"8100", 0, "8100", ErrCanonSize}, + {"8200FF", 0, "8200FF", ErrCanonInt}, + {"8103FF", 0, "8103FF", ErrCanonSize}, + {"89FAFAFAFAFAFAFAFAFAFF", 0, "89FAFAFAFAFAFAFAFAFAFF", errUintOverflow}, + } + + for i, test := range tests { + val, rest, err := SplitUint64(unhex(test.input)) + if val != test.val { + t.Errorf("test %d: val mismatch: got %x, want %x (input %q)", i, val, test.val, test.input) + } + if !bytes.Equal(rest, unhex(test.rest)) { + t.Errorf("test %d: rest mismatch: got %x, want %s (input %q)", i, rest, test.rest, test.input) + } + if err != test.err { + t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) + } + } +} + func TestSplit(t *testing.T) { tests := []struct { input string @@ -78,7 +121,9 @@ func TestSplit(t *testing.T) { val, rest string err error }{ + {input: "00FFFF", kind: Byte, val: "00", rest: "FFFF"}, {input: "01FFFF", kind: Byte, val: "01", rest: "FFFF"}, + {input: "7FFFFF", kind: Byte, val: "7F", rest: "FFFF"}, {input: "80FFFF", kind: String, val: "", rest: "FFFF"}, {input: "C3010203", kind: List, val: "010203"}, From 4996fce25a5d137aa570663e80be168ba37ce983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 14 Sep 2020 22:44:20 +0200 Subject: [PATCH 025/948] les, les/lespay/server: refactor client pool (#21236) * les, les/lespay/server: refactor client pool * les: use ns.Operation and sub calls where needed * les: fixed tests * les: removed active/inactive logic from peerSet * les: removed active/inactive peer logic * les: fixed linter warnings * les: fixed more linter errors and added missing metrics * les: addressed comments * cmd/geth: fixed TestPriorityClient * les: simplified clientPool state machine * les/lespay/server: do not use goroutine for balance callbacks * internal/web3ext: fix addBalance required parameters * les: removed freeCapacity, always connect at minCapacity initially * les: only allow capacity change with priority status Co-authored-by: rjl493456442 --- cmd/geth/les_test.go | 2 +- common/prque/lazyqueue.go | 49 +- internal/web3ext/web3ext.go | 2 +- les/api.go | 110 ++- les/api_test.go | 13 +- les/balance.go | 389 ---------- les/balance_test.go | 260 ------- les/clientpool.go | 972 +++++++------------------ les/clientpool_test.go | 462 ++++++------ les/lespay/server/balance.go | 609 ++++++++++++++++ les/lespay/server/balance_test.go | 400 ++++++++++ les/lespay/server/balance_tracker.go | 291 ++++++++ les/lespay/server/clientdb.go | 245 +++++++ les/lespay/server/clientdb_test.go | 144 ++++ les/lespay/server/prioritypool.go | 503 +++++++++++++ les/lespay/server/prioritypool_test.go | 129 ++++ les/metrics.go | 4 +- les/peer.go | 26 +- les/server.go | 22 +- les/server_handler.go | 17 +- les/test_helper.go | 4 +- les/utils/expiredvalue.go | 23 + les/utils/timeutils.go | 69 ++ les/utils/timeutils_test.go | 47 ++ 24 files changed, 3100 insertions(+), 1692 deletions(-) delete mode 100644 les/balance.go delete mode 100644 les/balance_test.go create mode 100644 les/lespay/server/balance.go create mode 100644 les/lespay/server/balance_test.go create mode 100644 les/lespay/server/balance_tracker.go create mode 100644 les/lespay/server/clientdb.go create mode 100644 les/lespay/server/clientdb_test.go create mode 100644 les/lespay/server/prioritypool.go create mode 100644 les/lespay/server/prioritypool_test.go create mode 100644 les/utils/timeutils.go create mode 100644 les/utils/timeutils_test.go diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index ae9ed5ecf5..259d4a8067 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -152,7 +152,7 @@ func TestPriorityClient(t *testing.T) { defer prioCli.killAndWait() // 3_000_000_000 once we move to Go 1.13 tokens := 3000000000 - lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens, "foobar") + lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens) prioCli.addPeer(lightServer) // Check if priority client is actually syncing and the regular client got kicked out diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go index 92ddd77f67..52403df464 100644 --- a/common/prque/lazyqueue.go +++ b/common/prque/lazyqueue.go @@ -36,14 +36,15 @@ type LazyQueue struct { // Items are stored in one of two internal queues ordered by estimated max // priority until the next and the next-after-next refresh. Update and Refresh // always places items in queue[1]. - queue [2]*sstack - popQueue *sstack - period time.Duration - maxUntil mclock.AbsTime - indexOffset int - setIndex SetIndexCallback - priority PriorityCallback - maxPriority MaxPriorityCallback + queue [2]*sstack + popQueue *sstack + period time.Duration + maxUntil mclock.AbsTime + indexOffset int + setIndex SetIndexCallback + priority PriorityCallback + maxPriority MaxPriorityCallback + lastRefresh1, lastRefresh2 mclock.AbsTime } type ( @@ -54,14 +55,17 @@ type ( // NewLazyQueue creates a new lazy queue func NewLazyQueue(setIndex SetIndexCallback, priority PriorityCallback, maxPriority MaxPriorityCallback, clock mclock.Clock, refreshPeriod time.Duration) *LazyQueue { q := &LazyQueue{ - popQueue: newSstack(nil), - setIndex: setIndex, - priority: priority, - maxPriority: maxPriority, - clock: clock, - period: refreshPeriod} + popQueue: newSstack(nil), + setIndex: setIndex, + priority: priority, + maxPriority: maxPriority, + clock: clock, + period: refreshPeriod, + lastRefresh1: clock.Now(), + lastRefresh2: clock.Now(), + } q.Reset() - q.Refresh() + q.refresh(clock.Now()) return q } @@ -71,9 +75,19 @@ func (q *LazyQueue) Reset() { q.queue[1] = newSstack(q.setIndex1) } -// Refresh should be called at least with the frequency specified by the refreshPeriod parameter +// Refresh performs queue re-evaluation if necessary func (q *LazyQueue) Refresh() { - q.maxUntil = q.clock.Now() + mclock.AbsTime(q.period) + now := q.clock.Now() + for time.Duration(now-q.lastRefresh2) >= q.period*2 { + q.refresh(now) + q.lastRefresh2 = q.lastRefresh1 + q.lastRefresh1 = now + } +} + +// refresh re-evaluates items in the older queue and swaps the two queues +func (q *LazyQueue) refresh(now mclock.AbsTime) { + q.maxUntil = now + mclock.AbsTime(q.period) for q.queue[0].Len() != 0 { q.Push(heap.Pop(q.queue[0]).(*item).value) } @@ -139,6 +153,7 @@ func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) boo } return } + nextIndex = q.peekIndex() // re-check because callback is allowed to push items back } } } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 41d8657787..300c2b054f 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -844,7 +844,7 @@ web3._extend({ new web3._extend.Method({ name: 'addBalance', call: 'les_addBalance', - params: 3 + params: 2 }), ], properties: diff --git a/les/api.go b/les/api.go index cd5c99a5f1..66d133b854 100644 --- a/les/api.go +++ b/les/api.go @@ -19,11 +19,11 @@ package les import ( "errors" "fmt" - "math" "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/mclock" + lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -31,16 +31,13 @@ var ( errNoCheckpoint = errors.New("no local checkpoint provided") errNotActivated = errors.New("checkpoint registrar is not activated") errUnknownBenchmarkType = errors.New("unknown benchmark type") - errBalanceOverflow = errors.New("balance overflow") errNoPriority = errors.New("priority too low to raise capacity") ) -const maxBalance = math.MaxInt64 - // PrivateLightServerAPI provides an API to access the LES light server. type PrivateLightServerAPI struct { server *LesServer - defaultPosFactors, defaultNegFactors priceFactors + defaultPosFactors, defaultNegFactors lps.PriceFactors } // NewPrivateLightServerAPI creates a new LES light server API. @@ -57,7 +54,6 @@ func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} { res := make(map[string]interface{}) res["minimumCapacity"] = api.server.minCapacity res["maximumCapacity"] = api.server.maxCapacity - res["freeClientCapacity"] = api.server.freeCapacity res["totalCapacity"], res["totalConnectedCapacity"], res["priorityConnectedCapacity"] = api.server.clientPool.capacityInfo() return res } @@ -65,9 +61,8 @@ func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} { // ClientInfo returns information about clients listed in the ids list or matching the given tags func (api *PrivateLightServerAPI) ClientInfo(ids []enode.ID) map[enode.ID]map[string]interface{} { res := make(map[enode.ID]map[string]interface{}) - api.server.clientPool.forClients(ids, func(client *clientInfo, id enode.ID) error { - res[id] = api.clientInfo(client, id) - return nil + api.server.clientPool.forClients(ids, func(client *clientInfo) { + res[client.node.ID()] = api.clientInfo(client) }) return res } @@ -80,48 +75,40 @@ func (api *PrivateLightServerAPI) ClientInfo(ids []enode.ID) map[enode.ID]map[st // assigned to it. func (api *PrivateLightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} { res := make(map[enode.ID]map[string]interface{}) - ids := api.server.clientPool.ndb.getPosBalanceIDs(start, stop, maxCount+1) + ids := api.server.clientPool.bt.GetPosBalanceIDs(start, stop, maxCount+1) if len(ids) > maxCount { res[ids[maxCount]] = make(map[string]interface{}) ids = ids[:maxCount] } if len(ids) != 0 { - api.server.clientPool.forClients(ids, func(client *clientInfo, id enode.ID) error { - res[id] = api.clientInfo(client, id) - return nil + api.server.clientPool.forClients(ids, func(client *clientInfo) { + res[client.node.ID()] = api.clientInfo(client) }) } return res } // clientInfo creates a client info data structure -func (api *PrivateLightServerAPI) clientInfo(c *clientInfo, id enode.ID) map[string]interface{} { +func (api *PrivateLightServerAPI) clientInfo(c *clientInfo) map[string]interface{} { info := make(map[string]interface{}) - if c != nil { - now := mclock.Now() - info["isConnected"] = true - info["connectionTime"] = float64(now-c.connectedAt) / float64(time.Second) - info["capacity"] = c.capacity - pb, nb := c.balanceTracker.getBalance(now) - info["pricing/balance"], info["pricing/negBalance"] = pb, nb - info["pricing/balanceMeta"] = c.balanceMetaInfo - info["priority"] = pb != 0 - } else { - info["isConnected"] = false - pb := api.server.clientPool.ndb.getOrNewPB(id) - info["pricing/balance"], info["pricing/balanceMeta"] = pb.value, pb.meta - info["priority"] = pb.value != 0 + pb, nb := c.balance.GetBalance() + info["isConnected"] = c.connected + info["pricing/balance"] = pb + info["priority"] = pb != 0 + // cb := api.server.clientPool.ndb.getCurrencyBalance(id) + // info["pricing/currency"] = cb.amount + if c.connected { + info["connectionTime"] = float64(mclock.Now()-c.connectedAt) / float64(time.Second) + info["capacity"], _ = api.server.clientPool.ns.GetField(c.node, priorityPoolSetup.CapacityField).(uint64) + info["pricing/negBalance"] = nb } return info } // setParams either sets the given parameters for a single connected client (if specified) // or the default parameters applicable to clients connected in the future -func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *priceFactors) (updateFactors bool, err error) { +func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *lps.PriceFactors) (updateFactors bool, err error) { defParams := client == nil - if !defParams { - posFactors, negFactors = &client.posFactors, &client.negFactors - } for name, value := range params { errValue := func() error { return fmt.Errorf("invalid value for parameter '%s'", name) @@ -137,20 +124,20 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien switch { case name == "pricing/timeFactor": - setFactor(&posFactors.timeFactor) + setFactor(&posFactors.TimeFactor) case name == "pricing/capacityFactor": - setFactor(&posFactors.capacityFactor) + setFactor(&posFactors.CapacityFactor) case name == "pricing/requestCostFactor": - setFactor(&posFactors.requestFactor) + setFactor(&posFactors.RequestFactor) case name == "pricing/negative/timeFactor": - setFactor(&negFactors.timeFactor) + setFactor(&negFactors.TimeFactor) case name == "pricing/negative/capacityFactor": - setFactor(&negFactors.capacityFactor) + setFactor(&negFactors.CapacityFactor) case name == "pricing/negative/requestCostFactor": - setFactor(&negFactors.requestFactor) + setFactor(&negFactors.RequestFactor) case !defParams && name == "capacity": if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity { - err = api.server.clientPool.setCapacity(client, uint64(capacity)) + _, err = api.server.clientPool.setCapacity(client.node, client.address, uint64(capacity), 0, true) // Don't have to call factor update explicitly. It's already done // in setCapacity function. } else { @@ -170,27 +157,25 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien return } -// AddBalance updates the balance of a client (either overwrites it or adds to it). -// It also updates the balance meta info string. -func (api *PrivateLightServerAPI) AddBalance(id enode.ID, value int64, meta string) ([2]uint64, error) { - oldBalance, newBalance, err := api.server.clientPool.addBalance(id, value, meta) - return [2]uint64{oldBalance, newBalance}, err -} - // SetClientParams sets client parameters for all clients listed in the ids list // or all connected clients if the list is empty func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[string]interface{}) error { - return api.server.clientPool.forClients(ids, func(client *clientInfo, id enode.ID) error { - if client != nil { - update, err := api.setParams(params, client, nil, nil) + var err error + api.server.clientPool.forClients(ids, func(client *clientInfo) { + if client.connected { + posFactors, negFactors := client.balance.GetPriceFactors() + update, e := api.setParams(params, client, &posFactors, &negFactors) if update { - client.updatePriceFactors() + client.balance.SetPriceFactors(posFactors, negFactors) + } + if e != nil { + err = e } - return err } else { - return fmt.Errorf("client %064x is not connected", id[:]) + err = fmt.Errorf("client %064x is not connected", client.node.ID()) } }) + return err } // SetDefaultParams sets the default parameters applicable to clients connected in the future @@ -214,6 +199,15 @@ func (api *PrivateLightServerAPI) SetConnectedBias(bias time.Duration) error { return nil } +// AddBalance adds the given amount to the balance of a client if possible and returns +// the balance before and after the operation +func (api *PrivateLightServerAPI) AddBalance(id enode.ID, amount int64) (balance [2]uint64, err error) { + api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { + balance[0], balance[1], err = c.balance.AddBalance(amount) + }) + return +} + // Benchmark runs a request performance benchmark with a given set of measurement setups // in multiple passes specified by passCount. The measurement time for each setup in each // pass is specified in milliseconds by length. @@ -304,13 +298,15 @@ func NewPrivateDebugAPI(server *LesServer) *PrivateDebugAPI { // FreezeClient forces a temporary client freeze which normally happens when the server is overloaded func (api *PrivateDebugAPI) FreezeClient(id enode.ID) error { - return api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo, id enode.ID) error { - if c == nil { - return fmt.Errorf("client %064x is not connected", id[:]) + var err error + api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { + if c.connected { + c.peer.freeze() + } else { + err = fmt.Errorf("client %064x is not connected", id[:]) } - c.peer.freezeClient() - return nil }) + return err } // PrivateLightAPI provides an API to access the LES light server or light client. diff --git a/les/api_test.go b/les/api_test.go index 7f6aca55cc..2895264f67 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -107,7 +107,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { t.Fatalf("Failed to obtain rpc client: %v", err) } headNum, headHash := getHead(ctx, t, serverRpcClient) - minCap, freeCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient) + minCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient) testCap := totalCap * 3 / 4 t.Logf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash) reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1))) @@ -202,7 +202,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { weights := make([]float64, len(clients)) for c := 0; c < 5; c++ { - setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), freeCap) + setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), minCap) freeIdx = rand.Intn(len(clients)) var sum float64 for i := range clients { @@ -214,7 +214,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { sum += weights[i] } for i, client := range clients { - weights[i] *= float64(testCap-freeCap-100) / sum + weights[i] *= float64(testCap-minCap-100) / sum capacity := uint64(weights[i]) if i != freeIdx && capacity < getCapacity(ctx, t, serverRpcClient, client.ID()) { setCapacity(ctx, t, serverRpcClient, client.ID(), capacity) @@ -227,7 +227,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { setCapacity(ctx, t, serverRpcClient, client.ID(), capacity) } } - weights[freeIdx] = float64(freeCap) + weights[freeIdx] = float64(minCap) for i := range clients { weights[i] /= float64(testCap) } @@ -247,7 +247,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { default: } - _, _, totalCap = getCapacityInfo(ctx, t, serverRpcClient) + _, totalCap = getCapacityInfo(ctx, t, serverRpcClient) if totalCap < testCap { t.Log("Total capacity underrun") close(stop) @@ -370,7 +370,7 @@ func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID return uint64(vv) } -func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, freeCap, totalCap uint64) { +func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, totalCap uint64) { var res map[string]interface{} if err := server.CallContext(ctx, &res, "les_serverInfo"); err != nil { t.Fatalf("Failed to query server info: %v", err) @@ -387,7 +387,6 @@ func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (min return uint64(vv) } minCap = decode("minimumCapacity") - freeCap = decode("freeClientCapacity") totalCap = decode("totalCapacity") return } diff --git a/les/balance.go b/les/balance.go deleted file mode 100644 index 51cef15c80..0000000000 --- a/les/balance.go +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -const ( - balanceCallbackQueue = iota - balanceCallbackZero - balanceCallbackCount -) - -// balanceTracker keeps track of the positive and negative balances of a connected -// client and calculates actual and projected future priority values required by -// prque.LazyQueue. -type balanceTracker struct { - lock sync.Mutex - clock mclock.Clock - stopped bool - capacity uint64 - balance balance - timeFactor, requestFactor float64 - negTimeFactor, negRequestFactor float64 - sumReqCost uint64 - lastUpdate, nextUpdate, initTime mclock.AbsTime - updateEvent mclock.Timer - // since only a limited and fixed number of callbacks are needed, they are - // stored in a fixed size array ordered by priority threshold. - callbacks [balanceCallbackCount]balanceCallback - // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active) - callbackIndex [balanceCallbackCount]int - callbackCount int // number of active callbacks -} - -// balance represents a pair of positive and negative balances -type balance struct { - pos, neg uint64 -} - -// balanceCallback represents a single callback that is activated when client priority -// reaches the given threshold -type balanceCallback struct { - id int - threshold int64 - callback func() -} - -// init initializes balanceTracker -func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) { - bt.clock = clock - bt.initTime, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps - for i := range bt.callbackIndex { - bt.callbackIndex[i] = -1 - } - bt.capacity = capacity -} - -// stop shuts down the balance tracker -func (bt *balanceTracker) stop(now mclock.AbsTime) { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.stopped = true - bt.addBalance(now) - bt.negTimeFactor = 0 - bt.negRequestFactor = 0 - bt.timeFactor = 0 - bt.requestFactor = 0 - if bt.updateEvent != nil { - bt.updateEvent.Stop() - bt.updateEvent = nil - } -} - -// balanceToPriority converts a balance to a priority value. Higher priority means -// first to disconnect. Positive balance translates to negative priority. If positive -// balance is zero then negative balance translates to a positive priority. -func (bt *balanceTracker) balanceToPriority(b balance) int64 { - if b.pos > 0 { - return ^int64(b.pos / bt.capacity) - } - return int64(b.neg) -} - -// reducedBalance estimates the reduced balance at a given time in the fututre based -// on the current balance, the time factor and an estimated average request cost per time ratio -func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64) balance { - dt := float64(at - bt.lastUpdate) - b := bt.balance - if b.pos != 0 { - factor := bt.timeFactor + bt.requestFactor*avgReqCost - diff := uint64(dt * factor) - if diff <= b.pos { - b.pos -= diff - dt = 0 - } else { - dt -= float64(b.pos) / factor - b.pos = 0 - } - } - if dt != 0 { - factor := bt.negTimeFactor + bt.negRequestFactor*avgReqCost - b.neg += uint64(dt * factor) - } - return b -} - -// timeUntil calculates the remaining time needed to reach a given priority level -// assuming that no requests are processed until then. If the given level is never -// reached then (0, false) is returned. -// Note: the function assumes that the balance has been recently updated and -// calculates the time starting from the last update. -func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) { - var dt float64 - if bt.balance.pos != 0 { - if bt.timeFactor < 1e-100 { - return 0, false - } - if priority < 0 { - newBalance := uint64(^priority) * bt.capacity - if newBalance > bt.balance.pos { - return 0, false - } - dt = float64(bt.balance.pos-newBalance) / bt.timeFactor - return time.Duration(dt), true - } else { - dt = float64(bt.balance.pos) / bt.timeFactor - } - } else { - if priority < 0 { - return 0, false - } - } - // if we have a positive balance then dt equals the time needed to get it to zero - if uint64(priority) > bt.balance.neg { - if bt.negTimeFactor < 1e-100 { - return 0, false - } - dt += float64(uint64(priority)-bt.balance.neg) / bt.negTimeFactor - } - return time.Duration(dt), true -} - -// setCapacity updates the capacity value used for priority calculation -func (bt *balanceTracker) setCapacity(capacity uint64) { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.capacity = capacity -} - -// getPriority returns the actual priority based on the current balance -func (bt *balanceTracker) getPriority(now mclock.AbsTime) int64 { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.addBalance(now) - return bt.balanceToPriority(bt.balance) -} - -// estimatedPriority gives an upper estimate for the priority at a given time in the future. -// If addReqCost is true then an average request cost per time is assumed that is twice the -// average cost per time in the current session. If false, zero request cost is assumed. -func (bt *balanceTracker) estimatedPriority(at mclock.AbsTime, addReqCost bool) int64 { - bt.lock.Lock() - defer bt.lock.Unlock() - - var avgReqCost float64 - if addReqCost { - dt := time.Duration(bt.lastUpdate - bt.initTime) - if dt > time.Second { - avgReqCost = float64(bt.sumReqCost) * 2 / float64(dt) - } - } - return bt.balanceToPriority(bt.reducedBalance(at, avgReqCost)) -} - -// addBalance updates balance based on the time factor -func (bt *balanceTracker) addBalance(now mclock.AbsTime) { - if now > bt.lastUpdate { - bt.balance = bt.reducedBalance(now, 0) - bt.lastUpdate = now - } -} - -// checkCallbacks checks whether the threshold of any of the active callbacks -// have been reached and calls them if necessary. It also sets up or updates -// a scheduled event to ensure that is will be called again just after the next -// threshold has been reached. -// Note: checkCallbacks assumes that the balance has been recently updated. -func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) { - if bt.callbackCount == 0 { - return - } - pri := bt.balanceToPriority(bt.balance) - for bt.callbackCount != 0 && bt.callbacks[bt.callbackCount-1].threshold <= pri { - bt.callbackCount-- - bt.callbackIndex[bt.callbacks[bt.callbackCount].id] = -1 - go bt.callbacks[bt.callbackCount].callback() - } - if bt.callbackCount != 0 { - d, ok := bt.timeUntil(bt.callbacks[bt.callbackCount-1].threshold) - if !ok { - bt.nextUpdate = 0 - bt.updateAfter(0) - return - } - if bt.nextUpdate == 0 || bt.nextUpdate > now+mclock.AbsTime(d) { - if d > time.Second { - // Note: if the scheduled update is not in the very near future then we - // schedule the update a bit earlier. This way we do need to update a few - // extra times but don't need to reschedule every time a processed request - // brings the expected firing time a little bit closer. - d = ((d - time.Second) * 7 / 8) + time.Second - } - bt.nextUpdate = now + mclock.AbsTime(d) - bt.updateAfter(d) - } - } else { - bt.nextUpdate = 0 - bt.updateAfter(0) - } -} - -// updateAfter schedules a balance update and callback check in the future -func (bt *balanceTracker) updateAfter(dt time.Duration) { - if bt.updateEvent == nil || bt.updateEvent.Stop() { - if dt == 0 { - bt.updateEvent = nil - } else { - bt.updateEvent = bt.clock.AfterFunc(dt, func() { - bt.lock.Lock() - defer bt.lock.Unlock() - - if bt.callbackCount != 0 { - now := bt.clock.Now() - bt.addBalance(now) - bt.checkCallbacks(now) - } - }) - } - } -} - -// requestCost should be called after serving a request for the given peer -func (bt *balanceTracker) requestCost(cost uint64) { - bt.lock.Lock() - defer bt.lock.Unlock() - - if bt.stopped { - return - } - now := bt.clock.Now() - bt.addBalance(now) - fcost := float64(cost) - - if bt.balance.pos != 0 { - if bt.requestFactor != 0 { - c := uint64(fcost * bt.requestFactor) - if bt.balance.pos >= c { - bt.balance.pos -= c - fcost = 0 - } else { - fcost *= 1 - float64(bt.balance.pos)/float64(c) - bt.balance.pos = 0 - } - bt.checkCallbacks(now) - } else { - fcost = 0 - } - } - if fcost > 0 { - if bt.negRequestFactor != 0 { - bt.balance.neg += uint64(fcost * bt.negRequestFactor) - bt.checkCallbacks(now) - } - } - bt.sumReqCost += cost -} - -// getBalance returns the current positive and negative balance -func (bt *balanceTracker) getBalance(now mclock.AbsTime) (uint64, uint64) { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.addBalance(now) - return bt.balance.pos, bt.balance.neg -} - -// setBalance sets the positive and negative balance to the given values -func (bt *balanceTracker) setBalance(pos, neg uint64) error { - bt.lock.Lock() - defer bt.lock.Unlock() - - now := bt.clock.Now() - bt.addBalance(now) - bt.balance.pos = pos - bt.balance.neg = neg - bt.checkCallbacks(now) - return nil -} - -// setFactors sets the price factors. timeFactor is the price of a nanosecond of -// connection while requestFactor is the price of a "realCost" unit. -func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) { - bt.lock.Lock() - defer bt.lock.Unlock() - - if bt.stopped { - return - } - now := bt.clock.Now() - bt.addBalance(now) - if neg { - bt.negTimeFactor = timeFactor - bt.negRequestFactor = requestFactor - } else { - bt.timeFactor = timeFactor - bt.requestFactor = requestFactor - } - bt.checkCallbacks(now) -} - -// setCallback sets up a one-time callback to be called when priority reaches -// the threshold. If it has already reached the threshold the callback is called -// immediately. -func (bt *balanceTracker) addCallback(id int, threshold int64, callback func()) { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.removeCb(id) - idx := 0 - for idx < bt.callbackCount && threshold < bt.callbacks[idx].threshold { - idx++ - } - for i := bt.callbackCount - 1; i >= idx; i-- { - bt.callbackIndex[bt.callbacks[i].id]++ - bt.callbacks[i+1] = bt.callbacks[i] - } - bt.callbackCount++ - bt.callbackIndex[id] = idx - bt.callbacks[idx] = balanceCallback{id, threshold, callback} - now := bt.clock.Now() - bt.addBalance(now) - bt.checkCallbacks(now) -} - -// removeCallback removes the given callback and returns true if it was active -func (bt *balanceTracker) removeCallback(id int) bool { - bt.lock.Lock() - defer bt.lock.Unlock() - - return bt.removeCb(id) -} - -// removeCb removes the given callback and returns true if it was active -// Note: should be called while bt.lock is held -func (bt *balanceTracker) removeCb(id int) bool { - idx := bt.callbackIndex[id] - if idx == -1 { - return false - } - bt.callbackIndex[id] = -1 - for i := idx; i < bt.callbackCount-1; i++ { - bt.callbackIndex[bt.callbacks[i+1].id]-- - bt.callbacks[i] = bt.callbacks[i+1] - } - bt.callbackCount-- - return true -} diff --git a/les/balance_test.go b/les/balance_test.go deleted file mode 100644 index b571c2cc5c..0000000000 --- a/les/balance_test.go +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -func TestSetBalance(t *testing.T) { - var clock = &mclock.Simulated{} - var inputs = []struct { - pos uint64 - neg uint64 - }{ - {1000, 0}, - {0, 1000}, - {1000, 1000}, - } - - tracker := balanceTracker{} - tracker.init(clock, 1000) - defer tracker.stop(clock.Now()) - - for _, i := range inputs { - tracker.setBalance(i.pos, i.neg) - pos, neg := tracker.getBalance(clock.Now()) - if pos != i.pos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) - } - if neg != i.neg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg) - } - } -} - -func TestBalanceTimeCost(t *testing.T) { - var ( - clock = &mclock.Simulated{} - tracker = balanceTracker{} - ) - tracker.init(clock, 1000) - defer tracker.stop(clock.Now()) - tracker.setFactors(false, 1, 1) - tracker.setFactors(true, 1, 1) - - tracker.setBalance(uint64(time.Minute), 0) // 1 minute time allowance - - var inputs = []struct { - runTime time.Duration - expPos uint64 - expNeg uint64 - }{ - {time.Second, uint64(time.Second * 59), 0}, - {0, uint64(time.Second * 59), 0}, - {time.Second * 59, 0, 0}, - {time.Second, 0, uint64(time.Second)}, - } - for _, i := range inputs { - clock.Run(i.runTime) - if pos, _ := tracker.getBalance(clock.Now()); pos != i.expPos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) - } - if _, neg := tracker.getBalance(clock.Now()); neg != i.expNeg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) - } - } - - tracker.setBalance(uint64(time.Minute), 0) // Refill 1 minute time allowance - for _, i := range inputs { - clock.Run(i.runTime) - if pos, _ := tracker.getBalance(clock.Now()); pos != i.expPos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) - } - if _, neg := tracker.getBalance(clock.Now()); neg != i.expNeg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) - } - } -} - -func TestBalanceReqCost(t *testing.T) { - var ( - clock = &mclock.Simulated{} - tracker = balanceTracker{} - ) - tracker.init(clock, 1000) - defer tracker.stop(clock.Now()) - tracker.setFactors(false, 1, 1) - tracker.setFactors(true, 1, 1) - - tracker.setBalance(uint64(time.Minute), 0) // 1 minute time serving time allowance - var inputs = []struct { - reqCost uint64 - expPos uint64 - expNeg uint64 - }{ - {uint64(time.Second), uint64(time.Second * 59), 0}, - {0, uint64(time.Second * 59), 0}, - {uint64(time.Second * 59), 0, 0}, - {uint64(time.Second), 0, uint64(time.Second)}, - } - for _, i := range inputs { - tracker.requestCost(i.reqCost) - if pos, _ := tracker.getBalance(clock.Now()); pos != i.expPos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) - } - if _, neg := tracker.getBalance(clock.Now()); neg != i.expNeg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) - } - } -} - -func TestBalanceToPriority(t *testing.T) { - var ( - clock = &mclock.Simulated{} - tracker = balanceTracker{} - ) - tracker.init(clock, 1000) // cap = 1000 - defer tracker.stop(clock.Now()) - tracker.setFactors(false, 1, 1) - tracker.setFactors(true, 1, 1) - - var inputs = []struct { - pos uint64 - neg uint64 - priority int64 - }{ - {1000, 0, ^int64(1)}, - {2000, 0, ^int64(2)}, // Higher balance, lower priority value - {0, 0, 0}, - {0, 1000, 1000}, - } - for _, i := range inputs { - tracker.setBalance(i.pos, i.neg) - priority := tracker.getPriority(clock.Now()) - if priority != i.priority { - t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority) - } - } -} - -func TestEstimatedPriority(t *testing.T) { - var ( - clock = &mclock.Simulated{} - tracker = balanceTracker{} - ) - tracker.init(clock, 1000000000) // cap = 1000,000,000 - defer tracker.stop(clock.Now()) - tracker.setFactors(false, 1, 1) - tracker.setFactors(true, 1, 1) - - tracker.setBalance(uint64(time.Minute), 0) - var inputs = []struct { - runTime time.Duration // time cost - futureTime time.Duration // diff of future time - reqCost uint64 // single request cost - priority int64 // expected estimated priority - }{ - {time.Second, time.Second, 0, ^int64(58)}, - {0, time.Second, 0, ^int64(58)}, - - // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost, - // 10^9 estimated request cost per second. - {time.Second, time.Second, 1000000000, ^int64(55)}, - - // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost, - // 4*10^9 estimated request cost. - {time.Second, 3 * time.Second, 1000000000, ^int64(48)}, - - // All positive balance is used up - {time.Second * 55, 0, 0, 0}, - - // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. - {0, time.Minute, 0, int64(time.Minute) + int64(time.Second)*120/29}, - } - for _, i := range inputs { - clock.Run(i.runTime) - tracker.requestCost(i.reqCost) - priority := tracker.estimatedPriority(clock.Now()+mclock.AbsTime(i.futureTime), true) - if priority != i.priority { - t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) - } - } -} - -func TestCallbackChecking(t *testing.T) { - var ( - clock = &mclock.Simulated{} - tracker = balanceTracker{} - ) - tracker.init(clock, 1000000) // cap = 1000,000 - defer tracker.stop(clock.Now()) - tracker.setFactors(false, 1, 1) - tracker.setFactors(true, 1, 1) - - var inputs = []struct { - priority int64 - expDiff time.Duration - }{ - {^int64(500), time.Millisecond * 500}, - {0, time.Second}, - {int64(time.Second), 2 * time.Second}, - } - tracker.setBalance(uint64(time.Second), 0) - for _, i := range inputs { - diff, _ := tracker.timeUntil(i.priority) - if diff != i.expDiff { - t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff) - } - } -} - -func TestCallback(t *testing.T) { - var ( - clock = &mclock.Simulated{} - tracker = balanceTracker{} - ) - tracker.init(clock, 1000) // cap = 1000 - defer tracker.stop(clock.Now()) - tracker.setFactors(false, 1, 1) - tracker.setFactors(true, 1, 1) - - callCh := make(chan struct{}, 1) - tracker.setBalance(uint64(time.Minute), 0) - tracker.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) - - clock.Run(time.Minute) - select { - case <-callCh: - case <-time.NewTimer(time.Second).C: - t.Fatalf("Callback hasn't been called yet") - } - - tracker.setBalance(uint64(time.Minute), 0) - tracker.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) - tracker.removeCallback(balanceCallbackZero) - - clock.Run(time.Minute) - select { - case <-callCh: - t.Fatalf("Callback shouldn't be called") - case <-time.NewTimer(time.Millisecond * 100).C: - } -} diff --git a/les/clientpool.go b/les/clientpool.go index 9c4060fc2c..4f6e3fafe0 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -17,34 +17,48 @@ package les import ( - "bytes" - "encoding/binary" "fmt" - "io" - "math" + "reflect" "sync" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/ethdb" + lps "github.com/ethereum/go-ethereum/les/lespay/server" + "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" - lru "github.com/hashicorp/golang-lru" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/nodestate" ) const ( - negBalanceExpTC = time.Hour // time constant for exponentially reducing negative balance - fixedPointMultiplier = 0x1000000 // constant to convert logarithms to fixed point format - lazyQueueRefresh = time.Second * 10 // refresh period of the connected queue - persistCumulativeTimeRefresh = time.Minute * 5 // refresh period of the cumulative running time persistence - posBalanceCacheLimit = 8192 // the maximum number of cached items in positive balance queue - negBalanceCacheLimit = 8192 // the maximum number of cached items in negative balance queue - defaultConnectedBias = time.Minute * 3 // the default connectedBias used in clientPool + defaultNegExpTC = 3600 // default time constant (in seconds) for exponentially reducing negative balance + + // defaultConnectedBias is applied to already connected clients So that + // already connected client won't be kicked out very soon and we + // can ensure all connected clients can have enough time to request + // or sync some data. + // + // todo(rjl493456442) make it configurable. It can be the option of + // free trial time! + defaultConnectedBias = time.Minute * 3 + inactiveTimeout = time.Second * 10 +) + +var ( + clientPoolSetup = &nodestate.Setup{} + clientField = clientPoolSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{})) + connAddressField = clientPoolSetup.NewField("connAddr", reflect.TypeOf("")) + balanceTrackerSetup = lps.NewBalanceTrackerSetup(clientPoolSetup) + priorityPoolSetup = lps.NewPriorityPoolSetup(clientPoolSetup) ) +func init() { + balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField) + priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority +} + // clientPool implements a client database that assigns a priority to each client // based on a positive and negative balance. Positive balance is externally assigned // to prioritized clients and is decreased with connection time and processed @@ -52,7 +66,7 @@ const ( // then negative balance is accumulated. // // Balance tracking and priority calculation for connected clients is done by -// balanceTracker. connectedQueue ensures that clients with the lowest positive or +// balanceTracker. activeQueue ensures that clients with the lowest positive or // highest negative balance get evicted when the total capacity allowance is full // and new clients with a better balance want to connect. // @@ -61,32 +75,24 @@ const ( // each client can have several minutes of connection time. // // Balances of disconnected clients are stored in nodeDB including positive balance -// and negative banalce. Negative balance is transformed into a logarithmic form -// with a constantly shifting linear offset in order to implement an exponential -// decrease. Besides nodeDB will have a background thread to check the negative -// balance of disconnected client. If the balance is low enough, then the record -// will be dropped. +// and negative banalce. Boeth positive balance and negative balance will decrease +// exponentially. If the balance is low enough, then the record will be dropped. type clientPool struct { - ndb *nodeDB + lps.BalanceTrackerSetup + lps.PriorityPoolSetup lock sync.Mutex clock mclock.Clock - stopCh chan struct{} closed bool removePeer func(enode.ID) + ns *nodestate.NodeStateMachine + pp *lps.PriorityPool + bt *lps.BalanceTracker - connectedMap map[enode.ID]*clientInfo - connectedQueue *prque.LazyQueue - - defaultPosFactors, defaultNegFactors priceFactors - - connLimit int // The maximum number of connections that clientpool can support - capLimit uint64 // The maximum cumulative capacity that clientpool can support - connectedCap uint64 // The sum of the capacity of the current clientpool connected - priorityConnected uint64 // The sum of the capacity of currently connected priority clients - freeClientCap uint64 // The capacity value of each free client - startTime mclock.AbsTime // The timestamp at which the clientpool started running - cumulativeTime int64 // The cumulative running time of clientpool at the start point. - connectedBias time.Duration // The connection bias. 0: Disable connection bias(used in testing) + defaultPosFactors, defaultNegFactors lps.PriceFactors + posExpTC, negExpTC uint64 + minCap uint64 // The minimal capacity value allowed for any client + connectedBias time.Duration + capLimit uint64 } // clientPoolPeer represents a client peer in the pool. @@ -95,264 +101,190 @@ type clientPool struct { // clients have a limited access to IP addresses while new node keys can be easily // generated so it would be useless to assign a negative value to them. type clientPoolPeer interface { - ID() enode.ID + Node() *enode.Node freeClientId() string updateCapacity(uint64) - freezeClient() + freeze() + allowInactive() bool } -// clientInfo represents a connected client +// clientInfo defines all information required by clientpool. type clientInfo struct { - address string - id enode.ID - connectedAt mclock.AbsTime - capacity uint64 - priority bool - pool *clientPool - peer clientPoolPeer - queueIndex int // position in connectedQueue - balanceTracker balanceTracker - posFactors, negFactors priceFactors - balanceMetaInfo string -} - -// connSetIndex callback updates clientInfo item index in connectedQueue -func connSetIndex(a interface{}, index int) { - a.(*clientInfo).queueIndex = index + node *enode.Node + address string + peer clientPoolPeer + connected, priority bool + connectedAt mclock.AbsTime + balance *lps.NodeBalance } -// connPriority callback returns actual priority of clientInfo item in connectedQueue -func connPriority(a interface{}, now mclock.AbsTime) int64 { - c := a.(*clientInfo) - return c.balanceTracker.getPriority(now) -} - -// connMaxPriority callback returns estimated maximum priority of clientInfo item in connectedQueue -func connMaxPriority(a interface{}, until mclock.AbsTime) int64 { - c := a.(*clientInfo) - pri := c.balanceTracker.estimatedPriority(until, true) - c.balanceTracker.addCallback(balanceCallbackQueue, pri+1, func() { - c.pool.lock.Lock() - if c.queueIndex != -1 { - c.pool.connectedQueue.Update(c.queueIndex) +// newClientPool creates a new client pool +func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { + ns := nodestate.NewNodeStateMachine(nil, nil, clock, clientPoolSetup) + pool := &clientPool{ + ns: ns, + BalanceTrackerSetup: balanceTrackerSetup, + PriorityPoolSetup: priorityPoolSetup, + clock: clock, + minCap: minCap, + connectedBias: connectedBias, + removePeer: removePeer, + } + pool.bt = lps.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) + pool.pp = lps.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) + + // set default expiration constants used by tests + // Note: server overwrites this if token sale is active + pool.bt.SetExpirationTCs(0, defaultNegExpTC) + + ns.SubscribeState(pool.InactiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + if newState.Equals(pool.InactiveFlag) { + ns.AddTimeout(node, pool.InactiveFlag, inactiveTimeout) + } + if oldState.Equals(pool.InactiveFlag) && newState.Equals(pool.InactiveFlag.Or(pool.PriorityFlag)) { + ns.SetStateSub(node, pool.InactiveFlag, nodestate.Flags{}, 0) // remove timeout } - c.pool.lock.Unlock() }) - return pri -} -// priceFactors determine the pricing policy (may apply either to positive or -// negative balances which may have different factors). -// - timeFactor is cost unit per nanosecond of connection time -// - capacityFactor is cost unit per nanosecond of connection time per 1000000 capacity -// - requestFactor is cost unit per request "realCost" unit -type priceFactors struct { - timeFactor, capacityFactor, requestFactor float64 -} + ns.SubscribeState(pool.ActiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + c, _ := ns.GetField(node, clientField).(*clientInfo) + if c == nil { + return + } + c.priority = newState.HasAll(pool.PriorityFlag) + if newState.Equals(pool.ActiveFlag) { + cap, _ := ns.GetField(node, pool.CapacityField).(uint64) + if cap > minCap { + pool.pp.RequestCapacity(node, minCap, 0, true) + } + } + }) -// newClientPool creates a new client pool -func newClientPool(db ethdb.Database, freeClientCap uint64, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { - ndb := newNodeDB(db, clock) - pool := &clientPool{ - ndb: ndb, - clock: clock, - connectedMap: make(map[enode.ID]*clientInfo), - connectedQueue: prque.NewLazyQueue(connSetIndex, connPriority, connMaxPriority, clock, lazyQueueRefresh), - freeClientCap: freeClientCap, - removePeer: removePeer, - startTime: clock.Now(), - cumulativeTime: ndb.getCumulativeTime(), - stopCh: make(chan struct{}), - connectedBias: defaultConnectedBias, - } - // If the negative balance of free client is even lower than 1, - // delete this entry. - ndb.nbEvictCallBack = func(now mclock.AbsTime, b negBalance) bool { - balance := math.Exp(float64(b.logValue-pool.logOffset(now)) / fixedPointMultiplier) - return balance <= 1 - } - go func() { - for { - select { - case <-clock.After(lazyQueueRefresh): - pool.lock.Lock() - pool.connectedQueue.Refresh() - pool.lock.Unlock() - case <-clock.After(persistCumulativeTimeRefresh): - pool.ndb.setCumulativeTime(pool.logOffset(clock.Now())) - case <-pool.stopCh: - return + ns.SubscribeState(pool.InactiveFlag.Or(pool.ActiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + if oldState.IsEmpty() { + clientConnectedMeter.Mark(1) + log.Debug("Client connected", "id", node.ID()) + } + if oldState.Equals(pool.InactiveFlag) && newState.Equals(pool.ActiveFlag) { + clientActivatedMeter.Mark(1) + log.Debug("Client activated", "id", node.ID()) + } + if oldState.Equals(pool.ActiveFlag) && newState.Equals(pool.InactiveFlag) { + clientDeactivatedMeter.Mark(1) + log.Debug("Client deactivated", "id", node.ID()) + c, _ := ns.GetField(node, clientField).(*clientInfo) + if c == nil || !c.peer.allowInactive() { + pool.removePeer(node.ID()) } } - }() + if newState.IsEmpty() { + clientDisconnectedMeter.Mark(1) + log.Debug("Client disconnected", "id", node.ID()) + pool.removePeer(node.ID()) + } + }) + + var totalConnected uint64 + ns.SubscribeField(pool.CapacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + oldCap, _ := oldValue.(uint64) + newCap, _ := newValue.(uint64) + totalConnected += newCap - oldCap + totalConnectedGauge.Update(int64(totalConnected)) + c, _ := ns.GetField(node, clientField).(*clientInfo) + if c != nil { + c.peer.updateCapacity(newCap) + } + }) + + ns.Start() return pool } // stop shuts the client pool down func (f *clientPool) stop() { - close(f.stopCh) f.lock.Lock() f.closed = true f.lock.Unlock() - f.ndb.setCumulativeTime(f.logOffset(f.clock.Now())) - f.ndb.close() + f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + // enforces saving all balances in BalanceTracker + f.disconnectNode(node) + }) + f.bt.Stop() + f.ns.Stop() } // connect should be called after a successful handshake. If the connection was // rejected, there is no need to call disconnect. -func (f *clientPool) connect(peer clientPoolPeer, capacity uint64) bool { +func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) { f.lock.Lock() defer f.lock.Unlock() // Short circuit if clientPool is already closed. if f.closed { - return false + return 0, fmt.Errorf("Client pool is already closed") } // Dedup connected peers. - id, freeID := peer.ID(), peer.freeClientId() - if _, ok := f.connectedMap[id]; ok { - clientRejectedMeter.Mark(1) - log.Debug("Client already connected", "address", freeID, "id", id.String()) - return false - } - // Create a clientInfo but do not add it yet - var ( - posBalance uint64 - negBalance uint64 - now = f.clock.Now() - ) - pb := f.ndb.getOrNewPB(id) - posBalance = pb.value - - nb := f.ndb.getOrNewNB(freeID) - if nb.logValue != 0 { - negBalance = uint64(math.Exp(float64(nb.logValue-f.logOffset(now))/fixedPointMultiplier) * float64(time.Second)) - } - e := &clientInfo{ - pool: f, - peer: peer, - address: freeID, - queueIndex: -1, - id: id, - connectedAt: now, - priority: posBalance != 0, - posFactors: f.defaultPosFactors, - negFactors: f.defaultNegFactors, - balanceMetaInfo: pb.meta, - } - // If the client is a free client, assign with a low free capacity, - // Otherwise assign with the given value(priority client) - if !e.priority || capacity == 0 { - capacity = f.freeClientCap + node, freeID := peer.Node(), peer.freeClientId() + if f.ns.GetField(node, clientField) != nil { + log.Debug("Client already connected", "address", freeID, "id", node.ID().String()) + return 0, fmt.Errorf("Client already connected address=%s id=%s", freeID, node.ID().String()) + } + now := f.clock.Now() + c := &clientInfo{ + node: node, + address: freeID, + peer: peer, + connected: true, + connectedAt: now, + } + f.ns.SetField(node, clientField, c) + f.ns.SetField(node, connAddressField, freeID) + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { + f.disconnect(peer) + return 0, nil + } + c.balance.SetPriceFactors(f.defaultPosFactors, f.defaultNegFactors) + + f.ns.SetState(node, f.InactiveFlag, nodestate.Flags{}, 0) + var allowed bool + f.ns.Operation(func() { + _, allowed = f.pp.RequestCapacity(node, f.minCap, f.connectedBias, true) + }) + if allowed { + return f.minCap, nil } - e.capacity = capacity - - // Starts a balance tracker - e.balanceTracker.init(f.clock, capacity) - e.balanceTracker.setBalance(posBalance, negBalance) - e.updatePriceFactors() - - // If the number of clients already connected in the clientpool exceeds its - // capacity, evict some clients with lowest priority. - // - // If the priority of the newly added client is lower than the priority of - // all connected clients, the client is rejected. - newCapacity := f.connectedCap + capacity - newCount := f.connectedQueue.Size() + 1 - if newCapacity > f.capLimit || newCount > f.connLimit { - var ( - kickList []*clientInfo - kickPriority int64 - ) - f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { - c := data.(*clientInfo) - kickList = append(kickList, c) - kickPriority = priority - newCapacity -= c.capacity - newCount-- - return newCapacity > f.capLimit || newCount > f.connLimit - }) - if newCapacity > f.capLimit || newCount > f.connLimit || (e.balanceTracker.estimatedPriority(now+mclock.AbsTime(f.connectedBias), false)-kickPriority) > 0 { - for _, c := range kickList { - f.connectedQueue.Push(c) - } - clientRejectedMeter.Mark(1) - log.Debug("Client rejected", "address", freeID, "id", id.String()) - return false - } - // accept new client, drop old ones - for _, c := range kickList { - f.dropClient(c, now, true) - } + if !peer.allowInactive() { + f.disconnect(peer) } + return 0, nil +} - // Register new client to connection queue. - f.connectedMap[id] = e - f.connectedQueue.Push(e) - f.connectedCap += e.capacity +// setConnectedBias sets the connection bias, which is applied to already connected clients +// So that already connected client won't be kicked out very soon and we can ensure all +// connected clients can have enough time to request or sync some data. +func (f *clientPool) setConnectedBias(bias time.Duration) { + f.lock.Lock() + defer f.lock.Unlock() - // If the current client is a paid client, monitor the status of client, - // downgrade it to normal client if positive balance is used up. - if e.priority { - f.priorityConnected += capacity - e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) - } - // If the capacity of client is not the default value(free capacity), notify - // it to update capacity. - if e.capacity != f.freeClientCap { - e.peer.updateCapacity(e.capacity) - } - totalConnectedGauge.Update(int64(f.connectedCap)) - clientConnectedMeter.Mark(1) - log.Debug("Client accepted", "address", freeID) - return true + f.connectedBias = bias + f.pp.SetActiveBias(bias) } // disconnect should be called when a connection is terminated. If the disconnection // was initiated by the pool itself using disconnectFn then calling disconnect is // not necessary but permitted. func (f *clientPool) disconnect(p clientPoolPeer) { - f.lock.Lock() - defer f.lock.Unlock() - - // Short circuit if client pool is already closed. - if f.closed { - return - } - // Short circuit if the peer hasn't been registered. - e := f.connectedMap[p.ID()] - if e == nil { - log.Debug("Client not connected", "address", p.freeClientId(), "id", p.ID().String()) - return - } - f.dropClient(e, f.clock.Now(), false) + f.disconnectNode(p.Node()) } -// forClients iterates through a list of clients, calling the callback for each one. -// If a client is not connected then clientInfo is nil. If the specified list is empty -// then the callback is called for all connected clients. -func (f *clientPool) forClients(ids []enode.ID, callback func(*clientInfo, enode.ID) error) error { - f.lock.Lock() - defer f.lock.Unlock() - - if len(ids) > 0 { - for _, id := range ids { - if err := callback(f.connectedMap[id], id); err != nil { - return err - } - } - } else { - for _, c := range f.connectedMap { - if err := callback(c, c.id); err != nil { - return err - } - } - } - return nil +// disconnectNode removes node fields and flags related to connected status +func (f *clientPool) disconnectNode(node *enode.Node) { + f.ns.SetField(node, connAddressField, nil) + f.ns.SetField(node, clientField, nil) } // setDefaultFactors sets the default price factors applied to subsequently connected clients -func (f *clientPool) setDefaultFactors(posFactors, negFactors priceFactors) { +func (f *clientPool) setDefaultFactors(posFactors, negFactors lps.PriceFactors) { f.lock.Lock() defer f.lock.Unlock() @@ -360,512 +292,110 @@ func (f *clientPool) setDefaultFactors(posFactors, negFactors priceFactors) { f.defaultNegFactors = negFactors } -// setConnectedBias sets the connection bias, which is applied to already connected clients -// So that already connected client won't be kicked out very soon and we can ensure all -// connected clients can have enough time to request or sync some data. -func (f *clientPool) setConnectedBias(bias time.Duration) { - f.lock.Lock() - defer f.lock.Unlock() - - f.connectedBias = bias -} - -// dropClient removes a client from the connected queue and finalizes its balance. -// If kick is true then it also initiates the disconnection. -func (f *clientPool) dropClient(e *clientInfo, now mclock.AbsTime, kick bool) { - if _, ok := f.connectedMap[e.id]; !ok { - return - } - f.finalizeBalance(e, now) - f.connectedQueue.Remove(e.queueIndex) - delete(f.connectedMap, e.id) - f.connectedCap -= e.capacity - if e.priority { - f.priorityConnected -= e.capacity - } - totalConnectedGauge.Update(int64(f.connectedCap)) - if kick { - clientKickedMeter.Mark(1) - log.Debug("Client kicked out", "address", e.address) - f.removePeer(e.id) - } else { - clientDisconnectedMeter.Mark(1) - log.Debug("Client disconnected", "address", e.address) - } -} - // capacityInfo returns the total capacity allowance, the total capacity of connected // clients and the total capacity of connected and prioritized clients func (f *clientPool) capacityInfo() (uint64, uint64, uint64) { f.lock.Lock() defer f.lock.Unlock() - return f.capLimit, f.connectedCap, f.priorityConnected -} - -// finalizeBalance stops the balance tracker, retrieves the final balances and -// stores them in posBalanceQueue and negBalanceQueue -func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) { - c.balanceTracker.stop(now) - pos, neg := c.balanceTracker.getBalance(now) - - pb, nb := f.ndb.getOrNewPB(c.id), f.ndb.getOrNewNB(c.address) - pb.value = pos - f.ndb.setPB(c.id, pb) - - neg /= uint64(time.Second) // Convert the expanse to second level. - if neg > 1 { - nb.logValue = int64(math.Log(float64(neg))*fixedPointMultiplier) + f.logOffset(now) - f.ndb.setNB(c.address, nb) - } else { - f.ndb.delNB(c.address) // Negative balance is small enough, drop it directly. - } -} - -// balanceExhausted callback is called by balanceTracker when positive balance is exhausted. -// It revokes priority status and also reduces the client capacity if necessary. -func (f *clientPool) balanceExhausted(id enode.ID) { - f.lock.Lock() - defer f.lock.Unlock() - - c := f.connectedMap[id] - if c == nil || !c.priority { - return - } - if c.priority { - f.priorityConnected -= c.capacity - } - c.priority = false - if c.capacity != f.freeClientCap { - f.connectedCap += f.freeClientCap - c.capacity - totalConnectedGauge.Update(int64(f.connectedCap)) - c.capacity = f.freeClientCap - c.balanceTracker.setCapacity(c.capacity) - c.peer.updateCapacity(c.capacity) - } - pb := f.ndb.getOrNewPB(id) - pb.value = 0 - f.ndb.setPB(id, pb) + // total priority active cap will be supported when the token issuer module is added + return f.capLimit, f.pp.ActiveCapacity(), 0 } -// setConnLimit sets the maximum number and total capacity of connected clients, +// setLimits sets the maximum number and total capacity of connected clients, // dropping some of them if necessary. func (f *clientPool) setLimits(totalConn int, totalCap uint64) { f.lock.Lock() defer f.lock.Unlock() - f.connLimit = totalConn f.capLimit = totalCap - if f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit { - f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { - f.dropClient(data.(*clientInfo), mclock.Now(), true) - return f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit - }) - } + f.pp.SetLimits(uint64(totalConn), totalCap) } // setCapacity sets the assigned capacity of a connected client -func (f *clientPool) setCapacity(c *clientInfo, capacity uint64) error { - if f.connectedMap[c.id] != c { - return fmt.Errorf("client %064x is not connected", c.id[:]) - } - if c.capacity == capacity { - return nil - } - if !c.priority { - return errNoPriority +func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint64, bias time.Duration, setCap bool) (uint64, error) { + c, _ := f.ns.GetField(node, clientField).(*clientInfo) + if c == nil { + if setCap { + return 0, fmt.Errorf("client %064x is not connected", node.ID()) + } + c = &clientInfo{node: node} + f.ns.SetField(node, clientField, c) + f.ns.SetField(node, connAddressField, freeID) + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { + log.Error("BalanceField is missing", "node", node.ID()) + return 0, fmt.Errorf("BalanceField of %064x is missing", node.ID()) + } + defer func() { + f.ns.SetField(node, connAddressField, nil) + f.ns.SetField(node, clientField, nil) + }() } - oldCapacity := c.capacity - c.capacity = capacity - f.connectedCap += capacity - oldCapacity - c.balanceTracker.setCapacity(capacity) - f.connectedQueue.Update(c.queueIndex) - if f.connectedCap > f.capLimit { - var kickList []*clientInfo - kick := true - f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool { - client := data.(*clientInfo) - kickList = append(kickList, client) - f.connectedCap -= client.capacity - if client == c { - kick = false - } - return kick && (f.connectedCap > f.capLimit) - }) - if kick { - now := mclock.Now() - for _, c := range kickList { - f.dropClient(c, now, true) - } - } else { - c.capacity = oldCapacity - c.balanceTracker.setCapacity(oldCapacity) - for _, c := range kickList { - f.connectedCap += c.capacity - f.connectedQueue.Push(c) - } - return errNoPriority + var ( + minPriority int64 + allowed bool + ) + f.ns.Operation(func() { + if !setCap || c.priority { + // check clientInfo.priority inside Operation to ensure thread safety + minPriority, allowed = f.pp.RequestCapacity(node, capacity, bias, setCap) } + }) + if allowed { + return 0, nil } - totalConnectedGauge.Update(int64(f.connectedCap)) - f.priorityConnected += capacity - oldCapacity - c.updatePriceFactors() - c.peer.updateCapacity(c.capacity) - return nil -} - -// requestCost feeds request cost after serving a request from the given peer. -func (f *clientPool) requestCost(p *clientPeer, cost uint64) { - f.lock.Lock() - defer f.lock.Unlock() - - info, exist := f.connectedMap[p.ID()] - if !exist || f.closed { - return + missing := c.balance.PosBalanceMissing(minPriority, capacity, bias) + if missing < 1 { + // ensure that we never return 0 missing and insufficient priority error + missing = 1 } - info.balanceTracker.requestCost(cost) + return missing, errNoPriority } -// logOffset calculates the time-dependent offset for the logarithmic -// representation of negative balance -// -// From another point of view, the result returned by the function represents -// the total time that the clientpool is cumulatively running(total_hours/multiplier). -func (f *clientPool) logOffset(now mclock.AbsTime) int64 { - // Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor - // is to avoid int64 overflow. We assume that int64(negBalanceExpTC) >> fixedPointMultiplier. - cumulativeTime := int64((time.Duration(now - f.startTime)) / (negBalanceExpTC / fixedPointMultiplier)) - return f.cumulativeTime + cumulativeTime -} - -// setClientPriceFactors sets the pricing factors for an individual connected client -func (c *clientInfo) updatePriceFactors() { - c.balanceTracker.setFactors(true, c.negFactors.timeFactor+float64(c.capacity)*c.negFactors.capacityFactor/1000000, c.negFactors.requestFactor) - c.balanceTracker.setFactors(false, c.posFactors.timeFactor+float64(c.capacity)*c.posFactors.capacityFactor/1000000, c.posFactors.requestFactor) -} - -// getPosBalance retrieves a single positive balance entry from cache or the database -func (f *clientPool) getPosBalance(id enode.ID) posBalance { +// setCapacityLocked is the equivalent of setCapacity used when f.lock is already locked +func (f *clientPool) setCapacityLocked(node *enode.Node, freeID string, capacity uint64, minConnTime time.Duration, setCap bool) (uint64, error) { f.lock.Lock() defer f.lock.Unlock() - return f.ndb.getOrNewPB(id) + return f.setCapacity(node, freeID, capacity, minConnTime, setCap) } -// addBalance updates the balance of a client (either overwrites it or adds to it). -// It also updates the balance meta info string. -func (f *clientPool) addBalance(id enode.ID, amount int64, meta string) (uint64, uint64, error) { +// forClients calls the supplied callback for either the listed node IDs or all connected +// nodes. It passes a valid clientInfo to the callback and ensures that the necessary +// fields and flags are set in order for BalanceTracker and PriorityPool to work even if +// the node is not connected. +func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { f.lock.Lock() defer f.lock.Unlock() - pb := f.ndb.getOrNewPB(id) - var negBalance uint64 - c := f.connectedMap[id] - if c != nil { - pb.value, negBalance = c.balanceTracker.getBalance(f.clock.Now()) - } - oldBalance := pb.value - if amount > 0 { - if amount > maxBalance || pb.value > maxBalance-uint64(amount) { - return oldBalance, oldBalance, errBalanceOverflow - } - pb.value += uint64(amount) + if len(ids) == 0 { + f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + c, _ := f.ns.GetField(node, clientField).(*clientInfo) + if c != nil { + cb(c) + } + }) } else { - if uint64(-amount) > pb.value { - pb.value = 0 - } else { - pb.value -= uint64(-amount) - } - } - pb.meta = meta - f.ndb.setPB(id, pb) - if c != nil { - c.balanceTracker.setBalance(pb.value, negBalance) - if !c.priority && pb.value > 0 { - // The capacity should be adjusted based on the requirement, - // but we have no idea about the new capacity, need a second - // call to update it. - c.priority = true - f.priorityConnected += c.capacity - c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) }) - } - // if balance is set to zero then reverting to non-priority status - // is handled by the balanceExhausted callback - c.balanceMetaInfo = meta - } - return oldBalance, pb.value, nil -} - -// posBalance represents a recently accessed positive balance entry -type posBalance struct { - value uint64 - meta string -} - -// EncodeRLP implements rlp.Encoder -func (e *posBalance) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{e.value, e.meta}) -} - -// DecodeRLP implements rlp.Decoder -func (e *posBalance) DecodeRLP(s *rlp.Stream) error { - var entry struct { - Value uint64 - Meta string - } - if err := s.Decode(&entry); err != nil { - return err - } - e.value = entry.Value - e.meta = entry.Meta - return nil -} - -// negBalance represents a negative balance entry of a disconnected client -type negBalance struct{ logValue int64 } - -// EncodeRLP implements rlp.Encoder -func (e *negBalance) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{uint64(e.logValue)}) -} - -// DecodeRLP implements rlp.Decoder -func (e *negBalance) DecodeRLP(s *rlp.Stream) error { - var entry struct { - LogValue uint64 - } - if err := s.Decode(&entry); err != nil { - return err - } - e.logValue = int64(entry.LogValue) - return nil -} - -const ( - // nodeDBVersion is the version identifier of the node data in db - // - // Changelog: - // * Replace `lastTotal` with `meta` in positive balance: version 0=>1 - nodeDBVersion = 1 - - // dbCleanupCycle is the cycle of db for useless data cleanup - dbCleanupCycle = time.Hour -) - -var ( - positiveBalancePrefix = []byte("pb:") // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance - negativeBalancePrefix = []byte("nb:") // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance - cumulativeRunningTimeKey = []byte("cumulativeTime:") // dbVersion(uint16 big endian) + cumulativeRunningTimeKey -> cumulativeTime -) - -type nodeDB struct { - db ethdb.Database - pcache *lru.Cache - ncache *lru.Cache - auxbuf []byte // 37-byte auxiliary buffer for key encoding - verbuf [2]byte // 2-byte auxiliary buffer for db version - nbEvictCallBack func(mclock.AbsTime, negBalance) bool // Callback to determine whether the negative balance can be evicted. - clock mclock.Clock - closeCh chan struct{} - cleanupHook func() // Test hook used for testing -} - -func newNodeDB(db ethdb.Database, clock mclock.Clock) *nodeDB { - pcache, _ := lru.New(posBalanceCacheLimit) - ncache, _ := lru.New(negBalanceCacheLimit) - ndb := &nodeDB{ - db: db, - pcache: pcache, - ncache: ncache, - auxbuf: make([]byte, 37), - clock: clock, - closeCh: make(chan struct{}), - } - binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion)) - go ndb.expirer() - return ndb -} - -func (db *nodeDB) close() { - close(db.closeCh) -} - -func (db *nodeDB) getPrefix(neg bool) []byte { - prefix := positiveBalancePrefix - if neg { - prefix = negativeBalancePrefix - } - return append(db.verbuf[:], prefix...) -} - -func (db *nodeDB) key(id []byte, neg bool) []byte { - prefix := positiveBalancePrefix - if neg { - prefix = negativeBalancePrefix - } - if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) { - db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...) - } - copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:]) - copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix) - copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id) - return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] -} - -func (db *nodeDB) getCumulativeTime() int64 { - blob, err := db.db.Get(append(cumulativeRunningTimeKey, db.verbuf[:]...)) - if err != nil || len(blob) == 0 { - return 0 - } - return int64(binary.BigEndian.Uint64(blob)) -} - -func (db *nodeDB) setCumulativeTime(v int64) { - binary.BigEndian.PutUint64(db.auxbuf[:8], uint64(v)) - db.db.Put(append(cumulativeRunningTimeKey, db.verbuf[:]...), db.auxbuf[:8]) -} - -func (db *nodeDB) getOrNewPB(id enode.ID) posBalance { - key := db.key(id.Bytes(), false) - item, exist := db.pcache.Get(string(key)) - if exist { - return item.(posBalance) - } - var balance posBalance - if enc, err := db.db.Get(key); err == nil { - if err := rlp.DecodeBytes(enc, &balance); err != nil { - log.Error("Failed to decode positive balance", "err", err) - } - } - db.pcache.Add(string(key), balance) - return balance -} - -func (db *nodeDB) setPB(id enode.ID, b posBalance) { - if b.value == 0 && len(b.meta) == 0 { - db.delPB(id) - return - } - key := db.key(id.Bytes(), false) - enc, err := rlp.EncodeToBytes(&(b)) - if err != nil { - log.Error("Failed to encode positive balance", "err", err) - return - } - db.db.Put(key, enc) - db.pcache.Add(string(key), b) -} - -func (db *nodeDB) delPB(id enode.ID) { - key := db.key(id.Bytes(), false) - db.db.Delete(key) - db.pcache.Remove(string(key)) -} - -// getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts -// with a positive balance -func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { - if maxCount <= 0 { - return - } - prefix := db.getPrefix(false) - it := db.db.NewIterator(prefix, start.Bytes()) - defer it.Release() - for i := len(stop[:]) - 1; i >= 0; i-- { - stop[i]-- - if stop[i] != 255 { - break - } - } - stopKey := db.key(stop.Bytes(), false) - keyLen := len(stopKey) - - for it.Next() { - var id enode.ID - if len(it.Key()) != keyLen || bytes.Compare(it.Key(), stopKey) == 1 { - return - } - copy(id[:], it.Key()[keyLen-len(id):]) - result = append(result, id) - if len(result) == maxCount { - return - } - } - return -} - -func (db *nodeDB) getOrNewNB(id string) negBalance { - key := db.key([]byte(id), true) - item, exist := db.ncache.Get(string(key)) - if exist { - return item.(negBalance) - } - var balance negBalance - if enc, err := db.db.Get(key); err == nil { - if err := rlp.DecodeBytes(enc, &balance); err != nil { - log.Error("Failed to decode negative balance", "err", err) - } - } - db.ncache.Add(string(key), balance) - return balance -} - -func (db *nodeDB) setNB(id string, b negBalance) { - key := db.key([]byte(id), true) - enc, err := rlp.EncodeToBytes(&(b)) - if err != nil { - log.Error("Failed to encode negative balance", "err", err) - return - } - db.db.Put(key, enc) - db.ncache.Add(string(key), b) -} - -func (db *nodeDB) delNB(id string) { - key := db.key([]byte(id), true) - db.db.Delete(key) - db.ncache.Remove(string(key)) -} - -func (db *nodeDB) expirer() { - for { - select { - case <-db.clock.After(dbCleanupCycle): - db.expireNodes() - case <-db.closeCh: - return - } - } -} - -// expireNodes iterates the whole node db and checks whether the negative balance -// entry can deleted. -// -// The rationale behind this is: server doesn't need to keep the negative balance -// records if they are low enough. -func (db *nodeDB) expireNodes() { - var ( - visited int - deleted int - start = time.Now() - prefix = db.getPrefix(true) - ) - iter := db.db.NewIterator(prefix, nil) - for iter.Next() { - visited += 1 - var balance negBalance - if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil { - log.Error("Failed to decode negative balance", "err", err) - continue - } - if db.nbEvictCallBack != nil && db.nbEvictCallBack(db.clock.Now(), balance) { - deleted += 1 - db.db.Delete(iter.Key()) + for _, id := range ids { + node := f.ns.GetNode(id) + if node == nil { + node = enode.SignNull(&enr.Record{}, id) + } + c, _ := f.ns.GetField(node, clientField).(*clientInfo) + if c != nil { + cb(c) + } else { + c = &clientInfo{node: node} + f.ns.SetField(node, clientField, c) + f.ns.SetField(node, connAddressField, "") + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance != nil { + cb(c) + } else { + log.Error("BalanceField is missing") + } + f.ns.SetField(node, connAddressField, nil) + f.ns.SetField(node, clientField, nil) + } } } - // Invoke testing hook if it's not nil. - if db.cleanupHook != nil { - db.cleanupHook() - } - log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start))) } diff --git a/les/clientpool_test.go b/les/clientpool_test.go index f8c4ef0c0c..cfd1486b43 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -17,17 +17,17 @@ package les import ( - "bytes" "fmt" - "math" "math/rand" - "reflect" "testing" "time" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/rawdb" + lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/nodestate" ) func TestClientPoolL10C100Free(t *testing.T) { @@ -56,29 +56,68 @@ func TestClientPoolL100C300P20(t *testing.T) { const testClientPoolTicks = 100000 -type poolTestPeer int +type poolTestPeer struct { + node *enode.Node + index int + disconnCh chan int + cap uint64 + inactiveAllowed bool +} -func (i poolTestPeer) ID() enode.ID { - return enode.ID{byte(i % 256), byte(i >> 8)} +func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer { + return &poolTestPeer{ + index: i, + disconnCh: disconnCh, + node: enode.SignNull(&enr.Record{}, enode.ID{byte(i % 256), byte(i >> 8)}), + } } -func (i poolTestPeer) freeClientId() string { - return fmt.Sprintf("addr #%d", i) +func (i *poolTestPeer) Node() *enode.Node { + return i.node } -func (i poolTestPeer) updateCapacity(uint64) {} +func (i *poolTestPeer) freeClientId() string { + return fmt.Sprintf("addr #%d", i.index) +} + +func (i *poolTestPeer) updateCapacity(cap uint64) { + i.cap = cap +} -type poolTestPeerWithCap struct { - poolTestPeer +func (i *poolTestPeer) freeze() {} - cap uint64 +func (i *poolTestPeer) allowInactive() bool { + return i.inactiveAllowed } -func (i *poolTestPeerWithCap) updateCapacity(cap uint64) { i.cap = cap } +func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) { + temp := pool.ns.GetField(p.node, clientField) == nil + if temp { + pool.ns.SetField(p.node, connAddressField, p.freeClientId()) + } + n, _ := pool.ns.GetField(p.node, pool.BalanceField).(*lps.NodeBalance) + pos, neg = n.GetBalance() + if temp { + pool.ns.SetField(p.node, connAddressField, nil) + } + return +} -func (i poolTestPeer) freezeClient() {} +func addBalance(pool *clientPool, id enode.ID, amount int64) { + pool.forClients([]enode.ID{id}, func(c *clientInfo) { + c.balance.AddBalance(amount) + }) +} -func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomDisconnect bool) { +func checkDiff(a, b uint64) bool { + maxDiff := (a + b) / 2000 + if maxDiff < 1 { + maxDiff = 1 + } + return a > b+maxDiff || b > a+maxDiff +} + +func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, randomDisconnect bool) { rand.Seed(time.Now().UnixNano()) var ( clock mclock.Simulated @@ -89,15 +128,15 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD disconnFn = func(id enode.ID) { disconnCh <- int(id[0]) + int(id[1])<<8 } - pool = newClientPool(db, 1, &clock, disconnFn) + pool = newClientPool(db, 1, 0, &clock, disconnFn) ) - pool.setConnectedBias(0) - pool.setLimits(connLimit, uint64(connLimit)) - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + + pool.setLimits(activeLimit, uint64(activeLimit)) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // pool should accept new peers up to its connected limit - for i := 0; i < connLimit; i++ { - if pool.connect(poolTestPeer(i), 0) { + for i := 0; i < activeLimit; i++ { + if cap, _ := pool.connect(newPoolTestPeer(i, disconnCh)); cap != 0 { connected[i] = true } else { t.Fatalf("Test peer #%d rejected", i) @@ -111,28 +150,30 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD // give a positive balance to some of the peers amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period for i := 0; i < paidCount; i++ { - pool.addBalance(poolTestPeer(i).ID(), amount, "") + addBalance(pool, newPoolTestPeer(i, disconnCh).node.ID(), amount) } } i := rand.Intn(clientCount) if connected[i] { if randomDisconnect { - pool.disconnect(poolTestPeer(i)) + pool.disconnect(newPoolTestPeer(i, disconnCh)) connected[i] = false connTicks[i] += tickCounter } } else { - if pool.connect(poolTestPeer(i), 0) { + if cap, _ := pool.connect(newPoolTestPeer(i, disconnCh)); cap != 0 { connected[i] = true connTicks[i] -= tickCounter + } else { + pool.disconnect(newPoolTestPeer(i, disconnCh)) } } pollDisconnects: for { select { case i := <-disconnCh: - pool.disconnect(poolTestPeer(i)) + pool.disconnect(newPoolTestPeer(i, disconnCh)) if connected[i] { connTicks[i] += tickCounter connected[i] = false @@ -143,10 +184,10 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD } } - expTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2*(connLimit-paidCount)/(clientCount-paidCount) + expTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2*(activeLimit-paidCount)/(clientCount-paidCount) expMin := expTicks - expTicks/5 expMax := expTicks + expTicks/5 - paidTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2 + paidTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2 paidMin := paidTicks - paidTicks/5 paidMax := paidTicks + paidTicks/5 @@ -167,22 +208,39 @@ func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomD pool.stop() } +func testPriorityConnect(t *testing.T, pool *clientPool, p *poolTestPeer, cap uint64, expSuccess bool) { + if cap, _ := pool.connect(p); cap == 0 { + if expSuccess { + t.Fatalf("Failed to connect paid client") + } else { + return + } + } + if _, err := pool.setCapacity(p.node, "", cap, defaultConnectedBias, true); err != nil { + if expSuccess { + t.Fatalf("Failed to raise capacity of paid client") + } else { + return + } + } + if !expSuccess { + t.Fatalf("Should reject high capacity paid client") + } +} + func TestConnectPaidClient(t *testing.T) { var ( clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, &clock, nil) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) defer pool.stop() pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client - pool.addBalance(poolTestPeer(0).ID(), 1000, "") - - if !pool.connect(poolTestPeer(0), 10) { - t.Fatalf("Failed to connect paid client") - } + addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) + testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 10, true) } func TestConnectPaidClientToSmallPool(t *testing.T) { @@ -190,18 +248,16 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, &clock, nil) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client - pool.addBalance(poolTestPeer(0).ID(), 1000, "") + addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) // Connect a fat paid client to pool, should reject it. - if pool.connect(poolTestPeer(0), 100) { - t.Fatalf("Connected fat paid client, should reject it") - } + testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 100, false) } func TestConnectPaidClientToFullPool(t *testing.T) { @@ -210,23 +266,23 @@ func TestConnectPaidClientToFullPool(t *testing.T) { db = rawdb.NewMemoryDatabase() ) removeFn := func(enode.ID) {} // Noop - pool := newClientPool(db, 1, &clock, removeFn) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.addBalance(poolTestPeer(i).ID(), 1000000000, "") - pool.connect(poolTestPeer(i), 1) + addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20)) + pool.connect(newPoolTestPeer(i, nil)) } - pool.addBalance(poolTestPeer(11).ID(), 1000, "") // Add low balance to new paid client - if pool.connect(poolTestPeer(11), 1) { + addBalance(pool, newPoolTestPeer(11, nil).node.ID(), int64(time.Second*2)) // Add low balance to new paid client + if cap, _ := pool.connect(newPoolTestPeer(11, nil)); cap != 0 { t.Fatalf("Low balance paid client should be rejected") } clock.Run(time.Second) - pool.addBalance(poolTestPeer(12).ID(), 1000000000*60*3, "") // Add high balance to new paid client - if !pool.connect(poolTestPeer(12), 1) { - t.Fatalf("High balance paid client should be accpected") + addBalance(pool, newPoolTestPeer(12, nil).node.ID(), int64(time.Minute*5)) // Add high balance to new paid client + if cap, _ := pool.connect(newPoolTestPeer(12, nil)); cap == 0 { + t.Fatalf("High balance paid client should be accepted") } } @@ -234,23 +290,25 @@ func TestPaidClientKickedOut(t *testing.T) { var ( clock mclock.Simulated db = rawdb.NewMemoryDatabase() - kickedCh = make(chan int, 1) + kickedCh = make(chan int, 100) ) - removeFn := func(id enode.ID) { kickedCh <- int(id[0]) } - pool := newClientPool(db, 1, &clock, removeFn) + removeFn := func(id enode.ID) { + kickedCh <- int(id[0]) + } + pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool.bt.SetExpirationTCs(0, 0) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.addBalance(poolTestPeer(i).ID(), 1000000000, "") // 1 second allowance - pool.connect(poolTestPeer(i), 1) + addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance + pool.connect(newPoolTestPeer(i, kickedCh)) clock.Run(time.Millisecond) } - clock.Run(time.Second) - clock.Run(defaultConnectedBias) - if !pool.connect(poolTestPeer(11), 0) { - t.Fatalf("Free client should be accectped") + clock.Run(defaultConnectedBias + time.Second*11) + if cap, _ := pool.connect(newPoolTestPeer(11, kickedCh)); cap == 0 { + t.Fatalf("Free client should be accepted") } select { case id := <-kickedCh: @@ -267,13 +325,14 @@ func TestConnectFreeClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, &clock, nil) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) defer pool.stop() pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) - if !pool.connect(poolTestPeer(0), 10) { + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + if cap, _ := pool.connect(newPoolTestPeer(0, nil)); cap == 0 { t.Fatalf("Failed to connect free client") } + testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 2, false) } func TestConnectFreeClientToFullPool(t *testing.T) { @@ -282,24 +341,24 @@ func TestConnectFreeClientToFullPool(t *testing.T) { db = rawdb.NewMemoryDatabase() ) removeFn := func(enode.ID) {} // Noop - pool := newClientPool(db, 1, &clock, removeFn) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.connect(poolTestPeer(i), 1) + pool.connect(newPoolTestPeer(i, nil)) } - if pool.connect(poolTestPeer(11), 1) { + if cap, _ := pool.connect(newPoolTestPeer(11, nil)); cap != 0 { t.Fatalf("New free client should be rejected") } clock.Run(time.Minute) - if pool.connect(poolTestPeer(12), 1) { + if cap, _ := pool.connect(newPoolTestPeer(12, nil)); cap != 0 { t.Fatalf("New free client should be rejected") } clock.Run(time.Millisecond) clock.Run(4 * time.Minute) - if !pool.connect(poolTestPeer(13), 1) { + if cap, _ := pool.connect(newPoolTestPeer(13, nil)); cap == 0 { t.Fatalf("Old client connects more than 5min should be kicked") } } @@ -308,24 +367,30 @@ func TestFreeClientKickedOut(t *testing.T) { var ( clock mclock.Simulated db = rawdb.NewMemoryDatabase() - kicked = make(chan int, 10) + kicked = make(chan int, 100) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } - pool := newClientPool(db, 1, &clock, removeFn) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.connect(poolTestPeer(i), 1) + pool.connect(newPoolTestPeer(i, kicked)) clock.Run(time.Millisecond) } - if pool.connect(poolTestPeer(10), 1) { + if cap, _ := pool.connect(newPoolTestPeer(10, kicked)); cap != 0 { t.Fatalf("New free client should be rejected") } + select { + case <-kicked: + case <-time.NewTimer(time.Second).C: + t.Fatalf("timeout") + } + pool.disconnect(newPoolTestPeer(10, kicked)) clock.Run(5 * time.Minute) for i := 0; i < 10; i++ { - pool.connect(poolTestPeer(i+10), 1) + pool.connect(newPoolTestPeer(i+10, kicked)) } for i := 0; i < 10; i++ { select { @@ -346,19 +411,19 @@ func TestPositiveBalanceCalculation(t *testing.T) { kicked = make(chan int, 10) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(db, 1, &clock, removeFn) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - pool.addBalance(poolTestPeer(0).ID(), int64(time.Minute*3), "") - pool.connect(poolTestPeer(0), 10) + addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3)) + testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true) clock.Run(time.Minute) - pool.disconnect(poolTestPeer(0)) - pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID()) - if pb.value != uint64(time.Minute*2) { - t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb.value) + pool.disconnect(newPoolTestPeer(0, kicked)) + pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) + if checkDiff(pb, uint64(time.Minute*2)) { + t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb) } } @@ -369,18 +434,16 @@ func TestDowngradePriorityClient(t *testing.T) { kicked = make(chan int, 10) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(db, 1, &clock, removeFn) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - p := &poolTestPeerWithCap{ - poolTestPeer: poolTestPeer(0), - } - pool.addBalance(p.ID(), int64(time.Minute), "") - pool.connect(p, 10) + p := newPoolTestPeer(0, kicked) + addBalance(pool, p.node.ID(), int64(time.Minute)) + testPriorityConnect(t, pool, p, 10, true) if p.cap != 10 { - t.Fatalf("The capcacity of priority peer hasn't been updated, got: %d", p.cap) + t.Fatalf("The capacity of priority peer hasn't been updated, got: %d", p.cap) } clock.Run(time.Minute) // All positive balance should be used up. @@ -388,156 +451,131 @@ func TestDowngradePriorityClient(t *testing.T) { if p.cap != 1 { t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap) } - pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID()) - if pb.value != 0 { - t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb.value) + pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) + if pb != 0 { + t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb) } - pool.addBalance(poolTestPeer(0).ID(), int64(time.Minute), "") - pb = pool.ndb.getOrNewPB(poolTestPeer(0).ID()) - if pb.value != uint64(time.Minute) { - t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb.value) + addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute)) + pb, _ = getBalance(pool, newPoolTestPeer(0, kicked)) + if checkDiff(pb, uint64(time.Minute)) { + t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb) } } func TestNegativeBalanceCalculation(t *testing.T) { var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - kicked = make(chan int, 10) + clock mclock.Simulated + db = rawdb.NewMemoryDatabase() ) - removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(db, 1, &clock, removeFn) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1}) + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.connect(poolTestPeer(i), 1) + pool.connect(newPoolTestPeer(i, nil)) } clock.Run(time.Second) for i := 0; i < 10; i++ { - pool.disconnect(poolTestPeer(i)) - nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId()) - if nb.logValue != 0 { + pool.disconnect(newPoolTestPeer(i, nil)) + _, nb := getBalance(pool, newPoolTestPeer(i, nil)) + if nb != 0 { t.Fatalf("Short connection shouldn't be recorded") } } - for i := 0; i < 10; i++ { - pool.connect(poolTestPeer(i), 1) + pool.connect(newPoolTestPeer(i, nil)) } clock.Run(time.Minute) for i := 0; i < 10; i++ { - pool.disconnect(poolTestPeer(i)) - nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId()) - nb.logValue -= pool.logOffset(clock.Now()) - nb.logValue /= fixedPointMultiplier - if nb.logValue != int64(math.Log(float64(time.Minute/time.Second))) { - t.Fatalf("Negative balance mismatch, want %v, got %v", int64(math.Log(float64(time.Minute/time.Second))), nb.logValue) + pool.disconnect(newPoolTestPeer(i, nil)) + _, nb := getBalance(pool, newPoolTestPeer(i, nil)) + if checkDiff(nb, uint64(time.Minute)/1000) { + t.Fatalf("Negative balance mismatch, want %v, got %v", uint64(time.Minute)/1000, nb) } } } -func TestNodeDB(t *testing.T) { - ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{}) - defer ndb.close() - - if !bytes.Equal(ndb.verbuf[:], []byte{0x00, nodeDBVersion}) { - t.Fatalf("version buffer mismatch, want %v, got %v", []byte{0x00, nodeDBVersion}, ndb.verbuf) - } - var cases = []struct { - id enode.ID - ip string - balance interface{} - positive bool - }{ - {enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 100}, true}, - {enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 200}, true}, - {enode.ID{}, "127.0.0.1", negBalance{logValue: 10}, false}, - {enode.ID{}, "127.0.0.1", negBalance{logValue: 20}, false}, - } - for _, c := range cases { - if c.positive { - ndb.setPB(c.id, c.balance.(posBalance)) - if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, c.balance.(posBalance)) { - t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance.(posBalance), pb) - } - } else { - ndb.setNB(c.ip, c.balance.(negBalance)) - if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, c.balance.(negBalance)) { - t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance.(negBalance), nb) - } - } - } - for _, c := range cases { - if c.positive { - ndb.delPB(c.id) - if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, posBalance{}) { - t.Fatalf("Positive balance mismatch, want %v, got %v", posBalance{}, pb) - } - } else { - ndb.delNB(c.ip) - if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, negBalance{}) { - t.Fatalf("Negative balance mismatch, want %v, got %v", negBalance{}, nb) - } - } - } - ndb.setCumulativeTime(100) - if ndb.getCumulativeTime() != 100 { - t.Fatalf("Cumulative time mismatch, want %v, got %v", 100, ndb.getCumulativeTime()) - } -} - -func TestNodeDBExpiration(t *testing.T) { +func TestInactiveClient(t *testing.T) { var ( - iterated int - done = make(chan struct{}, 1) + clock mclock.Simulated + db = rawdb.NewMemoryDatabase() ) - callback := func(now mclock.AbsTime, b negBalance) bool { - iterated += 1 - return true - } - clock := &mclock.Simulated{} - ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock) - defer ndb.close() - ndb.nbEvictCallBack = callback - ndb.cleanupHook = func() { done <- struct{}{} } - - var cases = []struct { - ip string - balance negBalance - }{ - {"127.0.0.1", negBalance{logValue: 1}}, - {"127.0.0.2", negBalance{logValue: 1}}, - {"127.0.0.3", negBalance{logValue: 1}}, - {"127.0.0.4", negBalance{logValue: 1}}, - } - for _, c := range cases { - ndb.setNB(c.ip, c.balance) - } - clock.WaitForTimers(1) - clock.Run(time.Hour + time.Minute) - select { - case <-done: - case <-time.NewTimer(time.Second).C: - t.Fatalf("timeout") - } - if iterated != 4 { - t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated) - } - clock.WaitForTimers(1) - for _, c := range cases { - ndb.setNB(c.ip, c.balance) - } - clock.Run(time.Hour + time.Minute) - select { - case <-done: - case <-time.NewTimer(time.Second).C: - t.Fatalf("timeout") - } - if iterated != 8 { - t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated) + pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + defer pool.stop() + pool.setLimits(2, uint64(2)) + + p1 := newPoolTestPeer(1, nil) + p1.inactiveAllowed = true + p2 := newPoolTestPeer(2, nil) + p2.inactiveAllowed = true + p3 := newPoolTestPeer(3, nil) + p3.inactiveAllowed = true + addBalance(pool, p1.node.ID(), 1000*int64(time.Second)) + addBalance(pool, p3.node.ID(), 2000*int64(time.Second)) + // p1: 1000 p2: 0 p3: 2000 + p1.cap, _ = pool.connect(p1) + if p1.cap != 1 { + t.Fatalf("Failed to connect peer #1") + } + p2.cap, _ = pool.connect(p2) + if p2.cap != 1 { + t.Fatalf("Failed to connect peer #2") + } + p3.cap, _ = pool.connect(p3) + if p3.cap != 1 { + t.Fatalf("Failed to connect peer #3") + } + if p2.cap != 0 { + t.Fatalf("Failed to deactivate peer #2") + } + addBalance(pool, p2.node.ID(), 3000*int64(time.Second)) + // p1: 1000 p2: 3000 p3: 2000 + if p2.cap != 1 { + t.Fatalf("Failed to activate peer #2") + } + if p1.cap != 0 { + t.Fatalf("Failed to deactivate peer #1") + } + addBalance(pool, p2.node.ID(), -2500*int64(time.Second)) + // p1: 1000 p2: 500 p3: 2000 + if p1.cap != 1 { + t.Fatalf("Failed to activate peer #1") + } + if p2.cap != 0 { + t.Fatalf("Failed to deactivate peer #2") + } + pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) + p4 := newPoolTestPeer(4, nil) + addBalance(pool, p4.node.ID(), 1500*int64(time.Second)) + // p1: 1000 p2: 500 p3: 2000 p4: 1500 + p4.cap, _ = pool.connect(p4) + if p4.cap != 1 { + t.Fatalf("Failed to activate peer #4") + } + if p1.cap != 0 { + t.Fatalf("Failed to deactivate peer #1") + } + clock.Run(time.Second * 600) + // manually trigger a check to avoid a long real-time wait + pool.ns.SetState(p1.node, pool.UpdateFlag, nodestate.Flags{}, 0) + pool.ns.SetState(p1.node, nodestate.Flags{}, pool.UpdateFlag, 0) + // p1: 1000 p2: 500 p3: 2000 p4: 900 + if p1.cap != 1 { + t.Fatalf("Failed to activate peer #1") + } + if p4.cap != 0 { + t.Fatalf("Failed to deactivate peer #4") + } + pool.disconnect(p2) + pool.disconnect(p4) + addBalance(pool, p1.node.ID(), -1000*int64(time.Second)) + if p1.cap != 1 { + t.Fatalf("Should not deactivate peer #1") + } + if p2.cap != 0 { + t.Fatalf("Should not activate peer #2") } } diff --git a/les/lespay/server/balance.go b/les/lespay/server/balance.go new file mode 100644 index 0000000000..f820a4ad05 --- /dev/null +++ b/les/lespay/server/balance.go @@ -0,0 +1,609 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "errors" + "math" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nodestate" +) + +var errBalanceOverflow = errors.New("balance overflow") + +const maxBalance = math.MaxInt64 // maximum allowed balance value + +const ( + balanceCallbackUpdate = iota // called when priority drops below the last minimum estimate + balanceCallbackZero // called when priority drops to zero (positive balance exhausted) + balanceCallbackCount // total number of balance callbacks +) + +// PriceFactors determine the pricing policy (may apply either to positive or +// negative balances which may have different factors). +// - TimeFactor is cost unit per nanosecond of connection time +// - CapacityFactor is cost unit per nanosecond of connection time per 1000000 capacity +// - RequestFactor is cost unit per request "realCost" unit +type PriceFactors struct { + TimeFactor, CapacityFactor, RequestFactor float64 +} + +// timePrice returns the price of connection per nanosecond at the given capacity +func (p PriceFactors) timePrice(cap uint64) float64 { + return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 +} + +// NodeBalance keeps track of the positive and negative balances of a connected +// client and calculates actual and projected future priority values. +// Implements nodePriority interface. +type NodeBalance struct { + bt *BalanceTracker + lock sync.RWMutex + node *enode.Node + connAddress string + active bool + priority bool + capacity uint64 + balance balance + posFactor, negFactor PriceFactors + sumReqCost uint64 + lastUpdate, nextUpdate, initTime mclock.AbsTime + updateEvent mclock.Timer + // since only a limited and fixed number of callbacks are needed, they are + // stored in a fixed size array ordered by priority threshold. + callbacks [balanceCallbackCount]balanceCallback + // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active) + callbackIndex [balanceCallbackCount]int + callbackCount int // number of active callbacks +} + +// balance represents a pair of positive and negative balances +type balance struct { + pos, neg utils.ExpiredValue +} + +// balanceCallback represents a single callback that is activated when client priority +// reaches the given threshold +type balanceCallback struct { + id int + threshold int64 + callback func() +} + +// GetBalance returns the current positive and negative balance. +func (n *NodeBalance) GetBalance() (uint64, uint64) { + n.lock.Lock() + defer n.lock.Unlock() + + now := n.bt.clock.Now() + n.updateBalance(now) + return n.balance.pos.Value(n.bt.posExp.LogOffset(now)), n.balance.neg.Value(n.bt.negExp.LogOffset(now)) +} + +// GetRawBalance returns the current positive and negative balance +// but in the raw(expired value) format. +func (n *NodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { + n.lock.Lock() + defer n.lock.Unlock() + + now := n.bt.clock.Now() + n.updateBalance(now) + return n.balance.pos, n.balance.neg +} + +// AddBalance adds the given amount to the positive balance and returns the balance +// before and after the operation. Exceeding maxBalance results in an error (balance is +// unchanged) while adding a negative amount higher than the current balance results in +// zero balance. +func (n *NodeBalance) AddBalance(amount int64) (uint64, uint64, error) { + var ( + err error + old, new uint64 + ) + n.bt.ns.Operation(func() { + var ( + callbacks []func() + setPriority bool + ) + n.bt.updateTotalBalance(n, func() bool { + now := n.bt.clock.Now() + n.updateBalance(now) + + // Ensure the given amount is valid to apply. + offset := n.bt.posExp.LogOffset(now) + old = n.balance.pos.Value(offset) + if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) { + err = errBalanceOverflow + return false + } + + // Update the total positive balance counter. + n.balance.pos.Add(amount, offset) + callbacks = n.checkCallbacks(now) + setPriority = n.checkPriorityStatus() + new = n.balance.pos.Value(offset) + n.storeBalance(true, false) + return true + }) + for _, cb := range callbacks { + cb() + } + if setPriority { + n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) + } + n.signalPriorityUpdate() + }) + if err != nil { + return old, old, err + } + + return old, new, nil +} + +// SetBalance sets the positive and negative balance to the given values +func (n *NodeBalance) SetBalance(pos, neg uint64) error { + if pos > maxBalance || neg > maxBalance { + return errBalanceOverflow + } + n.bt.ns.Operation(func() { + var ( + callbacks []func() + setPriority bool + ) + n.bt.updateTotalBalance(n, func() bool { + now := n.bt.clock.Now() + n.updateBalance(now) + + var pb, nb utils.ExpiredValue + pb.Add(int64(pos), n.bt.posExp.LogOffset(now)) + nb.Add(int64(neg), n.bt.negExp.LogOffset(now)) + n.balance.pos = pb + n.balance.neg = nb + callbacks = n.checkCallbacks(now) + setPriority = n.checkPriorityStatus() + n.storeBalance(true, true) + return true + }) + for _, cb := range callbacks { + cb() + } + if setPriority { + n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) + } + n.signalPriorityUpdate() + }) + return nil +} + +// RequestServed should be called after serving a request for the given peer +func (n *NodeBalance) RequestServed(cost uint64) uint64 { + n.lock.Lock() + var callbacks []func() + defer func() { + n.lock.Unlock() + if callbacks != nil { + n.bt.ns.Operation(func() { + for _, cb := range callbacks { + cb() + } + }) + } + }() + + now := n.bt.clock.Now() + n.updateBalance(now) + fcost := float64(cost) + + posExp := n.bt.posExp.LogOffset(now) + var check bool + if !n.balance.pos.IsZero() { + if n.posFactor.RequestFactor != 0 { + c := -int64(fcost * n.posFactor.RequestFactor) + cc := n.balance.pos.Add(c, posExp) + if c == cc { + fcost = 0 + } else { + fcost *= 1 - float64(cc)/float64(c) + } + check = true + } else { + fcost = 0 + } + } + if fcost > 0 { + if n.negFactor.RequestFactor != 0 { + n.balance.neg.Add(int64(fcost*n.negFactor.RequestFactor), n.bt.negExp.LogOffset(now)) + check = true + } + } + if check { + callbacks = n.checkCallbacks(now) + } + n.sumReqCost += cost + return n.balance.pos.Value(posExp) +} + +// Priority returns the actual priority based on the current balance +func (n *NodeBalance) Priority(now mclock.AbsTime, capacity uint64) int64 { + n.lock.Lock() + defer n.lock.Unlock() + + n.updateBalance(now) + return n.balanceToPriority(n.balance, capacity) +} + +// EstMinPriority gives a lower estimate for the priority at a given time in the future. +// An average request cost per time is assumed that is twice the average cost per time +// in the current session. +// If update is true then a priority callback is added that turns UpdateFlag on and off +// in case the priority goes below the estimated minimum. +func (n *NodeBalance) EstMinPriority(at mclock.AbsTime, capacity uint64, update bool) int64 { + n.lock.Lock() + defer n.lock.Unlock() + + var avgReqCost float64 + dt := time.Duration(n.lastUpdate - n.initTime) + if dt > time.Second { + avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) + } + pri := n.balanceToPriority(n.reducedBalance(at, capacity, avgReqCost), capacity) + if update { + n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) + } + return pri +} + +// PosBalanceMissing calculates the missing amount of positive balance in order to +// connect at targetCapacity, stay connected for the given amount of time and then +// still have a priority of targetPriority +func (n *NodeBalance) PosBalanceMissing(targetPriority int64, targetCapacity uint64, after time.Duration) uint64 { + n.lock.Lock() + defer n.lock.Unlock() + + now := n.bt.clock.Now() + if targetPriority < 0 { + timePrice := n.negFactor.timePrice(targetCapacity) + timeCost := uint64(float64(after) * timePrice) + negBalance := n.balance.neg.Value(n.bt.negExp.LogOffset(now)) + if timeCost+negBalance < uint64(-targetPriority) { + return 0 + } + if uint64(-targetPriority) > negBalance && timePrice > 1e-100 { + if negTime := time.Duration(float64(uint64(-targetPriority)-negBalance) / timePrice); negTime < after { + after -= negTime + } else { + after = 0 + } + } + targetPriority = 0 + } + timePrice := n.posFactor.timePrice(targetCapacity) + posRequired := uint64(float64(targetPriority)*float64(targetCapacity)+float64(after)*timePrice) + 1 + if posRequired >= maxBalance { + return math.MaxUint64 // target not reachable + } + posBalance := n.balance.pos.Value(n.bt.posExp.LogOffset(now)) + if posRequired > posBalance { + return posRequired - posBalance + } + return 0 +} + +// SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of +// connection while RequestFactor is the price of a request cost unit. +func (n *NodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { + n.lock.Lock() + now := n.bt.clock.Now() + n.updateBalance(now) + n.posFactor, n.negFactor = posFactor, negFactor + callbacks := n.checkCallbacks(now) + n.lock.Unlock() + if callbacks != nil { + n.bt.ns.Operation(func() { + for _, cb := range callbacks { + cb() + } + }) + } +} + +// GetPriceFactors returns the price factors +func (n *NodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) { + n.lock.Lock() + defer n.lock.Unlock() + + return n.posFactor, n.negFactor +} + +// activate starts time/capacity cost deduction. +func (n *NodeBalance) activate() { + n.bt.updateTotalBalance(n, func() bool { + if n.active { + return false + } + n.active = true + n.lastUpdate = n.bt.clock.Now() + return true + }) +} + +// deactivate stops time/capacity cost deduction and saves the balances in the database +func (n *NodeBalance) deactivate() { + n.bt.updateTotalBalance(n, func() bool { + if !n.active { + return false + } + n.updateBalance(n.bt.clock.Now()) + if n.updateEvent != nil { + n.updateEvent.Stop() + n.updateEvent = nil + } + n.storeBalance(true, true) + n.active = false + return true + }) +} + +// updateBalance updates balance based on the time factor +func (n *NodeBalance) updateBalance(now mclock.AbsTime) { + if n.active && now > n.lastUpdate { + n.balance = n.reducedBalance(now, n.capacity, 0) + n.lastUpdate = now + } +} + +// storeBalance stores the positive and/or negative balance of the node in the database +func (n *NodeBalance) storeBalance(pos, neg bool) { + if pos { + n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos) + } + if neg { + n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg) + } +} + +// addCallback sets up a one-time callback to be called when priority reaches +// the threshold. If it has already reached the threshold the callback is called +// immediately. +// Note: should be called while n.lock is held +// Note 2: the callback function runs inside a NodeStateMachine operation +func (n *NodeBalance) addCallback(id int, threshold int64, callback func()) { + n.removeCallback(id) + idx := 0 + for idx < n.callbackCount && threshold > n.callbacks[idx].threshold { + idx++ + } + for i := n.callbackCount - 1; i >= idx; i-- { + n.callbackIndex[n.callbacks[i].id]++ + n.callbacks[i+1] = n.callbacks[i] + } + n.callbackCount++ + n.callbackIndex[id] = idx + n.callbacks[idx] = balanceCallback{id, threshold, callback} + now := n.bt.clock.Now() + n.updateBalance(now) + n.scheduleCheck(now) +} + +// removeCallback removes the given callback and returns true if it was active +// Note: should be called while n.lock is held +func (n *NodeBalance) removeCallback(id int) bool { + idx := n.callbackIndex[id] + if idx == -1 { + return false + } + n.callbackIndex[id] = -1 + for i := idx; i < n.callbackCount-1; i++ { + n.callbackIndex[n.callbacks[i+1].id]-- + n.callbacks[i] = n.callbacks[i+1] + } + n.callbackCount-- + return true +} + +// checkCallbacks checks whether the threshold of any of the active callbacks +// have been reached and returns triggered callbacks. +// Note: checkCallbacks assumes that the balance has been recently updated. +func (n *NodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { + if n.callbackCount == 0 || n.capacity == 0 { + return + } + pri := n.balanceToPriority(n.balance, n.capacity) + for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri { + n.callbackCount-- + n.callbackIndex[n.callbacks[n.callbackCount].id] = -1 + callbacks = append(callbacks, n.callbacks[n.callbackCount].callback) + } + n.scheduleCheck(now) + return +} + +// scheduleCheck sets up or updates a scheduled event to ensure that it will be called +// again just after the next threshold has been reached. +func (n *NodeBalance) scheduleCheck(now mclock.AbsTime) { + if n.callbackCount != 0 { + d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold) + if !ok { + n.nextUpdate = 0 + n.updateAfter(0) + return + } + if n.nextUpdate == 0 || n.nextUpdate > now+mclock.AbsTime(d) { + if d > time.Second { + // Note: if the scheduled update is not in the very near future then we + // schedule the update a bit earlier. This way we do need to update a few + // extra times but don't need to reschedule every time a processed request + // brings the expected firing time a little bit closer. + d = ((d - time.Second) * 7 / 8) + time.Second + } + n.nextUpdate = now + mclock.AbsTime(d) + n.updateAfter(d) + } + } else { + n.nextUpdate = 0 + n.updateAfter(0) + } +} + +// updateAfter schedules a balance update and callback check in the future +func (n *NodeBalance) updateAfter(dt time.Duration) { + if n.updateEvent == nil || n.updateEvent.Stop() { + if dt == 0 { + n.updateEvent = nil + } else { + n.updateEvent = n.bt.clock.AfterFunc(dt, func() { + var callbacks []func() + n.lock.Lock() + if n.callbackCount != 0 { + now := n.bt.clock.Now() + n.updateBalance(now) + callbacks = n.checkCallbacks(now) + } + n.lock.Unlock() + if callbacks != nil { + n.bt.ns.Operation(func() { + for _, cb := range callbacks { + cb() + } + }) + } + }) + } + } +} + +// balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative) +// Note: this function should run inside a NodeStateMachine operation +func (n *NodeBalance) balanceExhausted() { + n.lock.Lock() + n.storeBalance(true, false) + n.priority = false + n.lock.Unlock() + n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.PriorityFlag, 0) +} + +// checkPriorityStatus checks whether the node has gained priority status and sets the priority +// callback and flag if necessary. It assumes that the balance has been recently updated. +// Note that the priority flag has to be set by the caller after the mutex has been released. +func (n *NodeBalance) checkPriorityStatus() bool { + if !n.priority && !n.balance.pos.IsZero() { + n.priority = true + n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() }) + return true + } + return false +} + +// signalPriorityUpdate signals that the priority fell below the previous minimum estimate +// Note: this function should run inside a NodeStateMachine operation +func (n *NodeBalance) signalPriorityUpdate() { + n.bt.ns.SetStateSub(n.node, n.bt.UpdateFlag, nodestate.Flags{}, 0) + n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.UpdateFlag, 0) +} + +// setCapacity updates the capacity value used for priority calculation +// Note: capacity should never be zero +// Note 2: this function should run inside a NodeStateMachine operation +func (n *NodeBalance) setCapacity(capacity uint64) { + n.lock.Lock() + now := n.bt.clock.Now() + n.updateBalance(now) + n.capacity = capacity + callbacks := n.checkCallbacks(now) + n.lock.Unlock() + for _, cb := range callbacks { + cb() + } +} + +// balanceToPriority converts a balance to a priority value. Lower priority means +// first to disconnect. Positive balance translates to positive priority. If positive +// balance is zero then negative balance translates to a negative priority. +func (n *NodeBalance) balanceToPriority(b balance, capacity uint64) int64 { + if !b.pos.IsZero() { + return int64(b.pos.Value(n.bt.posExp.LogOffset(n.bt.clock.Now())) / capacity) + } + return -int64(b.neg.Value(n.bt.negExp.LogOffset(n.bt.clock.Now()))) +} + +// reducedBalance estimates the reduced balance at a given time in the fututre based +// on the current balance, the time factor and an estimated average request cost per time ratio +func (n *NodeBalance) reducedBalance(at mclock.AbsTime, capacity uint64, avgReqCost float64) balance { + dt := float64(at - n.lastUpdate) + b := n.balance + if !b.pos.IsZero() { + factor := n.posFactor.timePrice(capacity) + n.posFactor.RequestFactor*avgReqCost + diff := -int64(dt * factor) + dd := b.pos.Add(diff, n.bt.posExp.LogOffset(at)) + if dd == diff { + dt = 0 + } else { + dt += float64(dd) / factor + } + } + if dt > 0 { + factor := n.negFactor.timePrice(capacity) + n.negFactor.RequestFactor*avgReqCost + b.neg.Add(int64(dt*factor), n.bt.negExp.LogOffset(at)) + } + return b +} + +// timeUntil calculates the remaining time needed to reach a given priority level +// assuming that no requests are processed until then. If the given level is never +// reached then (0, false) is returned. +// Note: the function assumes that the balance has been recently updated and +// calculates the time starting from the last update. +func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { + now := n.bt.clock.Now() + var dt float64 + if !n.balance.pos.IsZero() { + posBalance := n.balance.pos.Value(n.bt.posExp.LogOffset(now)) + timePrice := n.posFactor.timePrice(n.capacity) + if timePrice < 1e-100 { + return 0, false + } + if priority > 0 { + newBalance := uint64(priority) * n.capacity + if newBalance > posBalance { + return 0, false + } + dt = float64(posBalance-newBalance) / timePrice + return time.Duration(dt), true + } else { + dt = float64(posBalance) / timePrice + } + } else { + if priority > 0 { + return 0, false + } + } + // if we have a positive balance then dt equals the time needed to get it to zero + negBalance := n.balance.neg.Value(n.bt.negExp.LogOffset(now)) + timePrice := n.negFactor.timePrice(n.capacity) + if uint64(-priority) > negBalance { + if timePrice < 1e-100 { + return 0, false + } + dt += float64(uint64(-priority)-negBalance) / timePrice + } + return time.Duration(dt), true +} diff --git a/les/lespay/server/balance_test.go b/les/lespay/server/balance_test.go new file mode 100644 index 0000000000..67e1944373 --- /dev/null +++ b/les/lespay/server/balance_test.go @@ -0,0 +1,400 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "math/rand" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/nodestate" +) + +var ( + testFlag = testSetup.NewFlag("testFlag") + connAddrFlag = testSetup.NewField("connAddr", reflect.TypeOf("")) + btTestSetup = NewBalanceTrackerSetup(testSetup) +) + +func init() { + btTestSetup.Connect(connAddrFlag, ppTestSetup.CapacityField) +} + +type zeroExpirer struct{} + +func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64) {} +func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {} +func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64 { return 0 } + +type balanceTestSetup struct { + clock *mclock.Simulated + ns *nodestate.NodeStateMachine + bt *BalanceTracker +} + +func newBalanceTestSetup() *balanceTestSetup { + clock := &mclock.Simulated{} + ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + db := memorydb.New() + bt := NewBalanceTracker(ns, btTestSetup, db, clock, zeroExpirer{}, zeroExpirer{}) + ns.Start() + return &balanceTestSetup{ + clock: clock, + ns: ns, + bt: bt, + } +} + +func (b *balanceTestSetup) newNode(capacity uint64) *NodeBalance { + node := enode.SignNull(&enr.Record{}, enode.ID{}) + b.ns.SetState(node, testFlag, nodestate.Flags{}, 0) + b.ns.SetField(node, btTestSetup.connAddressField, "") + b.ns.SetField(node, ppTestSetup.CapacityField, capacity) + n, _ := b.ns.GetField(node, btTestSetup.BalanceField).(*NodeBalance) + return n +} + +func (b *balanceTestSetup) stop() { + b.bt.Stop() + b.ns.Stop() +} + +func TestAddBalance(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + + node := b.newNode(1000) + var inputs = []struct { + delta int64 + expect [2]uint64 + total uint64 + expectErr bool + }{ + {100, [2]uint64{0, 100}, 100, false}, + {-100, [2]uint64{100, 0}, 0, false}, + {-100, [2]uint64{0, 0}, 0, false}, + {1, [2]uint64{0, 1}, 1, false}, + {maxBalance, [2]uint64{0, 0}, 0, true}, + } + for _, i := range inputs { + old, new, err := node.AddBalance(i.delta) + if i.expectErr { + if err == nil { + t.Fatalf("Expect get error but nil") + } + continue + } else if err != nil { + t.Fatalf("Expect get no error but %v", err) + } + if old != i.expect[0] || new != i.expect[1] { + t.Fatalf("Positive balance mismatch, got %v -> %v", old, new) + } + if b.bt.TotalTokenAmount() != i.total { + t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount()) + } + } +} + +func TestSetBalance(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000) + + var inputs = []struct { + pos, neg uint64 + }{ + {1000, 0}, + {0, 1000}, + {1000, 1000}, + } + + for _, i := range inputs { + node.SetBalance(i.pos, i.neg) + pos, neg := node.GetBalance() + if pos != i.pos { + t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) + } + if neg != i.neg { + t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg) + } + } +} + +func TestBalanceTimeCost(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000) + + b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + node.SetBalance(uint64(time.Minute), 0) // 1 minute time allowance + + var inputs = []struct { + runTime time.Duration + expPos uint64 + expNeg uint64 + }{ + {time.Second, uint64(time.Second * 59), 0}, + {0, uint64(time.Second * 59), 0}, + {time.Second * 59, 0, 0}, + {time.Second, 0, uint64(time.Second)}, + } + for _, i := range inputs { + b.clock.Run(i.runTime) + if pos, _ := node.GetBalance(); pos != i.expPos { + t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) + } + if _, neg := node.GetBalance(); neg != i.expNeg { + t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) + } + } + + node.SetBalance(uint64(time.Minute), 0) // Refill 1 minute time allowance + for _, i := range inputs { + b.clock.Run(i.runTime) + if pos, _ := node.GetBalance(); pos != i.expPos { + t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) + } + if _, neg := node.GetBalance(); neg != i.expNeg { + t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) + } + } +} + +func TestBalanceReqCost(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + + b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) + node.SetBalance(uint64(time.Minute), 0) // 1 minute time serving time allowance + var inputs = []struct { + reqCost uint64 + expPos uint64 + expNeg uint64 + }{ + {uint64(time.Second), uint64(time.Second * 59), 0}, + {0, uint64(time.Second * 59), 0}, + {uint64(time.Second * 59), 0, 0}, + {uint64(time.Second), 0, uint64(time.Second)}, + } + for _, i := range inputs { + node.RequestServed(i.reqCost) + if pos, _ := node.GetBalance(); pos != i.expPos { + t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) + } + if _, neg := node.GetBalance(); neg != i.expNeg { + t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) + } + } +} + +func TestBalanceToPriority(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + + var inputs = []struct { + pos uint64 + neg uint64 + priority int64 + }{ + {1000, 0, 1}, + {2000, 0, 2}, // Higher balance, higher priority value + {0, 0, 0}, + {0, 1000, -1000}, + } + for _, i := range inputs { + node.SetBalance(i.pos, i.neg) + priority := node.Priority(b.clock.Now(), 1000) + if priority != i.priority { + t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority) + } + } +} + +func TestEstimatedPriority(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000000000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + + b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) + node.SetBalance(uint64(time.Minute), 0) + var inputs = []struct { + runTime time.Duration // time cost + futureTime time.Duration // diff of future time + reqCost uint64 // single request cost + priority int64 // expected estimated priority + }{ + {time.Second, time.Second, 0, 58}, + {0, time.Second, 0, 58}, + + // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost, + // 10^9 estimated request cost per second. + {time.Second, time.Second, 1000000000, 55}, + + // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost, + // 4*10^9 estimated request cost. + {time.Second, 3 * time.Second, 1000000000, 48}, + + // All positive balance is used up + {time.Second * 55, 0, 0, 0}, + + // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. + {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29}, + } + for _, i := range inputs { + b.clock.Run(i.runTime) + node.RequestServed(i.reqCost) + priority := node.EstMinPriority(b.clock.Now()+mclock.AbsTime(i.futureTime), 1000000000, false) + if priority != i.priority { + t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) + } + } +} + +func TestPosBalanceMissing(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + + b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) + var inputs = []struct { + pos, neg uint64 + priority int64 + cap uint64 + after time.Duration + expect uint64 + }{ + {uint64(time.Second * 2), 0, 0, 1, time.Second, 0}, + {uint64(time.Second * 2), 0, 0, 1, 2 * time.Second, 1}, + {uint64(time.Second * 2), 0, int64(time.Second), 1, 2 * time.Second, uint64(time.Second) + 1}, + {0, 0, int64(time.Second), 1, time.Second, uint64(2*time.Second) + 1}, + {0, 0, -int64(time.Second), 1, time.Second, 1}, + } + for _, i := range inputs { + node.SetBalance(i.pos, i.neg) + got := node.PosBalanceMissing(i.priority, i.cap, i.after) + if got != i.expect { + t.Fatalf("Missing budget mismatch, want %v, got %v", i.expect, got) + } + } +} + +func TestPostiveBalanceCounting(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + + var nodes []*NodeBalance + for i := 0; i < 100; i += 1 { + node := b.newNode(1000000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + nodes = append(nodes, node) + } + + // Allocate service token + var sum uint64 + for i := 0; i < 100; i += 1 { + amount := int64(rand.Intn(100) + 100) + nodes[i].AddBalance(amount) + sum += uint64(amount) + } + if b.bt.TotalTokenAmount() != sum { + t.Fatalf("Invalid token amount") + } + + // Change client status + for i := 0; i < 100; i += 1 { + if rand.Intn(2) == 0 { + b.ns.SetField(nodes[i].node, ppTestSetup.CapacityField, uint64(1)) + } + } + if b.bt.TotalTokenAmount() != sum { + t.Fatalf("Invalid token amount") + } + for i := 0; i < 100; i += 1 { + if rand.Intn(2) == 0 { + b.ns.SetField(nodes[i].node, ppTestSetup.CapacityField, uint64(1)) + } + } + if b.bt.TotalTokenAmount() != sum { + t.Fatalf("Invalid token amount") + } +} + +func TestCallbackChecking(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + + var inputs = []struct { + priority int64 + expDiff time.Duration + }{ + {500, time.Millisecond * 500}, + {0, time.Second}, + {-int64(time.Second), 2 * time.Second}, + } + node.SetBalance(uint64(time.Second), 0) + for _, i := range inputs { + diff, _ := node.timeUntil(i.priority) + if diff != i.expDiff { + t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff) + } + } +} + +func TestCallback(t *testing.T) { + b := newBalanceTestSetup() + defer b.stop() + node := b.newNode(1000) + node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) + b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) + + callCh := make(chan struct{}, 1) + node.SetBalance(uint64(time.Minute), 0) + node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) + + b.clock.Run(time.Minute) + select { + case <-callCh: + case <-time.NewTimer(time.Second).C: + t.Fatalf("Callback hasn't been called yet") + } + + node.SetBalance(uint64(time.Minute), 0) + node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) + node.removeCallback(balanceCallbackZero) + + b.clock.Run(time.Minute) + select { + case <-callCh: + t.Fatalf("Callback shouldn't be called") + case <-time.NewTimer(time.Millisecond * 100).C: + } +} diff --git a/les/lespay/server/balance_tracker.go b/les/lespay/server/balance_tracker.go new file mode 100644 index 0000000000..c1ea3c6496 --- /dev/null +++ b/les/lespay/server/balance_tracker.go @@ -0,0 +1,291 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "reflect" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nodestate" +) + +const ( + posThreshold = 1000000 // minimum positive balance that is persisted in the database + negThreshold = 1000000 // minimum negative balance that is persisted in the database + persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence +) + +// BalanceTrackerSetup contains node state flags and fields used by BalanceTracker +type BalanceTrackerSetup struct { + // controlled by PriorityPool + PriorityFlag, UpdateFlag nodestate.Flags + BalanceField nodestate.Field + // external connections + connAddressField, capacityField nodestate.Field +} + +// NewBalanceTrackerSetup creates a new BalanceTrackerSetup and initializes the fields +// and flags controlled by BalanceTracker +func NewBalanceTrackerSetup(setup *nodestate.Setup) BalanceTrackerSetup { + return BalanceTrackerSetup{ + // PriorityFlag is set if the node has a positive balance + PriorityFlag: setup.NewFlag("priorityNode"), + // UpdateFlag set and then immediately reset if the balance has been updated and + // therefore priority is suddenly changed + UpdateFlag: setup.NewFlag("balanceUpdate"), + // BalanceField contains the NodeBalance struct which implements nodePriority, + // allowing on-demand priority calculation and future priority estimation + BalanceField: setup.NewField("balance", reflect.TypeOf(&NodeBalance{})), + } +} + +// Connect sets the fields used by BalanceTracker as an input +func (bts *BalanceTrackerSetup) Connect(connAddressField, capacityField nodestate.Field) { + bts.connAddressField = connAddressField + bts.capacityField = capacityField +} + +// BalanceTracker tracks positive and negative balances for connected nodes. +// After connAddressField is set externally, a NodeBalance is created and previous +// balance values are loaded from the database. Both balances are exponentially expired +// values. Costs are deducted from the positive balance if present, otherwise added to +// the negative balance. If the capacity is non-zero then a time cost is applied +// continuously while individual request costs are applied immediately. +// The two balances are translated into a single priority value that also depends +// on the actual capacity. +type BalanceTracker struct { + BalanceTrackerSetup + clock mclock.Clock + lock sync.Mutex + ns *nodestate.NodeStateMachine + ndb *nodeDB + posExp, negExp utils.ValueExpirer + posExpTC, negExpTC uint64 + + active, inactive utils.ExpiredValue + balanceTimer *utils.UpdateTimer + quit chan struct{} +} + +// NewBalanceTracker creates a new BalanceTracker +func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *BalanceTracker { + ndb := newNodeDB(db, clock) + bt := &BalanceTracker{ + ns: ns, + BalanceTrackerSetup: setup, + ndb: ndb, + clock: clock, + posExp: posExp, + negExp: negExp, + balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), + quit: make(chan struct{}), + } + bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool { + bt.inactive.AddExp(balance) + return true + }) + + ns.SubscribeField(bt.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + n, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance) + if n == nil { + return + } + + ov, _ := oldValue.(uint64) + nv, _ := newValue.(uint64) + if ov == 0 && nv != 0 { + n.activate() + } + if nv != 0 { + n.setCapacity(nv) + } + if ov != 0 && nv == 0 { + n.deactivate() + } + }) + ns.SubscribeField(bt.connAddressField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + if newValue != nil { + ns.SetFieldSub(node, bt.BalanceField, bt.newNodeBalance(node, newValue.(string))) + } else { + ns.SetStateSub(node, nodestate.Flags{}, bt.PriorityFlag, 0) + if b, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance); b != nil { + b.deactivate() + } + ns.SetFieldSub(node, bt.BalanceField, nil) + } + }) + + // The positive and negative balances of clients are stored in database + // and both of these decay exponentially over time. Delete them if the + // value is small enough. + bt.ndb.evictCallBack = bt.canDropBalance + + go func() { + for { + select { + case <-clock.After(persistExpirationRefresh): + now := clock.Now() + bt.ndb.setExpiration(posExp.LogOffset(now), negExp.LogOffset(now)) + case <-bt.quit: + return + } + } + }() + return bt +} + +// Stop saves expiration offset and unsaved node balances and shuts BalanceTracker down +func (bt *BalanceTracker) Stop() { + now := bt.clock.Now() + bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now)) + close(bt.quit) + bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok { + n.lock.Lock() + n.storeBalance(true, true) + n.lock.Unlock() + bt.ns.SetField(node, bt.BalanceField, nil) + } + }) + bt.ndb.close() +} + +// TotalTokenAmount returns the current total amount of service tokens in existence +func (bt *BalanceTracker) TotalTokenAmount() uint64 { + bt.lock.Lock() + defer bt.lock.Unlock() + + bt.balanceTimer.Update(func(_ time.Duration) bool { + bt.active = utils.ExpiredValue{} + bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok { + pos, _ := n.GetRawBalance() + bt.active.AddExp(pos) + } + }) + return true + }) + total := bt.active + total.AddExp(bt.inactive) + return total.Value(bt.posExp.LogOffset(bt.clock.Now())) +} + +// GetPosBalanceIDs lists node IDs with an associated positive balance +func (bt *BalanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { + return bt.ndb.getPosBalanceIDs(start, stop, maxCount) +} + +// SetExpirationTCs sets positive and negative token expiration time constants. +// Specified in seconds, 0 means infinite (no expiration). +func (bt *BalanceTracker) SetExpirationTCs(pos, neg uint64) { + bt.lock.Lock() + defer bt.lock.Unlock() + + bt.posExpTC, bt.negExpTC = pos, neg + now := bt.clock.Now() + if pos > 0 { + bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second))) + } else { + bt.posExp.SetRate(now, 0) + } + if neg > 0 { + bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second))) + } else { + bt.negExp.SetRate(now, 0) + } +} + +// GetExpirationTCs returns the current positive and negative token expiration +// time constants +func (bt *BalanceTracker) GetExpirationTCs() (pos, neg uint64) { + bt.lock.Lock() + defer bt.lock.Unlock() + + return bt.posExpTC, bt.negExpTC +} + +// newNodeBalance loads balances from the database and creates a NodeBalance instance +// for the given node. It also sets the PriorityFlag and adds balanceCallbackZero if +// the node has a positive balance. +// Note: this function should run inside a NodeStateMachine operation +func (bt *BalanceTracker) newNodeBalance(node *enode.Node, negBalanceKey string) *NodeBalance { + pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false) + nb := bt.ndb.getOrNewBalance([]byte(negBalanceKey), true) + n := &NodeBalance{ + bt: bt, + node: node, + connAddress: negBalanceKey, + balance: balance{pos: pb, neg: nb}, + initTime: bt.clock.Now(), + lastUpdate: bt.clock.Now(), + } + for i := range n.callbackIndex { + n.callbackIndex[i] = -1 + } + if n.checkPriorityStatus() { + n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) + } + return n +} + +// storeBalance stores either a positive or a negative balance in the database +func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) { + if bt.canDropBalance(bt.clock.Now(), neg, value) { + bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly. + } else { + bt.ndb.setBalance(id, neg, value) + } +} + +// canDropBalance tells whether a positive or negative balance is below the threshold +// and therefore can be dropped from the database +func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { + if neg { + return b.Value(bt.negExp.LogOffset(now)) <= negThreshold + } else { + return b.Value(bt.posExp.LogOffset(now)) <= posThreshold + } +} + +// updateTotalBalance adjusts the total balance after executing given callback. +func (bt *BalanceTracker) updateTotalBalance(n *NodeBalance, callback func() bool) { + bt.lock.Lock() + defer bt.lock.Unlock() + + n.lock.Lock() + defer n.lock.Unlock() + + original, active := n.balance.pos, n.active + if !callback() { + return + } + if active { + bt.active.SubExp(original) + } else { + bt.inactive.SubExp(original) + } + if n.active { + bt.active.AddExp(n.balance.pos) + } else { + bt.inactive.AddExp(n.balance.pos) + } +} diff --git a/les/lespay/server/clientdb.go b/les/lespay/server/clientdb.go new file mode 100644 index 0000000000..c23f1e3b2f --- /dev/null +++ b/les/lespay/server/clientdb.go @@ -0,0 +1,245 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "bytes" + "encoding/binary" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" + lru "github.com/hashicorp/golang-lru" +) + +const ( + balanceCacheLimit = 8192 // the maximum number of cached items in service token balance queue + + // nodeDBVersion is the version identifier of the node data in db + // + // Changelog: + // * Replace `lastTotal` with `meta` in positive balance: version 0=>1 + nodeDBVersion = 1 + + // dbCleanupCycle is the cycle of db for useless data cleanup + dbCleanupCycle = time.Hour +) + +var ( + positiveBalancePrefix = []byte("pb:") // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance + negativeBalancePrefix = []byte("nb:") // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance + expirationKey = []byte("expiration:") // dbVersion(uint16 big endian) + expirationKey -> posExp, negExp +) + +type nodeDB struct { + db ethdb.KeyValueStore + cache *lru.Cache + auxbuf []byte // 37-byte auxiliary buffer for key encoding + verbuf [2]byte // 2-byte auxiliary buffer for db version + evictCallBack func(mclock.AbsTime, bool, utils.ExpiredValue) bool // Callback to determine whether the balance can be evicted. + clock mclock.Clock + closeCh chan struct{} + cleanupHook func() // Test hook used for testing +} + +func newNodeDB(db ethdb.KeyValueStore, clock mclock.Clock) *nodeDB { + cache, _ := lru.New(balanceCacheLimit) + ndb := &nodeDB{ + db: db, + cache: cache, + auxbuf: make([]byte, 37), + clock: clock, + closeCh: make(chan struct{}), + } + binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion)) + go ndb.expirer() + return ndb +} + +func (db *nodeDB) close() { + close(db.closeCh) +} + +func (db *nodeDB) getPrefix(neg bool) []byte { + prefix := positiveBalancePrefix + if neg { + prefix = negativeBalancePrefix + } + return append(db.verbuf[:], prefix...) +} + +func (db *nodeDB) key(id []byte, neg bool) []byte { + prefix := positiveBalancePrefix + if neg { + prefix = negativeBalancePrefix + } + if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) { + db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...) + } + copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:]) + copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix) + copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id) + return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] +} + +func (db *nodeDB) getExpiration() (utils.Fixed64, utils.Fixed64) { + blob, err := db.db.Get(append(db.verbuf[:], expirationKey...)) + if err != nil || len(blob) != 16 { + return 0, 0 + } + return utils.Fixed64(binary.BigEndian.Uint64(blob[:8])), utils.Fixed64(binary.BigEndian.Uint64(blob[8:16])) +} + +func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) { + var buff [16]byte + binary.BigEndian.PutUint64(buff[:8], uint64(pos)) + binary.BigEndian.PutUint64(buff[8:16], uint64(neg)) + db.db.Put(append(db.verbuf[:], expirationKey...), buff[:16]) +} + +func (db *nodeDB) getOrNewBalance(id []byte, neg bool) utils.ExpiredValue { + key := db.key(id, neg) + item, exist := db.cache.Get(string(key)) + if exist { + return item.(utils.ExpiredValue) + } + var b utils.ExpiredValue + enc, err := db.db.Get(key) + if err != nil || len(enc) == 0 { + return b + } + if err := rlp.DecodeBytes(enc, &b); err != nil { + log.Crit("Failed to decode positive balance", "err", err) + } + db.cache.Add(string(key), b) + return b +} + +func (db *nodeDB) setBalance(id []byte, neg bool, b utils.ExpiredValue) { + key := db.key(id, neg) + enc, err := rlp.EncodeToBytes(&(b)) + if err != nil { + log.Crit("Failed to encode positive balance", "err", err) + } + db.db.Put(key, enc) + db.cache.Add(string(key), b) +} + +func (db *nodeDB) delBalance(id []byte, neg bool) { + key := db.key(id, neg) + db.db.Delete(key) + db.cache.Remove(string(key)) +} + +// getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts +// with a positive balance +func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { + if maxCount <= 0 { + return + } + prefix := db.getPrefix(false) + keylen := len(prefix) + len(enode.ID{}) + + it := db.db.NewIterator(prefix, start.Bytes()) + defer it.Release() + + for it.Next() { + var id enode.ID + if len(it.Key()) != keylen { + return + } + copy(id[:], it.Key()[keylen-len(id):]) + if bytes.Compare(id.Bytes(), stop.Bytes()) >= 0 { + return + } + result = append(result, id) + if len(result) == maxCount { + return + } + } + return +} + +// forEachBalance iterates all balances and passes values to callback. +func (db *nodeDB) forEachBalance(neg bool, callback func(id enode.ID, balance utils.ExpiredValue) bool) { + prefix := db.getPrefix(neg) + keylen := len(prefix) + len(enode.ID{}) + + it := db.db.NewIterator(prefix, nil) + defer it.Release() + + for it.Next() { + var id enode.ID + if len(it.Key()) != keylen { + return + } + copy(id[:], it.Key()[keylen-len(id):]) + + var b utils.ExpiredValue + if err := rlp.DecodeBytes(it.Value(), &b); err != nil { + continue + } + if !callback(id, b) { + return + } + } +} + +func (db *nodeDB) expirer() { + for { + select { + case <-db.clock.After(dbCleanupCycle): + db.expireNodes() + case <-db.closeCh: + return + } + } +} + +// expireNodes iterates the whole node db and checks whether the +// token balances can be deleted. +func (db *nodeDB) expireNodes() { + var ( + visited int + deleted int + start = time.Now() + ) + for _, neg := range []bool{false, true} { + iter := db.db.NewIterator(db.getPrefix(neg), nil) + for iter.Next() { + visited++ + var balance utils.ExpiredValue + if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil { + log.Crit("Failed to decode negative balance", "err", err) + } + if db.evictCallBack != nil && db.evictCallBack(db.clock.Now(), neg, balance) { + deleted++ + db.db.Delete(iter.Key()) + } + } + } + // Invoke testing hook if it's not nil. + if db.cleanupHook != nil { + db.cleanupHook() + } + log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start))) +} diff --git a/les/lespay/server/clientdb_test.go b/les/lespay/server/clientdb_test.go new file mode 100644 index 0000000000..353d84aead --- /dev/null +++ b/les/lespay/server/clientdb_test.go @@ -0,0 +1,144 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +func expval(v uint64) utils.ExpiredValue { + return utils.ExpiredValue{Base: v} +} + +func TestNodeDB(t *testing.T) { + ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{}) + defer ndb.close() + + var cases = []struct { + id enode.ID + ip string + balance utils.ExpiredValue + positive bool + }{ + {enode.ID{0x00, 0x01, 0x02}, "", expval(100), true}, + {enode.ID{0x00, 0x01, 0x02}, "", expval(200), true}, + {enode.ID{}, "127.0.0.1", expval(100), false}, + {enode.ID{}, "127.0.0.1", expval(200), false}, + } + for _, c := range cases { + if c.positive { + ndb.setBalance(c.id.Bytes(), false, c.balance) + if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, c.balance) { + t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance, pb) + } + } else { + ndb.setBalance([]byte(c.ip), true, c.balance) + if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, c.balance) { + t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance, nb) + } + } + } + for _, c := range cases { + if c.positive { + ndb.delBalance(c.id.Bytes(), false) + if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, utils.ExpiredValue{}) { + t.Fatalf("Positive balance mismatch, want %v, got %v", utils.ExpiredValue{}, pb) + } + } else { + ndb.delBalance([]byte(c.ip), true) + if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, utils.ExpiredValue{}) { + t.Fatalf("Negative balance mismatch, want %v, got %v", utils.ExpiredValue{}, nb) + } + } + } + posExp, negExp := utils.Fixed64(1000), utils.Fixed64(2000) + ndb.setExpiration(posExp, negExp) + if pos, neg := ndb.getExpiration(); pos != posExp || neg != negExp { + t.Fatalf("Expiration mismatch, want %v / %v, got %v / %v", posExp, negExp, pos, neg) + } + /* curBalance := currencyBalance{typ: "ETH", amount: 10000} + ndb.setCurrencyBalance(enode.ID{0x01, 0x02}, curBalance) + if got := ndb.getCurrencyBalance(enode.ID{0x01, 0x02}); !reflect.DeepEqual(got, curBalance) { + t.Fatalf("Currency balance mismatch, want %v, got %v", curBalance, got) + }*/ +} + +func TestNodeDBExpiration(t *testing.T) { + var ( + iterated int + done = make(chan struct{}, 1) + ) + callback := func(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { + iterated += 1 + return true + } + clock := &mclock.Simulated{} + ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock) + defer ndb.close() + ndb.evictCallBack = callback + ndb.cleanupHook = func() { done <- struct{}{} } + + var cases = []struct { + id []byte + neg bool + balance utils.ExpiredValue + }{ + {[]byte{0x01, 0x02}, false, expval(1)}, + {[]byte{0x03, 0x04}, false, expval(1)}, + {[]byte{0x05, 0x06}, false, expval(1)}, + {[]byte{0x07, 0x08}, false, expval(1)}, + + {[]byte("127.0.0.1"), true, expval(1)}, + {[]byte("127.0.0.2"), true, expval(1)}, + {[]byte("127.0.0.3"), true, expval(1)}, + {[]byte("127.0.0.4"), true, expval(1)}, + } + for _, c := range cases { + ndb.setBalance(c.id, c.neg, c.balance) + } + clock.WaitForTimers(1) + clock.Run(time.Hour + time.Minute) + select { + case <-done: + case <-time.NewTimer(time.Second).C: + t.Fatalf("timeout") + } + if iterated != 8 { + t.Fatalf("Failed to evict useless balances, want %v, got %d", 8, iterated) + } + + for _, c := range cases { + ndb.setBalance(c.id, c.neg, c.balance) + } + clock.WaitForTimers(1) + clock.Run(time.Hour + time.Minute) + select { + case <-done: + case <-time.NewTimer(time.Second).C: + t.Fatalf("timeout") + } + if iterated != 16 { + t.Fatalf("Failed to evict useless balances, want %v, got %d", 16, iterated) + } +} diff --git a/les/lespay/server/prioritypool.go b/les/lespay/server/prioritypool.go new file mode 100644 index 0000000000..52224e093e --- /dev/null +++ b/les/lespay/server/prioritypool.go @@ -0,0 +1,503 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "math" + "reflect" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nodestate" +) + +const ( + lazyQueueRefresh = time.Second * 10 // refresh period of the active queue +) + +// PriorityPoolSetup contains node state flags and fields used by PriorityPool +// Note: ActiveFlag and InactiveFlag can be controlled both externally and by the pool, +// see PriorityPool description for details. +type PriorityPoolSetup struct { + // controlled by PriorityPool + ActiveFlag, InactiveFlag nodestate.Flags + CapacityField, ppNodeInfoField nodestate.Field + // external connections + updateFlag nodestate.Flags + priorityField nodestate.Field +} + +// NewPriorityPoolSetup creates a new PriorityPoolSetup and initializes the fields +// and flags controlled by PriorityPool +func NewPriorityPoolSetup(setup *nodestate.Setup) PriorityPoolSetup { + return PriorityPoolSetup{ + ActiveFlag: setup.NewFlag("active"), + InactiveFlag: setup.NewFlag("inactive"), + CapacityField: setup.NewField("capacity", reflect.TypeOf(uint64(0))), + ppNodeInfoField: setup.NewField("ppNodeInfo", reflect.TypeOf(&ppNodeInfo{})), + } +} + +// Connect sets the fields and flags used by PriorityPool as an input +func (pps *PriorityPoolSetup) Connect(priorityField nodestate.Field, updateFlag nodestate.Flags) { + pps.priorityField = priorityField // should implement nodePriority + pps.updateFlag = updateFlag // triggers an immediate priority update +} + +// PriorityPool handles a set of nodes where each node has a capacity (a scalar value) +// and a priority (which can change over time and can also depend on the capacity). +// A node is active if it has at least the necessary minimal amount of capacity while +// inactive nodes have 0 capacity (values between 0 and the minimum are not allowed). +// The pool ensures that the number and total capacity of all active nodes are limited +// and the highest priority nodes are active at all times (limits can be changed +// during operation with immediate effect). +// +// When activating clients a priority bias is applied in favor of the already active +// nodes in order to avoid nodes quickly alternating between active and inactive states +// when their priorities are close to each other. The bias is specified in terms of +// duration (time) because priorities are expected to usually get lower over time and +// therefore a future minimum prediction (see EstMinPriority) should monotonously +// decrease with the specified time parameter. +// This time bias can be interpreted as minimum expected active time at the given +// capacity (if the threshold priority stays the same). +// +// Nodes in the pool always have either InactiveFlag or ActiveFlag set. A new node is +// added to the pool by externally setting InactiveFlag. PriorityPool can switch a node +// between InactiveFlag and ActiveFlag at any time. Nodes can be removed from the pool +// by externally resetting both flags. ActiveFlag should not be set externally. +// +// The highest priority nodes in "inactive" state are moved to "active" state as soon as +// the minimum capacity can be granted for them. The capacity of lower priority active +// nodes is reduced or they are demoted to "inactive" state if their priority is +// insufficient even at minimal capacity. +type PriorityPool struct { + PriorityPoolSetup + ns *nodestate.NodeStateMachine + clock mclock.Clock + lock sync.Mutex + activeQueue *prque.LazyQueue + inactiveQueue *prque.Prque + changed []*ppNodeInfo + activeCount, activeCap uint64 + maxCount, maxCap uint64 + minCap uint64 + activeBias time.Duration + capacityStepDiv uint64 +} + +// nodePriority interface provides current and estimated future priorities on demand +type nodePriority interface { + // Priority should return the current priority of the node (higher is better) + Priority(now mclock.AbsTime, cap uint64) int64 + // EstMinPriority should return a lower estimate for the minimum of the node priority + // value starting from the current moment until the given time. If the priority goes + // under the returned estimate before the specified moment then it is the caller's + // responsibility to signal with updateFlag. + EstMinPriority(until mclock.AbsTime, cap uint64, update bool) int64 +} + +// ppNodeInfo is the internal node descriptor of PriorityPool +type ppNodeInfo struct { + nodePriority nodePriority + node *enode.Node + connected bool + capacity, origCap uint64 + bias time.Duration + forced, changed bool + activeIndex, inactiveIndex int +} + +// NewPriorityPool creates a new PriorityPool +func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, clock mclock.Clock, minCap uint64, activeBias time.Duration, capacityStepDiv uint64) *PriorityPool { + pp := &PriorityPool{ + ns: ns, + PriorityPoolSetup: setup, + clock: clock, + activeQueue: prque.NewLazyQueue(activeSetIndex, activePriority, activeMaxPriority, clock, lazyQueueRefresh), + inactiveQueue: prque.New(inactiveSetIndex), + minCap: minCap, + activeBias: activeBias, + capacityStepDiv: capacityStepDiv, + } + + ns.SubscribeField(pp.priorityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + if newValue != nil { + c := &ppNodeInfo{ + node: node, + nodePriority: newValue.(nodePriority), + activeIndex: -1, + inactiveIndex: -1, + } + ns.SetFieldSub(node, pp.ppNodeInfoField, c) + } else { + ns.SetStateSub(node, nodestate.Flags{}, pp.ActiveFlag.Or(pp.InactiveFlag), 0) + if n, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo); n != nil { + pp.disconnectedNode(n) + } + ns.SetFieldSub(node, pp.CapacityField, nil) + ns.SetFieldSub(node, pp.ppNodeInfoField, nil) + } + }) + ns.SubscribeState(pp.ActiveFlag.Or(pp.InactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + if c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo); c != nil { + if oldState.IsEmpty() { + pp.connectedNode(c) + } + if newState.IsEmpty() { + pp.disconnectedNode(c) + } + } + }) + ns.SubscribeState(pp.updateFlag, func(node *enode.Node, oldState, newState nodestate.Flags) { + if !newState.IsEmpty() { + pp.updatePriority(node) + } + }) + return pp +} + +// RequestCapacity checks whether changing the capacity of a node to the given target +// is possible (bias is applied in favor of other active nodes if the target is higher +// than the current capacity). +// If setCap is true then it also performs the change if possible. The function returns +// the minimum priority needed to do the change and whether it is currently allowed. +// If setCap and allowed are both true then the caller can assume that the change was +// successful. +// Note: priorityField should always be set before calling RequestCapacity. If setCap +// is false then both InactiveFlag and ActiveFlag can be unset and they are not changed +// by this function call either. +// Note 2: this function should run inside a NodeStateMachine operation +func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias time.Duration, setCap bool) (minPriority int64, allowed bool) { + pp.lock.Lock() + pp.activeQueue.Refresh() + var updates []capUpdate + defer func() { + pp.lock.Unlock() + pp.updateFlags(updates) + }() + + if targetCap < pp.minCap { + targetCap = pp.minCap + } + c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo) + if c == nil { + log.Error("RequestCapacity called for unknown node", "id", node.ID()) + return math.MaxInt64, false + } + var priority int64 + if targetCap > c.capacity { + priority = c.nodePriority.EstMinPriority(pp.clock.Now()+mclock.AbsTime(bias), targetCap, false) + } else { + priority = c.nodePriority.Priority(pp.clock.Now(), targetCap) + } + pp.markForChange(c) + pp.setCapacity(c, targetCap) + c.forced = true + pp.activeQueue.Remove(c.activeIndex) + pp.inactiveQueue.Remove(c.inactiveIndex) + pp.activeQueue.Push(c) + minPriority = pp.enforceLimits() + // if capacity update is possible now then minPriority == math.MinInt64 + // if it is not possible at all then minPriority == math.MaxInt64 + allowed = priority > minPriority + updates = pp.finalizeChanges(setCap && allowed) + return +} + +// SetLimits sets the maximum number and total capacity of simultaneously active nodes +func (pp *PriorityPool) SetLimits(maxCount, maxCap uint64) { + pp.lock.Lock() + pp.activeQueue.Refresh() + var updates []capUpdate + defer func() { + pp.lock.Unlock() + pp.ns.Operation(func() { pp.updateFlags(updates) }) + }() + + inc := (maxCount > pp.maxCount) || (maxCap > pp.maxCap) + dec := (maxCount < pp.maxCount) || (maxCap < pp.maxCap) + pp.maxCount, pp.maxCap = maxCount, maxCap + if dec { + pp.enforceLimits() + updates = pp.finalizeChanges(true) + } + if inc { + updates = pp.tryActivate() + } +} + +// SetActiveBias sets the bias applied when trying to activate inactive nodes +func (pp *PriorityPool) SetActiveBias(bias time.Duration) { + pp.lock.Lock() + defer pp.lock.Unlock() + + pp.activeBias = bias + pp.tryActivate() +} + +// ActiveCapacity returns the total capacity of currently active nodes +func (pp *PriorityPool) ActiveCapacity() uint64 { + pp.lock.Lock() + defer pp.lock.Unlock() + + return pp.activeCap +} + +// inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue +func inactiveSetIndex(a interface{}, index int) { + a.(*ppNodeInfo).inactiveIndex = index +} + +// activeSetIndex callback updates ppNodeInfo item index in activeQueue +func activeSetIndex(a interface{}, index int) { + a.(*ppNodeInfo).activeIndex = index +} + +// invertPriority inverts a priority value. The active queue uses inverted priorities +// because the node on the top is the first to be deactivated. +func invertPriority(p int64) int64 { + if p == math.MinInt64 { + return math.MaxInt64 + } + return -p +} + +// activePriority callback returns actual priority of ppNodeInfo item in activeQueue +func activePriority(a interface{}, now mclock.AbsTime) int64 { + c := a.(*ppNodeInfo) + if c.forced { + return math.MinInt64 + } + if c.bias == 0 { + return invertPriority(c.nodePriority.Priority(now, c.capacity)) + } else { + return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) + } +} + +// activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue +func activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { + c := a.(*ppNodeInfo) + if c.forced { + return math.MinInt64 + } + return invertPriority(c.nodePriority.EstMinPriority(until+mclock.AbsTime(c.bias), c.capacity, false)) +} + +// inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue +func (pp *PriorityPool) inactivePriority(p *ppNodeInfo) int64 { + return p.nodePriority.Priority(pp.clock.Now(), pp.minCap) +} + +// connectedNode is called when a new node has been added to the pool (InactiveFlag set) +// Note: this function should run inside a NodeStateMachine operation +func (pp *PriorityPool) connectedNode(c *ppNodeInfo) { + pp.lock.Lock() + pp.activeQueue.Refresh() + var updates []capUpdate + defer func() { + pp.lock.Unlock() + pp.updateFlags(updates) + }() + + if c.connected { + return + } + c.connected = true + pp.inactiveQueue.Push(c, pp.inactivePriority(c)) + updates = pp.tryActivate() +} + +// disconnectedNode is called when a node has been removed from the pool (both InactiveFlag +// and ActiveFlag reset) +// Note: this function should run inside a NodeStateMachine operation +func (pp *PriorityPool) disconnectedNode(c *ppNodeInfo) { + pp.lock.Lock() + pp.activeQueue.Refresh() + var updates []capUpdate + defer func() { + pp.lock.Unlock() + pp.updateFlags(updates) + }() + + if !c.connected { + return + } + c.connected = false + pp.activeQueue.Remove(c.activeIndex) + pp.inactiveQueue.Remove(c.inactiveIndex) + if c.capacity != 0 { + pp.setCapacity(c, 0) + updates = pp.tryActivate() + } +} + +// markForChange internally puts a node in a temporary state that can either be reverted +// or confirmed later. This temporary state allows changing the capacity of a node and +// moving it between the active and inactive queue. ActiveFlag/InactiveFlag and +// CapacityField are not changed while the changes are still temporary. +func (pp *PriorityPool) markForChange(c *ppNodeInfo) { + if c.changed { + return + } + c.changed = true + c.origCap = c.capacity + pp.changed = append(pp.changed, c) +} + +// setCapacity changes the capacity of a node and adjusts activeCap and activeCount +// accordingly. Note that this change is performed in the temporary state so it should +// be called after markForChange and before finalizeChanges. +func (pp *PriorityPool) setCapacity(n *ppNodeInfo, cap uint64) { + pp.activeCap += cap - n.capacity + if n.capacity == 0 { + pp.activeCount++ + } + if cap == 0 { + pp.activeCount-- + } + n.capacity = cap +} + +// enforceLimits enforces active node count and total capacity limits. It returns the +// lowest active node priority. Note that this function is performed on the temporary +// internal state. +func (pp *PriorityPool) enforceLimits() int64 { + if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount { + return math.MinInt64 + } + var maxActivePriority int64 + pp.activeQueue.MultiPop(func(data interface{}, priority int64) bool { + c := data.(*ppNodeInfo) + pp.markForChange(c) + maxActivePriority = priority + if c.capacity == pp.minCap { + pp.setCapacity(c, 0) + } else { + sub := c.capacity / pp.capacityStepDiv + if c.capacity-sub < pp.minCap { + sub = c.capacity - pp.minCap + } + pp.setCapacity(c, c.capacity-sub) + pp.activeQueue.Push(c) + } + return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount + }) + return invertPriority(maxActivePriority) +} + +// finalizeChanges either commits or reverts temporary changes. The necessary capacity +// field and according flag updates are not performed here but returned in a list because +// they should be performed while the mutex is not held. +func (pp *PriorityPool) finalizeChanges(commit bool) (updates []capUpdate) { + for _, c := range pp.changed { + // always remove and push back in order to update biased/forced priority + pp.activeQueue.Remove(c.activeIndex) + pp.inactiveQueue.Remove(c.inactiveIndex) + c.bias = 0 + c.forced = false + c.changed = false + if !commit { + pp.setCapacity(c, c.origCap) + } + if c.connected { + if c.capacity != 0 { + pp.activeQueue.Push(c) + } else { + pp.inactiveQueue.Push(c, pp.inactivePriority(c)) + } + if c.capacity != c.origCap && commit { + updates = append(updates, capUpdate{c.node, c.origCap, c.capacity}) + } + } + c.origCap = 0 + } + pp.changed = nil + return +} + +// capUpdate describes a CapacityField and ActiveFlag/InactiveFlag update +type capUpdate struct { + node *enode.Node + oldCap, newCap uint64 +} + +// updateFlags performs CapacityField and ActiveFlag/InactiveFlag updates while the +// pool mutex is not held +// Note: this function should run inside a NodeStateMachine operation +func (pp *PriorityPool) updateFlags(updates []capUpdate) { + for _, f := range updates { + if f.oldCap == 0 { + pp.ns.SetStateSub(f.node, pp.ActiveFlag, pp.InactiveFlag, 0) + } + if f.newCap == 0 { + pp.ns.SetStateSub(f.node, pp.InactiveFlag, pp.ActiveFlag, 0) + pp.ns.SetFieldSub(f.node, pp.CapacityField, nil) + } else { + pp.ns.SetFieldSub(f.node, pp.CapacityField, f.newCap) + } + } +} + +// tryActivate tries to activate inactive nodes if possible +func (pp *PriorityPool) tryActivate() []capUpdate { + var commit bool + for pp.inactiveQueue.Size() > 0 { + c := pp.inactiveQueue.PopItem().(*ppNodeInfo) + pp.markForChange(c) + pp.setCapacity(c, pp.minCap) + c.bias = pp.activeBias + pp.activeQueue.Push(c) + pp.enforceLimits() + if c.capacity > 0 { + commit = true + } else { + break + } + } + return pp.finalizeChanges(commit) +} + +// updatePriority gets the current priority value of the given node from the nodePriority +// interface and performs the necessary changes. It is triggered by updateFlag. +// Note: this function should run inside a NodeStateMachine operation +func (pp *PriorityPool) updatePriority(node *enode.Node) { + pp.lock.Lock() + pp.activeQueue.Refresh() + var updates []capUpdate + defer func() { + pp.lock.Unlock() + pp.updateFlags(updates) + }() + + c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo) + if c == nil || !c.connected { + return + } + pp.activeQueue.Remove(c.activeIndex) + pp.inactiveQueue.Remove(c.inactiveIndex) + if c.capacity != 0 { + pp.activeQueue.Push(c) + } else { + pp.inactiveQueue.Push(c, pp.inactivePriority(c)) + } + updates = pp.tryActivate() +} diff --git a/les/lespay/server/prioritypool_test.go b/les/lespay/server/prioritypool_test.go new file mode 100644 index 0000000000..cbb3f5b372 --- /dev/null +++ b/les/lespay/server/prioritypool_test.go @@ -0,0 +1,129 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "math/rand" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/nodestate" +) + +var ( + testSetup = &nodestate.Setup{} + ppTestClientFlag = testSetup.NewFlag("ppTestClientFlag") + ppTestClientField = testSetup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{})) + ppUpdateFlag = testSetup.NewFlag("ppUpdateFlag") + ppTestSetup = NewPriorityPoolSetup(testSetup) +) + +func init() { + ppTestSetup.Connect(ppTestClientField, ppUpdateFlag) +} + +const ( + testCapacityStepDiv = 100 + testCapacityToleranceDiv = 10 +) + +type ppTestClient struct { + node *enode.Node + balance, cap uint64 +} + +func (c *ppTestClient) Priority(now mclock.AbsTime, cap uint64) int64 { + return int64(c.balance / cap) +} + +func (c *ppTestClient) EstMinPriority(until mclock.AbsTime, cap uint64, update bool) int64 { + return int64(c.balance / cap) +} + +func TestPriorityPool(t *testing.T) { + clock := &mclock.Simulated{} + ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + + ns.SubscribeField(ppTestSetup.CapacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + if n := ns.GetField(node, ppTestSetup.priorityField); n != nil { + c := n.(*ppTestClient) + c.cap = newValue.(uint64) + } + }) + pp := NewPriorityPool(ns, ppTestSetup, clock, 100, 0, testCapacityStepDiv) + ns.Start() + pp.SetLimits(100, 1000000) + clients := make([]*ppTestClient, 100) + raise := func(c *ppTestClient) { + for { + var ok bool + ns.Operation(func() { + _, ok = pp.RequestCapacity(c.node, c.cap+c.cap/testCapacityStepDiv, 0, true) + }) + if !ok { + return + } + } + } + var sumBalance uint64 + check := func(c *ppTestClient) { + expCap := 1000000 * c.balance / sumBalance + capTol := expCap / testCapacityToleranceDiv + if c.cap < expCap-capTol || c.cap > expCap+capTol { + t.Errorf("Wrong node capacity (expected %d, got %d)", expCap, c.cap) + } + } + + for i := range clients { + c := &ppTestClient{ + node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), + balance: 1000000000, + cap: 1000, + } + sumBalance += c.balance + clients[i] = c + ns.SetState(c.node, ppTestClientFlag, nodestate.Flags{}, 0) + ns.SetField(c.node, ppTestSetup.priorityField, c) + ns.SetState(c.node, ppTestSetup.InactiveFlag, nodestate.Flags{}, 0) + raise(c) + check(c) + } + + for count := 0; count < 100; count++ { + c := clients[rand.Intn(len(clients))] + oldBalance := c.balance + c.balance = uint64(rand.Int63n(1000000000) + 1000000000) + sumBalance += c.balance - oldBalance + pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) + pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) + if c.balance > oldBalance { + raise(c) + } else { + for _, c := range clients { + raise(c) + } + } + for _, c := range clients { + check(c) + } + } + + ns.Stop() +} diff --git a/les/metrics.go b/les/metrics.go index c5edb61c3e..9a79fd1bbd 100644 --- a/les/metrics.go +++ b/les/metrics.go @@ -99,8 +99,8 @@ var ( sqQueuedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/queued", nil) clientConnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/connected", nil) - clientRejectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/rejected", nil) - clientKickedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/kicked", nil) + clientActivatedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/activated", nil) + clientDeactivatedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/deactivated", nil) clientDisconnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/disconnected", nil) clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil) clientErrorMeter = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil) diff --git a/les/peer.go b/les/peer.go index c529145239..0549daf9a6 100644 --- a/les/peer.go +++ b/les/peer.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" lpc "github.com/ethereum/go-ethereum/les/lespay/client" + lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" @@ -463,7 +464,7 @@ func (p *serverPeer) requestTxStatus(reqID uint64, txHashes []common.Hash) error return p.sendRequest(GetTxStatusMsg, reqID, txHashes, len(txHashes)) } -// SendTxStatus creates a reply with a batch of transactions to be added to the remote transaction pool. +// sendTxs creates a reply with a batch of transactions to be added to the remote transaction pool. func (p *serverPeer) sendTxs(reqID uint64, amount int, txs rlp.RawValue) error { p.Log().Debug("Sending batch of transactions", "amount", amount, "size", len(txs)) sizeFactor := (len(txs) + txSizeCostLimit/2) / txSizeCostLimit @@ -719,6 +720,8 @@ type clientPeer struct { responseLock sync.Mutex responseCount uint64 // Counter to generate an unique id for request processing. + balance *lps.NodeBalance + // invalidLock is used for protecting invalidCount. invalidLock sync.RWMutex invalidCount utils.LinearExpiredValue // Counter the invalid request the client peer has made. @@ -876,18 +879,25 @@ func (p *clientPeer) sendAnnounce(request announceData) error { return p2p.Send(p.rw, AnnounceMsg, request) } +// allowInactive implements clientPoolPeer +func (p *clientPeer) allowInactive() bool { + return false +} + // updateCapacity updates the request serving capacity assigned to a given client // and also sends an announcement about the updated flow control parameters func (p *clientPeer) updateCapacity(cap uint64) { p.lock.Lock() defer p.lock.Unlock() - p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio} - p.fcClient.UpdateParams(p.fcParams) - var kvList keyValueList - kvList = kvList.add("flowControl/MRR", cap) - kvList = kvList.add("flowControl/BL", cap*bufLimitRatio) - p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) + if cap != p.fcParams.MinRecharge { + p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio} + p.fcClient.UpdateParams(p.fcParams) + var kvList keyValueList + kvList = kvList.add("flowControl/MRR", cap) + kvList = kvList.add("flowControl/BL", cap*bufLimitRatio) + p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) + } } // freezeClient temporarily puts the client in a frozen state which means all @@ -974,7 +984,7 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge // set default announceType on server side p.announceType = announceTypeSimple } - p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams) + p.fcClient = flowcontrol.NewClientNode(server.fcManager, p.fcParams) } return nil }) diff --git a/les/server.go b/les/server.go index ecb65150a8..225a7ad1f0 100644 --- a/les/server.go +++ b/les/server.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" + lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -51,9 +52,9 @@ type LesServer struct { servingQueue *servingQueue clientPool *clientPool - minCapacity, maxCapacity, freeCapacity uint64 - threadsIdle int // Request serving threads count when system is idle. - threadsBusy int // Request serving threads count when system is busy(block insertion). + minCapacity, maxCapacity uint64 + threadsIdle int // Request serving threads count when system is idle. + threadsBusy int // Request serving threads count when system is busy(block insertion). p2pSrv *p2p.Server } @@ -94,7 +95,6 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer } srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) - srv.freeCapacity = srv.minCapacity srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config) // Initialize the bloom trie indexer. @@ -102,8 +102,8 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer // Initialize server capacity management fields. srv.defParams = flowcontrol.ServerParams{ - BufLimit: srv.freeCapacity * bufLimitRatio, - MinRecharge: srv.freeCapacity, + BufLimit: srv.minCapacity * bufLimitRatio, + MinRecharge: srv.minCapacity, } // LES flow control tries to more or less guarantee the possibility for the // clients to send a certain amount of requests at any time and get a quick @@ -111,13 +111,13 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer // to send requests most of the time. Our goal is to serve as many clients as // possible while the actually used server capacity does not exceed the limits totalRecharge := srv.costTracker.totalRecharge() - srv.maxCapacity = srv.freeCapacity * uint64(srv.config.LightPeers) + srv.maxCapacity = srv.minCapacity * uint64(srv.config.LightPeers) if totalRecharge > srv.maxCapacity { srv.maxCapacity = totalRecharge } - srv.fcManager.SetCapacityLimits(srv.freeCapacity, srv.maxCapacity, srv.freeCapacity*2) - srv.clientPool = newClientPool(srv.chainDb, srv.freeCapacity, mclock.System{}, func(id enode.ID) { go srv.peers.unregister(id.String()) }) - srv.clientPool.setDefaultFactors(priceFactors{0, 1, 1}, priceFactors{0, 1, 1}) + srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) + srv.clientPool = newClientPool(srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, func(id enode.ID) { go srv.peers.unregister(id.String()) }) + srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() if !checkpoint.Empty() { @@ -268,7 +268,7 @@ func (s *LesServer) capacityManagement() { updateRecharge() case totalCapacity = <-totalCapacityCh: totalCapacityGauge.Update(int64(totalCapacity)) - newFreePeers := totalCapacity / s.freeCapacity + newFreePeers := totalCapacity / s.minCapacity if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) { log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers) } diff --git a/les/server_handler.go b/les/server_handler.go index 463f51cb43..583df96008 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" + lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -138,10 +139,14 @@ func (h *serverHandler) handle(p *clientPeer) error { defer p.fcClient.Disconnect() // Disconnect the inbound peer if it's rejected by clientPool - if !h.server.clientPool.connect(p, 0) { - p.Log().Debug("Light Ethereum peer registration failed", "err", errFullClientPool) + if cap, err := h.server.clientPool.connect(p); cap != p.fcParams.MinRecharge || err != nil { + p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool) return errFullClientPool } + p.balance, _ = h.server.clientPool.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance) + if p.balance == nil { + return p2p.DiscRequested + } // Register the peer locally if err := h.server.peers.register(p); err != nil { h.server.clientPool.disconnect(p) @@ -157,6 +162,7 @@ func (h *serverHandler) handle(p *clientPeer) error { wg.Wait() // Ensure all background task routines have exited. h.server.peers.unregister(p.id) h.server.clientPool.disconnect(p) + p.balance = nil clientConnectionGauge.Update(int64(h.server.peers.len())) connectionTimer.Update(time.Duration(mclock.Now() - connectedAt)) }() @@ -256,13 +262,16 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { realCost = maxCost // Assign a fake cost for testing purpose } else { realCost = h.server.costTracker.realCost(servingTime, msg.Size, replySize) + if realCost > maxCost { + realCost = maxCost + } } bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) if amount != 0 { // Feed cost tracker request serving statistic. h.server.costTracker.updateStats(msg.Code, amount, servingTime, realCost) // Reduce priority "balance" for the specific peer. - h.server.clientPool.requestCost(p, realCost) + p.balance.RequestServed(realCost) } if reply != nil { p.queueSend(func() { @@ -380,7 +389,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { first = false } reply := p.replyBlockHeaders(req.ReqID, headers) - sendResponse(req.ReqID, query.Amount, p.replyBlockHeaders(req.ReqID, headers), task.done()) + sendResponse(req.ReqID, query.Amount, reply, task.done()) if metrics.EnabledExpensive { miscOutHeaderPacketsMeter.Mark(1) miscOutHeaderTrafficMeter.Mark(int64(reply.size())) diff --git a/les/test_helper.go b/les/test_helper.go index 4ce1d03c2b..9f9b28721e 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -282,9 +282,9 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da }, fcManager: flowcontrol.NewClientManager(nil, clock), } - server.costTracker, server.freeCapacity = newCostTracker(db, server.config) + server.costTracker, server.minCapacity = newCostTracker(db, server.config) server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism. - server.clientPool = newClientPool(db, 1, clock, nil) + server.clientPool = newClientPool(db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {}) server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) if server.oracle != nil { diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 980156d21c..55e82cee48 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -18,6 +18,7 @@ package utils import ( "math" + "sync" "github.com/ethereum/go-ethereum/common/mclock" ) @@ -124,6 +125,11 @@ func (e *ExpiredValue) SubExp(a ExpiredValue) { } } +// IsZero returns true if the value is zero +func (e *ExpiredValue) IsZero() bool { + return e.Base == 0 +} + // LinearExpiredValue is very similar with the expiredValue which the value // will continuously expired. But the different part is it's expired linearly. type LinearExpiredValue struct { @@ -168,12 +174,20 @@ func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 { return e.Val } +// ValueExpirer controls value expiration rate +type ValueExpirer interface { + SetRate(now mclock.AbsTime, rate float64) + SetLogOffset(now mclock.AbsTime, logOffset Fixed64) + LogOffset(now mclock.AbsTime) Fixed64 +} + // Expirer changes logOffset with a linear rate which can be changed during operation. // It is not thread safe, if access by multiple goroutines is needed then it should be // encapsulated into a locked structure. // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset // is thread safe. type Expirer struct { + lock sync.RWMutex logOffset Fixed64 rate float64 lastUpdate mclock.AbsTime @@ -182,6 +196,9 @@ type Expirer struct { // SetRate changes the expiration rate which is the inverse of the time constant in // nanoseconds. func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { + e.lock.Lock() + defer e.lock.Unlock() + dt := now - e.lastUpdate if dt > 0 { e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) @@ -192,12 +209,18 @@ func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { // SetLogOffset sets logOffset instantly. func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { + e.lock.Lock() + defer e.lock.Unlock() + e.lastUpdate = now e.logOffset = logOffset } // LogOffset returns the current logarithmic offset. func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { + e.lock.RLock() + defer e.lock.RUnlock() + dt := now - e.lastUpdate if dt <= 0 { return e.logOffset diff --git a/les/utils/timeutils.go b/les/utils/timeutils.go new file mode 100644 index 0000000000..62a4285d15 --- /dev/null +++ b/les/utils/timeutils.go @@ -0,0 +1,69 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +type UpdateTimer struct { + clock mclock.Clock + lock sync.Mutex + last mclock.AbsTime + threshold time.Duration +} + +func NewUpdateTimer(clock mclock.Clock, threshold time.Duration) *UpdateTimer { + // We don't accept the update threshold less than 0. + if threshold < 0 { + return nil + } + // Don't panic for lazy users + if clock == nil { + clock = mclock.System{} + } + return &UpdateTimer{ + clock: clock, + last: clock.Now(), + threshold: threshold, + } +} + +func (t *UpdateTimer) Update(callback func(diff time.Duration) bool) bool { + return t.UpdateAt(t.clock.Now(), callback) +} + +func (t *UpdateTimer) UpdateAt(at mclock.AbsTime, callback func(diff time.Duration) bool) bool { + t.lock.Lock() + defer t.lock.Unlock() + + diff := time.Duration(at - t.last) + if diff < 0 { + diff = 0 + } + if diff < t.threshold { + return false + } + if callback(diff) { + t.last = at + return true + } + return false +} diff --git a/les/utils/timeutils_test.go b/les/utils/timeutils_test.go new file mode 100644 index 0000000000..9f9e1c2dc9 --- /dev/null +++ b/les/utils/timeutils_test.go @@ -0,0 +1,47 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +func TestUpdateTimer(t *testing.T) { + timer := NewUpdateTimer(mclock.System{}, -1) + if timer != nil { + t.Fatalf("Create update timer with negative threshold") + } + sim := &mclock.Simulated{} + timer = NewUpdateTimer(sim, time.Second) + if updated := timer.Update(func(diff time.Duration) bool { return true }); updated { + t.Fatalf("Update the clock without reaching the threshold") + } + sim.Run(time.Second) + if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated { + t.Fatalf("Doesn't update the clock when reaching the threshold") + } + if updated := timer.UpdateAt(sim.Now()+mclock.AbsTime(time.Second), func(diff time.Duration) bool { return true }); !updated { + t.Fatalf("Doesn't update the clock when reaching the threshold") + } + timer = NewUpdateTimer(sim, 0) + if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated { + t.Fatalf("Doesn't update the clock without threshold limitaion") + } +} From b65c3841815ed4d12a6904d18efe39bbff5eb676 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 15 Sep 2020 08:22:47 +0200 Subject: [PATCH 026/948] eth/tracers: regenerate assets from #21549 (#21564) --- eth/tracers/internal/tracers/assets.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go index c2da1ed1f8..432398ebb5 100644 --- a/eth/tracers/internal/tracers/assets.go +++ b/eth/tracers/internal/tracers/assets.go @@ -2,7 +2,7 @@ // sources: // 4byte_tracer.js (2.933kB) // bigram_tracer.js (1.712kB) -// call_tracer.js (8.704kB) +// call_tracer.js (8.956kB) // evmdis_tracer.js (4.195kB) // noop_tracer.js (1.271kB) // opcount_tracer.js (1.372kB) @@ -117,7 +117,7 @@ func bigram_tracerJs() (*asset, error) { return a, nil } -var _call_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x59\xdf\x73\xdb\x36\xf2\x7f\x96\xfe\x8a\x4d\x1e\x6a\x69\xa2\x48\x4a\xd2\x6f\xbf\x33\x76\xd5\x1b\x9d\xa3\xa4\x9e\x71\xe3\x8c\xad\x34\x93\xc9\xe4\x01\x22\x97\x12\x6a\x08\x60\x01\xd0\x32\x2f\xf5\xff\x7e\xb3\x0b\x90\x22\x25\xd9\xf1\xf5\x6e\x6e\x7a\x6f\x24\xb1\xbb\x58\xec\x7e\xf6\x17\x38\x1a\xc1\xa9\xc9\x4b\x2b\x97\x2b\x0f\x2f\xc7\x2f\xfe\x1f\xe6\x2b\x84\xa5\x79\x8e\x7e\x85\x16\x8b\x35\x4c\x0b\xbf\x32\xd6\x75\x47\x23\x98\xaf\xa4\x83\x4c\x2a\x04\xe9\x20\x17\xd6\x83\xc9\xc0\xef\xd0\x2b\xb9\xb0\xc2\x96\xc3\xee\x68\x14\x78\x0e\x2e\x93\x84\xcc\x22\x82\x33\x99\xdf\x08\x8b\xc7\x50\x9a\x02\x12\xa1\xc1\x62\x2a\x9d\xb7\x72\x51\x78\x04\xe9\x41\xe8\x74\x64\x2c\xac\x4d\x2a\xb3\x92\x44\x4a\x0f\x85\x4e\xd1\xf2\xd6\x1e\xed\xda\x55\x7a\xbc\x7d\xf7\x01\xce\xd1\x39\xb4\xf0\x16\x35\x5a\xa1\xe0\x7d\xb1\x50\x32\x81\x73\x99\xa0\x76\x08\xc2\x41\x4e\x5f\xdc\x0a\x53\x58\xb0\x38\x62\x7c\x43\xaa\x5c\x45\x55\xe0\x8d\x29\x74\x2a\xbc\x34\x7a\x00\x28\x49\x73\xb8\x41\xeb\xa4\xd1\xf0\xaa\xda\x2a\x0a\x1c\x80\xb1\x24\xa4\x27\x3c\x1d\xc0\x82\xc9\x89\xaf\x0f\x42\x97\xa0\x84\xdf\xb2\x3e\xc2\x20\xdb\x73\xa7\x20\x35\x6f\xb3\x32\x39\x82\x5f\x09\x4f\xa7\xde\x48\xa5\x60\x81\x50\x38\xcc\x0a\x35\x20\x69\x8b\xc2\xc3\xc7\xb3\xf9\xcf\x17\x1f\xe6\x30\x7d\xf7\x09\x3e\x4e\x2f\x2f\xa7\xef\xe6\x9f\x4e\x60\x23\xfd\xca\x14\x1e\xf0\x06\x83\x28\xb9\xce\x95\xc4\x14\x36\xc2\x5a\xa1\x7d\x09\x26\x23\x09\xbf\xcc\x2e\x4f\x7f\x9e\xbe\x9b\x4f\xff\x7e\x76\x7e\x36\xff\x04\xc6\xc2\x9b\xb3\xf9\xbb\xd9\xd5\x15\xbc\xb9\xb8\x84\x29\xbc\x9f\x5e\xce\xcf\x4e\x3f\x9c\x4f\x2f\xe1\xfd\x87\xcb\xf7\x17\x57\xb3\x21\x5c\x21\x69\x85\xc4\xff\x6d\x9b\x67\xec\x3d\x8b\x90\xa2\x17\x52\xb9\xca\x12\x9f\x4c\x01\x6e\x65\x0a\x95\xc2\x4a\xdc\x20\x58\x4c\x50\xde\x60\x0a\x02\x12\x93\x97\x8f\x76\x2a\xc9\x12\xca\xe8\x25\x9f\xf9\x5e\x40\xc2\x59\x06\xda\xf8\x01\x38\x44\xf8\x71\xe5\x7d\x7e\x3c\x1a\x6d\x36\x9b\xe1\x52\x17\x43\x63\x97\x23\x15\xc4\xb9\xd1\x4f\xc3\x2e\xc9\x4c\x84\x52\x73\x2b\x12\xb4\xe4\x1c\x01\x59\x41\xe6\x57\x66\xa3\xc1\x5b\xa1\x9d\x48\xc8\xd5\xf4\x9c\x30\x18\x85\x07\xbc\xa5\x37\xef\x08\xb4\x60\x31\x37\x96\x9e\x95\xaa\x70\x26\xb5\x47\xab\x85\x62\xd9\x0e\xd6\x22\x45\x58\x94\x20\x9a\x02\x07\xcd\xc3\x10\x8c\x82\xbb\x41\xea\xcc\xd8\x35\xc3\x72\xd8\xfd\xda\xed\x44\x0d\x9d\x17\xc9\x35\x29\x48\xf2\x93\xc2\x5a\xd4\x9e\x4c\x59\x58\x27\x6f\x90\x49\x20\xd0\x44\x7b\xce\x7e\xfd\x05\xf0\x16\x93\x22\x48\xea\xd4\x42\x8e\xe1\xf3\xd7\xbb\x2f\x83\x2e\x8b\x4e\xd1\x25\xa8\x53\x4c\xf9\x7c\xd7\x0e\x36\x2b\xb6\x28\x6c\xf0\xe8\x06\xe1\xb7\xc2\xf9\x06\x4d\x66\xcd\x1a\x84\x06\x53\x10\xe2\x9b\xd6\x91\xda\x1b\x16\x28\xe8\x59\xa3\x65\x8d\x86\xdd\x4e\xcd\x7c\x0c\x99\x50\x0e\xe3\xbe\xce\x63\x4e\xa7\x91\xfa\xc6\x5c\x93\x64\x63\x09\xc2\xb6\x04\x93\x27\x26\x8d\xc1\x40\xe7\xa8\x8f\x81\x6e\xd8\xed\x10\xdf\x31\x64\x85\xe6\x6d\x7b\xca\x2c\x07\x90\x2e\xfa\xf0\xb5\xdb\x21\xb1\xa7\x22\xf7\x85\x45\xb6\x27\x5a\x6b\xac\x03\xb9\x5e\x63\x2a\x85\x47\x55\x76\x3b\x9d\x1b\x61\xc3\x02\x4c\x40\x99\xe5\x70\x89\x7e\x46\xaf\xbd\xfe\x49\xb7\xd3\x91\x19\xf4\xc2\xea\x93\xc9\x84\xb3\x4f\x26\x35\xa6\x41\x7c\xc7\xaf\xa4\x1b\x66\xa2\x50\xbe\xde\x97\x98\x3a\x16\x7d\x61\x35\x3d\xde\x05\x2d\x3e\x22\x18\xad\x4a\x48\x28\xcb\x88\x05\x85\xa7\x2b\x9d\xc7\x75\x3c\x9c\x1b\x40\x26\x1c\x99\x50\x66\xb0\x41\xc8\x2d\x3e\x4f\x56\x48\xbe\xd3\x09\x46\x2d\x5d\xe9\xd8\xa9\x13\xa0\xdd\x86\x26\x1f\x7a\xf3\xae\x58\x2f\xd0\xf6\xfa\xf0\x1d\x8c\x6f\xb3\x71\x1f\x26\x13\x7e\xa8\x74\x8f\x3c\x51\x5f\x92\x62\xf2\x78\x50\xe6\xbf\xf2\x56\xea\x65\x38\x6b\xd4\xf5\x2c\x03\x01\x1a\x37\x90\x18\xcd\xa0\x26\xaf\x2c\x50\xea\x25\x24\x16\x85\xc7\x74\x00\x22\x4d\xc1\x9b\x80\xbc\x1a\x67\xed\x2d\xe1\xbb\xef\xa0\x47\x9b\x4d\xe0\xe8\xf4\x72\x36\x9d\xcf\x8e\xe0\x8f\x3f\x20\x7c\x79\x1a\xbe\xbc\x7c\xda\x6f\x68\x26\xf5\x45\x96\x45\xe5\x58\xe0\x30\x47\xbc\xee\xbd\xe8\x0f\x6f\x84\x2a\xf0\x22\x0b\x6a\x46\xda\x99\x4e\x61\x12\x79\x9e\xed\xf2\xbc\x6c\xf1\x10\xd3\x68\x04\x53\xe7\x70\xbd\x50\xb8\x1f\x90\x31\x62\x39\x78\x9d\xa7\x8c\x45\xe8\x4b\xcc\x3a\x57\x48\xa8\xaa\x76\x8d\xe6\x67\x8d\x3b\xbe\xcc\xf1\x18\x00\xc0\xe4\x03\xfe\x40\xb1\xc0\x1f\xbc\xf9\x19\x6f\xd9\x47\x95\x09\x09\x55\xd3\x34\xb5\xe8\x5c\xaf\xdf\x0f\xe4\x52\xe7\x85\x3f\x6e\x91\xaf\x71\x6d\x6c\x39\x74\x94\x90\x7a\x7c\xb4\x41\x38\x69\xc5\xb3\x14\xee\x4c\x13\x4f\x44\xea\x5b\xe1\x7a\xdb\xa5\x53\xe3\xfc\x71\xb5\x44\x2f\xd5\x1a\xdb\x82\xd8\x8e\xc6\xb7\x47\xfb\xd6\x1a\xf7\xb7\x48\x78\xf1\x43\x9f\x58\xee\x4e\x6a\x7c\xd7\x69\x62\x98\x17\x6e\xd5\x63\x38\x6d\x57\xb7\xa9\x60\x02\xde\x16\x78\x10\xfe\x0c\xa9\x7d\x38\x39\x54\x19\xe5\x12\x6f\x8b\x84\x61\xb5\x14\x9c\x69\x38\xd2\x05\x65\x5e\x57\x2c\xd8\xe6\xde\x98\x7d\x74\x45\x70\x5d\xcd\xce\xdf\xbc\x9e\x5d\xcd\x2f\x3f\x9c\xce\x8f\x1a\x70\x52\x98\x79\x52\xaa\x7d\x06\x85\x7a\xe9\x57\xac\x3f\x89\x6b\xaf\x7e\x26\x9e\xe7\x2f\xbe\x84\x2f\x30\x39\x10\xf2\x9d\x87\x39\xe0\xf3\x17\x96\x7d\xb7\x6f\xbe\x36\x69\x30\xe6\xd7\x00\x22\x93\xdf\x35\x13\xc7\x81\x58\x5c\xa3\x5f\x99\x94\x93\x63\x22\x42\x7e\xad\xac\x98\x1a\x8d\xff\x7a\x44\x4e\xcf\xcf\x1b\xf1\xc8\xef\xa7\x17\xaf\x9b\x31\x7a\xf4\x7a\x76\x3e\x7b\x3b\x9d\xcf\x76\x69\xaf\xe6\xd3\xf9\xd9\x29\x7f\xad\xc2\x77\x34\x82\xab\x6b\x99\x73\x96\xe5\xdc\x65\xd6\x39\xb7\x8b\xb5\xbe\x6e\x00\x7e\x65\xa8\x11\xb3\xb1\x88\x64\x42\x27\x55\x72\x77\x95\xd3\xbc\x21\x97\x99\x2a\x56\xf6\x53\x41\x13\xa8\xfd\xda\x8d\xd2\xbd\xb7\x18\x37\x4d\x7b\xde\x54\x7a\x6d\x0d\x1a\x3c\xc2\x09\x90\x93\x4c\xef\xf1\x87\x84\xbf\xc1\x18\x8e\xe1\x45\xcc\x24\x0f\xa4\xaa\x97\xf0\x8c\xc4\xff\x89\x84\xf5\xea\x00\xe7\x5f\x33\x6d\x79\xc3\xc4\x15\xb9\x37\xff\xfd\x74\x66\x0a\x7f\x91\x65\xc7\xb0\x6b\xc4\xef\xf7\x8c\x58\xd3\x9f\xa3\xde\xa7\xff\xbf\x3d\xfa\x6d\xea\x23\x54\x99\x1c\x9e\xec\x41\x24\x24\x9e\x27\x3b\x71\x10\x8d\xcb\x2d\x0e\x4b\x83\xc9\x3d\xc9\xf6\x65\x1b\xc3\xf7\x65\x8b\x7f\x2b\xd9\x1e\x6c\xd5\xa8\x21\x6b\x37\x63\x03\xb0\xe8\xad\xc4\x1b\x1a\xb7\x8e\x1c\x8b\xa4\xa6\xd5\x6c\x84\x4e\x70\x08\x1f\x31\x48\xd4\x88\x9c\x5c\x62\x93\x4b\x3d\x0a\xf7\x7d\xd4\xa8\xc6\x71\x85\x21\x26\xb8\x17\xb5\x08\x6b\x51\xd2\xb8\x92\x15\xfa\xba\x84\xa5\x70\x90\x96\x5a\xac\x65\xe2\x82\x3c\x6e\x70\x2d\x2e\x85\x65\xb1\x16\x7f\x2f\xd0\xd1\xec\x43\x40\x16\x89\x2f\x84\x52\x25\x2c\x25\x0d\x30\xc4\xdd\x7b\xf9\x6a\x3c\x06\xe7\x65\x8e\x3a\x1d\xc0\x0f\xaf\x46\x3f\x7c\x0f\xb6\x50\xd8\x1f\x76\x1b\x69\xbc\x3e\x6a\xf4\x06\x2d\x44\xf4\xbc\xc6\xdc\xaf\x7a\x7d\xf8\xe9\x9e\x7a\x70\x4f\x72\x3f\x48\x0b\xcf\xe1\xc5\x97\x21\xe9\x35\x69\xe1\x36\x78\x12\x50\x39\x8c\xd2\x68\xe8\xbb\x78\x7d\xd1\xbb\x16\x56\x28\xb1\xc0\xfe\x31\x0f\x81\x6c\xab\x8d\x88\x53\x00\x39\x05\x72\x25\xa4\x06\x91\x24\xa6\xd0\x9e\x0c\x5f\x35\xf4\xaa\xa4\xfc\x7e\xe4\x2b\x79\x3c\x2f\x89\x24\x41\xe7\xaa\x74\xcf\x5e\x23\x75\xc4\x9a\xb8\x41\x6a\x27\x53\x6c\x78\x85\xb2\x83\xe1\xd4\x1c\x29\x68\x9c\xac\x04\xae\x8d\xa3\x4d\x16\x08\x1b\x4b\xc3\x87\x93\x3a\xe1\xe9\x3b\x45\xb2\xb6\x03\xa3\x41\x80\x32\x3c\xf2\x73\x8c\x83\xb0\x4b\x37\x0c\xf9\x9e\xb6\xa5\x9c\xa3\xcd\x66\xd8\x06\x72\x13\xaa\xdc\xe6\xef\xb4\x03\x1a\xf0\x56\x3a\xcf\x5d\x25\x69\x29\x1d\x04\x24\x4b\xbd\x1c\x40\x6e\x72\xce\xd3\xdf\x2a\x67\x31\x59\x5f\xce\x7e\x9d\x5d\xd6\xc5\xff\xf1\x4e\xac\xfa\xfe\xa7\xf5\x58\x04\x96\x66\x0e\x8f\xe9\xd3\x03\x8d\xfc\x01\x40\x4d\xee\x01\x14\xc9\xdf\xd6\xc6\xf7\x8d\xe3\x28\xe1\xfc\xd6\x31\x4b\x0c\x33\x4d\x53\x01\x57\x28\xef\x76\x72\xf7\x6e\x72\x30\x79\x55\x21\x48\x29\x4e\x3b\x94\xd8\x77\xbb\xed\xd6\xc2\xb6\xe9\xde\xe2\xf3\xac\x61\xe3\x0d\xb7\x5c\x81\xa8\x91\x1a\x78\xbd\xea\xdd\x44\xa8\x06\xac\xbb\x29\x3c\xc1\x81\xea\xf7\x36\xf9\x2d\x85\xfb\xe0\xd8\xeb\x31\xfd\x2d\xe4\xf2\x4c\xfb\x5e\xb5\x78\xa6\xe1\x39\x54\x2f\x94\xd4\xe1\x79\x2b\x8a\x0e\x64\xc7\x4e\x8a\x0a\x3d\xc2\x56\xc4\x09\xec\x7c\x22\x41\xc1\x1c\x6c\x34\x8b\x7e\xbf\x38\x8f\xa3\x34\x32\xd8\x13\x8b\x7e\x88\xbf\x17\x42\xb9\xde\xb8\x6e\x16\xc2\x09\xbc\xe1\xf2\x36\xa9\x0b\x5c\x55\x01\x89\xa7\xd5\x7e\x44\x81\x81\x2d\x5a\xa3\x62\x4b\x17\xa1\x6a\xa5\xf8\xa0\x84\x28\x22\xa6\x8d\xda\x97\x11\x98\x87\xfa\xcf\x4e\x93\x00\x9e\xd6\x0d\x41\x26\xa4\x2a\x2c\x3e\x3d\x81\x03\x69\xc7\x15\x36\x13\x09\xfb\xd2\x21\xf0\xc4\xea\xc0\x99\x35\xae\xcc\x26\x28\x70\x28\x79\xed\x83\xa3\xc6\xc1\x4e\xf9\xe0\xab\x17\xe1\xa0\x70\x62\x89\x0d\x70\xd4\x06\xaf\x1c\x75\x70\x8c\xfe\xd3\xd0\x79\x56\xbf\x3e\x02\x45\x77\xff\x19\x78\xec\xf8\x79\xaf\xcf\xa9\x88\xb8\xdb\x69\xbc\x54\xca\x86\x66\xe4\xaf\xe5\xf8\x47\x47\xd8\x2e\x6d\x38\x5a\x9b\x38\x1c\x70\xdb\xd7\x7c\xdb\xfd\xf5\xea\x7d\x9e\xbf\xaf\x65\x22\x8c\xea\xdf\x30\xf1\x5b\x9c\x72\x97\x43\x6f\xb9\xc5\x1b\x69\x0a\x2a\x60\xf8\xbf\x34\x12\xd6\x2d\xdf\x5d\xb7\x73\x17\xef\xc6\xd8\x6f\xcd\xcb\xb1\xcd\x2a\xde\xed\x86\x6e\xa9\x51\x3e\x0c\xd7\xd6\x78\x65\x96\x85\x5b\xd7\x0e\xf3\x3f\x70\x49\x16\x03\xdd\x9b\x9c\xda\x81\x58\x9d\x94\x45\x91\x96\x75\x41\x1c\x84\x46\x04\x56\x42\xa7\x71\x18\x11\x69\x2a\x49\x1e\x83\x90\x34\x14\x4b\x21\x75\xf7\xa0\x19\xbf\x59\x85\x0f\x21\x63\xaf\xb7\x6d\x16\xd2\x38\x44\xd2\xc4\xc7\x1a\x77\x1f\x51\x30\x77\x82\x68\xf7\xbe\x2f\x5e\x19\x1a\xed\x8a\x35\x77\xc2\x20\x6e\x84\x54\x82\xa6\x2f\xee\xb0\x74\x0a\x89\x42\xa1\xc3\x2d\x3f\x66\xde\xdc\xa0\x75\xdd\x47\x80\xfc\xcf\x60\x7c\x27\x2b\x56\xaf\xd1\x1c\x8f\x8f\xd9\xc7\x46\x6c\x38\xfe\x1b\x25\xbc\x8f\xf0\x6a\x98\x37\x44\x96\xf4\xfc\x03\x08\xb5\xef\x3e\x2e\xa4\xb8\x67\x22\x9a\x9f\x60\xdc\xe8\xcb\xff\x2a\x41\xb6\x0f\xb1\xf3\xba\x3f\x8b\x87\xf7\xc6\x0c\x40\xa1\xe0\x29\xa9\xfa\x3d\x53\xf5\xa3\x0f\x0d\x6d\x55\xf4\x86\x8e\x6e\x2f\x7c\xf9\x5e\x6b\x85\xd5\x0d\x48\x68\xed\x17\x88\x1a\xa4\x47\x2b\x68\x1e\x22\x74\xc5\x3f\x0a\xa4\xa5\x63\x71\xec\x17\x49\x41\x17\x05\xc7\xeb\x7d\x2a\xcc\x52\x2f\x87\xdd\x4e\xf8\xde\x88\xf7\xc4\xdf\x6e\xe3\x3d\x54\x40\xe6\x8c\x77\x02\xf5\x95\x40\xe2\x6f\xb9\x5b\xe4\xb1\x79\xe7\x5e\x80\xd6\xe8\x53\x98\xa9\x77\x6e\x01\x98\x31\xde\x04\xec\x5e\x36\xd2\x1a\x7f\x6b\x01\x9c\x49\x97\xc2\x05\x31\x3b\x21\xe1\x6f\xf7\x23\xa2\x62\xa0\x60\x38\x3e\xcc\x40\x4b\x07\x98\x76\x6e\x26\x88\x98\x3f\x85\xd5\x50\xcf\x8f\x9b\xab\xe1\x53\x3c\xa8\x5c\x37\x6c\x23\xd7\x6c\x9b\xbb\x93\xc3\x49\x6e\x5c\xe1\xf1\x70\x32\x23\x9b\xd7\x80\xbd\x87\xb5\x39\x6b\xec\x93\x3c\x94\x2a\x59\x7a\x95\xd9\xee\x61\x65\xe9\x8d\x96\xc3\xdf\x3e\x5e\x64\x4d\xdc\x54\xb1\x45\xd3\x12\xc2\xb7\x8d\x7b\xcb\x87\x26\x2d\x1a\x54\x22\x61\xd5\x5c\x4d\x26\x4f\xc7\xb7\xf5\xcf\x81\x98\xab\x5a\x34\x95\x12\x21\x32\xc2\x79\x39\x2a\xe4\x3f\x30\x6e\xdb\x8c\xc1\x6a\x09\x2c\x86\x9f\x18\xdc\xcd\x52\x08\x9a\x05\x37\x10\x85\xa3\x51\x74\x1b\x5b\x29\x3a\x69\x31\x85\x4c\xa2\x4a\xc1\xa4\x68\x79\xd0\xfd\xcd\x19\x1d\x7e\x57\xa1\x95\x24\x31\xfc\x96\x0b\x7f\xc8\xf9\x67\xa1\x96\x09\xfa\x12\x32\x14\xfc\xdf\xc9\x1b\xc8\x85\x73\xb0\x46\x41\xa3\x6d\x56\x28\x55\x82\xb1\x29\x92\xf0\x7a\xd6\xa3\xb0\x36\x50\x38\xb4\x0e\x36\x2b\x13\x4b\x2d\xb7\x78\x39\x75\xab\xd2\x0f\xe2\x75\x8e\x74\xb9\x12\x25\x48\x4f\x65\x3d\x1e\xaa\x19\xe9\xf5\xcf\x1e\xfe\x63\x64\xc8\xc0\xfb\x61\x5e\x4d\x85\xed\x38\xe7\xcf\xf4\xd6\x8e\xf0\x38\x14\xb5\x63\x7b\x7b\xd1\xd5\x0e\xe4\xaa\xf4\xb4\xa3\xb5\x59\xc8\xda\x21\xc9\x2b\xfc\xd6\x0e\xc6\x46\xab\xcd\x0b\x8c\xa0\x9a\x81\xdf\x76\xc2\x93\xb5\x8c\xf1\x19\x7e\x6d\xd6\xe4\xfc\x36\x88\x80\x21\x2f\xf6\xc8\x38\xd7\x58\x52\x36\x0f\x36\x6a\x94\xa6\xf0\xe1\xf3\x35\x96\x5f\x0e\x57\xa2\x08\xc7\x06\x5d\x5d\x7a\xaa\xb0\x08\x6b\x0f\x24\x83\x5a\x0b\x39\x19\x9f\x80\xfc\xb1\xc9\x50\x55\x4f\x90\xcf\x9e\x55\x7b\x36\xd7\x3f\xcb\x2f\x55\x84\xd7\x88\xdf\x59\xef\xb7\x34\x8a\x31\x12\x68\x28\x28\xba\x77\xdd\x7f\x06\x00\x00\xff\xff\x5a\x43\x33\xde\x00\x22\x00\x00") +var _call_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x5a\xdf\x6f\x1b\x37\xf2\x7f\x96\xfe\x8a\x89\x1f\x6a\x09\x51\x24\x39\xe9\xb7\x5f\xc0\xae\x7a\x50\x1d\x25\x35\xe0\xc6\x81\xad\x34\x08\x82\x3c\x50\xbb\xb3\x12\x6b\x8a\xdc\x92\x5c\xc9\xba\xd6\xff\xfb\x61\x86\xdc\xd5\xae\x24\x3b\xbe\x5e\x71\xe8\xbd\x69\x97\x33\xc3\xe1\xcc\x67\x7e\x71\x35\x18\xc0\xb9\xc9\x37\x56\xce\x17\x1e\x5e\x0e\x4f\xfe\x1f\xa6\x0b\x84\xb9\x79\x81\x7e\x81\x16\x8b\x25\x8c\x0b\xbf\x30\xd6\xb5\x07\x03\x98\x2e\xa4\x83\x4c\x2a\x04\xe9\x20\x17\xd6\x83\xc9\xc0\xef\xd0\x2b\x39\xb3\xc2\x6e\xfa\xed\xc1\x20\xf0\x1c\x5c\x26\x09\x99\x45\x04\x67\x32\xbf\x16\x16\x4f\x61\x63\x0a\x48\x84\x06\x8b\xa9\x74\xde\xca\x59\xe1\x11\xa4\x07\xa1\xd3\x81\xb1\xb0\x34\xa9\xcc\x36\x24\x52\x7a\x28\x74\x8a\x96\xb7\xf6\x68\x97\xae\xd4\xe3\xed\xbb\x0f\x70\x89\xce\xa1\x85\xb7\xa8\xd1\x0a\x05\xef\x8b\x99\x92\x09\x5c\xca\x04\xb5\x43\x10\x0e\x72\x7a\xe3\x16\x98\xc2\x8c\xc5\x11\xe3\x1b\x52\xe5\x26\xaa\x02\x6f\x4c\xa1\x53\xe1\xa5\xd1\x3d\x40\x49\x9a\xc3\x0a\xad\x93\x46\xc3\xab\x72\xab\x28\xb0\x07\xc6\x92\x90\x8e\xf0\x74\x00\x0b\x26\x27\xbe\x2e\x08\xbd\x01\x25\xfc\x96\xf5\x09\x06\xd9\x9e\x3b\x05\xa9\x79\x9b\x85\xc9\x11\xfc\x42\x78\x3a\xf5\x5a\x2a\x05\x33\x84\xc2\x61\x56\xa8\x1e\x49\x9b\x15\x1e\x3e\x5e\x4c\x7f\xba\xfa\x30\x85\xf1\xbb\x4f\xf0\x71\x7c\x7d\x3d\x7e\x37\xfd\x74\x06\x6b\xe9\x17\xa6\xf0\x80\x2b\x0c\xa2\xe4\x32\x57\x12\x53\x58\x0b\x6b\x85\xf6\x1b\x30\x19\x49\xf8\x79\x72\x7d\xfe\xd3\xf8\xdd\x74\xfc\xe3\xc5\xe5\xc5\xf4\x13\x18\x0b\x6f\x2e\xa6\xef\x26\x37\x37\xf0\xe6\xea\x1a\xc6\xf0\x7e\x7c\x3d\xbd\x38\xff\x70\x39\xbe\x86\xf7\x1f\xae\xdf\x5f\xdd\x4c\xfa\x70\x83\xa4\x15\x12\xff\xd7\x6d\x9e\xb1\xf7\x2c\x42\x8a\x5e\x48\xe5\x4a\x4b\x7c\x32\x05\xb8\x85\x29\x54\x0a\x0b\xb1\x42\xb0\x98\xa0\x5c\x61\x0a\x02\x12\x93\x6f\x9e\xec\x54\x92\x25\x94\xd1\x73\x3e\xf3\x83\x80\x84\x8b\x0c\xb4\xf1\x3d\x70\x88\xf0\xfd\xc2\xfb\xfc\x74\x30\x58\xaf\xd7\xfd\xb9\x2e\xfa\xc6\xce\x07\x2a\x88\x73\x83\x1f\xfa\x6d\x92\x99\x08\xa5\xa6\x56\x24\x68\xc9\x39\x02\xb2\x82\xcc\xaf\xcc\x5a\x83\xb7\x42\x3b\x91\x90\xab\xe9\x77\xc2\x60\x14\x1e\xf0\x8e\x9e\xbc\x23\xd0\x82\xc5\xdc\x58\xfa\xad\x54\x89\x33\xa9\x3d\x5a\x2d\x14\xcb\x76\xb0\x14\x29\xc2\x6c\x03\xa2\x2e\xb0\x57\x3f\x0c\xc1\x28\xb8\x1b\xa4\xce\x8c\x5d\x32\x2c\xfb\xed\xdf\xdb\xad\xa8\xa1\xf3\x22\xb9\x25\x05\x49\x7e\x52\x58\x8b\xda\x93\x29\x0b\xeb\xe4\x0a\x99\x04\x02\x4d\xb4\xe7\xe4\x97\x9f\x01\xef\x30\x29\x82\xa4\x56\x25\xe4\x14\x3e\xff\x7e\xff\xa5\xd7\x66\xd1\x29\xba\x04\x75\x8a\x29\x9f\xef\xd6\xc1\x7a\xc1\x16\x85\x35\x1e\xaf\x10\x7e\x2d\x9c\xaf\xd1\x64\xd6\x2c\x41\x68\x30\x05\x21\xbe\x6e\x1d\xa9\xbd\x61\x81\x82\x7e\x6b\xb4\xac\x51\xbf\xdd\xaa\x98\x4f\x21\x13\xca\x61\xdc\xd7\x79\xcc\xe9\x34\x52\xaf\xcc\x2d\x49\x36\x96\x20\x6c\x37\x60\xf2\xc4\xa4\x31\x18\xe8\x1c\xd5\x31\xd0\xf5\xdb\x2d\xe2\x3b\x85\xac\xd0\xbc\x6d\x47\x99\x79\x0f\xd2\x59\x17\x7e\x6f\xb7\x48\xec\xb9\xc8\x7d\x61\x91\xed\x89\xd6\x1a\xeb\x40\x2e\x97\x98\x4a\xe1\x51\x6d\xda\xad\xd6\x4a\xd8\xb0\x00\x23\x50\x66\xde\x9f\xa3\x9f\xd0\x63\xa7\x7b\xd6\x6e\xb5\x64\x06\x9d\xb0\xfa\x6c\x34\xe2\xec\x93\x49\x8d\x69\x10\xdf\xf2\x0b\xe9\xfa\x99\x28\x94\xaf\xf6\x25\xa6\x96\x45\x5f\x58\x4d\x3f\xef\x83\x16\x1f\x11\x8c\x56\x1b\x48\x28\xcb\x88\x19\x85\xa7\xdb\x38\x8f\xcb\x78\x38\xd7\x83\x4c\x38\x32\xa1\xcc\x60\x8d\x90\x5b\x7c\x91\x2c\x90\x7c\xa7\x13\x8c\x5a\xba\x8d\x63\xa7\x8e\x80\x76\xeb\x9b\xbc\xef\xcd\xbb\x62\x39\x43\xdb\xe9\xc2\x37\x30\xbc\xcb\x86\x5d\x18\x8d\xf8\x47\xa9\x7b\xe4\x89\xfa\x92\x14\x93\xc7\x83\x32\xff\x8d\xb7\x52\xcf\xc3\x59\xa3\xae\x17\x19\x08\xd0\xb8\x86\xc4\x68\x06\x35\x79\x65\x86\x52\xcf\x21\xb1\x28\x3c\xa6\x3d\x10\x69\x0a\xde\x04\xe4\x55\x38\x6b\x6e\x09\xdf\x7c\x03\x1d\xda\x6c\x04\xc7\xe7\xd7\x93\xf1\x74\x72\x0c\x7f\xfc\x01\xe1\xcd\x51\x78\xf3\xf2\xa8\x5b\xd3\x4c\xea\xab\x2c\x8b\xca\xb1\xc0\x7e\x8e\x78\xdb\x39\xe9\xf6\x57\x42\x15\x78\x95\x05\x35\x23\xed\x44\xa7\x30\x8a\x3c\xcf\x77\x79\x5e\x36\x78\x88\x69\x30\x80\xb1\x73\xb8\x9c\x29\xdc\x0f\xc8\x18\xb1\x1c\xbc\xce\x53\xc6\x22\xf4\x25\x66\x99\x2b\x24\x54\x95\xbb\x46\xf3\xb3\xc6\x2d\xbf\xc9\xf1\x14\x00\xc0\xe4\x3d\x7e\x41\xb1\xc0\x2f\xbc\xf9\x09\xef\xd8\x47\xa5\x09\x09\x55\xe3\x34\xb5\xe8\x5c\xa7\xdb\x0d\xe4\x52\xe7\x85\x3f\x6d\x90\x2f\x71\x69\xec\xa6\xef\x28\x21\x75\xf8\x68\xbd\x70\xd2\x92\x67\x2e\xdc\x85\x26\x9e\x88\xd4\xb7\xc2\x75\xb6\x4b\xe7\xc6\xf9\xd3\x72\x89\x1e\xca\x35\xb6\x05\xb1\x1d\x0f\xef\x8e\xf7\xad\x35\xec\x6e\x91\x70\xf2\x5d\x97\x58\xee\xcf\x2a\x7c\x57\x69\xa2\x9f\x17\x6e\xd1\x61\x38\x6d\x57\xb7\xa9\x60\x04\xde\x16\x78\x10\xfe\x0c\xa9\x7d\x38\x39\x54\x19\xe5\x12\x6f\x8b\x84\x61\x35\x17\x9c\x69\x38\xd2\x05\x65\x5e\x57\xcc\xd8\xe6\xde\x98\x7d\x74\x45\x70\xdd\x4c\x2e\xdf\xbc\x9e\xdc\x4c\xaf\x3f\x9c\x4f\x8f\x6b\x70\x52\x98\x79\x52\xaa\x79\x06\x85\x7a\xee\x17\xac\x3f\x89\x6b\xae\x7e\x26\x9e\x17\x27\x5f\xc2\x1b\x18\x1d\x08\xf9\xd6\xe3\x1c\xf0\xf9\x0b\xcb\xbe\xdf\x37\x5f\x93\x34\x18\xf3\xaf\x41\x92\x37\x4c\x5c\x92\x7b\x53\x12\x3c\xee\xe7\xbf\x18\x54\xe9\x8c\x28\x7e\x14\x4a\xe8\x04\x1f\xd1\x79\x1f\x6b\xf5\xa4\x79\x20\x0f\x2d\xd1\x2f\x4c\xca\x85\x21\x11\xa1\xb6\x94\x08\x4a\x8d\xc6\x7f\x3f\x1b\x8d\x2f\x2f\x6b\xb9\x88\x9f\xcf\xaf\x5e\xd7\xf3\xd3\xf1\xeb\xc9\xe5\xe4\xed\x78\x3a\xd9\xa5\xbd\x99\x8e\xa7\x17\xe7\xfc\xb6\x4c\x5d\x83\x01\xdc\xdc\xca\x9c\x2b\x0c\xe7\x6d\xb3\xcc\xb9\x55\xae\xf4\x75\x3d\xf0\x0b\x43\x4d\xa8\x8d\x05\x34\x13\x3a\x29\x0b\x9b\x2b\x01\xeb\x0d\xc1\xf5\x21\xe7\x9d\xec\x38\xaf\x82\xb0\x74\xef\x2d\xc6\x4d\xd3\x8e\x37\xa5\x5e\x5b\x83\x06\x34\x72\xf2\xe7\x04\xdb\x79\xfa\x21\xe1\x1f\x30\x84\x53\x38\x89\x59\xf4\x91\x34\xfd\x12\x9e\x93\xf8\x3f\x91\xac\x5f\x1d\xe0\xfc\x7b\xa6\xec\xbd\x40\xfb\xef\xa7\x72\x53\xf8\xab\x2c\x3b\x85\x5d\x23\x7e\xbb\x67\xc4\x8a\xfe\x12\xf5\x3e\xfd\xff\xed\xd1\x6f\xd3\x3e\xa1\xca\xe4\xf0\x6c\x0f\x22\x21\xe9\x3e\xdb\x89\x83\x68\x5c\x6e\xef\x58\x1a\x8c\x1e\x28\x34\x2f\x9b\x18\x7e\x28\x53\xfe\x47\x85\xe6\x60\x9b\x4a\xcd\x68\xb3\x11\xed\x81\x45\x6f\x25\xae\x68\xd4\x3c\x76\x2c\x92\x1a\x76\xb3\xa6\xf4\xd5\x87\x8f\x18\x24\x6a\x44\x4e\x2e\xb1\xc1\xa7\xfe\x8c\x7b\x5e\x6a\xd2\xe3\xa8\xc6\x10\x13\xdc\x87\x5b\x84\xa5\xd8\xd0\xa8\x96\x15\xfa\x76\x03\x73\xe1\x20\xdd\x68\xb1\x94\x89\x0b\xf2\xb8\xb9\xb7\x38\x17\x96\xc5\x5a\xfc\xad\x40\x47\x73\x1f\x01\x59\x24\xbe\x10\x4a\x6d\x60\x2e\x69\x78\x23\xee\xce\xcb\x57\xc3\x21\x38\x2f\x73\xd4\x69\x0f\xbe\x7b\x35\xf8\xee\x5b\xb0\x85\xc2\x6e\xbf\x5d\x2b\x61\xd5\x51\xa3\x37\x68\x21\xa2\xe7\x35\xe6\x7e\xd1\xe9\xc2\x0f\x0f\xd4\xc2\x07\x0a\xdb\x41\x5a\x78\x01\x27\x5f\xfa\xa4\xd7\xa8\x81\xdb\xe0\x49\x40\xe5\x30\x4a\xa3\x81\xf7\xea\xf5\x55\xe7\x56\x58\xa1\xc4\x0c\xbb\xa7\x3c\x00\xb3\xad\xd6\x22\x4e\x40\xe4\x14\xc8\x95\x90\x1a\x44\x92\x98\x42\x7b\x32\x7c\x39\xcc\xa8\x0d\xe5\xf7\x63\x5f\xca\xe3\x59\x51\x24\x09\x3a\x57\xa6\x7b\xf6\x1a\xa9\x23\x96\xc4\x0d\x52\x3b\x99\x62\xcd\x2b\x94\x1d\x0c\xa7\xe6\x48\x41\xa3\x74\x29\x70\x69\x1c\x6d\x32\x43\x58\x5b\x1a\xbc\x9c\xd4\x09\xdf\x3c\xa4\x48\xd6\x76\x60\x34\x08\x50\x86\xaf\x3b\x38\xc6\x41\xd8\xb9\xeb\x87\x7c\x4f\xdb\x52\xce\xd1\x66\xdd\x6f\x02\xb9\x0e\x55\x1e\x71\x76\x5a\x21\x0d\x78\x27\x9d\xe7\x8e\x9a\xb4\x94\x0e\x02\x92\xa5\x9e\xf7\x20\x37\x39\xe7\xe9\xaf\x95\xb3\x98\xac\xaf\x27\xbf\x4c\xae\xab\xc6\xe7\xe9\x4e\x2c\x67\x9e\xa3\x6a\x24\x04\x4b\xf3\x96\xc7\xf4\xe8\xc0\x10\x73\x00\x50\xa3\x07\x00\x45\xf2\xb7\xb5\xf1\x7d\xed\x38\x4a\x38\xbf\x75\xcc\x1c\xc3\x3c\x57\x57\xc0\x15\xca\xbb\x9d\xdc\xbd\x9b\x1c\x4c\x5e\x56\x08\x52\x8a\xd3\x0e\x25\xf6\xdd\x49\xa3\xb1\xb0\x1d\x38\xb6\xf8\xbc\xa8\xd9\x78\xcd\xed\x66\x20\xaa\xa5\x06\x5e\x2f\xfb\x56\x11\xaa\x01\xeb\x6e\x0a\x4f\x70\xa0\xfa\xbd\x4d\x7e\x73\xe1\x3e\x38\xf6\x7a\x4c\x7f\x33\x39\xbf\xd0\xbe\x53\x2e\x5e\x68\x78\x01\xe5\x03\x25\x75\x78\xd1\x88\xa2\x03\xd9\xb1\x95\xa2\x42\x8f\xb0\x15\x71\x06\x3b\xaf\x48\x50\x30\x07\x1b\xcd\xa2\xdf\x2f\xce\xc3\x28\x8d\x0c\xf6\xcc\xa2\xef\xe3\x6f\x85\x50\xae\x33\xac\x9a\x85\x70\x02\x6f\xb8\xbc\x8d\xf6\x3a\x49\xe2\x69\xf6\x8e\x67\x35\xb6\x68\x8d\x92\x2d\x74\x82\xe7\x26\xc5\x47\x25\x44\x11\x31\x6d\x54\xbe\x8c\xc0\x3c\xd4\x7b\xb7\xea\x04\x70\x54\x35\x04\x99\x90\xaa\xb0\x78\x74\x06\x07\xd2\x8e\x2b\x6c\x26\x12\xf6\xa5\x43\xe0\x69\xdd\x81\x33\x4b\x5c\x98\x75\x50\xe0\x50\xf2\xda\x07\x47\x85\x83\x9d\xf2\xc1\xd7\x4e\xc2\x41\xe1\xc4\x1c\x6b\xe0\xa8\x0c\x5e\x3a\xea\xe0\x15\xc2\x9f\x86\xce\xf3\xea\xf1\x09\x28\xba\xff\x6b\xe0\xb1\xe3\xe7\xbd\x3e\xa7\x24\xe2\x6e\xa7\xf6\x50\x2a\x1b\x9a\x91\xbf\x97\xe3\x9f\x1c\x61\xbb\xb4\xe1\x68\x4d\xe2\x70\xc0\x6d\x5f\xf3\x75\xf7\x57\xab\x0f\x79\xfe\xa1\x96\x89\x30\xaa\x7f\xc5\xc4\x6f\x71\xca\x5d\x0e\x3d\xe5\x16\x57\xd2\x14\x54\xc0\xf0\x7f\x69\x1c\xae\x5a\xbe\xfb\x76\xeb\x3e\xde\x0b\xb2\xdf\xea\x17\x83\xeb\x45\xbc\xd7\x0e\xdd\x52\xad\x7c\x18\xae\xad\xf1\xba\x30\x0b\x37\xce\x2d\xe6\x7f\xe4\x82\x30\x06\xba\x37\x39\xb5\x03\xb1\x3a\x29\x8b\x22\xdd\x54\x05\xb1\x17\x1a\x11\x58\x08\x9d\xc6\x61\x44\xa4\xa9\x24\x79\x0c\x42\xd2\x50\xcc\x85\xd4\xed\x83\x66\xfc\x6a\x15\x3e\x84\x8c\xbd\xde\xb6\x5e\x48\xe3\x10\x49\x13\x1f\x6b\xdc\x7e\x42\xc1\xdc\x09\xa2\xdd\xbb\xce\x78\x5d\x6a\xb4\x2b\x96\xdc\x09\x83\x58\x09\xa9\x04\x4d\x5f\xdc\x61\xe9\x14\x12\x85\x42\x87\x2f\x1c\x98\x79\xb3\x42\xeb\xda\x4f\x00\xf9\x9f\xc1\xf8\x4e\x56\x2c\x1f\xa3\x39\x9e\x1e\xb3\x4f\x8d\xd8\x70\xfc\x37\x4a\x78\x1f\xe1\x55\x33\x6f\x88\x2c\xe9\xf9\xe3\x17\x6a\xdf\x7e\x5a\x48\x71\xcf\x44\x34\x3f\xc0\xb0\xd6\x97\xff\x5d\x82\x6c\x1f\x62\x97\x55\x7f\x16\x0f\xef\x8d\xe9\x81\x42\xc1\x53\x52\xf9\x69\xaa\xec\x47\x1f\x1b\xda\xca\xe8\x0d\x1d\xdd\x5e\xf8\xf2\x9d\xde\x02\xcb\x1b\x90\xd0\xda\xcf\x10\x35\x48\x8f\x56\xd0\x3c\x44\xe8\x8a\x5f\x53\x48\x4b\xc7\xe2\xd8\x2f\x92\x82\x2e\x0a\x8e\x9f\x36\xa8\x30\x4b\x3d\xef\xb7\x5b\xe1\x7d\x2d\xde\x13\x7f\xb7\x8d\xf7\x50\x01\x99\x33\xde\x09\x54\x57\x02\x89\xbf\xe3\x6e\x91\xc7\xe6\x9d\x7b\x01\x5a\xa3\x57\x61\xa6\xde\xb9\x05\x60\xc6\x78\x13\xb0\x7b\x27\x46\x6b\xfc\xae\x01\x70\x26\x9d\x0b\x17\xc4\xec\x84\x84\xbf\xdb\x8f\x88\x92\x81\x82\xe1\xf4\x30\x03\x2d\x1d\x60\xda\xb9\x99\x20\x62\x7e\x15\x56\x43\x3d\x3f\xad\xaf\x86\x57\xf1\xa0\x72\x59\xb3\x8d\x5c\xb2\x6d\xee\xcf\x0e\x27\xb9\x61\x89\xc7\xc3\xc9\x8c\x6c\x5e\x01\xf6\x01\xd6\xfa\xac\xb1\x4f\xf2\x58\xaa\x64\xe9\x65\x66\x7b\x80\x95\xa5\xd7\x5a\x0e\x7f\xf7\x74\x91\x15\x71\x5d\xc5\x06\x4d\x43\x08\xdf\x36\xee\x2d\x1f\x9a\xb4\x68\x50\x89\x84\x65\x73\x35\x1a\x1d\x0d\xef\xaa\x0f\x23\x31\x57\x35\x68\x4a\x25\x42\x64\x84\xf3\x72\x54\xc8\x7f\x62\xdc\xb6\x1e\x83\xe5\x12\x58\x0c\x1f\x70\xb8\x9b\xa5\x10\x34\x33\x6e\x20\x0a\x47\xa3\xe8\x36\xb6\x52\x74\xd2\x62\x0a\x99\x44\x95\x82\x49\xd1\xf2\xa0\xfb\xab\x33\x3a\x7c\xaa\x43\x2b\x49\x62\xf8\x24\x19\xfe\x1d\xc0\x1f\x4a\xb5\x4c\xd0\x6f\x20\x43\xc1\xdf\xdc\xbc\x81\x5c\x38\x07\x4b\x14\x34\xda\x66\x85\x52\x1b\x30\x36\x45\x12\x5e\xcd\x7a\x14\xd6\x06\x0a\x87\xd6\xc1\x7a\x61\x62\xa9\xe5\x16\x2f\xa7\x6e\x55\xfa\x5e\xbc\xce\x91\x2e\x57\x62\x03\xd2\x53\x59\x8f\x87\xaa\x47\x7a\xf5\xa1\x8b\xbf\x96\x19\x32\xf0\x7e\x98\x97\x53\x61\x33\xce\xf9\x35\x3d\x35\x23\x3c\x0e\x45\xcd\xd8\xde\x5e\x74\x35\x03\xb9\x2c\x3d\xcd\x68\xad\x17\xb2\x66\x48\xf2\x0a\x3f\x35\x83\xb1\xd6\x6a\xf3\x02\x23\xa8\x62\xe0\xa7\x9d\xf0\x64\x2d\x63\x7c\x86\xcf\xba\x15\x39\x3f\xf5\x22\x60\xc8\x8b\x1d\x32\xce\x2d\x6e\x28\x9b\x07\x1b\xd5\x4a\x53\x78\xf1\xf9\x16\x37\x5f\x0e\x57\xa2\x08\xc7\x1a\x5d\x55\x7a\xca\xb0\x08\x6b\x8f\x24\x83\x4a\x0b\x39\x1a\x9e\x81\xfc\xbe\xce\x50\x56\x4f\x90\xcf\x9f\x97\x7b\xd6\xd7\x3f\xcb\x2f\x65\x84\x57\x88\xdf\x59\xef\x36\x34\x8a\x31\x12\x68\x28\x28\xda\xf7\xed\x7f\x05\x00\x00\xff\xff\xfb\x65\x93\x4f\xfc\x22\x00\x00") func call_tracerJsBytes() ([]byte, error) { return bindataRead( @@ -133,7 +133,7 @@ func call_tracerJs() (*asset, error) { } info := bindataFileInfo{name: "call_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x4d, 0x39, 0xde, 0xc6, 0x79, 0xff, 0xe3, 0x5d, 0x47, 0xed, 0xbd, 0xf4, 0x21, 0xe8, 0xc9, 0x4, 0xe0, 0xe0, 0xe4, 0x76, 0x88, 0x25, 0x7f, 0x4f, 0x30, 0xfe, 0x30, 0x1f, 0x8c, 0x4d, 0x76, 0x3d}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x46, 0x79, 0xb6, 0xbc, 0xd2, 0xc, 0x25, 0xb1, 0x22, 0x56, 0xef, 0x77, 0xb9, 0x5e, 0x2e, 0xf4, 0xda, 0xb2, 0x2f, 0x53, 0xa4, 0xff, 0xc8, 0xac, 0xbb, 0x75, 0x22, 0x46, 0x59, 0xe3, 0x1d, 0x7d}} return a, nil } From 4764b2f0be91b05395f6c25b0f74bd8d65aaa590 Mon Sep 17 00:00:00 2001 From: Kirill Elagin Date: Tue, 15 Sep 2020 02:27:17 -0400 Subject: [PATCH 027/948] COYPING: restore the full text text of GPL (#21568) When the license was added to the repository, its text was changed (some sections at the end removed) and, worse, the authors of go-ethereum tried to claim copyright on the license text. The correct way to apply GPL to a project is to copy it verbatim. This change reverts the text of the GPL to the original. --- COPYING | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/COPYING b/COPYING index 8d66e87723..f288702d2f 100644 --- a/COPYING +++ b/COPYING @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2014 The go-ethereum Authors. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -616,4 +616,59 @@ above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. \ No newline at end of file +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 0185ee09936a3657cdd6d03e2c0c630e66954f33 Mon Sep 17 00:00:00 2001 From: Giuseppe Bertone Date: Tue, 15 Sep 2020 10:37:01 +0200 Subject: [PATCH 028/948] core/rawdb: single point of maintenance for writing and deleting tx lookup indexes (#21480) --- core/blockchain.go | 8 +++--- core/rawdb/accessors_indexes.go | 38 +++++++++++++++------------- core/rawdb/accessors_indexes_test.go | 8 +++--- core/rawdb/chain_iterator.go | 4 +-- light/txpool.go | 2 +- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 948242ed89..ce1edd9b7f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -712,7 +712,7 @@ func (bc *BlockChain) writeHeadBlock(block *types.Block) { // Add the block to the canonical chain number scheme and mark as the head batch := bc.db.NewBatch() rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) - rawdb.WriteTxLookupEntries(batch, block) + rawdb.WriteTxLookupEntriesByBlock(batch, block) rawdb.WriteHeadBlockHash(batch, block.Hash()) // If the block is better than our head or is on a different chain, force update heads @@ -1217,9 +1217,9 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // range. In this case, all tx indices of newly imported blocks should be // generated. if bc.txLookupLimit == 0 || ancientLimit <= bc.txLookupLimit || block.NumberU64() >= ancientLimit-bc.txLookupLimit { - rawdb.WriteTxLookupEntries(batch, block) + rawdb.WriteTxLookupEntriesByBlock(batch, block) } else if rawdb.ReadTxIndexTail(bc.db) != nil { - rawdb.WriteTxLookupEntries(batch, block) + rawdb.WriteTxLookupEntriesByBlock(batch, block) } stats.processed++ } @@ -1303,7 +1303,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // Write all the data out into the database rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()) rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i]) - rawdb.WriteTxLookupEntries(batch, block) // Always write tx indices for live blocks, we assume they are needed + rawdb.WriteTxLookupEntriesByBlock(batch, block) // Always write tx indices for live blocks, we assume they are needed // Write everything belongs to the blocks into the database. So that // we can ensure all components of body is completed(body, receipts, diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index 9a05eba8d6..d6dab6808d 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -53,25 +53,29 @@ func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 { return &entry.BlockIndex } -// WriteTxLookupEntries stores a positional metadata for every transaction from -// a block, enabling hash based transaction and receipt lookups. -func WriteTxLookupEntries(db ethdb.KeyValueWriter, block *types.Block) { - number := block.Number().Bytes() - for _, tx := range block.Transactions() { - if err := db.Put(txLookupKey(tx.Hash()), number); err != nil { - log.Crit("Failed to store transaction lookup entry", "err", err) - } +// writeTxLookupEntry stores a positional metadata for a transaction, +// enabling hash based transaction and receipt lookups. +func writeTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash, numberBytes []byte) { + if err := db.Put(txLookupKey(hash), numberBytes); err != nil { + log.Crit("Failed to store transaction lookup entry", "err", err) } } -// WriteTxLookupEntriesByHash is identical to WriteTxLookupEntries, but does not -// require a full types.Block as input. -func WriteTxLookupEntriesByHash(db ethdb.KeyValueWriter, number uint64, hashes []common.Hash) { +// WriteTxLookupEntries is identical to WriteTxLookupEntry, but it works on +// a list of hashes +func WriteTxLookupEntries(db ethdb.KeyValueWriter, number uint64, hashes []common.Hash) { numberBytes := new(big.Int).SetUint64(number).Bytes() for _, hash := range hashes { - if err := db.Put(txLookupKey(hash), numberBytes); err != nil { - log.Crit("Failed to store transaction lookup entry", "err", err) - } + writeTxLookupEntry(db, hash, numberBytes) + } +} + +// WriteTxLookupEntriesByBlock stores a positional metadata for every transaction from +// a block, enabling hash based transaction and receipt lookups. +func WriteTxLookupEntriesByBlock(db ethdb.KeyValueWriter, block *types.Block) { + numberBytes := block.Number().Bytes() + for _, tx := range block.Transactions() { + writeTxLookupEntry(db, tx.Hash(), numberBytes) } } @@ -83,11 +87,9 @@ func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) { } // DeleteTxLookupEntries removes all transaction lookups for a given block. -func DeleteTxLookupEntriesByHash(db ethdb.KeyValueWriter, hashes []common.Hash) { +func DeleteTxLookupEntries(db ethdb.KeyValueWriter, hashes []common.Hash) { for _, hash := range hashes { - if err := db.Delete(txLookupKey(hash)); err != nil { - log.Crit("Failed to delete transaction lookup entry", "err", err) - } + DeleteTxLookupEntry(db, hash) } } diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index 87338c62bf..4734e986e2 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -57,13 +57,13 @@ func (h *testHasher) Hash() common.Hash { // Tests that positional lookup metadata can be stored and retrieved. func TestLookupStorage(t *testing.T) { tests := []struct { - name string - writeTxLookupEntries func(ethdb.Writer, *types.Block) + name string + writeTxLookupEntriesByBlock func(ethdb.Writer, *types.Block) }{ { "DatabaseV6", func(db ethdb.Writer, block *types.Block) { - WriteTxLookupEntries(db, block) + WriteTxLookupEntriesByBlock(db, block) }, }, { @@ -110,7 +110,7 @@ func TestLookupStorage(t *testing.T) { // Insert all the transactions into the database, and verify contents WriteCanonicalHash(db, block.Hash(), block.NumberU64()) WriteBlock(db, block) - tc.writeTxLookupEntries(db, block) + tc.writeTxLookupEntriesByBlock(db, block) for i, tx := range txs { if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil { diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 947a40f88c..3130e922e8 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -218,7 +218,7 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) { // Next block available, pop it off and index it delivery := queue.PopItem().(*blockTxHashes) lastNum = delivery.number - WriteTxLookupEntriesByHash(batch, delivery.number, delivery.hashes) + WriteTxLookupEntries(batch, delivery.number, delivery.hashes) blocks++ txs += len(delivery.hashes) // If enough data was accumulated in memory or we're at the last block, dump to disk @@ -276,7 +276,7 @@ func UnindexTransactions(db ethdb.Database, from uint64, to uint64) { // Otherwise spin up the concurrent iterator and unindexer blocks, txs := 0, 0 for delivery := range hashesCh { - DeleteTxLookupEntriesByHash(batch, delivery.hashes) + DeleteTxLookupEntries(batch, delivery.hashes) txs += len(delivery.hashes) blocks++ diff --git a/light/txpool.go b/light/txpool.go index 11a0e76ae0..2831de5a65 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -185,7 +185,7 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results return err } - rawdb.WriteTxLookupEntries(pool.chainDb, block) + rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block) // Update the transaction pool's state for _, tx := range list { From cf2a77af28e58359f3ec1c0376c3b834f9ad8fb5 Mon Sep 17 00:00:00 2001 From: Mason Fischer Date: Tue, 15 Sep 2020 05:29:51 -0400 Subject: [PATCH 029/948] ethclient: fix BlockNumber (#21565) It didn't actually work because it called a method that doesn't exist. This fixes it also adds a test. Co-authored-by: Felix Lange --- ethclient/ethclient.go | 2 +- ethclient/ethclient_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index a964bd0f8d..8dc34a835e 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -91,7 +91,7 @@ func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl // BlockNumber returns the most recent block number func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) { var result hexutil.Uint64 - err := ec.c.CallContext(ctx, &result, "eth_getBlockNumber", nil) + err := ec.c.CallContext(ctx, &result, "eth_blockNumber") return uint64(result), err } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 16cf5ce61c..0ca72c6ee7 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -352,3 +352,19 @@ func TestChainID(t *testing.T) { t.Fatalf("ChainID returned wrong number: %+v", id) } } + +func TestBlockNumber(t *testing.T) { + backend, _ := newTestBackend(t) + client, _ := backend.Attach() + defer backend.Close() + defer client.Close() + ec := NewClient(client) + + blockNumber, err := ec.BlockNumber(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if blockNumber != 1 { + t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) + } +} From 8696dd39cbb5664a4a6900993e113713e56f3def Mon Sep 17 00:00:00 2001 From: Vinod Damle Date: Wed, 16 Sep 2020 02:39:35 -0400 Subject: [PATCH 030/948] params: allow setting Petersburg block before chain head (#21473) * Allow setting PetersburgBlock before chainhead if it is at the same block as ConstantinopleBlock * Add a negative test --- params/config.go | 6 +++++- params/config_test.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index e5ec64b2bf..2cd66acc2f 100644 --- a/params/config.go +++ b/params/config.go @@ -528,7 +528,11 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock) } if isForkIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, head) { - return newCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock) + // the only case where we allow Petersburg to be set in the past is if it is equal to Constantinople + // mainly to satisfy fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set + if isForkIncompatible(c.ConstantinopleBlock, newcfg.PetersburgBlock, head) { + return newCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock) + } } if isForkIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, head) { return newCompatError("Istanbul fork block", c.IstanbulBlock, newcfg.IstanbulBlock) diff --git a/params/config_test.go b/params/config_test.go index 02c5fe2917..3c8ebaf4a5 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -70,6 +70,23 @@ func TestCheckCompatible(t *testing.T) { RewindTo: 9, }, }, + { + stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, + new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)}, + head: 40, + wantErr: nil, + }, + { + stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, + new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)}, + head: 40, + wantErr: &ConfigCompatError{ + What: "Petersburg fork block", + StoredConfig: nil, + NewConfig: big.NewInt(31), + RewindTo: 30, + }, + }, } for _, test := range tests { From 93f047023f8569e95b767cd000c7c95640dfcad7 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 16 Sep 2020 17:51:16 +0800 Subject: [PATCH 031/948] les/lespay/server: bump database version (#21571) --- les/lespay/server/clientdb.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/les/lespay/server/clientdb.go b/les/lespay/server/clientdb.go index c23f1e3b2f..30cd9a6528 100644 --- a/les/lespay/server/clientdb.go +++ b/les/lespay/server/clientdb.go @@ -37,8 +37,13 @@ const ( // nodeDBVersion is the version identifier of the node data in db // // Changelog: + // Version 0 => 1 // * Replace `lastTotal` with `meta` in positive balance: version 0=>1 - nodeDBVersion = 1 + // + // Version 1 => 2 + // * Positive Balance and negative balance is changed: + // * Cumulative time is replaced with expiration + nodeDBVersion = 2 // dbCleanupCycle is the cycle of db for useless data cleanup dbCleanupCycle = time.Hour From 89884dc3531c3766ba7c0b29be33d3c3af4d1674 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 16 Sep 2020 13:15:22 +0200 Subject: [PATCH 032/948] tests/fuzzers/abi: add fuzzer for fuzzing package accounts/abi (#21217) * tests/fuzzers/abi: added abi fuzzer * accounts/abi: fixed issues found by fuzzing * tests/fuzzers/abi: update fuzzers, added repro test * tests/fuzzers/abi: renamed abi_fuzzer to abifuzzer * tests/fuzzers/abi: updated abi fuzzer * tests/fuzzers/abi: updated abi fuzzer * accounts/abi: minor style fix * go.mod: added go-fuzz dependency * tests/fuzzers/abi: updated abi fuzzer * tests/fuzzers/abi: make linter happy * tests/fuzzers/abi: make linter happy * tests/fuzzers/abi: comment out false positives --- accounts/abi/abi_test.go | 4 +- accounts/abi/error.go | 2 +- accounts/abi/pack.go | 23 ++-- accounts/abi/type.go | 2 +- accounts/abi/unpack.go | 5 +- go.mod | 2 + go.sum | 6 + tests/fuzzers/abi/abifuzzer.go | 186 ++++++++++++++++++++++++++++ tests/fuzzers/abi/abifuzzer_test.go | 50 ++++++++ 9 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 tests/fuzzers/abi/abifuzzer.go create mode 100644 tests/fuzzers/abi/abifuzzer_test.go diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index f41c91aa78..aedb63ab2f 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -33,7 +33,7 @@ import ( const jsondata = ` [ - { "type" : "function", "name" : "", "stateMutability" : "view" }, + { "type" : "function", "name" : ""}, { "type" : "function", "name" : "balance", "stateMutability" : "view" }, { "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, { "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, @@ -88,7 +88,7 @@ var ( ) var methods = map[string]Method{ - "": NewMethod("", "", Function, "view", false, false, nil, nil), + "": NewMethod("", "", Function, "", false, false, nil, nil), "balance": NewMethod("balance", "balance", Function, "view", false, false, nil, nil), "send": NewMethod("send", "send", Function, "", false, false, []Argument{{"amount", Uint256, false}}, nil), "test": NewMethod("test", "test", Function, "", false, false, []Argument{{"number", Uint32, false}}, nil), diff --git a/accounts/abi/error.go b/accounts/abi/error.go index b63a215ad9..f0f71b6c91 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -52,7 +52,7 @@ func sliceTypeCheck(t Type, val reflect.Value) error { } } - if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.GetType().Kind() { + if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) } return nil diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go index 096648be12..beae070c16 100644 --- a/accounts/abi/pack.go +++ b/accounts/abi/pack.go @@ -17,6 +17,8 @@ package abi import ( + "errors" + "fmt" "math/big" "reflect" @@ -33,35 +35,38 @@ func packBytesSlice(bytes []byte, l int) []byte { // packElement packs the given reflect value according to the abi specification in // t. -func packElement(t Type, reflectValue reflect.Value) []byte { +func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { switch t.T { case IntTy, UintTy: - return packNum(reflectValue) + return packNum(reflectValue), nil case StringTy: - return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()) + return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil case AddressTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } - return common.LeftPadBytes(reflectValue.Bytes(), 32) + return common.LeftPadBytes(reflectValue.Bytes(), 32), nil case BoolTy: if reflectValue.Bool() { - return math.PaddedBigBytes(common.Big1, 32) + return math.PaddedBigBytes(common.Big1, 32), nil } - return math.PaddedBigBytes(common.Big0, 32) + return math.PaddedBigBytes(common.Big0, 32), nil case BytesTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } - return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()) + if reflectValue.Type() != reflect.TypeOf([]byte{}) { + return []byte{}, errors.New("Bytes type is neither slice nor array") + } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil case FixedBytesTy, FunctionTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } - return common.RightPadBytes(reflectValue.Bytes(), 32) + return common.RightPadBytes(reflectValue.Bytes(), 32), nil default: - panic("abi: fatal error") + return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T) } } diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 1b4ca318a1..9fbaa89fec 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -346,7 +346,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { return append(ret, tail...), nil default: - return packElement(t, v), nil + return packElement(t, v) } } diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index bfc35f7aef..1f11db9f36 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -224,7 +224,10 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { return forEachUnpack(t, output[begin:], 0, length) case ArrayTy: if isDynamicType(*t.Elem) { - offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:])) + offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) + if offset > uint64(len(output)) { + return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output)) + } return forEachUnpack(t, output[offset:], 0, t.Size) } return forEachUnpack(t, output[index:], 0, t.Size) diff --git a/go.mod b/go.mod index 3da9a262cf..77e1dda688 100755 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 + github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 // indirect github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c github.com/fatih/color v1.3.0 github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc @@ -27,6 +28,7 @@ require ( github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.4.2 github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 + github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.5.4 diff --git a/go.sum b/go.sum index 31c2c48221..949f7a21b4 100755 --- a/go.sum +++ b/go.sum @@ -59,6 +59,8 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= @@ -99,6 +101,10 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go new file mode 100644 index 0000000000..ed5c7c0586 --- /dev/null +++ b/tests/fuzzers/abi/abifuzzer.go @@ -0,0 +1,186 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "bytes" + "fmt" + "math/rand" + "reflect" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/crypto" + fuzz "github.com/google/gofuzz" +) + +func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byte) bool { + outptr := reflect.New(reflect.TypeOf(inputType)) + if err := abi.Unpack(outptr.Interface(), method, input); err == nil { + output, err := abi.Pack(method, input) + if err != nil { + // We have some false positives as we can unpack these type successfully, but not pack them + if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" || + err.Error() == "abi: cannot use uint8 as type int8 as argument" { + return false + } + panic(err) + } + if !bytes.Equal(input, output[4:]) { + panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, output[4:])) + } + return true + } + return false +} + +func packUnpack(abi abi.ABI, method string, input []interface{}) bool { + if packed, err := abi.Pack(method, input); err == nil { + outptr := reflect.New(reflect.TypeOf(input)) + err := abi.Unpack(outptr.Interface(), method, packed) + if err != nil { + panic(err) + } + out := outptr.Elem().Interface() + if !reflect.DeepEqual(input, out) { + panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out)) + } + return true + } + return false +} + +type args struct { + name string + typ string +} + +func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) { + sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name) + if stateMutability != nil { + sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability) + } + if payable != nil { + sig += fmt.Sprintf(`, "payable": %v `, *payable) + } + if len(inputs) > 0 { + sig += `, "inputs" : [ {` + for i, inp := range inputs { + sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) + if i+1 < len(inputs) { + sig += "," + } + } + sig += "} ]" + sig += `, "outputs" : [ {` + for i, inp := range inputs { + sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ) + if i+1 < len(inputs) { + sig += "," + } + } + sig += "} ]" + } + sig += `}]` + + return abi.JSON(strings.NewReader(sig)) +} + +func fillStruct(structs []interface{}, data []byte) { + if structs != nil && len(data) != 0 { + fuzz.NewFromGoFuzz(data).Fuzz(&structs) + } +} + +func createStructs(args []args) []interface{} { + structs := make([]interface{}, len(args)) + for i, arg := range args { + t, err := abi.NewType(arg.typ, "", nil) + if err != nil { + panic(err) + } + structs[i] = reflect.New(t.GetType()).Elem() + } + return structs +} + +func runFuzzer(input []byte) int { + good := false + + names := []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} + stateMut := []string{"", "pure", "view", "payable"} + stateMutabilites := []*string{nil, &stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} + pays := []string{"true", "false"} + payables := []*string{nil, &pays[0], &pays[1]} + varNames := []string{"a", "b", "c", "d", "e", "f", "g"} + varNames = append(varNames, names...) + varTypes := []string{"bool", "address", "bytes", "string", + "uint8", "int8", "uint8", "int8", "uint16", "int16", + "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", + "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", + "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", + "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", + "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", + "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", + "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", + "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", + "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", + "bytes32", "bytes"} + rnd := rand.New(rand.NewSource(123456)) + if len(input) > 0 { + kec := crypto.Keccak256(input) + rnd = rand.New(rand.NewSource(int64(kec[0]))) + } + name := names[rnd.Intn(len(names))] + stateM := stateMutabilites[rnd.Intn(len(stateMutabilites))] + payable := payables[rnd.Intn(len(payables))] + maxLen := 5 + for k := 1; k < maxLen; k++ { + var arg []args + for i := k; i > 0; i-- { + argName := varNames[i] + argTyp := varTypes[rnd.Int31n(int32(len(varTypes)))] + if rnd.Int31n(10) == 0 { + argTyp += "[]" + } else if rnd.Int31n(10) == 0 { + arrayArgs := rnd.Int31n(30) + 1 + argTyp += fmt.Sprintf("[%d]", arrayArgs) + } + arg = append(arg, args{ + name: argName, + typ: argTyp, + }) + } + abi, err := createABI(name, stateM, payable, arg) + if err != nil { + continue + } + structs := createStructs(arg) + b := unpackPack(abi, name, structs, input) + fillStruct(structs, input) + c := packUnpack(abi, name, structs) + good = good || b || c + } + if good { + return 1 + } + return 0 +} + +func Fuzz(input []byte) int { + return runFuzzer(input) +} diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go new file mode 100644 index 0000000000..c72577e9ee --- /dev/null +++ b/tests/fuzzers/abi/abifuzzer_test.go @@ -0,0 +1,50 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package abi + +import ( + "testing" +) + +// TestReplicate can be used to replicate crashers from the fuzzing tests. +// Just replace testString with the data in .quoted +func TestReplicate(t *testing.T) { + testString := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00000000000" + + "00000000000000000000" + + "00000000000000000000" + + "00000001" + + data := []byte(testString) + runFuzzer(data) +} + +// TestGenerateCorpus can be used to add corpus for the fuzzer. +// Just replace corpusHex with the hexEncoded output you want to add to the fuzzer. +func TestGenerateCorpus(t *testing.T) { + /* + corpusHex := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + data := common.FromHex(corpusHex) + checksum := sha1.Sum(data) + outf := fmt.Sprintf("corpus/%x", checksum) + if err := ioutil.WriteFile(outf, data, 0777); err != nil { + panic(err) + } + */ +} From faba018b29fa35e668e98926526cbaad5863e297 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 16 Sep 2020 13:17:50 +0200 Subject: [PATCH 033/948] cmd/utils: use preconfigured testnet flags instead of networkid (#21561) * cmd/utils: use preconfigured testnet flags instead of networkid * cmd/utils: shorter description Co-authored-by: Martin Holst Swende * Update flags.go Co-authored-by: Martin Holst Swende --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2c57e533ed..d47b3a2f66 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -128,7 +128,7 @@ var ( } NetworkIdFlag = cli.Uint64Flag{ Name: "networkid", - Usage: "Network identifier (integer, 1=Frontier, 3=Ropsten, 4=Rinkeby, 5=Görli)", + Usage: "Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead)", Value: eth.DefaultConfig.NetworkId, } GoerliFlag = cli.BoolFlag{ From 07751c3d26a9c740fa1bf792199a47065db8aa8a Mon Sep 17 00:00:00 2001 From: Giuseppe Bertone Date: Thu, 17 Sep 2020 10:23:56 +0200 Subject: [PATCH 034/948] cmd/geth: added counters to the geth inspect report (#21495) * database: added counters * Improved stats for ancient db * Small improvement * Better message and added percentage while counting receipts * Fast counting for receipts * added info message * Show both receips itemscount from ancient db and counted receipts * Fixed default case * Removed counter for receipts in ancient store * Removed counting of receipts present in leveldb --- core/rawdb/database.go | 190 ++++++++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 76 deletions(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 316b5addf3..b1ac3e9587 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -238,6 +238,36 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer return frdb, nil } +type counter uint64 + +func (c counter) String() string { + return fmt.Sprintf("%d", c) +} + +func (c counter) Percentage(current uint64) string { + return fmt.Sprintf("%d", current*100/uint64(c)) +} + +// stat stores sizes and count for a parameter +type stat struct { + size common.StorageSize + count counter +} + +// Add size to the stat and increase the counter by 1 +func (s *stat) Add(size common.StorageSize) { + s.size += size + s.count++ +} + +func (s *stat) Size() string { + return s.size.String() +} + +func (s *stat) Count() string { + return s.count.String() +} + // InspectDatabase traverses the entire database and checks the size // of all different categories of data. func InspectDatabase(db ethdb.Database) error { @@ -250,36 +280,38 @@ func InspectDatabase(db ethdb.Database) error { logged = time.Now() // Key-value store statistics - total common.StorageSize - headerSize common.StorageSize - bodySize common.StorageSize - receiptSize common.StorageSize - tdSize common.StorageSize - numHashPairing common.StorageSize - hashNumPairing common.StorageSize - trieSize common.StorageSize - codeSize common.StorageSize - txlookupSize common.StorageSize - accountSnapSize common.StorageSize - storageSnapSize common.StorageSize - preimageSize common.StorageSize - bloomBitsSize common.StorageSize - cliqueSnapsSize common.StorageSize + headers stat + bodies stat + receipts stat + tds stat + numHashPairings stat + hashNumPairings stat + tries stat + codes stat + txLookups stat + accountSnaps stat + storageSnaps stat + preimages stat + bloomBits stat + cliqueSnaps stat // Ancient store statistics - ancientHeaders common.StorageSize - ancientBodies common.StorageSize - ancientReceipts common.StorageSize - ancientHashes common.StorageSize - ancientTds common.StorageSize + ancientHeadersSize common.StorageSize + ancientBodiesSize common.StorageSize + ancientReceiptsSize common.StorageSize + ancientTdsSize common.StorageSize + ancientHashesSize common.StorageSize // Les statistic - chtTrieNodes common.StorageSize - bloomTrieNodes common.StorageSize + chtTrieNodes stat + bloomTrieNodes stat // Meta- and unaccounted data - metadata common.StorageSize - unaccounted common.StorageSize + metadata stat + unaccounted stat + + // Totals + total common.StorageSize ) // Inspect key-value database first. for it.Next() { @@ -289,98 +321,104 @@ func InspectDatabase(db ethdb.Database) error { ) total += size switch { - case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): - tdSize += size - case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): - numHashPairing += size case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength): - headerSize += size - case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): - hashNumPairing += size + headers.Add(size) case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength): - bodySize += size + bodies.Add(size) case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): - receiptSize += size + receipts.Add(size) + case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): + tds.Add(size) + case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): + numHashPairings.Add(size) + case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): + hashNumPairings.Add(size) + case len(key) == common.HashLength: + tries.Add(size) + case bytes.HasPrefix(key, codePrefix) && len(key) == len(codePrefix)+common.HashLength: + codes.Add(size) case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): - txlookupSize += size + txLookups.Add(size) case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): - accountSnapSize += size + accountSnaps.Add(size) case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): - storageSnapSize += size + storageSnaps.Add(size) case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength): - preimageSize += size + preimages.Add(size) case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): - bloomBitsSize += size + bloomBits.Add(size) case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength: - cliqueSnapsSize += size + cliqueSnaps.Add(size) case bytes.HasPrefix(key, []byte("cht-")) && len(key) == 4+common.HashLength: - chtTrieNodes += size + chtTrieNodes.Add(size) case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength: - bloomTrieNodes += size - case bytes.HasPrefix(key, codePrefix) && len(key) == len(codePrefix)+common.HashLength: - codeSize += size - case len(key) == common.HashLength: - trieSize += size + bloomTrieNodes.Add(size) default: var accounted bool for _, meta := range [][]byte{databaseVerisionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey} { if bytes.Equal(key, meta) { - metadata += size + metadata.Add(size) accounted = true break } } if !accounted { - unaccounted += size + unaccounted.Add(size) } } - count += 1 + count++ if count%1000 == 0 && time.Since(logged) > 8*time.Second { log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) logged = time.Now() } } // Inspect append-only file store then. - ancients := []*common.StorageSize{&ancientHeaders, &ancientBodies, &ancientReceipts, &ancientHashes, &ancientTds} + ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize} for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable} { if size, err := db.AncientSize(category); err == nil { - *ancients[i] += common.StorageSize(size) + *ancientSizes[i] += common.StorageSize(size) total += common.StorageSize(size) } } + // Get number of ancient rows inside the freezer + ancients := counter(0) + if count, err := db.Ancients(); err == nil { + ancients = counter(count) + } // Display the database statistic. stats := [][]string{ - {"Key-Value store", "Headers", headerSize.String()}, - {"Key-Value store", "Bodies", bodySize.String()}, - {"Key-Value store", "Receipts", receiptSize.String()}, - {"Key-Value store", "Difficulties", tdSize.String()}, - {"Key-Value store", "Block number->hash", numHashPairing.String()}, - {"Key-Value store", "Block hash->number", hashNumPairing.String()}, - {"Key-Value store", "Transaction index", txlookupSize.String()}, - {"Key-Value store", "Bloombit index", bloomBitsSize.String()}, - {"Key-Value store", "Contract codes", codeSize.String()}, - {"Key-Value store", "Trie nodes", trieSize.String()}, - {"Key-Value store", "Trie preimages", preimageSize.String()}, - {"Key-Value store", "Account snapshot", accountSnapSize.String()}, - {"Key-Value store", "Storage snapshot", storageSnapSize.String()}, - {"Key-Value store", "Clique snapshots", cliqueSnapsSize.String()}, - {"Key-Value store", "Singleton metadata", metadata.String()}, - {"Ancient store", "Headers", ancientHeaders.String()}, - {"Ancient store", "Bodies", ancientBodies.String()}, - {"Ancient store", "Receipts", ancientReceipts.String()}, - {"Ancient store", "Difficulties", ancientTds.String()}, - {"Ancient store", "Block number->hash", ancientHashes.String()}, - {"Light client", "CHT trie nodes", chtTrieNodes.String()}, - {"Light client", "Bloom trie nodes", bloomTrieNodes.String()}, + {"Key-Value store", "Headers", headers.Size(), headers.Count()}, + {"Key-Value store", "Bodies", bodies.Size(), bodies.Count()}, + {"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()}, + {"Key-Value store", "Difficulties", tds.Size(), tds.Count()}, + {"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()}, + {"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()}, + {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, + {"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()}, + {"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, + {"Key-Value store", "Trie nodes", tries.Size(), tries.Count()}, + {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, + {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, + {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, + {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, + {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, + {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, + {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, + {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, + {"Ancient store", "Difficulties", ancientTdsSize.String(), ancients.String()}, + {"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()}, + {"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()}, + {"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()}, } table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Database", "Category", "Size"}) - table.SetFooter([]string{"", "Total", total.String()}) + table.SetHeader([]string{"Database", "Category", "Size", "Items"}) + table.SetFooter([]string{"", "Total", total.String(), " "}) table.AppendBulk(stats) table.Render() - if unaccounted > 0 { - log.Error("Database contains unaccounted data", "size", unaccounted) + if unaccounted.size > 0 { + log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count) } + return nil } From fb835c024c9e9c02edd2351f20e7b8efcbe99543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 8 Sep 2020 11:13:16 +0300 Subject: [PATCH 035/948] eth/downloader: dynamically move pivot even during chain sync --- eth/downloader/downloader.go | 225 +++++++++++++++++++++++------- eth/downloader/downloader_test.go | 14 +- eth/downloader/testchain_test.go | 25 ++-- 3 files changed, 192 insertions(+), 72 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index df3b6bdb68..c6ca9af013 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -138,7 +138,10 @@ type Downloader struct { receiptWakeCh chan bool // [eth/63] Channel to signal the receipt fetcher of new tasks headerProcCh chan []*types.Header // [eth/62] Channel to feed the header processor new tasks - // for stateFetcher + // State sync + pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root + pivotLock sync.RWMutex // Lock protecting pivot header reads from updates + stateSyncStart chan *stateSync trackStateReq chan *stateReq stateCh chan dataPack // [eth/63] Channel receiving inbound node state data @@ -451,10 +454,17 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I }(time.Now()) // Look up the sync boundaries: the common ancestor and the target block - latest, err := d.fetchHeight(p) + latest, pivot, err := d.fetchHead(p) if err != nil { return err } + if mode == FastSync && pivot == nil { + // If no pivot block was returned, the head is below the min full block + // threshold (i.e. new chian). In that case we won't really fast sync + // anyway, but still need a valid pivot block to avoid some code hitting + // nil panics on an access. + pivot = d.blockchain.CurrentBlock().Header() + } height := latest.Number.Uint64() origin, err := d.findAncestor(p, latest) @@ -469,22 +479,21 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.syncStatsLock.Unlock() // Ensure our origin point is below any fast sync pivot point - pivot := uint64(0) if mode == FastSync { if height <= uint64(fsMinFullBlocks) { origin = 0 } else { - pivot = height - uint64(fsMinFullBlocks) - if pivot <= origin { - origin = pivot - 1 + pivotNumber := pivot.Number.Uint64() + if pivotNumber <= origin { + origin = pivotNumber - 1 } // Write out the pivot into the database so a rollback beyond it will // reenable fast sync - rawdb.WriteLastPivotNumber(d.stateDB, pivot) + rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber) } } d.committed = 1 - if mode == FastSync && pivot != 0 { + if mode == FastSync && pivot.Number.Uint64() != 0 { d.committed = 0 } if mode == FastSync { @@ -530,13 +539,17 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.syncInitHook(origin, height) } fetchers := []func() error{ - func() error { return d.fetchHeaders(p, origin+1, pivot) }, // Headers are always retrieved - func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync - func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync - func() error { return d.processHeaders(origin+1, pivot, td) }, + func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved + func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync + func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync + func() error { return d.processHeaders(origin+1, td) }, } if mode == FastSync { - fetchers = append(fetchers, func() error { return d.processFastSyncContent(latest) }) + d.pivotLock.Lock() + d.pivotHeader = pivot + d.pivotLock.Unlock() + + fetchers = append(fetchers, func() error { return d.processFastSyncContent() }) } else if mode == FullSync { fetchers = append(fetchers, d.processFullSyncContent) } @@ -617,22 +630,26 @@ func (d *Downloader) Terminate() { d.Cancel() } -// fetchHeight retrieves the head header of the remote peer to aid in estimating -// the total time a pending synchronisation would take. -func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) { - p.log.Debug("Retrieving remote chain height") +// fetchHead retrieves the head header and prior pivot block (if available) from +// a remote peer. +func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) { + p.log.Debug("Retrieving remote chain head") + mode := d.getMode() // Request the advertised remote head block and wait for the response - head, _ := p.peer.Head() - go p.peer.RequestHeadersByHash(head, 1, 0, false) + latest, _ := p.peer.Head() + fetch := 1 + if mode == FastSync { + fetch = 2 // head + pivot headers + } + go p.peer.RequestHeadersByHash(latest, fetch, fsMinFullBlocks-1, true) ttl := d.requestTTL() timeout := time.After(ttl) - mode := d.getMode() for { select { case <-d.cancelCh: - return nil, errCanceled + return nil, nil, errCanceled case packet := <-d.headerCh: // Discard anything not from the origin peer @@ -640,23 +657,36 @@ func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) { log.Debug("Received headers from incorrect peer", "peer", packet.PeerId()) break } - // Make sure the peer actually gave something valid + // Make sure the peer gave us at least one and at most the requested headers headers := packet.(*headerPack).headers - if len(headers) != 1 { - p.log.Warn("Multiple headers for single request", "headers", len(headers)) - return nil, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) + if len(headers) == 0 || len(headers) > fetch { + return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch) } + // The first header needs to be the head, validate against the checkpoint + // and request. If only 1 header was returned, make sure there's no pivot + // or there was not one requested. head := headers[0] if (mode == FastSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint { - p.log.Warn("Remote head below checkpoint", "number", head.Number, "hash", head.Hash()) - return nil, errUnsyncedPeer + return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint) + } + if len(headers) == 1 { + if mode == FastSync && head.Number.Uint64() > uint64(fsMinFullBlocks) { + return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer) + } + p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", head.Hash()) + return head, nil, nil + } + // At this point we have 2 headers in total and the first is the + // validated head of the chian. Check the pivot number and return, + pivot := headers[1] + if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) { + return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks)) } - p.log.Debug("Remote head header identified", "number", head.Number, "hash", head.Hash()) - return head, nil + return head, pivot, nil case <-timeout: p.log.Debug("Waiting for head header timed out", "elapsed", ttl) - return nil, errTimeout + return nil, nil, errTimeout case <-d.bodyCh: case <-d.receiptCh: @@ -871,14 +901,14 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) case <-d.cancelCh: return 0, errCanceled - case packer := <-d.headerCh: + case packet := <-d.headerCh: // Discard anything not from the origin peer - if packer.PeerId() != p.id { - log.Debug("Received headers from incorrect peer", "peer", packer.PeerId()) + if packet.PeerId() != p.id { + log.Debug("Received headers from incorrect peer", "peer", packet.PeerId()) break } // Make sure the peer actually gave something valid - headers := packer.(*headerPack).headers + headers := packet.(*headerPack).headers if len(headers) != 1 { p.log.Warn("Multiple headers for single request", "headers", len(headers)) return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) @@ -937,12 +967,13 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) // other peers are only accepted if they map cleanly to the skeleton. If no one // can fill in the skeleton - not even the origin peer - it's assumed invalid and // the origin is dropped. -func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) error { +func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { p.log.Debug("Directing header downloads", "origin", from) defer p.log.Debug("Header download terminated") // Create a timeout timer, and the associated header fetcher skeleton := true // Skeleton assembly phase or finishing up + pivoting := false // Whether the next request is pivot verification request := time.Now() // time of the last skeleton fetch request timeout := time.NewTimer(0) // timer to dump a non-responsive active peer <-timeout.C // timeout channel should be initially empty @@ -963,6 +994,20 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) go p.peer.RequestHeadersByNumber(from, MaxHeaderFetch, 0, false) } } + getNextPivot := func() { + pivoting = true + request = time.Now() + + ttl = d.requestTTL() + timeout.Reset(ttl) + + d.pivotLock.RLock() + pivot := d.pivotHeader.Number.Uint64() + d.pivotLock.RUnlock() + + p.log.Trace("Fetching next pivot header", "number", pivot+uint64(fsMinFullBlocks)) + go p.peer.RequestHeadersByNumber(pivot+uint64(fsMinFullBlocks), 2, fsMinFullBlocks-9, false) // move +64 when it's 2x64-8 deep + } // Start pulling the header chain skeleton until all is done ancestor := from getHeaders(from) @@ -982,8 +1027,46 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) headerReqTimer.UpdateSince(request) timeout.Stop() + // If the pivot is being checked, move if it became stale and run the real retrieval + var pivot uint64 + + d.pivotLock.RLock() + if d.pivotHeader != nil { + pivot = d.pivotHeader.Number.Uint64() + } + d.pivotLock.RUnlock() + + if pivoting { + if packet.Items() == 2 { + // Retrieve the headers and do some sanity checks, just in case + headers := packet.(*headerPack).headers + + if have, want := headers[0].Number.Uint64(), pivot+uint64(fsMinFullBlocks); have != want { + log.Warn("Peer sent invalid next pivot", "have", have, "want", want) + return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want) + } + if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want { + log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want) + return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want) + } + log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number) + pivot = headers[0].Number.Uint64() + + d.pivotLock.Lock() + d.pivotHeader = headers[0] + d.pivotLock.Unlock() + + // Write out the pivot into the database so a rollback beyond + // it will reenable fast sync and update the state root that + // the state syncer will be downloading. + rawdb.WriteLastPivotNumber(d.stateDB, pivot) + } + pivoting = false + getHeaders(from) + continue + } // If the skeleton's finished, pull any remaining head headers directly from the origin - if packet.Items() == 0 && skeleton { + if skeleton && packet.Items() == 0 { skeleton = false getHeaders(from) continue @@ -1061,7 +1144,14 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) return errCanceled } from += uint64(len(headers)) - getHeaders(from) + + // If we're still skeleton filling fast sync, check pivot staleness + // before continuing to the next skeleton filling + if skeleton && pivot > 0 { + getNextPivot() + } else { + getHeaders(from) + } } else { // No headers delivered, or all of them being delayed, sleep a bit and retry p.log.Trace("All headers delayed, waiting") @@ -1390,7 +1480,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) // processHeaders takes batches of retrieved headers from an input channel and // keeps processing and scheduling them into the header chain and downloader's // queue until the stream ends or a failure occurs. -func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) error { +func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { // Keep a count of uncertain headers to roll back var ( rollback uint64 // Zero means no rollback (fine as you can't unroll the genesis) @@ -1493,6 +1583,14 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er // In case of header only syncing, validate the chunk immediately if mode == FastSync || mode == LightSync { // If we're importing pure headers, verify based on their recentness + var pivot uint64 + + d.pivotLock.RLock() + if d.pivotHeader != nil { + pivot = d.pivotHeader.Number.Uint64() + } + d.pivotLock.RUnlock() + frequency := fsHeaderCheckFrequency if chunk[len(chunk)-1].Number.Uint64()+uint64(fsHeaderForceVerify) > pivot { frequency = 1 @@ -1609,10 +1707,13 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error { // processFastSyncContent takes fetch results from the queue and writes them to the // database. It also controls the synchronisation of state nodes of the pivot block. -func (d *Downloader) processFastSyncContent(latest *types.Header) error { +func (d *Downloader) processFastSyncContent() error { // Start syncing state of the reported head block. This should get us most of // the state of the pivot block. - sync := d.syncState(latest.Root) + d.pivotLock.RLock() + sync := d.syncState(d.pivotHeader.Root) + d.pivotLock.RUnlock() + defer func() { // The `sync` object is replaced every time the pivot moves. We need to // defer close the very last active one, hence the lazy evaluation vs. @@ -1627,12 +1728,6 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error { } go closeOnErr(sync) - // Figure out the ideal pivot block. Note, that this goalpost may move if the - // sync takes long enough for the chain head to move significantly. - pivot := uint64(0) - if height := latest.Number.Uint64(); height > uint64(fsMinFullBlocks) { - pivot = height - uint64(fsMinFullBlocks) - } // To cater for moving pivot points, track the pivot block and subsequently // accumulated download results separately. var ( @@ -1659,22 +1754,46 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error { if d.chainInsertHook != nil { d.chainInsertHook(results) } - if oldPivot != nil { + // If we haven't downloaded the pivot block yet, check pivot staleness + // notifications from the header downloader + d.pivotLock.RLock() + pivot := d.pivotHeader + d.pivotLock.RUnlock() + + if oldPivot == nil { + if pivot.Root != sync.root { + sync.Cancel() + sync = d.syncState(pivot.Root) + + go closeOnErr(sync) + } + } else { results = append(append([]*fetchResult{oldPivot}, oldTail...), results...) } // Split around the pivot block and process the two sides via fast/full sync if atomic.LoadInt32(&d.committed) == 0 { - latest = results[len(results)-1].Header - if height := latest.Number.Uint64(); height > pivot+2*uint64(fsMinFullBlocks) { - log.Warn("Pivot became stale, moving", "old", pivot, "new", height-uint64(fsMinFullBlocks)) - pivot = height - uint64(fsMinFullBlocks) + latest := results[len(results)-1].Header + // If the height is above the pivot block by 2 sets, it means the pivot + // become stale in the network and it was garbage collected, move to a + // new pivot. + // + // Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those + // need to be taken into account, otherwise we're detecting the pivot move + // late and will drop peers due to unavailable state!!! + if height := latest.Number.Uint64(); height >= pivot.Number.Uint64()+2*uint64(fsMinFullBlocks)-uint64(reorgProtHeaderDelay) { + log.Warn("Pivot became stale, moving", "old", pivot.Number.Uint64(), "new", height-uint64(fsMinFullBlocks)+uint64(reorgProtHeaderDelay)) + pivot = results[len(results)-1-fsMinFullBlocks+reorgProtHeaderDelay].Header // must exist as lower old pivot is uncommitted + + d.pivotLock.Lock() + d.pivotHeader = pivot + d.pivotLock.Unlock() // Write out the pivot into the database so a rollback beyond it will // reenable fast sync - rawdb.WriteLastPivotNumber(d.stateDB, pivot) + rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64()) } } - P, beforeP, afterP := splitAroundPivot(pivot, results) + P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results) if err := d.commitFastSyncData(beforeP, sync); err != nil { return err } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 5400049cc2..4bf1e4d469 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -427,11 +427,7 @@ func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) { // origin; associated with a particular peer in the download tester. The returned // function can be used to retrieve batches of headers from the particular peer. func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { - if reverse { - panic("reverse header requests not supported") - } - - result := dlp.chain.headersByHash(origin, amount, skip) + result := dlp.chain.headersByHash(origin, amount, skip, reverse) go dlp.dl.downloader.DeliverHeaders(dlp.id, result) return nil } @@ -440,11 +436,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i // origin; associated with a particular peer in the download tester. The returned // function can be used to retrieve batches of headers from the particular peer. func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { - if reverse { - panic("reverse header requests not supported") - } - - result := dlp.chain.headersByNumber(origin, amount, skip) + result := dlp.chain.headersByNumber(origin, amount, skip, reverse) go dlp.dl.downloader.DeliverHeaders(dlp.id, result) return nil } @@ -1698,7 +1690,7 @@ func testCheckpointEnforcement(t *testing.T, protocol int, mode SyncMode) { if mode == FastSync || mode == LightSync { expect = errUnsyncedPeer } - if err := tester.sync("peer", nil, mode); err != expect { + if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) { t.Fatalf("block sync error mismatch: have %v, want %v", err, expect) } if mode == FastSync || mode == LightSync { diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 66376502c5..2d7b4d1f10 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -170,18 +170,27 @@ func (tc *testChain) td(hash common.Hash) *big.Int { return tc.tdm[hash] } -// headersByHash returns headers in ascending order from the given hash. -func (tc *testChain) headersByHash(origin common.Hash, amount int, skip int) []*types.Header { +// headersByHash returns headers in order from the given hash. +func (tc *testChain) headersByHash(origin common.Hash, amount int, skip int, reverse bool) []*types.Header { num, _ := tc.hashToNumber(origin) - return tc.headersByNumber(num, amount, skip) + return tc.headersByNumber(num, amount, skip, reverse) } -// headersByNumber returns headers in ascending order from the given number. -func (tc *testChain) headersByNumber(origin uint64, amount int, skip int) []*types.Header { +// headersByNumber returns headers from the given number. +func (tc *testChain) headersByNumber(origin uint64, amount int, skip int, reverse bool) []*types.Header { result := make([]*types.Header, 0, amount) - for num := origin; num < uint64(len(tc.chain)) && len(result) < amount; num += uint64(skip) + 1 { - if header, ok := tc.headerm[tc.chain[int(num)]]; ok { - result = append(result, header) + + if !reverse { + for num := origin; num < uint64(len(tc.chain)) && len(result) < amount; num += uint64(skip) + 1 { + if header, ok := tc.headerm[tc.chain[int(num)]]; ok { + result = append(result, header) + } + } + } else { + for num := int64(origin); num >= 0 && len(result) < amount; num -= int64(skip) + 1 { + if header, ok := tc.headerm[tc.chain[int(num)]]; ok { + result = append(result, header) + } } } return result From f354c622ca05289d9190d3718fd0da3aa4e18f27 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 18 Sep 2020 14:26:19 +0200 Subject: [PATCH 036/948] core: fix a typo in comment (#21439) --- core/block_validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/block_validator.go b/core/block_validator.go index b7af12ff9e..8312093935 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -89,7 +89,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD if rbloom != header.Bloom { return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) } - // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]])) + // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) receiptSha := types.DeriveSha(receipts, new(trie.Trie)) if receiptSha != header.ReceiptHash { return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) From 9a39c6bcb1fe11a252ac832d995a72e5f2210fbe Mon Sep 17 00:00:00 2001 From: Osoro Bironga Date: Sun, 20 Sep 2020 11:43:57 +0300 Subject: [PATCH 037/948] accounts/abi: improve documentation and names (#21540) * accounts: abi/bid/backends; cleaned doc errors, camelCase refactors and anonymous variable assignments * acounts/abi/bind: doc errors, anonymous parameter assignments * accounts/abi: doc edits, camelCase refactors * accounts/abi/bind: review fix * reverted name changes * name revert Co-authored-by: Osoro Bironga --- accounts/abi/abi.go | 10 +++++----- accounts/abi/abi_test.go | 2 +- accounts/abi/argument.go | 18 +++++++++--------- accounts/abi/bind/auth.go | 2 +- accounts/abi/bind/backend.go | 6 +++--- accounts/abi/bind/base.go | 2 +- accounts/abi/bind/bind.go | 28 ++++++++++++++-------------- accounts/abi/bind/template.go | 15 ++++++++------- accounts/abi/event.go | 2 +- accounts/abi/event_test.go | 2 +- accounts/abi/method.go | 4 ++-- accounts/abi/pack.go | 5 ++--- accounts/abi/reflect.go | 2 +- accounts/abi/topics.go | 4 ++-- accounts/abi/type.go | 4 ++-- accounts/abi/unpack.go | 17 +++++++++-------- 16 files changed, 62 insertions(+), 61 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 6bbc01800d..38196ed25c 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -80,7 +80,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return append(method.ID, arguments...), nil } -// Unpack output in v according to the abi specification +// Unpack output in v according to the abi specification. func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event @@ -96,7 +96,7 @@ func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { return fmt.Errorf("abi: could not locate named method or event") } -// UnpackIntoMap unpacks a log into the provided map[string]interface{} +// UnpackIntoMap unpacks a log into the provided map[string]interface{}. func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event @@ -112,7 +112,7 @@ func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) return fmt.Errorf("abi: could not locate named method or event") } -// UnmarshalJSON implements json.Unmarshaler interface +// UnmarshalJSON implements json.Unmarshaler interface. func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { Type string @@ -201,8 +201,8 @@ func (abi *ABI) overloadedEventName(rawName string) string { return name } -// MethodById looks up a method by the 4-byte id -// returns nil if none found +// MethodById looks up a method by the 4-byte id, +// returns nil if none found. func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { if len(sigdata) < 4 { return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index aedb63ab2f..f7d7f7aa62 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -1092,7 +1092,7 @@ func TestDoubleDuplicateEventNames(t *testing.T) { } // TestUnnamedEventParam checks that an event with unnamed parameters is -// correctly handled +// correctly handled. // The test runs the abi of the following contract. // contract TestEvent { // event send(uint256, uint256); diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 81151ef0e0..aaef3bd41c 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -41,7 +41,7 @@ type ArgumentMarshaling struct { Indexed bool } -// UnmarshalJSON implements json.Unmarshaler interface +// UnmarshalJSON implements json.Unmarshaler interface. func (argument *Argument) UnmarshalJSON(data []byte) error { var arg ArgumentMarshaling err := json.Unmarshal(data, &arg) @@ -59,7 +59,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error { return nil } -// NonIndexed returns the arguments with indexed arguments filtered out +// NonIndexed returns the arguments with indexed arguments filtered out. func (arguments Arguments) NonIndexed() Arguments { var ret []Argument for _, arg := range arguments { @@ -70,12 +70,12 @@ func (arguments Arguments) NonIndexed() Arguments { return ret } -// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[] +// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]. func (arguments Arguments) isTuple() bool { return len(arguments) > 1 } -// Unpack performs the operation hexdata -> Go format +// Unpack performs the operation hexdata -> Go format. func (arguments Arguments) Unpack(v interface{}, data []byte) error { if len(data) == 0 { if len(arguments) != 0 { @@ -100,7 +100,7 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { return arguments.unpackAtomic(v, marshalledValues[0]) } -// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value +// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { // Make sure map is not nil if v == nil { @@ -122,7 +122,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) return nil } -// unpackAtomic unpacks ( hexdata -> go ) a single value +// unpackAtomic unpacks ( hexdata -> go ) a single value. func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { dst := reflect.ValueOf(v).Elem() src := reflect.ValueOf(marshalledValues) @@ -207,13 +207,13 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { return retval, nil } -// PackValues performs the operation Go format -> Hexdata -// It is the semantic opposite of UnpackValues +// PackValues performs the operation Go format -> Hexdata. +// It is the semantic opposite of UnpackValues. func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { return arguments.Pack(args...) } -// Pack performs the operation Go format -> Hexdata +// Pack performs the operation Go format -> Hexdata. func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { // Make sure arguments match up and pack them abiArgs := arguments diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index e51f0bd8ea..c891b0a3e9 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -45,7 +45,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { } // NewKeyStoreTransactor is a utility method to easily create a transaction signer from -// an decrypted key from a keystore +// a decrypted key from a keystore. func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { return &TransactOpts{ From: account.Address, diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index ca60cc1b43..eed6a44bbc 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -41,7 +41,7 @@ var ( ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") ) -// ContractCaller defines the methods needed to allow operating with contract on a read +// ContractCaller defines the methods needed to allow operating with a contract on a read // only basis. type ContractCaller interface { // CodeAt returns the code of the given account. This is needed to differentiate @@ -62,8 +62,8 @@ type PendingContractCaller interface { PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) } -// ContractTransactor defines the methods needed to allow operating with contract -// on a write only basis. Beside the transacting method, the remainder are helpers +// ContractTransactor defines the methods needed to allow operating with a contract +// on a write only basis. Besides the transacting method, the remainder are helpers // used when the user does not provide some needed values, but rather leaves it up // to the transactor to decide. type ContractTransactor interface { diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 311e4108cd..d9935e3de7 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -177,7 +177,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in } // RawTransact initiates a transaction with the given raw calldata as the input. -// It's usually used to initiates transaction for invoking **Fallback** function. +// It's usually used to initiate transactions for invoking **Fallback** function. func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { // todo(rjl493456442) check the method is payable or not, // reject invalid transaction at the first place diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 7aaa85bdc0..0e98709b14 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -52,7 +52,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // contracts is the map of each individual contract requested binding contracts = make(map[string]*tmplContract) - // structs is the map of all reclared structs shared by passed contracts. + // structs is the map of all redeclared structs shared by passed contracts. structs = make(map[string]*tmplStruct) // isLib is the map used to flag each encountered library as such @@ -80,10 +80,10 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] fallback *tmplMethod receive *tmplMethod - // identifiers are used to detect duplicated identifier of function - // and event. For all calls, transacts and events, abigen will generate + // identifiers are used to detect duplicated identifiers of functions + // and events. For all calls, transacts and events, abigen will generate // corresponding bindings. However we have to ensure there is no - // identifier coliision in the bindings of these categories. + // identifier collisions in the bindings of these categories. callIdentifiers = make(map[string]bool) transactIdentifiers = make(map[string]bool) eventIdentifiers = make(map[string]bool) @@ -246,7 +246,7 @@ var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) stri LangJava: bindTypeJava, } -// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go one. +// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. func bindBasicTypeGo(kind abi.Type) string { switch kind.T { case abi.AddressTy: @@ -286,7 +286,7 @@ func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } } -// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java one. +// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java ones. func bindBasicTypeJava(kind abi.Type) string { switch kind.T { case abi.AddressTy: @@ -330,7 +330,7 @@ func bindBasicTypeJava(kind abi.Type) string { } // pluralizeJavaType explicitly converts multidimensional types to predefined -// type in go side. +// types in go side. func pluralizeJavaType(typ string) string { switch typ { case "boolean": @@ -369,7 +369,7 @@ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) } // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same -// funcionality as for simple types, but dynamic types get converted to hashes. +// functionality as for simple types, but dynamic types get converted to hashes. func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { bound := bindTypeGo(kind, structs) @@ -386,7 +386,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } // bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same -// funcionality as for simple types, but dynamic types get converted to hashes. +// functionality as for simple types, but dynamic types get converted to hashes. func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { bound := bindTypeJava(kind, structs) @@ -394,7 +394,7 @@ func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { // parameters that are not value types i.e. arrays and structs are not // stored directly but instead a keccak256-hash of an encoding is stored. // - // We only convert stringS and bytes to hash, still need to deal with + // We only convert strings and bytes to hash, still need to deal with // array(both fixed-size and dynamic-size) and struct. if bound == "String" || bound == "byte[]" { bound = "Hash" @@ -415,7 +415,7 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: - // We compose raw struct name and canonical parameter expression + // We compose a raw struct name and a canonical parameter expression // together here. The reason is before solidity v0.5.11, kind.TupleRawName // is empty, so we use canonical parameter expression to distinguish // different struct definition. From the consideration of backward @@ -454,7 +454,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: - // We compose raw struct name and canonical parameter expression + // We compose a raw struct name and a canonical parameter expression // together here. The reason is before solidity v0.5.11, kind.TupleRawName // is empty, so we use canonical parameter expression to distinguish // different struct definition. From the consideration of backward @@ -486,7 +486,7 @@ func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { } // namedType is a set of functions that transform language specific types to -// named versions that my be used inside method names. +// named versions that may be used inside method names. var namedType = map[Lang]func(string, abi.Type) string{ LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, LangJava: namedTypeJava, @@ -528,7 +528,7 @@ func alias(aliases map[string]string, n string) string { } // methodNormalizer is a name transformer that modifies Solidity method names to -// conform to target language naming concentions. +// conform to target language naming conventions. var methodNormalizer = map[Lang]func(string) string{ LangGo: abi.ToCamelCase, LangJava: decapitalise, diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index e57b03cfa6..f19f1315ae 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -30,7 +30,7 @@ type tmplData struct { type tmplContract struct { Type string // Type name of the main contract binding InputABI string // JSON ABI used as the input to generate the binding from - InputBin string // Optional EVM bytecode used to denetare deploy code from + InputBin string // Optional EVM bytecode used to generate deploy code from FuncSigs map[string]string // Optional map: string signature -> 4-byte signature Constructor abi.Method // Contract constructor for deploy parametrization Calls map[string]*tmplMethod // Contract calls that only read state data @@ -50,7 +50,8 @@ type tmplMethod struct { Structured bool // Whether the returns should be accumulated into a struct } -// tmplEvent is a wrapper around an a +// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed +// and cached data fields. type tmplEvent struct { Original abi.Event // Original event as parsed by the abi package Normalized abi.Event // Normalized version of the parsed fields @@ -64,7 +65,7 @@ type tmplField struct { SolKind abi.Type // Raw abi type information } -// tmplStruct is a wrapper around an abi.tuple contains an auto-generated +// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated // struct name. type tmplStruct struct { Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. @@ -78,8 +79,8 @@ var tmplSource = map[Lang]string{ LangJava: tmplSourceJava, } -// tmplSourceGo is the Go source template use to generate the contract binding -// based on. +// tmplSourceGo is the Go source template that the generated Go contract binding +// is based on. const tmplSourceGo = ` // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. @@ -543,8 +544,8 @@ var ( {{end}} ` -// tmplSourceJava is the Java source template use to generate the contract binding -// based on. +// tmplSourceJava is the Java source template that the generated Java contract binding +// is based on. const tmplSourceJava = ` // This file is an automatically generated Java binding. Do not modify as any // change will likely be lost upon the next re-generation! diff --git a/accounts/abi/event.go b/accounts/abi/event.go index f1e5398f79..b238a36d7c 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -32,7 +32,7 @@ type Event struct { // the raw name and a suffix will be added in the case of a event overload. // // e.g. - // There are two events have same name: + // These are two events that have the same name: // * foo(int,int) // * foo(uint,uint) // The event name of the first one wll be resolved as foo while the second one diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 1f19a27413..79504c28ce 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -371,7 +371,7 @@ func TestEventUnpackIndexed(t *testing.T) { require.Equal(t, uint8(8), rst.Value2) } -// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input. +// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input. func TestEventIndexedWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` type testStruct struct { diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 37c7af65ea..f69e3ee9b5 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -45,7 +45,7 @@ const ( // If the method is `Const` no transaction needs to be created for this // particular Method call. It can easily be simulated using a local VM. // For example a `Balance()` method only needs to retrieve something -// from the storage and therefore requires no Tx to be send to the +// from the storage and therefore requires no Tx to be sent to the // network. A method such as `Transact` does require a Tx and thus will // be flagged `false`. // Input specifies the required input parameters for this gives method. @@ -54,7 +54,7 @@ type Method struct { // the raw name and a suffix will be added in the case of a function overload. // // e.g. - // There are two functions have same name: + // These are two functions that have the same name: // * foo(int,int) // * foo(uint,uint) // The method name of the first one will be resolved as foo while the second one diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go index beae070c16..0cd91cb4fa 100644 --- a/accounts/abi/pack.go +++ b/accounts/abi/pack.go @@ -27,7 +27,7 @@ import ( ) // packBytesSlice packs the given bytes as [L, V] as the canonical representation -// bytes slice +// bytes slice. func packBytesSlice(bytes []byte, l int) []byte { len := packNum(reflect.ValueOf(l)) return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...) @@ -70,7 +70,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { } } -// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation +// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation. func packNum(value reflect.Value) []byte { switch kind := value.Kind(); kind { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: @@ -82,5 +82,4 @@ func packNum(value reflect.Value) []byte { default: panic("abi: fatal error") } - } diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 75f1a00a5a..f4812b06bf 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -61,7 +61,7 @@ func reflectIntType(unsigned bool, size int) reflect.Type { return reflect.TypeOf(&big.Int{}) } -// mustArrayToBytesSlice creates a new byte slice with the exact same size as value +// mustArrayToByteSlice creates a new byte slice with the exact same size as value // and copies the bytes in value to the new slice. func mustArrayToByteSlice(value reflect.Value) reflect.Value { slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) diff --git a/accounts/abi/topics.go b/accounts/abi/topics.go index 474b9a4a7b..360df7d5e8 100644 --- a/accounts/abi/topics.go +++ b/accounts/abi/topics.go @@ -102,7 +102,7 @@ func genIntType(rule int64, size uint) []byte { var topic [common.HashLength]byte if rule < 0 { // if a rule is negative, we need to put it into two's complement. - // extended to common.Hashlength bytes. + // extended to common.HashLength bytes. topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} } for i := uint(0); i < size; i++ { @@ -120,7 +120,7 @@ func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error }) } -// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs +// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs. func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error { return parseTopicWithSetter(fields, topics, func(arg Argument, reconstr interface{}) { diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 9fbaa89fec..ffa3acafe9 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -44,7 +44,7 @@ const ( FunctionTy ) -// Type is the reflection of the supported argument type +// Type is the reflection of the supported argument type. type Type struct { Elem *Type Size int @@ -264,7 +264,7 @@ func overloadedArgName(rawName string, names map[string]string) (string, error) return fieldName, nil } -// String implements Stringer +// String implements Stringer. func (t Type) String() (out string) { return t.stringKind } diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 1f11db9f36..ec06984936 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -26,13 +26,13 @@ import ( ) var ( - // MaxUint256 is the maximum value that can be represented by a uint256 + // MaxUint256 is the maximum value that can be represented by a uint256. MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) - // MaxInt256 is the maximum value that can be represented by a int256 + // MaxInt256 is the maximum value that can be represented by a int256. MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1) ) -// ReadInteger reads the integer based on its kind and returns the appropriate value +// ReadInteger reads the integer based on its kind and returns the appropriate value. func ReadInteger(typ Type, b []byte) interface{} { if typ.T == UintTy { switch typ.Size { @@ -73,7 +73,7 @@ func ReadInteger(typ Type, b []byte) interface{} { } } -// reads a bool +// readBool reads a bool. func readBool(word []byte) (bool, error) { for _, b := range word[:31] { if b != 0 { @@ -91,7 +91,8 @@ func readBool(word []byte) (bool, error) { } // A function type is simply the address with the function selection signature at the end. -// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +// +// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { if t.T != FunctionTy { return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") @@ -104,7 +105,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { return } -// ReadFixedBytes uses reflection to create a fixed array to be read from +// ReadFixedBytes uses reflection to create a fixed array to be read from. func ReadFixedBytes(t Type, word []byte) (interface{}, error) { if t.T != FixedBytesTy { return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") @@ -117,7 +118,7 @@ func ReadFixedBytes(t Type, word []byte) (interface{}, error) { } -// iteratively unpack elements +// forEachUnpack iteratively unpack elements. func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { if size < 0 { return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) @@ -252,7 +253,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) { } } -// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. +// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type. func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32]) bigOffsetEnd.Add(bigOffsetEnd, common.Big32) From 2c097bb7a2f6e048dd9a0c4f6d74c10c07ec12df Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 21 Sep 2020 16:33:35 +0200 Subject: [PATCH 038/948] mobile: better api for java users (#21580) * (mobile): Adds string representations for types * mobile: better interfaces add stringer to types Co-authored-by: sarath --- go.mod | 1 + go.sum | 22 ++++++++++++++++++++++ mobile/common.go | 10 ++++++++++ mobile/geth.go | 16 ++++++++++++++++ mobile/types.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+) diff --git a/go.mod b/go.mod index 77e1dda688..2a701c3614 100755 --- a/go.mod +++ b/go.mod @@ -60,6 +60,7 @@ require ( github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/mobile v0.0.0-20200801112145-973feb4309de // indirect golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 golang.org/x/text v0.3.3 diff --git a/go.sum b/go.sum index 949f7a21b4..10bec96411 100755 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -212,11 +213,25 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= @@ -224,6 +239,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -245,6 +261,12 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/mobile/common.go b/mobile/common.go index d7e0457261..124712b4b1 100644 --- a/mobile/common.go +++ b/mobile/common.go @@ -87,6 +87,11 @@ func (h *Hash) GetHex() string { return h.hash.Hex() } +// String implements Stringer interface for printable representation of the hash. +func (h *Hash) String() string { + return h.GetHex() +} + // Hashes represents a slice of hashes. type Hashes struct{ hashes []common.Hash } @@ -188,6 +193,11 @@ func (a *Address) GetHex() string { return a.address.Hex() } +// String returns a printable representation of the address. +func (a *Address) String() string { + return a.GetHex() +} + // Addresses represents a slice of addresses. type Addresses struct{ addresses []common.Address } diff --git a/mobile/geth.go b/mobile/geth.go index ba58507d63..b561e33675 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -90,6 +90,22 @@ func NewNodeConfig() *NodeConfig { return &config } +// AddBootstrapNode adds an additional bootstrap node to the node config. +func (conf *NodeConfig) AddBootstrapNode(node *Enode) { + conf.BootstrapNodes.Append(node) +} + +// EncodeJSON encodes a NodeConfig into a JSON data dump. +func (conf *NodeConfig) EncodeJSON() (string, error) { + data, err := json.Marshal(conf) + return string(data), err +} + +// String returns a printable representation of the node config. +func (conf *NodeConfig) String() string { + return encodeOrError(conf) +} + // Node represents a Geth Ethereum node instance. type Node struct { node *node.Node diff --git a/mobile/types.go b/mobile/types.go index 9d75520282..de6457e7e1 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -28,6 +28,20 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +type jsonEncoder interface { + EncodeJSON() (string, error) +} + +// encodeOrError tries to encode the object into json. +// If the encoding fails the resulting error is returned. +func encodeOrError(encoder jsonEncoder) string { + enc, err := encoder.EncodeJSON() + if err != nil { + return err.Error() + } + return enc +} + // A Nonce is a 64-bit hash which proves (combined with the mix-hash) that // a sufficient amount of computation has been carried out on a block. type Nonce struct { @@ -44,6 +58,11 @@ func (n *Nonce) GetHex() string { return fmt.Sprintf("0x%x", n.nonce[:]) } +// String returns a printable representation of the nonce. +func (n *Nonce) String() string { + return n.GetHex() +} + // Bloom represents a 256 bit bloom filter. type Bloom struct { bloom types.Bloom @@ -59,6 +78,11 @@ func (b *Bloom) GetHex() string { return fmt.Sprintf("0x%x", b.bloom[:]) } +// String returns a printable representation of the bloom filter. +func (b *Bloom) String() string { + return b.GetHex() +} + // Header represents a block header in the Ethereum blockchain. type Header struct { header *types.Header @@ -97,6 +121,11 @@ func (h *Header) EncodeJSON() (string, error) { return string(data), err } +// String returns a printable representation of the header. +func (h *Header) String() string { + return encodeOrError(h) +} + func (h *Header) GetParentHash() *Hash { return &Hash{h.header.ParentHash} } func (h *Header) GetUncleHash() *Hash { return &Hash{h.header.UncleHash} } func (h *Header) GetCoinbase() *Address { return &Address{h.header.Coinbase} } @@ -168,6 +197,11 @@ func (b *Block) EncodeJSON() (string, error) { return string(data), err } +// String returns a printable representation of the block. +func (b *Block) String() string { + return encodeOrError(b) +} + func (b *Block) GetParentHash() *Hash { return &Hash{b.block.ParentHash()} } func (b *Block) GetUncleHash() *Hash { return &Hash{b.block.UncleHash()} } func (b *Block) GetCoinbase() *Address { return &Address{b.block.Coinbase()} } @@ -244,6 +278,11 @@ func (tx *Transaction) EncodeJSON() (string, error) { return string(data), err } +// String returns a printable representation of the transaction. +func (tx *Transaction) String() string { + return encodeOrError(tx) +} + func (tx *Transaction) GetData() []byte { return tx.tx.Data() } func (tx *Transaction) GetGas() int64 { return int64(tx.tx.Gas()) } func (tx *Transaction) GetGasPrice() *BigInt { return &BigInt{tx.tx.GasPrice()} } @@ -336,6 +375,11 @@ func (r *Receipt) EncodeJSON() (string, error) { return string(data), err } +// String returns a printable representation of the receipt. +func (r *Receipt) String() string { + return encodeOrError(r) +} + func (r *Receipt) GetStatus() int { return int(r.receipt.Status) } func (r *Receipt) GetPostState() []byte { return r.receipt.PostState } func (r *Receipt) GetCumulativeGasUsed() int64 { return int64(r.receipt.CumulativeGasUsed) } From 129cf075e963df10f42da81d817a4c12f7d4bf16 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 22 Sep 2020 10:17:39 +0200 Subject: [PATCH 039/948] p2p: move rlpx into separate package (#21464) This change moves the RLPx protocol implementation into a separate package, p2p/rlpx. The new package can be used to establish RLPx connections for protocol testing purposes. Co-authored-by: Felix Lange --- cmd/devp2p/main.go | 1 + cmd/devp2p/rlpxcmd.go | 94 +++++ p2p/message_test.go | 11 - p2p/peer_test.go | 15 +- p2p/{ => rlpx}/rlpx.go | 724 +++++++++++++++++------------------- p2p/{ => rlpx}/rlpx_test.go | 400 +++++--------------- p2p/server.go | 18 +- p2p/server_test.go | 55 ++- p2p/transport.go | 177 +++++++++ p2p/transport_test.go | 148 ++++++++ 10 files changed, 901 insertions(+), 742 deletions(-) create mode 100644 cmd/devp2p/rlpxcmd.go rename p2p/{ => rlpx}/rlpx.go (63%) rename p2p/{ => rlpx}/rlpx_test.go (58%) create mode 100644 p2p/transport.go create mode 100644 p2p/transport_test.go diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go index 6064d17c70..9eebd9b137 100644 --- a/cmd/devp2p/main.go +++ b/cmd/devp2p/main.go @@ -63,6 +63,7 @@ func init() { discv5Command, dnsCommand, nodesetCommand, + rlpxCommand, } } diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go new file mode 100644 index 0000000000..14eb5989d1 --- /dev/null +++ b/cmd/devp2p/rlpxcmd.go @@ -0,0 +1,94 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "net" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/rlpx" + "github.com/ethereum/go-ethereum/rlp" + "gopkg.in/urfave/cli.v1" +) + +var ( + rlpxCommand = cli.Command{ + Name: "rlpx", + Usage: "RLPx Commands", + Subcommands: []cli.Command{ + rlpxPingCommand, + }, + } + rlpxPingCommand = cli.Command{ + Name: "ping", + Usage: "Perform a RLPx handshake", + ArgsUsage: "", + Action: rlpxPing, + } +) + +func rlpxPing(ctx *cli.Context) error { + n := getNodeArg(ctx) + + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP())) + if err != nil { + return err + } + conn := rlpx.NewConn(fd, n.Pubkey()) + + ourKey, _ := crypto.GenerateKey() + _, err = conn.Handshake(ourKey) + if err != nil { + return err + } + + code, data, _, err := conn.Read() + if err != nil { + return err + } + switch code { + case 0: + var h devp2pHandshake + if err := rlp.DecodeBytes(data, &h); err != nil { + return fmt.Errorf("invalid handshake: %v", err) + } + fmt.Printf("%+v\n", h) + case 1: + var msg []p2p.DiscReason + if rlp.DecodeBytes(data, &msg); len(msg) == 0 { + return fmt.Errorf("invalid disconnect message") + } + return fmt.Errorf("received disconnect message: %v", msg[0]) + default: + return fmt.Errorf("invalid message code %d, expected handshake (code zero)", code) + } + return nil +} + +// devp2pHandshake is the RLP structure of the devp2p protocol handshake. +type devp2pHandshake struct { + Version uint64 + Name string + Caps []p2p.Cap + ListenPort uint64 + ID hexutil.Bytes // secp256k1 public key + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} diff --git a/p2p/message_test.go b/p2p/message_test.go index a01f755561..e575c5d96e 100644 --- a/p2p/message_test.go +++ b/p2p/message_test.go @@ -18,11 +18,9 @@ package p2p import ( "bytes" - "encoding/hex" "fmt" "io" "runtime" - "strings" "testing" "time" ) @@ -141,12 +139,3 @@ func TestEOFSignal(t *testing.T) { default: } } - -func unhex(str string) []byte { - r := strings.NewReplacer("\t", "", " ", "", "\n", "") - b, err := hex.DecodeString(r.Replace(str)) - if err != nil { - panic(fmt.Sprintf("invalid hex string: %q", str)) - } - return b -} diff --git a/p2p/peer_test.go b/p2p/peer_test.go index e40deb98f0..4308bbd2eb 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -86,9 +86,15 @@ func newNode(id enode.ID, addr string) *enode.Node { } func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) { - fd1, fd2 := net.Pipe() - c1 := &conn{fd: fd1, node: newNode(randomID(), ""), transport: newTestTransport(&newkey().PublicKey, fd1)} - c2 := &conn{fd: fd2, node: newNode(randomID(), ""), transport: newTestTransport(&newkey().PublicKey, fd2)} + var ( + fd1, fd2 = net.Pipe() + key1, key2 = newkey(), newkey() + t1 = newTestTransport(&key2.PublicKey, fd1, nil) + t2 = newTestTransport(&key1.PublicKey, fd2, &key1.PublicKey) + ) + + c1 := &conn{fd: fd1, node: newNode(uintID(1), ""), transport: t1} + c2 := &conn{fd: fd2, node: newNode(uintID(2), ""), transport: t2} for _, p := range protos { c1.caps = append(c1.caps, p.cap()) c2.caps = append(c2.caps, p.cap()) @@ -173,9 +179,12 @@ func TestPeerPing(t *testing.T) { } } +// This test checks that a disconnect message sent by a peer is returned +// as the error from Peer.run. func TestPeerDisconnect(t *testing.T) { closer, rw, _, disc := testPeer(nil) defer closer() + if err := SendItems(rw, discMsg, DiscQuitting); err != nil { t.Fatal(err) } diff --git a/p2p/rlpx.go b/p2p/rlpx/rlpx.go similarity index 63% rename from p2p/rlpx.go rename to p2p/rlpx/rlpx.go index 4d903a08a0..2021bf08be 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx/rlpx.go @@ -14,7 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package p2p +// Package rlpx implements the RLPx transport protocol. +package rlpx import ( "bytes" @@ -29,169 +30,312 @@ import ( "fmt" "hash" "io" - "io/ioutil" mrand "math/rand" "net" - "sync" "time" - "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/golang/snappy" "golang.org/x/crypto/sha3" ) -const ( - maxUint24 = ^uint32(0) >> 8 - - sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 - sigLen = crypto.SignatureLength // elliptic S256 - pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte - shaLen = 32 // hash length (for nonce etc) - - authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 - authRespLen = pubLen + shaLen + 1 - - eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */ - - encAuthMsgLen = authMsgLen + eciesOverhead // size of encrypted pre-EIP-8 initiator handshake - encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply +// Conn is an RLPx network connection. It wraps a low-level network connection. The +// underlying connection should not be used for other activity when it is wrapped by Conn. +// +// Before sending messages, a handshake must be performed by calling the Handshake method. +// This type is not generally safe for concurrent use, but reading and writing of messages +// may happen concurrently after the handshake. +type Conn struct { + dialDest *ecdsa.PublicKey + conn net.Conn + handshake *handshakeState + snappy bool +} - // total timeout for encryption handshake and protocol - // handshake in both directions. - handshakeTimeout = 5 * time.Second +type handshakeState struct { + enc cipher.Stream + dec cipher.Stream - // This is the timeout for sending the disconnect reason. - // This is shorter than the usual timeout because we don't want - // to wait if the connection is known to be bad anyway. - discWriteTimeout = 1 * time.Second -) - -// errPlainMessageTooLarge is returned if a decompressed message length exceeds -// the allowed 24 bits (i.e. length >= 16MB). -var errPlainMessageTooLarge = errors.New("message length >= 16MB") + macCipher cipher.Block + egressMAC hash.Hash + ingressMAC hash.Hash +} -// rlpx is the transport protocol used by actual (non-test) connections. -// It wraps the frame encoder with locks and read/write deadlines. -type rlpx struct { - fd net.Conn +// NewConn wraps the given network connection. If dialDest is non-nil, the connection +// behaves as the initiator during the handshake. +func NewConn(conn net.Conn, dialDest *ecdsa.PublicKey) *Conn { + return &Conn{ + dialDest: dialDest, + conn: conn, + } +} - rmu, wmu sync.Mutex - rw *rlpxFrameRW +// SetSnappy enables or disables snappy compression of messages. This is usually called +// after the devp2p Hello message exchange when the negotiated version indicates that +// compression is available on both ends of the connection. +func (c *Conn) SetSnappy(snappy bool) { + c.snappy = snappy } -func newRLPX(fd net.Conn) transport { - fd.SetDeadline(time.Now().Add(handshakeTimeout)) - return &rlpx{fd: fd} +// SetReadDeadline sets the deadline for all future read operations. +func (c *Conn) SetReadDeadline(time time.Time) error { + return c.conn.SetReadDeadline(time) } -func (t *rlpx) ReadMsg() (Msg, error) { - t.rmu.Lock() - defer t.rmu.Unlock() - t.fd.SetReadDeadline(time.Now().Add(frameReadTimeout)) - return t.rw.ReadMsg() +// SetWriteDeadline sets the deadline for all future write operations. +func (c *Conn) SetWriteDeadline(time time.Time) error { + return c.conn.SetWriteDeadline(time) } -func (t *rlpx) WriteMsg(msg Msg) error { - t.wmu.Lock() - defer t.wmu.Unlock() - t.fd.SetWriteDeadline(time.Now().Add(frameWriteTimeout)) - return t.rw.WriteMsg(msg) +// SetDeadline sets the deadline for all future read and write operations. +func (c *Conn) SetDeadline(time time.Time) error { + return c.conn.SetDeadline(time) } -func (t *rlpx) close(err error) { - t.wmu.Lock() - defer t.wmu.Unlock() - // Tell the remote end why we're disconnecting if possible. - if t.rw != nil { - if r, ok := err.(DiscReason); ok && r != DiscNetworkError { - // rlpx tries to send DiscReason to disconnected peer - // if the connection is net.Pipe (in-memory simulation) - // it hangs forever, since net.Pipe does not implement - // a write deadline. Because of this only try to send - // the disconnect reason message if there is no error. - if err := t.fd.SetWriteDeadline(time.Now().Add(discWriteTimeout)); err == nil { - SendItems(t.rw, discMsg, r) - } +// Read reads a message from the connection. +func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) { + if c.handshake == nil { + panic("can't ReadMsg before handshake") + } + + frame, err := c.handshake.readFrame(c.conn) + if err != nil { + return 0, nil, 0, err + } + code, data, err = rlp.SplitUint64(frame) + if err != nil { + return 0, nil, 0, fmt.Errorf("invalid message code: %v", err) + } + wireSize = len(data) + + // If snappy is enabled, verify and decompress message. + if c.snappy { + var actualSize int + actualSize, err = snappy.DecodedLen(data) + if err != nil { + return code, nil, 0, err + } + if actualSize > maxUint24 { + return code, nil, 0, errPlainMessageTooLarge } + data, err = snappy.Decode(nil, data) } - t.fd.Close() + return code, data, wireSize, err } -func (t *rlpx) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) { - // Writing our handshake happens concurrently, we prefer - // returning the handshake read error. If the remote side - // disconnects us early with a valid reason, we should return it - // as the error so it can be tracked elsewhere. - werr := make(chan error, 1) - go func() { werr <- Send(t.rw, handshakeMsg, our) }() - if their, err = readProtocolHandshake(t.rw); err != nil { - <-werr // make sure the write terminates too +func (h *handshakeState) readFrame(conn io.Reader) ([]byte, error) { + // read the header + headbuf := make([]byte, 32) + if _, err := io.ReadFull(conn, headbuf); err != nil { return nil, err } - if err := <-werr; err != nil { - return nil, fmt.Errorf("write error: %v", err) + + // verify header mac + shouldMAC := updateMAC(h.ingressMAC, h.macCipher, headbuf[:16]) + if !hmac.Equal(shouldMAC, headbuf[16:]) { + return nil, errors.New("bad header MAC") } - // If the protocol version supports Snappy encoding, upgrade immediately - t.rw.snappy = their.Version >= snappyProtocolVersion + h.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted + fsize := readInt24(headbuf) + // ignore protocol type for now - return their, nil -} + // read the frame content + var rsize = fsize // frame size rounded up to 16 byte boundary + if padding := fsize % 16; padding > 0 { + rsize += 16 - padding + } + framebuf := make([]byte, rsize) + if _, err := io.ReadFull(conn, framebuf); err != nil { + return nil, err + } -func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) { - msg, err := rw.ReadMsg() - if err != nil { + // read and validate frame MAC. we can re-use headbuf for that. + h.ingressMAC.Write(framebuf) + fmacseed := h.ingressMAC.Sum(nil) + if _, err := io.ReadFull(conn, headbuf[:16]); err != nil { return nil, err } - if msg.Size > baseProtocolMaxMsgSize { - return nil, fmt.Errorf("message too big") + shouldMAC = updateMAC(h.ingressMAC, h.macCipher, fmacseed) + if !hmac.Equal(shouldMAC, headbuf[:16]) { + return nil, errors.New("bad frame MAC") + } + + // decrypt frame content + h.dec.XORKeyStream(framebuf, framebuf) + return framebuf[:fsize], nil +} + +// Write writes a message to the connection. +// +// Write returns the written size of the message data. This may be less than or equal to +// len(data) depending on whether snappy compression is enabled. +func (c *Conn) Write(code uint64, data []byte) (uint32, error) { + if c.handshake == nil { + panic("can't WriteMsg before handshake") } - if msg.Code == discMsg { - // Disconnect before protocol handshake is valid according to the - // spec and we send it ourself if the post-handshake checks fail. - // We can't return the reason directly, though, because it is echoed - // back otherwise. Wrap it in a string instead. - var reason [1]DiscReason - rlp.Decode(msg.Payload, &reason) - return nil, reason[0] + if len(data) > maxUint24 { + return 0, errPlainMessageTooLarge } - if msg.Code != handshakeMsg { - return nil, fmt.Errorf("expected handshake, got %x", msg.Code) + if c.snappy { + data = snappy.Encode(nil, data) } - var hs protoHandshake - if err := msg.Decode(&hs); err != nil { - return nil, err + + wireSize := uint32(len(data)) + err := c.handshake.writeFrame(c.conn, code, data) + return wireSize, err +} + +func (h *handshakeState) writeFrame(conn io.Writer, code uint64, data []byte) error { + ptype, _ := rlp.EncodeToBytes(code) + + // write header + headbuf := make([]byte, 32) + fsize := len(ptype) + len(data) + if fsize > maxUint24 { + return errPlainMessageTooLarge + } + putInt24(uint32(fsize), headbuf) + copy(headbuf[3:], zeroHeader) + h.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted + + // write header MAC + copy(headbuf[16:], updateMAC(h.egressMAC, h.macCipher, headbuf[:16])) + if _, err := conn.Write(headbuf); err != nil { + return err + } + + // write encrypted frame, updating the egress MAC hash with + // the data written to conn. + tee := cipher.StreamWriter{S: h.enc, W: io.MultiWriter(conn, h.egressMAC)} + if _, err := tee.Write(ptype); err != nil { + return err + } + if _, err := tee.Write(data); err != nil { + return err } - if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { - return nil, DiscInvalidIdentity + if padding := fsize % 16; padding > 0 { + if _, err := tee.Write(zero16[:16-padding]); err != nil { + return err + } } - return &hs, nil + + // write frame MAC. egress MAC hash is up to date because + // frame content was written to it as well. + fmacseed := h.egressMAC.Sum(nil) + mac := updateMAC(h.egressMAC, h.macCipher, fmacseed) + _, err := conn.Write(mac) + return err +} + +func readInt24(b []byte) uint32 { + return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 } -// doEncHandshake runs the protocol handshake using authenticated -// messages. the protocol handshake is the first authenticated message -// and also verifies whether the encryption handshake 'worked' and the -// remote side actually provided the right public key. -func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { +func putInt24(v uint32, b []byte) { + b[0] = byte(v >> 16) + b[1] = byte(v >> 8) + b[2] = byte(v) +} + +// updateMAC reseeds the given hash with encrypted seed. +// it returns the first 16 bytes of the hash sum after seeding. +func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte { + aesbuf := make([]byte, aes.BlockSize) + block.Encrypt(aesbuf, mac.Sum(nil)) + for i := range aesbuf { + aesbuf[i] ^= seed[i] + } + mac.Write(aesbuf) + return mac.Sum(nil)[:16] +} + +// Handshake performs the handshake. This must be called before any data is written +// or read from the connection. +func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { var ( - sec secrets + sec Secrets err error ) - if dial == nil { - sec, err = receiverEncHandshake(t.fd, prv) + if c.dialDest != nil { + sec, err = initiatorEncHandshake(c.conn, prv, c.dialDest) } else { - sec, err = initiatorEncHandshake(t.fd, prv, dial) + sec, err = receiverEncHandshake(c.conn, prv) } if err != nil { return nil, err } - t.wmu.Lock() - t.rw = newRLPXFrameRW(t.fd, sec) - t.wmu.Unlock() - return sec.Remote.ExportECDSA(), nil + c.InitWithSecrets(sec) + return sec.remote, err +} + +// InitWithSecrets injects connection secrets as if a handshake had +// been performed. This cannot be called after the handshake. +func (c *Conn) InitWithSecrets(sec Secrets) { + if c.handshake != nil { + panic("can't handshake twice") + } + macc, err := aes.NewCipher(sec.MAC) + if err != nil { + panic("invalid MAC secret: " + err.Error()) + } + encc, err := aes.NewCipher(sec.AES) + if err != nil { + panic("invalid AES secret: " + err.Error()) + } + // we use an all-zeroes IV for AES because the key used + // for encryption is ephemeral. + iv := make([]byte, encc.BlockSize()) + c.handshake = &handshakeState{ + enc: cipher.NewCTR(encc, iv), + dec: cipher.NewCTR(encc, iv), + macCipher: macc, + egressMAC: sec.EgressMAC, + ingressMAC: sec.IngressMAC, + } +} + +// Close closes the underlying network connection. +func (c *Conn) Close() error { + return c.conn.Close() +} + +// Constants for the handshake. +const ( + maxUint24 = int(^uint32(0) >> 8) + + sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 + sigLen = crypto.SignatureLength // elliptic S256 + pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte + shaLen = 32 // hash length (for nonce etc) + + authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 + authRespLen = pubLen + shaLen + 1 + + eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */ + + encAuthMsgLen = authMsgLen + eciesOverhead // size of encrypted pre-EIP-8 initiator handshake + encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply +) + +var ( + // this is used in place of actual frame header data. + // TODO: replace this when Msg contains the protocol type code. + zeroHeader = []byte{0xC2, 0x80, 0x80} + // sixteen zero bytes + zero16 = make([]byte, 16) + + // errPlainMessageTooLarge is returned if a decompressed message length exceeds + // the allowed 24 bits (i.e. length >= 16MB). + errPlainMessageTooLarge = errors.New("message length >= 16MB") +) + +// Secrets represents the connection secrets which are negotiated during the handshake. +type Secrets struct { + AES, MAC []byte + EgressMAC, IngressMAC hash.Hash + remote *ecdsa.PublicKey } // encHandshake contains the state of the encryption handshake. @@ -203,15 +347,6 @@ type encHandshake struct { remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk } -// secrets represents the connection secrets -// which are negotiated during the encryption handshake. -type secrets struct { - Remote *ecies.PublicKey - AES, MAC []byte - EgressMAC, IngressMAC hash.Hash - Token []byte -} - // RLPx v4 handshake auth (defined in EIP-8). type authMsgV4 struct { gotPlain bool // whether read packet had plain format. @@ -235,19 +370,85 @@ type authRespV4 struct { Rest []rlp.RawValue `rlp:"tail"` } +// receiverEncHandshake negotiates a session token on conn. +// it should be called on the listening side of the connection. +// +// prv is the local client's private key. +func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) { + authMsg := new(authMsgV4) + authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn) + if err != nil { + return s, err + } + h := new(encHandshake) + if err := h.handleAuthMsg(authMsg, prv); err != nil { + return s, err + } + + authRespMsg, err := h.makeAuthResp() + if err != nil { + return s, err + } + var authRespPacket []byte + if authMsg.gotPlain { + authRespPacket, err = authRespMsg.sealPlain(h) + } else { + authRespPacket, err = sealEIP8(authRespMsg, h) + } + if err != nil { + return s, err + } + if _, err = conn.Write(authRespPacket); err != nil { + return s, err + } + return h.secrets(authPacket, authRespPacket) +} + +func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { + // Import the remote identity. + rpub, err := importPublicKey(msg.InitiatorPubkey[:]) + if err != nil { + return err + } + h.initNonce = msg.Nonce[:] + h.remote = rpub + + // Generate random keypair for ECDH. + // If a private key is already set, use it instead of generating one (for testing). + if h.randomPrivKey == nil { + h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) + if err != nil { + return err + } + } + + // Check the signature. + token, err := h.staticSharedSecret(prv) + if err != nil { + return err + } + signedMsg := xor(token, h.initNonce) + remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:]) + if err != nil { + return err + } + h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) + return nil +} + // secrets is called after the handshake is completed. // It extracts the connection secrets from the handshake values. -func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { +func (h *encHandshake) secrets(auth, authResp []byte) (Secrets, error) { ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen) if err != nil { - return secrets{}, err + return Secrets{}, err } // derive base secrets from ephemeral key agreement sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce)) aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret) - s := secrets{ - Remote: h.remote, + s := Secrets{ + remote: h.remote.ExportECDSA(), AES: aesSecret, MAC: crypto.Keccak256(ecdheSecret, aesSecret), } @@ -278,7 +479,7 @@ func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) // it should be called on the dialing side of the connection. // // prv is the local client's private key. -func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) { +func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) { h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)} authMsg, err := h.makeAuthMsg(prv) if err != nil { @@ -288,6 +489,7 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ec if err != nil { return s, err } + if _, err = conn.Write(authPacket); err != nil { return s, err } @@ -342,72 +544,6 @@ func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) { return err } -// receiverEncHandshake negotiates a session token on conn. -// it should be called on the listening side of the connection. -// -// prv is the local client's private key. -func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets, err error) { - authMsg := new(authMsgV4) - authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn) - if err != nil { - return s, err - } - h := new(encHandshake) - if err := h.handleAuthMsg(authMsg, prv); err != nil { - return s, err - } - - authRespMsg, err := h.makeAuthResp() - if err != nil { - return s, err - } - var authRespPacket []byte - if authMsg.gotPlain { - authRespPacket, err = authRespMsg.sealPlain(h) - } else { - authRespPacket, err = sealEIP8(authRespMsg, h) - } - if err != nil { - return s, err - } - if _, err = conn.Write(authRespPacket); err != nil { - return s, err - } - return h.secrets(authPacket, authRespPacket) -} - -func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { - // Import the remote identity. - rpub, err := importPublicKey(msg.InitiatorPubkey[:]) - if err != nil { - return err - } - h.initNonce = msg.Nonce[:] - h.remote = rpub - - // Generate random keypair for ECDH. - // If a private key is already set, use it instead of generating one (for testing). - if h.randomPrivKey == nil { - h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) - if err != nil { - return err - } - } - - // Check the signature. - token, err := h.staticSharedSecret(prv) - if err != nil { - return err - } - signedMsg := xor(token, h.initNonce) - remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:]) - if err != nil { - return err - } - h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) - return nil -} - func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) { // Generate random nonce. h.respNonce = make([]byte, shaLen) @@ -531,201 +667,3 @@ func xor(one, other []byte) (xor []byte) { } return xor } - -var ( - // this is used in place of actual frame header data. - // TODO: replace this when Msg contains the protocol type code. - zeroHeader = []byte{0xC2, 0x80, 0x80} - // sixteen zero bytes - zero16 = make([]byte, 16) -) - -// rlpxFrameRW implements a simplified version of RLPx framing. -// chunked messages are not supported and all headers are equal to -// zeroHeader. -// -// rlpxFrameRW is not safe for concurrent use from multiple goroutines. -type rlpxFrameRW struct { - conn io.ReadWriter - enc cipher.Stream - dec cipher.Stream - - macCipher cipher.Block - egressMAC hash.Hash - ingressMAC hash.Hash - - snappy bool -} - -func newRLPXFrameRW(conn io.ReadWriter, s secrets) *rlpxFrameRW { - macc, err := aes.NewCipher(s.MAC) - if err != nil { - panic("invalid MAC secret: " + err.Error()) - } - encc, err := aes.NewCipher(s.AES) - if err != nil { - panic("invalid AES secret: " + err.Error()) - } - // we use an all-zeroes IV for AES because the key used - // for encryption is ephemeral. - iv := make([]byte, encc.BlockSize()) - return &rlpxFrameRW{ - conn: conn, - enc: cipher.NewCTR(encc, iv), - dec: cipher.NewCTR(encc, iv), - macCipher: macc, - egressMAC: s.EgressMAC, - ingressMAC: s.IngressMAC, - } -} - -func (rw *rlpxFrameRW) WriteMsg(msg Msg) error { - ptype, _ := rlp.EncodeToBytes(msg.Code) - - // if snappy is enabled, compress message now - if rw.snappy { - if msg.Size > maxUint24 { - return errPlainMessageTooLarge - } - payload, _ := ioutil.ReadAll(msg.Payload) - payload = snappy.Encode(nil, payload) - - msg.Payload = bytes.NewReader(payload) - msg.Size = uint32(len(payload)) - } - msg.meterSize = msg.Size - if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages - m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode) - metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) - metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) - } - // write header - headbuf := make([]byte, 32) - fsize := uint32(len(ptype)) + msg.Size - if fsize > maxUint24 { - return errors.New("message size overflows uint24") - } - putInt24(fsize, headbuf) // TODO: check overflow - copy(headbuf[3:], zeroHeader) - rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted - - // write header MAC - copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16])) - if _, err := rw.conn.Write(headbuf); err != nil { - return err - } - - // write encrypted frame, updating the egress MAC hash with - // the data written to conn. - tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)} - if _, err := tee.Write(ptype); err != nil { - return err - } - if _, err := io.Copy(tee, msg.Payload); err != nil { - return err - } - if padding := fsize % 16; padding > 0 { - if _, err := tee.Write(zero16[:16-padding]); err != nil { - return err - } - } - - // write frame MAC. egress MAC hash is up to date because - // frame content was written to it as well. - fmacseed := rw.egressMAC.Sum(nil) - mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed) - _, err := rw.conn.Write(mac) - return err -} - -func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) { - // read the header - headbuf := make([]byte, 32) - if _, err := io.ReadFull(rw.conn, headbuf); err != nil { - return msg, err - } - // verify header mac - shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16]) - if !hmac.Equal(shouldMAC, headbuf[16:]) { - return msg, errors.New("bad header MAC") - } - rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted - fsize := readInt24(headbuf) - // ignore protocol type for now - - // read the frame content - var rsize = fsize // frame size rounded up to 16 byte boundary - if padding := fsize % 16; padding > 0 { - rsize += 16 - padding - } - framebuf := make([]byte, rsize) - if _, err := io.ReadFull(rw.conn, framebuf); err != nil { - return msg, err - } - - // read and validate frame MAC. we can re-use headbuf for that. - rw.ingressMAC.Write(framebuf) - fmacseed := rw.ingressMAC.Sum(nil) - if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil { - return msg, err - } - shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed) - if !hmac.Equal(shouldMAC, headbuf[:16]) { - return msg, errors.New("bad frame MAC") - } - - // decrypt frame content - rw.dec.XORKeyStream(framebuf, framebuf) - - // decode message code - content := bytes.NewReader(framebuf[:fsize]) - if err := rlp.Decode(content, &msg.Code); err != nil { - return msg, err - } - msg.Size = uint32(content.Len()) - msg.meterSize = msg.Size - msg.Payload = content - - // if snappy is enabled, verify and decompress message - if rw.snappy { - payload, err := ioutil.ReadAll(msg.Payload) - if err != nil { - return msg, err - } - size, err := snappy.DecodedLen(payload) - if err != nil { - return msg, err - } - if size > int(maxUint24) { - return msg, errPlainMessageTooLarge - } - payload, err = snappy.Decode(nil, payload) - if err != nil { - return msg, err - } - msg.Size, msg.Payload = uint32(size), bytes.NewReader(payload) - } - return msg, nil -} - -// updateMAC reseeds the given hash with encrypted seed. -// it returns the first 16 bytes of the hash sum after seeding. -func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte { - aesbuf := make([]byte, aes.BlockSize) - block.Encrypt(aesbuf, mac.Sum(nil)) - for i := range aesbuf { - aesbuf[i] ^= seed[i] - } - mac.Write(aesbuf) - return mac.Sum(nil)[:16] -} - -func readInt24(b []byte) uint32 { - return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 -} - -func putInt24(v uint32, b []byte) { - b[0] = byte(v >> 16) - b[1] = byte(v >> 8) - b[2] = byte(v) -} diff --git a/p2p/rlpx_test.go b/p2p/rlpx/rlpx_test.go similarity index 58% rename from p2p/rlpx_test.go rename to p2p/rlpx/rlpx_test.go index 3f686fe09f..127a018164 100644 --- a/p2p/rlpx_test.go +++ b/p2p/rlpx/rlpx_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -14,298 +14,145 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package p2p +package rlpx import ( "bytes" "crypto/ecdsa" - "crypto/rand" - "errors" + "encoding/hex" "fmt" "io" - "io/ioutil" "net" "reflect" "strings" - "sync" "testing" - "time" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" - "github.com/ethereum/go-ethereum/p2p/simulations/pipes" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" + "github.com/stretchr/testify/assert" ) -func TestSharedSecret(t *testing.T) { - prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) - pub0 := &prv0.PublicKey - prv1, _ := crypto.GenerateKey() - pub1 := &prv1.PublicKey - - ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen) - if err != nil { - return - } - ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen) - if err != nil { - return - } - t.Logf("Secret:\n%v %x\n%v %x", len(ss0), ss0, len(ss0), ss1) - if !bytes.Equal(ss0, ss1) { - t.Errorf("don't match :(") - } +type message struct { + code uint64 + data []byte + err error } -func TestEncHandshake(t *testing.T) { - for i := 0; i < 10; i++ { - start := time.Now() - if err := testEncHandshake(nil); err != nil { - t.Fatalf("i=%d %v", i, err) - } - t.Logf("(without token) %d %v\n", i+1, time.Since(start)) - } - for i := 0; i < 10; i++ { - tok := make([]byte, shaLen) - rand.Reader.Read(tok) - start := time.Now() - if err := testEncHandshake(tok); err != nil { - t.Fatalf("i=%d %v", i, err) - } - t.Logf("(with token) %d %v\n", i+1, time.Since(start)) - } +func TestHandshake(t *testing.T) { + p1, p2 := createPeers(t) + p1.Close() + p2.Close() } -func testEncHandshake(token []byte) error { - type result struct { - side string - pubkey *ecdsa.PublicKey - err error - } - var ( - prv0, _ = crypto.GenerateKey() - prv1, _ = crypto.GenerateKey() - fd0, fd1 = net.Pipe() - c0, c1 = newRLPX(fd0).(*rlpx), newRLPX(fd1).(*rlpx) - output = make(chan result) - ) - - go func() { - r := result{side: "initiator"} - defer func() { output <- r }() - defer fd0.Close() +// This test checks that messages can be sent and received through WriteMsg/ReadMsg. +func TestReadWriteMsg(t *testing.T) { + peer1, peer2 := createPeers(t) + defer peer1.Close() + defer peer2.Close() - r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey) - if r.err != nil { - return - } - if !reflect.DeepEqual(r.pubkey, &prv1.PublicKey) { - r.err = fmt.Errorf("remote pubkey mismatch: got %v, want: %v", r.pubkey, &prv1.PublicKey) - } - }() - go func() { - r := result{side: "receiver"} - defer func() { output <- r }() - defer fd1.Close() - - r.pubkey, r.err = c1.doEncHandshake(prv1, nil) - if r.err != nil { - return - } - if !reflect.DeepEqual(r.pubkey, &prv0.PublicKey) { - r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.pubkey, &prv0.PublicKey) - } - }() + testCode := uint64(23) + testData := []byte("test") + checkMsgReadWrite(t, peer1, peer2, testCode, testData) - // wait for results from both sides - r1, r2 := <-output, <-output - if r1.err != nil { - return fmt.Errorf("%s side error: %v", r1.side, r1.err) - } - if r2.err != nil { - return fmt.Errorf("%s side error: %v", r2.side, r2.err) - } - - // compare derived secrets - if !reflect.DeepEqual(c0.rw.egressMAC, c1.rw.ingressMAC) { - return fmt.Errorf("egress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.egressMAC, c1.rw.ingressMAC) - } - if !reflect.DeepEqual(c0.rw.ingressMAC, c1.rw.egressMAC) { - return fmt.Errorf("ingress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.ingressMAC, c1.rw.egressMAC) - } - if !reflect.DeepEqual(c0.rw.enc, c1.rw.enc) { - return fmt.Errorf("enc cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.enc, c1.rw.enc) - } - if !reflect.DeepEqual(c0.rw.dec, c1.rw.dec) { - return fmt.Errorf("dec cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.dec, c1.rw.dec) - } - return nil + t.Log("enabling snappy") + peer1.SetSnappy(true) + peer2.SetSnappy(true) + checkMsgReadWrite(t, peer1, peer2, testCode, testData) } -func TestProtocolHandshake(t *testing.T) { - var ( - prv0, _ = crypto.GenerateKey() - pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:] - hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}} - - prv1, _ = crypto.GenerateKey() - pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:] - hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}} - - wg sync.WaitGroup - ) +func checkMsgReadWrite(t *testing.T, p1, p2 *Conn, msgCode uint64, msgData []byte) { + // Set up the reader. + ch := make(chan message, 1) + go func() { + var msg message + msg.code, msg.data, _, msg.err = p1.Read() + ch <- msg + }() - fd0, fd1, err := pipes.TCPPipe() + // Write the message. + _, err := p2.Write(msgCode, msgData) if err != nil { t.Fatal(err) } - wg.Add(2) - go func() { - defer wg.Done() - defer fd0.Close() - rlpx := newRLPX(fd0) - rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey) - if err != nil { - t.Errorf("dial side enc handshake failed: %v", err) - return - } - if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { - t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) - return - } + // Check it was received correctly. + msg := <-ch + assert.Equal(t, msgCode, msg.code, "wrong message code returned from ReadMsg") + assert.Equal(t, msgData, msg.data, "wrong message data returned from ReadMsg") +} - phs, err := rlpx.doProtoHandshake(hs0) - if err != nil { - t.Errorf("dial side proto handshake error: %v", err) - return - } - phs.Rest = nil - if !reflect.DeepEqual(phs, hs1) { - t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1)) - return - } - rlpx.close(DiscQuitting) - }() - go func() { - defer wg.Done() - defer fd1.Close() - rlpx := newRLPX(fd1) - rpubkey, err := rlpx.doEncHandshake(prv1, nil) - if err != nil { - t.Errorf("listen side enc handshake failed: %v", err) - return - } - if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { - t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) - return - } +func createPeers(t *testing.T) (peer1, peer2 *Conn) { + conn1, conn2 := net.Pipe() + key1, key2 := newkey(), newkey() + peer1 = NewConn(conn1, &key2.PublicKey) // dialer + peer2 = NewConn(conn2, nil) // listener + doHandshake(t, peer1, peer2, key1, key2) + return peer1, peer2 +} - phs, err := rlpx.doProtoHandshake(hs1) +func doHandshake(t *testing.T, peer1, peer2 *Conn, key1, key2 *ecdsa.PrivateKey) { + keyChan := make(chan *ecdsa.PublicKey, 1) + go func() { + pubKey, err := peer2.Handshake(key2) if err != nil { - t.Errorf("listen side proto handshake error: %v", err) - return - } - phs.Rest = nil - if !reflect.DeepEqual(phs, hs0) { - t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0)) - return - } - - if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil { - t.Errorf("error receiving disconnect: %v", err) + t.Errorf("peer2 could not do handshake: %v", err) } + keyChan <- pubKey }() - wg.Wait() -} -func TestProtocolHandshakeErrors(t *testing.T) { - tests := []struct { - code uint64 - msg interface{} - err error - }{ - { - code: discMsg, - msg: []DiscReason{DiscQuitting}, - err: DiscQuitting, - }, - { - code: 0x989898, - msg: []byte{1}, - err: errors.New("expected handshake, got 989898"), - }, - { - code: handshakeMsg, - msg: make([]byte, baseProtocolMaxMsgSize+2), - err: errors.New("message too big"), - }, - { - code: handshakeMsg, - msg: []byte{1, 2, 3}, - err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"), - }, - { - code: handshakeMsg, - msg: &protoHandshake{Version: 3}, - err: DiscInvalidIdentity, - }, + pubKey2, err := peer1.Handshake(key1) + if err != nil { + t.Errorf("peer1 could not do handshake: %v", err) } + pubKey1 := <-keyChan - for i, test := range tests { - p1, p2 := MsgPipe() - go Send(p1, test.code, test.msg) - _, err := readProtocolHandshake(p2) - if !reflect.DeepEqual(err, test.err) { - t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) - } + // Confirm the handshake was successful. + if !reflect.DeepEqual(pubKey1, &key1.PublicKey) || !reflect.DeepEqual(pubKey2, &key2.PublicKey) { + t.Fatal("unsuccessful handshake") } } -func TestRLPXFrameFake(t *testing.T) { - buf := new(bytes.Buffer) +// This test checks the frame data of written messages. +func TestFrameReadWrite(t *testing.T) { + conn := NewConn(nil, nil) hash := fakeHash([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) - rw := newRLPXFrameRW(buf, secrets{ + conn.InitWithSecrets(Secrets{ AES: crypto.Keccak256(), MAC: crypto.Keccak256(), IngressMAC: hash, EgressMAC: hash, }) + h := conn.handshake golden := unhex(` -00828ddae471818bb0bfa6b551d1cb42 -01010101010101010101010101010101 -ba628a4ba590cb43f7848f41c4382885 -01010101010101010101010101010101 -`) - - // Check WriteMsg. This puts a message into the buffer. - if err := Send(rw, 8, []uint{1, 2, 3, 4}); err != nil { + 00828ddae471818bb0bfa6b551d1cb42 + 01010101010101010101010101010101 + ba628a4ba590cb43f7848f41c4382885 + 01010101010101010101010101010101 + `) + msgCode := uint64(8) + msg := []uint{1, 2, 3, 4} + msgEnc, _ := rlp.EncodeToBytes(msg) + + // Check writeFrame. The frame that's written should be equal to the test vector. + buf := new(bytes.Buffer) + if err := h.writeFrame(buf, msgCode, msgEnc); err != nil { t.Fatalf("WriteMsg error: %v", err) } - written := buf.Bytes() - if !bytes.Equal(written, golden) { - t.Fatalf("output mismatch:\n got: %x\n want: %x", written, golden) + if !bytes.Equal(buf.Bytes(), golden) { + t.Fatalf("output mismatch:\n got: %x\n want: %x", buf.Bytes(), golden) } - // Check ReadMsg. It reads the message encoded by WriteMsg, which - // is equivalent to the golden message above. - msg, err := rw.ReadMsg() + // Check readFrame on the test vector. + content, err := h.readFrame(bytes.NewReader(golden)) if err != nil { t.Fatalf("ReadMsg error: %v", err) } - if msg.Size != 5 { - t.Errorf("msg size mismatch: got %d, want %d", msg.Size, 5) - } - if msg.Code != 8 { - t.Errorf("msg code mismatch: got %d, want %d", msg.Code, 8) - } - payload, _ := ioutil.ReadAll(msg.Payload) - wantPayload := unhex("C401020304") - if !bytes.Equal(payload, wantPayload) { - t.Errorf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload) + wantContent := unhex("08C401020304") + if !bytes.Equal(content, wantContent) { + t.Errorf("frame content mismatch:\ngot %x\nwant %x", content, wantContent) } } @@ -314,66 +161,8 @@ type fakeHash []byte func (fakeHash) Write(p []byte) (int, error) { return len(p), nil } func (fakeHash) Reset() {} func (fakeHash) BlockSize() int { return 0 } - -func (h fakeHash) Size() int { return len(h) } -func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) } - -func TestRLPXFrameRW(t *testing.T) { - var ( - aesSecret = make([]byte, 16) - macSecret = make([]byte, 16) - egressMACinit = make([]byte, 32) - ingressMACinit = make([]byte, 32) - ) - for _, s := range [][]byte{aesSecret, macSecret, egressMACinit, ingressMACinit} { - rand.Read(s) - } - conn := new(bytes.Buffer) - - s1 := secrets{ - AES: aesSecret, - MAC: macSecret, - EgressMAC: sha3.NewLegacyKeccak256(), - IngressMAC: sha3.NewLegacyKeccak256(), - } - s1.EgressMAC.Write(egressMACinit) - s1.IngressMAC.Write(ingressMACinit) - rw1 := newRLPXFrameRW(conn, s1) - - s2 := secrets{ - AES: aesSecret, - MAC: macSecret, - EgressMAC: sha3.NewLegacyKeccak256(), - IngressMAC: sha3.NewLegacyKeccak256(), - } - s2.EgressMAC.Write(ingressMACinit) - s2.IngressMAC.Write(egressMACinit) - rw2 := newRLPXFrameRW(conn, s2) - - // send some messages - for i := 0; i < 10; i++ { - // write message into conn buffer - wmsg := []interface{}{"foo", "bar", strings.Repeat("test", i)} - err := Send(rw1, uint64(i), wmsg) - if err != nil { - t.Fatalf("WriteMsg error (i=%d): %v", i, err) - } - - // read message that rw1 just wrote - msg, err := rw2.ReadMsg() - if err != nil { - t.Fatalf("ReadMsg error (i=%d): %v", i, err) - } - if msg.Code != uint64(i) { - t.Fatalf("msg code mismatch: got %d, want %d", msg.Code, i) - } - payload, _ := ioutil.ReadAll(msg.Payload) - wantPayload, _ := rlp.EncodeToBytes(wmsg) - if !bytes.Equal(payload, wantPayload) { - t.Fatalf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload) - } - } -} +func (h fakeHash) Size() int { return len(h) } +func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) } type handshakeAuthTest struct { input string @@ -598,3 +387,20 @@ func TestHandshakeForwardCompatibility(t *testing.T) { t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash) } } + +func unhex(str string) []byte { + r := strings.NewReplacer("\t", "", " ", "", "\n", "") + b, err := hex.DecodeString(r.Replace(str)) + if err != nil { + panic(fmt.Sprintf("invalid hex string: %q", str)) + } + return b +} + +func newkey() *ecdsa.PrivateKey { + key, err := crypto.GenerateKey() + if err != nil { + panic("couldn't generate key: " + err.Error()) + } + return key +} diff --git a/p2p/server.go b/p2p/server.go index 1fe5f39789..a343f4320a 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -166,7 +166,7 @@ type Server struct { // Hooks for testing. These are useful because we can inhibit // the whole protocol stack. - newTransport func(net.Conn) transport + newTransport func(net.Conn, *ecdsa.PublicKey) transport newPeerHook func(*Peer) listenFunc func(network, addr string) (net.Listener, error) @@ -231,7 +231,7 @@ type conn struct { type transport interface { // The two handshakes. - doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) + doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) // The MsgReadWriter can only be used after the encryption // handshake has completed. The code uses conn.id to track this @@ -914,7 +914,13 @@ func (srv *Server) checkInboundConn(fd net.Conn, remoteIP net.IP) error { // as a peer. It returns when the connection has been added as a peer // or the handshakes have failed. func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error { - c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)} + c := &conn{fd: fd, flags: flags, cont: make(chan error)} + if dialDest == nil { + c.transport = srv.newTransport(fd, nil) + } else { + c.transport = srv.newTransport(fd, dialDest.Pubkey()) + } + err := srv.setupConn(c, flags, dialDest) if err != nil { c.close(err) @@ -943,16 +949,12 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro } // Run the RLPx handshake. - remotePubkey, err := c.doEncHandshake(srv.PrivateKey, dialPubkey) + remotePubkey, err := c.doEncHandshake(srv.PrivateKey) if err != nil { srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } if dialDest != nil { - // For dialed connections, check that the remote public key matches. - if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 { - return DiscUnexpectedIdentity - } c.node = dialDest } else { c.node = nodeFromConn(remotePubkey, c.fd) diff --git a/p2p/server_test.go b/p2p/server_test.go index 7dc344a67d..a5b3190aed 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -18,6 +18,7 @@ package p2p import ( "crypto/ecdsa" + "crypto/sha256" "errors" "io" "math/rand" @@ -31,28 +32,27 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" - "golang.org/x/crypto/sha3" + "github.com/ethereum/go-ethereum/p2p/rlpx" ) type testTransport struct { - rpub *ecdsa.PublicKey - *rlpx - + *rlpxTransport + rpub *ecdsa.PublicKey closeErr error } -func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport { - wrapped := newRLPX(fd).(*rlpx) - wrapped.rw = newRLPXFrameRW(fd, secrets{ - MAC: zero16, - AES: zero16, - IngressMAC: sha3.NewLegacyKeccak256(), - EgressMAC: sha3.NewLegacyKeccak256(), +func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn, dialDest *ecdsa.PublicKey) transport { + wrapped := newRLPX(fd, dialDest).(*rlpxTransport) + wrapped.conn.InitWithSecrets(rlpx.Secrets{ + AES: make([]byte, 16), + MAC: make([]byte, 16), + EgressMAC: sha256.New(), + IngressMAC: sha256.New(), }) - return &testTransport{rpub: rpub, rlpx: wrapped} + return &testTransport{rpub: rpub, rlpxTransport: wrapped} } -func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { +func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { return c.rpub, nil } @@ -62,7 +62,7 @@ func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake, } func (c *testTransport) close(err error) { - c.rlpx.fd.Close() + c.conn.Close() c.closeErr = err } @@ -76,9 +76,11 @@ func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) * Logger: testlog.Logger(t, log.LvlTrace), } server := &Server{ - Config: config, - newPeerHook: pf, - newTransport: func(fd net.Conn) transport { return newTestTransport(remoteKey, fd) }, + Config: config, + newPeerHook: pf, + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { + return newTestTransport(remoteKey, fd, dialDest) + }, } if err := server.Start(); err != nil { t.Fatalf("Could not start server: %v", err) @@ -253,7 +255,7 @@ func TestServerAtCap(t *testing.T) { newconn := func(id enode.ID) *conn { fd, _ := net.Pipe() - tx := newTestTransport(&trustedNode.PublicKey, fd) + tx := newTestTransport(&trustedNode.PublicKey, fd, nil) node := enode.SignNull(new(enr.Record), id) return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)} } @@ -321,7 +323,7 @@ func TestServerPeerLimits(t *testing.T) { Protocols: []Protocol{discard}, Logger: testlog.Logger(t, log.LvlTrace), }, - newTransport: func(fd net.Conn) transport { return tp }, + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return tp }, } if err := srv.Start(); err != nil { t.Fatalf("couldn't start server: %v", err) @@ -390,13 +392,6 @@ func TestServerSetupConn(t *testing.T) { wantCalls: "doEncHandshake,close,", wantCloseErr: errors.New("read error"), }, - { - tt: &setupTransport{pubkey: clientpub}, - dialDest: enode.NewV4(&newkey().PublicKey, nil, 0, 0), - flags: dynDialedConn, - wantCalls: "doEncHandshake,close,", - wantCloseErr: DiscUnexpectedIdentity, - }, { tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}}, dialDest: enode.NewV4(clientpub, nil, 0, 0), @@ -437,7 +432,7 @@ func TestServerSetupConn(t *testing.T) { } srv := &Server{ Config: cfg, - newTransport: func(fd net.Conn) transport { return test.tt }, + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return test.tt }, log: cfg.Logger, } if !test.dontstart { @@ -468,7 +463,7 @@ type setupTransport struct { closeErr error } -func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) { +func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { c.calls += "doEncHandshake," return c.pubkey, c.encHandshakeErr } @@ -522,9 +517,9 @@ func TestServerInboundThrottle(t *testing.T) { Protocols: []Protocol{discard}, Logger: testlog.Logger(t, log.LvlTrace), }, - newTransport: func(fd net.Conn) transport { + newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { newTransportCalled <- struct{}{} - return newRLPX(fd) + return newRLPX(fd, dialDest) }, listenFunc: func(network, laddr string) (net.Listener, error) { fakeAddr := &net.TCPAddr{IP: net.IP{95, 33, 21, 2}, Port: 4444} diff --git a/p2p/transport.go b/p2p/transport.go new file mode 100644 index 0000000000..3f1cd7d64f --- /dev/null +++ b/p2p/transport.go @@ -0,0 +1,177 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package p2p + +import ( + "bytes" + "crypto/ecdsa" + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/bitutil" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/p2p/rlpx" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // total timeout for encryption handshake and protocol + // handshake in both directions. + handshakeTimeout = 5 * time.Second + + // This is the timeout for sending the disconnect reason. + // This is shorter than the usual timeout because we don't want + // to wait if the connection is known to be bad anyway. + discWriteTimeout = 1 * time.Second +) + +// rlpxTransport is the transport used by actual (non-test) connections. +// It wraps an RLPx connection with locks and read/write deadlines. +type rlpxTransport struct { + rmu, wmu sync.Mutex + wbuf bytes.Buffer + conn *rlpx.Conn +} + +func newRLPX(conn net.Conn, dialDest *ecdsa.PublicKey) transport { + return &rlpxTransport{conn: rlpx.NewConn(conn, dialDest)} +} + +func (t *rlpxTransport) ReadMsg() (Msg, error) { + t.rmu.Lock() + defer t.rmu.Unlock() + + var msg Msg + t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout)) + code, data, wireSize, err := t.conn.Read() + if err == nil { + msg = Msg{ + ReceivedAt: time.Now(), + Code: code, + Size: uint32(len(data)), + meterSize: uint32(wireSize), + Payload: bytes.NewReader(data), + } + } + return msg, err +} + +func (t *rlpxTransport) WriteMsg(msg Msg) error { + t.wmu.Lock() + defer t.wmu.Unlock() + + // Copy message data to write buffer. + t.wbuf.Reset() + if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil { + return err + } + + // Write the message. + t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout)) + size, err := t.conn.Write(msg.Code, t.wbuf.Bytes()) + if err != nil { + return err + } + + // Set metrics. + msg.meterSize = size + if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages + m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode) + metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize)) + metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1) + } + return nil +} + +func (t *rlpxTransport) close(err error) { + t.wmu.Lock() + defer t.wmu.Unlock() + + // Tell the remote end why we're disconnecting if possible. + // We only bother doing this if the underlying connection supports + // setting a timeout tough. + if t.conn != nil { + if r, ok := err.(DiscReason); ok && r != DiscNetworkError { + deadline := time.Now().Add(discWriteTimeout) + if err := t.conn.SetWriteDeadline(deadline); err == nil { + // Connection supports write deadline. + t.wbuf.Reset() + rlp.Encode(&t.wbuf, []DiscReason{r}) + t.conn.Write(discMsg, t.wbuf.Bytes()) + } + } + } + t.conn.Close() +} + +func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { + t.conn.SetDeadline(time.Now().Add(handshakeTimeout)) + return t.conn.Handshake(prv) +} + +func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) { + // Writing our handshake happens concurrently, we prefer + // returning the handshake read error. If the remote side + // disconnects us early with a valid reason, we should return it + // as the error so it can be tracked elsewhere. + werr := make(chan error, 1) + go func() { werr <- Send(t, handshakeMsg, our) }() + if their, err = readProtocolHandshake(t); err != nil { + <-werr // make sure the write terminates too + return nil, err + } + if err := <-werr; err != nil { + return nil, fmt.Errorf("write error: %v", err) + } + // If the protocol version supports Snappy encoding, upgrade immediately + t.conn.SetSnappy(their.Version >= snappyProtocolVersion) + + return their, nil +} + +func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) { + msg, err := rw.ReadMsg() + if err != nil { + return nil, err + } + if msg.Size > baseProtocolMaxMsgSize { + return nil, fmt.Errorf("message too big") + } + if msg.Code == discMsg { + // Disconnect before protocol handshake is valid according to the + // spec and we send it ourself if the post-handshake checks fail. + // We can't return the reason directly, though, because it is echoed + // back otherwise. Wrap it in a string instead. + var reason [1]DiscReason + rlp.Decode(msg.Payload, &reason) + return nil, reason[0] + } + if msg.Code != handshakeMsg { + return nil, fmt.Errorf("expected handshake, got %x", msg.Code) + } + var hs protoHandshake + if err := msg.Decode(&hs); err != nil { + return nil, err + } + if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) { + return nil, DiscInvalidIdentity + } + return &hs, nil +} diff --git a/p2p/transport_test.go b/p2p/transport_test.go new file mode 100644 index 0000000000..753ea30bf1 --- /dev/null +++ b/p2p/transport_test.go @@ -0,0 +1,148 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package p2p + +import ( + "errors" + "reflect" + "sync" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/simulations/pipes" +) + +func TestProtocolHandshake(t *testing.T) { + var ( + prv0, _ = crypto.GenerateKey() + pub0 = crypto.FromECDSAPub(&prv0.PublicKey)[1:] + hs0 = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}} + + prv1, _ = crypto.GenerateKey() + pub1 = crypto.FromECDSAPub(&prv1.PublicKey)[1:] + hs1 = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}} + + wg sync.WaitGroup + ) + + fd0, fd1, err := pipes.TCPPipe() + if err != nil { + t.Fatal(err) + } + + wg.Add(2) + go func() { + defer wg.Done() + defer fd0.Close() + frame := newRLPX(fd0, &prv1.PublicKey) + rpubkey, err := frame.doEncHandshake(prv0) + if err != nil { + t.Errorf("dial side enc handshake failed: %v", err) + return + } + if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) { + t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey) + return + } + + phs, err := frame.doProtoHandshake(hs0) + if err != nil { + t.Errorf("dial side proto handshake error: %v", err) + return + } + phs.Rest = nil + if !reflect.DeepEqual(phs, hs1) { + t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1)) + return + } + frame.close(DiscQuitting) + }() + go func() { + defer wg.Done() + defer fd1.Close() + rlpx := newRLPX(fd1, nil) + rpubkey, err := rlpx.doEncHandshake(prv1) + if err != nil { + t.Errorf("listen side enc handshake failed: %v", err) + return + } + if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) { + t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey) + return + } + + phs, err := rlpx.doProtoHandshake(hs1) + if err != nil { + t.Errorf("listen side proto handshake error: %v", err) + return + } + phs.Rest = nil + if !reflect.DeepEqual(phs, hs0) { + t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0)) + return + } + + if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil { + t.Errorf("error receiving disconnect: %v", err) + } + }() + wg.Wait() +} + +func TestProtocolHandshakeErrors(t *testing.T) { + tests := []struct { + code uint64 + msg interface{} + err error + }{ + { + code: discMsg, + msg: []DiscReason{DiscQuitting}, + err: DiscQuitting, + }, + { + code: 0x989898, + msg: []byte{1}, + err: errors.New("expected handshake, got 989898"), + }, + { + code: handshakeMsg, + msg: make([]byte, baseProtocolMaxMsgSize+2), + err: errors.New("message too big"), + }, + { + code: handshakeMsg, + msg: []byte{1, 2, 3}, + err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"), + }, + { + code: handshakeMsg, + msg: &protoHandshake{Version: 3}, + err: DiscInvalidIdentity, + }, + } + + for i, test := range tests { + p1, p2 := MsgPipe() + go Send(p1, test.code, test.msg) + _, err := readProtocolHandshake(p2) + if !reflect.DeepEqual(err, test.err) { + t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err) + } + } +} From fdb742419e6ff8373e57bdf99836c22f689b0e55 Mon Sep 17 00:00:00 2001 From: Binacs Date: Wed, 23 Sep 2020 05:22:54 +0800 Subject: [PATCH 040/948] cmd/clef, cmd/geth: use SplitAndTrim from cmd/utils (#21579) --- cmd/clef/main.go | 26 +++--------------------- cmd/geth/retesteth.go | 15 ++------------ cmd/utils/customflags.go | 4 ++-- cmd/utils/flags.go | 43 ++++++++++++++++++++-------------------- 4 files changed, 28 insertions(+), 60 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 8c8778c249..aef3cfba4f 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -29,7 +29,6 @@ import ( "math/big" "os" "os/signal" - "os/user" "path/filepath" "runtime" "sort" @@ -666,8 +665,8 @@ func signer(c *cli.Context) error { Version: "1.0"}, } if c.GlobalBool(utils.HTTPEnabledFlag.Name) { - vhosts := splitAndTrim(c.GlobalString(utils.HTTPVirtualHostsFlag.Name)) - cors := splitAndTrim(c.GlobalString(utils.HTTPCORSDomainFlag.Name)) + vhosts := utils.SplitAndTrim(c.GlobalString(utils.HTTPVirtualHostsFlag.Name)) + cors := utils.SplitAndTrim(c.GlobalString(utils.HTTPCORSDomainFlag.Name)) srv := rpc.NewServer() err := node.RegisterApisFromWhitelist(rpcAPI, []string{"account"}, srv, false) @@ -736,21 +735,11 @@ func signer(c *cli.Context) error { return nil } -// splitAndTrim splits input separated by a comma -// and trims excessive white space from the substrings. -func splitAndTrim(input string) []string { - result := strings.Split(input, ",") - for i, r := range result { - result[i] = strings.TrimSpace(r) - } - return result -} - // DefaultConfigDir is the default config directory to use for the vaults and other // persistence requirements. func DefaultConfigDir() string { // Try to place the data folder in the user's home dir - home := homeDir() + home := utils.HomeDir() if home != "" { if runtime.GOOS == "darwin" { return filepath.Join(home, "Library", "Signer") @@ -769,15 +758,6 @@ func DefaultConfigDir() string { return "" } -func homeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - if usr, err := user.Current(); err == nil { - return usr.HomeDir - } - return "" -} func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { var ( file string diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index 1d4c15d1e6..debee1182b 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -23,7 +23,6 @@ import ( "math/big" "os" "os/signal" - "strings" "time" "github.com/ethereum/go-ethereum/cmd/utils" @@ -840,16 +839,6 @@ func (api *RetestethAPI) ClientVersion(ctx context.Context) (string, error) { return "Geth-" + params.VersionWithCommit(gitCommit, gitDate), nil } -// splitAndTrim splits input separated by a comma -// and trims excessive white space from the substrings. -func splitAndTrim(input string) []string { - result := strings.Split(input, ",") - for i, r := range result { - result[i] = strings.TrimSpace(r) - } - return result -} - func retesteth(ctx *cli.Context) error { log.Info("Welcome to retesteth!") // register signer API with server @@ -887,8 +876,8 @@ func retesteth(ctx *cli.Context) error { Version: "1.0", }, } - vhosts := splitAndTrim(ctx.GlobalString(utils.HTTPVirtualHostsFlag.Name)) - cors := splitAndTrim(ctx.GlobalString(utils.HTTPCORSDomainFlag.Name)) + vhosts := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPVirtualHostsFlag.Name)) + cors := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPCORSDomainFlag.Name)) // register apis and create handler stack srv := rpc.NewServer() diff --git a/cmd/utils/customflags.go b/cmd/utils/customflags.go index 17dcba5f74..0a72e80349 100644 --- a/cmd/utils/customflags.go +++ b/cmd/utils/customflags.go @@ -192,14 +192,14 @@ func GlobalBig(ctx *cli.Context, name string) *big.Int { // Note, it has limitations, e.g. ~someuser/tmp will not be expanded func expandPath(p string) string { if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { - if home := homeDir(); home != "" { + if home := HomeDir(); home != "" { p = home + p[1:] } } return path.Clean(os.ExpandEnv(p)) } -func homeDir() string { +func HomeDir() string { if home := os.Getenv("HOME"); home != "" { return home } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d47b3a2f66..47d663eeef 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -162,7 +162,7 @@ var ( DocRootFlag = DirectoryFlag{ Name: "docroot", Usage: "Document Root for HTTPClient file scheme", - Value: DirectoryString(homeDir()), + Value: DirectoryString(HomeDir()), } ExitWhenSyncedFlag = cli.BoolFlag{ Name: "exitwhensynced", @@ -793,9 +793,9 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { switch { case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV4Flag.Name): if ctx.GlobalIsSet(LegacyBootnodesV4Flag.Name) { - urls = splitAndTrim(ctx.GlobalString(LegacyBootnodesV4Flag.Name)) + urls = SplitAndTrim(ctx.GlobalString(LegacyBootnodesV4Flag.Name)) } else { - urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) + urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): urls = params.RopstenBootnodes @@ -829,9 +829,9 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { switch { case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name): if ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name) { - urls = splitAndTrim(ctx.GlobalString(LegacyBootnodesV5Flag.Name)) + urls = SplitAndTrim(ctx.GlobalString(LegacyBootnodesV5Flag.Name)) } else { - urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) + urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } case ctx.GlobalBool(RopstenFlag.Name): urls = params.RopstenBootnodes @@ -877,13 +877,12 @@ func setNAT(ctx *cli.Context, cfg *p2p.Config) { } } -// splitAndTrim splits input separated by a comma +// SplitAndTrim splits input separated by a comma // and trims excessive white space from the substrings. -func splitAndTrim(input string) (ret []string) { +func SplitAndTrim(input string) (ret []string) { l := strings.Split(input, ",") for _, r := range l { - r = strings.TrimSpace(r) - if len(r) > 0 { + if r = strings.TrimSpace(r); r != "" { ret = append(ret, r) } } @@ -917,27 +916,27 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { } if ctx.GlobalIsSet(LegacyRPCCORSDomainFlag.Name) { - cfg.HTTPCors = splitAndTrim(ctx.GlobalString(LegacyRPCCORSDomainFlag.Name)) + cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(LegacyRPCCORSDomainFlag.Name)) log.Warn("The flag --rpccorsdomain is deprecated and will be removed in the future, please use --http.corsdomain") } if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) { - cfg.HTTPCors = splitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name)) + cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name)) } if ctx.GlobalIsSet(LegacyRPCApiFlag.Name) { - cfg.HTTPModules = splitAndTrim(ctx.GlobalString(LegacyRPCApiFlag.Name)) + cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(LegacyRPCApiFlag.Name)) log.Warn("The flag --rpcapi is deprecated and will be removed in the future, please use --http.api") } if ctx.GlobalIsSet(HTTPApiFlag.Name) { - cfg.HTTPModules = splitAndTrim(ctx.GlobalString(HTTPApiFlag.Name)) + cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(HTTPApiFlag.Name)) } if ctx.GlobalIsSet(LegacyRPCVirtualHostsFlag.Name) { - cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(LegacyRPCVirtualHostsFlag.Name)) + cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(LegacyRPCVirtualHostsFlag.Name)) log.Warn("The flag --rpcvhosts is deprecated and will be removed in the future, please use --http.vhosts") } if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) { - cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name)) + cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name)) } } @@ -945,10 +944,10 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { // command line flags, returning empty if the GraphQL endpoint is disabled. func setGraphQL(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) { - cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name)) + cfg.GraphQLCors = SplitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name)) } if ctx.GlobalIsSet(GraphQLVirtualHostsFlag.Name) { - cfg.GraphQLVirtualHosts = splitAndTrim(ctx.GlobalString(GraphQLVirtualHostsFlag.Name)) + cfg.GraphQLVirtualHosts = SplitAndTrim(ctx.GlobalString(GraphQLVirtualHostsFlag.Name)) } } @@ -974,19 +973,19 @@ func setWS(ctx *cli.Context, cfg *node.Config) { } if ctx.GlobalIsSet(LegacyWSAllowedOriginsFlag.Name) { - cfg.WSOrigins = splitAndTrim(ctx.GlobalString(LegacyWSAllowedOriginsFlag.Name)) + cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(LegacyWSAllowedOriginsFlag.Name)) log.Warn("The flag --wsorigins is deprecated and will be removed in the future, please use --ws.origins") } if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) { - cfg.WSOrigins = splitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name)) + cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name)) } if ctx.GlobalIsSet(LegacyWSApiFlag.Name) { - cfg.WSModules = splitAndTrim(ctx.GlobalString(LegacyWSApiFlag.Name)) + cfg.WSModules = SplitAndTrim(ctx.GlobalString(LegacyWSApiFlag.Name)) log.Warn("The flag --wsapi is deprecated and will be removed in the future, please use --ws.api") } if ctx.GlobalIsSet(WSApiFlag.Name) { - cfg.WSModules = splitAndTrim(ctx.GlobalString(WSApiFlag.Name)) + cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name)) } } @@ -1580,7 +1579,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if urls == "" { cfg.DiscoveryURLs = []string{} } else { - cfg.DiscoveryURLs = splitAndTrim(urls) + cfg.DiscoveryURLs = SplitAndTrim(urls) } } From e1365b24647481ca92d95f678f6f8ee9773202cb Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 23 Sep 2020 15:59:11 +0800 Subject: [PATCH 041/948] trie: fix gaped range proof test case (#21484) --- trie/proof_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/proof_test.go b/trie/proof_test.go index 55585e4daf..db0e4051c8 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -451,7 +451,7 @@ func TestBadRangeProof(t *testing.T) { // with the first/last second element(since small values are // embedded in the parent). Avoid this case. index = mrand.Intn(end - start) - if (index == end-start-1 || index == 0) && end <= 100 { + if (index == 0 && start < 100) || (index == end-start-1 && end <= 100) { continue } keys = append(keys[:index], keys[index+1:]...) From 25b16085da1bab4b84f52de7275ea481ea923d68 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 23 Sep 2020 16:03:21 +0800 Subject: [PATCH 042/948] trie: support empty range proof (#21199) --- trie/proof.go | 21 +++++++++++++++++---- trie/proof_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/trie/proof.go b/trie/proof.go index 6fc21fc1f7..326235a3fd 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -393,7 +393,7 @@ func hasRightElement(node node, key []byte) bool { // (unless firstProof is an existent proof). // // Expect the normal case, this function can also be used to verify the following -// range proofs(note this function doesn't accept zero element proof): +// range proofs: // // - All elements proof. In this case the left and right proof can be nil, but the // range should be all the leaves in the trie. @@ -401,15 +401,16 @@ func hasRightElement(node node, key []byte) bool { // - One element proof. In this case no matter the left edge proof is a non-existent // proof or not, we can always verify the correctness of the proof. // +// - Zero element proof(left edge proof should be a non-existent proof). In this +// case if there are still some other leaves available on the right side, then +// an error will be returned. +// // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, values [][]byte, firstProof ethdb.KeyValueReader, lastProof ethdb.KeyValueReader) (error, bool) { if len(keys) != len(values) { return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)), false } - if len(keys) == 0 { - return errors.New("empty proof"), false - } // Ensure the received batch is monotonic increasing. for i := 0; i < len(keys)-1; i++ { if bytes.Compare(keys[i], keys[i+1]) >= 0 { @@ -431,6 +432,18 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu } return nil, false // no more element. } + // Special case, there is a provided left edge proof and zero key/value + // pairs, ensure there are no more accounts / slots in the trie. + if len(keys) == 0 { + root, val, err := proofToPath(rootHash, nil, firstKey, firstProof, true) + if err != nil { + return err, false + } + if val != nil || hasRightElement(root, firstKey) { + return errors.New("more entries available"), false + } + return nil, false + } // Special case, there is only one element and left edge // proof is an existent one. if len(keys) == 1 && bytes.Equal(keys[0], firstKey) { diff --git a/trie/proof_test.go b/trie/proof_test.go index db0e4051c8..4d76f3f783 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -571,6 +571,39 @@ func TestHasRightElement(t *testing.T) { } } +// TestEmptyRangeProof tests the range proof with "no" element. +// The first edge proof must be a non-existent proof. +func TestEmptyRangeProof(t *testing.T) { + trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { + entries = append(entries, kv) + } + sort.Sort(entries) + + var cases = []struct { + pos int + err bool + }{ + {len(entries) - 1, false}, + {500, true}, + } + for _, c := range cases { + firstProof := memorydb.New() + first := increseKey(common.CopyBytes(entries[c.pos].k)) + if err := trie.Prove(first, 0, firstProof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, firstProof, nil) + if c.err && err == nil { + t.Fatalf("Expected error, got nil") + } + if !c.err && err != nil { + t.Fatalf("Expected no error, got %v", err) + } + } +} + // mutateByte changes one byte in b. func mutateByte(b []byte) { for r := mrand.Intn(len(b)); ; { From 0921f8a74f1c050792453fc241eef67d7400d94c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 23 Sep 2020 10:29:48 +0200 Subject: [PATCH 043/948] internal/ethapi: add optional parameter blockNrOrHash to estimateGas (#21545) This allows users to estimate gas on top of arbitrary blocks as well as pending and latest. Tracing on pending is useful for most users as it takes into account the current txpool while tracing on latest might be useful for users that have little to know knowledge of the current transactions in the network. If blockNrOrHash is not specified, estimateGas defaults to pending --- internal/ethapi/api.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c035b7a9ed..eaa743fe34 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1051,9 +1051,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash // EstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { - blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - return DoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap()) +func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { + bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + if blockNrOrHash != nil { + bNrOrHash = *blockNrOrHash + } + return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) } // ExecutionResult groups all structured logs emitted by the EVM From e5defccd581880101aa0897705e3e9c3735a8186 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 23 Sep 2020 17:44:09 +0800 Subject: [PATCH 044/948] trie: extend range proof (#21250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * trie: support non-existent right proof * trie: improve test * trie: minor linter fix Co-authored-by: Péter Szilágyi --- trie/proof.go | 221 +++++++++++++++++----------- trie/proof_test.go | 356 ++++++++++++++++++++++++++++++++------------- 2 files changed, 395 insertions(+), 182 deletions(-) diff --git a/trie/proof.go b/trie/proof.go index 326235a3fd..2f52438f98 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -129,10 +129,11 @@ func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) } } -// proofToPath converts a merkle proof to trie node path. -// The main purpose of this function is recovering a node -// path from the merkle proof stream. All necessary nodes -// will be resolved and leave the remaining as hashnode. +// proofToPath converts a merkle proof to trie node path. The main purpose of +// this function is recovering a node path from the merkle proof stream. All +// necessary nodes will be resolved and leave the remaining as hashnode. +// +// The given edge proof is allowed to be an existent or non-existent proof. func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyValueReader, allowNonExistent bool) (node, []byte, error) { // resolveNode retrieves and resolves trie node from merkle proof stream resolveNode := func(hash common.Hash) (node, error) { @@ -205,54 +206,61 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV } // unsetInternal removes all internal node references(hashnode, embedded node). -// It should be called after a trie is constructed with two edge proofs. Also -// the given boundary keys must be the one used to construct the edge proofs. +// It should be called after a trie is constructed with two edge paths. Also +// the given boundary keys must be the one used to construct the edge paths. // // It's the key step for range proof. All visited nodes should be marked dirty // since the node content might be modified. Besides it can happen that some // fullnodes only have one child which is disallowed. But if the proof is valid, // the missing children will be filled, otherwise it will be thrown anyway. +// +// Note we have the assumption here the given boundary keys are different +// and right is larger than left. func unsetInternal(n node, left []byte, right []byte) error { left, right = keybytesToHex(left), keybytesToHex(right) - // todo(rjl493456442) different length edge keys should be supported - if len(left) != len(right) { - return errors.New("inconsistent edge path") - } // Step down to the fork point. There are two scenarios can happen: - // - the fork point is a shortnode: the left proof MUST point to a - // non-existent key and the key doesn't match with the shortnode - // - the fork point is a fullnode: the left proof can point to an - // existent key or not. + // - the fork point is a shortnode: either the key of left proof or + // right proof doesn't match with shortnode's key. + // - the fork point is a fullnode: both two edge proofs are allowed + // to point to a non-existent key. var ( pos = 0 parent node + + // fork indicator, 0 means no fork, -1 means proof is less, 1 means proof is greater + shortForkLeft, shortForkRight int ) findFork: for { switch rn := (n).(type) { case *shortNode: - // The right proof must point to an existent key. - if len(right)-pos < len(rn.Key) || !bytes.Equal(rn.Key, right[pos:pos+len(rn.Key)]) { - return errors.New("invalid edge path") - } rn.flags = nodeFlag{dirty: true} - // Special case, the non-existent proof points to the same path - // as the existent proof, but the path of existent proof is longer. - // In this case, the fork point is this shortnode. - if len(left)-pos < len(rn.Key) || !bytes.Equal(rn.Key, left[pos:pos+len(rn.Key)]) { + + // If either the key of left proof or right proof doesn't match with + // shortnode, stop here and the forkpoint is the shortnode. + if len(left)-pos < len(rn.Key) { + shortForkLeft = bytes.Compare(left[pos:], rn.Key) + } else { + shortForkLeft = bytes.Compare(left[pos:pos+len(rn.Key)], rn.Key) + } + if len(right)-pos < len(rn.Key) { + shortForkRight = bytes.Compare(right[pos:], rn.Key) + } else { + shortForkRight = bytes.Compare(right[pos:pos+len(rn.Key)], rn.Key) + } + if shortForkLeft != 0 || shortForkRight != 0 { break findFork } parent = n n, pos = rn.Val, pos+len(rn.Key) case *fullNode: - leftnode, rightnode := rn.Children[left[pos]], rn.Children[right[pos]] - // The right proof must point to an existent key. - if rightnode == nil { - return errors.New("invalid edge path") - } rn.flags = nodeFlag{dirty: true} - if leftnode != rightnode { + + // If either the node pointed by left proof or right proof is nil, + // stop here and the forkpoint is the fullnode. + leftnode, rightnode := rn.Children[left[pos]], rn.Children[right[pos]] + if leftnode == nil || rightnode == nil || leftnode != rightnode { break findFork } parent = n @@ -263,12 +271,42 @@ findFork: } switch rn := n.(type) { case *shortNode: - if _, ok := rn.Val.(valueNode); ok { - parent.(*fullNode).Children[right[pos-1]] = nil + // There can have these five scenarios: + // - both proofs are less than the trie path => no valid range + // - both proofs are greater than the trie path => no valid range + // - left proof is less and right proof is greater => valid range, unset the shortnode entirely + // - left proof points to the shortnode, but right proof is greater + // - right proof points to the shortnode, but left proof is less + if shortForkLeft == -1 && shortForkRight == -1 { + return errors.New("empty range") + } + if shortForkLeft == 1 && shortForkRight == 1 { + return errors.New("empty range") + } + if shortForkLeft != 0 && shortForkRight != 0 { + parent.(*fullNode).Children[left[pos-1]] = nil return nil } - return unset(rn, rn.Val, right[pos:], len(rn.Key), true) + // Only one proof points to non-existent key. + if shortForkRight != 0 { + // Unset left proof's path + if _, ok := rn.Val.(valueNode); ok { + parent.(*fullNode).Children[left[pos-1]] = nil + return nil + } + return unset(rn, rn.Val, left[pos:], len(rn.Key), false) + } + if shortForkLeft != 0 { + // Unset right proof's path. + if _, ok := rn.Val.(valueNode); ok { + parent.(*fullNode).Children[right[pos-1]] = nil + return nil + } + return unset(rn, rn.Val, right[pos:], len(rn.Key), true) + } + return nil case *fullNode: + // unset all internal nodes in the forkpoint for i := left[pos] + 1; i < right[pos]; i++ { rn.Children[i] = nil } @@ -285,19 +323,17 @@ findFork: } // unset removes all internal node references either the left most or right most. -// If we try to unset all right most references, it can meet these scenarios: +// It can meet these scenarios: // -// - The given path is existent in the trie, unset the associated shortnode +// - The given path is existent in the trie, unset the associated nodes with the +// specific direction // - The given path is non-existent in the trie // - the fork point is a fullnode, the corresponding child pointed by path // is nil, return -// - the fork point is a shortnode, the key of shortnode is less than path, +// - the fork point is a shortnode, the shortnode is included in the range, // keep the entire branch and return. -// - the fork point is a shortnode, the key of shortnode is greater than path, +// - the fork point is a shortnode, the shortnode is excluded in the range, // unset the entire branch. -// -// If we try to unset all left most references, then the given path should -// be existent. func unset(parent node, child node, key []byte, pos int, removeLeft bool) error { switch cld := child.(type) { case *fullNode: @@ -317,18 +353,29 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) { // Find the fork point, it's an non-existent branch. if removeLeft { - return errors.New("invalid right edge proof") - } - if bytes.Compare(cld.Key, key[pos:]) > 0 { - // The key of fork shortnode is greater than the - // path(it belongs to the range), unset the entrie - // branch. The parent must be a fullnode. - fn := parent.(*fullNode) - fn.Children[key[pos-1]] = nil + if bytes.Compare(cld.Key, key[pos:]) < 0 { + // The key of fork shortnode is less than the path + // (it belongs to the range), unset the entrie + // branch. The parent must be a fullnode. + fn := parent.(*fullNode) + fn.Children[key[pos-1]] = nil + } else { + // The key of fork shortnode is greater than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + } } else { - // The key of fork shortnode is less than the - // path(it doesn't belong to the range), keep - // it with the cached hash available. + if bytes.Compare(cld.Key, key[pos:]) > 0 { + // The key of fork shortnode is greater than the + // path(it belongs to the range), unset the entrie + // branch. The parent must be a fullnode. + fn := parent.(*fullNode) + fn.Children[key[pos-1]] = nil + } else { + // The key of fork shortnode is less than the + // path(it doesn't belong to the range), keep + // it with the cached hash available. + } } return nil } @@ -340,11 +387,8 @@ func unset(parent node, child node, key []byte, pos int, removeLeft bool) error cld.flags = nodeFlag{dirty: true} return unset(cld, cld.Val, key, pos+len(cld.Key), removeLeft) case nil: - // If the node is nil, it's a child of the fork point - // fullnode(it's an non-existent branch). - if removeLeft { - return errors.New("invalid right edge proof") - } + // If the node is nil, then it's a child of the fork point + // fullnode(it's a non-existent branch). return nil default: panic("it shouldn't happen") // hashNode, valueNode @@ -380,34 +424,37 @@ func hasRightElement(node node, key []byte) bool { return false } -// VerifyRangeProof checks whether the given leaf nodes and edge proofs -// can prove the given trie leaves range is matched with given root hash -// and the range is consecutive(no gap inside) and monotonic increasing. +// VerifyRangeProof checks whether the given leaf nodes and edge proof +// can prove the given trie leaves range is matched with the specific root. +// Besides, the range should be consecutive(no gap inside) and monotonic +// increasing. // -// Note the given first edge proof can be non-existing proof. For example -// the first proof is for an non-existent values 0x03. The given batch -// leaves are [0x04, 0x05, .. 0x09]. It's still feasible to prove. But the -// last edge proof should always be an existent proof. +// Note the given proof actually contains two edge proofs. Both of them can +// be non-existent proofs. For example the first proof is for a non-existent +// key 0x03, the last proof is for a non-existent key 0x10. The given batch +// leaves are [0x04, 0x05, .. 0x09]. It's still feasible to prove the given +// batch is valid. // // The firstKey is paired with firstProof, not necessarily the same as keys[0] -// (unless firstProof is an existent proof). +// (unless firstProof is an existent proof). Similarly, lastKey and lastProof +// are paired. // // Expect the normal case, this function can also be used to verify the following // range proofs: // -// - All elements proof. In this case the left and right proof can be nil, but the -// range should be all the leaves in the trie. +// - All elements proof. In this case the proof can be nil, but the range should +// be all the leaves in the trie. // -// - One element proof. In this case no matter the left edge proof is a non-existent +// - One element proof. In this case no matter the edge proof is a non-existent // proof or not, we can always verify the correctness of the proof. // -// - Zero element proof(left edge proof should be a non-existent proof). In this -// case if there are still some other leaves available on the right side, then +// - Zero element proof. In this case a single non-existent proof is enough to prove. +// Besides, if there are still some other leaves available on the right side, then // an error will be returned. // // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. -func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, values [][]byte, firstProof ethdb.KeyValueReader, lastProof ethdb.KeyValueReader) (error, bool) { +func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (error, bool) { if len(keys) != len(values) { return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)), false } @@ -419,7 +466,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu } // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. - if firstProof == nil && lastProof == nil { + if proof == nil { emptytrie, err := New(common.Hash{}, NewDatabase(memorydb.New())) if err != nil { return err, false @@ -432,10 +479,10 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu } return nil, false // no more element. } - // Special case, there is a provided left edge proof and zero key/value + // Special case, there is a provided edge proof but zero key/value // pairs, ensure there are no more accounts / slots in the trie. if len(keys) == 0 { - root, val, err := proofToPath(rootHash, nil, firstKey, firstProof, true) + root, val, err := proofToPath(rootHash, nil, firstKey, proof, true) if err != nil { return err, false } @@ -444,35 +491,47 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu } return nil, false } - // Special case, there is only one element and left edge - // proof is an existent one. - if len(keys) == 1 && bytes.Equal(keys[0], firstKey) { - root, val, err := proofToPath(rootHash, nil, firstKey, firstProof, false) + // Special case, there is only one element and two edge keys are same. + // In this case, we can't construct two edge paths. So handle it here. + if len(keys) == 1 && bytes.Equal(firstKey, lastKey) { + root, val, err := proofToPath(rootHash, nil, firstKey, proof, false) if err != nil { return err, false } + if !bytes.Equal(firstKey, keys[0]) { + return errors.New("correct proof but invalid key"), false + } if !bytes.Equal(val, values[0]) { - return fmt.Errorf("correct proof but invalid data"), false + return errors.New("correct proof but invalid data"), false } - return nil, hasRightElement(root, keys[0]) + return nil, hasRightElement(root, firstKey) + } + // Ok, in all other cases, we require two edge paths available. + // First check the validity of edge keys. + if bytes.Compare(firstKey, lastKey) >= 0 { + return errors.New("invalid edge keys"), false + } + // todo(rjl493456442) different length edge keys should be supported + if len(firstKey) != len(lastKey) { + return errors.New("inconsistent edge keys"), false } // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. - root, _, err := proofToPath(rootHash, nil, firstKey, firstProof, true) + root, _, err := proofToPath(rootHash, nil, firstKey, proof, true) if err != nil { return err, false } // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent - // proof is not allowed. - root, _, err = proofToPath(rootHash, root, keys[len(keys)-1], lastProof, false) + // proof is also allowed. + root, _, err = proofToPath(rootHash, root, lastKey, proof, true) if err != nil { return err, false } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. - if err := unsetInternal(root, firstKey, keys[len(keys)-1]); err != nil { + if err := unsetInternal(root, firstKey, lastKey); err != nil { return err, false } // Rebuild the trie with the leave stream, the shape of trie diff --git a/trie/proof_test.go b/trie/proof_test.go index 4d76f3f783..6cdc242d9a 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -166,15 +166,13 @@ func TestRangeProof(t *testing.T) { sort.Sort(entries) for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) - end := mrand.Intn(len(entries)-start) + start - if start == end { - continue - } - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(entries[start].k, 0, firstProof); err != nil { + end := mrand.Intn(len(entries)-start) + start + 1 + + proof := memorydb.New() + if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, lastProof); err != nil { + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -183,15 +181,15 @@ func TestRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } } } -// TestRangeProof tests normal range proof with the first edge proof -// as the non-existent proof. The test cases are generated randomly. +// TestRangeProof tests normal range proof with two non-existent proofs. +// The test cases are generated randomly. func TestRangeProofWithNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) var entries entrySlice @@ -201,20 +199,31 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { sort.Sort(entries) for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) - end := mrand.Intn(len(entries)-start) + start - if start == end { - continue - } - firstProof, lastProof := memorydb.New(), memorydb.New() + end := mrand.Intn(len(entries)-start) + start + 1 + proof := memorydb.New() + // Short circuit if the decreased key is same with the previous key first := decreseKey(common.CopyBytes(entries[start].k)) if start != 0 && bytes.Equal(first, entries[start-1].k) { continue } - if err := trie.Prove(first, 0, firstProof); err != nil { + // Short circuit if the decreased key is underflow + if bytes.Compare(first, entries[start].k) > 0 { + continue + } + // Short circuit if the increased key is same with the next key + last := increseKey(common.CopyBytes(entries[end-1].k)) + if end != len(entries) && bytes.Equal(last, entries[end].k) { + continue + } + // Short circuit if the increased key is overflow + if bytes.Compare(last, entries[end-1].k) < 0 { + continue + } + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, lastProof); err != nil { + if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -223,16 +232,36 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, keys, vals, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } } + // Special case, two edge proofs for two edge key. + proof := memorydb.New() + first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() + last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() + if err := trie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + var k [][]byte + var v [][]byte + for i := 0; i < len(entries); i++ { + k = append(k, entries[i].k) + v = append(v, entries[i].v) + } + err, _ := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + if err != nil { + t.Fatal("Failed to verify whole rang with non-existent edges") + } } // TestRangeProofWithInvalidNonExistentProof tests such scenarios: -// - The last edge proof is an non-existent proof // - There exists a gap between the first element and the left edge proof +// - There exists a gap between the last element and the right edge proof func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) var entries entrySlice @@ -243,44 +272,45 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { // Case 1 start, end := 100, 200 - first, last := decreseKey(common.CopyBytes(entries[start].k)), increseKey(common.CopyBytes(entries[end].k)) - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(first, 0, firstProof); err != nil { + first := decreseKey(common.CopyBytes(entries[start].k)) + + proof := memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, lastProof); err != nil { + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - var k [][]byte - var v [][]byte + start = 105 // Gap created + k := make([][]byte, 0) + v := make([][]byte, 0) for i := start; i < end; i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, k, v, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } // Case 2 start, end = 100, 200 - first = decreseKey(common.CopyBytes(entries[start].k)) - - firstProof, lastProof = memorydb.New(), memorydb.New() - if err := trie.Prove(first, 0, firstProof); err != nil { + last := increseKey(common.CopyBytes(entries[end-1].k)) + proof = memorydb.New() + if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, lastProof); err != nil { + if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - start = 105 // Gap created + end = 195 // Capped slice k = make([][]byte, 0) v = make([][]byte, 0) for i := start; i < end; i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ = VerifyRangeProof(trie.Hash(), first, k, v, firstProof, lastProof) + err, _ = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -297,31 +327,59 @@ func TestOneElementRangeProof(t *testing.T) { } sort.Sort(entries) - // One element with existent edge proof + // One element with existent edge proof, both edge proofs + // point to the SAME key. start := 1000 - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(entries[start].k, 0, firstProof); err != nil { + proof := memorydb.New() + if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[start].k, 0, lastProof); err != nil { + err, _ := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // One element with left non-existent edge proof + start = 1000 + first := decreseKey(common.CopyBytes(entries[start].k)) + proof = memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, firstProof, lastProof) + err, _ = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } - // One element with non-existent edge proof + // One element with right non-existent edge proof start = 1000 - first := decreseKey(common.CopyBytes(entries[start].k)) - firstProof, lastProof = memorydb.New(), memorydb.New() - if err := trie.Prove(first, 0, firstProof); err != nil { + last := increseKey(common.CopyBytes(entries[start].k)) + proof = memorydb.New() + if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[start].k, 0, lastProof); err != nil { + if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[start].k}, [][]byte{entries[start].v}, firstProof, lastProof) + err, _ = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // One element with two non-existent edge proofs + start = 1000 + first, last = decreseKey(common.CopyBytes(entries[start].k)), increseKey(common.CopyBytes(entries[start].k)) + proof = memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -343,20 +401,35 @@ func TestAllElementsProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), k[0], k, v, nil, nil) + err, _ := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) } - // Even with edge proofs, it should still work. - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(entries[0].k, 0, firstProof); err != nil { + // With edge proofs, it should still work. + proof := memorydb.New() + if err := trie.Prove(entries[0].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[len(entries)-1].k, 0, lastProof); err != nil { + if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), k[0], k, v, firstProof, lastProof) + err, _ = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Even with non-existent edge proofs, it should still work. + proof = memorydb.New() + first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() + last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() + if err := trie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + err, _ = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -376,11 +449,11 @@ func TestSingleSideRangeProof(t *testing.T) { var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1} for _, pos := range cases { - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(common.Hash{}.Bytes(), 0, firstProof); err != nil { + proof := memorydb.New() + if err := trie.Prove(common.Hash{}.Bytes(), 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[pos].k, 0, lastProof); err != nil { + if err := trie.Prove(entries[pos].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } k := make([][]byte, 0) @@ -389,7 +462,43 @@ func TestSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k, v, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + } + } +} + +// TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff. +func TestReverseSingleSideRangeProof(t *testing.T) { + for i := 0; i < 64; i++ { + trie := new(Trie) + var entries entrySlice + for i := 0; i < 4096; i++ { + value := &kv{randBytes(32), randBytes(20), false} + trie.Update(value.k, value.v) + entries = append(entries, value) + } + sort.Sort(entries) + + var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1} + for _, pos := range cases { + proof := memorydb.New() + if err := trie.Prove(entries[pos].k, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + if err := trie.Prove(last.Bytes(), 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + k := make([][]byte, 0) + v := make([][]byte, 0) + for i := pos; i < len(entries); i++ { + k = append(k, entries[i].k) + v = append(v, entries[i].v) + } + err, _ := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -409,15 +518,12 @@ func TestBadRangeProof(t *testing.T) { for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) - end := mrand.Intn(len(entries)-start) + start - if start == end { - continue - } - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(entries[start].k, 0, firstProof); err != nil { + end := mrand.Intn(len(entries)-start) + start + 1 + proof := memorydb.New() + if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, lastProof); err != nil { + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -426,6 +532,7 @@ func TestBadRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } + var first, last = keys[0], keys[len(keys)-1] testcase := mrand.Intn(6) var index int switch testcase { @@ -439,17 +546,6 @@ func TestBadRangeProof(t *testing.T) { vals[index] = randBytes(20) // In theory it can't be same case 2: // Gapped entry slice - - // There are only two elements, skip it. Dropped any element - // will lead to single edge proof which is always correct. - if end-start <= 2 { - continue - } - // If the dropped element is the first or last one and it's a - // batch of small size elements. In this special case, it can - // happen that the proof for the edge element is exactly same - // with the first/last second element(since small values are - // embedded in the parent). Avoid this case. index = mrand.Intn(end - start) if (index == 0 && start < 100) || (index == end-start-1 && end <= 100) { continue @@ -457,20 +553,24 @@ func TestBadRangeProof(t *testing.T) { keys = append(keys[:index], keys[index+1:]...) vals = append(vals[:index], vals[index+1:]...) case 3: - // Switched entry slice, same effect with gapped - index = mrand.Intn(end - start) - keys[index] = entries[len(entries)-1].k - vals[index] = entries[len(entries)-1].v + // Out of order + index1 := mrand.Intn(end - start) + index2 := mrand.Intn(end - start) + if index1 == index2 { + continue + } + keys[index1], keys[index2] = keys[index2], keys[index1] + vals[index1], vals[index2] = vals[index2], vals[index1] case 4: - // Set random key to nil + // Set random key to nil, do nothing index = mrand.Intn(end - start) keys[index] = nil case 5: - // Set random value to nil + // Set random value to nil, deletion index = mrand.Intn(end - start) vals[index] = nil } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err == nil { t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1) } @@ -488,11 +588,11 @@ func TestGappedRangeProof(t *testing.T) { entries = append(entries, value) } first, last := 2, 8 - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(entries[first].k, 0, firstProof); err != nil { + proof := memorydb.New() + if err := trie.Prove(entries[first].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[last-1].k, 0, lastProof); err != nil { + if err := trie.Prove(entries[last-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -504,12 +604,55 @@ func TestGappedRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err == nil { t.Fatal("expect error, got nil") } } +// TestSameSideProofs tests the element is not in the range covered by proofs +func TestSameSideProofs(t *testing.T) { + trie, vals := randomTrie(4096) + var entries entrySlice + for _, kv := range vals { + entries = append(entries, kv) + } + sort.Sort(entries) + + pos := 1000 + first := decreseKey(common.CopyBytes(entries[pos].k)) + first = decreseKey(first) + last := decreseKey(common.CopyBytes(entries[pos].k)) + + proof := memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + err, _ := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + if err == nil { + t.Fatalf("Expected error, got nil") + } + + first = increseKey(common.CopyBytes(entries[pos].k)) + last = increseKey(common.CopyBytes(entries[pos].k)) + last = increseKey(last) + + proof = memorydb.New() + if err := trie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := trie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + if err == nil { + t.Fatalf("Expected error, got nil") + } +} + func TestHasRightElement(t *testing.T) { trie := new(Trie) var entries entrySlice @@ -530,38 +673,49 @@ func TestHasRightElement(t *testing.T) { {0, 10, true}, {50, 100, true}, {50, len(entries), false}, // No more element expected - {len(entries) - 1, len(entries), false}, // Single last element + {len(entries) - 1, len(entries), false}, // Single last element with two existent proofs(point to same key) + {len(entries) - 1, -1, false}, // Single last element with non-existent right proof {0, len(entries), false}, // The whole set with existent left proof {-1, len(entries), false}, // The whole set with non-existent left proof + {-1, -1, false}, // The whole set with non-existent left/right proof } for _, c := range cases { var ( - firstKey []byte - start = c.start - firstProof = memorydb.New() - lastProof = memorydb.New() + firstKey []byte + lastKey []byte + start = c.start + end = c.end + proof = memorydb.New() ) if c.start == -1 { firstKey, start = common.Hash{}.Bytes(), 0 - if err := trie.Prove(firstKey, 0, firstProof); err != nil { + if err := trie.Prove(firstKey, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } else { firstKey = entries[c.start].k - if err := trie.Prove(entries[c.start].k, 0, firstProof); err != nil { + if err := trie.Prove(entries[c.start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } - if err := trie.Prove(entries[c.end-1].k, 0, lastProof); err != nil { - t.Fatalf("Failed to prove the first node %v", err) + if c.end == -1 { + lastKey, end = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes(), len(entries) + if err := trie.Prove(lastKey, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + } else { + lastKey = entries[c.end-1].k + if err := trie.Prove(entries[c.end-1].k, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } } k := make([][]byte, 0) v := make([][]byte, 0) - for i := start; i < c.end; i++ { + for i := start; i < end; i++ { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, hasMore := VerifyRangeProof(trie.Hash(), firstKey, k, v, firstProof, lastProof) + err, hasMore := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -589,12 +743,12 @@ func TestEmptyRangeProof(t *testing.T) { {500, true}, } for _, c := range cases { - firstProof := memorydb.New() + proof := memorydb.New() first := increseKey(common.CopyBytes(entries[c.pos].k)) - if err := trie.Prove(first, 0, firstProof); err != nil { + if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, firstProof, nil) + err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) if c.err && err == nil { t.Fatalf("Expected error, got nil") } @@ -688,11 +842,11 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { start := 2 end := start + size - firstProof, lastProof := memorydb.New(), memorydb.New() - if err := trie.Prove(entries[start].k, 0, firstProof); err != nil { + proof := memorydb.New() + if err := trie.Prove(entries[start].k, 0, proof); err != nil { b.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, lastProof); err != nil { + if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { b.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -704,7 +858,7 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { b.ResetTimer() for i := 0; i < b.N; i++ { - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys, values, firstProof, lastProof) + err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) if err != nil { b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } From c1544423d655f540b55bb6ec0e7167dad9683c42 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 23 Sep 2020 13:08:40 +0200 Subject: [PATCH 045/948] internal/ethapi: fix nil deref + fix estimateGas console bindings (#21601) * tried to fix * fix for js api * fix for nil pointer ex * rev space * rev space * input call formatter --- internal/ethapi/api.go | 3 +++ internal/web3ext/web3ext.go | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index eaa743fe34..c7d1e0020f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -963,6 +963,9 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash if err != nil { return 0, err } + if block == nil { + return 0, errors.New("block not found") + } hi = block.GasLimit() } // Recap the highest gas limit with account's available balance. diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 300c2b054f..e2b8ad1718 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -500,6 +500,13 @@ web3._extend({ params: 1, inputFormatter: [web3._extend.formatters.inputTransactionFormatter] }), + new web3._extend.Method({ + name: 'estimateGas', + call: 'eth_estimateGas', + params: 2, + inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputBlockNumberFormatter], + outputFormatter: web3._extend.utils.toDecimal + }), new web3._extend.Method({ name: 'submitTransaction', call: 'eth_submitTransaction', From a25899f3dc2494cad515ed1a65b0c4e28db951a9 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 23 Sep 2020 15:18:17 +0200 Subject: [PATCH 046/948] cmd/devp2p: add eth protocol test suite (#21598) This change adds a test framework for the "eth" protocol and some basic tests. The tests can be run using the './devp2p rlpx eth-test' command. --- cmd/devp2p/internal/ethtest/chain.go | 113 +++++++++ cmd/devp2p/internal/ethtest/suite.go | 337 +++++++++++++++++++++++++++ cmd/devp2p/internal/ethtest/types.go | 134 +++++++++++ cmd/devp2p/main.go | 2 +- cmd/devp2p/rlpxcmd.go | 51 ++-- core/forkid/forkid.go | 15 +- core/forkid/forkid_test.go | 2 +- eth/discovery.go | 3 +- eth/handler.go | 3 +- eth/helper_test.go | 3 +- eth/protocol_test.go | 2 +- 11 files changed, 628 insertions(+), 37 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/chain.go create mode 100644 cmd/devp2p/internal/ethtest/suite.go create mode 100644 cmd/devp2p/internal/ethtest/types.go diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go new file mode 100644 index 0000000000..250be64fe6 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -0,0 +1,113 @@ +package ethtest + +import ( + "compress/gzip" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "math/big" + "os" + "strings" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +type Chain struct { + blocks []*types.Block + chainConfig *params.ChainConfig +} + +func (c *Chain) WriteTo(writer io.Writer) error { + for _, block := range c.blocks { + if err := rlp.Encode(writer, block); err != nil { + return err + } + } + + return nil +} + +// Len returns the length of the chain. +func (c *Chain) Len() int { + return len(c.blocks) +} + +// TD calculates the total difficulty of the chain. +func (c *Chain) TD(height int) *big.Int { // TODO later on channge scheme so that the height is included in range + sum := big.NewInt(0) + for _, block := range c.blocks[:height] { + sum.Add(sum, block.Difficulty()) + } + return sum +} + +// ForkID gets the fork id of the chain. +func (c *Chain) ForkID() forkid.ID { + return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len())) +} + +// Shorten returns a copy chain of a desired height from the imported +func (c *Chain) Shorten(height int) *Chain { + blocks := make([]*types.Block, height) + copy(blocks, c.blocks[:height]) + + config := *c.chainConfig + return &Chain{ + blocks: blocks, + chainConfig: &config, + } +} + +// Head returns the chain head. +func (c *Chain) Head() *types.Block { + return c.blocks[c.Len()-1] +} + +// loadChain takes the given chain.rlp file, and decodes and returns +// the blocks from the file. +func loadChain(chainfile string, genesis string) (*Chain, error) { + // Open the file handle and potentially unwrap the gzip stream + fh, err := os.Open(chainfile) + if err != nil { + return nil, err + } + defer fh.Close() + + var reader io.Reader = fh + if strings.HasSuffix(chainfile, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return nil, err + } + } + stream := rlp.NewStream(reader, 0) + var blocks []*types.Block + for i := 0; ; i++ { + var b types.Block + if err := stream.Decode(&b); err == io.EOF { + break + } else if err != nil { + return nil, fmt.Errorf("at block %d: %v", i, err) + } + blocks = append(blocks, &b) + } + + // Open the file handle and potentially unwrap the gzip stream + chainConfig, err := ioutil.ReadFile(genesis) + if err != nil { + return nil, err + } + var gen core.Genesis + if err := json.Unmarshal(chainConfig, &gen); err != nil { + return nil, err + } + + return &Chain{ + blocks: blocks, + chainConfig: gen.Config, + }, nil +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go new file mode 100644 index 0000000000..8a9438bebd --- /dev/null +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -0,0 +1,337 @@ +package ethtest + +import ( + "crypto/ecdsa" + "fmt" + "net" + "reflect" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/rlpx" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" +) + +// Suite represents a structure used to test the eth +// protocol of a node(s). +type Suite struct { + Dest *enode.Node + + chain *Chain + fullChain *Chain +} + +type Conn struct { + *rlpx.Conn + ourKey *ecdsa.PrivateKey +} + +func (c *Conn) Read() Message { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return &Error{fmt.Errorf("could not read from connection: %v", err)} + } + + var msg Message + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + msg = new(GetBlockHeaders) + case (BlockHeaders{}).Code(): + msg = new(BlockHeaders) + case (GetBlockBodies{}).Code(): + msg = new(GetBlockBodies) + case (BlockBodies{}).Code(): + msg = new(BlockBodies) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + default: + return &Error{fmt.Errorf("invalid message code: %d", code)} + } + + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return &Error{fmt.Errorf("could not rlp decode message: %v", err)} + } + + return msg +} + +func (c *Conn) Write(msg Message) error { + payload, err := rlp.EncodeToBytes(msg) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(msg.Code()), payload) + return err + +} + +// handshake checks to make sure a `HELLO` is received. +func (c *Conn) handshake(t *utesting.T) Message { + // write protoHandshake to client + pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &Hello{ + Version: 3, + Caps: []p2p.Cap{{Name: "eth", Version: 64}, {Name: "eth", Version: 65}}, + ID: pub0, + } + if err := c.Write(ourHandshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // read protoHandshake from client + switch msg := c.Read().(type) { + case *Hello: + return msg + default: + t.Fatalf("bad handshake: %v", msg) + return nil + } +} + +// statusExchange performs a `Status` message exchange with the given +// node. +func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { + // read status message from client + var message Message + switch msg := c.Read().(type) { + case *Status: + if msg.Head != chain.blocks[chain.Len()-1].Hash() { + t.Fatalf("wrong head in status: %v", msg.Head) + } + if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { + t.Fatalf("wrong TD in status: %v", msg.TD) + } + if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) { + t.Fatalf("wrong fork ID in status: %v", msg.ForkID) + } + message = msg + default: + t.Fatalf("bad status message: %v", msg) + } + // write status message to client + status := Status{ + ProtocolVersion: 65, + NetworkID: 1, + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + if err := c.Write(status); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + return message +} + +// waitForBlock waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock(block *types.Block) error { + for { + req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} + if err := c.Write(req); err != nil { + return err + } + + switch msg := c.Read().(type) { + case *BlockHeaders: + if len(*msg) > 0 { + return nil + } + time.Sleep(100 * time.Millisecond) + default: + return fmt.Errorf("invalid message: %v", msg) + } + } +} + +// NewSuite creates and returns a new eth-test suite that can +// be used to test the given node against the given blockchain +// data. +func NewSuite(dest *enode.Node, chainfile string, genesisfile string) *Suite { + chain, err := loadChain(chainfile, genesisfile) + if err != nil { + panic(err) + } + return &Suite{ + Dest: dest, + chain: chain.Shorten(1000), + fullChain: chain, + } +} + +func (s *Suite) AllTests() []utesting.Test { + return []utesting.Test{ + {Name: "Status", Fn: s.TestStatus}, + {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + } +} + +// TestStatus attempts to connect to the given node and exchange +// a status message with it, and then check to make sure +// the chain head is correct. +func (s *Suite) TestStatus(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + // get protoHandshake + conn.handshake(t) + // get status + switch msg := conn.statusExchange(t, s.chain).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("error: %v", msg) + } +} + +// TestGetBlockHeaders tests whether the given node can respond to +// a `GetBlockHeaders` request and that the response is accurate. +func (s *Suite) TestGetBlockHeaders(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + + conn.handshake(t) + conn.statusExchange(t, s.chain) + + // get block headers + req := &GetBlockHeaders{ + Origin: hashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + } + + if err := conn.Write(req); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := conn.Read().(type) { + case *BlockHeaders: + headers := msg + for _, header := range *headers { + num := header.Number.Uint64() + assert.Equal(t, s.chain.blocks[int(num)].Header(), header) + t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header) + } + default: + t.Fatalf("error: %v", msg) + } +} + +// TestGetBlockBodies tests whether the given node can respond to +// a `GetBlockBodies` request and that the response is accurate. +func (s *Suite) TestGetBlockBodies(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + + conn.handshake(t) + conn.statusExchange(t, s.chain) + // create block bodies request + req := &GetBlockBodies{s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash()} + if err := conn.Write(req); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := conn.Read().(type) { + case *BlockBodies: + bodies := msg + for _, body := range *bodies { + t.Logf("\nBODY: %+v\n", body) + } + default: + t.Fatalf("error: %v", msg) + } +} + +// TestBroadcast tests whether a block announcement is correctly +// propagated to the given node's peer(s). +func (s *Suite) TestBroadcast(t *utesting.T) { + // create conn to send block announcement + sendConn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + // create conn to receive block announcement + receiveConn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + + sendConn.handshake(t) + receiveConn.handshake(t) + + sendConn.statusExchange(t, s.chain) + receiveConn.statusExchange(t, s.chain) + + // sendConn sends the block announcement + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[1000], + TD: s.fullChain.TD(1001), + } + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := receiveConn.Read().(type) { + case *NewBlock: + assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement") + assert.Equal(t, blockAnnouncement.TD, msg.TD, + "wrong TD in announcement") + case *NewBlockHashes: + hashes := *msg + assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash, + "wrong block hash in announcement") + default: + t.Fatal(msg) + } + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000]) + // wait for client to update its chain + if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { + t.Fatal(err) + } +} + +// dial attempts to dial the given node and perform a handshake, +// returning the created Conn if successful. +func (s *Suite) dial() (*Conn, error) { + var conn Conn + + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) + if err != nil { + return nil, err + } + conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey()) + + // do encHandshake + conn.ourKey, _ = crypto.GenerateKey() + _, err = conn.Handshake(conn.ourKey) + if err != nil { + return nil, err + } + + return &conn, nil +} diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go new file mode 100644 index 0000000000..ef2c52ddfd --- /dev/null +++ b/cmd/devp2p/internal/ethtest/types.go @@ -0,0 +1,134 @@ +package ethtest + +import ( + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" +) + +type Message interface { + Code() int +} + +type Error struct { + err error +} + +func (e *Error) Unwrap() error { return e.err } +func (e *Error) Error() string { return e.err.Error() } +func (e *Error) Code() int { return -1 } + +// Hello is the RLP structure of the protocol handshake. +type Hello struct { + Version uint64 + Name string + Caps []p2p.Cap + ListenPort uint64 + ID []byte // secp256k1 public key + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +func (h Hello) Code() int { return 0x00 } + +// Disconnect is the RLP structure for a disconnect message. +type Disconnect struct { + Reason p2p.DiscReason +} + +func (d Disconnect) Code() int { return 0x01 } + +// Status is the network packet for the status message for eth/64 and later. +type Status struct { + ProtocolVersion uint32 + NetworkID uint64 + TD *big.Int + Head common.Hash + Genesis common.Hash + ForkID forkid.ID +} + +func (s Status) Code() int { return 16 } + +// NewBlockHashes is the network packet for the block announcements. +type NewBlockHashes []struct { + Hash common.Hash // Hash of one particular block being announced + Number uint64 // Number of one particular block being announced +} + +func (nbh NewBlockHashes) Code() int { return 17 } + +// NewBlock is the network packet for the block propagation message. +type NewBlock struct { + Block *types.Block + TD *big.Int +} + +func (nb NewBlock) Code() int { return 23 } + +// GetBlockHeaders represents a block header query. +type GetBlockHeaders struct { + Origin hashOrNumber // Block from which to retrieve headers + Amount uint64 // Maximum number of headers to retrieve + Skip uint64 // Blocks to skip between consecutive headers + Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) +} + +func (g GetBlockHeaders) Code() int { return 19 } + +type BlockHeaders []*types.Header + +func (bh BlockHeaders) Code() int { return 20 } + +// HashOrNumber is a combined field for specifying an origin block. +type hashOrNumber struct { + Hash common.Hash // Block hash from which to retrieve headers (excludes Number) + Number uint64 // Block hash from which to retrieve headers (excludes Hash) +} + +// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the +// two contained union fields. +func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { + if hn.Hash == (common.Hash{}) { + return rlp.Encode(w, hn.Number) + } + if hn.Number != 0 { + return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) + } + return rlp.Encode(w, hn.Hash) +} + +// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents +// into either a block hash or a block number. +func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { + _, size, _ := s.Kind() + origin, err := s.Raw() + if err == nil { + switch { + case size == 32: + err = rlp.DecodeBytes(origin, &hn.Hash) + case size <= 8: + err = rlp.DecodeBytes(origin, &hn.Number) + default: + err = fmt.Errorf("invalid input size %d for origin", size) + } + } + return err +} + +// GetBlockBodies represents a GetBlockBodies request +type GetBlockBodies []common.Hash + +func (gbb GetBlockBodies) Code() int { return 21 } + +// BlockBodies is the network packet for block content distribution. +type BlockBodies []*types.Body + +func (bb BlockBodies) Code() int { return 22 } diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go index 9eebd9b137..4a4e905a42 100644 --- a/cmd/devp2p/main.go +++ b/cmd/devp2p/main.go @@ -81,7 +81,7 @@ func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool { // getNodeArg handles the common case of a single node descriptor argument. func getNodeArg(ctx *cli.Context) *enode.Node { - if ctx.NArg() != 1 { + if ctx.NArg() < 1 { exit("missing node as command-line argument") } n, err := parseNode(ctx.Args()[0]) diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index 14eb5989d1..17019aee00 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -19,9 +19,11 @@ package main import ( "fmt" "net" + "os" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" @@ -34,38 +36,42 @@ var ( Usage: "RLPx Commands", Subcommands: []cli.Command{ rlpxPingCommand, + rlpxEthTestCommand, }, } rlpxPingCommand = cli.Command{ - Name: "ping", - Usage: "Perform a RLPx handshake", - ArgsUsage: "", - Action: rlpxPing, + Name: "ping", + Usage: "ping ", + Action: rlpxPing, + } + rlpxEthTestCommand = cli.Command{ + Name: "eth-test", + Usage: "Runs tests against a node", + ArgsUsage: " ", + Action: rlpxEthTest, + Flags: []cli.Flag{testPatternFlag}, } ) func rlpxPing(ctx *cli.Context) error { n := getNodeArg(ctx) - fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP())) if err != nil { return err } conn := rlpx.NewConn(fd, n.Pubkey()) - ourKey, _ := crypto.GenerateKey() _, err = conn.Handshake(ourKey) if err != nil { return err } - code, data, _, err := conn.Read() if err != nil { return err } switch code { case 0: - var h devp2pHandshake + var h ethtest.Hello if err := rlp.DecodeBytes(data, &h); err != nil { return fmt.Errorf("invalid handshake: %v", err) } @@ -82,13 +88,22 @@ func rlpxPing(ctx *cli.Context) error { return nil } -// devp2pHandshake is the RLP structure of the devp2p protocol handshake. -type devp2pHandshake struct { - Version uint64 - Name string - Caps []p2p.Cap - ListenPort uint64 - ID hexutil.Bytes // secp256k1 public key - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` +func rlpxEthTest(ctx *cli.Context) error { + if ctx.NArg() < 3 { + exit("missing path to chain.rlp as command-line argument") + } + + suite := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) + + // Filter and run test cases. + tests := suite.AllTests() + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + results := utesting.RunTests(tests, os.Stdout) + if fails := utesting.CountFailures(results); fails > 0 { + return fmt.Errorf("%v of %v tests passed.", len(tests)-fails, len(tests)) + } + fmt.Printf("all tests passed\n") + return nil } diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index b8d670f399..c432858617 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -65,19 +65,8 @@ type ID struct { // Filter is a fork id filter to validate a remotely advertised ID. type Filter func(id ID) error -// NewID calculates the Ethereum fork ID from the chain config and head. -func NewID(chain Blockchain) ID { - return newID( - chain.Config(), - chain.Genesis().Hash(), - chain.CurrentHeader().Number.Uint64(), - ) -} - -// newID is the internal version of NewID, which takes extracted values as its -// arguments instead of a chain. The reason is to allow testing the IDs without -// having to simulate an entire blockchain. -func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { +// NewID calculates the Ethereum fork ID from the chain config, genesis hash, and head. +func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { // Calculate the starting checksum from the genesis hash hash := crc32.ChecksumIEEE(genesis[:]) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index da56530214..888b553475 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -118,7 +118,7 @@ func TestCreation(t *testing.T) { } for i, tt := range tests { for j, ttt := range tt.cases { - if have := newID(tt.config, tt.genesis, ttt.head); have != ttt.want { + if have := NewID(tt.config, tt.genesis, ttt.head); have != ttt.want { t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want) } } diff --git a/eth/discovery.go b/eth/discovery.go index 97d6322ca1..48f6159017 100644 --- a/eth/discovery.go +++ b/eth/discovery.go @@ -60,7 +60,8 @@ func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) { } func (eth *Ethereum) currentEthEntry() *ethEntry { - return ðEntry{ForkID: forkid.NewID(eth.blockchain)} + return ðEntry{ForkID: forkid.NewID(eth.blockchain.Config(), eth.blockchain.Genesis().Hash(), + eth.blockchain.CurrentHeader().Number.Uint64())} } // setupDiscovery creates the node discovery source for the eth protocol. diff --git a/eth/handler.go b/eth/handler.go index 7482a2f96e..f5ec2c4fbf 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -319,7 +319,8 @@ func (pm *ProtocolManager) handle(p *peer) error { number = head.Number.Uint64() td = pm.blockchain.GetTd(hash, number) ) - if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkid.NewID(pm.blockchain), pm.forkFilter); err != nil { + forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) + if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkID, pm.forkFilter); err != nil { p.Log().Debug("Ethereum handshake failed", "err", err) return err } diff --git a/eth/helper_test.go b/eth/helper_test.go index 65effcc165..c0bda181ea 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -185,7 +185,8 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te head = pm.blockchain.CurrentHeader() td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) ) - tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkid.NewID(pm.blockchain), forkid.NewFilter(pm.blockchain)) + forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) + tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(pm.blockchain)) } return tp, errc } diff --git a/eth/protocol_test.go b/eth/protocol_test.go index fc916a2263..331dd05ce1 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -104,7 +104,7 @@ func TestStatusMsgErrors64(t *testing.T) { genesis = pm.blockchain.Genesis() head = pm.blockchain.CurrentHeader() td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - forkID = forkid.NewID(pm.blockchain) + forkID = forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) ) defer pm.Stop() From 39f8268147b5f4f0e42ce56dea8e1cd37aeedb75 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 23 Sep 2020 17:48:47 +0200 Subject: [PATCH 047/948] cmd/devp2p/internal/ethtest: update version in handshake (#21603) --- cmd/devp2p/internal/ethtest/suite.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 8a9438bebd..86494bb6b7 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -83,7 +83,7 @@ func (c *Conn) handshake(t *utesting.T) Message { // write protoHandshake to client pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] ourHandshake := &Hello{ - Version: 3, + Version: 5, Caps: []p2p.Cap{{Name: "eth", Version: 64}, {Name: "eth", Version: 65}}, ID: pub0, } From fdd42d425b49383c4544314eba69b26f450b537b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 24 Sep 2020 10:46:43 +0200 Subject: [PATCH 048/948] cmd/devp2p/internal/ethtest: lower protocol version to 64 (#21604) --- cmd/devp2p/internal/ethtest/suite.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 86494bb6b7..6951f13bfb 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -122,7 +122,7 @@ func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { } // write status message to client status := Status{ - ProtocolVersion: 65, + ProtocolVersion: 64, NetworkID: 1, TD: chain.TD(chain.Len()), Head: chain.blocks[chain.Len()-1].Hash(), From b5d362b2bf1ef6aece6d0d54b89d2e7ea4b86c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 28 Sep 2020 11:19:22 +0300 Subject: [PATCH 049/948] params: update CHTs for Geth v1.9.22 --- params/config.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/params/config.go b/params/config.go index 2cd66acc2f..a69d513080 100644 --- a/params/config.go +++ b/params/config.go @@ -73,10 +73,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 329, - SectionHead: common.HexToHash("0x96bb6d286ded20a18480dd98d537ab503bd81110c6b9c3f8ad1f9338f3b9852d"), - CHTRoot: common.HexToHash("0x10627ff648077adeaab9dbd4e5bbed8671c86005b2aef5f5d4857acca19a49d8"), - BloomRoot: common.HexToHash("0xf499b0cfaf426a490b7b5ddca58d3031b008f0c15338f8f25c20f3df050bf785"), + SectionIndex: 333, + SectionHead: common.HexToHash("0xb80784cbe88077e5911b446765edc814dd67ca3f6bdd33b6ec72d66058df4a11"), + CHTRoot: common.HexToHash("0x4da9cde840dd3de39916620f7a97674c5747a89a9359e6b918e134d199a8dd45"), + BloomRoot: common.HexToHash("0xdd0f4fef7fa2a5cc05d49568e38f15dab24098ffc7677a2e35d1a8d67f5458af"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -154,10 +154,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 217, - SectionHead: common.HexToHash("0x9afa4900a60cb44b102eb2eb5e5ef1d7f4cc1911c1c0588518995fb778ffe894"), - CHTRoot: common.HexToHash("0xcc963e5085622c7cb6b3bf747fbfdfe71887e0d5bc9e4b3fb0474d44fc97942a"), - BloomRoot: common.HexToHash("0x1064ca3a36b6f129783cff51bb18fb038bade47d2b776d1cccb9c74925106703"), + SectionIndex: 220, + SectionHead: common.HexToHash("0x9513befa126a83c96a6408ee8b34502699094a49b2bf1064b2de31b010a03798"), + CHTRoot: common.HexToHash("0x490a17d3bfbfc9bca9de087c5ee9c9f69dc2359cad9c1fe68cab639fdbcfccee"), + BloomRoot: common.HexToHash("0x56bf5fda940ca4ca8346e42ef86f9092c82268c304c03c4093b21c1aa07190fc"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -194,10 +194,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 101, - SectionHead: common.HexToHash("0x396f5dd8e526edfb550873bcfe0e93dc00d70be4b881ab256980833b97a18c3e"), - CHTRoot: common.HexToHash("0x0d145657a6595508ef878c9bbf8eca045631986f664bfab0d898fc64804a4e64"), - BloomRoot: common.HexToHash("0x12df34d07cf1268abe22d40ee6deb199b8918e3d57d52f9e70f9b2883f57d74f"), + SectionIndex: 105, + SectionHead: common.HexToHash("0x695f5b67d1985fb13d177c56d20ded0622d7f63a1623959fb4b5c5e38dc6bbee"), + CHTRoot: common.HexToHash("0x4c281ef1ca63e6f9bb4ce8e46e80e478787c91da95c3727550ee418886dd6415"), + BloomRoot: common.HexToHash("0xa02463cc6ee54f12990e9adb019e34696ad1efe2694cf07187d7ce0802cd653d"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From c71a7e26a8b1e332bbf3262d88ba3ff32071456c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 28 Sep 2020 11:21:47 +0300 Subject: [PATCH 050/948] params: release Geth v1.9.22 --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 521efe15f5..54e3c806ee 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 22 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 22 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From c9959145a9c1f1141784a2e7e0b2cb070d399065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 28 Sep 2020 11:23:02 +0300 Subject: [PATCH 051/948] params: begin v1.9.23 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 54e3c806ee..d9dbeea19a 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 22 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 23 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 420b78659bef661a83c5c442121b13f13288c09f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 28 Sep 2020 14:10:26 +0200 Subject: [PATCH 052/948] accounts/abi: ABI explicit difference between Unpack and UnpackIntoInterface (#21091) * accounts/abi: refactored abi.Unpack * accounts/abi/bind: fixed error * accounts/abi/bind: modified template * accounts/abi/bind: added ToStruct for conversion * accounts/abi: reenabled tests * accounts/abi: fixed tests * accounts/abi: fixed tests for packing/unpacking * accounts/abi: fixed tests * accounts/abi: added more logic to ToStruct * accounts/abi/bind: fixed template * accounts/abi/bind: fixed ToStruct conversion * accounts/abi/: removed unused code * accounts/abi: updated template * accounts/abi: refactored unused code * contracts/checkpointoracle: updated contracts to sol ^0.6.0 * accounts/abi: refactored reflection logic * accounts/abi: less code duplication in Unpack* * accounts/abi: fixed rebasing bug * fix a few typos in comments * rebase on master Co-authored-by: Guillaume Ballet --- accounts/abi/abi.go | 60 ++++++++++------ accounts/abi/abi_test.go | 17 ++--- accounts/abi/argument.go | 54 ++++++++------ accounts/abi/bind/base.go | 15 ++-- accounts/abi/bind/base_test.go | 7 +- accounts/abi/bind/bind_test.go | 4 +- accounts/abi/bind/template.go | 34 +++++---- accounts/abi/event_test.go | 22 +++--- accounts/abi/pack_test.go | 13 +--- accounts/abi/packing_test.go | 50 ++++++------- accounts/abi/reflect.go | 26 +++++++ accounts/abi/reflect_test.go | 70 +++++++++++++++++++ accounts/abi/unpack_test.go | 60 ++++++++-------- contracts/checkpointoracle/contract/oracle.go | 68 +++++++++++------- .../checkpointoracle/contract/oracle.sol | 2 +- go.mod | 2 +- mobile/bind.go | 18 ++--- tests/fuzzers/abi/abifuzzer.go | 4 +- 18 files changed, 325 insertions(+), 201 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 38196ed25c..5ca7c241db 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -80,36 +80,56 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return append(method.ID, arguments...), nil } -// Unpack output in v according to the abi specification. -func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { +func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event + var args Arguments if method, ok := abi.Methods[name]; ok { if len(data)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) + return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) } - return method.Outputs.Unpack(v, data) + args = method.Outputs } if event, ok := abi.Events[name]; ok { - return event.Inputs.Unpack(v, data) + args = event.Inputs } - return fmt.Errorf("abi: could not locate named method or event") + if args == nil { + return nil, errors.New("abi: could not locate named method or event") + } + return args, nil +} + +// Unpack unpacks the output according to the abi specification. +func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { + args, err := abi.getArguments(name, data) + if err != nil { + return nil, err + } + return args.Unpack(data) +} + +// UnpackIntoInterface unpacks the output in v according to the abi specification. +// It performs an additional copy. Please only use, if you want to unpack into a +// structure that does not strictly conform to the abi structure (e.g. has additional arguments) +func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + unpacked, err := args.Unpack(data) + if err != nil { + return err + } + return args.Copy(v, unpacked) } // UnpackIntoMap unpacks a log into the provided map[string]interface{}. func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { - // since there can't be naming collisions with contracts and events, - // we need to decide whether we're calling a method or an event - if method, ok := abi.Methods[name]; ok { - if len(data)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output") - } - return method.Outputs.UnpackIntoMap(v, data) - } - if event, ok := abi.Events[name]; ok { - return event.Inputs.UnpackIntoMap(v, data) + args, err := abi.getArguments(name, data) + if err != nil { + return err } - return fmt.Errorf("abi: could not locate named method or event") + return args.UnpackIntoMap(v, data) } // UnmarshalJSON implements json.Unmarshaler interface. @@ -250,10 +270,10 @@ func UnpackRevert(data []byte) (string, error) { if !bytes.Equal(data[:4], revertSelector) { return "", errors.New("invalid data for unpacking") } - var reason string typ, _ := NewType("string", "", nil) - if err := (Arguments{{Type: typ}}).Unpack(&reason, data[4:]); err != nil { + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { return "", err } - return reason, nil + return unpacked[0].(string), nil } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index f7d7f7aa62..ad8acdf522 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -181,18 +181,15 @@ func TestConstructor(t *testing.T) { if err != nil { t.Error(err) } - v := struct { - A *big.Int - B *big.Int - }{new(big.Int), new(big.Int)} - //abi.Unpack(&v, "", packed) - if err := abi.Constructor.Inputs.Unpack(&v, packed); err != nil { + unpacked, err := abi.Constructor.Inputs.Unpack(packed) + if err != nil { t.Error(err) } - if !reflect.DeepEqual(v.A, big.NewInt(1)) { + + if !reflect.DeepEqual(unpacked[0], big.NewInt(1)) { t.Error("Unable to pack/unpack from constructor") } - if !reflect.DeepEqual(v.B, big.NewInt(2)) { + if !reflect.DeepEqual(unpacked[1], big.NewInt(2)) { t.Error("Unable to pack/unpack from constructor") } } @@ -743,7 +740,7 @@ func TestUnpackEvent(t *testing.T) { } var ev ReceivedEvent - err = abi.Unpack(&ev, "received", data) + err = abi.UnpackIntoInterface(&ev, "received", data) if err != nil { t.Error(err) } @@ -752,7 +749,7 @@ func TestUnpackEvent(t *testing.T) { Sender common.Address } var receivedAddrEv ReceivedAddrEvent - err = abi.Unpack(&receivedAddrEv, "receivedAddr", data) + err = abi.UnpackIntoInterface(&receivedAddrEv, "receivedAddr", data) if err != nil { t.Error(err) } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index aaef3bd41c..e6d5245596 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -76,28 +76,20 @@ func (arguments Arguments) isTuple() bool { } // Unpack performs the operation hexdata -> Go format. -func (arguments Arguments) Unpack(v interface{}, data []byte) error { +func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { if len(data) == 0 { if len(arguments) != 0 { - return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") + return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") } - return nil // Nothing to unmarshal, return - } - // make sure the passed value is arguments pointer - if reflect.Ptr != reflect.ValueOf(v).Kind() { - return fmt.Errorf("abi: Unpack(non-pointer %T)", v) - } - marshalledValues, err := arguments.UnpackValues(data) - if err != nil { - return err - } - if len(marshalledValues) == 0 { - return fmt.Errorf("abi: Unpack(no-values unmarshalled %T)", v) - } - if arguments.isTuple() { - return arguments.unpackTuple(v, marshalledValues) + // Nothing to unmarshal, return default variables + nonIndexedArgs := arguments.NonIndexed() + defaultVars := make([]interface{}, len(nonIndexedArgs)) + for index, arg := range nonIndexedArgs { + defaultVars[index] = reflect.New(arg.Type.GetType()) + } + return defaultVars, nil } - return arguments.unpackAtomic(v, marshalledValues[0]) + return arguments.UnpackValues(data) } // UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. @@ -122,8 +114,26 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) return nil } -// unpackAtomic unpacks ( hexdata -> go ) a single value. -func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { +// Copy performs the operation go format -> provided struct. +func (arguments Arguments) Copy(v interface{}, values []interface{}) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) + } + if len(values) == 0 { + if len(arguments) != 0 { + return fmt.Errorf("abi: attempting to copy no values while %d arguments are expected", len(arguments)) + } + return nil // Nothing to copy, return + } + if arguments.isTuple() { + return arguments.copyTuple(v, values) + } + return arguments.copyAtomic(v, values[0]) +} + +// unpackAtomic unpacks ( hexdata -> go ) a single value +func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { dst := reflect.ValueOf(v).Elem() src := reflect.ValueOf(marshalledValues) @@ -133,8 +143,8 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interfac return set(dst, src) } -// unpackTuple unpacks ( hexdata -> go ) a batch of values. -func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { +// copyTuple copies a batch of values from marshalledValues to v. +func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error { value := reflect.ValueOf(v).Elem() nonIndexedArgs := arguments.NonIndexed() diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index d9935e3de7..0216f6b544 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -117,11 +117,14 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { +func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { // Don't crash on a lazy user if opts == nil { opts = new(CallOpts) } + if results == nil { + results = new([]interface{}) + } // Pack the input, call and unpack the results input, err := c.abi.Pack(method, params...) if err != nil { @@ -158,10 +161,14 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, } } } - if err != nil { + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res return err } - return c.abi.Unpack(result, method, output) + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) } // Transact invokes the (paid) contract method with params as input values. @@ -339,7 +346,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter // UnpackLog unpacks a retrieved log into the provided output structure. func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { if len(log.Data) > 0 { - if err := c.abi.Unpack(out, event, log.Data); err != nil { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { return err } } diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 7d287850f4..c4740f68b7 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -71,11 +71,10 @@ func TestPassingBlockNumber(t *testing.T) { }, }, }, mc, nil, nil) - var ret string blockNumber := big.NewInt(42) - bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something") + bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something") if mc.callContractBlockNumber != blockNumber { t.Fatalf("CallContract() was not passed the block number") @@ -85,7 +84,7 @@ func TestPassingBlockNumber(t *testing.T) { t.Fatalf("CodeAt() was not passed the block number") } - bc.Call(&bind.CallOpts{}, &ret, "something") + bc.Call(&bind.CallOpts{}, nil, "something") if mc.callContractBlockNumber != nil { t.Fatalf("CallContract() was passed a block number when it should not have been") @@ -95,7 +94,7 @@ func TestPassingBlockNumber(t *testing.T) { t.Fatalf("CodeAt() was passed a block number when it should not have been") } - bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, &ret, "something") + bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something") if !mc.pendingCallContractCalled { t.Fatalf("CallContract() was not passed the block number") diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index a5f08499d2..8bfbf30b53 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1696,11 +1696,11 @@ func TestGolangBindings(t *testing.T) { t.Skip("go sdk not found for testing") } // Create a temporary workspace for the test suite - ws, err := ioutil.TempDir("", "") + ws, err := ioutil.TempDir("", "binding-test") if err != nil { t.Fatalf("failed to create temporary workspace: %v", err) } - defer os.RemoveAll(ws) + //defer os.RemoveAll(ws) pkg := filepath.Join(ws, "bindtest") if err = os.MkdirAll(pkg, 0700); err != nil { diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index f19f1315ae..5329b3ebc3 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -261,7 +261,7 @@ var ( // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. - func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) } @@ -280,7 +280,7 @@ var ( // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. - func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) } @@ -300,19 +300,23 @@ var ( // // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) { - {{if .Structured}}ret := new(struct{ - {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}} - {{end}} - }){{else}}var ( - {{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type $structs}}) - {{end}} - ){{end}} - out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{ - {{range $i, $_ := .Normalized.Outputs}}ret{{$i}}, - {{end}} - }{{end}}{{end}} - err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) - return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err + var out []interface{} + err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = out[{{$i}}].({{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} } // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 79504c28ce..3332f8a072 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -147,10 +147,6 @@ func TestEventString(t *testing.T) { // TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. func TestEventMultiValueWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` - type testStruct struct { - Value1 [2]uint8 - Value2 uint8 - } abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) var b bytes.Buffer @@ -158,10 +154,10 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) { for ; i <= 3; i++ { b.Write(packNum(reflect.ValueOf(i))) } - var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) - require.Equal(t, [2]uint8{1, 2}, rst.Value1) - require.Equal(t, uint8(3), rst.Value2) + unpacked, err := abi.Unpack("test", b.Bytes()) + require.NoError(t, err) + require.Equal(t, [2]uint8{1, 2}, unpacked[0]) + require.Equal(t, uint8(3), unpacked[1]) } func TestEventTupleUnpack(t *testing.T) { @@ -351,14 +347,14 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass var e Event assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI") a := ABI{Events: map[string]Event{"e": e}} - return a.Unpack(dest, "e", data) + return a.UnpackIntoInterface(dest, "e", data) } // TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. func TestEventUnpackIndexed(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` type testStruct struct { - Value1 uint8 + Value1 uint8 // indexed Value2 uint8 } abi, err := JSON(strings.NewReader(definition)) @@ -366,7 +362,7 @@ func TestEventUnpackIndexed(t *testing.T) { var b bytes.Buffer b.Write(packNum(reflect.ValueOf(uint8(8)))) var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, uint8(0), rst.Value1) require.Equal(t, uint8(8), rst.Value2) } @@ -375,7 +371,7 @@ func TestEventUnpackIndexed(t *testing.T) { func TestEventIndexedWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` type testStruct struct { - Value1 [2]uint8 + Value1 [2]uint8 // indexed Value2 string } abi, err := JSON(strings.NewReader(definition)) @@ -388,7 +384,7 @@ func TestEventIndexedWithArrayUnpack(t *testing.T) { b.Write(common.RightPadBytes([]byte(stringOut), 32)) var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, [2]uint8{0, 0}, rst.Value1) require.Equal(t, stringOut, rst.Value2) } diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 284215a7d7..5c7cb1cc1a 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -44,18 +44,7 @@ func TestPack(t *testing.T) { t.Fatalf("invalid ABI definition %s, %v", inDef, err) } var packed []byte - if reflect.TypeOf(test.unpacked).Kind() != reflect.Struct { - packed, err = inAbi.Pack("method", test.unpacked) - } else { - // if want is a struct we need to use the components. - elem := reflect.ValueOf(test.unpacked) - var values []interface{} - for i := 0; i < elem.NumField(); i++ { - field := elem.Field(i) - values = append(values, field.Interface()) - } - packed, err = inAbi.Pack("method", values...) - } + packed, err = inAbi.Pack("method", test.unpacked) if err != nil { t.Fatalf("test %d (%v) failed: %v", i, test.def, err) diff --git a/accounts/abi/packing_test.go b/accounts/abi/packing_test.go index 16b4dc43d7..eae3b0df20 100644 --- a/accounts/abi/packing_test.go +++ b/accounts/abi/packing_test.go @@ -620,7 +620,7 @@ var packUnpackTests = []packUnpackTest{ { def: `[{"type": "bytes32[]"}]`, - unpacked: []common.Hash{{1}, {2}}, + unpacked: [][32]byte{{1}, {2}}, packed: "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000002" + "0100000000000000000000000000000000000000000000000000000000000000" + @@ -722,7 +722,7 @@ var packUnpackTests = []packUnpackTest{ }, // struct outputs { - def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`, + def: `[{"components": [{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002", unpacked: struct { @@ -731,28 +731,28 @@ var packUnpackTests = []packUnpackTest{ }{big.NewInt(1), big.NewInt(2)}, }, { - def: `[{"name":"int_one","type":"int256"}]`, + def: `[{"components": [{"name":"int_one","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001", unpacked: struct { IntOne *big.Int }{big.NewInt(1)}, }, { - def: `[{"name":"int__one","type":"int256"}]`, + def: `[{"components": [{"name":"int__one","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001", unpacked: struct { IntOne *big.Int }{big.NewInt(1)}, }, { - def: `[{"name":"int_one_","type":"int256"}]`, + def: `[{"components": [{"name":"int_one_","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001", unpacked: struct { IntOne *big.Int }{big.NewInt(1)}, }, { - def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`, + def: `[{"components": [{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}], "type":"tuple"}]`, packed: "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002", unpacked: struct { @@ -831,11 +831,11 @@ var packUnpackTests = []packUnpackTest{ }, { // static tuple - def: `[{"name":"a","type":"int64"}, + def: `[{"components": [{"name":"a","type":"int64"}, {"name":"b","type":"int256"}, {"name":"c","type":"int256"}, {"name":"d","type":"bool"}, - {"name":"e","type":"bytes32[3][2]"}]`, + {"name":"e","type":"bytes32[3][2]"}], "type":"tuple"}]`, unpacked: struct { A int64 B *big.Int @@ -855,21 +855,22 @@ var packUnpackTests = []packUnpackTest{ "0500000000000000000000000000000000000000000000000000000000000000", // struct[e] array[1][2] }, { - def: `[{"name":"a","type":"string"}, + def: `[{"components": [{"name":"a","type":"string"}, {"name":"b","type":"int64"}, {"name":"c","type":"bytes"}, {"name":"d","type":"string[]"}, {"name":"e","type":"int256[]"}, - {"name":"f","type":"address[]"}]`, + {"name":"f","type":"address[]"}], "type":"tuple"}]`, unpacked: struct { - FieldA string `abi:"a"` // Test whether abi tag works - FieldB int64 `abi:"b"` - C []byte - D []string - E []*big.Int - F []common.Address + A string + B int64 + C []byte + D []string + E []*big.Int + F []common.Address }{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}}, - packed: "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] "0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset "0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset @@ -894,23 +895,24 @@ var packUnpackTests = []packUnpackTest{ "0000000000000000000000000200000000000000000000000000000000000000", // common.Address{2} }, { - def: `[{"components": [{"name": "a","type": "uint256"}, + def: `[{"components": [{ "type": "tuple","components": [{"name": "a","type": "uint256"}, {"name": "b","type": "uint256[]"}], "name": "a","type": "tuple"}, - {"name": "b","type": "uint256[]"}]`, + {"name": "b","type": "uint256[]"}], "type": "tuple"}]`, unpacked: struct { A struct { - FieldA *big.Int `abi:"a"` - B []*big.Int + A *big.Int + B []*big.Int } B []*big.Int }{ A: struct { - FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple - B []*big.Int + A *big.Int + B []*big.Int }{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(2)}}, B: []*big.Int{big.NewInt(1), big.NewInt(2)}}, - packed: "0000000000000000000000000000000000000000000000000000000000000040" + // a offset + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "0000000000000000000000000000000000000000000000000000000000000040" + // a offset "00000000000000000000000000000000000000000000000000000000000000e0" + // b offset "0000000000000000000000000000000000000000000000000000000000000001" + // a.a value "0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index f4812b06bf..11248e0730 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -24,6 +24,29 @@ import ( "strings" ) +// ConvertType converts an interface of a runtime type into a interface of the +// given type +// e.g. turn +// var fields []reflect.StructField +// fields = append(fields, reflect.StructField{ +// Name: "X", +// Type: reflect.TypeOf(new(big.Int)), +// Tag: reflect.StructTag("json:\"" + "x" + "\""), +// } +// into +// type TupleT struct { X *big.Int } +func ConvertType(in interface{}, proto interface{}) interface{} { + protoType := reflect.TypeOf(proto) + if reflect.TypeOf(in).ConvertibleTo(protoType) { + return reflect.ValueOf(in).Convert(protoType).Interface() + } + // Use set as a last ditch effort + if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { + panic(err) + } + return proto +} + // indirect recursively dereferences the value until it either gets the value // or finds a big.Int func indirect(v reflect.Value) reflect.Value { @@ -119,6 +142,9 @@ func setSlice(dst, src reflect.Value) error { } func setArray(dst, src reflect.Value) error { + if src.Kind() == reflect.Ptr { + return set(dst, indirect(src)) + } array := reflect.New(dst.Type()).Elem() min := src.Len() if src.Len() > dst.Len() { diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index c425e6e54b..bac4cd9530 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -17,6 +17,7 @@ package abi import ( + "math/big" "reflect" "testing" ) @@ -189,3 +190,72 @@ func TestReflectNameToStruct(t *testing.T) { }) } } + +func TestConvertType(t *testing.T) { + // Test Basic Struct + type T struct { + X *big.Int + Y *big.Int + } + // Create on-the-fly structure + var fields []reflect.StructField + fields = append(fields, reflect.StructField{ + Name: "X", + Type: reflect.TypeOf(new(big.Int)), + Tag: reflect.StructTag("json:\"" + "x" + "\""), + }) + fields = append(fields, reflect.StructField{ + Name: "Y", + Type: reflect.TypeOf(new(big.Int)), + Tag: reflect.StructTag("json:\"" + "y" + "\""), + }) + val := reflect.New(reflect.StructOf(fields)) + val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val.Elem().Field(1).Set(reflect.ValueOf(big.NewInt(2))) + // ConvertType + out := *ConvertType(val.Interface(), new(T)).(*T) + if out.X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.X, big.NewInt(1)) + } + if out.Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.Y, big.NewInt(2)) + } + // Slice Type + val2 := reflect.MakeSlice(reflect.SliceOf(reflect.StructOf(fields)), 2, 2) + val2.Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val2.Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val2.Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val2.Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out2 := *ConvertType(val2.Interface(), new([]T)).(*[]T) + if out2[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + if out2[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + // Array Type + val3 := reflect.New(reflect.ArrayOf(2, reflect.StructOf(fields))) + val3.Elem().Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val3.Elem().Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val3.Elem().Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val3.Elem().Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out3 := *ConvertType(val3.Interface(), new([2]T)).(*[2]T) + if out3[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } + if out3[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } +} diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 767d1540e6..b88f77805b 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -44,15 +44,13 @@ func TestUnpack(t *testing.T) { if err != nil { t.Fatalf("invalid hex %s: %v", test.packed, err) } - outptr := reflect.New(reflect.TypeOf(test.unpacked)) - err = abi.Unpack(outptr.Interface(), "method", encb) + out, err := abi.Unpack("method", encb) if err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return } - out := outptr.Elem().Interface() - if !reflect.DeepEqual(test.unpacked, out) { - t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out) + if !reflect.DeepEqual(test.unpacked, ConvertType(out[0], test.unpacked)) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out[0]) } }) } @@ -221,7 +219,7 @@ func TestLocalUnpackTests(t *testing.T) { t.Fatalf("invalid hex %s: %v", test.enc, err) } outptr := reflect.New(reflect.TypeOf(test.want)) - err = abi.Unpack(outptr.Interface(), "method", encb) + err = abi.UnpackIntoInterface(outptr.Interface(), "method", encb) if err := test.checkError(err); err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return @@ -234,7 +232,7 @@ func TestLocalUnpackTests(t *testing.T) { } } -func TestUnpackSetDynamicArrayOutput(t *testing.T) { +func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`)) if err != nil { t.Fatal(err) @@ -249,7 +247,7 @@ func TestUnpackSetDynamicArrayOutput(t *testing.T) { ) // test 32 - err = abi.Unpack(&out32, "testDynamicFixedBytes32", marshalledReturn32) + err = abi.UnpackIntoInterface(&out32, "testDynamicFixedBytes32", marshalledReturn32) if err != nil { t.Fatal(err) } @@ -266,7 +264,7 @@ func TestUnpackSetDynamicArrayOutput(t *testing.T) { } // test 15 - err = abi.Unpack(&out15, "testDynamicFixedBytes32", marshalledReturn15) + err = abi.UnpackIntoInterface(&out15, "testDynamicFixedBytes32", marshalledReturn15) if err != nil { t.Fatal(err) } @@ -367,7 +365,7 @@ func TestMethodMultiReturn(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) - err := abi.Unpack(tc.dest, "multi", data) + err := abi.UnpackIntoInterface(tc.dest, "multi", data) if tc.error == "" { require.Nil(err, "Should be able to unpack method outputs.") require.Equal(tc.expected, tc.dest) @@ -390,7 +388,7 @@ func TestMultiReturnWithArray(t *testing.T) { ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} ret2, ret2Exp := new(uint64), uint64(8) - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -414,7 +412,7 @@ func TestMultiReturnWithStringArray(t *testing.T) { ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} ret4, ret4Exp := new(bool), false - if err := abi.Unpack(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -452,7 +450,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"} ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)} - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -492,7 +490,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, } ret2, ret2Exp := new(uint64), uint64(0x9876) - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -531,7 +529,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) - err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&mixedBytes, "mixedBytes", buff.Bytes()) if err != nil { t.Error(err) } else { @@ -546,7 +544,7 @@ func TestUnmarshal(t *testing.T) { // marshal int var Int *big.Int - err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + err = abi.UnpackIntoInterface(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) } @@ -557,7 +555,7 @@ func TestUnmarshal(t *testing.T) { // marshal bool var Bool bool - err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + err = abi.UnpackIntoInterface(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) } @@ -574,7 +572,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(bytesOut) var Bytes []byte - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -590,7 +588,7 @@ func TestUnmarshal(t *testing.T) { bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -606,7 +604,7 @@ func TestUnmarshal(t *testing.T) { bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -616,7 +614,7 @@ func TestUnmarshal(t *testing.T) { } // marshal dynamic bytes output empty - err = abi.Unpack(&Bytes, "bytes", nil) + err = abi.UnpackIntoInterface(&Bytes, "bytes", nil) if err == nil { t.Error("expected error") } @@ -627,7 +625,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) buff.Write(common.RightPadBytes([]byte("hello"), 32)) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -641,7 +639,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.RightPadBytes([]byte("hello"), 32)) var hash common.Hash - err = abi.Unpack(&hash, "fixed", buff.Bytes()) + err = abi.UnpackIntoInterface(&hash, "fixed", buff.Bytes()) if err != nil { t.Error(err) } @@ -654,12 +652,12 @@ func TestUnmarshal(t *testing.T) { // marshal error buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err == nil { t.Error("expected error") } - err = abi.Unpack(&Bytes, "multi", make([]byte, 64)) + err = abi.UnpackIntoInterface(&Bytes, "multi", make([]byte, 64)) if err == nil { t.Error("expected error") } @@ -670,7 +668,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) // marshal int array var intArray [3]*big.Int - err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&intArray, "intArraySingle", buff.Bytes()) if err != nil { t.Error(err) } @@ -691,7 +689,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) var outAddr []common.Address - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) } @@ -718,7 +716,7 @@ func TestUnmarshal(t *testing.T) { A []common.Address B []common.Address } - err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddrStruct, "addressSliceDouble", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) } @@ -746,7 +744,7 @@ func TestUnmarshal(t *testing.T) { buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err == nil { t.Fatal("expected error:", err) } @@ -769,7 +767,7 @@ func TestUnpackTuple(t *testing.T) { B *big.Int }{new(big.Int), new(big.Int)} - err = abi.Unpack(&v, "tuple", buff.Bytes()) + err = abi.UnpackIntoInterface(&v, "tuple", buff.Bytes()) if err != nil { t.Error(err) } else { @@ -841,7 +839,7 @@ func TestUnpackTuple(t *testing.T) { A: big.NewInt(1), } - err = abi.Unpack(&ret, "tuple", buff.Bytes()) + err = abi.UnpackIntoInterface(&ret, "tuple", buff.Bytes()) if err != nil { t.Error(err) } diff --git a/contracts/checkpointoracle/contract/oracle.go b/contracts/checkpointoracle/contract/oracle.go index 998ccb93c2..a4a308f5c5 100644 --- a/contracts/checkpointoracle/contract/oracle.go +++ b/contracts/checkpointoracle/contract/oracle.go @@ -27,10 +27,17 @@ var ( ) // CheckpointOracleABI is the input ABI used to generate the binding from. -const CheckpointOracleABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"name\":\"_hash\",\"type\":\"bytes32\"},{\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"name\":\"v\",\"type\":\"uint8[]\"},{\"name\":\"r\",\"type\":\"bytes32[]\"},{\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"name\":\"_threshold\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"}]" +const CheckpointOracleABI = "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_hash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"internalType\":\"uint8[]\",\"name\":\"v\",\"type\":\"uint8[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"r\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +// CheckpointOracleFuncSigs maps the 4-byte function signature to its string representation. +var CheckpointOracleFuncSigs = map[string]string{ + "45848dfc": "GetAllAdmin()", + "4d6a304c": "GetLatestCheckpoint()", + "d459fc46": "SetCheckpoint(uint256,bytes32,bytes32,uint64,uint8[],bytes32[],bytes32[])", +} // CheckpointOracleBin is the compiled bytecode used for deploying new contracts. -const CheckpointOracleBin = `0x608060405234801561001057600080fd5b506040516108153803806108158339818101604052608081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8201602081018481111561005e57600080fd5b815185602082028301116401000000008211171561007b57600080fd5b505060208201516040830151606090930151919450925060005b84518110156101415760016000808784815181106100af57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff02191690831515021790555060018582815181106100fc57fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b039093169290921790915501610095565b50600592909255600655600755506106b78061015e6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a661034f565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061036a945050505050565b604080519115158252519081900360200190f35b6060806001805490506040519080825280602002602001820160405280156102e2578160200160208202803883390190505b50905060005b60015481101561034957600181815481106102ff57fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061032957fe5b6001600160a01b03909216602092830291909101909101526001016102e8565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661038657600080fd5b8688401461039357600080fd5b82518451146103a157600080fd5b81518451146103af57600080fd5b6006546005548660010167ffffffffffffffff1602014310156103d457506000610677565b60025467ffffffffffffffff90811690861610156103f457506000610677565b60025467ffffffffffffffff8681169116148015610426575067ffffffffffffffff8516151580610426575060035415155b1561043357506000610677565b8561044057506000610677565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106715760006001848984815181106104b457fe5b60200260200101518985815181106104c857fe5b60200260200101518986815181106104dc57fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561053b573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061057057600080fd5b826001600160a01b0316816001600160a01b03161161058e57600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105ca57fe5b60200260200101518a86815181106105de57fe5b60200260200101518a87815181106105f257fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a260075482600101106106685750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff86161790556001610677565b5060010161049a565b50600080fd5b97965050505050505056fea265627a7a723058207f6a191ce575596a2f1e907c8c0a01003d16b69fb2c4f432d10878e8c0a99a0264736f6c634300050a0032` +var CheckpointOracleBin = "0x608060405234801561001057600080fd5b506040516108703803806108708339818101604052608081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825186602082028301116401000000008211171561008557600080fd5b82525081516020918201928201910280838360005b838110156100b257818101518382015260200161009a565b50505050919091016040908152602083015190830151606090930151909450919250600090505b84518110156101855760016000808784815181106100f357fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550600185828151811061014057fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155016100d9565b50600592909255600655600755506106ce806101a26000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a6610365565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610380945050505050565b604080519115158252519081900360200190f35b600154606090819067ffffffffffffffff811180156102ce57600080fd5b506040519080825280602002602001820160405280156102f8578160200160208202803683370190505b50905060005b60015481101561035f576001818154811061031557fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061033f57fe5b6001600160a01b03909216602092830291909101909101526001016102fe565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661039c57600080fd5b868840146103a957600080fd5b82518451146103b757600080fd5b81518451146103c557600080fd5b6006546005548660010167ffffffffffffffff1602014310156103ea5750600061068d565b60025467ffffffffffffffff908116908616101561040a5750600061068d565b60025467ffffffffffffffff868116911614801561043c575067ffffffffffffffff851615158061043c575060035415155b156104495750600061068d565b856104565750600061068d565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106875760006001848984815181106104ca57fe5b60200260200101518985815181106104de57fe5b60200260200101518986815181106104f257fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015610551573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061058657600080fd5b826001600160a01b0316816001600160a01b0316116105a457600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105e057fe5b60200260200101518a86815181106105f457fe5b60200260200101518a878151811061060857fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a2600754826001011061067e5750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff8616179055600161068d565b506001016104b0565b50600080fd5b97965050505050505056fea26469706673582212202ddf9eda76bf59c0fc65584c0b22d84ecef2c703765de60439596d6ac34c2b7264736f6c634300060b0033" // DeployCheckpointOracle deploys a new Ethereum contract, binding an instance of CheckpointOracle to it. func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address, _sectionSize *big.Int, _processConfirms *big.Int, _threshold *big.Int) (common.Address, *types.Transaction, *CheckpointOracle, error) { @@ -38,6 +45,7 @@ func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBacken if err != nil { return common.Address{}, nil, nil, err } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(CheckpointOracleBin), backend, _adminlist, _sectionSize, _processConfirms, _threshold) if err != nil { return common.Address{}, nil, nil, err @@ -153,7 +161,7 @@ func bindCheckpointOracle(address common.Address, caller bind.ContractCaller, tr // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _CheckpointOracle.Contract.CheckpointOracleCaller.contract.Call(opts, result, method, params...) } @@ -172,7 +180,7 @@ func (_CheckpointOracle *CheckpointOracleRaw) Transact(opts *bind.TransactOpts, // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { +func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _CheckpointOracle.Contract.contract.Call(opts, result, method, params...) } @@ -189,58 +197,64 @@ func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transact(opts *bind.Tran // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. // -// Solidity: function GetAllAdmin() constant returns(address[]) +// Solidity: function GetAllAdmin() view returns(address[]) func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) { - var ( - ret0 = new([]common.Address) - ) - out := ret0 - err := _CheckpointOracle.contract.Call(opts, out, "GetAllAdmin") - return *ret0, err + var out []interface{} + err := _CheckpointOracle.contract.Call(opts, &out, "GetAllAdmin") + + if err != nil { + return *new([]common.Address), err + } + + out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) + + return out0, err + } // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. // -// Solidity: function GetAllAdmin() constant returns(address[]) +// Solidity: function GetAllAdmin() view returns(address[]) func (_CheckpointOracle *CheckpointOracleSession) GetAllAdmin() ([]common.Address, error) { return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts) } // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc. // -// Solidity: function GetAllAdmin() constant returns(address[]) +// Solidity: function GetAllAdmin() view returns(address[]) func (_CheckpointOracle *CheckpointOracleCallerSession) GetAllAdmin() ([]common.Address, error) { return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts) } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. // -// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256) +// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256) func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind.CallOpts) (uint64, [32]byte, *big.Int, error) { - var ( - ret0 = new(uint64) - ret1 = new([32]byte) - ret2 = new(*big.Int) - ) - out := &[]interface{}{ - ret0, - ret1, - ret2, + var out []interface{} + err := _CheckpointOracle.contract.Call(opts, &out, "GetLatestCheckpoint") + + if err != nil { + return *new(uint64), *new([32]byte), *new(*big.Int), err } - err := _CheckpointOracle.contract.Call(opts, out, "GetLatestCheckpoint") - return *ret0, *ret1, *ret2, err + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + out1 := *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) + out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + + return out0, out1, out2, err + } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. // -// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256) +// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256) func (_CheckpointOracle *CheckpointOracleSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) { return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts) } // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c. // -// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256) +// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256) func (_CheckpointOracle *CheckpointOracleCallerSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) { return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts) } diff --git a/contracts/checkpointoracle/contract/oracle.sol b/contracts/checkpointoracle/contract/oracle.sol index 0106447273..65bac09d28 100644 --- a/contracts/checkpointoracle/contract/oracle.sol +++ b/contracts/checkpointoracle/contract/oracle.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.10; +pragma solidity ^0.6.0; /** * @title CheckpointOracle diff --git a/go.mod b/go.mod index 2a701c3614..ae1cf64aaf 100755 --- a/go.mod +++ b/go.mod @@ -68,5 +68,5 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 - gotest.tools v2.2.0+incompatible // indirect + gotest.tools v2.2.0+incompatible ) diff --git a/mobile/bind.go b/mobile/bind.go index f64b37ec15..afa97b5382 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -171,20 +171,12 @@ func (c *BoundContract) GetDeployer() *Transaction { // Call invokes the (constant) contract method with params as input values and // sets the output to result. func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, args *Interfaces) error { - if len(out.objects) == 1 { - result := out.objects[0] - if err := c.contract.Call(&opts.opts, result, method, args.objects...); err != nil { - return err - } - out.objects[0] = result - } else { - results := make([]interface{}, len(out.objects)) - copy(results, out.objects) - if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil { - return err - } - copy(out.objects, results) + results := make([]interface{}, len(out.objects)) + copy(results, out.objects) + if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil { + return err } + copy(out.objects, results) return nil } diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index ed5c7c0586..76d3c800f7 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -30,7 +30,7 @@ import ( func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byte) bool { outptr := reflect.New(reflect.TypeOf(inputType)) - if err := abi.Unpack(outptr.Interface(), method, input); err == nil { + if err := abi.UnpackIntoInterface(outptr.Interface(), method, input); err == nil { output, err := abi.Pack(method, input) if err != nil { // We have some false positives as we can unpack these type successfully, but not pack them @@ -51,7 +51,7 @@ func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byt func packUnpack(abi abi.ABI, method string, input []interface{}) bool { if packed, err := abi.Pack(method, input); err == nil { outptr := reflect.New(reflect.TypeOf(input)) - err := abi.Unpack(outptr.Interface(), method, packed) + err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) if err != nil { panic(err) } From a90e645ccda7d8d373783e161e6c6fc4430cb6d0 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 28 Sep 2020 14:12:08 +0200 Subject: [PATCH 053/948] mobile: added constructor for big int (#21597) * mobile: added constructor for big int * mobile: tiny nitpick --- mobile/big.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mobile/big.go b/mobile/big.go index 86ea93245a..c08bcf93f2 100644 --- a/mobile/big.go +++ b/mobile/big.go @@ -35,6 +35,16 @@ func NewBigInt(x int64) *BigInt { return &BigInt{big.NewInt(x)} } +// NewBigIntFromString allocates and returns a new BigInt set to x +// interpreted in the provided base. +func NewBigIntFromString(x string, base int) *BigInt { + b, success := new(big.Int).SetString(x, base) + if !success { + return nil + } + return &BigInt{b} +} + // GetBytes returns the absolute value of x as a big-endian byte slice. func (bi *BigInt) GetBytes() []byte { return bi.bigint.Bytes() From 0ddd4612b7db34a6b23926f05f0dd5807d86c7ab Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 28 Sep 2020 14:14:45 +0200 Subject: [PATCH 054/948] core/vm, params: make 2200 in line with spec (#21605) --- core/vm/gas_table.go | 34 +++++++++++++++++----------------- params/protocol_params.go | 12 ++++-------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 6655f9bf42..01249a5388 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -163,18 +163,18 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi } // 0. If *gasleft* is less than or equal to 2300, fail the current call. -// 1. If current value equals new value (this is a no-op), SSTORE_NOOP_GAS gas is deducted. +// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted. // 2. If current value does not equal new value: // 2.1. If original value equals current value (this storage slot has not been changed by the current execution context): -// 2.1.1. If original value is 0, SSTORE_INIT_GAS gas is deducted. -// 2.1.2. Otherwise, SSTORE_CLEAN_GAS gas is deducted. If new value is 0, add SSTORE_CLEAR_REFUND to refund counter. -// 2.2. If original value does not equal current value (this storage slot is dirty), SSTORE_DIRTY_GAS gas is deducted. Apply both of the following clauses: +// 2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. +// 2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. +// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: // 2.2.1. If original value is not 0: -// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEAR_REFUND gas from refund counter. We can prove that refund counter will never go below 0. -// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEAR_REFUND gas to refund counter. +// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter. +// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter. // 2.2.2. If original value equals new value (this storage slot is reset): -// 2.2.2.1. If original value is 0, add SSTORE_INIT_REFUND to refund counter. -// 2.2.2.2. Otherwise, add SSTORE_CLEAN_REFUND gas to refund counter. +// 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. +// 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // If we fail the minimum gas availability invariant, fail (0) if contract.Gas <= params.SstoreSentryGasEIP2200 { @@ -188,33 +188,33 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return params.SstoreNoopGasEIP2200, nil + return params.SloadGasEIP2200, nil } original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return params.SstoreInitGasEIP2200, nil + return params.SstoreSetGasEIP2200, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } - return params.SstoreCleanGasEIP2200, nil // write existing slot (2.1.2) + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) } } if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - evm.StateDB.AddRefund(params.SstoreInitRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) } else { // reset to original existing slot (2.2.2.2) - evm.StateDB.AddRefund(params.SstoreCleanRefundEIP2200) + evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) } } - return params.SstoreDirtyGasEIP2200, nil // dirty update (2.2) + return params.SloadGasEIP2200, nil // dirty update (2.2) } func makeGasLog(n uint64) gasFunc { diff --git a/params/protocol_params.go b/params/protocol_params.go index eae935743c..fd5452bf15 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -52,14 +52,10 @@ const ( NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value - SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed - SstoreNoopGasEIP2200 uint64 = 800 // Once per SSTORE operation if the value doesn't change. - SstoreDirtyGasEIP2200 uint64 = 800 // Once per SSTORE operation if a dirty value is changed. - SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero - SstoreInitRefundEIP2200 uint64 = 19200 // Once per SSTORE operation for resetting to the original zero value - SstoreCleanGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else - SstoreCleanRefundEIP2200 uint64 = 4200 // Once per SSTORE operation for resetting to the original non-zero value - SstoreClearRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot + SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed + SstoreSetGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero + SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else + SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. From eebfb13053a06433408f3b7b43837e3b2c7a8145 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Mon, 28 Sep 2020 08:24:01 -0400 Subject: [PATCH 055/948] core: free pointer from slice after popping element from price heap (#21572) * Fix potential memory leak in price heap * core: nil free pointer slice (alternative version) Co-authored-by: Martin Holst Swende --- core/tx_list.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tx_list.go b/core/tx_list.go index bf304eedcf..cdd3df14c5 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -433,6 +433,7 @@ func (h *priceHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] + old[n-1] = nil *h = old[0 : n-1] return x } From a04294d1607d0526002770cdc93bed73ee78a3a5 Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Mon, 28 Sep 2020 20:28:38 +0800 Subject: [PATCH 056/948] internal/web3ext: improve eth_getBlockByNumber and eth_getBlockByHash console api (#21608) --- internal/web3ext/web3ext.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index e2b8ad1718..20e0ea11f5 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -532,12 +532,14 @@ web3._extend({ new web3._extend.Method({ name: 'getBlockByNumber', call: 'eth_getBlockByNumber', - params: 2 + params: 2, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, function (val) { return !!val; }] }), new web3._extend.Method({ name: 'getBlockByHash', call: 'eth_getBlockByHash', - params: 2 + params: 2, + inputFormatter: [null, function (val) { return !!val; }] }), new web3._extend.Method({ name: 'getRawTransaction', From b007df89ddc3d91610332f90af18092cd2793202 Mon Sep 17 00:00:00 2001 From: shigeyuki azuchi Date: Mon, 28 Sep 2020 21:30:10 +0900 Subject: [PATCH 057/948] light: fix wrong description in a comment (#21573) --- light/odr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/light/odr.go b/light/odr.go index 0b854b0b6c..7016ef8ef2 100644 --- a/light/odr.go +++ b/light/odr.go @@ -133,7 +133,7 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { } } -// ChtRequest is the ODR request type for state/storage trie entries +// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie type ChtRequest struct { Untrusted bool // Indicator whether the result retrieved is trusted or not PeerId string // The specified peer id from which to retrieve data. From 666092936cf766a88ad7eb972768537127c5adc9 Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Tue, 29 Sep 2020 02:10:11 +0800 Subject: [PATCH 058/948] p2p/enode: remove unused code (#21612) --- p2p/enode/node.go | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 3f6cda6d4a..c2429e0e8e 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" "math/bits" - "math/rand" "net" "strings" @@ -278,23 +277,3 @@ func LogDist(a, b ID) int { } return len(a)*8 - lz } - -// RandomID returns a random ID b such that logdist(a, b) == n. -func RandomID(a ID, n int) (b ID) { - if n == 0 { - return a - } - // flip bit at position n, fill the rest with random bits - b = a - pos := len(a) - n/8 - 1 - bit := byte(0x01) << (byte(n%8) - 1) - if bit == 0 { - pos++ - bit = 0x80 - } - b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits - for i := pos + 1; i < len(a); i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} From 901471f733dbd5f08fdf1523a16227afd96efcd9 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 28 Sep 2020 20:11:30 +0200 Subject: [PATCH 059/948] build: keep geth-sources.jar build result for JavaDoc (#21596) * ci: tooltips for javadoc for mobile app * f space --- Makefile | 4 +++- build/ci.go | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 67095f4d00..ecee5f1894 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,9 @@ android: $(GORUN) build/ci.go aar --local @echo "Done building." @echo "Import \"$(GOBIN)/geth.aar\" to use the library." - + @echo "Import \"$(GOBIN)/geth-sources.jar\" to add javadocs" + @echo "For more info see https://stackoverflow.com/questions/20994336/android-studio-how-to-attach-javadoc" + ios: $(GORUN) build/ci.go xcode --local @echo "Done building." diff --git a/build/ci.go b/build/ci.go index ea708d5e7b..ab153eb85e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -836,6 +836,7 @@ func doAndroidArchive(cmdline []string) { if *local { // If we're building locally, copy bundle to build dir and skip Maven os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar")) + os.Rename("geth-sources.jar", filepath.Join(GOBIN, "geth-sources.jar")) return } meta := newMavenMetadata(env) From 4ee11b072e8b3ebaa025354bb346e2ea9cc3b27c Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Tue, 29 Sep 2020 17:31:14 +0800 Subject: [PATCH 060/948] cmd/bootnode,internal/debug: fix some comments (#21623) --- cmd/bootnode/main.go | 2 +- internal/debug/api.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index f6e2a14c3b..6c9ff615a1 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -44,7 +44,7 @@ func main() { natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:)") netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)") runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode") - verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-9)") + verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-5)") vmodule = flag.String("vmodule", "", "log verbosity pattern") nodeKey *ecdsa.PrivateKey diff --git a/internal/debug/api.go b/internal/debug/api.go index 86a4218f6a..efd8626776 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -196,7 +196,7 @@ func (*HandlerT) Stacks() string { return buf.String() } -// FreeOSMemory returns unused memory to the OS. +// FreeOSMemory forces a garbage collection. func (*HandlerT) FreeOSMemory() { debug.FreeOSMemory() } From 6c8310ebb40d260811580d188e0a815ba314ff9d Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 29 Sep 2020 17:38:13 +0200 Subject: [PATCH 061/948] trie: use stacktrie for Derivesha operation (#21407) core/types: use stacktrie for derivesha trie: add stacktrie file trie: fix linter core/types: use stacktrie for derivesha rebased: adapt stacktrie to the newer version of DeriveSha Co-authored-by: Martin Holst Swende More linter fixes review feedback: no key offset for nodes converted to hashes trie: use EncodeRLP for full nodes core/types: insert txs in order in derivesha trie: tests for derivesha with stacktrie trie: make stacktrie use pooled hashers trie: make stacktrie reuse tmp slice space trie: minor polishes on stacktrie trie/stacktrie: less rlp dancing core/types: explain the contorsions in DeriveSha ci: fix goimport errors trie: clear mem on subtrie hashing squashme: linter fix stracktrie: use pooling, less allocs (#3) trie: in-place hex prefix, reduce allocs and add rawNode.EncodeRLP Reintroduce the `[]node` method, add the missing `EncodeRLP` implementation for `rawNode` and calculate the hex prefix in place. Co-authored-by: Martin Holst Swende Co-authored-by: Martin Holst Swende --- core/block_validator.go | 4 +- core/types/derive_sha.go | 18 +- eth/downloader/queue.go | 4 +- eth/handler.go | 2 +- trie/database.go | 7 +- trie/encoding.go | 29 +++ trie/encoding_test.go | 36 ++++ trie/stacktrie.go | 404 +++++++++++++++++++++++++++++++++++++++ trie/stacktrie_test.go | 242 +++++++++++++++++++++++ 9 files changed, 738 insertions(+), 8 deletions(-) create mode 100644 trie/stacktrie.go create mode 100644 trie/stacktrie_test.go diff --git a/core/block_validator.go b/core/block_validator.go index 8312093935..8dbd0f7552 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -62,7 +62,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash) } - if hash := types.DeriveSha(block.Transactions(), new(trie.Trie)); hash != header.TxHash { + if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash) } if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { @@ -90,7 +90,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) } // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) - receiptSha := types.DeriveSha(receipts, new(trie.Trie)) + receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) if receiptSha != header.ReceiptHash { return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) } diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go index 7d40c7f660..51b8506bce 100644 --- a/core/types/derive_sha.go +++ b/core/types/derive_sha.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// DerivableList is the interface which can derive the hash. type DerivableList interface { Len() int GetRlp(i int) []byte @@ -39,7 +38,22 @@ type Hasher interface { func DeriveSha(list DerivableList, hasher Hasher) common.Hash { hasher.Reset() keybuf := new(bytes.Buffer) - for i := 0; i < list.Len(); i++ { + + // StackTrie requires values to be inserted in increasing + // hash order, which is not the order that `list` provides + // hashes in. This insertion sequence ensures that the + // order is correct. + for i := 1; i < list.Len() && i <= 0x7f; i++ { + keybuf.Reset() + rlp.Encode(keybuf, uint(i)) + hasher.Update(keybuf.Bytes(), list.GetRlp(i)) + } + if list.Len() > 0 { + keybuf.Reset() + rlp.Encode(keybuf, uint(0)) + hasher.Update(keybuf.Bytes(), list.GetRlp(0)) + } + for i := 0x80; i < list.Len(); i++ { keybuf.Reset() rlp.Encode(keybuf, uint(i)) hasher.Update(keybuf.Bytes(), list.GetRlp(i)) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 745f7c7480..51a57f0d4c 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -774,7 +774,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi q.lock.Lock() defer q.lock.Unlock() validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Transactions(txLists[index]), new(trie.Trie)) != header.TxHash { + if types.DeriveSha(types.Transactions(txLists[index]), trie.NewStackTrie(nil)) != header.TxHash { return errInvalidBody } if types.CalcUncleHash(uncleLists[index]) != header.UncleHash { @@ -799,7 +799,7 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, q.lock.Lock() defer q.lock.Unlock() validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Receipts(receiptList[index]), new(trie.Trie)) != header.ReceiptHash { + if types.DeriveSha(types.Receipts(receiptList[index]), trie.NewStackTrie(nil)) != header.ReceiptHash { return errInvalidReceipt } return nil diff --git a/eth/handler.go b/eth/handler.go index f5ec2c4fbf..5b89986539 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -715,7 +715,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { log.Warn("Propagated block has invalid uncles", "have", hash, "exp", request.Block.UncleHash()) break // TODO(karalabe): return error eventually, but wait a few releases } - if hash := types.DeriveSha(request.Block.Transactions(), new(trie.Trie)); hash != request.Block.TxHash() { + if hash := types.DeriveSha(request.Block.Transactions(), trie.NewStackTrie(nil)); hash != request.Block.TxHash() { log.Warn("Propagated block has invalid body", "have", hash, "exp", request.Block.TxHash()) break // TODO(karalabe): return error eventually, but wait a few releases } diff --git a/trie/database.go b/trie/database.go index fa8906b7a3..c0c8870f8f 100644 --- a/trie/database.go +++ b/trie/database.go @@ -99,6 +99,11 @@ type rawNode []byte func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") } +func (n rawNode) EncodeRLP(w io.Writer) error { + _, err := w.Write([]byte(n)) + return err +} + // rawFullNode represents only the useful data content of a full node, with the // caches and flags stripped out to minimize its data storage. This type honors // the same RLP encoding as the original parent. @@ -199,7 +204,7 @@ func forGatherChildren(n node, onChild func(hash common.Hash)) { } case hashNode: onChild(common.BytesToHash(n)) - case valueNode, nil: + case valueNode, nil, rawNode: default: panic(fmt.Sprintf("unknown node type: %T", n)) } diff --git a/trie/encoding.go b/trie/encoding.go index 1955a3e664..8ee0022ef3 100644 --- a/trie/encoding.go +++ b/trie/encoding.go @@ -51,6 +51,35 @@ func hexToCompact(hex []byte) []byte { return buf } +// hexToCompactInPlace places the compact key in input buffer, returning the length +// needed for the representation +func hexToCompactInPlace(hex []byte) int { + var ( + hexLen = len(hex) // length of the hex input + firstByte = byte(0) + ) + // Check if we have a terminator there + if hexLen > 0 && hex[hexLen-1] == 16 { + firstByte = 1 << 5 + hexLen-- // last part was the terminator, ignore that + } + var ( + binLen = hexLen/2 + 1 + ni = 0 // index in hex + bi = 1 // index in bin (compact) + ) + if hexLen&1 == 1 { + firstByte |= 1 << 4 // odd flag + firstByte |= hex[0] // first nibble is contained in the first byte + ni++ + } + for ; ni < hexLen; bi, ni = bi+1, ni+2 { + hex[bi] = hex[ni]<<4 | hex[ni+1] + } + hex[0] = firstByte + return binLen +} + func compactToHex(compact []byte) []byte { if len(compact) == 0 { return compact diff --git a/trie/encoding_test.go b/trie/encoding_test.go index 97d8da1361..16393313f7 100644 --- a/trie/encoding_test.go +++ b/trie/encoding_test.go @@ -18,6 +18,8 @@ package trie import ( "bytes" + "encoding/hex" + "math/rand" "testing" ) @@ -75,6 +77,40 @@ func TestHexKeybytes(t *testing.T) { } } +func TestHexToCompactInPlace(t *testing.T) { + for i, keyS := range []string{ + "00", + "060a040c0f000a090b040803010801010900080d090a0a0d0903000b10", + "10", + } { + hexBytes, _ := hex.DecodeString(keyS) + exp := hexToCompact(hexBytes) + sz := hexToCompactInPlace(hexBytes) + got := hexBytes[:sz] + if !bytes.Equal(exp, got) { + t.Fatalf("test %d: encoding err\ninp %v\ngot %x\nexp %x\n", i, keyS, got, exp) + } + } +} + +func TestHexToCompactInPlaceRandom(t *testing.T) { + for i := 0; i < 10000; i++ { + l := rand.Intn(128) + key := make([]byte, l) + rand.Read(key) + hexBytes := keybytesToHex(key) + hexOrig := []byte(string(hexBytes)) + exp := hexToCompact(hexBytes) + sz := hexToCompactInPlace(hexBytes) + got := hexBytes[:sz] + + if !bytes.Equal(exp, got) { + t.Fatalf("encoding err \ncpt %x\nhex %x\ngot %x\nexp %x\n", + key, hexOrig, got, exp) + } + } +} + func BenchmarkHexToCompact(b *testing.B) { testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} for i := 0; i < b.N; i++ { diff --git a/trie/stacktrie.go b/trie/stacktrie.go new file mode 100644 index 0000000000..fc653101ae --- /dev/null +++ b/trie/stacktrie.go @@ -0,0 +1,404 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +var stPool = sync.Pool{ + New: func() interface{} { + return NewStackTrie(nil) + }, +} + +func stackTrieFromPool(db ethdb.KeyValueStore) *StackTrie { + st := stPool.Get().(*StackTrie) + st.db = db + return st +} + +func returnToPool(st *StackTrie) { + st.Reset() + stPool.Put(st) +} + +// StackTrie is a trie implementation that expects keys to be inserted +// in order. Once it determines that a subtree will no longer be inserted +// into, it will hash it and free up the memory it uses. +type StackTrie struct { + nodeType uint8 // node type (as in branch, ext, leaf) + val []byte // value contained by this node if it's a leaf + key []byte // key chunk covered by this (full|ext) node + keyOffset int // offset of the key chunk inside a full key + children [16]*StackTrie // list of children (for fullnodes and exts) + + db ethdb.KeyValueStore // Pointer to the commit db, can be nil +} + +// NewStackTrie allocates and initializes an empty trie. +func NewStackTrie(db ethdb.KeyValueStore) *StackTrie { + return &StackTrie{ + nodeType: emptyNode, + db: db, + } +} + +func newLeaf(ko int, key, val []byte, db ethdb.KeyValueStore) *StackTrie { + st := stackTrieFromPool(db) + st.nodeType = leafNode + st.keyOffset = ko + st.key = append(st.key, key[ko:]...) + st.val = val + return st +} + +func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueStore) *StackTrie { + st := stackTrieFromPool(db) + st.nodeType = extNode + st.keyOffset = ko + st.key = append(st.key, key[ko:]...) + st.children[0] = child + return st +} + +// List all values that StackTrie#nodeType can hold +const ( + emptyNode = iota + branchNode + extNode + leafNode + hashedNode +) + +// TryUpdate inserts a (key, value) pair into the stack trie +func (st *StackTrie) TryUpdate(key, value []byte) error { + k := keybytesToHex(key) + if len(value) == 0 { + panic("deletion not supported") + } + st.insert(k[:len(k)-1], value) + return nil +} + +func (st *StackTrie) Update(key, value []byte) { + if err := st.TryUpdate(key, value); err != nil { + log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) + } +} + +func (st *StackTrie) Reset() { + st.db = nil + st.key = st.key[:0] + st.val = st.val[:0] + for i := range st.children { + st.children[i] = nil + } + st.nodeType = emptyNode + st.keyOffset = 0 +} + +// Helper function that, given a full key, determines the index +// at which the chunk pointed by st.keyOffset is different from +// the same chunk in the full key. +func (st *StackTrie) getDiffIndex(key []byte) int { + diffindex := 0 + for ; diffindex < len(st.key) && st.key[diffindex] == key[st.keyOffset+diffindex]; diffindex++ { + } + return diffindex +} + +// Helper function to that inserts a (key, value) pair into +// the trie. +func (st *StackTrie) insert(key, value []byte) { + switch st.nodeType { + case branchNode: /* Branch */ + idx := int(key[st.keyOffset]) + // Unresolve elder siblings + for i := idx - 1; i >= 0; i-- { + if st.children[i] != nil { + if st.children[i].nodeType != hashedNode { + st.children[i].hash() + } + break + } + } + // Add new child + if st.children[idx] == nil { + st.children[idx] = stackTrieFromPool(st.db) + st.children[idx].keyOffset = st.keyOffset + 1 + } + st.children[idx].insert(key, value) + case extNode: /* Ext */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Check if chunks are identical. If so, recurse into + // the child node. Otherwise, the key has to be split + // into 1) an optional common prefix, 2) the fullnode + // representing the two differing path, and 3) a leaf + // for each of the differentiated subtrees. + if diffidx == len(st.key) { + // Ext key and key segment are identical, recurse into + // the child node. + st.children[0].insert(key, value) + return + } + // Save the original part. Depending if the break is + // at the extension's last byte or not, create an + // intermediate extension or use the extension's child + // node directly. + var n *StackTrie + if diffidx < len(st.key)-1 { + n = newExt(diffidx+1, st.key, st.children[0], st.db) + } else { + // Break on the last byte, no need to insert + // an extension node: reuse the current node + n = st.children[0] + } + // Convert to hash + n.hash() + var p *StackTrie + if diffidx == 0 { + // the break is on the first byte, so + // the current node is converted into + // a branch node. + st.children[0] = nil + p = st + st.nodeType = branchNode + } else { + // the common prefix is at least one byte + // long, insert a new intermediate branch + // node. + st.children[0] = stackTrieFromPool(st.db) + st.children[0].nodeType = branchNode + st.children[0].keyOffset = st.keyOffset + diffidx + p = st.children[0] + } + // Create a leaf for the inserted part + o := newLeaf(st.keyOffset+diffidx+1, key, value, st.db) + + // Insert both child leaves where they belong: + origIdx := st.key[diffidx] + newIdx := key[diffidx+st.keyOffset] + p.children[origIdx] = n + p.children[newIdx] = o + st.key = st.key[:diffidx] + + case leafNode: /* Leaf */ + // Compare both key chunks and see where they differ + diffidx := st.getDiffIndex(key) + + // Overwriting a key isn't supported, which means that + // the current leaf is expected to be split into 1) an + // optional extension for the common prefix of these 2 + // keys, 2) a fullnode selecting the path on which the + // keys differ, and 3) one leaf for the differentiated + // component of each key. + if diffidx >= len(st.key) { + panic("Trying to insert into existing key") + } + + // Check if the split occurs at the first nibble of the + // chunk. In that case, no prefix extnode is necessary. + // Otherwise, create that + var p *StackTrie + if diffidx == 0 { + // Convert current leaf into a branch + st.nodeType = branchNode + p = st + st.children[0] = nil + } else { + // Convert current node into an ext, + // and insert a child branch node. + st.nodeType = extNode + st.children[0] = NewStackTrie(st.db) + st.children[0].nodeType = branchNode + st.children[0].keyOffset = st.keyOffset + diffidx + p = st.children[0] + } + + // Create the two child leaves: the one containing the + // original value and the one containing the new value + // The child leave will be hashed directly in order to + // free up some memory. + origIdx := st.key[diffidx] + p.children[origIdx] = newLeaf(diffidx+1, st.key, st.val, st.db) + p.children[origIdx].hash() + + newIdx := key[diffidx+st.keyOffset] + p.children[newIdx] = newLeaf(p.keyOffset+1, key, value, st.db) + + // Finally, cut off the key part that has been passed + // over to the children. + st.key = st.key[:diffidx] + st.val = nil + case emptyNode: /* Empty */ + st.nodeType = leafNode + st.key = key[st.keyOffset:] + st.val = value + case hashedNode: + panic("trying to insert into hash") + default: + panic("invalid type") + } +} + +// hash() hashes the node 'st' and converts it into 'hashedNode', if possible. +// Possible outcomes: +// 1. The rlp-encoded value was >= 32 bytes: +// - Then the 32-byte `hash` will be accessible in `st.val`. +// - And the 'st.type' will be 'hashedNode' +// 2. The rlp-encoded value was < 32 bytes +// - Then the <32 byte rlp-encoded value will be accessible in 'st.val'. +// - And the 'st.type' will be 'hashedNode' AGAIN +// +// This method will also: +// set 'st.type' to hashedNode +// clear 'st.key' +func (st *StackTrie) hash() { + /* Shortcut if node is already hashed */ + if st.nodeType == hashedNode { + return + } + // The 'hasher' is taken from a pool, but we don't actually + // claim an instance until all children are done with their hashing, + // and we actually need one + var h *hasher + + switch st.nodeType { + case branchNode: + var nodes [17]node + for i, child := range st.children { + if child == nil { + nodes[i] = nilValueNode + continue + } + child.hash() + if len(child.val) < 32 { + nodes[i] = rawNode(child.val) + } else { + nodes[i] = hashNode(child.val) + } + st.children[i] = nil // Reclaim mem from subtree + returnToPool(child) + } + nodes[16] = nilValueNode + h = newHasher(false) + defer returnHasherToPool(h) + h.tmp.Reset() + if err := rlp.Encode(&h.tmp, nodes); err != nil { + panic(err) + } + case extNode: + h = newHasher(false) + defer returnHasherToPool(h) + h.tmp.Reset() + st.children[0].hash() + // This is also possible: + //sz := hexToCompactInPlace(st.key) + //n := [][]byte{ + // st.key[:sz], + // st.children[0].val, + //} + n := [][]byte{ + hexToCompact(st.key), + st.children[0].val, + } + if err := rlp.Encode(&h.tmp, n); err != nil { + panic(err) + } + returnToPool(st.children[0]) + st.children[0] = nil // Reclaim mem from subtree + case leafNode: + h = newHasher(false) + defer returnHasherToPool(h) + h.tmp.Reset() + st.key = append(st.key, byte(16)) + sz := hexToCompactInPlace(st.key) + n := [][]byte{st.key[:sz], st.val} + if err := rlp.Encode(&h.tmp, n); err != nil { + panic(err) + } + case emptyNode: + st.val = st.val[:0] + st.val = append(st.val, emptyRoot[:]...) + st.key = st.key[:0] + st.nodeType = hashedNode + return + default: + panic("Invalid node type") + } + st.key = st.key[:0] + st.nodeType = hashedNode + if len(h.tmp) < 32 { + st.val = st.val[:0] + st.val = append(st.val, h.tmp...) + return + } + // Going to write the hash to the 'val'. Need to ensure it's properly sized first + // Typically, 'branchNode's will have no 'val', and require this allocation + if required := 32 - len(st.val); required > 0 { + buf := make([]byte, required) + st.val = append(st.val, buf...) + } + st.val = st.val[:32] + h.sha.Reset() + h.sha.Write(h.tmp) + h.sha.Read(st.val) + if st.db != nil { + // TODO! Is it safe to Put the slice here? + // Do all db implementations copy the value provided? + st.db.Put(st.val, h.tmp) + } +} + +// Hash returns the hash of the current node +func (st *StackTrie) Hash() (h common.Hash) { + st.hash() + if len(st.val) != 32 { + // If the node's RLP isn't 32 bytes long, the node will not + // be hashed, and instead contain the rlp-encoding of the + // node. For the top level node, we need to force the hashing. + ret := make([]byte, 32) + h := newHasher(false) + defer returnHasherToPool(h) + h.sha.Reset() + h.sha.Write(st.val) + h.sha.Read(ret) + return common.BytesToHash(ret) + } + return common.BytesToHash(st.val) +} + +// Commit will commit the current node to database db +func (st *StackTrie) Commit(db ethdb.KeyValueStore) common.Hash { + oldDb := st.db + st.db = db + defer func() { + st.db = oldDb + }() + st.hash() + h := common.BytesToHash(st.val) + return h +} diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go new file mode 100644 index 0000000000..26e3bade27 --- /dev/null +++ b/trie/stacktrie_test.go @@ -0,0 +1,242 @@ +package trie + +import ( + "bytes" + "fmt" + "math/big" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +func TestSizeBug(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + + leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + + nt.TryUpdate(leaf, value) + st.TryUpdate(leaf, value) + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func TestEmptyBug(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + + //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + kvs := []struct { + K string + V string + }{ + {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "9496f4ec2bf9dab484cac6be589e8417d84781be08"}, + {K: "40edb63a35fcf86c08022722aa3287cdd36440d671b4918131b2514795fefa9c", V: "01"}, + {K: "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", V: "947a30f7736e48d6599356464ba4c150d8da0302ff"}, + {K: "c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b", V: "02"}, + } + + for _, kv := range kvs { + nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + } + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func TestValLength56(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + + //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") + kvs := []struct { + K string + V string + }{ + {K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"}, + } + + for _, kv := range kvs { + nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + } + + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +func genTxs(num uint64) (types.Transactions, error) { + key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + if err != nil { + return nil, err + } + var addr = crypto.PubkeyToAddress(key.PublicKey) + newTx := func(i uint64) (*types.Transaction, error) { + signer := types.NewEIP155Signer(big.NewInt(18)) + tx, err := types.SignTx(types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil), signer, key) + return tx, err + } + var txs types.Transactions + for i := uint64(0); i < num; i++ { + tx, err := newTx(i) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + return txs, nil +} + +func TestDeriveSha(t *testing.T) { + txs, err := genTxs(0) + if err != nil { + t.Fatal(err) + } + for len(txs) < 1000 { + exp := types.DeriveSha(txs, newEmpty()) + got := types.DeriveSha(txs, NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) + } + newTxs, err := genTxs(uint64(len(txs) + 1)) + if err != nil { + t.Fatal(err) + } + txs = append(txs, newTxs...) + } +} + +func BenchmarkDeriveSha200(b *testing.B) { + txs, err := genTxs(200) + if err != nil { + b.Fatal(err) + } + var exp common.Hash + var got common.Hash + b.Run("std_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + exp = types.DeriveSha(txs, newEmpty()) + } + }) + + b.Run("stack_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got = types.DeriveSha(txs, NewStackTrie(nil)) + } + }) + if got != exp { + b.Errorf("got %x exp %x", got, exp) + } +} + +type dummyDerivableList struct { + len int + seed int +} + +func newDummy(seed int) *dummyDerivableList { + d := &dummyDerivableList{} + src := mrand.NewSource(int64(seed)) + // don't use lists longer than 4K items + d.len = int(src.Int63() & 0x0FFF) + d.seed = seed + return d +} + +func (d *dummyDerivableList) Len() int { + return d.len +} + +func (d *dummyDerivableList) GetRlp(i int) []byte { + src := mrand.NewSource(int64(d.seed + i)) + // max item size 256, at least 1 byte per item + size := 1 + src.Int63()&0x00FF + data := make([]byte, size) + _, err := mrand.New(src).Read(data) + if err != nil { + panic(err) + } + return data +} + +func printList(l types.DerivableList) { + fmt.Printf("list length: %d\n", l.Len()) + fmt.Printf("{\n") + for i := 0; i < l.Len(); i++ { + v := l.GetRlp(i) + fmt.Printf("\"0x%x\",\n", v) + } + fmt.Printf("},\n") +} + +func TestFuzzDeriveSha(t *testing.T) { + // increase this for longer runs -- it's set to quite low for travis + rndSeed := mrand.Int() + for i := 0; i < 10; i++ { + seed := rndSeed + i + exp := types.DeriveSha(newDummy(i), newEmpty()) + got := types.DeriveSha(newDummy(i), NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + printList(newDummy(seed)) + t.Fatalf("seed %d: got %x exp %x", seed, got, exp) + } + } +} + +type flatList struct { + rlpvals []string +} + +func newFlatList(rlpvals []string) *flatList { + return &flatList{rlpvals} +} +func (f *flatList) Len() int { + return len(f.rlpvals) +} +func (f *flatList) GetRlp(i int) []byte { + return hexutil.MustDecode(f.rlpvals[i]) +} + +// TestDerivableList contains testcases found via fuzzing +func TestDerivableList(t *testing.T) { + type tcase []string + tcs := []tcase{ + { + "0xc041", + }, + { + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + }, + { + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", + "0x14abd5c47c0be87b0454596baad2", + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + }, + } + for i, tc := range tcs[1:] { + exp := types.DeriveSha(newFlatList(tc), newEmpty()) + got := types.DeriveSha(newFlatList(tc), NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("case %d: got %x exp %x", i, got, exp) + } + } +} From dad26582b6ec5e3870a27db9f25ad1743ae00cbf Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 29 Sep 2020 17:40:08 +0200 Subject: [PATCH 062/948] accounts, signer: implement gnosis safe support (#21593) * accounts, signer: implement gnosis safe support * common/math: add type for marshalling big to dec * accounts, signer: properly sign gnosis requests * signer, clef: implement account_signGnosisTx * signer: fix auditlog print, change rpc-name (signGnosisTx to signGnosisSafeTx) * signer: pass validation-messages/warnings to the UI for gnonsis-safe txs * signer/core: minor change to validationmessages of typed data --- cmd/clef/extapi_changelog.md | 58 ++++++++++++++++ common/math/big.go | 34 ++++++++++ signer/core/api.go | 32 ++++++++- signer/core/auditlog.go | 22 +++++- signer/core/cliui.go | 7 ++ signer/core/gnosis_safe.go | 91 +++++++++++++++++++++++++ signer/core/signed_data.go | 51 ++++++++++---- signer/core/signed_data_test.go | 117 ++++++++++++++++++++++++++++++++ 8 files changed, 396 insertions(+), 16 deletions(-) create mode 100644 signer/core/gnosis_safe.go diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index dbc302631b..31554f0790 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -10,6 +10,64 @@ TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. +### 6.1.0 + +The API-method `account_signGnosisSafeTx` was added. This method takes two parameters, +`[address, safeTx]`. The latter, `safeTx`, can be copy-pasted from the gnosis relay. For example: + +``` +{ + "jsonrpc": "2.0", + "method": "account_signGnosisSafeTx", + "params": ["0xfd1c4226bfD1c436672092F4eCbfC270145b7256", + { + "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3", + "to": "0xB372a646f7F05Cc1785018dBDA7EBc734a2A20E2", + "value": "20000000000000000", + "data": null, + "operation": 0, + "gasToken": "0x0000000000000000000000000000000000000000", + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 2, + "executionDate": null, + "submissionDate": "2020-09-15T21:54:49.617634Z", + "modified": "2020-09-15T21:54:49.617634Z", + "blockNumber": null, + "transactionHash": null, + "safeTxHash": "0x2edfbd5bc113ff18c0631595db32eb17182872d88d9bf8ee4d8c2dd5db6d95e2", + "executor": null, + "isExecuted": false, + "isSuccessful": null, + "ethGasPrice": null, + "gasUsed": null, + "fee": null, + "origin": null, + "dataDecoded": null, + "confirmationsRequired": null, + "confirmations": [ + { + "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", + "submissionDate": "2020-09-15T21:54:49.663299Z", + "transactionHash": null, + "confirmationType": "CONFIRMATION", + "signature": "0x95a7250bb645f831c86defc847350e7faff815b2fb586282568e96cc859e39315876db20a2eed5f7a0412906ec5ab57652a6f645ad4833f345bda059b9da2b821c", + "signatureType": "EOA" + } + ], + "signatures": null + } + ], + "id": 67 +} +``` + +Not all fields are required, though. This method is really just a UX helper, which massages the +input to conform to the `EIP-712` [specification](https://docs.gnosis.io/safe/docs/contracts_tx_execution/#transaction-hash) +for the Gnosis Safe, and making the output be directly importable to by a relay service. + ### 6.0.0 diff --git a/common/math/big.go b/common/math/big.go index 17a57df9dc..1af5b4d879 100644 --- a/common/math/big.go +++ b/common/math/big.go @@ -67,6 +67,40 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil } +// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling, +// it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal) +type Decimal256 big.Int + +// NewHexOrDecimal256 creates a new Decimal256 +func NewDecimal256(x int64) *Decimal256 { + b := big.NewInt(x) + d := Decimal256(*b) + return &d +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (i *Decimal256) UnmarshalText(input []byte) error { + bigint, ok := ParseBig256(string(input)) + if !ok { + return fmt.Errorf("invalid hex or decimal integer %q", input) + } + *i = Decimal256(*bigint) + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (i *Decimal256) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// String implements Stringer. +func (i *Decimal256) String() string { + if i == nil { + return "0" + } + return fmt.Sprintf("%#d", (*big.Int)(i)) +} + // ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax. // Leading zeros are accepted. The empty string parses as zero. func ParseBig256(s string) (*big.Int, bool) { diff --git a/signer/core/api.go b/signer/core/api.go index 3817345c8f..6b7d18dfde 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -41,7 +41,7 @@ const ( // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive numberOfAccountsToDerive = 10 // ExternalAPIVersion -- see extapi_changelog.md - ExternalAPIVersion = "6.0.0" + ExternalAPIVersion = "6.1.0" // InternalAPIVersion -- see intapi_changelog.md InternalAPIVersion = "7.0.1" ) @@ -62,6 +62,8 @@ type ExternalAPI interface { EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Version info about the APIs Version(ctx context.Context) (string, error) + // SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction + SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) } // UIClientAPI specifies what method a UI needs to implement to be able to be used as a @@ -234,6 +236,7 @@ type ( Address common.MixedcaseAddress `json:"address"` Rawdata []byte `json:"raw_data"` Messages []*NameValueType `json:"messages"` + Callinfo []ValidationInfo `json:"call_info"` Hash hexutil.Bytes `json:"hash"` Meta Metadata `json:"meta"` } @@ -581,6 +584,33 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } +func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { + // Do the usual validations, but on the last-stage transaction + args := gnosisTx.ArgsForValidation() + msgs, err := api.validator.ValidateTransaction(methodSelector, args) + if err != nil { + return nil, err + } + // If we are in 'rejectMode', then reject rather than show the user warnings + if api.rejectMode { + if err := msgs.getWarnings(); err != nil { + return nil, err + } + } + typedData := gnosisTx.ToTypedData() + signature, preimage, err := api.signTypedData(ctx, signerAddress, typedData, msgs) + if err != nil { + return nil, err + } + checkSummedSender, _ := common.NewMixedcaseAddressFromString(signerAddress.Address().Hex()) + + gnosisTx.Signature = signature + gnosisTx.SafeTxHash = common.BytesToHash(preimage) + gnosisTx.Sender = *checkSummedSender // Must be checksumed to be accepted by relay + + return &gnosisTx, nil +} + // Returns the external api version. This method does not require user acceptance. Available methods are // available via enumeration anyway, and this info does not contain user-specific data func (api *SignerAPI) Version(ctx context.Context) (string, error) { diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index 1092e7a923..bda88a8b2e 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -18,6 +18,7 @@ package core import ( "context" + "encoding/json" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -61,13 +62,32 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth } func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) { + marshalledData, _ := json.Marshal(data) // can ignore error, marshalling what we just unmarshalled l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(), - "addr", addr.String(), "data", data, "content-type", contentType) + "addr", addr.String(), "data", marshalledData, "content-type", contentType) b, e := l.api.SignData(ctx, contentType, addr, data) l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e) return b, e } +func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) { + sel := "" + if methodSelector != nil { + sel = *methodSelector + } + data, _ := json.Marshal(gnosisTx) // can ignore error, marshalling what we just unmarshalled + l.log.Info("SignGnosisSafeTx", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", string(data), "selector", sel) + res, e := l.api.SignGnosisSafeTx(ctx, addr, gnosisTx, methodSelector) + if res != nil { + data, _ := json.Marshal(res) // can ignore error, marshalling what we just unmarshalled + l.log.Info("SignGnosisSafeTx", "type", "response", "data", string(data), "error", e) + } else { + l.log.Info("SignGnosisSafeTx", "type", "response", "data", res, "error", e) + } + return res, e +} + func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(), "addr", addr.String(), "data", data) diff --git a/signer/core/cliui.go b/signer/core/cliui.go index 27a2f71aaa..cbfb56c9df 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -148,6 +148,13 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("Account: %s\n", request.Address.String()) + if len(request.Callinfo) != 0 { + fmt.Printf("\nValidation messages:\n") + for _, m := range request.Callinfo { + fmt.Printf(" * %s : %s\n", m.Typ, m.Message) + } + fmt.Println() + } fmt.Printf("messages:\n") for _, nvt := range request.Messages { fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1))) diff --git a/signer/core/gnosis_safe.go b/signer/core/gnosis_safe.go new file mode 100644 index 0000000000..e4385f9dc3 --- /dev/null +++ b/signer/core/gnosis_safe.go @@ -0,0 +1,91 @@ +package core + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" +) + +// GnosisSafeTx is a type to parse the safe-tx returned by the relayer, +// it also conforms to the API required by the Gnosis Safe tx relay service. +// See 'SafeMultisigTransaction' on https://safe-transaction.mainnet.gnosis.io/ +type GnosisSafeTx struct { + // These fields are only used on output + Signature hexutil.Bytes `json:"signature"` + SafeTxHash common.Hash `json:"contractTransactionHash"` + Sender common.MixedcaseAddress `json:"sender"` + // These fields are used both on input and output + Safe common.MixedcaseAddress `json:"safe"` + To common.MixedcaseAddress `json:"to"` + Value math.Decimal256 `json:"value"` + GasPrice math.Decimal256 `json:"gasPrice"` + Data *hexutil.Bytes `json:"data"` + Operation uint8 `json:"operation"` + GasToken common.Address `json:"gasToken"` + RefundReceiver common.Address `json:"refundReceiver"` + BaseGas big.Int `json:"baseGas"` + SafeTxGas big.Int `json:"safeTxGas"` + Nonce big.Int `json:"nonce"` + InputExpHash common.Hash `json:"safeTxHash"` +} + +// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing +func (tx *GnosisSafeTx) ToTypedData() TypedData { + var data hexutil.Bytes + if tx.Data != nil { + data = *tx.Data + } + gnosisTypedData := TypedData{ + Types: Types{ + "EIP712Domain": []Type{{Name: "verifyingContract", Type: "address"}}, + "SafeTx": []Type{ + {Name: "to", Type: "address"}, + {Name: "value", Type: "uint256"}, + {Name: "data", Type: "bytes"}, + {Name: "operation", Type: "uint8"}, + {Name: "safeTxGas", Type: "uint256"}, + {Name: "baseGas", Type: "uint256"}, + {Name: "gasPrice", Type: "uint256"}, + {Name: "gasToken", Type: "address"}, + {Name: "refundReceiver", Type: "address"}, + {Name: "nonce", Type: "uint256"}, + }, + }, + Domain: TypedDataDomain{ + VerifyingContract: tx.Safe.Address().Hex(), + }, + PrimaryType: "SafeTx", + Message: TypedDataMessage{ + "to": tx.To.Address().Hex(), + "value": tx.Value.String(), + "data": data, + "operation": fmt.Sprintf("%d", tx.Operation), + "safeTxGas": fmt.Sprintf("%#d", &tx.SafeTxGas), + "baseGas": fmt.Sprintf("%#d", &tx.BaseGas), + "gasPrice": tx.GasPrice.String(), + "gasToken": tx.GasToken.Hex(), + "refundReceiver": tx.RefundReceiver.Hex(), + "nonce": fmt.Sprintf("%d", tx.Nonce.Uint64()), + }, + } + return gnosisTypedData +} + +// ArgsForValidation returns a SendTxArgs struct, which can be used for the +// common validations, e.g. look up 4byte destinations +func (tx *GnosisSafeTx) ArgsForValidation() *SendTxArgs { + args := &SendTxArgs{ + From: tx.Safe, + To: &tx.To, + Gas: hexutil.Uint64(tx.SafeTxGas.Uint64()), + GasPrice: hexutil.Big(tx.GasPrice), + Value: hexutil.Big(tx.Value), + Nonce: hexutil.Uint64(tx.Nonce.Uint64()), + Data: tx.Data, + Input: nil, + } + return args +} diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 7fc66b4b74..19377a521b 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -125,7 +125,7 @@ var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`) // // Note, the produced signature conforms to the secp256k1 curve R, S and V values, // where the V value will be 27 or 28 for legacy reasons, if legacyV==true. -func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { +func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) { // We make the request prior to looking up if we actually have the account, to prevent // account-enumeration via the API res, err := api.UI.ApproveSignData(req) @@ -136,7 +136,7 @@ func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, l return nil, ErrRequestDenied } // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} + account := accounts.Account{Address: req.Address.Address()} wallet, err := api.am.Find(account) if err != nil { return nil, err @@ -167,7 +167,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com if err != nil { return nil, err } - signature, err := api.sign(addr, req, transformV) + signature, err := api.sign(req, transformV) if err != nil { api.UI.ShowError(err.Error()) return nil, err @@ -312,28 +312,47 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error) // SignTypedData signs EIP-712 conformant typed data // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") +// It returns +// - the signature, +// - and/or any error func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { + signature, _, err := api.signTypedData(ctx, addr, typedData, nil) + return signature, err +} + +// signTypedData is identical to the capitalized version, except that it also returns the hash (preimage) +// - the signature preimage (hash) +func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress, + typedData TypedData, validationMessages *ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) { domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) if err != nil { - return nil, err + return nil, nil, err } typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { - return nil, err + return nil, nil, err } rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) sighash := crypto.Keccak256(rawData) messages, err := typedData.Format() if err != nil { - return nil, err - } - req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Messages: messages, Hash: sighash} - signature, err := api.sign(addr, req, true) + return nil, nil, err + } + req := &SignDataRequest{ + ContentType: DataTyped.Mime, + Rawdata: rawData, + Messages: messages, + Hash: sighash, + Address: addr} + if validationMessages != nil { + req.Callinfo = validationMessages.Messages + } + signature, err := api.sign(req, true) if err != nil { api.UI.ShowError(err.Error()) - return nil, err + return nil, nil, err } - return signature, nil + return signature, sighash, nil } // HashStruct generates a keccak256 hash of the encoding of the provided data @@ -420,8 +439,8 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter buffer := bytes.Buffer{} // Verify extra data - if len(typedData.Types[primaryType]) < len(data) { - return nil, errors.New("there is extra data provided in the message") + if exp, got := len(typedData.Types[primaryType]), len(data); exp < got { + return nil, fmt.Errorf("there is extra data provided in the message (%d < %d)", exp, got) } // Add typehash @@ -834,7 +853,11 @@ func (nvt *NameValueType) Pprint(depth int) string { output.WriteString(sublevel) } } else { - output.WriteString(fmt.Sprintf("%q\n", nvt.Value)) + if nvt.Value != nil { + output.WriteString(fmt.Sprintf("%q\n", nvt.Value)) + } else { + output.WriteString("\n") + } } return output.String() } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index ab5f2cc962..23b7b9897b 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -17,6 +17,7 @@ package core_test import ( + "bytes" "context" "encoding/json" "fmt" @@ -414,3 +415,119 @@ func TestFuzzerFiles(t *testing.T) { typedData.Format() } } + +var gnosisTypedData = ` +{ + "types": { + "EIP712Domain": [ + { "type": "address", "name": "verifyingContract" } + ], + "SafeTx": [ + { "type": "address", "name": "to" }, + { "type": "uint256", "name": "value" }, + { "type": "bytes", "name": "data" }, + { "type": "uint8", "name": "operation" }, + { "type": "uint256", "name": "safeTxGas" }, + { "type": "uint256", "name": "baseGas" }, + { "type": "uint256", "name": "gasPrice" }, + { "type": "address", "name": "gasToken" }, + { "type": "address", "name": "refundReceiver" }, + { "type": "uint256", "name": "nonce" } + ] + }, + "domain": { + "verifyingContract": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3" + }, + "primaryType": "SafeTx", + "message": { + "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D", + "value": "20000000000000000", + "data": "0x", + "operation": 0, + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "gasToken": "0x0000000000000000000000000000000000000000", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 3 + } +}` + +var gnosisTx = ` +{ + "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3", + "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D", + "value": "20000000000000000", + "data": null, + "operation": 0, + "gasToken": "0x0000000000000000000000000000000000000000", + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 3, + "executionDate": null, + "submissionDate": "2020-09-15T21:59:23.815748Z", + "modified": "2020-09-15T21:59:23.815748Z", + "blockNumber": null, + "transactionHash": null, + "safeTxHash": "0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f", + "executor": null, + "isExecuted": false, + "isSuccessful": null, + "ethGasPrice": null, + "gasUsed": null, + "fee": null, + "origin": null, + "dataDecoded": null, + "confirmationsRequired": null, + "confirmations": [ + { + "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", + "submissionDate": "2020-09-15T21:59:28.281243Z", + "transactionHash": null, + "confirmationType": "CONFIRMATION", + "signature": "0x5e562065a0cb15d766dac0cd49eb6d196a41183af302c4ecad45f1a81958d7797753f04424a9b0aa1cb0448e4ec8e189540fbcdda7530ef9b9d95dfc2d36cb521b", + "signatureType": "EOA" + } + ], + "signatures": null + } +` + +// TestGnosisTypedData tests the scenario where a user submits a full EIP-712 +// struct without using the gnosis-specific endpoint +func TestGnosisTypedData(t *testing.T) { + var td core.TypedData + err := json.Unmarshal([]byte(gnosisTypedData), &td) + if err != nil { + t.Fatalf("unmarshalling failed '%v'", err) + } + _, sighash, err := sign(td) + if err != nil { + t.Fatal(err) + } + expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f") + if !bytes.Equal(expSigHash, sighash) { + t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) + } +} + +// TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe +// specific data, and we fill the TypedData struct on our side +func TestGnosisCustomData(t *testing.T) { + var tx core.GnosisSafeTx + err := json.Unmarshal([]byte(gnosisTx), &tx) + if err != nil { + t.Fatal(err) + } + var td = tx.ToTypedData() + _, sighash, err := sign(td) + if err != nil { + t.Fatal(err) + } + expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f") + if !bytes.Equal(expSigHash, sighash) { + t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash) + } +} From 053ed9cc847647a9b3ef707d0efe7104c4ab2a4c Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 30 Sep 2020 19:45:56 +0800 Subject: [PATCH 063/948] trie: polishes to trie committer (#21351) * trie: update tests to check commit integrity * trie: polish committer * trie: fix typo * trie: remove hasvalue notion According to the benchmarks, type assertion between the pointer and interface is extremely fast. BenchmarkIntmethod-12 1000000000 1.91 ns/op BenchmarkInterface-12 1000000000 2.13 ns/op BenchmarkTypeSwitch-12 1000000000 1.81 ns/op BenchmarkTypeAssertion-12 2000000000 1.78 ns/op So the overhead for asserting whether the shortnode has "valuenode" child is super tiny. No necessary to have another field. * trie: linter nitpicks Co-authored-by: Martin Holst Swende --- trie/committer.go | 118 +++++++++++++++++++--------------------- trie/hasher.go | 4 +- trie/trie.go | 5 +- trie/trie_test.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 65 deletions(-) diff --git a/trie/committer.go b/trie/committer.go index fc8b7ceda5..20c95bed08 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" ) @@ -33,10 +32,9 @@ const leafChanSize = 200 // leaf represents a trie leaf value type leaf struct { - size int // size of the rlp data (estimate) - hash common.Hash // hash of rlp data - node node // the node to commit - vnodes bool // set to true if the node (possibly) contains a valueNode + size int // size of the rlp data (estimate) + hash common.Hash // hash of rlp data + node node // the node to commit } // committer is a type used for the trie Commit operation. A committer has some @@ -74,18 +72,12 @@ func returnCommitterToPool(h *committer) { committerPool.Put(h) } -// commitNeeded returns 'false' if the given node is already in sync with db -func (c *committer) commitNeeded(n node) bool { - hash, dirty := n.cache() - return hash == nil || dirty -} - // commit collapses a node down into a hash node and inserts it into the database func (c *committer) Commit(n node, db *Database) (hashNode, error) { if db == nil { return nil, errors.New("no db provided") } - h, err := c.commit(n, db, true) + h, err := c.commit(n, db) if err != nil { return nil, err } @@ -93,7 +85,7 @@ func (c *committer) Commit(n node, db *Database) (hashNode, error) { } // commit collapses a node down into a hash node and inserts it into the database -func (c *committer) commit(n node, db *Database, force bool) (node, error) { +func (c *committer) commit(n node, db *Database) (node, error) { // if this path is clean, use available cached data hash, dirty := n.cache() if hash != nil && !dirty { @@ -104,8 +96,11 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) { case *shortNode: // Commit child collapsed := cn.copy() - if _, ok := cn.Val.(valueNode); !ok { - childV, err := c.commit(cn.Val, db, false) + + // If the child is fullnode, recursively commit. + // Otherwise it can only be hashNode or valueNode. + if _, ok := cn.Val.(*fullNode); ok { + childV, err := c.commit(cn.Val, db) if err != nil { return nil, err } @@ -113,78 +108,78 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) { } // The key needs to be copied, since we're delivering it to database collapsed.Key = hexToCompact(cn.Key) - hashedNode := c.store(collapsed, db, force, true) + hashedNode := c.store(collapsed, db) if hn, ok := hashedNode.(hashNode); ok { return hn, nil } return collapsed, nil case *fullNode: - hashedKids, hasVnodes, err := c.commitChildren(cn, db, force) + hashedKids, err := c.commitChildren(cn, db) if err != nil { return nil, err } collapsed := cn.copy() collapsed.Children = hashedKids - hashedNode := c.store(collapsed, db, force, hasVnodes) + hashedNode := c.store(collapsed, db) if hn, ok := hashedNode.(hashNode); ok { return hn, nil } return collapsed, nil - case valueNode: - return c.store(cn, db, force, false), nil - // hashnodes aren't stored case hashNode: return cn, nil + default: + // nil, valuenode shouldn't be committed + panic(fmt.Sprintf("%T: invalid node: %v", n, n)) } - return hash, nil } // commitChildren commits the children of the given fullnode -func (c *committer) commitChildren(n *fullNode, db *Database, force bool) ([17]node, bool, error) { +func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, error) { var children [17]node - var hasValueNodeChildren = false - for i, child := range n.Children { + for i := 0; i < 16; i++ { + child := n.Children[i] if child == nil { continue } - hnode, err := c.commit(child, db, false) - if err != nil { - return children, false, err + // If it's the hashed child, save the hash value directly. + // Note: it's impossible that the child in range [0, 15] + // is a valuenode. + if hn, ok := child.(hashNode); ok { + children[i] = hn + continue } - children[i] = hnode - if _, ok := hnode.(valueNode); ok { - hasValueNodeChildren = true + // Commit the child recursively and store the "hashed" value. + // Note the returned node can be some embedded nodes, so it's + // possible the type is not hashnode. + hashed, err := c.commit(child, db) + if err != nil { + return children, err } + children[i] = hashed } - return children, hasValueNodeChildren, nil + // For the 17th child, it's possible the type is valuenode. + if n.Children[16] != nil { + children[16] = n.Children[16] + } + return children, nil } // store hashes the node n and if we have a storage layer specified, it writes // the key/value pair to it and tracks any node->child references as well as any // node->external trie references. -func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren bool) node { +func (c *committer) store(n node, db *Database) node { // Larger nodes are replaced by their hash and stored in the database. var ( hash, _ = n.cache() size int ) if hash == nil { - if vn, ok := n.(valueNode); ok { - c.tmp.Reset() - if err := rlp.Encode(&c.tmp, vn); err != nil { - panic("encode error: " + err.Error()) - } - size = len(c.tmp) - if size < 32 && !force { - return n // Nodes smaller than 32 bytes are stored inside their parent - } - hash = c.makeHashNode(c.tmp) - } else { - // This was not generated - must be a small node stored in the parent - // No need to do anything here - return n - } + // This was not generated - must be a small node stored in the parent. + // In theory we should apply the leafCall here if it's not nil(embedded + // node usually contains value). But small value(less than 32bytes) is + // not our target. + return n } else { // We have the hash already, estimate the RLP encoding-size of the node. // The size is used for mem tracking, does not need to be exact @@ -194,10 +189,9 @@ func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren boo // The leaf channel will be active only when there an active leaf-callback if c.leafCh != nil { c.leafCh <- &leaf{ - size: size, - hash: common.BytesToHash(hash), - node: n, - vnodes: hasVnodeChildren, + size: size, + hash: common.BytesToHash(hash), + node: n, } } else if db != nil { // No leaf-callback used, but there's still a database. Do serial @@ -209,30 +203,30 @@ func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren boo return hash } -// commitLoop does the actual insert + leaf callback for nodes +// commitLoop does the actual insert + leaf callback for nodes. func (c *committer) commitLoop(db *Database) { for item := range c.leafCh { var ( - hash = item.hash - size = item.size - n = item.node - hasVnodes = item.vnodes + hash = item.hash + size = item.size + n = item.node ) // We are pooling the trie nodes into an intermediate memory cache db.lock.Lock() db.insert(hash, size, n) db.lock.Unlock() - if c.onleaf != nil && hasVnodes { + + if c.onleaf != nil { switch n := n.(type) { case *shortNode: if child, ok := n.Val.(valueNode); ok { c.onleaf(nil, child, hash) } case *fullNode: - for i := 0; i < 16; i++ { - if child, ok := n.Children[i].(valueNode); ok { - c.onleaf(nil, child, hash) - } + // For children in range [0, 15], it's impossible + // to contain valuenode. Only check the 17th child. + if n.Children[16] != nil { + c.onleaf(nil, n.Children[16].(valueNode), hash) } } } diff --git a/trie/hasher.go b/trie/hasher.go index 57cd3e1f36..3a62a2f119 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -66,11 +66,11 @@ func returnHasherToPool(h *hasher) { // hash collapses a node down into a hash node, also returning a copy of the // original node initialized with the computed hash to replace the original one. func (h *hasher) hash(n node, force bool) (hashed node, cached node) { - // We're not storing the node, just hashing, use available cached data + // Return the cached hash if it's available if hash, _ := n.cache(); hash != nil { return hash, n } - // Trie not processed yet or needs storage, walk the children + // Trie not processed yet, walk the children switch n := n.(type) { case *shortNode: collapsed, cached := h.hashShortNodeChildren(n) diff --git a/trie/trie.go b/trie/trie.go index 1e1749a4ff..6ddbbd78d3 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -505,13 +505,16 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { if t.root == nil { return emptyRoot, nil } + // Derive the hash for all dirty nodes first. We hold the assumption + // in the following procedure that all nodes are hashed. rootHash := t.Hash() h := newCommitter() defer returnCommitterToPool(h) + // Do a quick check if we really need to commit, before we spin // up goroutines. This can happen e.g. if we load a trie for reading storage // values, but don't write to it. - if !h.commitNeeded(t.root) { + if _, dirty := t.root.cache(); !dirty { return rootHash, nil } var wg sync.WaitGroup diff --git a/trie/trie_test.go b/trie/trie_test.go index 2356b7a746..6a32f24367 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -19,7 +19,9 @@ package trie import ( "bytes" "encoding/binary" + "errors" "fmt" + "hash" "io/ioutil" "math/big" "math/rand" @@ -31,9 +33,11 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" ) func init() { @@ -659,6 +663,136 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { return addresses, accounts } +// spongeDb is a dummy db backend which accumulates writes in a sponge +type spongeDb struct { + sponge hash.Hash +} + +func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") } +func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") } +func (s *spongeDb) Delete(key []byte) error { panic("implement me") } +func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} } +func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") } +func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } +func (s *spongeDb) Close() error { return nil } +func (s *spongeDb) Put(key []byte, value []byte) error { + s.sponge.Write(key) + s.sponge.Write(value) + return nil +} +func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } + +// spongeBatch is a dummy batch which immediately writes to the underlying spongedb +type spongeBatch struct { + db *spongeDb +} + +func (b *spongeBatch) Put(key, value []byte) error { + b.db.Put(key, value) + return nil +} +func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } +func (b *spongeBatch) ValueSize() int { return 100 } +func (b *spongeBatch) Write() error { return nil } +func (b *spongeBatch) Reset() {} +func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil } + +// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie +// in the expected order, and calls the callbacks in the expected order. +// The test data was based on the 'master' code, and is basically random. It can be used +// to check whether changes to the trie modifies the write order or data in any way. +func TestCommitSequence(t *testing.T) { + for i, tc := range []struct { + count int + expWriteSeqHash []byte + expCallbackSeqHash []byte + }{ + {20, common.FromHex("68c495e45209e243eb7e4f4e8ca8f9f7be71003bd9cafb8061b4534373740193"), + common.FromHex("01783213033d6b7781a641ab499e680d959336d025ac16f44d02f4f0c021bbf5")}, + {200, common.FromHex("3b20d16c13c4bc3eb3b8d0ad7a169fef3b1600e056c0665895d03d3d2b2ff236"), + common.FromHex("fb8db0ec82e8f02729f11228940885b181c3047ab0d654ed0110291ca57111a8")}, + {2000, common.FromHex("34eff3d1048bebdf77e9ae8bd939f2e7c742edc3dcd1173cff1aad9dbd20451a"), + common.FromHex("1c981604b1a9f8ffa40e0ae66b14830a87f5a4ed8345146a3912e6b2dcb05e63")}, + } { + addresses, accounts := makeAccounts(tc.count) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used to check the callback-sequence + callbackSponge := sha3.NewLegacyKeccak256() + // Fill the trie with elements + for i := 0; i < tc.count; i++ { + trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) + } + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, func(c common.Hash) { + // And spongify the callback-order + callbackSponge.Write(c[:]) + }) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + } + if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) + } + } +} + +// TestCommitSequenceRandomBlobs is identical to TestCommitSequence +// but uses random blobs instead of 'accounts' +func TestCommitSequenceRandomBlobs(t *testing.T) { + for i, tc := range []struct { + count int + expWriteSeqHash []byte + expCallbackSeqHash []byte + }{ + {20, common.FromHex("8e4a01548551d139fa9e833ebc4e66fc1ba40a4b9b7259d80db32cff7b64ebbc"), + common.FromHex("450238d73bc36dc6cc6f926987e5428535e64be403877c4560e238a52749ba24")}, + {200, common.FromHex("6869b4e7b95f3097a19ddb30ff735f922b915314047e041614df06958fc50554"), + common.FromHex("0ace0b03d6cb8c0b82f6289ef5b1a1838306b455a62dafc63cada8e2924f2550")}, + {2000, common.FromHex("444200e6f4e2df49f77752f629a96ccf7445d4698c164f962bbd85a0526ef424"), + common.FromHex("117d30dafaa62a1eed498c3dfd70982b377ba2b46dd3e725ed6120c80829e518")}, + } { + prng := rand.New(rand.NewSource(int64(i))) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used to check the callback-sequence + callbackSponge := sha3.NewLegacyKeccak256() + // Fill the trie with elements + for i := 0; i < tc.count; i++ { + key := make([]byte, 32) + var val []byte + // 50% short elements, 50% large elements + if prng.Intn(2) == 0 { + val = make([]byte, 1+prng.Intn(32)) + } else { + val = make([]byte, 1+prng.Intn(4096)) + } + prng.Read(key) + prng.Read(val) + trie.Update(key, val) + } + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, func(c common.Hash) { + // And spongify the callback-order + callbackSponge.Write(c[:]) + }) + if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + } + if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) { + t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) + } + } +} + // BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie. // This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically, // storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple From 9d1e2027a05ed099b9ef43021a6cb09839e94a3a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 30 Sep 2020 19:49:20 +0200 Subject: [PATCH 064/948] trie: add Commit-sequence tests for stacktrie commit (#21643) --- trie/trie_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/trie/trie_test.go b/trie/trie_test.go index 6a32f24367..03ec0cab89 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -665,7 +665,9 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { // spongeDb is a dummy db backend which accumulates writes in a sponge type spongeDb struct { - sponge hash.Hash + sponge hash.Hash + id string + journal []string } func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") } @@ -676,6 +678,11 @@ func (s *spongeDb) Stat(property string) (string, error) { panic("implement func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } func (s *spongeDb) Close() error { return nil } func (s *spongeDb) Put(key []byte, value []byte) error { + valbrief := value + if len(valbrief) > 8 { + valbrief = valbrief[:8] + } + s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, key[:8], len(value), valbrief)) s.sponge.Write(key) s.sponge.Write(value) return nil @@ -793,6 +800,56 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { } } +func TestCommitSequenceStackTrie(t *testing.T) { + for count := 1; count < 200; count++ { + prng := rand.New(rand.NewSource(int64(count))) + // This spongeDb is used to check the sequence of disk-db-writes + s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used for the stacktrie commits + stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} + stTrie := NewStackTrie(stackTrieSponge) + // Fill the trie with elements + for i := 1; i < count; i++ { + // For the stack trie, we need to do inserts in proper order + key := make([]byte, 32) + binary.BigEndian.PutUint64(key, uint64(i)) + var val []byte + // 50% short elements, 50% large elements + if prng.Intn(2) == 0 { + val = make([]byte, 1+prng.Intn(32)) + } else { + val = make([]byte, 1+prng.Intn(1024)) + } + prng.Read(val) + trie.TryUpdate(key, common.CopyBytes(val)) + stTrie.TryUpdate(key, common.CopyBytes(val)) + } + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, nil) + // And flush stacktrie -> disk + stRoot := stTrie.Commit(stTrie.db) + if stRoot != root { + t.Fatalf("root wrong, got %x exp %x", stRoot, root) + } + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { + // Show the journal + t.Logf("Expected:") + for i, v := range s.journal { + t.Logf("op %d: %v", i, v) + } + t.Logf("Stacktrie:") + for i, v := range stackTrieSponge.journal { + t.Logf("op %d: %v", i, v) + } + t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", count, got, exp) + } + } +} + // BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie. // This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically, // storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple From a15d71a255686541e04f446dd92aadbf9ff496a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 5 Oct 2020 11:52:36 +0300 Subject: [PATCH 065/948] core/state/snapshot: stop generator if it hits missing trie nodes (#21649) * core/state/snapshot: exit Geth if generator hits missing trie nodes * core/state/snapshot: error instead of hard die on generator fault * core/state/snapshot: don't enable logging on the tests --- core/state/snapshot/generate.go | 19 ++- core/state/snapshot/generate_test.go | 190 +++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 core/state/snapshot/generate_test.go diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index cf9b2b0393..a6b3e4420d 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -129,7 +129,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { stats.wiping = nil stats.start = time.Now() - // If generator was aboted during wipe, return + // If generator was aborted during wipe, return case abort := <-dl.genAbort: abort <- stats return @@ -203,7 +203,10 @@ func (dl *diskLayer) generate(stats *generatorStats) { if acc.Root != emptyRoot { storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) if err != nil { - log.Crit("Storage trie inaccessible for snapshot generation", "err", err) + log.Error("Generator failed to access storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) + abort := <-dl.genAbort + abort <- stats + return } var storeMarker []byte if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { @@ -238,6 +241,12 @@ func (dl *diskLayer) generate(stats *generatorStats) { } } } + if err := storeIt.Err; err != nil { + log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) + abort := <-dl.genAbort + abort <- stats + return + } } if time.Since(logged) > 8*time.Second { stats.Log("Generating state snapshot", dl.root, accIt.Key) @@ -246,6 +255,12 @@ func (dl *diskLayer) generate(stats *generatorStats) { // Some account processed, unmark the marker accMarker = nil } + if err := accIt.Err; err != nil { + log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err) + abort := <-dl.genAbort + abort <- stats + return + } // Snapshot fully generated, set the marker to nil if batch.ValueSize() > 0 { batch.Write() diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go new file mode 100644 index 0000000000..03263f3976 --- /dev/null +++ b/core/state/snapshot/generate_test.go @@ -0,0 +1,190 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// Tests that snapshot generation errors out correctly in case of a missing trie +// node in the account trie. +func TestGenerateCorruptAccountTrie(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // without any storage slots to keep the test smaller. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + tr, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + tr.Update([]byte("acc-1"), val) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074 + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + tr.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + tr.Update([]byte("acc-3"), val) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4 + tr.Commit(nil) // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978 + + // Delete an account trie leaf and ensure the generator chokes + triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil) + diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes()) + + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), nil) + select { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt account trie") + + case <-time.After(250 * time.Millisecond): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation errors out correctly in case of a missing root +// trie node for a storage trie. It's similar to internal corruption but it is +// handled differently inside the generator. +func TestGenerateMissingStorageTrie(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // two of which also has the same 3-slot storage trie attached. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 + stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 + stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 + stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + + // We can only corrupt the disk database, so flush the tries out + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"), + ) + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"), + ) + triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil) + + // Delete a storage trie root and ensure the generator chokes + diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes()) + + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + select { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt storage trie") + + case <-time.After(250 * time.Millisecond): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation errors out correctly in case of a missing trie +// node in a storage trie. +func TestGenerateCorruptStorageTrie(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // two of which also has the same 3-slot storage trie attached. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 + stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 + stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 + stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + + // We can only corrupt the disk database, so flush the tries out + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"), + ) + triedb.Reference( + common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"), + common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"), + ) + triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil) + + // Delete a storage trie leaf and ensure the generator chokes + diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes()) + + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + select { + case <-snap.genPending: + // Snapshot generation succeeded + t.Errorf("Snapshot generated against corrupt storage trie") + + case <-time.After(250 * time.Millisecond): + // Not generated fast enough, hopefully blocked inside on missing trie node fail + } + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} From d9890a6a8fc245c31dd0852c5fb0e1081708c4bc Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 5 Oct 2020 12:50:26 +0200 Subject: [PATCH 066/948] cmd/faucet: enable DNS discovery for known networks (#21636) --- cmd/faucet/faucet.go | 2 ++ cmd/utils/flags.go | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 7dc5536eba..346c412acb 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -43,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -241,6 +242,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis + utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash()) lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 47d663eeef..55b751d625 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1590,19 +1590,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.NetworkId = 3 } cfg.Genesis = core.DefaultRopstenGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash) case ctx.GlobalBool(RinkebyFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 4 } cfg.Genesis = core.DefaultRinkebyGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash) case ctx.GlobalBool(GoerliFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 5 } cfg.Genesis = core.DefaultGoerliGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) case ctx.GlobalBool(YoloV1Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 133519467574833 // "yolov1" @@ -1656,14 +1656,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } default: if cfg.NetworkId == 1 { - setDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) } } } -// setDNSDiscoveryDefaults configures DNS discovery with the given URL if +// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // no URLs are set. -func setDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { +func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { if cfg.DiscoveryURLs != nil { return // already set through flags/config } From 2b2fd74158795bfccaff053558b4c1d1d60fa2fa Mon Sep 17 00:00:00 2001 From: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Date: Tue, 6 Oct 2020 07:35:21 +0200 Subject: [PATCH 067/948] params: update goerli testnet bootnodes (#21659) * params: update pegasys besu bootnode * params: update goerli initiative bootnodes --- params/bootnodes.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/params/bootnodes.go b/params/bootnodes.go index c8736b8ae8..42a6e2ec7c 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -56,11 +56,15 @@ var GoerliBootnodes = []string{ "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://c1f8b7c2ac4453271fa07d8e9ecf9a2e8285aa0bd0c07df0131f47153306b0736fd3db8924e7a9bf0bed6b1d8d4f87362a71b033dc7c64547728d953e43e59b2@52.64.155.147:30303", - "enode://f4a9c6ee28586009fb5a96c8af13a58ed6d8315a9eee4772212c1d4d9cebe5a8b8a78ea4434f318726317d04a3f531a1ef0420cf9752605a562cfe858c46e263@213.186.16.82:30303", + "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", // Ethereum Foundation bootnode "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", + + // Goerli Initiative bootnodes + "enode://a869b02cec167211fb4815a82941db2e7ed2936fd90e78619c53eb17753fcf0207463e3419c264e2a1dd8786de0df7e68cf99571ab8aeb7c4e51367ef186b1dd@51.15.116.226:30303", + "enode://807b37ee4816ecf407e9112224494b74dd5933625f655962d892f2f0f02d7fbbb3e2a94cf87a96609526f30c998fd71e93e2f53015c558ffc8b03eceaf30ee33@51.15.119.157:30303", + "enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303", } // YoloV1Bootnodes are the enode URLs of the P2P bootstrap nodes running on the From eb87121300725623d7f504f7761fb7277579d689 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 6 Oct 2020 15:34:29 +0200 Subject: [PATCH 068/948] core/bloombits: faster generator (#21625) * core/bloombits: add benchmark * core/bloombits: optimize inserts --- core/bloombits/generator.go | 23 +++++++++++-------- core/bloombits/generator_test.go | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go index ae07481ada..646151db0b 100644 --- a/core/bloombits/generator.go +++ b/core/bloombits/generator.go @@ -65,18 +65,23 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error { } // Rotate the bloom and insert into our collection byteIndex := b.nextSec / 8 - bitMask := byte(1) << byte(7-b.nextSec%8) - - for i := 0; i < types.BloomBitLength; i++ { - bloomByteIndex := types.BloomByteLength - 1 - i/8 - bloomBitMask := byte(1) << byte(i%8) - - if (bloom[bloomByteIndex] & bloomBitMask) != 0 { - b.blooms[i][byteIndex] |= bitMask + bitIndex := byte(7 - b.nextSec%8) + for byt := 0; byt < types.BloomByteLength; byt++ { + bloomByte := bloom[types.BloomByteLength-1-byt] + if bloomByte == 0 { + continue } + base := 8 * byt + b.blooms[base+7][byteIndex] |= ((bloomByte >> 7) & 1) << bitIndex + b.blooms[base+6][byteIndex] |= ((bloomByte >> 6) & 1) << bitIndex + b.blooms[base+5][byteIndex] |= ((bloomByte >> 5) & 1) << bitIndex + b.blooms[base+4][byteIndex] |= ((bloomByte >> 4) & 1) << bitIndex + b.blooms[base+3][byteIndex] |= ((bloomByte >> 3) & 1) << bitIndex + b.blooms[base+2][byteIndex] |= ((bloomByte >> 2) & 1) << bitIndex + b.blooms[base+1][byteIndex] |= ((bloomByte >> 1) & 1) << bitIndex + b.blooms[base][byteIndex] |= (bloomByte & 1) << bitIndex } b.nextSec++ - return nil } diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go index f9bcef96e0..88e3ed6b06 100644 --- a/core/bloombits/generator_test.go +++ b/core/bloombits/generator_test.go @@ -58,3 +58,42 @@ func TestGenerator(t *testing.T) { } } } + +func BenchmarkGenerator(b *testing.B) { + var input [types.BloomBitLength][types.BloomByteLength]byte + b.Run("empty", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + b.Fatalf("failed to create bloombit generator: %v", err) + } + for j, bloom := range input { + if err := gen.AddBloom(uint(j), bloom); err != nil { + b.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + } + }) + for i := 0; i < types.BloomBitLength; i++ { + rand.Read(input[i][:]) + } + b.Run("random", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Crunch the input through the generator and verify the result + gen, err := NewGenerator(types.BloomBitLength) + if err != nil { + b.Fatalf("failed to create bloombit generator: %v", err) + } + for j, bloom := range input { + if err := gen.AddBloom(uint(j), bloom); err != nil { + b.Fatalf("bloom %d: failed to add: %v", i, err) + } + } + } + }) +} From e43d827a1987c362dce6efd0afd8e753a6ecf9ae Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 6 Oct 2020 15:57:00 +0200 Subject: [PATCH 069/948] core/types: optimize bloom filters (#21624) * core/types: tests for bloom * core/types: refactored bloom filter for receipts, added tests core/types: replaced old bloom implementation core/types: change interface of bloom add+test * core/types: refactor bloom * core/types: minor tweak on LogsBloom Co-authored-by: Marius van der Wijden --- core/types/bloom9.go | 100 +++++++++++++++++++------------ core/types/bloom9_test.go | 123 ++++++++++++++++++++++++++++++-------- 2 files changed, 161 insertions(+), 62 deletions(-) diff --git a/core/types/bloom9.go b/core/types/bloom9.go index d045c9e667..1793c2adc7 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -17,6 +17,7 @@ package types import ( + "encoding/binary" "fmt" "math/big" @@ -57,28 +58,36 @@ func (b *Bloom) SetBytes(d []byte) { } // Add adds d to the filter. Future calls of Test(d) will return true. -func (b *Bloom) Add(d *big.Int) { - bin := new(big.Int).SetBytes(b[:]) - bin.Or(bin, bloom9(d.Bytes())) - b.SetBytes(bin.Bytes()) +func (b *Bloom) Add(d []byte) { + b.add(d, make([]byte, 6)) +} + +// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) +func (b *Bloom) add(d []byte, buf []byte) { + i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) + b[i1] |= v1 + b[i2] |= v2 + b[i3] |= v3 } // Big converts b to a big integer. +// Note: Converting a bloom filter to a big.Int and then calling GetBytes +// does not return the same bytes, since big.Int will trim leading zeroes func (b Bloom) Big() *big.Int { return new(big.Int).SetBytes(b[:]) } +// Bytes returns the backing byte slice of the bloom func (b Bloom) Bytes() []byte { return b[:] } -func (b Bloom) Test(test *big.Int) bool { - return BloomLookup(b, test) -} - -func (b Bloom) TestBytes(test []byte) bool { - return b.Test(new(big.Int).SetBytes(test)) - +// Test checks if the given topic is present in the bloom filter +func (b Bloom) Test(topic []byte) bool { + i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + return v1 == v1&b[i1] && + v2 == v2&b[i2] && + v3 == v3&b[i3] } // MarshalText encodes b as a hex string with 0x prefix. @@ -91,46 +100,61 @@ func (b *Bloom) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedText("Bloom", input, b[:]) } +// CreateBloom creates a bloom filter out of the give Receipts (+Logs) func CreateBloom(receipts Receipts) Bloom { - bin := new(big.Int) + buf := make([]byte, 6) + var bin Bloom for _, receipt := range receipts { - bin.Or(bin, LogsBloom(receipt.Logs)) + for _, log := range receipt.Logs { + bin.add(log.Address.Bytes(), buf) + for _, b := range log.Topics { + bin.add(b[:], buf) + } + } } - - return BytesToBloom(bin.Bytes()) + return bin } -func LogsBloom(logs []*Log) *big.Int { - bin := new(big.Int) +// LogsBloom returns the bloom bytes for the given logs +func LogsBloom(logs []*Log) []byte { + buf := make([]byte, 6) + var bin Bloom for _, log := range logs { - bin.Or(bin, bloom9(log.Address.Bytes())) + bin.add(log.Address.Bytes(), buf) for _, b := range log.Topics { - bin.Or(bin, bloom9(b[:])) + bin.add(b[:], buf) } } - - return bin + return bin[:] } -func bloom9(b []byte) *big.Int { - b = crypto.Keccak256(b) - - r := new(big.Int) - - for i := 0; i < 6; i += 2 { - t := big.NewInt(1) - b := (uint(b[i+1]) + (uint(b[i]) << 8)) & 2047 - r.Or(r, t.Lsh(t, b)) - } - - return r +// Bloom9 returns the bloom filter for the given data +func Bloom9(data []byte) []byte { + var b Bloom + b.SetBytes(data) + return b.Bytes() } -var Bloom9 = bloom9 +// bloomValues returns the bytes (index-value pairs) to set for the given data +func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { + sha := hasherPool.Get().(crypto.KeccakState) + sha.Reset() + sha.Write(data) + sha.Read(hashbuf) + hasherPool.Put(sha) + // The actual bits to flip + v1 := byte(1 << (hashbuf[1] & 0x7)) + v2 := byte(1 << (hashbuf[3] & 0x7)) + v3 := byte(1 << (hashbuf[5] & 0x7)) + // The indices for the bytes to OR in + i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 + i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 + i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 + + return i1, v1, i2, v2, i3, v3 +} +// BloomLookup is a convenience-method to check presence int he bloom filter func BloomLookup(bin Bloom, topic bytesBacked) bool { - bloom := bin.Big() - cmp := bloom9(topic.Bytes()) - - return bloom.And(bloom, cmp).Cmp(cmp) == 0 + return bin.Test(topic.Bytes()) } diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go index a28ac0e7af..893df486dd 100644 --- a/core/types/bloom9_test.go +++ b/core/types/bloom9_test.go @@ -17,8 +17,12 @@ package types import ( + "fmt" "math/big" "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) func TestBloom(t *testing.T) { @@ -35,47 +39,118 @@ func TestBloom(t *testing.T) { var bloom Bloom for _, data := range positive { - bloom.Add(new(big.Int).SetBytes([]byte(data))) + bloom.Add([]byte(data)) } for _, data := range positive { - if !bloom.TestBytes([]byte(data)) { + if !bloom.Test([]byte(data)) { t.Error("expected", data, "to test true") } } for _, data := range negative { - if bloom.TestBytes([]byte(data)) { + if bloom.Test([]byte(data)) { t.Error("did not expect", data, "to test true") } } } -/* -import ( - "testing" - - "github.com/ethereum/go-ethereum/core/state" -) +// TestBloomExtensively does some more thorough tests +func TestBloomExtensively(t *testing.T) { + var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263") + var b Bloom + // Add 100 "random" things + for i := 0; i < 100; i++ { + data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i) + b.Add([]byte(data)) + //b.Add(new(big.Int).SetBytes([]byte(data))) + } + got := crypto.Keccak256Hash(b.Bytes()) + if got != exp { + t.Errorf("Got %x, exp %x", got, exp) + } + var b2 Bloom + b2.SetBytes(b.Bytes()) + got2 := crypto.Keccak256Hash(b2.Bytes()) + if got != got2 { + t.Errorf("Got %x, exp %x", got, got2) + } +} -func TestBloom9(t *testing.T) { - testCase := []byte("testtest") - bin := LogsBloom([]state.Log{ - {testCase, [][]byte{[]byte("hellohello")}, nil}, - }).Bytes() - res := BloomLookup(bin, testCase) +func BenchmarkBloom9(b *testing.B) { + test := []byte("testestestest") + for i := 0; i < b.N; i++ { + Bloom9(test) + } +} - if !res { - t.Errorf("Bloom lookup failed") +func BenchmarkBloom9Lookup(b *testing.B) { + toTest := []byte("testtest") + bloom := new(Bloom) + for i := 0; i < b.N; i++ { + bloom.Test(toTest) } } +func BenchmarkCreateBloom(b *testing.B) { -func TestAddress(t *testing.T) { - block := &Block{} - block.Coinbase = common.Hex2Bytes("22341ae42d6dd7384bc8584e50419ea3ac75b83f") - fmt.Printf("%x\n", crypto.Keccak256(block.Coinbase)) + var txs = Transactions{ + NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), + NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), + } + var rSmall = Receipts{ + &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x11})}, + {Address: common.BytesToAddress([]byte{0x01, 0x11})}, + }, + TxHash: txs[0].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 1, + }, + &Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 3, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x22})}, + {Address: common.BytesToAddress([]byte{0x02, 0x22})}, + }, + TxHash: txs[1].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), + GasUsed: 2, + }, + } - bin := CreateBloom(block) - fmt.Printf("bin = %x\n", common.LeftPadBytes(bin, 64)) + var rLarge = make(Receipts, 200) + // Fill it with 200 receipts x 2 logs + for i := 0; i < 200; i += 2 { + copy(rLarge[i:], rSmall) + } + b.Run("small", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rSmall) + } + b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) + b.Run("large", func(b *testing.B) { + b.ReportAllocs() + var bl Bloom + for i := 0; i < b.N; i++ { + bl = CreateBloom(rLarge) + } + b.StopTimer() + var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") + got := crypto.Keccak256Hash(bl.Bytes()) + if got != exp { + b.Errorf("Got %x, exp %x", got, exp) + } + }) } -*/ From 716864deba26d2afea6b3cb62eeb573613d6b86b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 7 Oct 2020 17:22:44 +0200 Subject: [PATCH 070/948] cmd/devp2p/internal/ethtest: improve eth test suite (#21615) This fixes issues with the protocol handshake and status exchange and adds support for responding to GetBlockHeaders requests. --- cmd/devp2p/internal/ethtest/chain.go | 53 ++++ cmd/devp2p/internal/ethtest/chain_test.go | 150 +++++++++++ cmd/devp2p/internal/ethtest/suite.go | 167 ++---------- .../internal/ethtest/testdata/chain.rlp.gz | Bin 0 -> 247322 bytes .../internal/ethtest/testdata/genesis.json | 26 ++ cmd/devp2p/internal/ethtest/types.go | 238 +++++++++++++++++- 6 files changed, 487 insertions(+), 147 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/chain_test.go create mode 100755 cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/testdata/genesis.json diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 250be64fe6..654888a4ca 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -1,3 +1,19 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package ethtest import ( @@ -68,6 +84,43 @@ func (c *Chain) Head() *types.Block { return c.blocks[c.Len()-1] } +func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) { + if req.Amount < 1 { + return nil, fmt.Errorf("no block headers requested") + } + + headers := make(BlockHeaders, req.Amount) + var blockNumber uint64 + + // range over blocks to check if our chain has the requested header + for _, block := range c.blocks { + if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number { + headers[0] = block.Header() + blockNumber = block.Number().Uint64() + } + } + if headers[0] == nil { + return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash) + } + + if req.Reverse { + for i := 1; i < int(req.Amount); i++ { + blockNumber -= (1 - req.Skip) + headers[i] = c.blocks[blockNumber].Header() + + } + + return headers, nil + } + + for i := 1; i < int(req.Amount); i++ { + blockNumber += (1 + req.Skip) + headers[i] = c.blocks[blockNumber].Header() + } + + return headers, nil +} + // loadChain takes the given chain.rlp file, and decodes and returns // the blocks from the file. func loadChain(chainfile string, genesis string) (*Chain, error) { diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go new file mode 100644 index 0000000000..c8b977d237 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -0,0 +1,150 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "path/filepath" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/stretchr/testify/assert" +) + +// TestEthProtocolNegotiation tests whether the test suite +// can negotiate the highest eth protocol in a status message exchange +func TestEthProtocolNegotiation(t *testing.T) { + var tests = []struct { + conn *Conn + caps []p2p.Cap + expected uint32 + }{ + { + conn: &Conn{}, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{}, + caps: []p2p.Cap{ + {Name: "eth", Version: 0}, + {Name: "eth", Version: 89}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{}, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + tt.conn.negotiateEthProtocol(tt.caps) + assert.Equal(t, tt.expected, uint32(tt.conn.ethProtocolVersion)) + }) + } +} + +// TestChain_GetHeaders tests whether the test suite can correctly +// respond to a GetBlockHeaders request from a node. +func TestChain_GetHeaders(t *testing.T) { + chainFile, err := filepath.Abs("./testdata/chain.rlp.gz") + if err != nil { + t.Fatal(err) + } + genesisFile, err := filepath.Abs("./testdata/genesis.json") + if err != nil { + t.Fatal(err) + } + + chain, err := loadChain(chainFile, genesisFile) + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + req GetBlockHeaders + expected BlockHeaders + }{ + { + req: GetBlockHeaders{ + Origin: hashOrNumber{ + Number: uint64(2), + }, + Amount: uint64(5), + Skip: 1, + Reverse: false, + }, + expected: BlockHeaders{ + chain.blocks[2].Header(), + chain.blocks[4].Header(), + chain.blocks[6].Header(), + chain.blocks[8].Header(), + chain.blocks[10].Header(), + }, + }, + { + req: GetBlockHeaders{ + Origin: hashOrNumber{ + Number: uint64(chain.Len() - 1), + }, + Amount: uint64(3), + Skip: 0, + Reverse: true, + }, + expected: BlockHeaders{ + chain.blocks[chain.Len()-1].Header(), + chain.blocks[chain.Len()-2].Header(), + chain.blocks[chain.Len()-3].Header(), + }, + }, + { + req: GetBlockHeaders{ + Origin: hashOrNumber{ + Hash: chain.Head().Hash(), + }, + Amount: uint64(1), + Skip: 0, + Reverse: false, + }, + expected: BlockHeaders{ + chain.Head().Header(), + }, + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + headers, err := chain.GetHeaders(tt.req) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, headers, tt.expected) + }) + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 6951f13bfb..f70bc43efa 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -1,19 +1,29 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package ethtest import ( - "crypto/ecdsa" "fmt" "net" - "reflect" - "time" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/rlpx" - "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" ) @@ -26,137 +36,6 @@ type Suite struct { fullChain *Chain } -type Conn struct { - *rlpx.Conn - ourKey *ecdsa.PrivateKey -} - -func (c *Conn) Read() Message { - code, rawData, _, err := c.Conn.Read() - if err != nil { - return &Error{fmt.Errorf("could not read from connection: %v", err)} - } - - var msg Message - switch int(code) { - case (Hello{}).Code(): - msg = new(Hello) - case (Disconnect{}).Code(): - msg = new(Disconnect) - case (Status{}).Code(): - msg = new(Status) - case (GetBlockHeaders{}).Code(): - msg = new(GetBlockHeaders) - case (BlockHeaders{}).Code(): - msg = new(BlockHeaders) - case (GetBlockBodies{}).Code(): - msg = new(GetBlockBodies) - case (BlockBodies{}).Code(): - msg = new(BlockBodies) - case (NewBlock{}).Code(): - msg = new(NewBlock) - case (NewBlockHashes{}).Code(): - msg = new(NewBlockHashes) - default: - return &Error{fmt.Errorf("invalid message code: %d", code)} - } - - if err := rlp.DecodeBytes(rawData, msg); err != nil { - return &Error{fmt.Errorf("could not rlp decode message: %v", err)} - } - - return msg -} - -func (c *Conn) Write(msg Message) error { - payload, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - _, err = c.Conn.Write(uint64(msg.Code()), payload) - return err - -} - -// handshake checks to make sure a `HELLO` is received. -func (c *Conn) handshake(t *utesting.T) Message { - // write protoHandshake to client - pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] - ourHandshake := &Hello{ - Version: 5, - Caps: []p2p.Cap{{Name: "eth", Version: 64}, {Name: "eth", Version: 65}}, - ID: pub0, - } - if err := c.Write(ourHandshake); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // read protoHandshake from client - switch msg := c.Read().(type) { - case *Hello: - return msg - default: - t.Fatalf("bad handshake: %v", msg) - return nil - } -} - -// statusExchange performs a `Status` message exchange with the given -// node. -func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { - // read status message from client - var message Message - switch msg := c.Read().(type) { - case *Status: - if msg.Head != chain.blocks[chain.Len()-1].Hash() { - t.Fatalf("wrong head in status: %v", msg.Head) - } - if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { - t.Fatalf("wrong TD in status: %v", msg.TD) - } - if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) { - t.Fatalf("wrong fork ID in status: %v", msg.ForkID) - } - message = msg - default: - t.Fatalf("bad status message: %v", msg) - } - // write status message to client - status := Status{ - ProtocolVersion: 64, - NetworkID: 1, - TD: chain.TD(chain.Len()), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), - } - if err := c.Write(status); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - - return message -} - -// waitForBlock waits for confirmation from the client that it has -// imported the given block. -func (c *Conn) waitForBlock(block *types.Block) error { - for { - req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} - if err := c.Write(req); err != nil { - return err - } - - switch msg := c.Read().(type) { - case *BlockHeaders: - if len(*msg) > 0 { - return nil - } - time.Sleep(100 * time.Millisecond) - default: - return fmt.Errorf("invalid message: %v", msg) - } - } -} - // NewSuite creates and returns a new eth-test suite that can // be used to test the given node against the given blockchain // data. @@ -196,7 +75,7 @@ func (s *Suite) TestStatus(t *utesting.T) { case *Status: t.Logf("%+v\n", msg) default: - t.Fatalf("error: %v", msg) + t.Fatalf("unexpected: %#v", msg) } } @@ -225,7 +104,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := conn.Read().(type) { + switch msg := conn.ReadAndServe(s.chain).(type) { case *BlockHeaders: headers := msg for _, header := range *headers { @@ -234,7 +113,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header) } default: - t.Fatalf("error: %v", msg) + t.Fatalf("unexpected: %#v", msg) } } @@ -254,14 +133,14 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := conn.Read().(type) { + switch msg := conn.ReadAndServe(s.chain).(type) { case *BlockBodies: bodies := msg for _, body := range *bodies { t.Logf("\nBODY: %+v\n", body) } default: - t.Fatalf("error: %v", msg) + t.Fatalf("unexpected: %#v", msg) } } @@ -294,7 +173,7 @@ func (s *Suite) TestBroadcast(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := receiveConn.Read().(type) { + switch msg := receiveConn.ReadAndServe(s.chain).(type) { case *NewBlock: assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(), "wrong block header in announcement") @@ -305,7 +184,7 @@ func (s *Suite) TestBroadcast(t *utesting.T) { assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash, "wrong block hash in announcement") default: - t.Fatal(msg) + t.Fatalf("unexpected: %#v", msg) } // update test suite chain s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000]) diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz new file mode 100755 index 0000000000000000000000000000000000000000..bdd6290ce6e569c50323c02fcf3cb12b0d803d1e GIT binary patch literal 247322 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

=XnITf)MFj(D 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

=XnITf)MFj(D zYy3pE>d;*NV55y1G}o6ZG;?Z-NN|&_crd~7#_X!1fM4$#1P}Qs1B^f# zw&|YdDGFowsZsT6A~YRi*4%Cq%AxqqI(&w!ibuUx?cCdT(JNZSBVV&1r89j~Gh8VV zV2>3>;EyFXIrp)a6$BRO6kpVK_b9JZ?z#41IX{1;2#(%EC`;2!Y%nZ}%jv z@wOGWcJlB1K!o0-xR>WW=0Ai4{|yE`8#-I4+l(jndO=uhs~pK&Zq%rE-x+eFEPXj1zMC5z==F) zIk9B%Zx#c`39nHIr$#V;_z9+KHVE5(-DDy&DT49BBj`U7e)<48W({{+u7VS3G8}XV zJKGmeyus24a}{2n(wBEv>ByYX9&=%9_Tq*8y4YoL3?mW*=5HvOZ7jUavLYyzE0>yo zeG7dieO>wv9T3~)27>>57t+$f#FV?+1313L4wGc{r)C{s>qfH{l}?8r3jNIPp8LP>$PGyc%EFwWTPf-Mi}Y7PsM7zr^7omC!$zCDrzXu|*Sy9@j zMd41P{St8(x7pSN>2n)17!ST02U5^@>EyHo>l?q4{QAj(Y&aNDYVqI{K7Bdi zx9aCElnis4+n?c@uAfR=B3-cl+==5BkXn+ygu1Y7Z+qAsx zMQ1@0o$t>{F%@BfKc+wr2O({3r!+!;uc+87{4%B@Ikb^{@JxYZMBW>qS~ ztt{3?Zj-?D8R&f5hLzkKFPNJ9bu3&J_$BoW&L)qEJ7Jakni+!u1vMIvhJ1e-l!TK3 zd8%i^vp>nd_lN1~PD}Nl+L)Vl5CQ(y@lH5c7A+35*MBXC2#!?-=r^1SXWdl{!nQR( z(tP8@P&-FStjOZA`t#0Q_nbIW9xoTa8sosSeV{}LFsiH&hO?Nu#=Qzgq=#>jM-__M z>Hpj`A$D$oHff>`5~BHAQuIBl)k2>``Yzkj!q2FQs8UVEopUXA8Y7rbv(oja|9?UR zfB_Lq!vM&(eS({wv|a5DugCmda~6}D6%ZlQa6-#ZPS;JkQ}P*ghwV1=O}?9EH_Vn- z4|oR5Ggcd56q2;0|C>b8n_Jj`P@f^Yg6K0aYTT9Q=RxBnsejAa42$nl8n z;iUdnLnpy?KVR+l(agVG0dy}0s9?fFk<$&xzn`zHB-9phpGf|M*0rvC_7sc)lPkp6sOjb?;s0Spbe zrF)2mO2ux7Y0oF)Ew^6Q@{h|}5j2J6W!O*8<9{d2`u;^tLPTK20*K!-yo%Z9%Npkh z%LmSg+B-NF5B>WrzTOh@CpBxGBlz~P`w02M8aE^h0ky#O)YE#4AkHpBft|Jq$4P#x zh~0D_vomvw+p_J*Faw_)J|t@fOulGRJ?&XUp8yRP zwXRtcJPl5(7+v8{6v#wcT>MnmRQ~mBA7Tx^S9+ivHGG~|GKXT0|EUrtFqZSqlWTCK zP`*!&^UpBdj|M3m1FY8#M}zKZ!vN%EoiUK}Gcua32XhQlWBjI_F1aXZ?|9(uhr`en zIPfb$$Vivs!Ma96LVygOWn&21WY|F>CAtH27u+HA8`frbuSw=x?xRKMwFk`sPrppq_{L;QEGcPIeLH$d*jCmt4?f;hI{jB3)u zIT&nKm5R2bst#`nnSvd$Qst)s91XvjiJgCC02mpz2iqGM4rK53A7M9rMo7|9nz~&h{T0>rNNskmD4cQIR7qrJ zN4_1RVUwo1_%xZ@fdsdWA|ts=Cf=-zZVFFj)NOfzWzk;CGfp$!S8{p~fCZczW&@|+ zN9C_FS9@^H_leYqqmJSCZ@rM05-8(;rK8)WL<|1P+XX9=}}5kgrJ13IW1oYA8wUiE!Bc z1y!;`My`Grs+BSwCI|i=qI1VY%5CU0;H*~dGsZyUK>s5P|C7l(u8_+m>DV#c$61$D z)2H7CQqcdd_B;i!%>$6*`p0!s3aS`7RJ8fHPp;;N7vW1W@4_%o%Eg93e`R; zB@i|*?+(aZs#iygdYmd%I5Pj$7iu+6y>Z%LRK|T!ZV(HfbuA0P0y{U2>uq{`dBOIT zJV;JwRIuNfb>qBl-N+ZMV;;{Dd-w-xfZ<_5AxJ>72WPn>zwn0(zEI*N?LRs$5%*DP zmm{vXQI=OkhwED)^8EBx5yOck$a`jci84S9x0w94b7ReS81uwO$P6UTLL)m18k|tX zA0wfMqDIz9hSS?0xNC8DpZq$y&!fCdl+PH0?$c(KY2CbU81Qeu(=h7~o}>>QQJ z{)ca2XSJ~##&Rc-9`1XF^2iBZmwrJ2VeD}MICPKp4TaW9T}k#!r^8%Lqa$=C0o@wJ z3bOMq8j71HDDVk1JZc;yBbc8nsw$1270dJGXG&zQw7So<}CxR3)a*HF0$)h#Nb824}V zyB9hWsdaKlHAU4X8&t=yT2Pg^vE?7%XzqOAmMbSU==4YSn6xF>Rc08pwz4AgvA4nO zt@(C55UFxh_r4;%(j*X)EBw~YvLIITpD15RXnP1{#A^wD%c??ZIW6?)qN%^~4s3)T z27yT-F>~cFpc2Hz;wYv3XZ-F*Koj0zM$!rBOS`BMA< zLvgj+_mjh0`|_z;pYlhAt!5P3Sq^>UehYkqH4JTJ6a>G@CprC~bF3CTv*tNg5}SdbGq5^H8B)H1n-Fg@!q80c9G$}o7&4Qu8Q$sC3MemK8^D=_SZeg zJ37$IgoBBiex_#USw*@7&%)cp3arTux?1T}{EwRc&hsTQH)#RCwYP8u2!k?2X5gq6$x4&u8 ztacTB3}bUx-o?Q7jiI09jvLDQ0VbmIC)CVDJ3RrGA~V&j_zhZqnl*Ian_Y)#?Nrbl zm){4k+?`kp#lLY{x~raD?ZU416E1`q>gdwyO{pcb5|*ldDx?Js?X_tve)V$(O1ovxbc{5 zs=us6658Px9Cmq-m7c26Qz9sp^0YuRZ#i~X&na1lq z=?-qe9tXPWury#c{h&HV^+*jp!vGaiG!s`{Wp%Z`w`m@G4HYYNt+>bQyTIkBCvm{{7s}dPk zzt+NX9~_m`o9Qz_F;9Cpmp>)X5S}gdJ>oA6dEkeD^I&7#=^pa)@KW0BOA%$37Lh%^ zc}-)Hqw=AHnc-_^HuMDwgZ2ghFS)s5HyyOihN`mlMc>;seN%Dk3Ztn}TDR=ZFtXVZ zhT8TrEtBr(YtO-ve-a#K=^e5w-VCKiMR(~QHNAj-7m2IN#qhNVAADt#8t(^4< z4W!98%zLp8)2uPGtcKBR1;h!mKfJQ23FJfGU@8e+{fv+?AjE=MM%p)$B?+ zu6dq4G4;|vqU^A2e(%#E_=+f?UlT~@iQqCnOw)4K?+c~CZ8_VCd%C?Hh=>sA3*wCk zH$j1lV=j87eD@GQlZRsgSRQ?*PU+diJ$M*TLdHI#(KTM8Jyv0+>w6{7YK-{1h^h)~ z%d(pEA_r)|h&rWmbS3>+tVHp?#8~C?d^V=`OvH*e_vYUD65rl*Hqv8x8EuJZbAd0; z-5V0R=g}teFt>A!NhM7{#b&Y18j87zEi zlmm?@0~w<-d4QPC_ji(TwWf5xFG3y5UUg*|>y7-@O%Jl%2mMfgv{1-=g^Vrr_GBdACc>BQ$?hH8qQN52{xb2~$=2Rb$nLJ@Tes!U>CEK|lTF z2>WFEcjW{CSDi-s*@cy1Y$wMYHf)z2xLL#05r2J=qdzNi@X6o`!2;oaNhtj^p_=s; zCY(xoG2v9pT*@(<<9@OJ;_H_OeznGb#aqv#7(hvH1E{Wl2qNV`tWFBKdL&F_lux%0 z5VW_I{Bl|Mo_`KJgy4{E~{QLf_2rjaqB>r$6+6ZweeN;7kbY zh>`nA2cHR7*Kpj+iclAli;ewSH}~93<5uWpyCyEf-JEnzn~%glPYEx#~2ufZII5 zT1QkGsnGisWM-xSvf-gXinjw|n%YVx+KH{y?~`ex#8Nu9n+c_X~p3WNvTQPJx#Dj0rBzg-;bQ4^9WW& z|MiJL_j@A6!-l|vUG54_*S?MXyf-ZWj(ANV@R=PR>{dNeHF#A*kC)+oExPqrhEPE` zQwW5YLksbX#Ehn+hCiRhhv)bG-Ijylv~K%YQm|Ehjc@nkWRM~_YQFI@tGnBt7kn=k z6>a*7P#g(B!RTB`zC=8m`#y+ZdQ24ZF%ni1mci&9R%Pr4UF!2G_J7VsJw(vt%A^1# z;~yqY79j-_tp+mxN6CKKzhUdA-Ww=(kG`b1KMFlLa@&70N_-->8z%ue9nl6pwACVb z1D?bSro9cA-}+!QqOVd;b@d7_Wp_aIP?gh8*c@%G$L104ZW+GBIJn%(kKfBZ(nRZ? z(@R_1C0~=gB@m#A1Puc3^q7CB?=ogt9HAE#SI9T($j#4I^D^~B>TgV~EMq1AojrCu zb+G>+m$$tjO61~Xp6rLn$!C?adEksd%OTnZrzNbN?egYw$`>N#a5^wtghCA(9bd9E zlW!Rb5p2Ji3V zc9c@f9YRc^)e$pgw}d10F18k?<9Lv0qk}E%zS}po4bJdF0GEk>9HuO_P41ix#9*=) zbo*&)vOl4H8N+zjrih8tV4(_qs!fhVpj+kzqkj0XR;9P@rDdg%P#?@Q5tS5q&K9(`WMr|aRqz@ZzUWySq7B7hYUK{g(lw{!jInQP~N_z zc6O6ic|6W(1Jc)DT7Rka7K<5cZl>lZCgX32cQUzs2~!^;qlc2!L41$wo0gGTh20^# zN#^pE?C$LNf)(pI4;*=EwG#I#<@ksWMQ(`%Ao&veyF2OHMvq2b@TZh3ac~JAkf;PF zj13`$U7%{!>E*TpPr-~yKb}gLe4LxKwPBaN2%>hNpx?i(#iT@DtUubdo?Gs#JX6_} zMA@33G_6WG_>pYX=EGxnbF0&{HS4B^-K;jmagDUvT8H`Xa(xB^?BTFrZf?Hzu@d>pVaa^(@;ZPBa=zC_f@|B@k)Wng|`KdWR)++WZ zL_h@#Hi7{z+x`w5hgC1mrmvro#YW;cRQy2L6jjdrj>E1x;Jfir98ELr63uUT@z56` z_x9yQX5i?|5c8u)s*Zqx2z9u_;e6cUJvGJEo^B35L`fbZ;f-e-s>&rom{Hg)Mz14) z=2XU9j&e4+V4Yjr*!}snP*;Yy-8=xQ+nB#iC>hlVLk)uYYQ?zAa{$n-jye z-c{e$gBVpL7}BLf%7lPs4!?))2*Uq`#3H{fr@_|doA4C?+E(6TBz9Z-Fa$( zb7H`(u);` z`}quX5sNI2M}W(Hx5Ia%z_uoqc(>!A!tz!Ouc12d6NYa$;@Bf${?q{!B9C#Ac!ZSldR@*Norc0nBU{h7N>8dOK>R-~7oD?QY0 z%TkQ57_nbE9&_r)a@#46(t)RJAnAjxq$?ZiRc{*XzigpJ9Acpb4_*U1*c87I8K`ho z-tL#7HeTg?L^j5+blNKaBBJ)W;X91paq%`&|J(+8czpP@3+b_cgmA=KN;tyGQ0k{b z_tV`nC--pAAZ8Y>4hRbNNpE~lHek(_Q6erIU*%W-!k6unJIuMHp^XpxKRDaJtNerY z-4Vehx`66;wO?G7lv=FrqT`S9`eX@rr<;%A-N{m=T8B8xrc`xcMZNN4AC#!*o9e(g zgVdQKA?}b5_EK`HwZDS7n)PC}D#a9hb)+V*lQDy_Ks=`Frro*un9rUR$jK|?wT?{M zyjS-nj?|)2Jlaw`9Ivl940I1B0-zpFHv87W)ove2K8qs0ZDQtH)JQT2k}x=FT;%BfK%m zK9cb6ZCD_D^}!?> zln&^^aCK?)mY5z2<4+@L4nMv=1*}VxuL>m>-Bvd)fxSh!bCep2`j`#q6%fkC0C=Q; zc;D@2eVd=eCD|4-oUmzs;$~-pH(?DuVATOnRrs}jc!-ADWC(l8Bf$JWIv6269c@sx8R`Ibx{G5$-J^B#5cag;BPK??g8Bly^PgMJW=jR-iZ6k8-=`px{toA zok}M~8w~j@A{I%Jsd%0#Vf^RryVN#bS7wES;5rJ+i`2M8k$ETxyI<@Fr@(+Z0q-Q` zvj`)Bfk1~B1bYk7kY~+&2#}sb6=lSO+$|4DAaev76sPQX)BeIcC9H|*8u9dvAtDyE z?bLo=MmOhnTpdy&#yU-rXC5WGXnBqXCW^7^ZyK*1pBs5C5dP=G(}N} z*6j?!RE5qR_*mU=oo=%7ZMeW2gnxI?H%TDvw78*9asrd1Gvl_fSXJ7g;z*#_C%Y(4Z|o6@p-HQsXWt2ZRSfx+6LfN$ zwEr%a>o}6V?l=k*c{&u}SUZpQxW>!y>86-pW~Mk?U66{p17ma$d(>dpAfYZ7KHZ_D zW1^<|R||~lUDfUodRKWKyYlz1yxg@3b@m3 z3FEX-wHv6on`;kTh}a9nh0r^mN1jmpi<%y9pg!iq0TkBn51I8>lS*x-NheiZMuxG! zN*ntz`!(Y9G`is^lfd=gHrIsKcZ7XL=rK{SdK`)r57N;bE$&CMwy+q*==wl!t33#!lLC{$0$^BR z^kAJcuO*tGIz3C3tM(lkaT{{k$MssbKHiKeKdHfmHw`=jk!p1sU7LhEMc+7$v=7Kk zLdBUl;p=KAUAyr(4hGKgmkL;GDPIten!!K6qh(ZnnvDH6UpMN$O?-j6Mq)mwU*w(F zsu26Y@Kf&fMInRE{fG_RANRS&%HACQAt8)Ke}}W~w%g~dQQr#=(GiM8Y)4xBm;oLf%b@`UuA}kJI@&=S z3vM3^U5!we{R>KV?49tfqqc;!oBH-wv=tTv_}C+WFO7(1UFKMBOp?Cshq)QieC^C% zQhB+khPb11@&<7~T+V2IRx^@{vu{XDF<`BxhBx?>gA*Y*8H1`%0Hj$f zoT)y>AK$2Wc#(fY@luVI@~Cv=Ir#rloE7*t>3lt2og1qz7&i=JNwZi7Zo`B_3g+m1w5>41^W zP%su{^W0>=j9gIx72%C6Nrx_td;-j)Zc`UDg;r~JQz&SC_J#l*gN(FlsvZG9D*jUW zv+qE#Gwcr9s1K5@X;4|&H02GyhMQfI*>usb^dsgReb?rGoWD}Y0O+l|7bxN1&za{X z%{Te``s^%mmCT#-?jKeDj!6r~31~S!ATgVe9}pF>x%TYj2m!yo2n;WP4@)AS^L{>z zz2n+wOz7F=vo4p-7-Z_x2=;-x=cI@tJa%w)5>`D_gbS0t7Sdg--!wX2M^P9Gav#J^o zn!W2X^JPQU_?0_Xutjw*JY_)q?>WOjhKhfFAq!|?_!QBH2Iow_VL|4t{``!KXp1c! zd66CWuGC?e7G?q}V#up8E)C&%eG`?aNd4{Kl|?s31CGMG$ENf!$#iK4|HWxq(y;Tq zCv0!a#UX7n;sm_P-AtW=M!xGh2(GYO~#3$Mq1NinIl5_Z@ zc_233)r^+ur?Bqtk*Aoe+2$m;shG`67AMm1pLpSk;v+dKKYhi>^9OrKkv!u&q`$Fu zWqt0)mK#t9)qOh0-&J;`sJs)k{PLk@=8(Yma`Log(>{)n_lr zgvqBG%b@I3VmXPj;%9T3cY>W1MeZvISjj}1F&!ppA&#(G)1-)M`&qV@6wW2JrAj`v zRxI+?Z#602tEH*?6yJUai+5YY{L2fa9uYuT39tZ`wBs<4cif*s)71~L7~%(nar1$*k(tq&kPkspPNGLZyuX24I8!~<{be$@QnCIz^OueFtg^-FLX*x4?SF1NvdnE zB0pFm--KgQIYs`p)cy(oN+AOz0RBftDaWP)83^8ua3$G^s-C zWD5USELP}97_{{6KGCAjC1o4Z=9+ge8o8xVf9HlKN(Ml3Bq>Rte+(KFBl!EiAT}v5 zW}fA!S2PXwTe%0N@nYpk=T3VQ59$+RQH=ZQO4R^t$9c`!U{P_ClR*yiAuVCQC{%3AHe ziVuVe3$o(?f^$b1-6ne+{=iL_n8j|OF=R;j9Qf|X zgDgR8kpOw_=v&c1Ww)vpAt2o>;9wlS4e1L@qOn>WgK0Lr?pE28p>MIV8ujP)J*Xov zBD3)i3}d$!86MUYw-icu8ETZ?YkHgf`gGC4cxzEAqu1vQ0qT?v4Nz|*P#AHlT*>zG zpvv95r!Ak|JW@Pj;X6ng7D*M|;kL^~aW(!CvyC)C0{+4ASj}e7K&hw@?o{F|VY(WH z5sD_7(&b`aQbl9nmId(`_3e=`o1R*jy_ov&!JA>V_%~^R0$3ttf$6Y+*>rDE_TSQ{ zzr_IcWPTuiulU*Guo^dYzeDAnXhR2l6S~f9r0;PXGFDwVr~e}gg6v4Dq*Zkm#566} zfsOY%QaTOoBv(JUy}=6pJM4*SqpfcP8)YQTK&}xYL`-fa(t=O)p7i^e%Dk*yaW$oi z{z6o-hS$Yo;B>#@#?ZfR7uW+4C}RdnyLxIm$o94;W)N539$M^^r3onO`l9%JmfV!K z?&_;;geS6OS$8^(-E=_T6d=tuC(ALmH<_C*fW4}_yWHK0Fp+@PE-HU75&> zF`3*m3HNp+;M3B(FFtA5mqXR?M=AFZR#vVwx3Yh|*hme)` zg;R!JRjqsOu8xwWik{ohxy@h3gCJMqKUoI{&k;hvjt9dGThl}@L)1akm_3>Uz8|9K zJk3w7cw<5tuUcc&qrI)OHx$Xln_uu2k_KC8P3`_9`y=&E`bG4UkZB01&$3j#ezp`M zc-RpLM-TbsLX$+uVUT9oS<#w|t2Dj9!GWDILZ_^|GA$SjJA99J(T+HB_cThts=}sHJk>JlaQTYlaj5p}nD#-o3&BptRk}73Lw-bmBVh}9n(o{He zY=WmcXxwI@n5%y$oi|PIxZ@KvTpC+Y%wzj0{(tZ=IfUF3{T5gm1aud)9~Dq0MWXAJ zHr6cs*b@eej7lE|kx8ge+Xje-=}HRaYQA>*cC(xCxs%+PJ9aNr)}Cs9!SFQ>Oo2|a z2{YQa>%Hzic{@K%iX3Co(|3)uTfyO(9CAh(v3-Q)WHJ%3*iE2dV|;`+=lSo9$D=}> zdXoVwUW)`VHmnQ2-kg_0+;bjE_%`)t@ADT9x#ZSMH#W+|$*+F(A0!zD_X`1EoWGl< z;d=bpV`>8NR6dP}Wi)$=JXy~j2HAX@wIrQpjaDs0BmMHZ;@DSROgj9RtzOv;)|3cm zx3VSj8=h_(CbVY(0?1QZ2nn0%kks&i#QWeVRD^ohcyx;-s*0>~I(((yO~+lyr+?au zw{GPPjvwt8XYuY|9tc}0nZM>Yzg$S!8N_87;cR!y;Hxp-4Vo5EO>}uv@{PBnkC6#@o}VhopHSV7qbf`fN&{j(B|FZlaA;IHvCw8 zIIgxRO^P&w%cQQ-$3^e-;7Pn7bvP-tbJ-emUuj>2o}i7muSDQ$gOtno1jRto<+`PJ z+E|TBOjNCr!?p?R5CP`>LIbl(G46iEX<7$D;orft`U&`F5wj`WL67%OSDb{R)X4vR zw>a=s2O4x|^ZtdjHB!nvvq}Rti)*-Kh>ol<#Yd<2KE1hMFqj$H?2jy`ui;GA=NCr) zq=15%uZuR1+6^O*bz(IK=SP>4Z||l&j96s&SVr=6YZ5 z=`%<_O%~-*#2(pw@hk$?fU8is?c-w5yk=;l)bh<5N|G_=@QeTM7eFu#RCl_zG~oQ38X8S@PG*+O{?pjIT>WL8(%s0=Te)FsA+J`D6AEr-d-T z&-puRBE?=XPdD@XoX*mjG~qa@@!z1Q)^Bzrn$YjwKG-(-S}P(vIrAMn{GAyiIqrju zj0yHEz#_)2!-lYXI_KU^Y`rOg09XgbPif1opFhkEiE}~WjQ)uyU$p=6nw6$OB(EM& zxq>W$zxs08BIg_ygzy7&O!Acb89>u&pL)9-mmNg!NyxGNjSUua_8Q%RRR8W}TD#x* z^KxuN554ArXA`3QWFHktc)OEv7N3$o;;gKHztD*5Ecp!?BxT2`?pm*E_+ek<4K2Er zbiq}^zQj%YD_Kz&1L%f@A{RkEsIHP@kUU#y`_ya(? zpBa0n>n3pw=&*@hs#x7>9$6QUwX(>Vym!;lO}lxlofCGHZ0 zC|8#u;?J|9sRf-sMd)D#5C42rbHOEmh$<+v^DnCte@@hS*A_jddvLdTe(pzcK9D7_ z+Ohd~&Vki%13j%{EwOCda^{L%USU0N{^&cGAhnXzFEr?zUrr*V`jnL0wKHh{uWFI2e!65ck zZ8p=eH=G0A&{F=S1yl=U_%_r0lAL4 zuQ1jjzZIqW&zYkm2w+Er2A^w%&Ejn^xu*|*p*C!VJ)UY4HVGcr8a)P)zQVBQI?>ITq~CJ)%RfIubD^qp7an3G{!U!rKMwx z2K$Z6Gqy^onW})L)!5CxMGi3`I$cf;$-i8DI0Qgset0n@KXUn+!-1AsRh1lIDLOV+ zJmbY&pjW|eas_iN`{n5~d5j{0w`Xz~gsb6;G&2{^Ym+iZ&Ks8Gfi~XhQ+xJ?H~$3U zpDta8iA$E-N%G+|<>%^GKbv(r#kktM@IT51W5OKQPtxumic;5$y8HiODiA|*a{o8L z0ecqg#^Ctv;9*KQ^nGGu6XSK9E8FVT8fqBhB4Qyr`0*~U#DC+mqYdU1KhE-x6Z1DZ zsi4~E7%DAvT~Sy4&T1a5h+(`i`aEF80csK;nHe6Y7xiO1=Mffsq)8)t?#@9jP`Sm? zt^Fc5{r8m}iUA7}V*#jxrm>te^4Cj)TM0Mzy6j&fLi1)d)U{lgm4jE#&CT#{m+F!> zBHhBT{p{9`-^)*Q75SUGn%u%mXe}8gjg}Ay4m3QhF_M5uq6X%4>~(Pj2ybEM+}?m5|{f5jKq4W7&p>=-^x_F zcE!W^qXQ7eIWh%sOAke!Y%Bg4JbOO+)a(Byb5l7I-;}@peTD##XlNt`?_IMG^sM9Y^>%E}dwGdzyfvePAp&P>e$Ix$2T}2MN- z86>Qg^==L-b3n3nW;X0Z3>Nn)d($O-djy^!(t_<^e!YT_hi}HpIi~#zjitM0N}Y2E zT<=mzg=AYow_iN_xL;HX8>evO0XF&OD57GYp}-txI1W=72OJ~RE@>`#G(!a{4L=!< zVDx|JStJDjA_Y_f(r*Y_+ZwB}lDKg7i>a1dTx&Fi!b^>sC>tS1*5` z-@NM;F0~XL+1F~5L5vSeD%jilwwFPRZW8ZHPHNhCVIFpEj^SsHOk=)(-HkxD}Bm> zJGwr#HLnxyER!y>*3}wVKqww&%19G$qeAjf)}A<+GQ)Y@a3t6sGz@@WnedKHjHMJ7Y%LDhHpKTfgsL6n#eVxN0vab{(vIAR(j*oI0 zYlBVRGJj;%de{p`zOdaZT_RZ=IK74eea+j(7a}f^lSu^`&uD0Po#`fqD_2NxO+v@- zIC(Up$6q$x*$q^Ds(PSML#B_T7CxPt=x0z4EW-&S@Z+{+ySc6-ZdO2c~PW%j$CNI4VVnq#TQAKZUp^svpMphb<(vN`}%SCuy zYRHIy@;y#}{qv?)T+Ac!OvPW5(gd-7SK=|m4G0p5eO2myGiO;`S8*B#RNd=%%%*g? zM^OA7-gS1R(mk)VR7S|%LU{2| z!=cbpI!y{bVu%h=r%@CLo5j^z`h)?+zlm3XIDlz5}CsjB_ zxqorxjBm8DA~te>7@c?sdlG`5t>C+ONW;(7pxzF$1!2I{q(U0PPU~ zh7DCVt^MD#2GSU8E=$sGlVaNaC++ApJGFH5;yO`TJCGRo)Css~xFrG1q6OYdOLl0- z(Q{Vb_XfkdhAJK8oPWUb-4!6iZCc+C#R7%i1hUbt_xT!s8#LoV{TX0ZjFU3zqi{WU zv2g?U3m^aWltD0!^A>wxT17Xs|7M`z2NjEC29x#MVl}-IB1+NFPg(qiB*rS%?YsnV+6_P`)k+1Xj?q^g?{|>eRFL<|D_kcZ zMUUl9&wpAP{K_?jfm?$WsSEi!203P)jDm@AWGABjbQ=z|Ehb=G5_Qtw!tHCArQCn| z$PkXwK+DWSzG)@*x<7L2q*{w-g3wUBqb`dkH_M9O*o&B2u)Xp$Trq&q10X(NvV=0m zNFt6rC|V~It*A?%dt-NRL1?_lnH)V@fty!1i!^hrX+sRV85j|hO#~&Gm}R;bC=1_ z9pF5jw0f^-E3-{wKG>m{=@3cA#*%>i5f&BN)!A>x#|QoB>Xc%rT7?w>Jz}1lWrl7u zTJN?sxGb?~i*ifm&&&MF*sG~55jlYD3gl*#V2PoMKBCH{<*<5T*F;cz+bv96-a0?$ z^vHPDm;{li2Dj5d9n~EAd3)TqG25VAWTG(zlH_$8MfY#)1AQmOm9|kMUx@}6H}ecg zC!}X!3Q=~;(tHLg1=%Kj`|Qd+yaKH?l|R)V=S43QLj8-GL=*tL9TfR>cH3?+xCiH3 zoaymMXI=o*hXrOIXIWAajjpU>ul8!N&w3%gMB7BRJ8=29G?=*VQgz35=IP<7HtZSF z0Oh})z+tGIKZ;7;B>q4-tPm+&Z)dsVF2viuA(M8ph@vtxLE>;|eEw`(yQfBe>;2zg ziD_7rk->7vqNR>}DFK>IJzwR0kvBBr#(fpBg0KcJ~dbUK(`B3y`W z$5l!*pF3|$+DmCHEqd${jqen0#**h$_rYxI>P`GJ#Fj(2)-2ea(5{KeBrGBO0vyci zd=&)Mr`D*?#G!g;p#t@<#+`m71KQdtOrY16_U+w}1#S4IlSkK7ose!b5l^8P?i%<}m1RC9S#zH@8d-&ORHHT= z{xU70WZJWyQXdQHwu{;MA*OV}35n+8M7WQ95|7d8DF3qI1`vQa0R#H=?i{^@a*gg! z|LR;vTpf#YmccM-o*cgR8*ajklF(;3_4B;$9BE#nHkg{bHtcz~$CnPX(!3V~+SU{n}!ow?ntn+EG#izi^GIcjY^(k9IvkZ6Pt zQyCT;%An-ORYuVDajzaCR7OhRMlO+lzu&ACcC7+(Yp^=Fa@&t<#;YE!h#*VO!Dn5tOH(NmaYA7dL2x5b zPF<`DEFJ>8l$tLv2k}Aq?Z5+N;csS?Ff6EzeUl4to!^MMf4n$$=1=3f?6`w87n>J6 zV1KMBAC8t^E%1e)+b(sCIx~od+_9vRo`{y2f?iPtJBT&k2E@Ra^gdi8Y^*OYd&*JT zWt!b^E+m`#cd%vmVqYgm1#bjGKKoW{hl>(g3k=wtQ~B!gtK$3a+$DQyS;;i&d+Q>t z#ZO{>&_C}4#GWkZp-S5Xd1Ym0aa*1rJSzM37+uTwh84vzpAv#GSG)@|6Kd@mU6aJ*)xNxRpdo}<%e9{#06N0 z6{Dg}hT?YM{WfPG>w@0Sg!pOtg&EN%Q+o3W*@L{fWUm|;=9I?R9IQrvYRJg97C3PD z3#Co+M573#=2#(X{j2uCXoz4_4M4Ujc^jtq4PIxggth^TP!ca%jNKuQj5`wMlb*s- z5P=jqlXFI^_3Y8mLv;Wd_qq>qXlwoLn1%X)eioaBylDl^Ltxa%I#`JZStS?J$T=YT zc$=yEZsBk1*p;@}0_j2xoSdj!n*~fcrICNk-RshmF93Za8WboI2I0Hloj-xU#*4t^ zmRx2R=|mHWtctkZqBYCo{r#>sIUU+a(!!6N8@51Ld3LV2dSX#NB+^8U!S`Q{(oI#+ zY7G%M34^I-3lAlQD5O@LdxgMvV@(eyx51#O&Y(}?%e9GO@8Keh2S3bxNUeWO#Ia$) z!Q23j$_5n%bArvU2FxTMmV?=8U)rNHVtklp2ACT0`oKrD;XZN#S0>hVcLefioefo# zTJ4i!{kh!-4uR1Ho76DJ76MpB{IB|4h)SXm=gVpHOcP%Nv9(-j==5!Uxdh8g@{Bm~ zYhxls%s~Ry&=7(ahkW1&F*a#Gw(%aHABMsFLM%_T!w(H# zX9MXU-LLD4*F8XCcDa#+D`qbRjgePsf*)#j z&a|0Bygf&lRu#I`E}H1%!6RN5OYELUa%<(ADD}(UHpk0z;ximAX?xBPC#{MPBV$-B}<7?*vh7tsfccyT%XBGuReLAYCVUKV(9K^LcLsw$I zB2LR2b-hSqdR;mM_=*gG=XDfG*D-wx;3bTe3b-YBoj&MoIBAdsch(cL9!k1CsCdBHUd3`2%`;` z-Ys*0vqv*2>kI29sN#EpUrrsJmcR8HLkPs*yoP<(FX|7aC@pXQ_mvNjSZoTB4Nf7^ zxQR_k)2t|PK>hJkT|$I;&u|^JfZx+0f5V@$AGto4e9*y|9&!~Tb`Z^$e@oP8cqljg zV(ds_VVBQ+&t+%d^rh|Z!JGa;& zZGra&Nl73<0^k6XsVF~s7RI?=3mic7$x|frFWJYRn~r25^Z_U(+-7ma_Zt;R()$n1 z7ten-jQ3xpOYVfb=U*Y!Uk9n-j$0p{`C;7KIYP5r zD#+%}Nl@DRYS(VNyL?T(?i?TRZ;k_n4xqvU=#T#Om+^xK)9fRFIQ~ABu3k)a*-+=E z1KD@(^%&)azrH7&J5ZiAReuEGET4>VSfc8&(j*-2xvmcnJ#OL0eMp4lgBh6`rPjB+ z8$&Pwy&n#BuPtgXx+gQ_#?V@D)_5&917T0B{+$|sD@EAP?|}aj4ACJ;<8T1Qrha%w zeUmczo32O0A2pP#_wmq|1s z(=e5z4lB6hmVOhl*}h@d-*kCb;!{w^^X(O~8N*RI7F)f2hzeNlMJT zPo^qazovz9k~U-YIJxXS1F)YD4g@Dqu3qH+Cf#~*b)8~=R{vJn972kAMk*R9kGO=v z3a>qkWYEqm@bxd|-!j@?Bs=KeFN!$?J-6e>u8`W!191LruJqfdj8t{-Zjo z8eMz*L>M9rMr>{=w;}n0mTdyp{ZBZ5_(VX<+<`|nf2-nLfWHYxJDm%Bf~G48MYpM4 za(tjdq2fM!xg?4ep(P{Qz+3hE@-|m>xrNy}qFPEa%Q}PagY&*DvRanU2wD-0H6*sg zZ@mix9!SOjB-i`8Wdul-G_C1UTV2A9f9wAday(}~!H~$2s1vuXzw#?S3AyY^Q@f+_ zr+*A8XKX=BkNm3B@mPj0mgB`dGro>J70X^h?~zbD3=O6OUBU=G*E#oOr<-qKu@Y4h zznkecW4bA2)?%e@OOo^Zx7hmFkQt?&AFo9aXHiV@rqPNvy2 z6&oIjSwUSXsE5Ruf?rI}0G10E1h!3=&p+WeYD8b2H7_i~GyXv3uP{-&OX_R^({w?-B zm;~hp4QOye3pA&y&dcV=F`(mIVzMSZ3kzs|<;bbCU;Q!sOA^MDfh^;7h^z)28B@95 z2l-5b)Gt<_C!c|^6_6G~sYlYDD!(T;gK8ng!W(?6P+ZSGotEfmc^j!u$jFj zS0>d#2hzHU{49V?VILKQaHq|d$FIaAZy&2GX5!s9lRlnp7>6lO-*JQ4l(2uL0I8sg z=1S5ReEZpvZ~VZ*0*5O>l+`o4*l8oK@lmEVBKS?jm+A}LlOX~b17|-OF2)nwk}@6d zjrPdKg!#43b`^4BA|jOEc^GUnlhRBn$Zw#lJrMa~g+yT!J#zMK{PC}JbwifXF5h7= z@`*moWkVpVXGlb+g>Z#Wz~{dz{l(+#lxO3{`E} z@BzmxwK#Sy*ysdh>C87rSjuhSOff}b9dMqPIU9*BFM{bns* zebNs9tSQ6aDO0O5_SVGtlyO1?P6&5SOPDA3(+VJHLyO&=4v!EY+FquzqoD*Z85>Yot%fp zlveE|+{RrW#n?OO|8&Ec6Qy-W8g$|A61kB)66+&B%f9kCbISRxbAdw)Pmq93_^qlh z%=<4 z)n#(`R;U_idlp3R6~g`)P0s$Vez~`X;5*)MNEZ8+i!obR?wk~#okckW#}zt=s#Qy<66Os2(Al?VlY=$ETmT0P`6%qs*Bur8WV>G87oiQtnGzXe#cjRc zAnB#yCl5T@fOUox-7nd8a2va{R~~i}$~8!y5GB4m(G6*~?$PxhboOpMiWi{2 zkDvo~+(2xwACf_g)rSeo36h*2_=M5pZC2zAOwLv4ttkv1E8k>%W1#T^nT7i9@(eE) zm&B?ggG`Mh79NMU*l@7PKhwJiR$aT5?0$nNS8~jOR=}Y=s(!vz!xmCN_!C+DJEgag zZSHiZe064JApelqKlQC!!$4;1VgmFO`nMbiCLXLBR1S3vA12(l(mjv&#d5>MhnxKx z#L^%<1-J^2(m4kl;e79Z%dTY(NX~7pwPCu`>B*TJ5pY%^Fs=5~=YOI`Mm8%73{!F! zg+|>^S2@&JKyunxA553m3ujPt@Zm+_|9d^vL-KEaR4xoKoIrwL4iCE~_$tc2d&{Gn z^OAgP%=xrIz(x9L@pu1`rleyy1WBQ2H4mQ3Dq0x+O6#a1X|WD(6`z`+MH9ZWB3 zPd_FOp@|CVA5|w{W_&?GR^8*Mg`D03yGuxH|Cno1Lq`tHg3<@_w$+()p02Zh9ngfAh zg0yEgAq6br5P1oUrfqgv29#Is93NIX6ar)EjWt{nISFsRJ zDepE*sGfWdI98&iXhizc1NS(XSG?6!BwWu$;IIsBdy#wb>(ioET3qMb4ci%*p56q+ zrn-Mxp3REE&6>c6(Mk&`%RdBCe$O`$dzUHk&W{IAcac*0**^^;mGJ_e1wLjphkkV_ zb$Dq%J-^I5c6@PfS)qu2X(4Jj6G?yqTcGp_{<^!7*47RwN8^SlS^L$`NERGw9;}?{ zLAxs$-kR~ovICp^+_C}r(=L~+H~Y7J&M}+Zmo7xXrf2e_x36is%T(V!!z9XxLz+|M zV_~M4MEwa7#lw49{s^XiJ5iXdf5VX;+aRxI#8D}O4kSuVB0l>f^e6}t5j*s}^VT$T zlR=193!xyV&6eN7fSV|12y^3nsD}MlP!D2CV(wzj9?;rmo&tL{3d ztt7_>!P`H7D~9f@H$KHrkwrPWxTH8XIfgg}kmfnUOG-*bp`#zhCdU3jk5#~jNo1mP z>lmn;*tjElv^(qEbh5g0)|+?JY|vd}kVYLQfyA)Zj`4Zyc>V^r!O5-@ctT~(SevW8 z_hET^%OqE>Etj+>Ew`c=zTGGLBP|4nn??AJe#0U6k5II_B3z9ks(M{>WGW}D;Rxc? zx%#8G>nd4z-&1*Da*4Nl3G-`uRTtHl z+bJvQrpOWE4M`@>H7qt>oTRsMCT}TMkBPmUBHhTMq4MKDT@r+oMCkEXs-$!f%pW!Sj^$;4}feQwdr z-(=Icr_=?Wzh0!^epA!;y>@*c&}RjJ_7{%0^C`=^CZCM@)1$FH3s=Mzv7smJC%*j-%;(!8=1PDhkiVLs5;EGiKS-Fdfnlhwi9bCs7aSW90wRr{?Nm77b947CaQtN0#i zyfXwCTp%WgerBn!)CrKjz4bCW@Ehz6@i|4S`hrJK#zfEUFkJe^&^wTWZQ|+Uvrvgh z0_kcYOzgxj0tsli>pN86b9Tbo$d;hW*~C1guMCIPcVqbz?^h($wkt=^Kcgkcf=y|o zHttQhe`$A2IEfOXPY)!T8ah8R-{>gWQsME`Lo|v}7p8 zFQ3Yw^hVvtB}b&wElE*pJboLE^cLH`#%?_5oZS|2*;iG8+6Gk{X-4m_URiIqRo)_i z3=#km7N~Sy_id|*(yIu30ODNvP0XN=I)0IzuIhf`xCSi5o7umW|~wxy_yx%I3`ux>tT z&_1!3^vOBeM)VEF`;Y>a+7Q{Ep7@Q{dxQAM+&3!&0FsQ~BK#b={b<>^=Z)jy$T@8G zhpivXINm2+&&BE#YOyRfuUy`pvrD6!-;GDiXa8nF5Y`&OI2eas6aN5Dce6k2z&>r2 z_^iZ>-d1|G8H<02RRojtQQ{uOwvYWC?%4sb@NwZ8%@{f7e9*O*_ zl4%dZQBkG~uQX3h+p8dp#yPO`>*4l2!V&J zkRS z`I+~=rlm?r=ZWWS`^gw{o)UO9edq#vx8E80E#orJ!fb7p2C{A+{=odr8e zhya(H{RXv5Hz=lWqx&Fxq_jpUR(55-B=AFrxyaMh2QFP?$I%p3qK=Y0Il3I5+P>$xGrTzeO=e zMUk7-d;ie-@P~M)3aTRDmh*%y{SOIb4VxuKz3T2-))yK^WeKD&E5rsZPpjM{PR|WY z&XG#>z$p!A=AINv@uqjcQn5Y9+~0SjqsinH&)0vZKwhBm@cw+h8$4m%fQAIo!T^d? zxl89!_an*ClJ0zqw0J>nT%#%6<}=jGw7N_i8O!jZx#HQQmHoOcP?NNM_#Rq3$J3dq zIQg9(l(`PO#HPhXW?K?L8V`&GI7`djw5fBeYf(vOF#&xM*n4ds;VKVV#jJWD1)Gb+ z3)-Iw;Qoz6C1{ftF(V+e~Pq*{&3}v(Iim^L?D$vp2)0YTB-=(qt*g-`L5n4H|o-isKM}BSb zmjME5dRL_+!erkqXb>r+=8=PO{0(5?L4&Sw0ZBJqx7AaT7q`A!>hyy(v>j`Mj37Jm z#~e%VSGMq4e}rtVh^rWg*s@0=Un*Yxuddm_;@#W_1%j<@a{@#C!m#7!pkt&Liq1qY z8nboSOmq9~-6IZN#gg<5%;+1h%gnVnAxLo=G2KPQ_BfO`-N-&VfFlRQlA9PaPP=8^ z))W`@;vB`Nt!7g@yX74$S2Y`YbJ;f&fYLFPK00{sKUVp6zk}e?Et2HuE`n*SdI^gV zKrm{}Ua-8~HHtlVty@GmWe3SR$3l}kXd2(s;v|4vC=0j3BfqcqFboA<)YcCQ;6Ca6 zS4F{@^DH8Wz~L7!jZKH@LfkZ_mK%GYbcUG#<6RB8NL{-%wT+DSE;x=$+gwM33p222 zo_?vFLN|%1d;W-vxpCbd-w+=3-bvCyDK0&ijgq-I5VI?)Nx}>B?B(cbN>9~TMs5r{ z#qT7sIU;pz-VZqXIQ_<_*?@%Ha7P3%J^EJffzwhxt2NwEJe`@J0YNU)RH=~p13!?+g#)q)hCNK-1Wpt%Xy}nd&t=SQ=w@iu&A!Mh- z9M9Zl^^t{e)VQnUD!rEuHj6pjxRa{DSR6_!c(Hk_WDg_&V=xC0x=-#O{4K7ORwo-~ z8g$lFIE5*j%-=J9r&pZFg;pYA7|e8V&kJbD{Ge*yr_9tA!s9h{#0N=+ zz;Q7(@}ZG>v?#6!oZ5AM@l^GTS<=8`rPkB-X9O0_0q&(6-`Ac33>#~K7vVs`|6*=@ zKfnV{;4uRfN9MEe>+*mI$ir6)r73m8Z!R<4vY!%>Fn zhHNR$CLTC`5wz}?s0yAvlL1J}c8Fn!I`xREb(nez!$HDgJstJPgoZIiedIAecnvMvxBIl}mXbl&e8>*Hq z(sM{sIL`Q9s%WFo+f~CxO2~rsDzX^V`sw8^-z`~0z@UGl# zaWCw~?_>)9l0yCl%WTMi7g7L3;A0}w(bj>S5}w(aRQy9#g^dg~o9`tVC+U2+Qhq=l z(`=#bn+5gGwkMn~*>O!>r?>2Q))FF9_+gk615fU@p=s>_My#A6&(Jx3BH+K^_yS%I zvE2SP)IGaqL?TDD>lezKae{($4xzGw_W#rquV?_?rXa8s_zG1W5Zs6{Cb74FGltZ+ z$ff#lF+YTnY}TmcBu zi_R)wgE_j6YT3n5Lf-oHXk`Bofg3BYr{WL=`Sus30~_?RZ}*fWYK5)!_8CyTj!5w*5omF{j6 zJ%%cFO!Z^Ejl-q)*U)bjVYMD;ZOg$z&0Cma27=_Dr#)8SGJ@yF^pZt?v}U&xwq{+I zgYZ;>7WN^UZXI@mMmL6>ubeoYb}+%Ki28Up>UGs}-M^ra`}y|5eav!$_* zBbZ-Cs9RHCls}ys%(0MZ%OVKZ+TnJSFKVoH_M+q4njt}aBgr3%s+EvDRq?u?;!eDN zhhj%(Ml(yS5@M$S$--dwFl1a6s}`0oO)xX>x5HfQ%~?nz@N-?#sbIne=Izuohz{I1 z1L#zA(xmB+*TaG>=d*IR9a0U;@iEZO(OYgUC|0@z!lxiES&P~wA1pnv!KZh!m4T`S zrYgBlOF231Pb;M)c1s;+s?U`-plLf~xFraTfqAHC>}~;5=5HkLTj2XHh;H+|_Guq9 z3(8A7MvGl;-l&P$U|$G%C|})l;t%=)rga6YWPjP7V&L<(_?LNv%e_9QPIjD77R%B8b8)%m{}SgqqF*dKM#~oe>gRbapdS(dr2r^dJOenw<63FMt83TR?C&}IP>(P?f zC`R)TZy9z*c*P=90ZYglAM}b~o^FgcscdSgJKlef5#}F2M73y{_4vI;hW`&30w|Ea z3E1Ga@VA&p0qy;5?Bapv3|s$S&lKjL%~TlZ-rvkId8_=8JaZMwSJkh6?0P$x2?Ox# z12qy!*g4nG=0Xizz1n*}v#KYt&L?=CyWcN80M-H-xzXnCF)a(dUarobdo8uQ9edWs z9c_HXmwJ~oB5(XDG%^7B2apZw=QNcBga;HbJO~wmj`!4Ed zw8z2B%4Zr3PD|d7^W;-t>kzoLAgqk^IY_6Y7h+n#XO9dP)z@`eI)J0{wb#E&^&_17 zRpnOO@sL)sP#s!Bqe3LR3jtX)^>Y@f^zEgIAW5&0fIv$h(6jfLrpfU7&bLt+eKMHG z5tkm~kOO%Uys!3XKl^<-ppCkVwrTk9(ER8}_Js?f1=*B$T#o-r>D=;zjG>0ERf)wk_#8IKCbM0yhM2V3c zD){)cdc%pq?tDHs$J6*n}^@V&+$EQ`P0DRB=n1yP~ zm=etk=;ptl+Yt##;1jc)d+60Nl=-tx|3j@+sQYF8ye4||KZCaL`M-`~`K2?i6C7A~ z()8cUD5lZ2R+>1Yuy|%m`70^jesNsLt~z|+DEal^h+?LELr{-uJ4fv=ji)p9^LmqD z*^l^I)KV`cNlT0yLrUa1Q)FPfyf$vXUNSDE@Fx+znuNOBBhXs z0_6RNfJfRd`Xayj^R&{|`(5T6`zDd+1N~W$>?ErD_Hdu7^#7}8a1gO2D1aB6n5lWF z;nH($>rGzH^U8xuF*-Z1ym7BWX!hZGH7L!=6+5dCS7{=^hI`~iksQ_gGOfCpa`S9S~^D0Ra$~NnOSlE{ITzk z9cuulucs;Z-a|}~RUbXGPtW@ihy+q-LfG5R!0SpfrRZXzfPZVf@!JQuCT@WQ)rB}e z^oPH!T=$_-=mjdxvvbmprT@?&!d1h}?L&mIg~ID6Mv3Lmx$b}>^;nYqR3yc9UpE>y ze2`H)-Vx{dOAw`ZY-gxlkW5@4S;DlJ{oA)eOsP|xylZ>pUk zlfv+g;t_}rU_!n=;$#aPkJr7Vej@v#@jJhp=0(BKOX9EHsqNWt|CtBYJ9p6M_#-aw zC+1AB2+y`qpHOs&1@YadXTw94-h!^^#J>uM5$L$96C$kqPhi)6LQPr9=Owhcl`u?F z6pL9FG8|{robOjh92xmh-%+?l-pE^`;N;+9Kmk!oN)9PY$fFs-f0;I}%zqPALO*H7 zqmcF8AH0?C8mgotD5M2mVVPi}#YxEA88t1`r!4hl6K0X0nDK*a5gWSYRVVlZ=5wL@ z@E`<`sH4qkp)hxV>H>?|q53(LCUJ@MpQLYz=B~aX>KjvU*pM_B2-Sn~MaPY|lujne zoR{Lw{~;CM(iee6@ID63>)u}F!hZNUfN*lStPthv6zwA_Xgb}v;m>Mw-?xi++%>^u zb8E|KVP2Y!2AoUXHn7i(2{=-z)@2nz0E5bEm^qF19ofnTYDLM*S?ojWEb>V*C5QRbcrJc__tmlwm3n%k*-y>a747ezdxlIrEzG3mSZ?yJ+oSDl$x?>qGH^RtB;K=s|7QnIGlRoN5K$6edNo~fc9e1lZ zz*(bs9Scs0EW4vGa?dI#LXXyOfGqgLq){L?1eS#LZA!xg>`;g>G%P$c6PSPA73_FN zU}>BcZ}Hv%J2_X*53_3r>Vr$-4BB>K5(VV7Lt>T;ytBjmyDdnB4p|bLIIqB}nE&zu zcF=*JSwOZ+&pj$4OlvL}wc5*M@=av%4$ItzYMzAXVBb`dip@bKF*ZwNfpybudiX&d zK7976GX&9vQO8Lsmcc_qsUQQ3T%!x|Ni(t|cCfvu&uq*T&L=XXPgh*9W>()ZlEjz7 z$ZfR4tP_5uf}I;Q-+DG8*!Gbhh;5n^-*?t|+UdSJ?;4h?+W(aS9p35`g+z33v1N(P zQ40E@UQh+i&UNDgRI1HgKad0j#|bVlVM&HVL80Evrhl!j4N71DGh2!T(Txj-zAt0rr^! zX<~LRX~EiLEF~p0H3Xj>kI*atr0G4YvZQo8`)Kf6qM7quNC@$tAiK1jkEv*p^US5n z=nfr&VfZcFdV=uag7)L0?B^Ur-k(q5AuZ53a-(UIB_DPVeD(${cBTTdnymk^JOC+p z-%B50((;mT;G_$2UWO_tnN;f6XE|v;(Dv3EDWTnifrT#%Byke0n3`SUWS^^Zg?@z^`ozza@Iji#gy23)5o&Qu|n<4k%iV zJKEFEjSLlgKfXDz`yxW>C!+wDB<~h<@bt_^<}{Hm`E&^M9-hk^b156P8{P{KYj$&D zD4d=-OzTNwvw{;E_$OZzovr;=PP)IHrsev?R`WllS`3$>pXT6~_Mm+M`zgh8j=Q(~ z#nS6&@^p1wgj7U~NAtqa<>AC6jO*@8N}}|$7*FaX zcAQ}fTYI{Pchb_3v*Pj@ZN93n3-QdOM5TX#C*{6BuFg+3o6r4UirgE^onSMKtw)%~ zuk6dxQU_prmlPmVKYhrF`k@@Vb3bF&m}_ZgBJp%4QfBxe;PoX(AfiwDK8^+4vq4uR z0Mt8lAhFv;;}U*07|D1!?SVn4wQ|a>KjU%TB|yVOr96c>E4iBeV~^z=R_Q z<0r+oQe;d{PG8mjUgnocb$T;HQ?Ynkr0Ap-MG;=oEt(rCx0=U1vHWq3WDe$>AUy1XH{vPLX$|A$M#$=v7Kh(K;16@AWt?p<9pu0^5b z%0f-O`R6j*em7|H)CZz#8h7a8)hl)5B@gig<_P+OU!f9iHuiQOXj_;dC-3jsf|HS>M7;NSS)K^g&gB3JvA zP051S7*ki12#FH^0D~_Cq&_kputj_>?OJ6rcJ5uBEf`g@aH&2jd#vJzDBfjoD2#1? zLLDm-%_9CG&+8P`S01i{SLJ&^+;(SEk2UX*vxWFkNOnY^jJG2bjMM(zEchy!r%s!1 zAPX6fu&VZDdE$UKyFJb-2pijzm@sE^+d=0|?)jb!7=M1f!_oiwGm(z#sWQnS>?&Bv zg2uNfRUkC!51x6yjSFYGAx|u6$5j)*d0ICSz}P^yM!>j9yniR8JH8c+9Vq}UfrclU zk;QRLZsX!`CE~%F9{^JYN_pP==^;;5DE2zi@vHv)C1t7!7j>J4d6sMa?LRAg1>R2) zLV#XnK?5VM<#-!ew!0BF=j{4wV7MKDqFGj5eLK_%dOc1lFMAe=cLDs%Fb_GBKgc=E zz%Lr}p|xCOcFbkIGWuO8uXnNirb2{U$`BefNbu=xs`ebegmZfa>_)u(ybcDs@2Qqc zTKffx9N&I%6v*sX1fc$O41?x!&+kJpn!=C?Os}!-y3n<&(k^>U^~bz!&vYz>%CV?1 zhaau4s6flmsk5N&8U+?*6W7y_91~3qt+hHG1c+ghoY>MAl^!fB<~LftvR)0lidQRM zsP=xOsb^@B{fOUwOP2DIm$#b&{cmG0kO8Q20O@XG+Hy+ki1;BdUpv#NVB%A_da2d~ z0yD;P5%ViXVKbykbskPK=g7htT9Bc=n%6+hEIy-C`7)-{osy_+F9ktlRNScQ&|}04 z(NYMQA*{QU^^Rm!o_e^_UIw zxnHB|Eb)kSuh0+E$`#7-%-fOR7Q&T|vXwe0$qW1W9Y&{0Yrs% zDBoauZCxI*zP6PH;mgbNmk&5dALV)yy{CeY1TDu1$c)X-VE=A5I;PTFl)Y|>$_cpN z{$<8tbegV`Vl>6n`cC|pR^(q9?}CBYV1fnQouN4b4~j;apUg( zJ}TY5Tqp)_43eLHmTmUh{qQZjhDTun&hj06oAz(GbOjy2a|L3{>wnXYA&26s z9as)|ZbL}*HUG*z5--5yl5%Tkd=0}#Z-MG2Mfh_6V!c7iNwU2?ckUWqsrxLQ*8XuS zsI-cw--ksJjc8kjsY*HrTA#JiM#O+wW@g)Wk zu9cVDoS1R@JtZ#RvbsEn!prwtSxZyo?tXavl6~Rw>GUTULgNWOZWG4cD zFii}^lxX1CPB1cI-cYG7yze>^5IcvVkk+8jc_PY)XI;8h%B%fH86zr6-(s{z)Z6Pk z4~9jwyx~X-4q}eT_eP)GShi!uT#5Wq*w6}reAo-m0-1T#3I}45C9^}=9_$REPIK9b za-WI=UY~v|EK9$6m7BvD*<5#FzN0F0MgDUyi2usynIOF-DQ=Xgx*_2II=^mg!W1ymPjnUbAR6Zs^~|6W|RG z9D)tEsN|Nyo^QwHl3kRiY2e7fo1=8iaBeyv`nxHm<6!kCNb~u))cA7f2!D z`3Wg1RjwJDc(^J9U)Mfwt6af;%~h(6_!Kh*zUk{xH#}&iuDiC@Q}OJLNk7;ir6J4u z0wEz`v`g&ycaNaYR+EcbR>5I~wQDvhrA8`!_ z{Cny&S-~GzIna|Lviw;bU#d@t^d)y}iPyAA8CO2zy7VzIm~h9Ng{iz1(m-(RJqAR! zi}lt_LT^tO4sA#o{3I#Fp6 zP4*X0Sx!$jc0IpCq- zJ)OYhI2=p=)w~oO-v!v^>>Z7?tPfiO@A+N^PDcFxnY8^))6cNC#m0sNk>deU%$QF< zE+YpipSads&6u$q49FK{E$wTq72GaAwWSJSO(IZr4RQ+6UU@5;*}NaykmqUIN{hWa zSVNA1Y9}F~-mHIaEq+~~U3WqW#Pls4plSckOxp^tw5EwwS+&9{fHGLfnZ3ybXH4F5 z9$)*0&RF4r5Dg$yxU>H(A;m;;KhQWT6;MPJ+9W1TaIT4oq?5+%b^3%|jLAmry4`lY zhw5v@7^Eedzv27H^%&)#<90~XUbdr5`DuR}gVxJ#m+teVE?SeE{aG5u9+!B-*Q?59 zPdR&Ubc|sNwPvClG4fUVAh>@ts8A7rk`(|Miq>9MN=Q^ic-9E(AvnuU@a6a{uWCMx zotOs_H^x>IP3rb|*%Gb3f=|Ud`$AO6f!J0;(VDeVL5Lw?Vqr9fX8unis)wa@uN@+7 z?C=ingI13BjTKkzR+Ee+IUS^%s@k7~gqRw3+D>sbQ(Ii7UsOKYte z-di~Xhg6SI0io?TaL&yg{bX@lWp}l&0x~&&uj^n$8=yT zS<(o%qsiXG1}7Yw+byLML6!qzWuBa0U;B@0vg&pJTn^W72iu|qfTuZA=zw7cFv;oc3^yBR za&jD3WunKhqlf+tUW?3{mG7|k@=uMk*5JPm>&}x1W3Mz^fjsdp~NWfC%aihu>i4Mf_k=E$rQ5% z$Mj*JQTd6RgBYR!|EX8-oPB&n5Nv?^%YV@NjIO{P4_?h zhVDCFohfCadVYzyGKJz2(Ad_Y(_rvlqi3HSKWRKDTYh-1|K`qdbvll^M>63lk&n{u zu58wbjAH$ao=Q`v&aQs(pexkIAtk|Js)-`0bH5a!#pK^*>Rc&DE(!~BAI7J8pgC^@ z{_X2Q5x`0t3bY>|FB3s@90h2N38PAic(y&G3xx~$_MXVbREASNM97_St-ENxKrhJy?q6qd;|tp%kQgHi<0;4j z#Cwf%HDQT@2;y5z>u`GC>L{4DP}nR6#Eut?4HVFbGxx8cvjqLF7O|GVSXq;vF#MZd`HAH*?Upoao|;no7Aw)8*G;pbPBT@@Clg$!I=jq9gLA8 zQ_b2Rx5!Dh=2Z7N77e=Te%uyGhcjxjk_<*qOc721|IQ!SWWX zGRCO+KjdQxuw78689uW=ev}5b8l6Fn0)HeoSwCzy*lRW3l~Gd`Q%v&{EZJye6MYk@C9G zahvT>)TYd%t`qnUyJ(8$qsOnSYuLiA0 zsnb?A+7IZ93YMDbiWPS%O@M*BeCm)VMm?I}^u+Hl=PgQlWnK>VwMTWY!)F=}Q!$v) z)!v>$%*}eqH>O4@7^L(%0KR4=B5X8;hbH23)fX|NS`s1V&J~c~IEb*He>Ep|I#)q_ z)LOL6zr`<6$|Qd?g9cOM5g`t*+Ie>Q zcXGqRsgX@I3MrF_U;I?L>1&|inCZe^-kJv|JYeSq3_iH12j9CAA?ox)`^@nbE^wkg zZ(aZ{=3N*l;`~!P^phvKyE&*Vth=aP@DMcymW1D7u&j?$wQBm8I8^yfeUKqi)~E?G zik;`F5`pTh6reDFchywEko>OCu7kv=INrvjgq`yJpW{TMzus@q>F*~?z?`>*n%zEJXlJekGg+Y$ykpKo^Dv`g(Pis> zMv^|pi;MEouk~7ereuE_47>olf#VA#i+s8a=#2ImM+pi8@`cCC??Ahy2%nK)eK++L z?>+zXG|Gm6h|Na;GK#&+%zErSHA_4_tnQr1x<{<~PRqN)f+CE5WXLW&!VZpO2;e-( z-MnOhW`sp%4V-sVb%jR;kSxT`vVJw;1rb-GARhuW;ih-l{O`uIo@1CJeBk|V(IH~I ze=U3Vb9y?-8TR2SJz|-Tul!#Ik&OT`d5aCSf+}_Q#?vTCZkGGPsC=@8h^Qn_b6j1o zSvX==gf*QBB-L$HTgt55J>WLB5X2L&`Xv1!Ck8)dDA}|$8a3T7Ou8ii)HYhVDt6$0 zNjQU`)f{YdemY!Ti=QlAGDQcOHaqV;7J1#!gc0xhKa#G3t%@y*&ZR*bq&uWTP>{ZK zcehBlgmi-f(%m7QBHba~-QC^Yy!+%A%r`UV?6c0^E57_|ANk~A+jz@ zH*N|qs=UM#nnALWIK?n+L{b3m3U!Pd7mZNpTy+?=W6nxE1@(v*=UsVKSB>S57V3>x z^W3Tz=+API2!JmAe0>Y)*pY#>^77r-Ip2dn)S8Z#bgc#Ld=`#Ep9?{+Az`1JRpg+P zH|L)jQc`%q3zV`$+H1+-j+Exy>0kWD3ZW{Ctpt5?!49;b&Tel+-6lcp4%T8vYcHlN zpg2Mi!Bq%7*B7r#rRMX}>$$ZHhWP6MU>*t{?fHHfymZTA!Vfqt%ChlA0(XIpqAXrY zab9%!^UBc!SJT`o_sMaK<7a|%9TDHJ|A+K>uz&EH_>3Q=&!91~(1)}1% zV!X0YiQ>a}u{@|=`K~!P%znoE62L^p9{0htR@RK*UrE`81S=eYz&Z95C*@1Dt?9YC z22O)jP^Tp~K$g7lfSW39P|I49CFolhUr~W--~KV6(9*&ijz1NmxOQWIjn#FCxiT}O z)B5@4a%p@`S2nN_!u1P4(Z`&5EKfZ4&}kuyTO&u+e zsDX;VFZ#Yxb%fJ2qy<4Rkw+r^X2ZT9g(@wDDBv0kER$r=!BT+p=o0x-6#EFJX9AbVG?s2RE>EThNh;HU_^O2TvVkGl1^nV)hw^se{5H3n@U z&iPC4?zVu;cPBfenR2gko$WjLnknE715wSl1OL(`Axe9jr5}?BVUn+h_2*ks;LR6q zAc$zGO~)bgStOvv?JEB!;#1mQSxMBk-K?KY*e@?R{5f#0hiEm9BF88(FqV(SaI|;r z7hNAhqfU>~?j|FCFu*Iz8v?^~RlmKltN_(kAOh}|iM1Zc>%k@+Xa8d5erAE9tA6uG zDZ+_h=o=UQzh#Ce2=qd|+#r)h;*2N=UFmF#o)27QbNW%Fh8H345z4V-v~-APSWZGW zNN%rVRQ!Zp1c8w*a)v1$aSDo(Kbjgo-(JH{D{nV-Q}B9oncs{Z0zzgq-{0Bt$bZC; za2bI}c$YUDDHmUs~^DQnGlCGj}zD9psE z>?fgViMxK+CJO4ukM{8+1b%Jsj>>LqX>#)EeyQpnRO}Q(6z)B)5dp9D3=szWgA=%9 zR!^JuJ73Awnr8ZQBUxsiLZLUIU<Rsjjnt43c#l6wFvV=o zJjXsjJq!HLk9R==je5cXPm;(VoFp$ajW$?$|MJVL5h-uui+u|1Rbo9H{amH=74B#M z+2bep#n2UbggFZ@chNh{?^9))zP6?ag}u{mbzB_8u5u6WIG%@TAf84De+ISnzsAFV z)uyofQ(ZT!h|W*?lFs_@m+uJnN!lZoFfK;JC2Wzkph0dL)S2-h zaGX5kBF?&lXybmCAUVMHqWAuhx4R8}Km`!nw5Z-yTBYul#h(9JR-q3}v#>z+7LKt& z(yksUcZFmPPd}c!-KFp#40HoyA7|t>4(jROQNoDM;91g!OrTY-IYa-Sa5m=+Fw&7g zgQpKaedsC@@&7m=M9wt%v@TM#JK|r_PNxyqoE9EpF67%yJ@735?uY0xU=@2PQ5T2l zOr5M3nIBU$_!8r;{rJZHbWY}j+cXsyY!!pyH1`jJ2Q*Z*%0WsyiakUQvp2#MVTJLA zhNMZ*GF>xNum740rY*(^*fr&|oIMo!N-DS8D0(ybOPPG8MiH!E#4`NJ&)t_ekajd- zEm)VJRIZO0PslI$f%XcVrr0}Nvd(!otb@UwVuP0J43zLKMUbYcK z`}D|XB`v_X&niV~mF%a{!($J(E{pE%$p3uO&mhn$#E4^zuvUNwxm6d3?%^&_B@!%) z!WYk%>mTeD@0c2}{<8obVrQ;?lJ>3M6+YAG+B5u?n>bw0)5Ljof=-Sv_eA82D4Ljx zt^y>A43iuZnBq}_%C&SllZTbAfTep0@1B#qkb(qPFJm=r%BgM4!SwpCF`>v3-~ddA zMR5rafrBMyUGaYRzYW4Qi!WGRW6LQ|u11w5=cLGFjW0TOLy0p7Jb*|kQsrIGbE8r9 zb#rU2N2-nxg09P9?xNb#3LHsNa{|OoF&ZacEo;ivuLspOw;}63d$%b>|7Zudqt&V9 zcm6>1(JL%uK=Aqd7U)Ib+VAvf@5~cEZuKX+1X5g+HAoLS=e1E;LJr6c0GVrQ$@0?`2k;9ZUUF2hck*3E*ZU3$ zgNx}4RMfdxVgcXf9P@|4M0{LlfFcd2zmqe;X6IXn+V!-K!j#5u=obRXbNDYde=zg~ zMt)$n-K|?P^o7t9OdF}kL1m;ma1ObDz*z$d zB+ld7o^PA~)BAXp6G{t3QFe}02NE{S7oPw}H%co)(|lH`V{wzO>Cu0r;LvbjILmD& z-5dKV46q^xJ`4hxd^hUXQRx(lBnytI_;HhSN^gMu3UzSM=HVs7Bz*A+Wp{i<(M%ycp7D zS6@$Ro`L%bj@U`4oD4KcyRn6iLrzC#vgzUsVK>^$DOt^vTRk%AjE0Linq%V1+ZZ`ovSEud^!ju%&4F#NK)QGQO z0UD-_iWCs}E@S&wg|W#(_tvGumd?J9nA)~&Z*E;92C1lLXG9&;rtm`8^}ERI@&S=t z<`FrK_dV7*(!>3N5(GSI4qNP@=^Zj^pfXB7eTl@baFyPjot>$#OS4IM>lh27JoM3v;Iq-SNcXj(;L z(v}y18Rw05|D`SJ+RD1lByY@1Vz+0)JXJ5_h6^qqF+!L_kN2l%8Z zRX4K`Wpydg)Eb^3=aMfV!vo{d$miCeNpb09jNlk>M1A0MvmE?NOlEEBXl*ie|9naR zoZ24+#o%-Dt!|AHEq#b=cnk`;uPdk&!mFrf%#SM#Okkc=%kGKX;(vVIUc(T3 z9}Yl4y=PFn#AEOk8%OevP&}4Kl>!CO^Tm6x_ z?UOtc6wASl@UO283q|%w4Pdv)%ON!uF6eg`xbU--1&6`qCp13HUyQnIbzmPP?pJYn$GI-&cAS*l1oT zu>FONC*LQ3$Kf|=rB6Wu;^A7LB91eF-I%nOQx?y=lSF^CGjMn-efce^!4%P`1N*35 zhd`1Z(xwzqS7m3qiBX5C$N$+MB#l3!oUNt)`%*sRs+cd;11;ipg)_jkK`eA7RCbu0 zgX?ZIl5Jm?waQj`mBK-ZfzDij;Bj zXopDWY&7YeT(6sGUJp@43EVSO*+L&Y@|_h2RJEU(rW6w(ugo6cWR|b3d(|b~z~(aK zx4bHnZ6K9Ya0|*ODBY&$i1mv(hAqMZh3Cn^XR2;SLJ!5Vi2Eczv|FzLnpB%O`JK9CR+;>uK-=BgNG}UrCefu(+DK4u$Var>VtIR^Kwu zL*i1paKBL(TY1{*z4-CO5Ol1aA;A^}J2n{o5OCVXY<>4=f(}83r#`@+M8Jn$-A)jP(|y% zIe9ImG%#RBG(cqR37ouBu4wwcx;J9wJ~Oq=VP4dEx92s!7YGK#U*^R_VJ=LxhM@w7XJBgsn)G@;72 zFLdb@Blx{*Y45h5S&C>HHDA{mE(~~s2SA81g%`oTs(D|Wk!*Zg*3!K-;Kt*wtww+W zpZ`TM*oiQcr*h!aYOmEjDp+@f=~#2dW6C;3$6;lE?NJk#W;zVMIz~zX7?Im(^8hJ#exN{q| zH|tlNfB!wH9PF9%>S&Nj-LX65(bF*N4bED}uX2YDzJ@BlH*9g%F|Szt22W*D9ruTn zu(piMGMSMv?z}AM_;caq~E(K0^Dk85$=Z+9^v*-iF6DeD>VzX42 z4dkd1-A#z0PTd5*YOWZqOc|_sKotJ=Cb3=jkQ!WZu9@f1h4!oRX3X0vlMl1T6u&sI)sjUeGJVT)j@n)^Fz$*5PTH`B9lV~g3}{I7bX!>-ssOHD6b)J zJ9w|C>a06{V8*R*e}ve*V)VW@SX{iVZ8{+VzN_1Bm08w~5~RRPE=eB@szmW@cVHcq zo&|0Y)uxd}j2J-`y*YVElfp&>X(U$fI^sk0%i#^6UD`fZGEJ@bI8&yUm0D zP#hN8v>gE^>$E9;O)x&38g1E=;bmsTHv>aN4NxM!d^tWhshM9i3&hi+$ z2Xhd?c`;hQp1iQ2NHP>aGfP5Cl z2^#-?*~qA~X6p0G#|<#v|DZNUYWg$%bwX&1;Bzet(3bn2D7bRO8~4x}ohm9nyQN4g zsrL_Bo|CuKVfU9=dx3G6OoPj9iSSN9*b;1T&_Su8aQJNZN=Nm(=Q&vmRpopw3sHkx zCT^9U{+PgXdf;Gb_)a8ICYi>fnNWem=&-Vc8-AR%Uj>h)h$YECfOiW9h^Rnd^Qs4{ zJRwQizi`UiB{VL#Xy4uiRwoyFE3|kEKgLp*5bZwSuzp|@yjNnYvO9{uM4oB=wG;@smqykh>|306A@#V>cXtyiw9}O%TCvD~LaV&~jr1MnV z+)J*nEL;!B5|_+H1eikM$kk`?&}>6>zyG#iBhwMJri*nJ;5`xgZy8W62$@U zuGr*p_H=`TO|cnMa`ilLrZkgHU(X<0G5}i=lu7B(fdv7ZVXW6OlpYeMuR8NjO!BC{mQ>oIJ; zJCf<|f0CN@6;5q6CymAber~p$H$rO!rQx-^;3$VcU;qwg#`DmZ8V%I3r3(}WY#NRslGXK)9{WN217dD)tf z2mHIkJfj1eVZdmhWt>>2QT8FgesjCSeJ5Nk%5H@J4L;;KrAiV1c(}f8heuLW8oK#~ zA7mk*{qxV}Lm6KWvnBKUmSrXb5=Zxd&?iZU^%w?xQg4`80Ns>}MKyoBp%&tH)EaV8 zi5r^acr#YgP<{kuJp*xB_;&<>0Z1gbBhbtC$xcz+o@gZ9Z?|n^e)Y6RBt-8;x>0bm zwwgyH(fACEjJ}Pll8+MQ4rIV`P)aXxa*8)i$cK$*Z{_jZ$bSAY$Y<3>w9c%o7U-mQ z3f6YNtu!{@ToE`=vGVrd)ln1@!MtP>`-xg#5TT80{Yo*hg~+M{puwJD`l3?*{7)VI@**E6t6<8B~05LXXasPrfcZTOQ6*32gt$ z8QG}-pk@UCyC)>UH}WloNZ$IR=M@@!=`^Qsj)WHyPN*%hiH)S`<5J~}!v?FRPB`x8R#TzeQ`Af!~ zQwTcUf>q?fBzeRvZKg1-gb0xS8-NZpyg;M-3ZKWQsyEl&h3Ti)_c{%VJacmV9#Lv8 zHe?Y{?McK1sWP6;bj>HeSR-q?J~y_t-;Vy}6UtWGD;Df}FpbWgaJYSx6aK4aju@gVrDzIei#84)sM zGc4>@$dF^p{XDeeP4O-onRt3im1_vQ+14)MR#+Z8KNvV@8FL5N;haMj?DWunp3ThJ zb19W=Va)#6FGIJq3p$)=#zY_g1?lr3>{D}Xpp+RDeXI2n{kU@X>Dgj;O)Zn5BUd^7A+HT)g`nz}XE+KF`sFpRM`=uN0zXC$g9RFi4k?W5 zwAZJ2#5RdT_GsuY&q`6hvhS`>L=-UFCElyUQum@>q&~!D9(<@S4?@ZKN;*EYUuo_< zKTHbo>+E7s*7Nqas&h`e-Z&x*X7!Baov`I+`pajPQSzyzwd5bI$L9tDtGl&eefWu@ zr-Kz~eAYiUY%WPp;jXlbB8iD2m~E&@NgfI+#{ihu*Z54i+KkK4E?>&-O{u4fImU*E zYfzSY`V(Hv>lyYvq>D2v-=o}9KxV>vMMJy>ik~}Q7HXetnmw8VmQICsm|7HrlSSVe zF!xK}cA-N$VdzjGG7%&ou3F?kVPJ%=iM6sR$z&wwS7(k>G^gSWDo`h~&6k!2lA>4@ zInHZFZGr!qx`RqpTeN!lmn`eaKseG^G_OV>EY}w|ZD{4Z*+vgl3JB#CaQ62(+BfUg z%CsbOY=I*>Yt@XsJZJX301cpEEK0i~RR< z?;4P;b-lj5V6YA7lQo`Y21p-Hc-Q*(bqK@}t_w7~i1UtN9dM2jE%*|Ky}vzIHL}li z_Q(&yj|EsMC8Xr)n&!haL-WRkOH}rQp-gQJzpf(^f6(XKb|HM7T?vr`>(M}_->@mq z{HN4=k{UzL%4CqBBpk^{4_}y-Gg2Cj#bzoUxckB39kBtJ&LwD<=~LBf=TEX92I?Hj z^o08e1GkT9G0Qc(I*QU9eg#jsn)P(nWBs3wGQ+>N82%!|y6*m_jeG=~LF?hmp;?B` z9fBAA|BBAcqM&Jk6Lg6c4C)?<#2D`vRwzfHiv;CVbNK42;c>jS$E-)AGX z>@9nDWC@Sa`Dq;robPg~pdf&lxuGs~-t6f-~lYn0W?vcuRHI8tJWUMxyk~d{)k*f0h<`4Gg>$P4#K6j(9RxD%P)}Cw?>Z%4L%MkB_Gs;oc>4P|+jDYYbt)3w+xT3N*k?vrqS| zq2?W&vcW`Jv8>E8Heb*D$WA4>D1uy;_HTZS$Zi`!9f;*#YpXA^Yd3oniD`5C!Y{jc zB8e3&jJlD{CUG{6rD!n1@EyDYR9*8|j-S!|T@%(|C%G>!WCo;4Wzied+k zFd5kY>&f|r09tftu=afvvLPl)<0o_W@{nxzu95c@`WvL#>0X?j8GXv(Hv-`M^3`h*jS+2$By zlFW85%$oeEdd`bU^!E>0x8UUep+g@81YiUKFw1C9MyrilfBL~aKzfj*Pc-tO{ZkMw zel*jhL;h}``Hl}ufA7ftC|1WY2sz-3uFd4*wVvP&!lb1a*%Ey}TY~;G33S)g`e^71 z{=D+v1>Z^1ggE{Sj{ylr2UhNo6y`w9E08B`0F4Ta(wez-NLZ<8&tr_fK?30WiiW znX4?$C9~Sc++drpn(A{FvTvjS(JTSdIqf%ivS(bR>a?yuI5eJqj@}nf(FZfn%Q{Id zg8s2z1AE48iZwXw@qxTb7p~^}5mJXiujy1MYSd$evI8;f z6OanM!XLr``jq0OtMkPtZgxfL_XGAT72FX@%-f>;H*J2HV4L9QbqZ;sa9oV1kjz1L zB;{>HDW@??&BozdOp2Hd+J83zbQI8O!i~2+sB8N@hJ4_U>0p84v ziJQ277iEqg=Kkqt^y4lD(>JtL1BFIeG#gs7Ux7&eCtC;crd=CM*UF|i+o|)u_aDxG zDnMm2*y@`YX1v2oFl`;=%XDs#1!C>HoGU^pkz|^f=^0-k+IvV9-@=3QarwbPtj_;2 zKO85VoN2bHSS)?G)2d=|^i$E8;rhbkGdgX_q&*L%XqE#SaB(D8UxFYVm|9qQMPTh} zEpN+Z;^N>+sd%F2HGt_18w>C*wi7jMTHl)=G_m=7VM)QOqv5QMooSsLC}=_ZY-x?Lu}?F3q`0F+m`K4nHcMvg8Ky}V|+fr z*N%>o{C3V`bKrH38YBy`STa$6&Dy?RpEQEdj#kLj%&hS7DLw%(6~>`!te0(bx^Cddkz(Iq!c^1PXnxvZ!;wz)$9}zD$^bpxHl6kYGEevW_{YS zq;II(-j;uN@;>WRtZZDR^0~7|uMik+(7Kq`ilD?DxbLs--QDV&WvaHF$frInZZA9L z)v~n>4nc5w;q{tEJhJt<@EB%MWgm_?NR!^)VVjw@gc1nLndg7I^C%Gq=A3?n{R&70d?D<|?t)R(K?+USB7ER+A16XzR)10thteeS^1!;5Z;WM`! z&X)0Ycj_9ERA93% zO>9n*c_MX*^V2TfKTq@dpxGsgmv5|aGSasf-_Y)Pdd;I?5oNn1r-$uJrn_8HgCB@| zM#^a$q|@V2MY&A|IukQ+>Nn$J9&u@;1CIW5)=^UY+g_0nyB`Boz%D(-$}-#@ggppn zdBkY)T`kg#>z&$+7R)6+D;*COn$L5uX(*aFw)8Pb0$DN{bMw?#I1xCI%=#u)$c7t?Ru}h;QK5s(y|v`YL_=BwoxXN|K+c^ z=`x#RhJb~c6YIaBnGY3$j}bZ0+jng^*e>>nmrN);syYl!getYgSv4qch4*cpM`RCI z%E#BZQb4|GE9C^VddcDB5_;Y;Nxup;sjy=9bBdRTKuf}Ec>NAf_%^5oWj;kP?zaSN zdF5@E#eR2PYU6`O@DsH@RL|Mv)+cT`DBCslj{mVZE`pC9KM;g*OK<%r{S#G8hcDI= zhOnw~byY$|qENGe`c02w*)Y=R*-|acNHaVOrrO{Gk^1wr6MDgy>26}nH~1+P zob|df90+dxs_Y(vGdcxBza{ptSU+jk*84&@0Dlh&X}Zf#hAo1dK#sIO#z=r;)&<2b zb+bXtUA+6##{V6}$hHK4YnciF9n+ls?^hYK^uw7>MK_1(w~RDVARP+xyP)9D$qFok zAINSl;TH|kU^3i++UIhMozUeGf4d0xLliyM97;qC)7F!M`4qVzGg>lrA55_S%j}>I z^O1fwuD7V|3QZjAQ~Hf-$!N@%zR?NF?&c@so8B67@W4p06l)y5#2qEY~#j@t!(x6!|czRPfdLueD zRt>qPe*de2=>5wRbye(GaJH1tqkH_qy)ZrZR(IIO;p-L5*}H*F^VM?lM+-bims!Fe z0GeQZ7OhCi5KykZ@?-16Hq#WaJ`hq1+BI)>IJWKEgj}&7(kZYMdwcVP6E8@B6eu}9 z>f(!?B!T>K-wd5n%GM$pxdYpeh);?kb4cVq*9yCo&?A(`xF>&_mUGql30VoRv5E|y zC9s+`3jQ>8H+HkY|Ep)?yL0Wze~Fqf{FA$nnA3&!$844`iP#bwL~wC_?JjNIW)pL&qRcze2=jAi@P%s^h0<8 zur+bz7Nl1WXb^)w;}L<6m9f}niAOXv#>6TwVJy^3eEy*gD$aD3X_0+M|4OOPGxVyD zEIjaC4EX?~Ut&$yW9SzvesQ$)69&Gx#Mxt>`EF8pGTPS1NCDeM7ipNQ$CdrrfS4!d z2>!EbJ~~7u_b`ku`n&bG(30he2ALYay#r+kfen5~bNnlIUvvK`g`gFd6R{S@flIs* ztNF(5g3O*UKJ34R)*Aqz*TDNQav0+=7mE9|!%=w|ezVr`3A`{BT zDp~Z5ec>ZFXkzLqo+7Z=DrFs_RQ5Td<5|@0DQ+b7A<5NIE>u^2(Sz$6mis#0dgQ85 zJ3l1AVK^0SF+u(*xLncYy|BiGVVoBvt^n6&MaU%Cs377{kwo+dzph9G-^-VtTe5P# zz$?s&(+!T-NBaW19%z6h>iNa+bGi3@aM;r5f(UeQ5z7wfc%ber-;`Lpb4#i?Sbflk z9kwLi`vI!q4a@FKH8x7)fj)+Jgixg0S@ExOZ62YxO=i8%-`ez{A+bt)>V!`a5BmKw z;iPga$#yF#oSt4NE>P|>U}fw?r2kq=-+;b)K(w>ya{2exbugOU$bl2B`a2(LRD$R4 zPVFSjY|LQfJEIZJ;t#S|A)3*ZAu>4`0?(8$*FERdGc6yJyowx^6L}qaW8rG#cDYo$ zq3$3)Lofxcg0#iaSk_rnl1bYLa`I6gh+1!V?u_~$Z}CJUEpUo4I%; z9k1MR%t%i4r{lNg*%~@$o*h3Yu~FYle5XqCCGv_aO0}}Z7|G-5Bbw(nw35D&>68Tz zI@o{ftW6yOsFntT+VWrS?ov?)0BN5Zgff`}E&V5LJ4@y5t15ae@v%WhGMnL`uK{=F z*L(oToVg~her~$7S-&x}W>N9|w9H<5%$+z3i9q-JC}H*=*MbVdMBfCQ(?PP0&rqDO zI|*h#4F}g0hQ|n>?QHy<-hTY|#ZOV7R+%6@G*|n~4u!?5>3_{g#u0 zD4Q2|ofi|cxk(V5Olq~mY*~`{1&i9AnN<@{-0sD`iqOAGPB(=CY<2+5+fb5hNq!RR z=tp0-nM)&FI==b)iYvTQ7kUwG*<#@rh-qfjN`;EFVF|i!TE5DS)cBEmg{T}Cey;wl zUFnb2h=bwi4qUszGH$j}k~b{;cosndE~mlI2jb)KaF~jz1M0dQ`z`NBRDfLWmPxLE zEz3**>T}Hl^?d#1D;##&8b75w?D4Q{ms;jO7@!MK5GLOI0X3=Fm958i#{$q}Qc1L~D_QphHnM-9&+8ZJ|jc=0Q*jMt(u74Vm;y#mo zyVY2qkEAMmBJPDWUX=0X;1wmczW|>MTmdlbJ#xrznBogvx@NV*PrtSn^YvjD4nBm_ zA?9LULeUTSa-4Vb;X1)|+yJ9{-#I@T7BS-Cat~!N-{;JN+CNED0$_ai=qSVslaF`U9t?>Ikum5 zH~KOZ>>VfSn@7gYh}j+#jv?d~;#el1L_)3cPxjoXH-P;Yp7KD~hiYv6V--t$o}{ba z^I%f9FwQtuXrvETgXy)uIo{e{L?9bE?5}UZqr1X(o{zVF0EQ<>}Ze04xPq_ zV{dBhZ>Po5=P&|ijKevOpqWiLNHf(EJ=adwaBpvbpIR=kjLIFZ0@^Wl0z5z}>{=GofU_RBf#hA`C7WcYBqBJlVg)&3pAF$)Bj)`tw- zNZvgs6Qn=n85G+Fex4Cv5obtTi?WIrM{T?MjtSm#<|L^t?j=Hpv-j0jk6%CUB?vof z@(qY|(_T+AfqAs2h2S&V2Q(k{Y;qvrMobkd+ep03J%J2Y(NeYJJX=*uv-LnjfpX@? ze**?Flklw5p>(X%+Z5{k5>oZ;_Uvd7T%6q^A#n&aC)Gz6XZ!j5k&jnTh%}j=*rCSW z{bV#jQx&^qA)d~+1B5I6l?QE2D-;(@0y=Vi0QI)5am9`$C4QFnDqfhFL zSjKe&oZhi|e~jt#dn4vW!|yQG--3c&OKEx1d7d9|BgrFtS~!M>+9<;%pNH-usvH%f zdnxY60w~dI%@PaHAVS1`WmM5ytB1!4|Bo#fr28zaV_NEgIA5Kgym7M@5mGz!|HTJG z{FR&`)S|Ufwa6S+zaI|5KJObvHS)wm9V)l-`q)pJdEeZpg&Sn5Sa-SWv6pwd14_B+ zyj~$~4l^9T65pXIY2(Yb%f=2AmQ<*dFN58`Yte$pMydr|ze?VxQgWNMw(zjAXq&Bv z8P>YuLPKKva%SUS!FWLcKsyEJLvcQ8w!?AOzM)UB2)lWv?u|6Hi_bG)AgAV5wdSM+ zz%)n*u%_r(RoBCxvK@(a4+cdiGvFi;XzTYX5(L~_&DDz-?Lfu(^##4DiV>0)CTY$Y zn4%0li&{H6uc^?{%KO#uErI8ANI`Fy=Ap-{K&*%iz$pb~hFS4_e8k!4XjC&D{y=x} zeX%N3JsvJtAIfG)%YkC-%J;g|`Wf@ksqqH*+8E%Lb}K8_5fbA0<4PU=Lzt25imQcQ z=u<)L@t5-)h{!hybjFnMm}Jx=K1RY6OS1`k;7p(h>q(*A6|r75>9qXXGu(dx>Q-Sv z-~#;vEA=7NqBk|8Sr4cQ5oV@HODKX5WhGAPak-tA3aJv5L<>C%EUb_S(HYS@lw=!4>iYRCCEex{vdl$@b% zhur0jf(!Fst!FIAfX-GB*l(fW!$3=UsSSMYY~a4`@?1vbPw3(6{rEv~oX+;39^aq- zD=;XQM;ON-ym)OmIwjZR{{HGX3%5J@%?b>?gDQB#L(aYHmtKpisH)(itiArAXAdvA>I1?|_ce zPq2c#BRl&r^LOnW$}`wCv@@!^=tkrQ(86QlY(LVc)gB*itx%#flzQP7OwMk|;b!2* z>#&kyfu#n@6$5A}kasfJDpcDQ6!M&^3CgzzLW*Z2m&I4EV!7&G2NB!|J}QtNvEyOR zLBDwB^&834go<|rTeNqoH=k5mqnR0+NAQw9A%o|NZ1<-blap_N^HL(a_mi;ZdNp68 z;Vu{rjGmtK7CzT2wn&uk%v_Me&UZ!mWM_xKNrpUD#w9`O&IFz)iANz4KS=dnqZzE}KxZ*oPR?(GyhR zj$JZUn0!heDHe9N5Tey;$v{=x?eY&KjRK&rY0$uxHCSs;MVBc0Ov)(VpCw1#`ZfKcQJGYW9p2foFRQ94GixRQlIm(*_e|&s5ZSXcs z8FH(P1*+h6mZ7BVQx7t1m{uDE%F0df!;AP_s)K5KEv6cXZ{PmAczt+=Z;LxZ1F)%N zjZX+t; zS`IaXNt{^m=k*ZU7G0LvN;pnI!Y^Io5U;MNL!q~FKQ9k*4HjdZMQq2ic)XhU)+0>d zB{>yEJ7~WD@5=rT0f5E=3Zj@TKHurGzo91_;7>jy_Y|60=@JtW22Bt&IEXoL>XOxe zWW|c1);b{Z>A}Tv=7bY?z7m@g`*57+B58^Cj&ys6czIHKg>=SURuGZQVz6yWvGU=M zT#Y@eiUak)aPA{!ZNOK0LA_$1gNc6J*DsC?0uJGQTngZ+($EgYdB3!7?9Xi?Q6Rg*qEOyhrf!_8F1ZB zC6~7nI088^NpL@HJ%nlckJoKO2#Z$N?%4Gy4HdZJntShB=XR-W4Sb_M2|L?XrSo)T zPa?d&H4^C07!n}K9#q%dE-W^$rEz9RXizVUMk1kh(MKHV7ARxkd{>Xr+ae)IT$)t) zXzO66UxOJ)-lP2eapoIeH9kLxZGkMss8C=6s_IvPC$bJCTh;!#MsMz;quZrYV9T-> zJ?5K;C4(JH`<7($3hovvKE!{!%M5a46VAN>XvIu7kNa-$a(euq=6&Hll!VB5<8wlmDZ(hpPItp%re1`2PtDb%uR z!0uch(8-0ak9bk!>OZUsUxX#x51N3A#L_FrEaD`Y_Qy-6`ib)kps!&F8sH~tF+V|;()ruS5-_+7}d^Cm3Ee=vA zz3sW;Vt*Er$%U&m9$wE)ZgSuplIc-3b(zP>S6TYjPCpJhyEP*hb9Xd2zE9(=l{Jdp zqD%&q?N8wvgSVmmp3k*$q&HAH zqUn2YX7L>+c>OSHs-G}LSj=kU%j2YcIe)_l(km#%g=s581ZXvfT2o6#+2KF>ciZ7p zmd#qET*UxdWQxfo5yHyv8@NUd^FQqj1$Pee`RoeD8rM_$7w1{Zp9%gDvC)+iT>ibr z87--ylPZ!!X0`+EA(6YISwMj|g11q`JeYBt9i04I|GUbP7M1SKR;aBc^1tpGgsX8% z@DUZ@UPPLbs3%TbX&8df^$>jVk1B7emI#e*_xa*s?o~wz<*`o_!&#?#YYP^UH<`U! zb^B3h6eDlturUx7WidQ8hVP$W$NWZaEH0z(*Al+UBiZL~g|`>HMQJCtrpg$+B*(hP z=6ULRc&&veR(Y%Je*Y5}&|uoIJOSGFFQ=EPcgt&fI4lG;To)KKe?!SEYg-lMFkebn zxmi#nV?^Iz>{*WQ@d0_o=opyg6GI2J4!v3bvBkL}myCuO+1!GQTeY(5(rhUyvB;d*^3W0}Q&g z)4Z;J-~t5G&jO8T{x_&%NxpJoHSZZ_jjC<{kEe<6QBvDZ8mN_~8oRJHNUWD|W4*fH zzUSNKtb9Q1gA`JB&e0lc1k;neoNWIRW+8^!cqYFr?PS6?)?brdDCA$Y4VQv|hZvL( zYm}0sVn#|I6j0?iEHIOhMK57(tTn4rfZnR#B!?LcC>>qKO(XJN)WK&S;4Ovn?MCP_ zpMNl*^K+ouHmYerHdRY7K21^0Hesx)^m}XkC*|Oy7vtNiyp^$$0-B6W_W-1RZZmAD z7LY}D&hYE5_CoL(zIt({C^(An*NsMDK|;|TH0?}GyY5MoK5{%aq_ZAOiq0DYpWq*s z+@x_SzQl{ARjDdH)?-QiZJ(4y?)mxh$H+GaZ^Di_bnrbnZdV^FwaB}yuwds& z&;rVHI@Wtyt_b4-J+I)_LYhvr;J{YY9}sH{NDh1c1`w%rBuD;?u`I-MRE$Z9=r~|K z=GZ6D(9HQxlwpzV8{yl`M>MRF_U5l^?m!aHTmxs#16#92f&q(?n3us9>G&_k3(srB z;_090`Sk)Y!43}=$Eaz1Yj-}PrIM>)NwSRi=wHwKh}AUj)xOCFz7lir9?^VwCddGz zrNK^8Izln(t;nOmFZ|cfW?j9X0*m5msiaD6tn0hqn3W|j?bnssIijt`5hJc1J^xy` zuB3a`cYnV0GhA!$B=AI;f`-8*q@glafcji{U)jZfW?@a)i#X+C7k9A0t4rPRhftRD zCHG6+6O9wf9$kqe|Z>cOZh!Qh(PVrBuaEWFauMt z;-C-`Ip85nh?<(mK*~98T*&NIqxUVq7VUpmTVZtI#1tSKIM=mA--wrU>$^l~o+LyX z4_y&}fAZb)@6M;E6M%p3OW~;Yw;-G#`v&01@S5WMg);z^Ysg?1$ME>Bd^oI6#QIl^ zZUe1ADiu3YiMFn)(hv7(mzn?rL?;3>_ov+}Wq;p?nT*O*Z?dSN@#uv|bWKeNj>)L*AGM-&&yX_*t_DBQ)_f zAu##^nPq~FoTJ3j(*CAu5`OT(6^u#My54c_5|YU~@g>c+%N5-4{8UH~UQVbG(8~p( ziKKN43vhpnMW5&oCP2cCxPJo|w*y^XJ7Fe;>YB^7=*z1Gx$QtPzo33UU&7S$*l)2U zI_Gd5ud!td6)|ZMip6z3znh4qy*o~c7(GS}&%>*IEVy?Y{~HXaSa$R1-a6ARVO<)W zp7mIetW@~sA5OvZ2TmEGd@N+-7bOm2w|)lCYMAC!Led6X1lk-@5z>Op9>bviz<%6L z`;!MK2>!bUqcrKtZXb?Ya*;Od8t)q@EpG-#=gdh<0V0n(nc9VhdZYWt7bY_&WciRoMj`Jae3$*YiVO!Vjg7cFb&4wWIddPT>8!k|J?!}{eVOl$TWEd zm6EU%7XA;f-1b2#R@gg=;v^QlfiMh>+HfPb`!x{ueUUUjYj5H=G_bUKiNN$X5xf1E zLFJ9R14=`tVGxybm)W}8Zvs;CGC^8U$vdid1z|bgtkP2wpTctTTu-ycgtn`}ACr81 ziMMDzG+H=7Ro10kaAwVu!GMhl;mNM<*pG@5qBCvOoX_ruU~H-nGMPN9 zvb$jz@>i`xzp!L#Or)I-BrbKBXPuLFLf)oz8vNal7dY4~Q=Bx_10%LoRG8i_Xu{Gn zy;K++%r~$a%h#b}aMYL|{0_LYJhZ+YB7^W2SP?;FK_EajcqP)vQzuk%NB)6qJmxnh zLweg1b8q&5&unc5ap@W|HpTPljGeI8Gf7*Nt9Q6>qa2U!S>5ypDYcEXc3humqJxV> z*AMrnPL4vfpqzqBWrX^#20HkT-!^4bQNCKtTYVfV_ppc3s?bwmR(rkZ(jfqZq`>aH zZ=NFPsQK3~E~I_T(G)lGx5}#ZDB?o|BRKxC4b%v<-)?4@8ONSi7Qj!wOis}mLjmQL zZg6EM=-|C`d6FUPtSE=K8r<@iTsFe)v{f!DoI2{PgSQGy5u~yeYRX1RU$U~^x^t;L zBM)V15dHhtG7$eTh{{;SUr6qUm%bw2%2VXw}J?`32+C zO!^G>GVf3w<<+;h&>9k|^armR}DPFQTNLl=4d4PO=ohqrZpB_5UT9%@&npdWaNv+Q7VbiEDcNC}G{p5ZIcl%-trsU+tAgs0Z3bB! zA+S7fP*mPAE6r`vCvllrxzyVB_k|XEk=Uw~AV96evG6`y9KL;`omy)QW#{%y2f?WG4tvLo(@bB^P&fC-)asYIbHlF=0|1f_?5bW@5C!Ae4AAs3K zq-J&X=F8yu3N1i&ZbB8`-qlMZw zl}&)vrqyG^MVVo;PdiK-rc?VZ%2J+(G=|W!0Cg~MnojNDqb>&v1avikfr2V##cbC? zY_$_M_?%`iS6T=)wb-R-w(6|$D-*FI_5{}>+c>IaDoI{%pzN~k;@1geSKwrdDVC(! z;L&~V_}0I#x9@Ryr|D<7zDXQ~Oiq0T|JFy8e{ypuyM*l;sdw9k>#3P?D;m@>C#jqI zF&&9c7+~`QKrhGZP=siaH&v<(0W00Z z;5gv7&+87!8kiAFg^ySt2m61S2pVK(B{iU8s`i`GTb$%I4<`ZYF+3qz3)=Z1=4Kd1 zYhK8qql)^UzaBDpBzHZE$O%~ESMh{6AGrxSd$E$QxTwOtNzyT*tjkgdcXwn+P~=dH zu3*xBQIZu=lf2&NXi3c2y?S35RixZCj8apt!YlWNM!J`#j^JTVWK5Rn*4FRv%?oGE&niXnl`Ez6-|uU zeWItrHN~B39B~?DY+;MeeWoaf06WX>^W3R<%d`$&mVo70BTLluWvE!cQYKf}Yq*+m zeig>Y{}Bu*+#>+)jwxf68b(T1H8DXu%dQN^Xl3mAFpCCO#N@MJ=T?K7Rm*Hp zr(jeB98hzPyh;{%r=khEa=K}(EJ>W~xIN+mPSy+?Q7$1jx_*E6QXQ*bZk`@D7B|dO z@th{hV!mw^8AIb%gcFbd6?w;+o$!#IOI$!`;9Hvm<&GOJ0hwHSW~6mkVZ2@P33fs9 zryY;;PJ2d}yGE1yUO$VC4laMS-8m?m@bti)+_A}ByGx&L5&zPsh?Fj zkOpZM{oCy6t+(P?vTM z!LWgR$V7LrZggz}wK-Yp#d;SiTN(SAptIhlOlkSA`3ro0v`T3?S+YG?UWxV2&L!1kTZx#b(YMPzvZj z7$-c)P9=7LY|!Ql<1zD-#N&jG9#=|nVb9*Z@$h0Z1FM+Vkdt`jm$kUQQKJ~Ut!z(# zUySiH!z7{1=Fr0CS{*6=ie5pAz41}tzBig|8sTJsi%L1j|04G5IWluZmFNpK=6cdZ z+>2IaRcmk4@9m*_-gA}raM<8k;B9WrHh>zH4j+8Xgzg%Fpov&&mQKwd?4}(Fl8C0^6wGVzH-IOg>@Nsl#OJ9DMU zg1J&7E2hc*cvY8F%|RC|`K@#rImw-;~cW?wo*_$$KkE*&;Tn zhrxa6K@Q^d@H==&V|;!yRjXtcEz22J8Kekby9WDW1^@F!!y=j2+OcKkTAq@5BUNv` zZ102J`;82^6K@tg3AI}cg-$+#hd~gGBBw(@V5QFv9RH%;M zuCzVbJDWVJn6RfzoLnJaXSqFhDjjZ%k*|E=l8K-L#=w>_#B_&cd0F=GN3V`mLEdAn zG@2`I$+EiLx^iK7DMLK4hrX*Rt$|=n<=5>7;We6&>;tQpFe@!XduHpzpkV7?R2?tjnfD3-#`@w=ccAJ|KUVdvk1(zm?9hIS{HtKu`Jau7;;!m9ww+Wp zy;4{in|xg6%LPoD0Ql{aIpYth-I6JeiWo`jryNK-FXymwAH$0+@aX1P+fPN99W_*QV%F|X4o#*@Axz%RKAKOL&?>ylFwhC;9m1UfpLZxgQsq4;1 zotUJe=oMb}FSPmEcLo1q^9rUL4b|CsRE2s1LILS6B>3h!Pfm;;0n*j2l{FC&D#eMY zd>F|r7YpX1Z~DcaO8yu#T7e4;{f2Zkx+1A-UR~%3OzP^5XQaQfO3)zQ4gBaWF4Tt* zWN0gRK8eL=HjBSVSB=q1v$}_u4CJ85eJHj(fGI_RuwUcW9;o!OHYYA4Ucg_IxCjX7dh)t~#;5(kR#&^<(M6`pyhe%FX- zX1$#ZR!bG!7`TZ|$eB=N38&rFI>dZOADn?eBR!PgM&PN;uWxBX-&Vg|bIgkx%DOj~ zG1uVfNVJ8fdwR1gB5#@B=874eTy?;l6`SX7+sp5dbN4NL_o@j^(f)Qt@-l5kQ0~25 z=a(jDZVT;QFn?2L{@H-V)>?XZlb%26x4;4yWUq2ppSg({5FdtVZ-AoV1oVs@; zsAKjdi+6vtZuX-Bg^I6__?*cr3)Dr)1I=Q(ZB1p9*r9g#!`qaNqSYLi#g#=*n9+LW z9AEPFuU5AwLD_zk#m5M@zij#pl@V zqf*+8-y4*yk2BXXzfR`KQKBD$UyUz*+%_aQ=&gzKV5DBv&=)UG^?HmnZ6jo4t_p2N zjoAEqae6^YS$rr}vY&0Ffb)_eu$5zv9x}{#>G@m@X)akl)H+f%Xyw5{7`@n0@ z3sIY?06wptKqFrs@r|$S^S^J+Z~~8?Ta~QlLwf7Jd5CD}x{%IYE0BO^h<}r-owK#D?}fzEG4?~rlI71-yZd!lK}!bq zjnuU-4W59QrTwU{9^>~a>7E^@l2z}nFH3wR#&gV%_p&uo!b0;{Qh;h+@(HbGRtB1? z72JOuP<+4L5ucQ5Yy)86-qh0{bEQ5)q zeN4$p5N_?%GQ}qH*P7YVfql5=@oIaS=H>Eipvj4sVzHIy(T2|l0ATx4eUzt$*s3S@^ECiGiEa5KJ3SSyzY`yG`2P{Bju(_LRP(K{YC zM%msP03AyIiZn-)flZx2I`2K8O%TF4wkP25x~`ykUcZ!6zi@!^;gckoEkwx@|5~{` zX@`DLPK|*8tS>Ck1eJ{Du|^S0M9IQvDQ)K~h4Ndc3TuJBrw#Vn$@ZdhiHm$(Rx$vU_MI9AD$^%f*hgQL@a}4@aj8f#7J&~Js`A1ut8f{Ho z8d>V~8EJX|qBMN|LFX5H9~5A9$WJ-e#?|jX!LgSfK4vFxV+XCHS-+OKi$<>Zt%pu{t>(v@idZK*WTzfDaFspjbhA5W zV=bG~W^?RLqCOJYMEEb&0_F=+9{l0Hy+`(^Dmm9i?Uo%rKMukxP2;X=5z$=73l#DN zZhb|m9Ns(nBk>>iVZAMH1GuDsD)uvtIE))k#zdcU z$oC)Q-&@KsYZeVCop_-Q2)`m*{`X=7OY|!1wO<2yLCL+8&v?bA$4PqL4*h`1kSFT+ zD%1uOT_g8N;}3*~uw*ba#uZn65>J&E-tUqlnW4qG)hcDhH>8X7iXUHgXeLldU0Vo~ z_k%#-k{2_2Id5lEjdXJ->HPGA0`Ua_Y&`&|cY9aYOv(a72KMA4ed$vup1kka@5+9z zox{!NT+K{JXtcTOTkQ5i?PeH1%1DL8(Ms@xIZ87CpvjeqV)a$db1*L(vvug_+NyxS{Vc7dw`5884+53g*V91L>-onj=*nO5 z8*J{@e?tbd`^8G0@C9EXGe~QTetdGO4Dm6NE<~`-+y!N4n3{7w>e8Y3@CtVJpx#+- ziGDn<7hyof5KPcPd?Q?6V7qMw_FgsmHDCASjNS38TQWz8-KGTQ*PRyp?3c$BBB*#F z%3C1Re$Ti!?msM3N34neBBv*Hps1~4$e7y9)SrLr7i9=OQgLo%Cu^2l5nGO7`)N=! zKAdA{AC(~wjaA`i(~Ff1w|Kw9A3K;gX%GNt0?{*<_O%$eO1@ZeYQ;>&>Kjb)$5%mf z8Tk3}mEwsCV(4}qK{yDEcmgV_o$m01`rRG67K|xN--i=Wy{=eIxzpKD6yu*HoRP92 z6?Pg#c+WiMf8N@I*Q=!1ckf%CG=JF_+cu_H`!V-N;H|H`owp6`^h&+Z1vAh*V^vw8NLGkCjn1}C*A!HZ?NKIBA?e%410_d z11TQr3^uppDPN`qcfN#g;VOoe!0}%u;Rdx$?PD%o!^^bjydI|b2@6SMTrBL8TAPe> z;fHS@65)QZVA)q4RerC=rwkpr7OR3$q#0Ao%ODzJB&8`TVrrzRsaS2n`Fy`{zaCT? z5Qrpp3JFb$bXh`@BYjo#5;5J?tU~kBq5Ty+t9Rt+ab=ENE)UPokrUZn%@Hem4r-=n zlJGBEh+(BsWFISPC^ABTJg3aPrXwYNl>qpRXiEw)e-45n0WU9D^MN@LcP9S!G}h^? zLRJUizqBB9zs^PY%aQpdHogcUJt@9`N3Nf&v2t+@rK~0=H-W`2^rT=Q5EY<; z4Tp%3j0zu4jw+bO3aV(kC2bm&amsB#2(m#yq|K{NjsibkJzg1H-@|DJXTroqzl-s{ z_q_K!;W(90Vn|yimWG7WG|v;BZdHlk3^=QdwD!F7C#90lijlL)R}0AaGqLZpX^dc! zij+jjis0_@C;`C2!UIUqYs0TT&?}7dFYKOJ6g~QrnoX#$pe*v@i7Q@I4kYCQrAExG zaEvVzxmjJiajRy21S*m_G^q$K=AZq#7(Rg{^?2%=1>~xCM{^C!neOZyCg6uBeJ}I9TO1#iZD97y{Er@?#^yy6CI}OOK%!F zD#Ryi!xg7uDS?#!)+s^^oA!_D7;tZ1p41GGITn&IV|Y9xusLFyJ@I9Y``GlR03ehv z35e!4#>L&_K<6711)Yh$cDsbd`p_D>=k4DhZb0yJs zDp*_yj@p(M2I=RQ4KNu37`_nE{Rl~>a4E(Z_33I)uH^ZNOT;)P;l*6x=VQ^hBBpt# zpsezEAFtt^TUx+H?^fx$H$hjRMyN-oR=>sN*^Xi(#!G?Z5zMo&eimir@9(B44j2bc zHE7>lx9MqfFJCMaJXQfJo62NZWyaX1n6#+(rGFU!UL`oqbK`+5u%F~SPbm{}4*AL8>`}?2oXAJ2qj^@Kypj4wc1~D%|X2jLHXL>ar6|;2+GewCk2LEd3!xjfT|Dr5E|c z#uuq_`w5H-VOWwM;GO!i7C$qGffQAM)-4HTDYO1U$=W_U!SNsxF_;m+#y?$Y}ZoM1l@ev%bg zZuDaD-zISkpiCv!Hl0rfhb)hdgBS$%tUp7-0boL4YcDXMhFo7>}ngkL1B-Ac95EIPx#Ht(jn64NhqT}<=5D9 z<85;kMbIkP4Ve@Dop2x@LS1ALS=q+Djl%Cw#reuFfoR|142x=7b1_8sRmZu}0PweM z2Y^L;LI^@Or|I$;pw3t4OrBqgRy)BAc zKDwExY3RTXL`n@`B7XN~UO0ff7oa4-XejLSq?GLN;FPy-s2W_@mZi*&DZ1pk@#OcP z*cxqU)XLwlWaRhz@PhMgE^FOwr8i#g*$UM|elXage$aYgEVvara85Jovz1+|6oF-xFXhn#UbCPzAF@_#BzyMLC27wUi}3fiWzz-x zoCHwq)yHN(QQ~!r2&COySanPMUWgHt_tpP2gmjz_O^o=a)*bQX(V_q3$>TdlM=fcu ze(SoxJ^fc??>+;Nlt945d zj4RzQ-_aG^ev|a~5R*^=Zyg4J8#&M;o}(m8%hDZPzNRPtUpqFl{#j_o0RE}dvV)li z)=!SUY-?@vKc~~irH8H%)ERDYkw!fyyDz#0L;v8%b~f{#5-#e?D%yAw4v}3+(}DWy zjek&thxete=B4mmI!IlP$PRx?(4W}Y_wT5F3hNWTn>gwpK*ACN)w3lrfIB|Cm>N&O zLuAx1ON7x5!cD9+6NNs*vrvL%%rAG{soTa}HCYhZ9xXdpPJYa^Q+6=pBSe;c*h$pbi*?CE!Ha za71=InePzNbFFNo)d?0amo$wAN+pR4b6)qx7+Ib| zD&7e?QdQ}bFQ<|mG8p7=mCEKsU*78uYY!q)@ z({~=r{S8S=8JQ*u5QOk}sdT1p;2;jwf9bRFPuGoZ(w-I*0 zJ22IQq_OinqhHV0ez>YwWy&qUsmU)tA@7scdUD-Vo|a*|;=&y7IxwO2A=rXWAxYls ztojvb?QB92Wa*GZsJqYKKn}-^JkroQnsULPi1QNh)yKpA#KI;)ilWpX?mblO!{7q7 zro6ewpegw)M3gFV&F?gjqgSNKrbp^FDZf^RJ)yz=co7wPR$_g-w5 zj5(ozhUE!88K{3AD#f?|fcM*u?gk``z)|7aUyAa*5i$8k_@mePrUm?eT$LjvCzORb znhKS1$P@^Tz^7REyDh`013tE@u*v4KrS;Z%MxWC}4Yi?enlaa;d{^#fUX8sN%!uTG zjyAG(0?6jSVIiq;kEO2!$3Kc``IEO4*nSXLcGR$l<+O+mEw-U;k{<0unZuKnk>vMB}vzdw3buR4X7wvD}thL zb_NF#{TED-gpfyTUJnEFOups0yUaPV%PX%#hH)m5=W&gIHS8=9PXe#`RFzk2#|5f0 zv3cpCRR;Z;@HUFRms$I)|Ll-57+uYms{Sh3!=EZ zx{#1n-Zr7?%K}@Cy-Mu&|0DYc7*07PDD5)6wqxZ5jYDG+pq4v&?2{?q6qvyfeom?#;ZV z_b2PS{*~^MRAG}y@Lt40&}jCSLI#W@{N(YHjCWN`k$)&Gyx)TWK&(*!Yzbj!BA$4T zMA)dZsa|9;IX$-5@--N1lb9twFh{%I8@$ zKTICYqmNLs)JzY{N0xOg=vK=uF8xZD-IXXT&DBy5L%O5p=jNxMGS8+DN1+kU>gg31 zA9gm;34|s=L~nKF{S@EoAn2@;T{<)ywe^>MEVBQ|q{U*xwr3T0l;x(G~i?{(s5sWGO@*Vl#T4RAXoV zDcYHz@C8=(q!8Q23R$ipWc+F9MIgY= z!`py`@)aO6}v@M1}@fzyufJ9dT`?lhmdL z){X{R=5o$>^Gj*yM@c$~ox%tW6g9!_i>B4TBh%^Y%ELvFKnCV2E|_@GLn{zEgDcj0 z`!a*{q-FXaBG7F$b=M&~U5AucxAyK2ACHyddn5(}Gq)+|a>jE`lAUw*mKN?Cs+w=ZGX4E zU&x;hNO`REvWaiE?Wo(n9+QtP_n7HOSV#-Lr33M#JFLk5qmKwY0q=zxnkxSH-t)&4P(I79iu6Lwv(ildt9A-;6) zwAjg0j+`2MuobKhq&BmuO+g_`)$r43!soiTjW2Z`(QSO`c6_>wJk#g0M@|RkndPif zjtVhvX;vMboad2pT3Y$j$CH!7ZbLMo^yn7TJL!f6AOoEkk97b6wsCbdyp+$W7 z=jQ@#pD@LYSx&3Uy_Ex3BTB*gFKn-rP&jaN+TDc{=fd;A!b z1;0E?2uUe!(ewax)6eD&QYKl5ypfk%vHd05RnpiB_kEUZD^FgXD%3;x?{1Xo55Oq` zNCX<+=$7$3LHBvO=XwkD8>hF(B9$bHISKGA8Sb*v!2C!?9}73mGOc`tXeDo+`}Z^a zWwImbhBi!NI~A@WoyHb@2}2xPrDC^_z&KK3k{MmH*A9+t+Ly2wzTRs zrR`bJns7@h=zY`4h5*PHY@lHaf?w>u5f;u2TZ(kP{j-SwXfJDjhi8YbDL9+WNo@${) zuA)@YK54ELn3z?7Vb!$4*ZlB$oEfOZPv45hg7fg_SkZ?FZ_4z zHC~mr3o&@`RT%x>pi9R!liu1A5>a2KyQv%GG9C=|$ z_uQ2z-OS7+&2*REA~GhzkVoHZ%`8Sqk-a~#JrAN^1P5Ta)8eSFA$ph8b^H*p%TkWg z5G2y6Io9uUyb@?uHrjK&mf;>%sCr6KcvxZhbd$cpUDDX?+0u{g>QGi^S8@=i?l~m< za|U3gL~^SN{7>J7Ti@KfJ6E8R^Rbtdp8r->j`h6DlY?o(%tb{=5d8hX?K**P#{jCL zg%4B`WYf&K17-w?LwAFajhh$$Z3UeXjJAkN8cNoWaeLZ_`HX;uN&vEA1bI(dR1$t# za?UNwL@#^A=Mo|6*#Fw6bu7RP3&PkA9CRa3nxDbBI$Ad2&4@o=vyA6K;jGt^)kryG zErj zy!==lz^l(n;!+Jw)S|J&0H)-)=%==2pPizM;qWU@@eh3tb$z z#h63uuE`yIeSFrCgTClZ*>xNIP5j6{mgoyqi-`kvcelZ2lge|bKZFRN3bte-D+ucg z{Ndahg4>{%k_V?8q_g&mRw;BzoouTnKcN_35kDj_bPt{dPC|cd`z6bpns?n4QbqdFRK` z6ap`o)k;4=?Bcx4Qd>f_W9#qwb>cst(K9!UE=DKXHromc)5NbRAs#COw3fcFK9~9I zMDFB>pM=29I)O%Nu;{{s;B?RbI=Co1BfnCF8Kn*zYn4LjD4wK(m#JyrrkQ5{%aZd@ zWLi3SgIz5UZVO_{LtlQ$bZto#jM|5gi_h^ga#3WXdbQbKK*#}(UkkZdcKG==muSMF zg8jY03Yb6lqeUQU$N!6=2}$( zt+d9;Q3%7VStb>_4?DA7W9#s~sn${Z*Z?6B*{Yy+r-&Y`&|5CWiwPhN0z}{RcohX) zbSy$yT{qOvzDg@459du%r~AtXOx_Soa8ha`~et{lV*UWc8-=fhxG% zAQBL){dTF%h-0gK;<3=|c+mwXIRA^i8N^}}?a)VN9_!A+q;nCiN@T)RCu6CkXZR)l z90&kvFvIz)_OttY@v_CaG57K2Fla zk0Ziq#Q>NF2;BT%ZVxgN(YDb4N4a1)0Iog2;zwh@X{aK(p)hE~kX2DlWFbbcBG4Qd%)9uF_JuBrm;9uxMamm5>qom?unTm1h^;#jlmHe@ZP`rb-b{2K(h zQlMD+Rp0d0dD$(m`M;GHw~RF#?f^-=dZ$}EZoW3}(D>gd?{|$F2zCYn2Xzpz?=Mq3 zEASJsv}TQ9lS-MrDdMN{nQ`dP4u&72m;>83g3ZO{|uBbM7DU&O7>Wqvw z;r(E9lh~VxeBuhhWuHto zGaWlNdQdU<1f}Glm-yu2j9+IHd#7qUN0yRNP%(t=P8|Tm_S?5?o(p* z_KfQfPB^V7O7Wfj#n=nBvvyojA2QwM(E;lf&TRLZ74Be|VRM!})`H(xA@RQg?jZ=@ z!*DwoXu%gs;6A=yH-}AeT@j7cXx&NqeB8!t3ygk^TZT&VTvAzq71is6refftTY3jx z(0F@Jh&Jd!AJdaTrb|dK3cE(5=_xWxjxY!EY6vkK1q@%AMuvC>f7`$^xj$tUAr#=qR-Tuil2w7J0jj=QqflkAl3m4b@!4TuUpc;H19u>wX)){RLwx|K}M2Crmi_^ zhp3f)NIxMm2f{sj6z56+^;A7i%*t|*$+ziA^J^m3>+#=31Vqd2UL&YmqA>`!Gvv@; zw+U6T61Oaw-W+ja1S_E0>Z-OckNqxS^gl)W29v?uKmUBoL=kvZ&Bxw`vAtGVLsO3|YQ?$(AMgXxy_6(#Bs8a74Z_Y?x`9AmQ(ajRL|@$x6=1v0zu z*vHS433v$u$b4LKy)u29onJNyPP=(u607A5^UQLu_AI|`NuL}FSpK0b%&b}g3QW|2 zF&=Z3P&`q!C-k|cKS-h37f#xM(2OlFaY(Q@lt7Zw8f5hL>$J5Mng18djd4TEc*fH0 zntY8@qWnU9m&A@%>$<}CxX_Oiu`|gKP(u4v!SG-*l3efh)Ovc=utnla6i~y!|01R^ zYJT&LlRp%*Gct>SNYTxkQX!(cLh{;+K= zNtatJJRU_|*w5_~YfL$D>LA;t7V& zT4FSnTkfd&pBO^Xo8Y<{0~%*E+t!jSM<47X_ntZm+>WHMoAV`7#^%C;efNR37f9NEhQOS6^u%w}n*7st~xK zpaKy43Kp6V4}cy3o}Losi;(>PO_>nInfIa%g*!mXDu{xcIQ%-% z2gCV2@oE+)D;>u(<5Gi@)u}(zAaI)YtMt5b=_;&$>=up_?2OV364{~hZCaWYn#&qb zwS}3mq$-E)*3Vq|%(==L1F`qDm6(77Wgws8sUfD1U(PqbICp(Itys8T@B5H-2_Z%q z)xu>-Y&nroX=_gPtVK=+EnwB6So)hom}F)nZ49O`5EgdTc~-KS|IDC5hVZ<8X*2-6 zb3BSe**mnM(E1_Z3(T9wk44CBzVHyDyNmB?&3rNSvhe%TQ%nG(3K;Awij=Fw-*RZ} z(4Ie*OZggR!C2r?-M@?RKcU&Cxk|}(dM2l9hg2udjf1%m=zWVjelD0e%F)qvnR%5= z$a1?$FFqZFi##)?&FTKe2_}J&j~!Ynfp}J)bAP#7YSQwns-FH>?&R5-_ZUbXqlt;;Ak}VNIzpe6vskrFp#L^qOi``|h(o78@11L>J#!aw9nE zAFHK+QMWM@L{7teZ-q5J>`%k$wxcTH!U4cHddmC|%5w-!Dh_wDwnzl!`p=m5&* zT=9C%6Vki?~cWAOQN+$%wD z=fV;D$qL%zQWSgq6Yk3KI-{eo)FV-R>ejKI7eaPfW!A=Mj?b?;_p_N&Xq}^D-n*NPjnXel71yhp(41 z2FG7#8#kwIPDQj%(*jE*yX_sqUY09Gk`_&HbGNqIl6Cn1`GhZ`QKmKM_6NGE97$0x z^f3_cDma-{+B{W7Q#X0a@1W~XWMrW9)k!+tVSI4+P&j@>O>gr*?;jhNpyypE-E11> z54&M5+LgJz45!RMXdt+gh&m}q0y0-hCm)GqU9h`SEd2^>dVCWLJ{|sn`<*eS2IVL+RZ2xR?fQeDi8Df@3Gu{(-f^UPhCO?w_qPG;-T1;J0Tw%;n$B($^`jj@!2~Z|-bV&@)v%c!D(J1sw{0etW?c)S8vkLZ1Y!7 zGaw+Hx6O^n@ep(XL#c{&GepX~G>Ori;Oxx_w#9M2{o4YM&YafoSah43ZlnhedOY}| zCzI>^8_SkK+&io42g3x=zYS+^dikOBMB+gH>~oN$b)U7{%a*2Q8?LFiLWDg*lf+IG zK!*u@TNb-rlO`BBMU_0A$dh>o&7=b6?Xxjm%yX9d-L6AztFbM{Xx)1QboFL zuKCaFmugG13oYwd`zj5jpmkLS+N;r@!_BpV&7mRf>6+Kj??i71a7mkyF=HB|htXmD zd~;IM+l<~H-A{_oecV!jLS0G3$1o^-LS8beg-5RJsfD@T*x6F?XWd7*u;>dIK{|oK zMww;erMXY$_%6Qn#05(;DCeN<^7!a^Djo<gRuL2ps2|a)Rw$bih18?=Hu= z_5UN!&&oU{zlInTTC5fxcSwww(S7>ZH8i4NH3e`lHcmLl;BjKM!Y6iTJQa2uG+81S zl&Dif;Of)(jByTaW+TRRMGK4|@{d^KP;@JV0=l-!Y1)^ZoCqOcj-g{bf zO5gR=EdAn0`bRPLrpi3y3o)o(rpv zx7CYkKK#gP_yshtTpg0<;Sejg;D~=(Chy>B99-QNN=Nb%Ip*gY=%(e8iKk8d|Ni$i zzxczY90~xxi){e1gv~l=iuFovWK!zkuZqyKULp^?eMyFN@pTi1o4F0c+2i*WB00sp zGnPQ{7YktmtZQjJaVlu5TiS^wy28hX5QLs#-=x2x8+l!D2BaI28i;ldp7=P)*)Ypz53NPqo3*KXwfR-C2U?hlsVp^p{!H zt@kEpG-g=`)%VV>DrY$M93#FZNWT%=55fX^@Uo7AGDwf{=N--;1GO`yRzpfI?OP71 zrak4bwVtqjN7|w0qm-{go}6Hz0(jD3M7(O)_$g6i$X=(bcM@8Zr3?eJv&Kx%Cr>zN zz}xTP1`82g2Pdep2q}k;W#ldAmQjuub=m#+NU#x7i(FZ;vZT?7B z9r$^`ap+cxh0&Gi0!h?&Gb?EY*KRLTpzoUgZ#-$yr4j73?yRFjn$pN^G&5ArrCBd^ zJ6LCUtiQsRRHcK%7 z6f9X%d}S)^T;rp}8iijv4El6?NUq|mZR5kzr_KgaXbkF3oao)$bWpECOQOM2eqnxt zx%KmrUF6qFk$NxBtS3Q>@-2~+uTU4c*N0+8qqU<9WFP$A-dj2zc@^Dnq#EAmR2zT^ z!oStcoya(Crlt&Gf+Bk+js`W1m;2(vT(octw5nRrctWiSU0kiki%a=a1}xE_L}0=f zt=dh`|1RYtqow!ww9kcjT6;#nVk`Tc5ohff5a~v28{l)W66%@dOUvJ!sU0h|@7VE? zkDqwU$6G>xlxQIt^-9mi6um6k1;h=*>D#}4Dw~)f>?8kWk)Xme46|YqN=kF_bnyr; zxHC*{mT6+bu3Hd-&I!>K2ri0q07lPDR&`aYwk_lPwG$2q>Y&Wr7Wt9)owaael(p(l z;2EW~qsWxc)7~?rd1_OhAkWt40 z?JT2@;g9z>vnYV0UuiJgked(XwNnzh|v^bpr#t$HWaVd(&D=;N} z`VwxuY|VvvvM!LZx7TzmhXpJV8EM8Nn+PRAw^qSp6VvbILdYSlW9k<6Y$~2>1{cqA zctBI7hQTB9;>li%fYf9ia_v>hQmmX^#n-wtL;ajIboDaNe3Y`aEZZc{)`#o%S>zq^ zo#8+m+`#%JtE$)#-Cp<_$j98V{I~3p9Z4jS=py(0_?Mg56fVae2xVw#Z0SjL^QPx* zTY;t&s^BSWJs=@|@-qMQOugRyt%c*0%${qniLm$BI^+ZwZSAN8#F|&U!@m=46mPkw zJJm_o&_jZ(1R7!OFhr^MrQar``>DsD0A#MIdi))ZPQM$69kr~cF=^H20Ux z{z<#*Jx1gx9i_tHF5OoHbKg2G*%d$z2A7D-+z;@6zZ?bopUp;cZ4DpDNj7b0~_)mj+7Zu zPyz=S?8c{mC1we38<=5SRy;+}wBR(bUPYQ&+T$#{Gkz;gV?mx%ufmgZ)su()?MS%Z zTc7}j+a{Ybsy}`xy>k(4hgn&dm6`r&)g<^00yt|77<{b+qaZc&DbvvR!n0Sy0-cCy z48b#{2OUK5etn0U6ih&Z6Cipf{i@(WN+3%{dl?1g5e!x3O3ff}geU{?r*LuAR`FQy zB39uXOG$W`7T6%E+tSLT@ZTWSWVEtbUS%(tT^vo=-4mfKj^28Vb9)PqWH>xh^^*CN zBZi)P_XH<})S&_e?UsgBxJnrdxxUyiejos49KZj#fm~gh$rZ{r;``Hk8{La!kR!-> zc`i$nB&iY)Rm5UjQ*ENqeOu`HRDO=u_@!NCgh1|JZ7mWLQ`Y!5^C{9rgMR-TC^DDE)9P1GD^|yxytv3%c_I))sZ6<||onY89!+TjtThYQKEfwP@NFrNr+?i5=Za ztqp8@vV+Q|D=u-h+^tXtxu|V&so^E!&^$kyyl~s_*-!{t_77vr0q`AHLD<+Ynxz+b z-_RF_`c|K;6VB@QS4rIKddf&;%UM4j7cvAY2ZZ2=l{GfTJ$CIzX4qyOQ^k2|{Xe&5 ziA=D2sozr(9Hj$EBXoyWp02B(_^#Vdds%l5miD8}K* z*_c~R1_b|n8df(MkeKmHLa%h%o)e4{Z}YPO8!wLXw1>BCVU%KYg&4cL#U+c2Tctp!+5yhXsMczY_mP> zB5xoXbYP+syGtVXxV|K5h*%P-_;KRbM1FzV6hM!HozX*Dl$NKhOSi*0bp6c%WfSu~ z(GJIo*q1cpUU`!rKI5%BJ}SMsuZS~)sf00?_1e_9hr8I5(kDfq@|Y&JHntkuw(X>`Z8x@VZt@HE*`1xUXJ*dMyk`N*^Z*vPCi(DQqd`mE0tqg~-2Kz`^X3t{5r(sUw*U@osdh#vETRllU8)~TVFu3%= z;F16O%4;lk0rEls(bTI_vuypV*+k?pgyqpwk*OT1F?`Yt(2xicXmaxO-za8Ag6 zs+vpCmBX9Y4OBgl&#_Eq^2WqRle-j0Un7)m0I zxeW+KKP`qLeC~O9;gk`P$~&S$bAlyrLa zU0D1V3DUSJg{i6;JAcxfoRGZQ*qyS;qd0TXLvQ1ulQiNox)036pmUEI*?)d}4rQdM zKQfP3l~M1T9%(bG_c7xvTCoYz03ZR8Q}BNBqjYAA9I9iS9VSXeEA|=UzhZ6;+|k=^ z#k`<}KPsGe^nht904ZdzsOu)q#qAqN#Lfg}5u2xolH{OuCl#YdrGKyB)H1*h?I)B6 zq?SYl;J@v#MexYD181n$e%^~T15G0$xPE*!Y3T)<=ZnBw>b7^_x;R>XBB$32VJ>?V z^31zhat#AlGa(D;J=u9@8PeGgqA$vT2GKQx;zPP;tkd@&26<$=om(SE`o)2)1mVFgt-K0*)C9* zLK*DuJAKcUrMsfL(%yg2fC`PTmPzN(D&)#PC3Ea8+r-1C1yr@(Fu)I$RWj2PmUddW z-X;_T--r(Nrpm)>q?)9ZEC@#J%JIa6k4h2D+umq>67cSrheSjxj#GT#D^70V3Ofr3 zUd0ajs72uF0gOffKSEpV0yy`AyQ>x?H$M7qkqyX%f4@;j6G`GRWP-uQ)$ht`ny3sf z@mA0RZYIBW#5}602Xka--;J6&giu0noQ-&?r51sH1ji2KCU8)yLs(CW_-Tvj zPJ5Pfc}q5V8jelt+1+x%W8WPL{Mz*^XQmj)U$Z{?1zc-MqY5l{#y+ z!?l&+qZC6W+cH4t(Bo<9d|Z|eu!@@*-L&ok*=t29r@?I}5yxyI(^;*7KtHg?O^Atj zY3mj0_YQ5OPV1UM!g!0-`avQSN;aF!?mCIx)16#%ovX1Q_Sf3I023%cv}XNuyX-f* zIl_c{Q9pw1i|E;dM;;A)H08ARN)bn_CjQN%ww)8@fp^#ZO)o z;<_rgg!AnFOP6N=DiFV0Wp-e+SSPqM3IbIld1l?6bJb*l(%Z3f{)=wmJJUJK`ikpr z7}1Bwu?rl6a2C<0Dt-n+A=M*fVDUfgWBJ4M=$FRiNA152+AFp}Ua2OR0nQuCUsA`w_F}W1hHc86JWR z){7|Ga`ihS=TyBN)5O_s{*t%xWb3fbDSl_waS#|DeVqIGobeHJe*8+X@drLQpLhe% zq+~~?-VHnc3@*hG%1<{|VfQ&L;!IL}cSq}X2blvj=hdET86WQJkdM}gw$*0(EZ_7#eu7Zc+Ww^w zdUMU1ELTZrMI2jzo~M0#^+*-JUB{pXm&gCwcQBB?9h!eN<3~Q#qYO;0lm27=&>M-2 zGwo&h)yA3&X_oY9vep{_O+Jz;nI(Iv2}Ewqsfdbvj1=P@iEpmfR9#}-M1AK}abI7k zUt%UMJPy(Z#AME^af|-jz=gXtWhfK!w$?2Yk7YJldj6YzkQRn{)0Z;n!UyPr2g*MS zvT=3dH~}3s0WtCW{Pp#|FrB$==1grjS+NLtqCkaqXg5iU2c!RK7se+;8>B!TKzTj+ zi_w9ak6x~NqK9+Xv5m5YME~V-+tWn{zPE+Y2wKO~oAqx>ETViN(Od`L8_gkrI@N-z zfWf1O3GdUrTzUWwF~I0x&Lh6}Xl!h$81h2V{8f*#o&*mM{Q>{!V#nPH5gT&s*&>V6 z5>||aHV~wBrla9$cI*Fg+g!r}DFt&GlPSZ7EV-N2Ml~FbD(61LhG*3yC2l7Pf3gKn zIr_yd*Y(p(I)%Z`NSA%1O*1q!E%|C^IzP5?fuUYRo@%by6zW8b{(A+knUKHcrJkJ}0$44}l=qFGp*tJADlQ zHH=D)$#|e9A?&M&^zt>S${FHj^tLjJdS+h1PteBXIPn+s55r$|snkzRP(0Y~3wl{o zqu%}a3PlL800-y*V%7`(EmXbn)Xr|)pZgJFe4dSK4B^7k%!((Dd|~<_$o}f+-7ka6 zs1+&zislZ_@hPive*-hU$O_)a-#@R%R&zk_l?&y=^L!M-P}(Yk&Jn`-0U@8aNYBncFw6Tnvs_XpT>UZ0qlMUjplmoBt4mnj0E)r)5_sqwS*k8%IZ|W zkUo8TFu)%_3jk|NU|QjEq*rhR_t`4;pZS*fJCQ{+DWNGL?o=_X3z7BgvUL}>4#+%l z*|r5ncx$&-Q=VGYZ}D(iYV3z5KDPJ)el}iZ&Tv14>-gF2tO@_0$wC0VI3NNrL_)hL zz7HJ^+(Vy@u;e2uD%BSWuU0hM2eSKS!^M|gXQk>+7~W0_eo&PHm;Zv~``oGHCt1Dco&8%?cNcf&I7~X?V;j(y@MTJAo;%N!ugiN6zBJ9 zmfiIKEA%(-34BOVU;z5L#AMQKrbPCY%Fx~&DysZb#}x_`8G>&bUab8QIdP6it;I{W zUCbOOPXd$VmbTAiTnOc9Sh#Ex&aQ8Zcu5pTQOb5d$Vt4Ez+iiz3421ViI+rvo8A_g zFTOu4tSmFc$j!8Z5rsEbwk^w}KYrzE`QUs;KmhWpYPUxv^i~WaT5~u#b|n1cQX}w| zrS-;V6i?%6V}GDa4q~*;LUO7#5r6;iB<8LZuLYv{W8^QT`PvgB1Ejpx1bi9{0yxFIuoeV$i4g9}b@ z8_NHg3Wy$HgbDCtfI2Bi;A9isjyb4a=4Bp6zFj63s!JqwaV3F)b|)TZ_C1wh%>}aj z5XOhG{`cPRt+@XPo;PaE;eusNb5+V&3+n8+xum#~ro0V4{QB-XbF_@5iQGNV?yjR@)Mv3;K&0El==e1fR1n1-}-w~g7K3lbexR9@>yve zZwqK%szrCmK$=i0pAN5i>vX#tkI#J}P)c zmH`YB>W(Cf_wl7uJXkCLE8Yzv4~_~XASXLZv-a9rN`(7F6sR}EE-qGp4^rDb&zqHp zv5U1emKZ$-iHfK*vNuH^C6fkO?4f7X5%TX?%zh4bOe=d&veP2ReLS@iaL!j5bR+EkGa^d ztss-odU0j!HMns9zQ4)s+#0J6`KCIDzNgFBJKv95`KHja0~eRQ3X1f;HyT1dM++F*LH{qsjO@kj(vA9IXsaypwg2j2bNFE{E z2!PNP1U*KR_($%uqqOhpo1T^qR%~qb%I%vQFL$sIItpK}CX_Gm@MR;ZD?PjF_TKu4 z!F*@v?GR(2#zQ%2A>OtdRqyTPN_!%?n~EZNw$LPwbkq$Tup_I-V|B3^J|fvZUxcu7 zFPKIq6q4G~FnodpVj*bdvM^W*7Vi}Da;w<5$1S}$>i=LEFN|1f-G2^aUPLc1$+@kyG6#r?eK z*g`JlNsAa__(}iUeM?`h;wLvmf&e9K0q{R)xrc>`68ur4!lY;2iDSo;=2p^^)o{OI zvR#DUxg>`&xeC9Y&I;W6i|#Yw%TYldW@1#k3xc>3h6Np;Gz8hH=Gh?NEp&7>bBqt2 z24Lk_kGOP5{-Jdm%yUBtQ*GIu;VzD%es<&fmJ_GR2Fw5HUvzo^O-jHJ){(6X3OH}$ zpPm^>1-zJsu(2^h3A@##n{sl*s<6&JABx~;U;J&E4r+j=UFMxsWD-gfLKBwO#6O3Z zv)j>c*Vud1CrOvj&9BE8X@DT#JJ`nM$*G6|jNscjy&Y2AU!m5mFTs650Td*0Z9_W1 z4{k_^3$eK)1i0dNPxxJ&}C}I?H>fW4`!@T0$Wo&G}Y@y`ODKH zpAR`9ObI9X+`79=d*Z)#7oShb2C**SdmIpoPS5XbdKXy9)$K(?>Hkw15H$F=+^fUh zJ;O$1xgj_)ODgtuy0ox!yRtAaxH_(q5>g*|#X(sV6K=O-%H!>Nk7f|@(4AN87v)pb zCYTL1b;im3S}jM#v$|~KFiOhmPnS`Mn;lkhuzk49b(dh@&$YYW0eR8ofauE59!Psh zq3VQR{WnpOwzeH84dWV0n>-rlML)ARFn)tw0`oDUozAN&$OA0WMurgLF{KeP^W#cd z2H|Nd^QiWZ=F3IVt;oO_y#1YjC^@NSJzkU%VG$4ql0Bd(->1yK;fh735;lm7OEc*fa;2pH%Ka3?dIpPN;lc`I{XR=w8Q zEKhyH=zL%yNU4y%Tl@p40F1y`Gr42L3Ytd28fkt}C6SE}FM4O3-8g zu$J!%nwrwKUiS-V?w2}M!^u-o01p626YiY0$85tOZ0KXIY| znI#!f1wIVv37Z1UvA}Z@D*O zXIy-|hlrl;BYOfZcQ@J3*$12qZhQfx4^b;oGA^>9_b`~Y6g0_dXCEZ(^i%*OnD~oQOZaK1nmCkVIPPPwCN`j9Yoo49ZG>LG2(j&cYn{hTN6Pc zVf`&MlP(u}=YI&(b)Fw{!wGcVcinTiY~ud&PvJiu#P75OnNqL1_&;-1plv{q;u2L* zWD~jecM8OH?8jlmwaQ18!1-2Jyg)9OQ1BbSGHAB+0F2`Idx^!f^unFUAkN!Irq!P} zewbxJ-lGbCLjcL8w0C9m_dkk&;eGc-nSWKvm8xdz&<*yg!T!Shm5I5+uli8jjT4X-BS_>DkQ)r5*R-kTGu8a6!+n-nvML!{lrx$3*L%r z{vQWH(d7nxJxfvF=53uZ54bM;MJp91fvGm&rr*-R)wqCOO^ioba;)r8;Lb*ow`=$! zT|$Cc+-QvAdp8teSF@(Boy@;#6H5&hjF?jyls{0`?GNTN=b;bglm!ob$e&B!cmq0g zJ3xJ*XU|VR^Z}1n`UL-+M0qgxQ&>n|Jg1MS7{q@M>|c<9yA=OQ-d<1@aR>E@587B4 znQZN+^B#ZDW0?^pm!7hIHmOjbbfy}#Qbbo>7ziZTkGH$^b@OMZvaJ6}EJz2Yq_*Zn z!`I$n2A#5Blp_rA`RHrU@S)aBBeYp;=i?el96eC%;OiUW#R?yUDza^_K+txS*ZWiA znjryqh{fk=_Sa9?0hEyF$n27h2giF$?QY2U)PBt#+IZKU!U5Uj+0jt!3Z^PDq*Z4= zYMuCxuVdnE6~u|Aoh$2|*-j7Hkk)?-?J~pWQUD+TK>*Mv2yC+U>#~(47e|cLP9(vz zt0d#WcH=;`FGyTO&5`NZRD22X=m(FMka)!jz2uCRoi z!9!pst{`?5z=wEgd#(G5P4N`6%yR|bEl62zd6T(5p-}j<1Uq2fv{UuLwE!Zr^~>70 zX#DaVP^;#?8bW3-p)4H^kx-*CI-Q$gLTMJz}B1FF|)>) zM@$3;Z#-E=e@`?hl++xj82^pULesdtR|<=ng~9^n&8Hy?*Hcu? z_B8wly?ksZ^Px|=8tZTXG~xHW`;1;U(Y5k#y{_%&Iklm+d@6~uxkz5!D^`vmCq=3V zHN=tYT=I4LewnHQeye5u)h(tIs2MX5z8<%rbJOl$yZ&fe6iblo(^aJN47a?TtGNVA zb2`C$^74JVrXg=h64}kegn+As1qI=U$9(?eBe{?>XjZRp2(RVd`tiy>Bxi4>N8&ka zW_udAb<1k6`XP}lBts8W7SYiHLwyS*mC4qlpkNq`{TfqUh(vAqxA5%x~?9G<&oNc+^5O8+4#$gsp7Xs@Hg>a)@Q?AwCS9vw_PJa<9e093WR z2=)%%UFs@d4!bW{xu#v(kxms-cvObhPof)h#OUE+Rtmg0uU7p?SNiAy_t*gb%7x4U zT1c+EzG1&gBO5DgAN=KD#Asb&L`bTtkvalb2d!g3CB?$?9@BKRLQ5KDZ)S|7CQ5Hc z-!~7i)j@e}QpRJ|Rlo&vZ9Vx#?G=4&E^HPeeB5ZrWq%H`i!jpPE?PsO2l#nZfzpI| z#l$~J7V58WLJqAy0kIyzdu$pbOa;OjBOap^eS?H9AJ+Z`lNE*u=B0^mWv1%Pe?i1q zY@#{^rwgqy{tt>}T!_rko=74~2;3lMdj2AKwa*%0BYQ71!CNl)iaKCeZ?rXj#h6qs z`20(ls1s71@{IPHAslcl&5L-Ez%wv{m|A#$*WZ4hUu7%!8|Qrb9okrr>6J=f7>?Ty z@WT(J2?OApO9`!?A-%r5-e0k6yV1?|M*v* z8H3_n>9!1|a3^6Vy!rFIyOQ1k9_aDlzJuioKdVT!VK=tqX^PLjOJGc^HekxWC02IR z4;}j1XCk<`%Kp>zNx8mLZNfX7ukN`|Yq^{5KJ=}8T+_eLQHI7RmBX+9ed@>#hS=M} z{pa_P_%c@DqQ^XYQkN-zO(}Ie23^XCa@i~5h0+l8w@*|d6#_1Hg80sb7sCcu1+vxM zOp3N%?XKen(1fY-nlM8jL9MB7M;k)E5c~0ci}IRn25u(KIRCwmYD>y07oVZv0~_|p zn9<#T2nooavNubF|A(!rGLZE$vO`G)g6FxF-lns$y2hwRq*b<63i%mi6B(?FjlyS16Uk+pI^W;3(jPfgZBPe=$2?87w{y4KT3Jkb zu<{1Q7T*cL-C?d2q*=*o{V`I}@P`4b92GguP=fe>e*#k>U=&4I6Q&Fc6#l6>a(1~< zEee6&%j-2BZ2<=^0>hR<#t@DSmVP-{%ZAv(NQK@LH9-Mvpg{gVr+s5+pI?_hct%Bj7a|8sV^w>uSBsIvJcy%9=_g! z*$RLqTYP>xhGMD#RUN1@`~9+>WTN0PZ6CHxvr1-agey4+*(c^|6#CbRj?vaGwx^>; zpnbNlhPED7bzHiK^!G4Q*=SearGI@2 zVXK-*b+h%9ezz!ugk%7nr?!DMAfcE_disg*F6y;R2A{yGmmg4Xr+StaZ=wY~wfM!w z3MCv+7DWCnuF5aLh@LndjK}?AgqwmFope_=c`jvgmh!zq^z+R&l>>}N5ZZ`8nEU)g z5pi6(_s3}1XuWPTlKvMUAp4H;Rx|TtQBBW3>s}R>zx#OkF-cYn8}*C}F&S^$YzYse zg$Ivh`OrAnh54;wO5Ji2s;>u1x9EjID4cE*e9wHe#%g<6L%0_=|F=Xk`peKCv!&9ABH#?J!T& zOc$2Emu{*(usUB(8BdxbunK+B;nU3bLdQsT2WOw?f#7B!eE$3S$4Wdeu~+GkJtdI- zXp`vknn_XkFq0#(-?QxF%hn4jX&$Hi)QS|g*tORmhJrJLcYoEFh%wVf>e|4tMI+4~ z^aRySOrNxL?49}5+AhEJCaXSN`M(8fBvL4|ZIB<0}CG?ku1M)v@@ zLC46QS#ua1)*_|E6uSeFcD7%34dveo3gHvpSS0 zKzWo$SW7rmlxf<(qd>TmBR)W0B-|TZ!n*GE8M%mt{L8ma-vK@$03@nS#;Tr#SU_9~@EWp%k4qZ@zfhS%W`t6YGwm}d_5loFq;p^ zP@2h&f(@`OR%x-OR+w~lE+Uvz8#1^|kvKI$5yyV3$M8eS2RoEf`?(tc`WOA;2PQe; zQ2BaAR_}X#{Bm~Xl9KD-xCiZ8Lh;Ze&Tzm(ZC{Fuw;>E(uv>id8S<*}6l&=DOeCib zA}w>Quv$kL01!+FfO!Lr9%+ULDV**27cWlZU=i8Ui76?g%;Ry>ak`WGTkA<)?he`T zoX$tF?b-$1(%}w*Uv-#zPP(MS*a>HXw=5{#At5 z;HbdGcZthvGiit z+!SbjCl^Rvi68CrSBltaGz*1FIa)ie0$0;geFu@NkaT`MzqI?#?GOX!F+rS0p>;~V zywoXYm3Uc`_0iMo)7V*9FuFZJus7u=2wd_Sf6=S4zuLPrfk{uJ1Zf?mWLpu}F-kQD z_GbXzkTR#x(VZkVK*`-@@thR8c&NZPc0j`rCg-FNty9HoqonSK}WO^Vfl25%@3*@*rWQCq`XrL6md;oolBU zW_cW{gbU=r2(Rb)TjKj1mUv^~Lutq68*_p;iUWXOeU0%hxtF7hNde{KfF^AtddOw; zI2*0+8Gn7``N>*F;6K1f^K1v)*&G+US`n}%MB5=p^fGWHreR=XA}cv86#c?81qz?S z62gn-e46r5biK#0rz| z>mqoeFynZ4XIB)hrQ^!{lbzpv!Aa|u`oPsD_5o+?UXq4XsN2h>y11SmB-shG2H`C0 z5oeO&=M3G|N-d7(znPIu#<;~4uOtyaFXGQ9n<5y*WD>-8K>?OL#ilL9*x1cM|Uc4|b7wP5EwVCn0?VJLid*a+zd z$NC{JI1yikMDVhH%PGN_MsVS-)b!BxzSo=noEAgM3Sp1|YnXbp2PLFmP5k+UI5DxH zv2MQYq6V_S&J&Dn-3iafGgxg@=M?NHqZ@ZClC-LokN($rk$r#7-@wp?npbtSRk9P` z^AE2Xyldnqs!)AQ0Zktz0HdWAxl-+Q&`CuGPmeSSRf`LQMxKX$o=w$vI#``~=>t+c z^;tNm<0-EJ*j-ONG+%q;PsGB<@^(5Ma@EMs9HZH;+{M@IzkN4Gz{dhQlvjt0KqWBe zJueh!S6OQ?Ed@TqvvdEWQBmWzQew#${A7m8a)87oLV5+6OC7g0%Y#?KImV4z`xbjT z!&~~uMy3SS=j7sgRSuuD-6Ywb7hz*IK+qHIl$Z3`)wr1nBJ?;=IDnU6PTXYWMy({!208=VIaA#CbSBg;`}TNmQrDIx3rLwoJ?L}HT_-EEAc97=Z9sbETPS$J;|-qUbuj_tRfL7Q|)!bo8?hbufBtXF|8 z%q^de)fXLRkpn0O5ZX9*kGV00nI1A%11YKT_qvs+h!Q(QFc%yk8h-v-WC7(rV7-ZJ zrQzR(@L41rgoWsMfb3&(782zek|kKh!Q)eik?f0^q0%Ff#0o$Lu=iwnoY8G)|2>I= zK;;#Hj;AW%#m*^`>j>5{`umHQ*a*Cr1j=XmqoRO%;?tETj8VLx@yeyNy@_gUD)P~b z#B#++DR8JSO0tu4pHdJgy*ULc?U1%os`obxwSBKnz10M#y5QCH4;Afk9bOcE{K4d^ z^NrTV@kPL`m@fD@r*e3Q*-iJrf#l%2>h$`^Z&ZN`D3B}!m`ycK zs4@Oax7Z8YzNaL^4x$)sjE03NiJb6w)?>}PHF!EMZs4ZgQx9xGZSx15tpp{4Zro^a zR+df&FGO{uBIH7`IUf(^(H%aDW43^M1S5CV`Y?!udM&W9gtK3@&xU2Fp*cf{lb!VD zd?abqX6p*SP#hnI@BswONcJuI_ z%$-jR>bhF!iTMR#C*C6(P8WY7?AScIN)ua3D-bn%bi~P+4GdIVof9Nm-gV>y-UV(nPDM1 z@_6XDhv)*oJLsl^9AEq>->r2R8uDHql|7dXw|>LmV$@-I3N*nN1@JogHgr8vpP!Qqx0ynJL4Up=CcYp;N)Wz{ z;$`T;kXXYsjg%$x5{<-8Ka{WzcRj?CF@_r4DN!|nE7N*ql}28Tbbi0pSx(aH>dN~> z_cXSrz-gK_Ob1q&@v%#l!6+Z(bclMPd_4USD+M21?G1=N)V2P)q)Am%=lhsT@b*rY zqLJQzz7i_@)M#C&a8NI5duIP7pwcLBs)totJLeTJG@`H72`)5lfi_Vv+U9^%sW#x` z)Kqu8V$dgkN0nT+aOP$eU;Ad4I9I$**YEJAM#*MKD(9@A;q?*xn4y=K*R(h#@CwX$ zlWn_!jwV;F4^hc#xAkES$2;9g@_&N35Ilx?P9MQQ z{um{$2c~;sLH@Q*JkafA(FO5x7AEHybxHQ?W{|!dT9&O2-IDCMCf5>b^^P|6f*^`YsXNHTdHUM z!t&~R#>2Lfjan3?muo`nnVUbZ8E6Cn<$Y!0ytecE+S5o@mkoyD5qkM;8eMHly!%Dd zzprwD13xez%CGCv)!FG8__o<%72ds3lWF^m&dm7EJzbTTU0Za&-**D?uX5p4MhOqM z9Vn;r+*#$mejwHw}D?73x*jJ+-56^5;^CO%ny}_ZmAA8y@t&Y4o6ng zMqm#r(+W?)xcOp;cM`g)-gj&Bk)@r42A})}3PN7{uzE7f*!(=6B6ibUJgJql)ih?^ zYWJM>Z$~#9B%T;BxYA;#eZO%wl8hE|4rAL)sZHiB>%x+pu3BKY)*1dXR@SXGy`>cFG6pT`O2g1A1Z%7 z!6*Kcol?#~w>ktP3C9xbnh_w&&WDicuA zANhY$m6mLPu&ThOR*Ahu&t4Xo2I^mW8gPne?dejjVzZ5K@bjUd3FF zZZ-hFyU0%D_ODK3+S02<0-ed*B^1d5k%)M^8do00$YP8jWPXbG7Xq@ItjrfX)@Jq4 zF$0}glH57_#=yj0Ht<89`|B@6Lqzj|g@KX^GG@r!%CIXdf)5z z-zxZO#1)xFs7}XDvFU3d{HSV6A~V%sqmb!Z>1C_}Z{LCL|BVd`E>{HtGHN|tCVcLo zv$6K*zx#M#Ro#8;$@2qK3Qj>ChMZZMT9uWmJ#@t7lN~s(`tmL> z3Ln`xLL2!}_L$^x^n~f8rbd$?iIG2*yX&x!qyI&-oW=!%rlxlBK-@rpT*VEo++KD@ zdQhGT**#sYvhQh*l!yP7c0S&lvg3VNU6A|XRRBZcQ`0hpPo>)CHS@ z{@=SC{p2K{-X1?N#XV>JK_-c_lV*H)Y^LHHG3ExgG6v3q@ZDTgnU{MO;z{1*B8L;( z2@AQnM0YZMMooT=TfAEA<=y>w;^;VGfsag|BH*L)^D(BKAAyw@3im+JN1Z>(B#m}o zw4PBZM#^$@bAHy9nepHX!t%Cw3YFhkkWPiGjQAX)>-x?ZgnN$3&8?U~6aCEQ-oPxF za&TuGjf{#*hLRpdFCD`xy5wE-NGIotYgDmBE912H<}fBHj|gOUY2Nchpm_Q>DA5Nk z)rErL{|N?mr^IUwmCmcJ=!KKUw`@!K)T5o;|FHK_e-(JXc#EscS&57;Kd)_33%<@k)fs}v z@&&D7FPU0J>7#2$>xuxkp`krn#0FI(PmSnh^%$JKCGN+GKY)YFr6Yoj*3?;TYqZn42+b8p=WW_s79a}?7%}SZkp?hXXQQedV)6KRkbd` za>+Se`85Nc7za~etLWg13^?z+jm!JjHSoJqA)|hSC0L49N<~t+zEM@^xK3;}zT@iLq)er!(8dQ&jj0Nc6jOv(iw#d@!VP=#^m9UM4JUQ634C2Zsww8F9WC=_*RK=Z- zTXIf@Ip@J2=wvRy$N>3ru8j{+*^7qstv`QR#ZoYh$B4kiM~vgIB%F2^kw-Sy8Jt0V zOmbTBL!iHRc|V_# z<_Kq_1a*h=VRwq?^TQgU6zRX3Y2ZKJSRSR^45OSteb%ZLmyEXasIiXxC19<0CdDFc z?YG5v;nNS?H!7l!uyw&MaBm|H+Lyh3SuLQ?V@@;8ltFY)1U!-H9ftvLhdacEEEAJ{-?!3-BWR zCXb0s&})7`ip)VGvHO|M&N<=MVRS|bQyDTcofTY_Ex3ly1kN=UB0iIhAdlErD#M0* z6~>G2x4g(!)ItE4k?+^i*?I-+au9=jmbDp0AiYZr;orkSnuJ|f5$q#carvvm+o6RmQrgCU z#Im^;L(spHR2eJHsX}oL^vJ6{ldOxAy|r>Q*+ce)r{?nic~NA zu0YJex5<==and0Acg=hGqL(p$;K0$up6jVfn>jpYF8bOX6dx4X zy}75H#?o~nTI&rwc520A)lpET&<{1DU;6ZibYR{Lp7!5ct#kZ%mx2^Aj1CGl7tl$nl;D`WwBCauSwFb-QVZuZ zC)L0<-VA=_X~Z0jexewEO5@ZwR9L+JCtao|cbqx55Nv<-58yW4i~cx&yLj1z;CC9|PBNUvu)LYT7FM$*!2G9+a!!Cl z?#gaerQz;A(-w%#Uo4t$S&SMPIFUx-To$85AynSJkpx*9=Weivg0o0y@r7oP;Bxw4 zAkP&(yjUT@=Ih?5;v>7k11%jovNzJUzvc6H4vYn4jXAqQ0{CeHk;+^_0VlUiJ>!`O z+sO>hFT8Fxlsg3&RR|bw*K2pb+)^{q#t=?YyAo7a6MDO8?0Y&#Na=<%#-iq;Bp*!P z;57^wG$|P31U|#fYdJtFqJVD8ni%+=8;BULCFKxo$!+L9rQVNS#HJa;dhTC*_Lonf z){f`9i47%ht-vnMxImg;;pLI{(W$-!sokrkiJbe|&Gdob;@9zi27(AaGIt{C?P&X_ za+ILCsLCGA9zkyK{`=7aVkh%n?tx_CBA(7MvfZHH=oilgTv14^%Wp;e7xwIMC`u`5`s*S|2*=lj{*!g zJV>eBp;*8(?4VMgopI7EJ7Nr3Kg0IQlA}aZvEzTteEpzc|M4U}*+_P-0ZcB3#C2jM zrA;)w%F4O;0*v&iepbwMgnHi=;v=TMw1b8NP8IFV^3Bf7O;(L9WxQetpEV!{6wF=F zO<8AZv#H5JzP1ewL)eT5X7nPY>4;I8Rc$<{T3eOHVyq}o2b!o!U8j;jrK?z;ilCy4(i1PoX9>r$CnmCB!x%eN3a#4$Qio zV(x5sA>_n)2)z=P>=l|L} z?)#SaA2k+`&>F&A-u>0oIBEW3rk~P9bKJk!QuB4($!JxxGwPZ}Uz7WTBB?$i@)PM% zLajzeJ9ua)5~v0bQ+cc$xx*e+zGfz;=Cu>V^;XVOGu8IP{yaHN}QXcIW`I z4K@7a8C&nxKjPWlGHYairCz*FHl}&nogZjN(-)Nl-h~8)|KWkD^>h58MBc&_`vBt2 za?&iN8g%5!KhCyUOKv%I)r>Y6Z2VK=7qcQ;xrM_XMk~inPwLd^ac+M+tY2~X!0za?WCLJ383JBCo zrAv}t<*^P|l5-&ZE6KUxTaXriOl~9@#j<|6Y6lwZMYo15d*O4J2PpqfH$3Mvyu+xB zA1Iz#Ak`WR0x5_*g=wbKDU9_Y&`u;}qq(&VGInKjnK{=kiny@`?3HTTR8p{O+m1#2++;=fOUg!q~$jyYE z>{HYT$PeYfN-B*KlY)36Ar3)G!m`?bRm4{fqhw3fxl&`ZrHFoxwg%Mdf^k`3_LY<} zQ0K!znoMUKb-YVwR|S#{G{eDG&}v`4^)c#Bg!qNfW<6WZtY7`-He!LuYkMCo0&glg z3y!1S&1Hyw5{31o7!WkhPEjK<=N1Rhj>hrpKk4IAqYVtEKW*FzRS=Yg9UI=2wGo;U z;vScB-{vQ}N5{4&qz15YSvl*A;#R<7ov_c1dGBo><Moz8_x zIf~Ww$}3j;b0WScI{#gq!OA7GhK%d-An7YE@1JLcrVeMdHne4`I^Alf-DOCnIJ_jT zLl^JtBm=*|>?8irgaRpzuS3*KQD^)`W`X_~kdlNSrterFX|e!gA1Bt~`O^q~F(eXl zoiDoO6`7)q>zYB}_&gg!afr9lk9sPdi^)zOMa9|6R1$4&6U^X$EL{V8oNW``joGlV z8mF<*uxZe+P14v_V{>EMw$s=~V>fDSV`JmnxA}g-b3Hfao;h>QnH~gTW$-%L?@5u{ zoTRFl{5;YD-RNieAa`-(G6QMrWVP0Tw>p;#yQ?1`fe2k`YvHBmVtjJm@<==9Kmo&P zWoO=PSN%QqgXz6*>4cBT!Cw;GSXeV|Kt<{mn^Mp7+80IEB^Wgl-ZL-C7B6q^L_e0h zV856&oH^(Zxpn^T&K{ajmRLMSiOoEl2bL{F^!RQaIx>KWDA7%@ zeh84_zzM@7`pw$>A?wo*W9aa@+?u&a2s)Xh zZhm?=<+@%Uf^%CG$L{+2Fs?4K8NTJ@dc|6XHTT@EimU~VV#^A~gHkMk1D+|{dcOK6 zA_ayqLP(_@fgWxp?>B;&tpieF&fuD}U(dv{Je2p%`#>hH8~&=KXpnUHCI>)GF0wAH zvj7ZyH!_pNpiNEvBkCtH_x-j{>1?KhWKY?Ar#v9)yJO`#EQwD zY;AFV=mwkrF&w+S0aB7S!n&9x%+5|{hSd5(97>HjUxrV=nGC40W~#SWc@LlA#N7y< zsYdYpv2xpbKB+tNRyU<UN8ihps8ei$4OwWIDq0!2^9q8; zVS%cVho3ZsYJZJ16DEv$%nRy@Ne=AxsQ!g<9)GBBQP4iTYr(KLri`&asKz=h-#e@> zf9#&_3vPG6nQg9@lF>PWV(~QJ9eM)X*+7%cE7?lx+@(BkP|V}He@qk@J4pRw`TjE1 z%z_O3 zJ@Xt9X@8)HSPdGtO-MM5mUcK@>V?Fy{}g@Hg$p-Y|b6B5l_(g)p8s#evzKJ?!i*%MdxZZ^Rj0BA@+R~-TxE-3%w9}N) zpGTprxr1&bAp`F0shgxdI*t1{J=YRVD6C~Gw8mdTarPe$NRO=du!2RpKnU1;+N7K*>}6n%mj=}8jai=-6=oHcl%OTj!e||6 zv5o4%Oco`>29s|P`|vv$%HmD9wTZb;D`MLSVv8kM{=d=qsZv9XE$#Tf<2AsQlcS=wK7E`c zPOdgZKP5Q#)EFc^GZIhgP1+H{0qG-u_M}#8{nO=KVWkBAv^Y!ZzOv-Sh2AO{|N66; zFN^x`-Aw-sY$Js8X}?@!51U^|#Try{IVPoDFbqMs`^F^0pY&VED?D_p9|@rYpE&r8 z4|)xdTtOo(F!4e?GV7s!daYR9sxg2`b?$DOJeH;=hy=Cef(IP5q@`n5`=!v_+S4%T?DAq@7MK98si=O&A8sxZtKv>qvZMLB5H?n z-&xLiEhz;;q9SWRdn0D^jq{ImYrygEEO11O20uGi{aVf7RvxWOrvaTPUw(Od2C#gq z^B#o)G-%l$TMU1AaJx1IzoAiwmzdqe`SHAQfP{1e=ScC0;+=mNq={}%3-V+~zx!l9 zFC20-WX-uqkQvwE(y*u!g%kEl91QcrsYBip6$MuN0@(mlHXLIWFezIN*RxhlP4v~^ zL|~r+heeX~OMCWRK|E`oS!_$N6eeIFCC_|kTnFb_ zRM~j}0_Ehg6jK>m)El z@IEs*p*lZ#sh_g^~-8iX*IAVhUuJkRHBX{JU^$nf;A+8Es_hs%n)v{UuN*1n`V zJO@ZjQHYq1Db-?-vbl=Bmz@#1P78ktA6vwF%?_a8ttA{NM)`NnG(~`h`hH!W*I*8= zf!z+I(BT8MFL|H%zRgfuC7|{@Itlz76WpAII&J+C+JLAK1JOeiRsNN$l`&biJl-}$ z+QivluE7rzMK(vW{8U|EXv9?tZ5x&uzfm@w=|!OD}De%D$i1;RZT{x}ui zIgJXMH+@L9WFPsmUIw4?{YCSy(Q#ezLoAXWVG4$BjQnG7GNIy4nJ}79HSkuX=g#s4r#8J9eZ2V{I_jU-3s0 zJE2L2(^#OI*i7~wQinOpzkKzI3X4(QB8GbywZ?6jzx!fQ?xgo8nAZn_iv#eHgu&jg zP{k(S?3@AX8#ot{c-?Liz2BK_s%1j>HP}{2v0SB?k=r)YcA9DMyY1YmS$I5iJ9TQY zPIo~ZY1wQRJ!1?PV#=c7X&qxnv)JZDtqqJ!you>zzwuXaYhjc{mebZosRs@?? z`qH~hjLRpSE|ask-X?P6hsZl#`vbgtQc+le$`whdY|%e#8lXfA+ISU!TYbc(>>?S@c-Qe+<%;!!aj8{1 zLn_?@WBrc)^WYM(BcLi0XYIa-Zp9NCR2r%FpoJt9#7C+qU+mRSfwka@s)!zyfx0$I zaRrV~t{O)?UNSpd;6co;ng}1cPiS9kyV+Bb(7QH5ELa$pMx%M-SAuRxL80!(WM`{o zY~vConMkhR^Za~>CzOZ3ki@cQ*t8IgWW$O@mYMZ&?;y8Gn-m6mpGI-);@Z44u8H4m*1(cL!R5&}1aQKFavXi(#bXVQ zAi|rZmq)Zj<%%#;O4jW~TOApSy=aS7R`0c3IJ_o$L;;9!%vFzIaXXwRF3_d;FMV~c z1hlA5Q9j){cjh2^Yi8gfdFFq*8^D}5;G>v^$U1io)8`@QW|{#o`W*PG7&UOXKDQ zMme)DcAfVAVSbwwIDuU71Vdi!snDtaj=ZH>spI7Eun%ER z#eni9WO)!g2R`Hmd^{y;U#ysww|W%`a6IvG z(-<9s<}^W?=p?tDq&|_GzHanKL|t;HH?*)VB9zbr>V)_-(`#oFqx!%l3DDN;lU>Lk z=OseGn7tafMDFsbhe9mCT$Ljjm&C($VesZu*NM&tme{_DP#j>~AeO~2o~%xqy0t0`)rd$ zGg)HL98|?;azOI9U4<8UE5TYtcu9orQ;GHM5)k_)_&C$sYmF-^ZvJALqsJR4D1;!V z(}h5`JI{%#vc~U%!;>yxcAH9fn||U#q&Q};D&bJ_CGz%-ObZ!to*hbo38Ovb#+xW* zSQvQeC^2kjq!v}e4mWa$`#s%ODL;LuZG%*pQkh-?#`XCe6=Z$h)VWJE_v(;UVpD&( zK4*x(U2jX3(_txnhxHuXwGJ#VGkC6G);F6G?^wth`Siq+VSO5DNncHj=61Nj+`p0N z82RtNPep*LioRW0vu+uPI1{H$3h~EQdYX=CUa4DKv2hWNAKX8jf9x(GL|4%chbw!% zY5{%|zMtLtddi`IsD7~;#j}?)G6OC0vk3E9)FyQ_2WJN&zJW4(EQkW{u<3Fk=EYR` z46cwmDK5AjU;cSVfKN9bGxHY6`DsC;`sm5}tM5~WW4_&x;iW_~3p@zC%41N-&pkV}JJ9ID z=%!;m(Z72?pQbsU$6IdTm!ztFl)rhNHtd8u;%wJTUG}dM0FWZ-et-7%2J)o5tFrKT z{R%A3V}4DnF>MEM;{3oeF^}H4i_y$qh2IAc;9xAp^Db9J>7;Jhv&4Uja^|{3)QJjR zYDq<&L>#_%d$LD3(6xYmKle3<^*y!N#?RN`m08;Q7J1Pb|L4s@5>rZjI3|uq@V)li zU;DlSl!SPoeI~qCJ1fYSkrU?`ez7{L&oriBUhrquX336WD8x>DWCySaMtxb;8g22! zY9cS$Z~H_ojf&E_$kMB_uC4Ry!3nI!7;&D=8Y4Po0HNj>recqA>{Jt*9({u?YhApp z%R4W<7056;w^!W4zS$Q)M)8$E2l#&jR3eG!5~tBly~{89Tf`VE50mu>YodyGh(T4Q zDP&ge!27Wc1^RD|y1&fCv$_=^d@dP-EXl}pN(D!IAo3SZSV*do-HQRoOs(1e?#_fN zgxyJDz7-%tcwlfW?wP8{O(#13Jk^hSj@ym%9~0mx}V6V@W@!GI|!F$bsN zpY35r%ys=MjwOv1o(mB1Zkro7Vl1yG&o<;8QE@`NWTo^@xJ;@8u+e;M1rW{jZr&Bob{Rn8<5|EFRGz#9d`p0tQ_VRXjJ) z2i-?6-j4dRpt3&$oo>$9R%6M#P7 zy8(H@@UewmxDhP7%#UuQa>%9i#R(!2Mz%|O^7uAXB1VX^PGwk=UsOv?Mz#IC=H79ZnnV7N?&xXRR53&s?^%%Pl z-rX~MW|5ERm8C5FeR9W1g}ZiMSdWs{DyO^{?G*W4KL)+Qe-C$`H^7`#N9jaDSKWln;ggMRI@sl%vzce*e%~z!F)@s|UMvL`7h18X_turjBm8>ed_Kqa2K9g2KNGsgn)kA-+um+C?-;s!fW3IuyYmXs|D;B^CfC{6FHl{MUt zqt?6j3h-GRo0i*R{ildi;a9b5*FTrP5ocUx3w4l8S)f$+lpTt)%AbU8PHX6g8<4wL zR{s21K|uIJ#z;k#;~@P}>ko4{>NC_a$4z~L3Z5&GiodR1Z0!2D}t zGQT&+U@S6ODSult{>YGm{f8e|*H9hvLpB*XE7zDNoipnCTn0I=r(Wder-I8$kVVB;vSVD@}kWCsk4HKS=@PYbI*@k&j%VwshJ-UnJI?#v~{ zzDvgeRjl&>3%Ry-EnETCe*sw+1XT><*VTAGd{y(+Ph)L7V%Tq!0x!dA%<9wZulHli zwTbSclVot{L%4+l+UxF`(9yr7t#vCj1eeXxhc*$p5$qUfiz%)ICcU!BYrS^d0?{Zy z5`XlX6?G4jAT?ZTdxAuL9mNKene(Psv4*1+yKILUs#qC3zQ9G z5}~(ZoWKB<{0~khM};d#wcRhWh)F9y@}E~j$b0|4+dfdVpP_vs_Mh!U%{b&spplxb zXtpMP#;0_KhXQ!o-A&v4S7XkB96yRAqC?R#bVQomy+|svEF=_7^jwj^<^}ERh!Uyg` zf<2o?hB59k9NjOqh*++U-;YN!`R#H>XO80Wl*qK(t?;L}9^w~nXzwQ8R9Db7<^jsl zXr90u>=>u9sUT66pWQgOV2l93@=X)%58d>~$W~>-pDh~M^jAOZ<3Ch^<$jEYj{VGO zUED7U5WDWZT;qNrKZS)rpMVB$Kx)c6;q^6RwKyabv2Kz^eX(HGpIlr~)SVc7r6b-h;CSo;OO>z7lQf&EKZO4e8(Q zfdAGU2z&Mp8AdRzN`B;BOv*vxx2|hRN{h(d8G%f9p*m`vfrc4LT`+RfuY~VWf;8<5 zBv#KxYl;Yd&SVLLd$`&?JXxM`ZYt*eqxL$}9^9)lmKb@^ z%_L$SGUTr|V6 z5U>Q;s^mw!OImnGhqo3do%)?Br>De}6`QjHb!U7@QFY+=wFLoF3KymoKOb?hi%;Qx zDXQ*$=FqLRkgM>>=C|n3e=FXU1BjIpUOx^tW$d2$``xuQ?~?ocZZYOFlNe0R_D}EG zG@9yBBpRRWD;DX3^^r&mK>32QM(2}-mZy;W5AR2_aPC;9(q9v$a2MA@g}d0zFuE{g z88vy^F$BWB62EB{SjauT`)ZbHRT#G{>Nk60=diiHwQ^jt0pc0L>p5%1=cFv1G}$Y3 zI1{#tNVVKr$$gX;A5L&E<@zOgMWIgDw^$t&Qt-8*CAG@%`ebJEt#*ZG3$;b8Gocj& z-hXxXFA>t2yZ7VI#m9K3~<{vgiO(T7Q&ddx z*NyNn$wEUF<*}gzBGi$LVp^45!e;f}+!RUe@{A~v*$Xb+z*;hgrCf!kv1aTuxR zpZ1

RTw}NPM)WwV?)*JeB!e2gvS3nuqbDJ%|*5Kz4GB4&^?l_Wm$dQ;VH;$# zU%R+MUuMOXex*OR`9mKW7m({hn8+0}lNT=YG;lFEKk9f0n(xOPyFp<@u7( z*x_@;BI3r{sGbB1#9|(?*}226aNmeLGKQlkzRTY1E-bVw#`tseOig^Q5!Amd3E?x) zKODl;B-0e^jipP(=V*&1OOrAD#O>H9T)&RKuF533hM+@~JxcRb;dAx(l>k&-=lK;~ z@H~=fb~$%SqTl$b{@H*<6%k#^G=HjD38)YT#2BY2&wKesLlm1TxLxFTP_pio!GW2; ziRtex(;*aIXV?w;uOVpSIpl0SzH_dGpCuqYxoAm}d_(zX zE(Vmto@H4Zr?Hto-=>)8KghUHx;r-6)I|IXX8x~0Q;E+&pX&CWUR-v#mrWb5J@cdZ zc_t)ur0vItJB=kpLQUeAc5Bd|O8xvFJ|?X50P-R%A$_4-`p?r6MTVSp-b52$$GdqI z^&eqmtVj%&8zFmb&3$r(IC*GPI@DyRK!=N@@;zU2BkCKxRQr{ZxSu86zY2;B3YM%7 z2-Vr&VCj03r@g^KAY{Nw&q67WYT=pa0_w8Bc7`?G(PUE`EsKr zxG9fQy>Cw)=Flvof#y0FOCfCLW>6W#hK5co;`h!mbBQF8-@y5m@GF~ zT8r+f9+wzo+$#}kY+pT_mBjo!TBOe^Y8#fy`Ktb!$Wl-i7|eA;&|W^bgtVPut0fc< zFOvG8xLDplpG;lo4j#WG;`A z;BWZs55|g7ROkKHpc{M8MyVRg9zoXyV$(wLR?O;xslTd+pd(OV^-;Z_2?GB1zKH++ z)kxCoMnWdbop#Xtc=-LyiDd~Zj?@g42-lpiD?tdx%(Dm$Mf(><(Sx9PGGTmr1*+~4 z%kj9?KKIlctP-$**%;gBX~gF@t~MLHO}fCYJ1SCv z7%vup?e~IpyeN$wbaG|ljeGFuge*g^Pf)>Z$E2tNr1yfT5aa#FW<|+{OECERd@7^v zgG+TbiT;V(aE-w82)}QUvlyI ztM4_1ux(9Bh3UTGeh1$*Z2Ivtr9#R zVQiY4P*ZO5$U$FdRQA%aAuQ#C(RIJo($w47(^*tw!0|g4X6k#MC$p7B#$!46{F4<0 z-OJm>&d&zCQxet{^GGjro5;xdVz?Z$=D-{(b&7(1^IE?I7B5vFqh7ax;kcZ>xMnE* zSpi&UTt&QywrWn=aHc1PHe9!%CeYRS8laQA8%0d6?(FJ9uBHDsbp#P0}$owtyvVb)V%tZWj>Ku z%4gcG%HpRrm&ze%RffEw-v{z=6~HIVQ~aFBYNEd7!kz3iA$RVAMF+bB9n05#FK)PS z5hEB1&`jnBL(-ic(bP4mtosJ<*%k7^017@C1)+cnL$b*6J3J*@2dNZ3Ls0E4z?y`o7&Q>n3-fD zaXLXi*qanV?xPZ(bVy0Q1v{kfTFE&13oN=+%vq#|m!J!5waf%zCWv+X5gAs}BHd-% zRQXS(799XZdjjo4aZ4}Zn8cX!&2p6R*3$iiNRcBd*>%#3n8SAHx0c#FAc1Y<@VO{4 z?L45(S_dY42x7*OE{^XEr0KZxfi2^5ySi8W85W5WQd$jZh!d_sb9mhVX@j;NtydD( zTBV-mm`|vSj$$gvj*>0BqW;D3?QB4UtcdPLn?HZW`^zLaeu_H8nhL6eTWcVHTlPeQ zY#(N)Rvrm3ABS4$Pu4<=HCIsNN^Qd@k@i=zKQ^~fVAYLi+mAeHQF9l5_km1#tnVR~ zLr-D!>hx(2XvoqC4S{2@dlCBy1ssZHzkkhj3uVl(}6b4 zjGqb1Qx!thjk9#2{Poj>);l#uW^UmTPH16XKLL5pU_;R&`S!2=w*7?fJHfzC~Y+Lg(oW+Na+tb2( zRlsCXq}=ChWY(r|-=AE>RJ+TzHXSf5zXc9NetO~!RwYLM@6$PexnPlX+UEhC{nK?* zP`Wy-PEiUPPXU7zruN33Lci^%2F<_L zmGA*6NN$b}J(*!>G2BR+Uj6c}g1@4#5#A#YW^bl0-1B|)UOY8X5GfFhJfxX_v)N<; z*p<{*Pm_`(HxUs~3;8Wx5nH%XV5TFxU7g1k!W$Ck57F_zlrNpn_Vg$FGs~Y8=c0;k zLGQ2>RNxrQ$Y*%dqO=LU757H8fU?}zRbWrj;xd z3i!E<8sJUI{GlY}sl+^>P~R*x#`4oVaH{bE=kM=3HBx%qK!519v&9DVX^LOc-3aSN zn1DN@3UPhQJ%KQ#k0DHvB$}|*$HN}9#N=hi}Iq<+&5g;ia# zmvc+)Yz$V+#sAmf;^x|k_PHviH}u(Rlz?|$@b$g1)ZfTl0jC6;8IS(l@0aTZuD>^b zg*sF1g|2uZeuy-!tI|e;rzFLJ5lr{q44%!yu`Szti8PxedYosi#cQ1TP-Y#N{)SH%6F`jJ)E9NFE`Sl zfIbPdpP?XUia4c4b3Q*>2jnH7Za990{6&38I~+QuOPUdg;^;$=21&E9_OdDcV9n9j zBhxjFfxfF}X1^N!$aW z;*8y?)|nN8{co|9;2U5KRYW%irREd=&lrpWWw%%^ty1kG@8$eMH|}?SGHfrHLW{#t z+wFAlcz9=7I?%~k3ivboZhoU{LA{?u=6{)84?ePe(u`zsnQ02@l5~U;2W&4SH&NK; zixan4lBL4Om-dPUY?NmmhM370-uEuIp~HXvkBgk=4QMgb5?QOSxT&J1~T&J zol)kIW7_off(S=C-H(}Jb;^}0HhAM~q~~6?D{U>5m9iJF9)IQY3q%$x-arkG96&f0 zM3MM#qf|Kt$8h$Y!R6uUClOMogC#~>n#zx(QNcMnu(WW#eja!Ld-{wMgr-ziv&CAF zpt(soo7PDB-R0VBqTg9_Ur)MmXfv}t=-OQMIjLyqQ@&qd79MHvBMTLln8yC4)wz zUy76{P54|xkYY% zcs!u!XgJWc<;Mg$K3myUH0RGe4d#6Oml;k5LM1QUqk-H5ZTU}ZGziR7i!bAD2VnlL z8lx3isnrhPS)3_d=tJFBS87U4nxw`1wMF^L=Ca+AC20}gd;Zl4ZZ(pYpB^#wXXvL+NDJa*-njp>I$Vt^s+CP3jCVD zD(7u3R>5`MhecG4LK+w8hhwid3ehbMx;xwcF85R3i26vkBYUOKFU97yYgSYqwV4_b zS?I*CFZW;H$^`{hcO3|Y%do~LP8CJpcFcv&_iRN5L*l6Eb(Tlk>E$cloGLR#ls%q; zL-K5->2OI^wP3zE(7VU>=@@&5uZ^vbx5W&D;P`R5HH#s zlegUER(s+vsm@P7d=+1(2dz0RW3=CR7wd@F!s@sP%i8tb=gstObWXHortSW+_Y=6s za)c}kAt?J_Sc5o!Bdog*pf8U3%{Zd2o50(~PSEvdB|r9p(^nTb7mLet9kX3eUs9as zJlkmtA<_yJY{djRU#ac6bbB(fGkW$NB>c<4_cv-X<^1&DHASRs6QJ2ZiOUP^rrkbBAMjoV}P9vwH-TY_MUBb!Z~`ya+^Ue z3h$w{`OYHl-2;bJPlZvbvur>pg!?U%*1f7ldifaFajYF}dx`zv^4ua5i@7ni7K{0$ zM=0|a&Ti3!nRx+F$OO;)1!3A#OZHp{H}943>zW&5IZ4R7$86~Xl9L1Z(4uiYu2W9n ze}Fjw=4)*=OaXYBu4xfl5B4W=)wR(YxP?(KPvZ8u(dSJk2o!)(A&w~hUa#{Hdmx(w zuB*S~%0|21#~8dh0kC}!OhdH z0Vn{N1t;n!?ZJbd1&=>ZWO5G%nGcod>b5!iYnrj|aYSq0ssJFia3~>yC9}7=^{ih} z0_h@e)%O*=!t2!+Vm@1-iK1t2>2zi`l?~X{uX+!wC)fh8$hBm#@*6X4kAf=o7B)?P zW$pd?`l3ahkUGa8Uw@XCF9tv@bA_D}%ZR|dhV(uAx!pdaYH?u;bbtUoAEU&?IV`$j z45fy=7L8Mcs^7oX# zwMIavO!W*A_)?%W`W%(o^X9Tc!x&o~G-KO9tJQe1WB~{uum504{HY_L(&^Aul);#N zrj2#w7XL(sB*9(@&dsG8p$i3#A9M9Wqvu&t)gf&(0j-zA?Fl%EA;jW$tK6pvbuRy3 zOkweXk~9bUh%EnUM6xIK;3zt%&7yfQmT*p0*zA|wRucAPSS`L?lU|Rr{@${nx95VY zYt8&K@XFE`XAIf(0+_QR<_uo}>d)`E$oF6oN0zR!B4xdci|%zIZMl0{c>S_ud78` zNpx^y9u3^v+f^!;n9mVjZ~p3j_a#pXP(Z41^NY{v-^JFwK@%3DgGP`^ThVC{NjXd2 z4^qjiUN0wn%{a&SF-M}k&3V>?i#u)AwL@8CI2FRQWo$I+U5Ow*W)jf(45lATl(?($ z{$oHe)bd3xMfJqJJvQ*#8`P$8`Z>|Lw@ih*^V`A;&qxZw51#&SElTBN4)Z2!R1)Qp z{{bqy-vE4JWK|=c3>6o?ldBBk5O3Qeuj%cRTdK%C!wus_gWKmVfO-0n&Bz9+ciYH(!)==E+2QJ+uyV+5CpjG~gh4NlCzZ9qZO`r^ zYX-$kH!WtFCKFAre2g`Sc)QuK*j-2Z=sc+yufIu*07d5=Z5d*xABiV^2F0ZRGIhut zF+8#nNR#RM!?N$W@l3s$_BAIHb5r&Ub6vQY?@WSST(NUN>+KNFfeEHB>(X!(n~9IN&DA?Qaj= zl^HF1TuB$LT`(?)o)fM^w>0#p9+yReQ0`Wp+RON@t~Yj7n_9XT`&$E*BxESJUqUA` znfxpj--G`k8^cNoNT!{(#f}9>^u}coTL`)fc46c^z*tcHdQj5;{lN+%A zU9WWr(I1+!pNvH@oZr|}BeO@sfx!B!_^HbWV0vfZrH5=3BN(an&}9Isi6Ohp8fHwV z5;&P);R8o#s{9(bI^u3VdUNFSIvA@JJcbLa(r99kxNd+4M4%f;et+!8Gx#!NqXxc@ zJ=g)yLh6|@s^ek5_|S^gRy*g%(k0o1lXH7RA+TKgROM|e+69k$haP0N z4{GXc)~X-BBkI&DVVfkYvq)@IULFvMaIfkp@TZh#y}g{A*j*NS=yx6XbG%-_wA8KE z7u!*|lV7tfU=M}wS!iuSuOdQLgJu$rZ5=*C!gy16 zBxE65i&iMw+A9t^uFG^}$c+&9{}nEfoI?C;>hIV}Vg%`?p z^z#VM1O1kB*tlP45Ni@}w^kf{=GC7qU3DP3R)9@QkH-qTf>h-`?iMaH(J47o;D6)+ zcUsVVBH0<5Y%G?}lpyM!v|bV`fA7MK-xrO>Y@rPPRSr@wp<@>m$r{{O6wRWUHn^^h ze*J{~#@Cq>!10Xq&c)e)dDRCoCu5Z9MsON!8nF_j^+WEJ(vD73wbf6f!F%=XgU<+! zhq{O>d_m82ER&0iG5WvO55FH29x}YoN+A-7#$ib0-+EVSj{+wfr7W97$)D2%-v(xm zuT4msp?=S@a~=hK;;#hi;^mQZvLK(+@cF@8W=<}`chzUiCuhzN;8z&W{xGV- za&$y2Y7~67rxsC1LBeD(| zVN?6OmLz;hQbGcq)r-Ao7pAH}7}Wslhm&3lb;}Cp&qZQJCIC1p?23o%-=@4X5)&t#=MIO*&elQ(Wv4 z(%7&p=UP;aiyJsiaA${%G@WIuYrxs7HB1hVS!sMn=d_glhV#kg@0(IVcDe$X4Ln<4 zq%&-ApK})aI{2ilgRc6I;P)^|RPa(N=R4%rkR<*Ih~&NjwmQhpBre~H9lku6ZNzU! z&9qbO#2<8iL5d$%Az=#dtHFJY0Mdd7FoTjUxUHPv1h^}o4CL`y$LdDh~H9kSR!ARq-Wcn&87kF=KlIO$iU>)8)AsoXePev{hj*_=ek3%NO zP!YrVv_3c=d7WDGc4(y#BzL=I^x+RYjv^9K`i-dfxuxS`a27 zB0%CzqFH4Zczqhp^iMNgABwIdmp`T061YD@Y&yo_IQE{t{m}`rZd%DKK`+l^j|%OMRYmqf~sehu4G%LC<`;dPI=!x<94iKaV6w-A~<`_?fh>Devkg1vlVp*I-^A zKy#d~VXZ+;9900LXC8H%3J1n9F+Gk*5{dRw+Sf2&7@fI;jmB30Zg+%ng##E~jVJQn zYvF(?)=Jvg$<&kEhT2J6Zx3({T+(-FA7b8=w>AM#l5&vy2vGkB=I zKIXa*I!$C=yuY}O z+t=D|+dO*o9R(n~349+p=t453K*aRfrVE!&o{;v9ja&=B0qYN?=`oB01nI!Yo z1Kov~^Rhj5&Jq>9Ku@!COhQYC)mN}y#suN$|i z;48d9$)d7+&n8p3A30)fab&1u^Az%wjA_C9+#9Y=di>df zePoO1NiO49mA5|ntv4WWNMBetEk;j5#^Ir9G)HB@(5OZBM}4X>>j#6V1GVTqqmNe+ zKou%QA1C|((s_XK4(ah`WwugKQX(Q#NRc1~JJWG!xM8QCv5iHmCAgKLoI<};RL+h{ z=~GmQ;$k&84`&)})$>I`izG?ZZDZYJ--X~G!f*zHnxugBk^b2<)?E7Dp(Nd}VYLxm zd+^F=kKmhG_U$gCXM@roXlUS1{tpdB>D+)=zm!3veq0l{(snTO!!*;AemJLbqmt|Q zT0)57&m!DW&g5_j8#LDbPeeIa^Q`5e&!D)$#@5* zAcGPGi%se2Vl5G0suNdfBY!lSs|j}(9aSRm+2uV)fZx)``;?GP5! zQjEVZzZfr49I3OOne<_}yr~@CjqI_SO&?IoUJ|JHJ`iDT{ z7tieIUr|l?Fc5IOEgMj{Ev);}cj2Pcy80b=A_14$l?zGJpfuGI;{XEL~*ywB8noaq-8l7n`(f<6W||?iD0%-c^Ln22 z+bcFEPsHd1OHCr_m(`Sk2PWU3aq*CEsP7Lv7%GJt&S1o2GR~u{xmK{EEA8pIlnIPwdpnt6R znN^TwI(-gxynHJz24nV?Z*Yz?fc@&iBI5rrL3h;!JCfHia`-6PZ2 zz13XLU()BsRP}zMmv`iPD?2YOa}8`zVNEs~SN4=d?hE`je&YqpfA7NOLZ(?0ue7eR zKloJ=qAP+R`wd_bY_J0se4C|Hfla`oT{gQzrzhWQompUXW1GP**4?GBm#?WJdj3ES zM9x4=X2mQkK!HJ~bkLSnmZUX)qc^LK=Z_QV~VbU9qW$v*`-0S@S^SKdb^#7$LtRfwwlH5Bcr81SClQCg$wMx(D$R!5!b!2;h%ZE zg+@*-m$==cqj+L@UYA2INgl{a1;nhpI+B@Ut@zhVy-fbA+$`~{6BhnvBhQfR2Vh=LzQZ0CXkn~CeLeor2FOpDxBYuv*#pO&llHOZvl;@T|C zy{U1Hd{DsF+1-?BZc^Z0hlx+18G*X;?Y}?kW>@xWcqY?~-TWaT+elAM`bXJUn96eM zev{s2$9X-Nzi&~6xz8s4Mjvme{=VZRv zsBB({lire}-a~3&6UKhWyfHWkDa%_HN*_V>683!O|8M9*B0F})u`596EZjU->O0|S z{~u2}u6r>F7|f`Vu z3yDojI88X-;(}bRtxcbbHG#xnuv`4)DFYcNM?vJX%TKN84%ljwftNPO7^OC2bEGU9 zOx^k=L*EQG?St}8orSl@GOG_{q*cMkj2@fyPQcy6{7chJ-$lhLqsj+$u;oaa@R$GM zzk&Xt@wOl+&Brr&@&uAKRLjxpNV~Mf`{7&tsB6V;dJQ9&E^Wju-BkddT%qr{o8$tGuN0dea& zRaqQtlIp*ac`PmQ-8&f9LEdcnL^n_&pj1bzsYL#33;O>d9C$a>qR^OcIZ`DJF z7pk>Qqx=NtQi)WZKQ$Kx{)#6#CMYOCzx-zZtGk}w>;_#5fJUu1()RIe|FANuhrO!SH6^O!r`hx;=`63ldn--aJF795eK$p9kd+CJQ!4vqH4Wfa_t+oi4a1j3!rAuDPY zH=n);Vw+bPCnis!9{r8?f-FW%5BHtpv_a7j1=4-z^$hkp_~svAczViTJLHZq4!r67 z=gz<&09_{*Ls5(NL;QwRw!^r!uJl){He$mdS5C1u&#Uw^Wu>vif?U zf%+^&x&!MfnDZaR{8(#4NIG0PS5y`!Qg-=T9SW$q0!(eO;Py5jR!_U_X7tWL% z&`pg_Gv-U)$r8<6`^=vIN@$`G@=-W`bbP_YdE5^l*=8f^GzR2;tiqW zY6+ElF-esKnlBD)@mA52$AQHc_fXTf%>zyv6J-t8?_L}c(V!Io-Js?Awbsz%%dO@} zq)+8R@yv&wCUjn%n2e{vZ@L!U0w`ULe{eaIM+Y|u!9E-Wa|36%J5t^c2)W}e?IyzdiZ6+KKN{>RBza%aA_v&)@%q4$W)iBvPLN9xS2xOsHYZydV8Y z1|Dq9C$}%}J-XNPE5A*3A4iifS6{?b#=g|4AJ?4EEUyX?0bc@3uUe?2e>E(mr;E{O z&>uDsV+fwb%m_v~GQVnnQJl%_iw_}WoatXs_|wA2@nOuJ|Ks4=8+O@(0PfqZ$-H-g zg}L67#V=11e=lDX&VNWH%pb}=2I%i*vzsEw#A12|sFL@p_HCF z-paG4%JM8-V;_;dZgZH@Zu~m>Ln*5bI~eDj@O?9{MhSp(Pk!F-qV^a@!fzPAx9R0; zk#M}pWKJw`Z>C+(8q=$H>$eE=yzBmfctonU3V2;LI#=VM%*bMh9i^!DK`Rp@)%<0_ zYb7DVt@0+`sUe9OF6Xa+T5XrPQ*w*7cxVh9y=fsEbG07mOxWgDpWM!D)S`p^n*ui(_2cnp{aVVu z+<(5$N!$3y!~Ool-{98M$;sfK>!QOHqc+2g^!@~)F`xcn$)s|nVnnn%mS1R~x5DYr z16*yt46;Vuwao#TYWMc#jaA|aMNYD{QBD?_rC8mKh+KRV`yHLGa4d;~Zw%vPc2})- za>$~b$0@FxzCkLbY2@_>zREpB#wB|!2$b)?904+J_=ntHPdAFu!*N-qK07)rYN;5uW-n8RiECfred-bYN|L zboac{WqvlkX?Eh575X?&x}WI+ZA@j??syMY>l^Dc6%Jir-#)8Pp;{er19p^qi;(uo zWNo>Hj%S@o%WZrU@GI>r)y+#E!Ymr0jUkYjuv`_U!VcvqcA=q= z$Q;Rt3mx%)?{Fbp2|(*6W-y2vQI*JiQ^*`f1<58kzl%yTSbhtt;`K2R^EXA+zB%^2*M#hjH53 zjB7;Xq%n^s4<@%HU;@>l`ji*=s_2qo@xZJCN%09|LhtpP*vU35%LDs6vDanbWBpLd zEMssfi6-JAZKD7yNSOLz`uoOIF zlSEQ^g}ru(S8nMsLi>JG8@JoMNr%{nLs*$1+;zgrXi(5v%c&$4#@l#9@+~_|27kSC zdNj=v9<4Gt)c;lQ;*eqfVxmHcSgb0T?hP%g+9Vzf>?1?8@nt#mV2ZqK%b9=|MX{N zj&SqJk%T)sjMJFkB57=mb!EN6^TpbR@B0DZL_a`bJkuMf%j#GFul{UI&tBSzOBuu|iM*!v{0@=w5aqL{&5 zFLa!~gYg$^hv`eO_@A8*r0qI={j~jer|lTK9voj#l&l0JISOs%AVuQKxS80j#yriG zAtFDDWw@&x%OuT6o1@x~`|_5=9%D9|aFW`gyzO0F(IhoqKD*?M)X5D0>8Rk&4oXB4$vC^>{&OdTbahDiYhg~-em-vp10dw z=Tg~@8&`V2mOK>v{dU4!k2kFcLU1`iIwim$e-^s0uR{1)^_po+yoAH2SvLPvJXD?} znFk-ZtZ|i9@3Ma?67L>ypw-&Dy`$<)ajm^HE8}z10c3?UGX1Bz{RU?MD~}KT4YS_J zW?zF%u6K_;I%?OhKxd*{`8dz*O!9zI%ng|v+Wqc{Y?nFWuP(^^Xkp{cC?x+yK;mgr zrDArtz%o#9+V@I`x!R|$kLC>xANUIo9kC>ADgEZ!Wmq{rJSoWg@K3B$%diB#{&QjG z(*65FMq4nHQaqf$Reg8pi!KQnAL{_@ej}Ll??oA#^9NmOi3k1HPkqL|fc0$9}x%rkk$>lhp1(G2dt()6if$i(5WC{||vv`U>t~CC7jDN-#tq_opbj=A%vU z&Fxp>!yhFTfm-V}5qm=Ztt9OdSbjZWRBBh8R=~X$Qj?v6h$}0A+UX6YZv4uF4$^Zv z&E)B$N(q^s;t{sV^%JMB!1;#&7vZjg?|5~Onqj?@yZED;8*Bfix;WQq)Kr=#(t+yo zR{XlLTbwR!4_PWWxwqhMA6}?Or8>6FoFPs_76L*oDf7ff8sV1vArGnFL zIkjPK%dV{e%PXCS4A@75o2t;;TD>c$M+2`3eo-X8m%0ZxP$y4xn$o=u9y&vQ(HE~{ z4NG@L#wJatJ>DI7Gt+r$hTS%qamD37|@(GpiBF|@6Oxl_dCs>JvB+{2m>kH z#+JuTW##Z@jixkLhkk-&%FHw`V&pJ*K@{#MGdmOsE8$kV;5zn5no~Eq9lI!r>>a(} z`^MhOjBfb#8fGM4|1aZryRTKUl#YW{HrAiT=~CKP3u;~nyPeYL-}`L_bYK%$bUuH+ z`%7Nn0DXJdBBCrKSDPgjSo7uTN<~*Ct;~^-pJ!ctJ^id_?Qe-Ktb~LUVMx7aQ5w8% z$oE=$Qr)PRri#t^^zKn_B_|A95=a&ho1km9#Za29eKqBoluUL_O}-&pM`v$+i4QB0 z-D>myY8TdYV7qbA^+MS<(e0p^C0Va*s%wEVk(8Rc`SY#D2I-|NxTa87XkZj~&-ZNK zFS2XINPho8=BAc6Cm)|ksN1n^SMGinHt*wI-6aOqX<5Gzof!JSk@HR~$x7*aIC0Bh zJZObou=H_6k6FDr0H2$8#XpKc|8HeA?h37o*c1tV$ShO%r8d!$dTOM0B?D( zdKeiU{WJyIen%Q#K#HZ#={~}1TN7+fW&jN?mKbg%i}YC?&n(V%C~MjFquYtfU!*6v z6d}J9!xhr8dlkE{F8!%N$r^LdDXo{j1J4HkDfEKgj+0eo9vKBvzHsgIv=knB49&6FJ`bR|^@mc;z$((Gdb*46u`SrL<(V^f^Y_5>H;` zXKm;y8mlW|*{z3Rc?V;HblvxOjRsTg4h@BLBxn2iGP(rKfg2E&dlZkubR)g>hs@R( ztp+w9Kd2t)82E|0!2^-^3~)AR-5>fE$91J+p3f&lvi5mDd7?yJ-MWI_83%=m7RCU& zYmm}XFZiW<^daU8ho_Fg?!1Ht+Hc)-oRzsej-QE7|F2TJP6qb)#*3a}Ye4JfUtaM> zn+l2qZ`^~PCUF&;S9?nX_;>215D&7!TLoZq+NrFoe zn;lgeEs>vu65(qS*BahR&ks$(kIf%-WQE7*r^J-0Y^!zeF#nk~ zwm49eEU+td2}HvIy*z46m0MC~YaW}y_4Fa7Lwo6Bd#!sGqkIK+e)u{HUe6;*4+MXN zO36sywC+rf+cvZ|*4KYn@%ux< z-S-IH$9!(n<14s}_NvzZtoUmld#!@L=hr2ri) zI##w!rE*);({>58BIsx_L@j0R8kL>7_4hOs{NN^hC`j~J{Rp&X_wVwB5-M|jov9H2 z=HjAOneGER37{;OuXFV~+^IIhQhM=k9b&cOal@3Y0&@L$GxRMN7HWt7W|Zp1A68ZR z+XOB_+>m7__HO)zu@!JsbKbz8EU(e5zf-5b^j-1gF&5iZ2j|P*h;Fuy=7=>42_2L` z$ZSq8q`|m%0OenV@HViwO2YLNXgr{Hb}m!Cz@@>ln*<%9W4fjM5RpjGRq=LVczA-B z%XiR_IKxdi2FfRDwL~{BShnL+> z{q9+v1IuJq+;niSjg@NYa9BNHD0qF z&|7$Y>9O`m{-b*0f1*0Z7fN-7QQ4?ZpO<^SEuP2W8ZjgSwCP}f7_HSGn8l2i<+fve z=8i!hWplrIp{=G^G_yWKTq8z|BnQaZ0f=n_yAUaRO7^$?MTJhp802AkX<{VU4aD&c z%n=^OB#c^Mumn-|Wc zhTYS*(ro;~4nn2V>UQ~@X+U-XMS{Yx5?1WmzF zQ^2600${}eiEDuKSfD?>>hSBt?rtcHtq^amkwre$o;c6XUV4DZqP|YHN8JR-wRv`e z*dh$g4!HfHEL;AP>?afTc~VwzGKLVrpX5(CZBeH;USk> z`y%Fi33tE#^Mf=ciafCD`(IT`X_Ql8`0%c317_bduMHnvLOZ&3i~#w?a5dge+Ltea z$6w9!F`6i~Mp zzG7@r5>Zac1pT{hHC}+pwCFmEoQ_cI+|Sji!0X9U+MgqQl^?WX3o*6biG-6RGp&6= zT)0;jQ)ZpJD^Tb7syT7q!`?SPnR$+`;^YvLTX>U)pV&)32Ec{Vo}uOFk}#_5Fg0Mk zyb8s?JZ>HC^^#t`VYfqCW!Fa-dLn}KXO zASfrb_>=xaya_l|zrP&w|NXZ#C9s#yoMHdH0Tp@dEt&zkDRpw1$B${H6^8s=;yMT< zq(D)`X@vaQPugwhM$x&?uVI2oP zQ`i74o9UHv%32D9 zA2Naap+kK|eQ2p3O2fAgff|Fboj_#NSr=Ip#aTNbzVXm0%*LE&t%CXbgE5m`j()l$ zYoaUC_yHg^M*i1d)UbOG`^*m6-?4X*^_%yG+oa<2POHut*s7tP1Sp7oc3J1LKdT|Z z_Y_vt8whVLWiA?65jq`BH)zA>(DbdnJBqb%mdJ;MvG}c?xNZ|fZXCvGSR#k%fs}L8 z(1q1C|GZ7j&yqsu}@@dHk4aHE-TjWlt%AXXVeg zHos;XEa@oOl}e2KU-PpNS0|1^}@s$I}{7DYgGi>7gUxtYB-!Q zgL%C^!szGS7lO1;vchFGZ-ZD>bW4k=XkP>?#Oa-$G<#zbY_pkF*Pg`-RXr==n2x6Zd1)HFL#f;Z2p8v>! zwol%x`P-DC>4ZOCh(0;MP@XBGo!u>Ve{MNcCH0x&FX6xN!L+{KWIZ=_}=GwK8oGXRwiAB#cSqykeQ5?11F6qZJWVoh>w@r>xK}xpF63 z9f=|zssYD0Prfd??xL;y*J4}uuT26NtQ?w1ZU&cLI+Fsa+LttoE>B%7Vw ze6F9jk-xHEIlZQ0uBu>Kr%rGSdrXKdf!65$v6 zbh}mPP!s#z=LIq2rLtxm5&QR z@NvZezt#ZrHgNtvdBBPoi&+1flbICrvM;rk7o0S8Y*xY45^F)MPz(-Wb$Hg-da=co zBLj__b@vdF{fUtqzw19&c$C>b$!c-(^*>%sT*=s-Dd?f(0N2_D{C~9&dHrAc`si$)dkyA|k%Zs;9 zh6mqY7LlPm>KX3NI7I6Oz<&U=aR=fB5 z^I@5%k$Z6}LVcxDN`OD19-p{|k?|r~xgUlt#V|9?z@bxs_`@G!{PLZ>T;KDq+wJ-@ z6I+*`7F7MQA#2-&(oECtR%%B{vYXYBW!C4@exM6%(8R* ze@9<01K=Y;{zW%FEL&|)rTRsftP@o+j9BN_t@t;}YP%=`R=7XzZ9uEY=A+``R(y>B zxe1cL+{|jbeliiHcp-!}t|a^&e0PIpbJV=0W+GzyA(&lch-$C04N1IC9Ru05e~E(% zidEh+UWsC+K0el1?TntIzaPB{xVHpA`FS(9UsQC;*V$akizRdR&7hm%i$3m?*fRXk zY-ne&CFu$i>WPxsbMoY+D6DL_iJEA`>;2sJToFb4R)8B5wh^ajioqUpZ?VvAW*#c3 z;n}(i=ktw!#qWSj1OJq0=tVTbQ?G!0&tJTmO;Ql?{hd9bbs?g1!ZW=!9VOX>uy)qQ z4o4ojt7xbQRN8Tr)1$fww${GIiQkXf~k2zBHc)GFCc1g*p=|FnD#YCvhN5UdNYTntSSwXt8b)b=|x^9mroP1`t|@e z2vB*01@mCSTQBU0NEx3PEHyrMUAr#`i#jtfe8D9)E_uMzIC&X|1A$@NsV(BU(Po$- z(rd~sy9-)Fb)HeU!Wt`um*+^q!*|38EGD7#K!3ZopV0O%ffNL97$tHkLN+TkN(kc` z*yYkD5E|EOvc&q`mp0+9fAGwo2oTOle(nq7mcopvtyvvWv#jlkj#e_~%T*W)d}#e+ znN&la=+;`%(cc0AdvSw^(B5-eN;_2-6On~6ms_ElmIUL8Atq-M(I?2l>^-s zad4u!K*r0K%?h<>>G<@4pAU1Wc8vaJVC!G+#=Ai@rDulyT7J%c;&*r1b3&nk|<(o$4TF949oks>{ErC_uo$0zC zK%ERFpeyv^b3pO_$m)}#^vkOt({g<3HkJ`fkNPW~_7H6COe{1u5gsoCi^`cTD0CcG zCt+-^y$59pnrzr7^7%P@_zC`z>A89dG+y`s- zI@~!gOkjY2k$`N4%%1E|lk=FL1IGC$laWZN9_WAeg}ef|=YnJ)oV81nH&slu%`sYA zw@yYfCT5aOGma4|STya#gNX)kP5k~;66ZTBt$<@Sw`TEA@toF|x^gQp$kL{n*#xS1 zrC&FqkFSoW{`?h2R5-D+Z$YHBbnL7hiGSuxlA)lS*1WK}LNC$(7)fcojq-liz90i1 zIOx^dufmGMCDSP1zKRb-_`-J^Zp9ATVpUL27JzQi#w{XU#KI?7zgyppJqv#Rx#OvP*du{0R zC-gZNMCeB{TQ3?tKodU9g&uGF;J-7?jsFbWU>g?PN(Zl#{f)Q1;p=ST>ude_siczQ z(q=z0OQ`4U(6O|F?^&@gZ~(mV@@wtQ)fPA)H~o_wD~{UQ9BF!YT_>Wz22nswH!$7( z+4D-M#V`G1tAKkCgSt7+gyL04t}bc!pUcUH*P3hik!pnm1kxwI0*rs)kj)d6Gll^D znzr@g)%K-4!>b776k&Ma94g=;KU>GU8mL3BFeLgBJx5}NxLJ`))d^{xc9W+fN;qJ; z3>`ZfoMr{ZU3Q~2o>n>G@@wJr6YP{I9gv(gf=@3vf%o#D1mx(Iy2!1A8{f~6D>V!g zoA#>riSYM7mr_Fn*rO&tFZF9Qj<{|_qlxE=$6llo zft}P6mp5s0W~`ranI%KhVxF# z$SEBVw2h6|Nm8m|JDxuHwW4zl|A0*GEaR5N2Hj>; zAI2p`Mf(z};tn$X1y52iyYW){-+XZl5}EIy3V~Tn_d%nNZck8z%ffiIpF0gn9e)#+ zbU144sfFoy1Qp(zj;a2UJ4g8?{Rg_e@!^Y<8@_Ppo#3?bCjWj-&8tGmIMeGBM*TcJ z17wYe4^ml3fqk~IOhlE%{6dxb1$lCS6$%K|V z;XX(=%z!DrOzqyDiC^nz)c5XwGJNhVZEVIKCHf5C&Yl^)pVe{Y05ZTKVSzG*E|6j6 z^V*V;vx%ah9|EFPQ`jO-<8uVb3+SIaD|`i2#{{E7@tz2sZmO}CAWGXQI65gJ^kUC@ z2u|4O{(T5 zHfq z=T3v);J}jT01;aN)v0q}iDL`?*0qI^e(umtzfz29Xvh z^^d8pNYx*xQ>70W!D~))ChdW0`8RfSrXYg!Z03S{UDb-l+ z>*5499T0h+lsxUccYHhArWEVONE=T6KV!m&)Ypr4qeZ+7nxcSzNp{^Ea6SjZ)AxJM znX}aeqbgGSElRWI5*d2otD)VZEHyV(+7R zWq&y65X*w*Vid-;P}rR+3SC)>UmU?8ihJn^Ioq1?#;R6}ar&+W<^od7F*t<tmr2$jFED-SvY9r%5mXSc@tt2FT zvll%h=xUB@<^Ew(SZF;i5MAQ31^EdV{AKAVr>BSGRU_9{M??9!B2#|h6UQN#m zrQDWKO43Pt`@26%%x)Ne zw-|qW!Rm63B61nQXUQyC6K$^RD24#=nW`(FMo~%nK`#?G-q2H`Du6wx{(16p9Iqa+ zMq}~qt&^3w>rDh6yau`NZD3%RAOpcB2mA2=7aT;)y*H#qVc)7~cp=532bY3VLo#Lv z{3m$(M{H>uwi{Z&@%8Iu>CaM*;&fU%TgITL$Vt3dgNQG1PTw^Vj4`IgT7_R{?ANz= zH=ol}D=t*?v_A#-Hkc8{5-i@aq7gfv%iz3!`7;WD6$za$l?L_RBdx;=M`@YVG_~y9 zHFg$Bh``jJ#&Ke3oFj|S{)f}vwQbC^8xpzN-7w4qQoFuFr|}j-q;AnL%dIhkVDgMA z{a3;dKR8Q&La(b6M8db>WY@9gU`?QM?wuH}H`6Xh$5w>>dJ(uWtY-MHB^K&y?Sv`sj^67c0MeqL;N@!ksDJ%FDKknKWWe$730H98ltv0P9o*XqMRg4b2zD zL%lb(C+7JthE%(Q)*lg}VYS`hE|k@&4vt-mXz!@=4msy#`*$W9!Wfz4*=}Z5p-AU? zyZ9sfR}R)EZ6fA=d#oeL+k3;Dwa%P1Yw8^6A7i63h|Lx}O-&psL8X-5H!Ja8tT!_6 zT>WTQPa$>UGy2wRUZCyDy2kQrk3s1@4Jdd6SPzc)cTx8c%^(*#Ol4^v95k6>B$AEX z_ObHMrYD&#z47^ZctI7b%u_1m9Im27N@ZRXVJD_{m)@7D8zp|V z!K{8}E5Xh@yD=u573R4H{!<+}#-e;+lVWvog$KYZ)K%{2aX`& zI0VAf$dG!@Go&PcO#rQMV5jpK9FJGLGASw6MRn2?0Pl$TRv$Q1=T>7`I^+v0LtStD zZHKFV4XP_P1-)l=NQb4pM}K8!uuh^)v`+pW8^5cAu2JOVQyl6^A{&HsQgR#311~h} z2;>ly#wL=)J;>*5=p1ByvKu)@&*c|cIOTmy~*ojx-8=xU87)dpcx&bfEy91lB#ML;oCES ztNVx`ntyx|!1GgA^Zd3lVFy zaS(WHl^_-4emd#rc7cxfo?SPFG`!AfBTFA*dab4q5&E|)ZX*#tNHVZHq0mofIpi6{ z{N97^rho@`@&YEGMAwVH7Kj5usr|tNFp>ZD_~|!q=*D1E{P5D-BgBS%NZkOPzHO#m z^)foz0?X-fl?=?iwJQtB=zSL{Lb;OgLyClqr&_Pd24ljf0`TMjC%*&^pp8tRF{JwZ zwiINW?Xx<4SbmBUexZ}Q`kB=-{a#WjUjb<9K>OF(pxjgT`Ln_pj&pTd-E?@Li-sTT zJ}gm47DPRL(Z;+1AI&Gu)2lm7F@s@%!6xdQ-xq>56ax_sCSw5PIIG03v3D3qjzQlT z8sQQFml%vOI%;$HdCpk__lD$_8*b@f=)@Ab1;2=izx0Sff1rG`Y% zIWJefV#c}V!u{28K$weSSY>;_0Fbb+-8(ZD%p0QT-;bzyj0?Wf^9uKvdZJxL0O_Z5 z3SC=?jAOGhQ(6dFd{?U%^eRH42GeXBN}u*1-I<#n{1B3;aP^lAVRsjd)voINWb@tP zvE>~uL=ypGAai?`KfU$lG5}w~4b9YqW~cu2H7BPT7<1~>9TXJOK{p_aIvMWu&IU%e zMs^{*M(zlX)!nZZA)Mvb2d*Ar$GyEo-LNOiMLdq^bt5irZHNRZ-=1}LVXZzIQ%qRd zsjeS&8)ES=MulwcuSxMW4l0b^%ZikQ zVs@$AF|=DnBTMH+olRp}ks90;f`&Ez=yvKAsi!>rEcr9n_Z#xkDu@96rsVdG#|Rej z1v7?VrJA*IX$C3do;ET5h@KP~ez;K^q|GRBi$MBoBhAoxE4^Ve$sT*vg%+x5lz@dZ zYMDTAWV0I+N4T(O#8vtQBh^O)unm(Z3LKeg-QV2Mb>w*IPh*aeNZuJe4?Dwsvn7)b{3k zCxblv9pO6zp>D(ykjbZf`7Gm__skWx`4*kug8CAXpkm20Sp<8o^piKPkM2#P1Q&bq zc__!PGUGbbkFnN+q*LBxSqW4$9Ox|yOIIR@2)r8j0@1VKJVx(rT>)NzC!^>(v5h3& zA^nQmRaxC=C6}4lOzEG4pzF-Tt(Tv-xJI|_;Cs_sqYko#mF_U8tO@Z`$E9YwI-g;J zdd%EhXz~WXy#lhxD{(H9vbq_FA9su0Q`D2$u-96Fy2<}zB|mGwCuQm!x;YzJwax!h z_V$hzPW=F`H3OpS^962qm@P*t8I8=y5{E3=ilO-qG){<(kx4~ra&I>_aLXjWkk1aD zJv@*A53&c33sELZye!95wjW3T6R8@ADH6uQn_wsNj4HfY&VdF?h4X#tIW)iE7$9W~ z_`Hh&cXoj^G885G@g?O-y_EePPwap9eFh7j^+I=VayDm}E}`L@3_A^mSK+o>7~`4Z zFQuAXz{0bbsAJs&h!o|phL;adGJ`XIsNIgWIYxXLrjOZTe>ujK^-VU#jS_iY998XdfckPxUikMIS5$ZMXLGVlUJ=k>4_@LJp>PVWem>`so{ z_+Ou~>o*EGn@rT;1hsA54R~-LYPbB{F#I>4qTA|nE9ms-*`*-OGWB#3I*#3AQsl?d ziVbS-;*XJ)kF;)05kSgL@iHo#Q&aOC+nN4^V*92 zd+!)0FWQR!xfffL8;>vt#-ofo1V zQ4cjtW;afKRArC>qOU~t0E^#&x9)?{J(4Gj(*DHh zB)^rQ3=$ij`R4D!kux8xtMwvfLSi;KW3p|7=+;8|L@$P;MF0J9duLErAqcwREo(!D z7{URzL~Mut37;%ir*EAh^_m?rHt|MZ8(nY7;Aud+QYbb zH1K$z{@_k=x9U7HT;lj<{=uOGj=;RB?YWyM1#hS~*I>rD`z5owNq!I~nXxqRt24yj zApVz!5U&B~>VW<)tQ}G76CH~(hed;>cW2eUno3eKFHc*${k}ynT5fwTU}HG_-&1p~ z+qQN&Lu?_EMX6wQT=^?Pe&t~CDW165BpHQ{sB(wR3xvfA!?lLw21zz|)jKGtD@@Pf z?UH=8=Z|>AlrwXzm|iPiiQX@J5CsfUMo+P3sd40`Vx0}&y9!>kmLqMXcD z*Z=mkOUgEFIP8Y>*p0-;_DEnkmC?@f4UaXZEgu+f;g6M)H^_7yPPCwG}N%3{3oG>DOGwtB~C zeA@YxKgI}Nl8U_64dBC+0~q4_(bIGmHu-;@b@-So_`NbLmr365S}4|AJX7@#4Z<58 zHUuALtN`FYLHnawwCeG-*O|nY;8b<2r$>9XfDLijnFIM;^?VH^8ZdD%}>a3fsjf_c+!yZYD2ls4?E5SpGI)wrBs8}S;zVZxQFsGzm z->UmCDKjj33p-|99>l4)-Z8@%WIH@1DB%WvFdtY=A- zJN&2pj3A;tjZ|wxpXORS(!E##fsv11L=$>d(VN>rl+PcET*8eDnh8Mm)V$J*9(5w% zT-F@Uj{?j4gqSA3*Y2zY1?!a*JC&A$2F|zh?}~9d^1qS}%PYtp`~VF-%X?|%g8B0g z7k7=U^2hNfqLO5u*UN|#QWOh~xK#pLhsHy%pX{89@X6iqr< zgI|GX*R@ujY+sHJ8Y*BmuL7C~coX_L_;vPIye(wZcJ8w8k#^o_zQwxodT2!+(qPNJ z+m{DO^a=w$Bl0dV{etLAo2C3QJQlCnPGw6-nu4=G>=omO4Db|&uPEO~pOG+5idS6l zix?C&=>y!ShCVSH0?UXjtnoWhPu|V;54z;Zv)|Alj~uQlU!;Lx3oFJhUZcgt>a#Y( zCNo)bq+$LqZa?LeFF)TAxdw!Of=zVYNbD=wjJ1-t&F?Ac)%^8x3Q+`Ro(?4dGLUs`2K`)S0GY+I_W`ZbLcu==M7Vv;bn@Y{ z+h|AHALC*8>9hv}CDwaSaCMDb&ebjSmSug*H^Tu<)@7xhy`<-* zrZ*G$Pfo}o8#ZM8-2mt$W%ci}!S0~0TR#z?T)*zo{S7Tg2k3zL~G z&e*NKN0YT9Vq3Y7TxWl+%biw}YK|0VbH|^;^c^C5yI!MkB>n|A{ZZ_A;xVM}4kmDr zexKa|zD6}mc+0{e7BxZM(5; zH)i9;wi?^E^YYUFXRTRh@0oA(%^nag5|_5TxUBYjgZ1;%L0kQ_~|&jxRgit-r?bUY}#8wzc_st0jHcT%{d1zJv(1`7*@qkrsq{#M;dvO#hY#V<5$~%1riV4mHN454S*CY zMh(rFEy1uxZ`~iA90eNzrNO^mcON5hjzv1&m^T)m2Z8^Qe_Y6XQgj-UvmT`-fOI!*kIj~ZnrRV;q=Q5h0k#;nT za67I5f=Lp{0l7AAoVl_-swBCz$IBDAuQ#)F+jnjE^TuhrlFX~2sy*%0=K2%(i0nL? zo2cEhy027^+BDCXMSbLFax!FzK2`nBd*z6A8XvV4zzd{LBs}yl8lR0qCF&>oU3}*q znCc!cp&S}YPQaA`{l?ndf@z0UXT5Xh3K=fN4TgZoIy2jT<16iD;K7phiBsRxuRkq+ zO65;*Mbdu@)|zI>0%ecs&ArG`$*v3p=SrL+T+EWUypjvgxi1z*q4taNtsDLY4KMFI zY`F#2cax?H2O!5_%u2G&8<6>O9}JWfb5PQLw^IF<^mgd(i0GRcFDxnBV?%D#T# zmY5t!ll!LZuYrQrBUovi+#v8uzE3Q-=c4U|BMB&qNo~gN)mZV!!JXkpbVbILbnozs zAWN(nbs-;}imdt&4d{dSf#`c%fkc?l>4Ca>A5iydw0j^aATbHaFNYQ#pC^V+l+7EHeJ_XASZZTg>o$w3eyV`i8g}i zBE4d}A;;91A!nH{JxKAwhEPotd1AjC^Tt;);IPWYQ4?O+d_67>{7uSD#A4c6N|OC+ z_qzo3e9Y?H-}&2LJV6xc{3Hh*kg& zUV!gIrWppuW^r#X124~w<%r(K!BiMhMyJ?|)ej9@-Cwsz=MRZEi#Q~sF3uci)h3Fe zdSj|42%5Wc3Nqj9li4u4br>o5y|bsZY&wBtK1q=%W;`T4PFf351P&yRtqayu!!&|g#8q5)GrhE zEgI*S3J*riSmlM?#c;UVCp=meE=R~Aos1Q~$K~6n?I|X}$u=;+SB5(ACk!+~*h=kf zx)p9S$ZysC=clDsO$B4gIS4;BX&QWAJpYuUPcE+6_fq~3DikAM{{TYUKAT=n-yUlKDlC+p zN?_g}TC$a{p#Ceus`UOuEc8Wt5;u6YjTqg=KFUuN{@y1EGDkiwD|0 zm1TMvw#)uV{LR+H+S@4(;b$drmaP^=++N9_%aBGt5=Arir~7TAP+qf1kaiAV0#V+l zec}q9Xy65{^MfA@lv&Y?AC`=mrtQDA#L@EXr7om@WZQsMc%Phi+99u~uJlox)cbCj zuz&%l@udh?Z#l`4HfZ<^tYVI*7!> z$oZRhU%MAR_Bxx=F~Ja7U_?s-(}TW-)uJ`Go3#4Nfur(*(a!^|Zf=bN`)e zJE&Jo`8x`KY4`GDo{c5P<8R{6wOC!k=YYcGPh;UYB`t4&3uKQDwfHdczE&=OSNA%R zS3K`kYhK+D6~AY!uFa8(y6az2 z=Y2)<+@yf+oOI8*vh8G1M|qctjwzbAnu313UZzZ{7~ae^f$XIRf(ZglpG-BP^fItY zb#z3Kz|F*Zt;P~O)42`Bt2lejE7GG=rmSh}(7)iF7R565<2f4hnv_MZ8Vx~jf9E*} zdfMl2>vj0JAA`woIO;z&yYL2#{l@&dUig!7$rYzB#~-mjh^Rj6y`$>Oa(w_vQ-*Kv z95_!a3_zR^=0P-L(YFHArF>MiOK}y)&qbVC8Z%TCX$}2`**7pF?BE@vnAE`nBm?+U zwVMS&d5P5Q;Hd@kt5tg+S=~EHuD&sbpK>HIM;`$`@J!qa;Bx2N374{rev632tmUQ4 z)`BQ5p`XLZM92~up!6zhHk8ii4uMr=c5{OnzWM?(9GlMR<>WIdWIVWT51TzVs!9s^ zGK8ni;GTcZXWBsE<7D(RByN4_mQmeR3hf2_0?@-;lr+L-x=GXBLRHkqN za1 zFe#~3ob@*P)cB41Up8>r0rPwj96qAyxjQo=iY)MX8)BYi8Xh%T7XnJFe;yF2Oeg|A z&x=PiYD42W!eHTe5C~9IAk9)Y)I?M4d1~`5G`IcZZVXrfiaj8G^N!JetiUBKOZGd5 zYH83@ZEH(o<1lo%x2j+4Fz63h)A|e>GWP$i8`{SJkd%x^tH#|6*0OzRasSKO%nEZL zTPf0ixOfYS87)R*i3b$xhz&bYRvwXl76D<_&s4SJcamPjNs&n_D7|IqoGpSrE_=Wl z(Bu5x`*LcXX8_m0eS)}f8%0Q+1rYOC+pBD8m$kHu4htiHwah;aqwR#CJlm$+dTm4j>`F@haTKr5OZRqP3|NZSTei)36e6a8BhvW_RtNAxi?%jWtno zdGJS<~9AS2%auOFuwlH;=+Rn!~?w(DkLo z`tmYIoI>kUbc;XuNq|yZ06GTYd7|I|x!<|&qz+&HIio)RBCB!%+H)d#-njYaTi%lg zd7l7ajd;l=O_~NEoQNT#gYaj&OD>bw*+xKihs2?9nf90IXXUz*a@EB~XIUU+R>tzB z#@Q43lda@N`B`G4d&Bl#@&vv=DrX!yn%#XeM=}UZ;62gD`UJH0R#&;jvX#JfwL!ud z+)n4X)TObv-@mjsg7d^G=zaxtDnImifP81}FaVaxnQxt)Jr}J@fxy7L%MLS#pf-J> zhJsRG=6rH`ZLs(1A;|3qty%e2YPl0`>{L-f?|1``a64=4T6Mb@6=A;0Kc0@sdzw$y z{=Nq;+8SD8zHv4Dft|_@%g-;XZF^Z@vuZgM*}#j>VchcbxoGw$*oI**7r1OEA2J@_ z2$RA)iMJPia(He2Zp)=fDqQLs$!HGmO%ZM(7{XLp4ykI^&70E%uC;4uHQa~xM6e%( zqQ7g*g*fI6YVH0hxjQ(a;%Cq;M1DgjQOt-Bjz)J!lzBW1%0b?ZKmLITD-rVJE=JQg9>tgq>^HIG?P{zm_y(gh{N--B#4|T6 zw4p#XDEwBr@LMdm8G1_l*C+WwF`CUQS)c&yh?=Jd;=TWi!FL=(i?8S|erMUM%)-%f zhF^AXsx9z7Dh!Ov0LBZTFZ-}S+J(7yG;AaWBY5kAF`}kvv5Cnz&4F5d$#0>KSFm%m zl!flwgT{9nT$3VeSUA?DNQeYC8U3Lya~7!4j6ihQf?sr2Np7bu@YX-N!4yJJqPtDz z6TfChI#EApAT57 z4gIHcYHG|8N zX%V_ZVWy@A{`DnBkO0=m$oCUpbz^5-KQk1LR!v7cTHpEJ5pE2)Fj43n7)zs9Xoo65 zcLT`2x8kU~l|GdFjPFI+@a3vQ$f769?%YUDOzD8z5LoYEIr2`pdto-sr?q zGeqAp(w`sCWZoT<&Z>gk_BKA8s~2*34LQNjylXKtiuB5C|~f!U=(@ z)({Q@zMa^=YjM4H&1Rdc7x0z-a-vM;XI{jW8Mm54Z*hPWL0W$}RC%j4AJz#o~h(DA?k z>O@MV4l(4SQ}VW*#iDN_SC6q?SX50LS97#ll$q=3|73TdXR85$?r~FRuFB!ff3Z>G zqt5%EqP%{HXYw1OTSB^5f9oSi0#LV4b+{Nkms>&1P=%+9@OrV&t*?Av>P?Z2ek0AG zH$m^ojf#Rwe|{1Hoq+wpquFQ}*4oe4j1^ za#%s+bNF`75`P*jLKSc3YJk1yn1URRw6KYmoG=d2ZWOOvfsq=~!_y?c27V#QkgV6Ovnj zXrzndBO$Ip*3a!N#*}_{8lOvtM!6%dGtaLl4V1mh;K1fuI|u3@$NT~%DI@K6YFJ7L z=*3Bdj`L2>5$uLUV?ys`eWr%Sp&xi+c>I`o+UUOl{A394TGnI*BCKBb>pXCbyjr95 zVpCHJ;ol;06Jg;EUt7#CoO~&0FlP1Tq{YdO;?QluEbaEB(XCqybytlo_ z&AuJPh9gJOj($MN$1q`f;Y)|7w#z(c$6LL754J1u_9i5y3=qVt*jmW`%6skC2>uTsT|f2y|` zxQ`JurK6$ge{c0t&IUDZ$J52|PqaNvwihHQ5yA^Lc;LWoik~bb>Jbcj8reRY#Y2N` zdYx}_rg79l;ZM|+YfCXhw9{Zl!I0K z{lu@nOB<3OeL{rRtt55Q3up|N&cWXhMOSJ%d_)-hk=zB*Nf-iH#RVIH79u=XKz-lh zk~nV|)TJDVzH)SNJ^T5<^_#i4(gOVoG%j}s;Do*RfkTF2Z3WimtC4t)z~c;9$R!LZ z4-A>1r16u(z^~`N#7q#U`{ou6C4|TLXWj;PmA>}ozrhYe#H4rplTud~%CP~|MG^+QmHQ;yA}wT(Oh2Qs14>#%wyQm)%!0fB zwmKfhRVoTHJwN0G2s`?`ZPFmeww||ZfYm%$?LR^>xnw#~tU%TO`g)_y&|UnDpL zq9PUF&xIoP+lzmVyrYGaJ~a=NOnCij>R{dl@p&GA($3uMmj(Hy5eR)=mFC`t*5r85 zEVicD;T#=xUqq%U%~y)4h+Sx91UkW=R0>pht9ZFZU_{tDq)p;G)?OtdEaVE?opFQ=y5*eTpX6cXWF$5`#*C3Mqu)e- z=|+2%IMe8k`$Kf>$ALjHVJd6?!M5Gf?}!HSGA!mSI$N=;hli}m!VV;M58RAQKDCpW zOMstxVEryJn*N%oP_ddAc1EVBs}1&nW&u_Z3(Rk4+JF8iM zzF07d%~fLvtb`u8EWn^Pb438$Qi~3l`E_o66(&b0@eL3~5}bmpNJ^jUPM&`+NxioT z4hpwWh)Up+BCW%r+9zbu*0z)1yCek5TWeEYr>(y63M}L%uCVssZ{I5m6o5f%Dp^<3 z%{vKFbFL+#kSg(*1u3>?{WO~}(6rSbuU~sWK%!W{R%hMqaA#p{&UzE3fImRWBJ|pO z-45KW(J%L#*pGaU@da{LQh$Yy2Qqg!kts++ii)WI3PN@|F*Iqf(nz*foZW~)A7mY* z%=UqMfAIp0xDhD5C9n>oxGvaf6xT0x`M03@2Woe4He$AM1yml3=%wAavtAD{;S}R` z&kX2(=XtPd*7%r%T3-HGhft|u+|Bz`G-g*?OG^(lzvoFQ zCVRm$zk6F~87`vi_~Nq_kUzCTwKBC*AkHpH0uPi2(ziKC!qU<~i#tmcIlQNB^01Qg zS80E3T^~k{kB47g;k;V^J}vKQ8WW$B4vr|mhB^S|tIc@7xg$<@Q+Yz3~#4^?>^zv%$p0en;Yd5@1UDu^kb<7^v8s;pe1=AK; zrSuSi#_)v*`3e$d1`1fe?tX@-Ua(&?l+tA_bY@r)E-nXEM=~5pL7WY^;fMn{t>Kr3 zJ|E%`0=vczGMRTBo~0Q*VhlWPnX_T9(DaTqf*>wKyfro$5S=I?qUGPPJnygrvrKh= z_rB9tCaw?%F+UTqobuSo`ym^ zV&^l%f^X{voSYBA@X}Ta%T2q_xT#N8l7z3)gKO}_QVCvQOOk}v=R>>RMTdF=Wd~IN z?;|*EWTm3r0Y4L(!Q5YfKeJ+E#yTaQx9C^<`03XOX2EV)U{Gvk8)FH9hW<)gu2u)L z&M849sJ2IfRt>6@q37_##Yh(39`yo}f%2VcCQyGGkN2f%&DCEdjNPG?>z&}@)_!-b zmhD&sCJyiY?+K~^5+Q(ZN`MgE3+GulS^N`m50m!re$f*n-QuwW|GWAj73hos%p`CD z58>@?GjJp*VSz73x|FbLLYt%}Q1wq_;}PjYJqhcwK}>O{USxsux%lzG^ZQkl##jw) z7!5a1*OsGRvG?tZj7}G>twU*Af(+OF=P)%@(6j=u#On$a0M--v+6mU{OE)+|ce%8`EO1ew-*KrEm`?`^4W7$SgBq+n{a5j@3()fL<{!-4$u*t2Ur>CL#{et-fl!|!ti}6h=!x#E~Az5VA z64$>YW3ak?eorQ?Mo&k7`hJ-(JTd3m%-08A5^N#7e5yCnl01^OtK5G%KD`fX5LxiE zpr{1j&$aH~rH9_nGA`L1#4x65%9imd0U+$YGB`{MG> zQZi4;qROZs6w;%=?+>X!^0m_3+h@E$<&bHGzOLZB$F)GX=s;*_PZ$92Dt$Tvhn5@D zK|Xaqv!r$kYARYg7vc~N?X70`H>p#wMFqx~)12inAy7WEjQTxxwgNwKlwr~_QNLol z@RHX;czgJLLhU38J1*Yh0TV0dqZKvlfjf?v%nq%6<=2fITsn1?;T*|GOhx0*uK%Mk ztpEbC?llhJvEXh>YT*xH4=N5XPz>{ZO&n8M{z3>Y?1jW}oXn@ybb&!6Wd9hMraKzJ z7CI15T=T(eTC=TZL~VifgtbHY8d4seN8Z~_zosbwRNzyyB1R}<@wV(J0vVTuT0A$p z*0FdLwjoEhrKTw=%9kAQ4#&D+mv5kKR7`-t3QH?#o<49ZtYX7N^|>;mt>|`wK=B?& zJWh3n)53ljyqZ0o4IIY!)EnlD^#rLSYfUTLH=NX4uhjZyd(n1yon8CM5F4Y{*t%ZI zc;LjV#}1zLCsRwxw$mo%X=hb!H+28~4lnM>|mM=DHS!zl2|u z*p)`$TF{JWl4bRK2w-RIxe3+JYbg%Ai3CAfd%3(~;@Gq?oqmnMZ(@2eo_b-SC5*L#c=pOYp3KRJSm&In}O zS#tPt{>yhV&`yfj1=(meot&APu3fY3HkhSf0>_<$sAd}&cQ=b;)8%h$`u;LuygOfC zh++W{2m(xiKdO(;$Ux(#ktlZ-g4Z44FUT&8d9U6`TBSI9Gn$@4-LIAq*)8}<&=N6WeQ8V$a zmA!eLt;NvPFXIQM3@Ut$yvBa=olP0-sBUY*MRh+xknW{=DTR0vi=w%ie`G2Md2HvV z)c(o-M7-Y10vJGMc|OWQmFTm?uP?|a#n&oy6BO34c9qjIwP3jG}EXj#ENm7gz z0{!fH_!%>1$Qx=AtvO++xAhsl`-z@EBzSF;{c?VQ5_0hYtkpIXWjK>^HGO_chs*ky zvJU&s63tj#+RWT;ahJ3p;vbIV2n$Tw1KwY?yf{Cc$!qS{&2>x9}Rn6{Sq7_mALxVnLd9 zZeO3k33vn{N2t(nt;i5drXs&-u^EOde82DUueb11A5gl^Dcs3G0x)shgE%FbH9wBx z0>SJcN{JEh>a&z;xQTR6A(O4WdM*J?CMqx~XHkbCkE`F26=5A2z7h8+HcBcS0>gHON3Ow}u^%M=Vj{bvc7Ls_;fQX#wUM2-Y@%?cE>I z!i_f&AGQT3z>ozNBtj_T1GhA@o5k;fMjmZ`lqNnaUlxf6@??A0@5 z^%-yR)5M&6YWvE9UECaXUWURn*0|CRu3fsT8p*hD=6E1}^b@8+C%V2jG`N zNxA6G_5i!O?G%nY`KnCdhi&5^f%#qlWF>hwSOY=RTUktOT1F6J=%-e~z}vc;C>C+( z$b)fQBOkkb5r|t(`CTM1w@OafT!qEOy4;{WX{k$kk+kje(Vw7j;+sK!4>vQI6HsXB z;ZHl)KDA$A5GNeqI(vWTLn*)iR#^FJtHAawl+vH^&wHT~>lR}Jn2%`ZJS1_4a3MqR zKJV1-I(-x7#ZubgL*Xod-XkV+gIA0-l(z5+%!WrkCp@8`nS{65_j`rDD3`UGq>$3M zTO(ub0M6La4Y&=ZXQO3X*3q-27wF4$oDhUeKdm0$EsBScDYSOnBS64lSA8P*02PxL z)bo&aB!F8p!!F0)1|%_zlg16PK6aX`2SEw7A+~d{(NR2`y=mrXVa%N1x+Y6ZV!-)2 z@(Eqn{BN8T%~oYNSe7`oOdJU8!vekPUk`GLo+6YG!tMtiy$rpFvO;$gN@+w>lO~q) z>Z-@W_ClYAHs$Y$fFDs(9T8C51|SeBwT6AAY=^CJuOA)C|DZhdI%&CsH}hT7gQnb* z>}eI(WW+?dC*Zsnj@ZY)eJ@GA#Y08<$)XJYkO2fUbi7K}x70_B?_UA4BnhH~$j>RV z5}v*2}0wF;h`jt*p5_617kYhAw&f-ODbUv{b8(BIpWMYWeo%fI(A$2QT0~1=$w_ zJs&=uVawttHKZp0x_|KTCh@2zClYFPvf=c5vX>X|>aUrFLL{_@l>6?KWWXp4$c1(9 zjS>xLiwuhDh|qityFsP$4vF(>AH3-Jo*WbA3SZze;heIkOyMYK&ayMN6DEVl&7^t3 zySIAn%}T+O-cw@oPs82by;tZ2xWE&y3eIN2S2Odcg{#V_F~>~xGBGOz55(qndd3B} z&|`=m{TjoQvHe&-=zFtDI&Ur`;moT~MPzWe3hPYdeSz9eN56icP9NE*>AO)vfNo8C zs;1F03Ti2=3G|KheiKWOl3iadHNJBm`}IS({>gqs0{bfPzKg8NMUZ~Z@nK@bflSi$ z`7u%n7h4aU1QgSk=%+gOfQ-ZfJsZ19MvFo0^T)!Z`?%UdsT@gJkJ)mI&kEd9B+9j> zXlxHGEzpk0ixOgeJpnw8%7#I_eUnX5g-4pMMJ>)_?)63GyiP_vkqrvrBT`}XfyUy4 z@<}hEMaZhE!BhoJipq$@w4$K?#o&9<%1#owZEjohEQT;Kd%oTbUEHGy@o8N}G|1f+ zS_*>9^%u&EL}xEEWxq@>#V-B@TT0}x87T|vatWV@eZRHGy_(Nhtt~BBdSO{ZqCt8F zSNS3EZjM)05B=Ju`2$~&UmxgN<|gu z`$`w#h-X~f5T!|FDx&?xpJ(p+d&@hjK6R3>=93i-6*02V(R%PObv--X) z-6GJ2G32fD3X#=>ACbvKUu`M(sLW|BmSYCU3tU-iZxXx|1@dX2OQopM!qQf9&=Zh& zS;3u2;z@3JdLev(iWDI*k_s%FaRv(II7w+rX3!M@);S(HQ($r+PB7hZ$}JWute^i3 zn7Q_Bx^$Wlc+u|kT?)1h_*U4b^Yg3xiFMte`GCmP?Wl z`dT=U9*0M7c4Z>5+{j!_NN6;Y&vJS0?iu3KzA?zY`N=K8GHUd@6?mAV{4NLS9cw*U zJ8m*;93A+?DWWXa>aZ%8NZTvQI8x68W{68yVu|4R&Hi-V4Z|JNcX#1`vQdZ$XYc{3 zUSBtIG4jzZ{T~Z#|B~oHvy^l_H3YSW^^(WW?diShWZ%=IgzYfPAPIMtuTP%&bk8P* z^oMz@eY=Zv1a(~4iF(a)#dn|-EIxvr99)=po|0uermpF>zwXaX=4Rt($@ek6iH*~F z)&JnH5xP$y`d`Okkr_qd01-MY#!Re}KNm@}ohkFRl-cGXv4P8!^gU_FXbVv<zVY4~4$u%`SqFhcK~3RdLW9C@JubWqR|I6bc71VZNGiN z-y=OlBJD?9BLcpQrX>Up0Rh=BIADcS@P{0S_H6d{=DXax>Cyc>F(Wa33iUMCr2_~8 z1|wH|+YYA0AwIW?hAopVSv*JBk|Pa!SCJ5P!wlwr)h%VHnw4GzQGzYx;vSK7QEo@Z z+$x-mBpl;u!>=w3K0s4-AI_^3lt__I`+tTFgKT341Qb|x&=Ym49vxiqh!LuUC7)aD z7;DXyxsY3Eei+bt*%!rRMBM3J%W(wO?T@%@*w7Rt z1+0)$Ol0DEMEs#gPM9yx-w^DZ+pg0tY@4??infc#W&0ZyJ{0l_@!_#mUr>E0=nw&a zAMB`IHVvLE#h>D4!X+a1$MXBxMJ}Q#^LQVseqJ3u zq|dFz48EJM?yb(plU!-i{-IcVpvs9UnOUxZ2q z{q_=zXQ{*$ymku8RoZb%vvJCE7N>vwJ8^Ji#!)E1g$EY0SL0ueM8_k7G7N`CvoDLt z@Gh!%ngcXVN%JQS7$&vq-;BMwy#UCaF1%=Hf&NZ+0?qvaS3i~+c=8(p`MBdlLZ75r z?KdM}J8zJ(e0{U2s8uPkoYEVPE};< z=1+TB8`K|r*>!zhd?s@jWFwMh#`Rvi^Fr@ijwt*!19@HK_nhyNJVJ-v)G9yuod=U= z#{Q;+?4M=R3Hy{Y17@nrZ~La-UtMetGO%3GYLhj{>vc}&eM`w1_*Bs-9t;+}2eXPS zx(Y%U7Vvvh!%AS3)Jos3ABGiQ3_6S4ccDGwT|{`}`t)wr$3(!2Za_9Ud5xgn z-F#z1B_cPUo(KNZn0q|w?F$=%##FJzC&w$q?2~7Op2%`bvJgOej;`r-{3lfl%|;lQJ9*rAQcr^*laIj`Mss8iV(-+p+EG*4#;fl_!k8o&35s{T){jQ z1b83D#qo?PkgfU-;ybQ9T{F*Q3#!z~5t%~S%|mfq_~Te%Zt-t&r8b5)L=)q_G84Xj zFJ4Ki0ki$oYyBfRTCCM8$&vFpCXp;H6e)IEw0dnsTqO1_?>(G4Y3_{gD)%gRg`nx# zHe^h~J9U$s#@C1qNgfMJ$RmSWNba zi7})HEQP69UHwrFl1)-)y{lT@`^!|hYkJ7Pm%{iQw|Qy*k{^z*AX-~6fyjK9IB$;V z*D5}|#%zGH(m(0ZYiB2kZC4Im5K6NR0yZzbqB~4(8+2l+p zi|y6?bZ1O@42iGAdfa|7Q;5okQEwkpTQ2F0GG2q!_-WqRdeK zKMlX)2MdVu1S9k#QkVu$xo3lHt+klj3^5?c5`|mSA zihxZhL)=$)X5DrPx(30FK$#I-~z)Hp*r-)Ea|$e0`>#V)?tw+M!*3gqQiV7S3keM z$nU8SQy+d))WIk6?TcLfJHvP+91i`iJM(B{5>>A+%o{Y z8McP)`0TM5a*j7~c=CJ#R{+T1QpUEYB{%Xl{$oHVWw)m7+apKnKwXD_Ff($poi+I9 z1J35(KuEj;_h;A>JUOE7!7Abto91Fl^#sS};b=d`XCBra6MIFZdA||KF7un4hwA^{ zeob0uF?$=&6Y|9$X>gxGjBPwRCKT6WDhAKKx1KIkd2NE$c? z%hynpyB04s*QG+v8NP%+0bn2Y)$|oqYyd9MhiQuzN5pNCRV>SlC2J9BM1mH?9W=tg z7b&g?6nrt3L2|^?MDj13 zI&R^|ZtU|k`u+gdolpMg>YU0mXj-HV0~}q+aZ$hcj#xi_8{iz`b8P|$L2@l0D&URW zNDf%W@3qXkYWrD>q+9%@M!{n;PdFMDrg)=l{b$r?Qy_9QIqB1p5HNMLhr6#VvqDRZ z%t0e)IT?q)B!aiOHp^gD7IwjPYuwaMV)qC0+w`f!V+iuM`%zVnLPY|7)vdDZ6Y~2{ z$nSySIv8@*S7-neI9Q(Wr{pX5lS+rq|P1#AsDrmuRmY z(@EDs>lGc~B0xT9cX6zZk|_updT#;Xv28M#F&1BU)G&@gwR7lMbH{UYb7q2X0NYoS zB@4Sg1EXOaA4^$-xJ8b%5PBD##m=ocK5ut53Ke6X64X`n(dUy10T8GGD9sAm>7%KR zDRf6StIisK`~nw?%gQ(0a>GHKv-C|S87R1ov$lP26*-mCo^yyC(5qXn=&S8P z+BtzrUN4`VuISoi)cf4v7g0%wpc~VI`P{-&T)&9^jq^vO;T4|R-z(iRW@Ut#B5rs4 z@Yn!`5T8>#0OcBg_|ehcZ~jjuQSAzn!#OcmS_?!NR=P{0wR-zW8_B?4Q!`IGprSGn zFtf0rwQCaI)I*)DgFHvN_GPIoo-D=}iY1S0)zO9s9d6M*7^1_=3yqv;t-tumz?xRj zgHWRuX@k6oAR*#WOQ=N*_aj6)f2HsNal->*g{|NUzY%8TyOD`96EvhUE*)JT1#U-)x&@eJId^QWii?#T=_pZLlpMO!2p0lJkIKWDN9j^ z-w#Yx5&SWn7CZcz8jwVW`CHD|Ezl0Qe>rKFXx_R1y1oBnXLeanwIkpP$yV92|AHxO zCwzR;ig{zTYlhSxB=TkA6!qHZ7Rv*@tlM>|$4)o^*H`MLYiYgAJ{l?|>J!W2BJsb! z4uqy8g!Cy~Q!7``tcpsL8|M*1)8xvZbZd<5u;cMnmV|T*H8h3jN#Pa(#S;k&fH)@3 z|GuH*Xb@BzEHAn_l)dMr-?7OqdDDe878uIzO}XNM+H%IXXFUCXDwA@%9HnSVRFMu4 zKC%8W(eh~Dx@jqlNB@A7)<9^EWq?m{uaxcQhvVoz+;0WZ*Ae|;%d%P84|!8eD9i-L zcrU`>@}!C#DcV9A&2L{8#-0B5h+R8(dtCYl18*GBZx3ofnz>%`TRC_t+`>;@L|#X@(akM>Sn z05Jf$uKAwM0IS{S#?@xd`vFIGL_Qb(9Q&>mzeyUz}&H|I26h zLZARcaDfxd!OG9jrb}xjh5ej1Mk8KT-oYx=p{FliliqZ!ZjA{0?_S!^|L*Q3HG9m) zFuqMq(zfcGI)LAHWoPPm&+uq!h* z1S6j(v2<@N!zJ_A{#CHRkiZOL0J3etGDiOS&=>w{S-FcUrh6?BEKlRrrkcC$s`hZ! z%ME~&^l608Y42==4%4Bh7yqk6;v?1;^)InHk)uikaeQRM?C!A@DZB-qvCR-Cv`ljy z3Juc=_S47BL_8L4-v_*_Bc=0>g?4*Sic`^lY|se4x3b?in}Hc_0|1l*!p!ZtpNO{4 zAu`70CE+*07+Uhw=3uJ@jU@CE-W@2bdz5sJk~BAT`y+hdyj%7 zYzz3Mq^_ry$&_+c0}W+iDIfK|OZBtYGxjNV=tq3F*T1J)FK5 z``RYr(jK*?Rk%=mC)2@B!jxz4zc$$0U~(BVFk0@wkd4N8qN~;@sfQ;0Rl@TL%XK4~ z+OfwVXn>IDMX&mO*2kRVL(!lEpo^c+Dm$;*yj(}{Fwd4o=J*5s8d09;y=Lc}y%C{m zAn$26g{ghL7J>-iB37dXLCdoxWzNKH8D{n3W?4nqx}w~9KYob~{*}#$eT^6)OouA) zDNLEUftr35i8qHVp+_#gR6X(z$;f(9B4gXNL$MV45&Ypo-g!1EZ!aiPo@PfRrAz9l zRVzYWtftf{6i&&?#@6&Udk$tx-Tl|tA#j`a`V4UxEo*eAsegGz_Gqx(Hy1{w;7U6V zcSRdnR$zAA(<$8M_+{S1vlMFU!qH!%NdnJzI^KAY@R0Po9Vp$3BPr0DGYt=_hyQal zJAr<0FoY`4XO=c9Ni5gu8Y2SgWkvFZH7lgmZzfed#!&u~W;;&$yUWOwGI_H4q?%bU z$O3D@Y@RfOr$!FSo6fsNgsox&{ye1IKR<1LdJw>&Rfaw(Ve;Pw_htP{8TV2!s+0Vy z1JJxv*YQifu+U$|$yKhm@gpo1fEE_$f&`!iV>a5?u^1gSF0kObL?k<{pP5y}t-jul z-d3ovEdC}#vew=rU**bpZ;)}Qrf>-I^xtk9gnW+SdZEzz$#hGFmP%}ZDYPcQBg-ET zY^PIu>Al^>wI&Z~N42;slbqLo6|}s2q3Aolr190K2Y$qcNU+FNQl9`XXp=l%W`Y}p zHTUN#i)yVRqnt!Uw!=&fz>;_Rfz~lWHu}}*gc@k}#no5NAhDv>h&3E*6eI13`#EJ< zrDe#1HDuX~_&11wK>L$W3;(*sQn&8*batM;Nv9knms7RuPSjWViq0=>GrO!rF!xFS zm#~m-WdIQ27=W3fbg3WKzy6F=i097AJ0NM&1~Kaj(JO|j{Ov2jpdy2;Q*ilhWGD0p zB<}3uf8nuP#HM+O_{()A;inLc&6`Ku6%3_isq!l2cd91~%b3SRK%Zi<+R#W`r7K5u z)RuT6Ue#c1k$1_joHlFg3IB1~T|~g;X#gR1dF+TrD2e%U7L&`&W{zN6i3|FHqQey3 z(di=WqBpfKh@VxFXC-a!tOFrwl{1PLUFhhW(i$a~bDoaUC$GfVypHj()|MWvUEm2m z5%({7G7%#rPA+lNwgAtLjP}&kSY=4GgTto0eVX}ugbxn_AObEr1G1Chn@0y2yZjRp zf9ua)o9*SHFL7}xb%NS!rWOa0@pzGH{j<{hc-NVP1w?*KGv~R8BwcToP`M|HGN2gU z^Tl8|J$6-4hN4v?zWc6o@q@y~*!sm{f~UZgN5x(iF)Z=3!TNAn`g-)pDsAF_q4@i{ zUrHt;uxv45sh@7}*G!QkC#?QO^};oc^QUP;g*q89kq&r1Sdv@6T<8XX?fXcUw+umqn=d(}ekk zj!4kzDPK@6l79^Z`g5sZlgHF5Vh`?^DYD(>?fXx1&>2V}>?@H8`4c66c&m&TFL!_V ziR`i@5f`vheVV>nZG-i;EcE=%%^)Z_l4i#&KyCV1@e>f_MWiI~rDxNzV0N%VJKiz4 z^!B{N(f<*XzaS=_z)b!(+a9i^OsZoaT~Q4k?Z7AY`6{GMPb5x3nUJ_Bp2zUqI3Izp z<3#fpu***@$w2nNv@!uMU&m%6SUC9mNcSQA$9S*(Us0a`7d&=un^@^B-uY4Mpq~p0z^! zY4vG|sN(y*IGBXHudP10h5$3BJSn;D+s9{U;k<<17H8oT9CVp=3t}bCK+#fd$fOq- z@2^*{f`C0A09ofb;xyPVyS};J;7j)X82h9Px_Zd_?MSJx*K z!L7gbeBw`(8LmY+#F!(0mC`dp3=oCNDr*ME8eQgxj2b2I4PqX4wvb_;ZvmpY5BA$E z1o^O{;=UKDqu5%j6Q_GSH_vf|A7#oA^#sXeGM3+$);^K6VT0Idw4KOYJT&l|8Ts(YbH&H+p7OkA5T``7h)nr}f{79_C_uD^1 zW0Z#QQ33M2(pCG#^13@3%Ze5lc_v;{$ugadYh|#jOMjnu#mj|Wl-qlrCmT|O^cRDy zD~RU|<@|`Q2rs^xMn9Z=IM+l#L1y`Lu` zADe_PR}7m}SX!LztawKMVyuKc4}+!o0k*m6vN_=wqaH4dZDEfalH>ire(iPJT|;@C zN8iHQWhFqAvK_vjiDHmx?zsQtY!G+!Eef%M>gHqMIE$gS7p0- z2t(cMM~D}Bw<=@PlQU;YbpHYxCZq8X5a1G>;{!KgidL13wkS7DsR*#GhqhbdebtWM z=y3UBumu=*WGhUE^Zgki`EE_c;~$2aYw_hL8K2^Xzw1x^CCru`luJw|(aN^%388># zK_sN$_lTvIk7>Cu`gV_{@Z{=zt;6(VX!%gVB*VLpf1~*K2aHh{R&)p_`K>RzZnkD5 zX#EGiZY=jt!Ln@Mp<4&#rJsgFxV;Uc)h8uh}pZnzQGz-WkN%1 zsXV2<5naZ++>L@odK#N|VAwl~mW^v6f(gr>-jtyQ)%lemi|dG}-Kt0{gbOS#oP zx0WZaK-T5--yG4G15y~GN-uIIMx;FuWF3qo&#L%8PkQ?aGHN`%<_v)s=$dU|CIZ(@ z`K^OX6G>?O+Y%VNH>C~?uU5EQ6tKF`{XT~bP_8s}n~>^2dc@J(K70bGjRYQh?uCXv)@(&Q7&V`1@_dL3!iexx6uttss_dUNEM`n?g42E#AQ*kIOPTS3=r z@hS2ARrbC1P+wHtjO?pJ6rs!N<#^IN4{xx!O#eC-M#REsqZF7b#X^TI?q6-_vP%C~ zX9!acgHfFVVtu@V9?E}VM_uH`_xod9H0_2@{ql;aa?PKj?zs1e7Ric-uJ-C!eWI0a z&%K#BFkpnXh-nwC6({ z?zYZ6Kdm2>v9v!@EdYOnCI-nEqcMPhipqd23`N$$4=Dm(T%6{uz0O#)6ouX@U8$lk z){{ilJk|z3VCKd7*^QXpwW72!);}@e4wn_IUWCPialx|C&8?3Ln(+~63;Qusl9Je? zt@z9~Lj6P}?%%7+#v5?i-fhau1pW3+Rd7U8K5OeNYvB8*XVoP^dx-`ZMJC&eUN$*_ z0^HMjgabB=H1KB9<7H(oPu=F7W&}@OEc5vIa9)Bs5w3xbbER5h_B@K%hPDGe(WVi; zVEO6NF{`^po(EoV3+@k;czNwR(K9gHRJQB*H)M5^D6!BumXRXW>~JX7#QTPA_4YQR zPE;h_4*+hPqgd7L(P6>+?aMD+5{m>iQfCE>`*1=Pts9W|r@c@^><~VPB0J@Rh$}7% z+~k-1LCfjj2qTlTwD`7o=_Y60g3nmsSG$5;(oDsHlYq*@g(|#Zl%JW@D4&Z9ricHq zx}H0Gui|A_bKjxideMt~ctG#OL_V|wT|hw7R&+>UucXmvOV@;DAn{#7M}!r5hX|)W zB#Jp*-BFN8`0aDpFaf9O2h{5+BxuA?>gJ)JNL;oTZktHt3Z#Z`u4Y0+jPlq;prA+IO9(oFE=XZ=?hQlA$B zNkw)o#H+?EL*t(zLM5ZOByD=zCE@Cic>o3ksF42iD&ix;Ue)<$B%#4-=UGh!47<;4j zq~p)W&vR7#d7A2$Q}C09%Nol0?(ZqmADh2kR?7?eKP5raqTIt&W%llS>#l+Z%^{w} z|H(3m*49}i7fyaQdSlI<_F#r1lHTzF}4%7C5MbZ%glMfYP1HoiJTyx=bg~9HNqSO zE}1GHv@C=EpvCh+Z`|knq&OvW_7B9N+^MtTV&O-P7w*rx;ZKbnQ~ADymkbuhAb*>P z$3FELMs(Zwg6)@a_?k;4|A6se613Yw0Le|7VMo*}pXtYb1mt<*(n!%hG2gWrA%ic@ zpSMqimES=qzlMrxx-cqTRd>gPE>3?RgDZk@t{i=L%xVljC2U%bc<+vIk$Zd>WkSz~ z3D@zDVzwS?--7L=BQE#}?b)RS)StR^XzVP+%w-0#E=@WOdA~;-4J8 zCr|-S*g!Lm*jG6<)x4pp1ZV5qm2jB<)gUns7^2AlAOzS<@wvExm*R8FNDUDGG##wT`FUBWs$j4I zbE(`p!Uz5CtAC1i62;;lXd;COJB_!wN`|Jba?aW>*mo(;vZ>#Sx4BIoU2iKBi05fv z{>J*WaDXh-ThEMhJc_4fefwFesgs4NIfTswO zS}jLZrCybOpVg?fuX?CQMAY*M{r-w%>^8hQctk>}2WuoXPP9E=Oq5Z5gaKICb5x|Fk zY&O_Y)uYN_T^fj|2^0UC)Me>LBYM0OIlv!wgldip(46yCwPJF4707&GYg;UMTVHBxK_vRNh_vNhy0gm1NlbI@OT zB5*gCZ#v>F`sUs(l5eqgeZg*gleFpFyJ8Ie-L>)?6po~ZMY`ON2A~dL5|0qrSkcpF zrGKLO)8ai*DOGdqfRkZW%Lo2ZHxCRqYG-mBjuZ&kNB)OLmZ>G(Mj19(MvWNN?sMey2MAQNv!k&=YNVZ!UY4Oyp|`{jY)OdEyDmU9{}4MYctP!eBU*D zQIkpcu4kn7fgGuz^Pz80+`M(9F6CPY(H@4e)XB#u(lCS`|LS*wxjs5W<*7C7#Ion= zs&6fxo9P{nL~UxzohN2dz@%H{xhc$GaSjG`3`Y}Y-I3nrgVL%D0`d=*L(TRI|HK2D zk{IhKrMkF|4JYopHq%V3bC5!sFhM=N`l*&!y5mBQQ^z;qH*}a)Q@iKcKm1ib;Tf7zMfJbP0%BjeB<0k1KUwmu;rK&3Lh?z!8ubv|AJV4S!flT7 z-X>H7LHzo+{LIW75D?>xs-J~cn%F0}2y8e%k|;WJMoo7XqeSs%@#!T?9Stg5VE1FG zf)Esbalm!&M<_RPVz60+S1nQlt&P=2$XT^D{)N&zC?K-S6|kM6t;=AYI(b9%DRqbN zK}mb#jU?qK=``QSl_~wQ)(J{b69eTq_uEOJG-UJrFx8Itd9%TxuZHd0V%i#m^Ot_E zi=q8H%t75$gbQkT#~tFmcUkEO6|H0BeR3);YY;bsSK#Cb)!tszoi zhdKv&NDi4Of5B+7m`*e35xHUyd!V#w?YTp^v$Reqy*I0FPd5I5Ld+I zitP;+8**|z7Kod)YcFmE;yH7bW5;rL!vm<=Hxqui#0sZ_GH#=I!u=dp@<&N@mcI*T z-)l|QAN+d!6Ua$Vy5jL+#)13gD@~KHr2;2vFD{c<7{>%h=QQYfP)>zvScQ3q!Db`? zvDp>iq2OXAJ2KfKH;+EL(}KsC-)@)=i2Kc+m|#rRV}giXcaGqgh&mE9Tb-Tia}nWT zgISyY6(2SH`GlTcJ=E#MFq9lc%DA%GEcohAoPczbO`DN;@bS^-G%2r<}yy!Z) zprcU*6V2a+0<~#g2*%F<4A`aL7!44(i??CB1{yRE%37Y6Mv0^38r$L99d(iB{Mz!oUoPc}&m6=E)1PTJ2K z<==5js03(|ITM@8X{f!1yZOx(J1mwF82^=zN6|c z^Al3rGqjwL(|m{7nXiq3mPOYPaObKY?jHu@h3^LTpD}Q!w;ZxROG>II`_p@J#E3x0 zemx}u?w+xLG%;#k23ZX0z8wZhG)xgh&QU#GZcJ$wR6Kiiq)(;$EeZecb#3(t=T#Rr zY@31b_D7w0H)1FDc+Md+lKx;PTIsmCfM-Ez{}2z_`CA{Qft2 z?Ru6}Ubep0z<^oY>qfBWqq zWRPTRv?aJ{L@gfvz-vL{Vu6W3idK#Pe)LZW29gB;d(F>Qri>>GVOzU+d)pX+LeoUeAaO`|z`;+#y zA|ChDJJkFant)FgpIBus>6IeWK$0@z<{NuP(wdQ_oX!jb)fTc?f1}ChEfmmn&IfR- z`@JPig)V%rtqleVR?)n@E$m*&F8+WjR;s^l8dNikDpB+7%l#a<0nUA><$GleJzq`daG062@kf&U9Cp$2w=U-wWuyfJs2{`9Q@&R zdB67ruiL-1u}Db}WB`{QIob4re3Q{wWi#X6S(@C3V{p*d3iEi{&?LrX6BB%rZh@X^ zH|8cX#w@-Jq?EeY>B^%{AGOrM1vD+6#2&KMV1eK^A96?Bx&7=v4DGAF=~^3DRea=d0S@Nxb1!kAl%h!En9qe;eyecjbMochH;Q+D`5$)F_l;Bg&=MV{)Tswd>e26PDf#PAd*zC zo-Gj=68Kt$>G3FCbE}&IT$5w<@o4osV>oIi<-$%BuU|<>=96F)q(S$9;Z%w0ipCjQ zx$!aL-w+Hsl+|-f+z*O6_w8m2-;}Z&iw6tt834!B{o^tws7FQRG(T7*vgB8W``kOEy5@9*$jeUCVy&PT!Rz@c zBr2?bU28iC=Xf6j7J|*!6_Tlz!+QBd$)0URiIa>vNT!U&5-4VrpY3b{#$XJ87G@wA zP)`MWQyI-Da3zCPG^nf50~>pyxRq9%ZSW>^jJHr)OyL4k5pCs6w)em&wpKF~FISs{ zGD*1v>#VIo>}bm3D<0DgDMlvSf1=J-2*!C45OVKgWKZVrUQ!30E1`tl^8bF6rZ880 z%2)%YsEs*I7DLl(?>F7st$utIicO{PQyFZLhN3J|GvX;VF*_{bR_P&mT zW1;|B?UxhqHGZ5YIYW;WpdYTWM)me^Jt`y-a{=GbO+(-An!I zd37#&b?um!*G`$}p$s+zFoja5nH*!4y@Y0{9b*+l-H#x3cs_n}i9LmD+! zEXBV%^A8DHfj=O&kQyYdstdEZO4ym$*xK(UCt|3JwwPi8%IDiGMLtqTYsdsme?@p( zOJMLO659CgMUNDCrSJRr56N?pp2HWuB0>Z6g0I!d09Yg_?H383b=;Us{OZrD!o^!* zs`LJv7)+J~iCwoxQ8z?4}IOtH|QR2oBlDWoyd(8$xhFyw&vfL2U8lL`4!Jg??Y969?8w=Qnd7K&$c zAw9~=KHr{e-}nd^@=_PRrCD`8)SNjYN`4X9KsI6i&#m!EorLfKe;CWVFwFIR@J`pP zd}&&>yr;Jf)$cggVc{e3vFR$3kC-!{FUlCgp5UUQz(Q+l_pGApWd1|%w@UFGHI`!f z=#rbAQR461>Nx{1)$rOYYrYj*A`%{->UMfr3vd>gA^DvToyrGwhKj<%IB-e-l0N6C zG$B772XMhT=OD)!LfZm%E%G)L{{85x=Jp0^%SnBaeiTh#kr@vtJ6_Kp?pMhH0fUBE z%(;j4FvB;6sHc}`zdIdvA0;bRx`D2Kt1fdYKayer9XGg?$=4yh5H=7qtRwewbUs5} z&I>i30xa8Zws~dif7>P|jWs=n^MD$ET{~Y^&3f+FZi>cJi1i5Lqn)ws3ujIdd)7-8 zbwZ@_4*Wac;y{lu>*Z!tk!V#vH~6fFyq!FW^JQkDn5+%1dSX5gbS3gaSlT&@t6ZinBtdf~B=^v0$n`iALhK?V!@Ciei+~ zr_`C1N522z9cL=bvV8sS>J7UnYYukTP-D5RE2sW@Mj!T1`F_b&jm`XsmAPCN7#<)V zsM?_pYbDj_1kyVDd!E#!9ErcuGRFHt?tk&&!<%t+GpvNZ`8K+H*%7rv96!d%21HSc zs3>NbofGp)x+QhtYZHzmn(4M56%}=PRQ=`wLKb4ytcby;jrAqIYD@1WnJ5#-HLN_qt21WlB z8{4XAxj9b9*<{3ie7W>`M_Fzh+A?CXFi*OLea?)(Nx>1XqFYc>|u&Z3r3^yPko~{*aG#s8R}j(pAx<`^!fTAcyhd#|1y-yv(ni!e=WlRSy2pL*4w9@M&^g#&G zJ%uzmPCnH1Kf9<1ME#0UsQYPkq@1hJ=XQh@|8`01NDqn13jCnm&x1*?GGs*)U*Z_MiLlji zj$wi}d*X`f3tTTaJmt>!5BL4T|E+QOmqz=gJ|X_sea@zV5m8s{^s5=!!k8zN#I*qn{#_akU44HFbTrPET4J_TIRe}z zx7B|C)Iz>5fE95SXAb1O(BlZmuKihGU9Wg|8rn^ecGx<%@zP;!yo5^d|A!#Tj`_IC z-%1BG(E8>l(ylpIDv;N;o2{0~mUoEIGyuUSv}jc zV$$1ni!r@hi^(Prl>QrVrOh|9)7N8fgVtnx@}8%%!15J^G~a(*#x*=LAQySQZYb*C zT$YtO5HhaYyCxh+I&*lO?n42#9hTK7 kY4Z0;Bg;KoYK=aK)oFkm^w&5ZeMAS$%eW_|77+vf2fDo-n*aa+ diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index ea5e2725b5..ed78488b67 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -1,6 +1,6 @@ { "config": { - "chainId": 1, + "chainId": 19763, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, @@ -11,16 +11,16 @@ "nonce": "0xdeadbeefdeadbeef", "timestamp": "0x0", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x8000000", - "difficulty": "0x10", + "gasLimit": "0x80000000", + "difficulty": "0x20000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xf4240" + "balance": "0xffffffff" } }, "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} \ No newline at end of file +} diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index b6298e8083..b8fa0f8f86 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -242,12 +242,14 @@ func (c *Conn) Write(msg Message) error { } _, err = c.Conn.Write(uint64(msg.Code()), payload) return err - } // handshake checks to make sure a `HELLO` is received. func (c *Conn) handshake(t *utesting.T) Message { - // write protoHandshake to client + defer c.SetDeadline(time.Time{}) + c.SetDeadline(time.Now().Add(10 * time.Second)) + + // write hello to client pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] ourHandshake := &Hello{ Version: 5, @@ -260,14 +262,13 @@ func (c *Conn) handshake(t *utesting.T) Message { if err := c.Write(ourHandshake); err != nil { t.Fatalf("could not write to connection: %v", err) } - // read protoHandshake from client + // read hello from client switch msg := c.Read().(type) { case *Hello: // set snappy if version is at least 5 if msg.Version >= 5 { c.SetSnappy(true) } - c.negotiateEthProtocol(msg.Caps) if c.ethProtocolVersion == 0 { t.Fatalf("unexpected eth protocol version") @@ -297,9 +298,11 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { // statusExchange performs a `Status` message exchange with the given // node. func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { + defer c.SetDeadline(time.Time{}) + c.SetDeadline(time.Now().Add(20 * time.Second)) + // read status message from client var message Message - loop: for { switch msg := c.Read().(type) { @@ -331,7 +334,7 @@ loop: // write status message to client status := Status{ ProtocolVersion: uint32(c.ethProtocolVersion), - NetworkID: 1, + NetworkID: chain.chainConfig.ChainID.Uint64(), TD: chain.TD(chain.Len()), Head: chain.blocks[chain.Len()-1].Hash(), Genesis: chain.blocks[0].Hash(), @@ -347,12 +350,15 @@ loop: // waitForBlock waits for confirmation from the client that it has // imported the given block. func (c *Conn) waitForBlock(block *types.Block) error { + defer c.SetReadDeadline(time.Time{}) + + timeout := time.Now().Add(20 * time.Second) + c.SetReadDeadline(timeout) for { req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} if err := c.Write(req); err != nil { return err } - switch msg := c.Read().(type) { case *BlockHeaders: if len(*msg) > 0 { From f5182c7b9c874aa77e90cc4c104ca01352fac6d2 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 23 Oct 2020 15:40:48 +0200 Subject: [PATCH 104/948] utils, params: add yolov2 bootnode --- cmd/utils/flags.go | 4 ++-- params/bootnodes.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 12562d8784..e5ccfd7435 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -804,7 +804,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes case ctx.GlobalBool(YoloV2Flag.Name): - urls = params.YoloV1Bootnodes + urls = params.YoloV2Bootnodes case cfg.BootstrapNodes != nil: return // already set, don't apply defaults. } @@ -840,7 +840,7 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes case ctx.GlobalBool(YoloV2Flag.Name): - urls = params.YoloV1Bootnodes + urls = params.YoloV2Bootnodes case cfg.BootstrapNodesV5 != nil: return // already set, don't apply defaults. } diff --git a/params/bootnodes.go b/params/bootnodes.go index 42a6e2ec7c..e4e2e8c934 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -67,10 +67,10 @@ var GoerliBootnodes = []string{ "enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303", } -// YoloV1Bootnodes are the enode URLs of the P2P bootstrap nodes running on the -// YOLOv1 ephemeral test network. -var YoloV1Bootnodes = []string{ - "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@35.178.210.161:30303", +// YoloV2Bootnodes are the enode URLs of the P2P bootstrap nodes running on the +// YOLOv2 ephemeral test network. +var YoloV2Bootnodes = []string{ + "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@18.130.52.230:30303", } const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@" From 68ac4eb796a243b9c79405ffdafab2a6125acb7a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 23 Oct 2020 16:47:26 +0200 Subject: [PATCH 105/948] params: update yolov2 bootnode with elastic ip --- params/bootnodes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/bootnodes.go b/params/bootnodes.go index e4e2e8c934..d4512bf789 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -70,7 +70,7 @@ var GoerliBootnodes = []string{ // YoloV2Bootnodes are the enode URLs of the P2P bootstrap nodes running on the // YOLOv2 ephemeral test network. var YoloV2Bootnodes = []string{ - "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@18.130.52.230:30303", + "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", } const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@" From 1a55e20d3562b08a4cf31b5b6b66d39d9acc43df Mon Sep 17 00:00:00 2001 From: James Prestwich <10149425+prestwich@users.noreply.github.com> Date: Mon, 26 Oct 2020 05:45:08 -0700 Subject: [PATCH 106/948] cmd/geth: fix dir path in geth attach for yolov2 network (#21749) --- cmd/geth/consolecmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index f15a306f17..cbecbe0a5f 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -137,7 +137,7 @@ func remoteConsole(ctx *cli.Context) error { } else if ctx.GlobalBool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") } else if ctx.GlobalBool(utils.YoloV2Flag.Name) { - path = filepath.Join(path, "yolo-v1") + path = filepath.Join(path, "yolo-v2") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) From 296a27d10624fa07b4fcb8c1964eefa6a0ba850d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 27 Oct 2020 17:22:44 +0100 Subject: [PATCH 107/948] accounts/abi/bind: restore error functionality (#21743) * accounts/abi/bind: restore error functionality * Update accounts/abi/bind/base.go Co-authored-by: Guillaume Ballet Co-authored-by: Guillaume Ballet --- accounts/abi/bind/base.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 0216f6b544..9e6d898eaf 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -152,7 +152,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri } } else { output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) - if err == nil && len(output) == 0 { + if err != nil { + return err + } + if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { return err From 18145adf084b9659c41343609677e6e94c0640c6 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 28 Oct 2020 20:00:22 +0800 Subject: [PATCH 108/948] core/state: maintain one more diff layer (#21730) * core/state: maintain one more diff layer * core/state: address comment --- core/state/statedb.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 6fd7723a16..fe30f595ed 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -881,8 +881,12 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil { log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) } - if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie - log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err) + // Keep 128 diff layers in the memory, persistent layer is 129th. + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state + if err := s.snaps.Cap(root, 128); err != nil { + log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err) } } s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil From 43c278cdf93d5469702fd1c2f570dbf3c1718ff0 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 28 Oct 2020 20:27:37 +0800 Subject: [PATCH 109/948] core/state: disable snapshot iteration if it's not fully constructed (#21682) * core/state/snapshot: add diskRoot function * core/state/snapshot: disable iteration if the snapshot is generating * core/state/snapshot: simplify the function * core/state: panic for undefined layer --- core/state/snapshot/snapshot.go | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index f6c5a6a9a8..4f496b4ff6 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -86,6 +86,10 @@ var ( // range of accounts covered. ErrNotCoveredYet = errors.New("not covered yet") + // ErrNotConstructed is returned if the callers want to iterate the snapshot + // while the generation is not finished yet. + ErrNotConstructed = errors.New("snapshot is not constructed") + // errSnapshotCycle is returned if a snapshot is attempted to be inserted // that forms a cycle in the snapshot tree. errSnapshotCycle = errors.New("snapshot cycle") @@ -609,11 +613,61 @@ func (t *Tree) Rebuild(root common.Hash) { // AccountIterator creates a new account iterator for the specified root hash and // seeks to a starting account hash. func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) { + ok, err := t.generating() + if err != nil { + return nil, err + } + if ok { + return nil, ErrNotConstructed + } return newFastAccountIterator(t, root, seek) } // StorageIterator creates a new storage iterator for the specified root hash and // account. The iterator will be move to the specific start position. func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) { + ok, err := t.generating() + if err != nil { + return nil, err + } + if ok { + return nil, ErrNotConstructed + } return newFastStorageIterator(t, root, account, seek) } + +// disklayer is an internal helper function to return the disk layer. +// The lock of snapTree is assumed to be held already. +func (t *Tree) disklayer() *diskLayer { + var snap snapshot + for _, s := range t.layers { + snap = s + break + } + if snap == nil { + return nil + } + switch layer := snap.(type) { + case *diskLayer: + return layer + case *diffLayer: + return layer.origin + default: + panic(fmt.Sprintf("%T: undefined layer", snap)) + } +} + +// generating is an internal helper function which reports whether the snapshot +// is still under the construction. +func (t *Tree) generating() (bool, error) { + t.lock.Lock() + defer t.lock.Unlock() + + layer := t.disklayer() + if layer == nil { + return false, errors.New("disk layer is missing") + } + layer.lock.RLock() + defer layer.lock.RUnlock() + return layer.genMarker != nil, nil +} From b63e3c37a62b4ff7dfafb06e05e760d22be94d86 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 30 Oct 2020 03:01:58 +0800 Subject: [PATCH 110/948] core: improve snapshot journal recovery (#21594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * core/state/snapshot: introduce snapshot journal version * core: update the disk layer in an atomic way * core: persist the disk layer generator periodically * core/state/snapshot: improve logging * core/state/snapshot: forcibly ensure the legacy snapshot is matched * core/state/snapshot: add debug logs * core, tests: fix tests and special recovery case * core: polish * core: add more blockchain tests for snapshot recovery * core/state: fix comment * core: add recovery flag for snapshot * core: add restart after start-after-crash tests * core/rawdb: fix imports * core: fix tests * core: remove log * core/state/snapshot: fix snapshot * core: avoid callbacks in SetHead * core: fix setHead cornercase where the threshold root has state * core: small docs for the test cases Co-authored-by: Péter Szilágyi --- core/blockchain.go | 92 +++- core/blockchain_repair_test.go | 318 +++++++++-- core/blockchain_sethead_test.go | 324 ++++++++++-- core/blockchain_snapshot_test.go | 732 ++++++++++++++++++++++++++ core/rawdb/accessors_snapshot.go | 57 ++ core/rawdb/schema.go | 6 + core/state/snapshot/disklayer_test.go | 76 +++ core/state/snapshot/generate.go | 1 + core/state/snapshot/journal.go | 226 +++++++- core/state/snapshot/snapshot.go | 117 +++- tests/state_test_util.go | 2 +- 11 files changed, 1792 insertions(+), 159 deletions(-) create mode 100644 core/blockchain_snapshot_test.go diff --git a/core/blockchain.go b/core/blockchain.go index 356340ae19..1c8a7fe60a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -207,9 +207,10 @@ type BlockChain struct { processor Processor // Block transaction processor interface vmConfig vm.Config - badBlocks *lru.Cache // Bad block cache - shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. - terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. + badBlocks *lru.Cache // Bad block cache + shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. + terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. + writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format. } // NewBlockChain returns a fully initialised block chain using information @@ -281,9 +282,29 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par // Make sure the state associated with the block is available head := bc.CurrentBlock() if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil { - log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash()) - if err := bc.SetHead(head.NumberU64()); err != nil { - return nil, err + // Head state is missing, before the state recovery, find out the + // disk layer point of snapshot(if it's enabled). Make sure the + // rewound point is lower than disk layer. + var diskRoot common.Hash + if bc.cacheConfig.SnapshotLimit > 0 { + diskRoot = rawdb.ReadSnapshotRoot(bc.db) + } + if diskRoot != (common.Hash{}) { + log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash(), "snaproot", diskRoot) + + snapDisk, err := bc.SetHeadBeyondRoot(head.NumberU64(), diskRoot) + if err != nil { + return nil, err + } + // Chain rewound, persist old snapshot number to indicate recovery procedure + if snapDisk != 0 { + rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk) + } + } else { + log.Warn("Head state missing, repairing", "number", head.Number(), "hash", head.Hash()) + if err := bc.SetHead(head.NumberU64()); err != nil { + return nil, err + } } } // Ensure that a previous crash in SetHead doesn't leave extra ancients @@ -339,7 +360,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } // Load any existing snapshot, regenerating it if loading failed if bc.cacheConfig.SnapshotLimit > 0 { - bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root(), !bc.cacheConfig.SnapshotWait) + // If the chain was rewound past the snapshot persistent layer (causing + // a recovery block number to be persisted to disk), check if we're still + // in recovery mode and in that case, don't invalidate the snapshot on a + // head mismatch. + var recover bool + + head := bc.CurrentBlock() + if layer := rawdb.ReadSnapshotRecoveryNumber(bc.db); layer != nil && *layer > head.NumberU64() { + log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer) + recover = true + } + bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, recover) } // Take ownership of this particular state go bc.update() @@ -444,9 +476,25 @@ func (bc *BlockChain) loadLastState() error { // was fast synced or full synced and in which state, the method will try to // delete minimal data from disk whilst retaining chain consistency. func (bc *BlockChain) SetHead(head uint64) error { + _, err := bc.SetHeadBeyondRoot(head, common.Hash{}) + return err +} + +// SetHeadBeyondRoot rewinds the local chain to a new head with the extra condition +// that the rewind must pass the specified state root. This method is meant to be +// used when rewiding with snapshots enabled to ensure that we go back further than +// persistent disk layer. Depending on whether the node was fast synced or full, and +// in which state, the method will try to delete minimal data from disk whilst +// retaining chain consistency. +// +// The method returns the block number where the requested root cap was found. +func (bc *BlockChain) SetHeadBeyondRoot(head uint64, root common.Hash) (uint64, error) { bc.chainmu.Lock() defer bc.chainmu.Unlock() + // Track the block number of the requested root hash + var rootNumber uint64 // (no root == always 0) + // Retrieve the last pivot block to short circuit rollbacks beyond it and the // current freezer limit to start nuking id underflown pivot := rawdb.ReadLastPivotNumber(bc.db) @@ -462,8 +510,16 @@ func (bc *BlockChain) SetHead(head uint64) error { log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash()) newHeadBlock = bc.genesisBlock } else { - // Block exists, keep rewinding until we find one with state + // Block exists, keep rewinding until we find one with state, + // keeping rewinding until we exceed the optional threshold + // root hash + beyondRoot := (root == common.Hash{}) // Flag whether we're beyond the requested root (no root, always true) + for { + // If a root threshold was requested but not yet crossed, check + if root != (common.Hash{}) && !beyondRoot && newHeadBlock.Root() == root { + beyondRoot, rootNumber = true, newHeadBlock.NumberU64() + } if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil { log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) if pivot == nil || newHeadBlock.NumberU64() > *pivot { @@ -474,8 +530,12 @@ func (bc *BlockChain) SetHead(head uint64) error { newHeadBlock = bc.genesisBlock } } - log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) - break + if beyondRoot || newHeadBlock.NumberU64() == 0 { + log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) + break + } + log.Debug("Skipping block with threshold state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "root", newHeadBlock.Root()) + newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) // Keep rewinding } } rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash()) @@ -555,7 +615,7 @@ func (bc *BlockChain) SetHead(head uint64) error { bc.txLookupCache.Purge() bc.futureBlocks.Purge() - return bc.loadLastState() + return rootNumber, bc.loadLastState() } // FastSyncCommitHead sets the current head block to the one defined by the hash @@ -940,8 +1000,14 @@ func (bc *BlockChain) Stop() { var snapBase common.Hash if bc.snaps != nil { var err error - if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { - log.Error("Failed to journal state snapshot", "err", err) + if bc.writeLegacyJournal { + if snapBase, err = bc.snaps.LegacyJournal(bc.CurrentBlock().Root()); err != nil { + log.Error("Failed to journal state snapshot", "err", err) + } + } else { + if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { + log.Error("Failed to journal state snapshot", "err", err) + } } } // Ensure the state of a recent block is also stored to disk before exiting. diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 27903dd06b..b5cd232a9c 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -25,6 +25,7 @@ import ( "math/big" "os" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -38,7 +39,10 @@ import ( // committed to disk and then the process crashed. In this case we expect the full // chain to be rolled back to the committed block, but the chain data itself left // in the database for replaying. -func TestShortRepair(t *testing.T) { +func TestShortRepair(t *testing.T) { testShortRepair(t, false) } +func TestShortRepairWithSnapshots(t *testing.T) { testShortRepair(t, true) } + +func testShortRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // @@ -68,14 +72,17 @@ func TestShortRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain where the fast sync pivot point was // already committed, after which the process crashed. In this case we expect the full // chain to be rolled back to the committed block, but the chain data itself left in // the database for replaying. -func TestShortFastSyncedRepair(t *testing.T) { +func TestShortFastSyncedRepair(t *testing.T) { testShortFastSyncedRepair(t, false) } +func TestShortFastSyncedRepairWithSnapshots(t *testing.T) { testShortFastSyncedRepair(t, true) } + +func testShortFastSyncedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // @@ -105,14 +112,17 @@ func TestShortFastSyncedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain where the fast sync pivot point was // not yet committed, but the process crashed. In this case we expect the chain to // detect that it was fast syncing and not delete anything, since we can just pick // up directly where we left off. -func TestShortFastSyncingRepair(t *testing.T) { +func TestShortFastSyncingRepair(t *testing.T) { testShortFastSyncingRepair(t, false) } +func TestShortFastSyncingRepairWithSnapshots(t *testing.T) { testShortFastSyncingRepair(t, true) } + +func testShortFastSyncingRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // @@ -142,7 +152,7 @@ func TestShortFastSyncingRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a shorter side chain, where a @@ -150,7 +160,10 @@ func TestShortFastSyncingRepair(t *testing.T) { // test scenario the side chain is below the committed block. In this case we expect // the canonical chain to be rolled back to the committed block, but the chain data // itself left in the database for replaying. -func TestShortOldForkedRepair(t *testing.T) { +func TestShortOldForkedRepair(t *testing.T) { testShortOldForkedRepair(t, false) } +func TestShortOldForkedRepairWithSnapshots(t *testing.T) { testShortOldForkedRepair(t, true) } + +func testShortOldForkedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3 @@ -182,7 +195,7 @@ func TestShortOldForkedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a shorter side chain, where @@ -191,6 +204,13 @@ func TestShortOldForkedRepair(t *testing.T) { // this case we expect the canonical chain to be rolled back to the committed block, // but the chain data itself left in the database for replaying. func TestShortOldForkedFastSyncedRepair(t *testing.T) { + testShortOldForkedFastSyncedRepair(t, false) +} +func TestShortOldForkedFastSyncedRepairWithSnapshots(t *testing.T) { + testShortOldForkedFastSyncedRepair(t, true) +} + +func testShortOldForkedFastSyncedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3 @@ -222,7 +242,7 @@ func TestShortOldForkedFastSyncedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a shorter side chain, where @@ -231,6 +251,13 @@ func TestShortOldForkedFastSyncedRepair(t *testing.T) { // the chain to detect that it was fast syncing and not delete anything, since we // can just pick up directly where we left off. func TestShortOldForkedFastSyncingRepair(t *testing.T) { + testShortOldForkedFastSyncingRepair(t, false) +} +func TestShortOldForkedFastSyncingRepairWithSnapshots(t *testing.T) { + testShortOldForkedFastSyncingRepair(t, true) +} + +func testShortOldForkedFastSyncingRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3 @@ -262,7 +289,7 @@ func TestShortOldForkedFastSyncingRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a shorter side chain, where a @@ -270,7 +297,10 @@ func TestShortOldForkedFastSyncingRepair(t *testing.T) { // test scenario the side chain reaches above the committed block. In this case we // expect the canonical chain to be rolled back to the committed block, but the // chain data itself left in the database for replaying. -func TestShortNewlyForkedRepair(t *testing.T) { +func TestShortNewlyForkedRepair(t *testing.T) { testShortNewlyForkedRepair(t, false) } +func TestShortNewlyForkedRepairWithSnapshots(t *testing.T) { testShortNewlyForkedRepair(t, true) } + +func testShortNewlyForkedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6 @@ -302,7 +332,7 @@ func TestShortNewlyForkedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a shorter side chain, where @@ -311,6 +341,13 @@ func TestShortNewlyForkedRepair(t *testing.T) { // In this case we expect the canonical chain to be rolled back to the committed // block, but the chain data itself left in the database for replaying. func TestShortNewlyForkedFastSyncedRepair(t *testing.T) { + testShortNewlyForkedFastSyncedRepair(t, false) +} +func TestShortNewlyForkedFastSyncedRepairWithSnapshots(t *testing.T) { + testShortNewlyForkedFastSyncedRepair(t, true) +} + +func testShortNewlyForkedFastSyncedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6 @@ -342,7 +379,7 @@ func TestShortNewlyForkedFastSyncedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a shorter side chain, where @@ -351,6 +388,13 @@ func TestShortNewlyForkedFastSyncedRepair(t *testing.T) { // case we expect the chain to detect that it was fast syncing and not delete // anything, since we can just pick up directly where we left off. func TestShortNewlyForkedFastSyncingRepair(t *testing.T) { + testShortNewlyForkedFastSyncingRepair(t, false) +} +func TestShortNewlyForkedFastSyncingRepairWithSnapshots(t *testing.T) { + testShortNewlyForkedFastSyncingRepair(t, true) +} + +func testShortNewlyForkedFastSyncingRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6 @@ -382,14 +426,17 @@ func TestShortNewlyForkedFastSyncingRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a longer side chain, where a // recent block was already committed to disk and then the process crashed. In this // case we expect the canonical chain to be rolled back to the committed block, but // the chain data itself left in the database for replaying. -func TestShortReorgedRepair(t *testing.T) { +func TestShortReorgedRepair(t *testing.T) { testShortReorgedRepair(t, false) } +func TestShortReorgedRepairWithSnapshots(t *testing.T) { testShortReorgedRepair(t, true) } + +func testShortReorgedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 @@ -421,7 +468,7 @@ func TestShortReorgedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a longer side chain, where @@ -429,6 +476,13 @@ func TestShortReorgedRepair(t *testing.T) { // crashed. In this case we expect the canonical chain to be rolled back to the // committed block, but the chain data itself left in the database for replaying. func TestShortReorgedFastSyncedRepair(t *testing.T) { + testShortReorgedFastSyncedRepair(t, false) +} +func TestShortReorgedFastSyncedRepairWithSnapshots(t *testing.T) { + testShortReorgedFastSyncedRepair(t, true) +} + +func testShortReorgedFastSyncedRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 @@ -460,7 +514,7 @@ func TestShortReorgedFastSyncedRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a short canonical chain and a longer side chain, where @@ -468,6 +522,13 @@ func TestShortReorgedFastSyncedRepair(t *testing.T) { // this case we expect the chain to detect that it was fast syncing and not delete // anything, since we can just pick up directly where we left off. func TestShortReorgedFastSyncingRepair(t *testing.T) { + testShortReorgedFastSyncingRepair(t, false) +} +func TestShortReorgedFastSyncingRepairWithSnapshots(t *testing.T) { + testShortReorgedFastSyncingRepair(t, true) +} + +func testShortReorgedFastSyncingRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 @@ -499,14 +560,17 @@ func TestShortReorgedFastSyncingRepair(t *testing.T) { expHeadHeader: 8, expHeadFastBlock: 8, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks where a recent // block - newer than the ancient limit - was already committed to disk and then // the process crashed. In this case we expect the chain to be rolled back to the // committed block, with everything afterwads kept as fast sync data. -func TestLongShallowRepair(t *testing.T) { +func TestLongShallowRepair(t *testing.T) { testLongShallowRepair(t, false) } +func TestLongShallowRepairWithSnapshots(t *testing.T) { testLongShallowRepair(t, true) } + +func testLongShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // @@ -541,14 +605,17 @@ func TestLongShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks where a recent // block - older than the ancient limit - was already committed to disk and then // the process crashed. In this case we expect the chain to be rolled back to the // committed block, with everything afterwads deleted. -func TestLongDeepRepair(t *testing.T) { +func TestLongDeepRepair(t *testing.T) { testLongDeepRepair(t, false) } +func TestLongDeepRepairWithSnapshots(t *testing.T) { testLongDeepRepair(t, true) } + +func testLongDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // @@ -582,7 +649,7 @@ func TestLongDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks where the fast @@ -590,6 +657,13 @@ func TestLongDeepRepair(t *testing.T) { // which the process crashed. In this case we expect the chain to be rolled back // to the committed block, with everything afterwads kept as fast sync data. func TestLongFastSyncedShallowRepair(t *testing.T) { + testLongFastSyncedShallowRepair(t, false) +} +func TestLongFastSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongFastSyncedShallowRepair(t, true) +} + +func testLongFastSyncedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // @@ -624,14 +698,17 @@ func TestLongFastSyncedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks where the fast // sync pivot point - older than the ancient limit - was already committed, after // which the process crashed. In this case we expect the chain to be rolled back // to the committed block, with everything afterwads deleted. -func TestLongFastSyncedDeepRepair(t *testing.T) { +func TestLongFastSyncedDeepRepair(t *testing.T) { testLongFastSyncedDeepRepair(t, false) } +func TestLongFastSyncedDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncedDeepRepair(t, true) } + +func testLongFastSyncedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // @@ -665,7 +742,7 @@ func TestLongFastSyncedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks where the fast @@ -674,6 +751,13 @@ func TestLongFastSyncedDeepRepair(t *testing.T) { // syncing and not delete anything, since we can just pick up directly where we // left off. func TestLongFastSyncingShallowRepair(t *testing.T) { + testLongFastSyncingShallowRepair(t, false) +} +func TestLongFastSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongFastSyncingShallowRepair(t, true) +} + +func testLongFastSyncingShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // @@ -708,7 +792,7 @@ func TestLongFastSyncingShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks where the fast @@ -716,7 +800,10 @@ func TestLongFastSyncingShallowRepair(t *testing.T) { // process crashed. In this case we expect the chain to detect that it was fast // syncing and not delete anything, since we can just pick up directly where we // left off. -func TestLongFastSyncingDeepRepair(t *testing.T) { +func TestLongFastSyncingDeepRepair(t *testing.T) { testLongFastSyncingDeepRepair(t, false) } +func TestLongFastSyncingDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncingDeepRepair(t, true) } + +func testLongFastSyncingDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // @@ -751,7 +838,7 @@ func TestLongFastSyncingDeepRepair(t *testing.T) { expHeadHeader: 24, expHeadFastBlock: 24, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -761,6 +848,13 @@ func TestLongFastSyncingDeepRepair(t *testing.T) { // rolled back to the committed block, with everything afterwads kept as fast // sync data; the side chain completely nuked by the freezer. func TestLongOldForkedShallowRepair(t *testing.T) { + testLongOldForkedShallowRepair(t, false) +} +func TestLongOldForkedShallowRepairWithSnapshots(t *testing.T) { + testLongOldForkedShallowRepair(t, true) +} + +func testLongOldForkedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3 @@ -796,7 +890,7 @@ func TestLongOldForkedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -805,7 +899,10 @@ func TestLongOldForkedShallowRepair(t *testing.T) { // chain is below the committed block. In this case we expect the canonical chain // to be rolled back to the committed block, with everything afterwads deleted; // the side chain completely nuked by the freezer. -func TestLongOldForkedDeepRepair(t *testing.T) { +func TestLongOldForkedDeepRepair(t *testing.T) { testLongOldForkedDeepRepair(t, false) } +func TestLongOldForkedDeepRepairWithSnapshots(t *testing.T) { testLongOldForkedDeepRepair(t, true) } + +func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3 @@ -840,7 +937,7 @@ func TestLongOldForkedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -850,6 +947,13 @@ func TestLongOldForkedDeepRepair(t *testing.T) { // to be rolled back to the committed block, with everything afterwads kept as // fast sync data; the side chain completely nuked by the freezer. func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) { + testLongOldForkedFastSyncedShallowRepair(t, false) +} +func TestLongOldForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncedShallowRepair(t, true) +} + +func testLongOldForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3 @@ -885,7 +989,7 @@ func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -895,6 +999,13 @@ func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) { // chain to be rolled back to the committed block, with everything afterwads deleted; // the side chain completely nuked by the freezer. func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) { + testLongOldForkedFastSyncedDeepRepair(t, false) +} +func TestLongOldForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncedDeepRepair(t, true) +} + +func testLongOldForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3 @@ -929,7 +1040,7 @@ func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -939,6 +1050,13 @@ func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) { // that it was fast syncing and not delete anything. The side chain is completely // nuked by the freezer. func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) { + testLongOldForkedFastSyncingShallowRepair(t, false) +} +func TestLongOldForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncingShallowRepair(t, true) +} + +func testLongOldForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3 @@ -974,7 +1092,7 @@ func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -984,6 +1102,13 @@ func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) { // that it was fast syncing and not delete anything. The side chain is completely // nuked by the freezer. func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) { + testLongOldForkedFastSyncingDeepRepair(t, false) +} +func TestLongOldForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncingDeepRepair(t, true) +} + +func testLongOldForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3 @@ -1019,7 +1144,7 @@ func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) { expHeadHeader: 24, expHeadFastBlock: 24, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -1029,6 +1154,13 @@ func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) { // rolled back to the committed block, with everything afterwads kept as fast // sync data; the side chain completely nuked by the freezer. func TestLongNewerForkedShallowRepair(t *testing.T) { + testLongNewerForkedShallowRepair(t, false) +} +func TestLongNewerForkedShallowRepairWithSnapshots(t *testing.T) { + testLongNewerForkedShallowRepair(t, true) +} + +func testLongNewerForkedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1064,7 +1196,7 @@ func TestLongNewerForkedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -1073,7 +1205,10 @@ func TestLongNewerForkedShallowRepair(t *testing.T) { // chain is above the committed block. In this case we expect the canonical chain // to be rolled back to the committed block, with everything afterwads deleted; // the side chain completely nuked by the freezer. -func TestLongNewerForkedDeepRepair(t *testing.T) { +func TestLongNewerForkedDeepRepair(t *testing.T) { testLongNewerForkedDeepRepair(t, false) } +func TestLongNewerForkedDeepRepairWithSnapshots(t *testing.T) { testLongNewerForkedDeepRepair(t, true) } + +func testLongNewerForkedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1108,7 +1243,7 @@ func TestLongNewerForkedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -1118,6 +1253,13 @@ func TestLongNewerForkedDeepRepair(t *testing.T) { // to be rolled back to the committed block, with everything afterwads kept as fast // sync data; the side chain completely nuked by the freezer. func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) { + testLongNewerForkedFastSyncedShallowRepair(t, false) +} +func TestLongNewerForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncedShallowRepair(t, true) +} + +func testLongNewerForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1153,7 +1295,7 @@ func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -1163,6 +1305,13 @@ func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) { // chain to be rolled back to the committed block, with everything afterwads deleted; // the side chain completely nuked by the freezer. func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) { + testLongNewerForkedFastSyncedDeepRepair(t, false) +} +func TestLongNewerForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncedDeepRepair(t, true) +} + +func testLongNewerForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1197,7 +1346,7 @@ func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -1207,6 +1356,13 @@ func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) { // that it was fast syncing and not delete anything. The side chain is completely // nuked by the freezer. func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) { + testLongNewerForkedFastSyncingShallowRepair(t, false) +} +func TestLongNewerForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncingShallowRepair(t, true) +} + +func testLongNewerForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1242,7 +1398,7 @@ func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a shorter @@ -1252,6 +1408,13 @@ func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) { // that it was fast syncing and not delete anything. The side chain is completely // nuked by the freezer. func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) { + testLongNewerForkedFastSyncingDeepRepair(t, false) +} +func TestLongNewerForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncingDeepRepair(t, true) +} + +func testLongNewerForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1287,7 +1450,7 @@ func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) { expHeadHeader: 24, expHeadFastBlock: 24, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a longer side @@ -1295,7 +1458,10 @@ func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) { // to disk and then the process crashed. In this case we expect the chain to be // rolled back to the committed block, with everything afterwads kept as fast sync // data. The side chain completely nuked by the freezer. -func TestLongReorgedShallowRepair(t *testing.T) { +func TestLongReorgedShallowRepair(t *testing.T) { testLongReorgedShallowRepair(t, false) } +func TestLongReorgedShallowRepairWithSnapshots(t *testing.T) { testLongReorgedShallowRepair(t, true) } + +func testLongReorgedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1331,7 +1497,7 @@ func TestLongReorgedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a longer side @@ -1339,7 +1505,10 @@ func TestLongReorgedShallowRepair(t *testing.T) { // to disk and then the process crashed. In this case we expect the canonical chains // to be rolled back to the committed block, with everything afterwads deleted. The // side chain completely nuked by the freezer. -func TestLongReorgedDeepRepair(t *testing.T) { +func TestLongReorgedDeepRepair(t *testing.T) { testLongReorgedDeepRepair(t, false) } +func TestLongReorgedDeepRepairWithSnapshots(t *testing.T) { testLongReorgedDeepRepair(t, true) } + +func testLongReorgedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1374,7 +1543,7 @@ func TestLongReorgedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a longer @@ -1384,6 +1553,13 @@ func TestLongReorgedDeepRepair(t *testing.T) { // afterwads kept as fast sync data. The side chain completely nuked by the // freezer. func TestLongReorgedFastSyncedShallowRepair(t *testing.T) { + testLongReorgedFastSyncedShallowRepair(t, false) +} +func TestLongReorgedFastSyncedShallowRepairWithSnapshots(t *testing.T) { + testLongReorgedFastSyncedShallowRepair(t, true) +} + +func testLongReorgedFastSyncedShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1419,7 +1595,7 @@ func TestLongReorgedFastSyncedShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a longer @@ -1428,6 +1604,13 @@ func TestLongReorgedFastSyncedShallowRepair(t *testing.T) { // expect the canonical chains to be rolled back to the committed block, with // everything afterwads deleted. The side chain completely nuked by the freezer. func TestLongReorgedFastSyncedDeepRepair(t *testing.T) { + testLongReorgedFastSyncedDeepRepair(t, false) +} +func TestLongReorgedFastSyncedDeepRepairWithSnapshots(t *testing.T) { + testLongReorgedFastSyncedDeepRepair(t, true) +} + +func testLongReorgedFastSyncedDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1462,7 +1645,7 @@ func TestLongReorgedFastSyncedDeepRepair(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a longer @@ -1471,6 +1654,13 @@ func TestLongReorgedFastSyncedDeepRepair(t *testing.T) { // chain to detect that it was fast syncing and not delete anything, since we // can just pick up directly where we left off. func TestLongReorgedFastSyncingShallowRepair(t *testing.T) { + testLongReorgedFastSyncingShallowRepair(t, false) +} +func TestLongReorgedFastSyncingShallowRepairWithSnapshots(t *testing.T) { + testLongReorgedFastSyncingShallowRepair(t, true) +} + +func testLongReorgedFastSyncingShallowRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1506,7 +1696,7 @@ func TestLongReorgedFastSyncingShallowRepair(t *testing.T) { expHeadHeader: 18, expHeadFastBlock: 18, expHeadBlock: 0, - }) + }, snapshots) } // Tests a recovery for a long canonical chain with frozen blocks and a longer @@ -1515,6 +1705,13 @@ func TestLongReorgedFastSyncingShallowRepair(t *testing.T) { // chain to detect that it was fast syncing and not delete anything, since we // can just pick up directly where we left off. func TestLongReorgedFastSyncingDeepRepair(t *testing.T) { + testLongReorgedFastSyncingDeepRepair(t, false) +} +func TestLongReorgedFastSyncingDeepRepairWithSnapshots(t *testing.T) { + testLongReorgedFastSyncingDeepRepair(t, true) +} + +func testLongReorgedFastSyncingDeepRepair(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1550,13 +1747,13 @@ func TestLongReorgedFastSyncingDeepRepair(t *testing.T) { expHeadHeader: 24, expHeadFastBlock: 24, expHeadBlock: 0, - }) + }, snapshots) } -func testRepair(t *testing.T, tt *rewindTest) { +func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { // It's hard to follow the test case, visualize the input //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - //fmt.Println(tt.dump(true)) + // fmt.Println(tt.dump(true)) // Create a temporary persistent database datadir, err := ioutil.TempDir("", "") @@ -1575,8 +1772,18 @@ func testRepair(t *testing.T, tt *rewindTest) { var ( genesis = new(Genesis).MustCommit(db) engine = ethash.NewFullFaker() + config = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, // Disable snapshot by default + } ) - chain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if snapshots { + config.SnapshotLimit = 256 + config.SnapshotWait = true + } + chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1599,6 +1806,11 @@ func testRepair(t *testing.T, tt *rewindTest) { } if tt.commitBlock > 0 { chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil) + if snapshots { + if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { + t.Fatalf("Failed to flatten snapshots: %v", err) + } + } } if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index dc1368ff4b..45c4073eb4 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -26,6 +26,7 @@ import ( "os" "strings" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -150,7 +151,10 @@ func (tt *rewindTest) dump(crash bool) string { // chain to be rolled back to the committed block. Everything above the sethead // point should be deleted. In between the committed block and the requested head // the data can remain as "fast sync" data to avoid redownloading it. -func TestShortSetHead(t *testing.T) { +func TestShortSetHead(t *testing.T) { testShortSetHead(t, false) } +func TestShortSetHeadWithSnapshots(t *testing.T) { testShortSetHead(t, true) } + +func testShortSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // @@ -181,7 +185,7 @@ func TestShortSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain where the fast sync pivot point was @@ -190,7 +194,10 @@ func TestShortSetHead(t *testing.T) { // Everything above the sethead point should be deleted. In between the committed // block and the requested head the data can remain as "fast sync" data to avoid // redownloading it. -func TestShortFastSyncedSetHead(t *testing.T) { +func TestShortFastSyncedSetHead(t *testing.T) { testShortFastSyncedSetHead(t, false) } +func TestShortFastSyncedSetHeadWithSnapshots(t *testing.T) { testShortFastSyncedSetHead(t, true) } + +func testShortFastSyncedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // @@ -221,7 +228,7 @@ func TestShortFastSyncedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain where the fast sync pivot point was @@ -229,7 +236,10 @@ func TestShortFastSyncedSetHead(t *testing.T) { // detect that it was fast syncing and delete everything from the new head, since // we can just pick up fast syncing from there. The head full block should be set // to the genesis. -func TestShortFastSyncingSetHead(t *testing.T) { +func TestShortFastSyncingSetHead(t *testing.T) { testShortFastSyncingSetHead(t, false) } +func TestShortFastSyncingSetHeadWithSnapshots(t *testing.T) { testShortFastSyncingSetHead(t, true) } + +func testShortFastSyncingSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // @@ -260,7 +270,7 @@ func TestShortFastSyncingSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a shorter side chain, where a @@ -270,7 +280,10 @@ func TestShortFastSyncingSetHead(t *testing.T) { // above the sethead point should be deleted. In between the committed block and // the requested head the data can remain as "fast sync" data to avoid redownloading // it. The side chain should be left alone as it was shorter. -func TestShortOldForkedSetHead(t *testing.T) { +func TestShortOldForkedSetHead(t *testing.T) { testShortOldForkedSetHead(t, false) } +func TestShortOldForkedSetHeadWithSnapshots(t *testing.T) { testShortOldForkedSetHead(t, true) } + +func testShortOldForkedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3 @@ -303,7 +316,7 @@ func TestShortOldForkedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a shorter side chain, where @@ -314,6 +327,13 @@ func TestShortOldForkedSetHead(t *testing.T) { // committed block and the requested head the data can remain as "fast sync" data // to avoid redownloading it. The side chain should be left alone as it was shorter. func TestShortOldForkedFastSyncedSetHead(t *testing.T) { + testShortOldForkedFastSyncedSetHead(t, false) +} +func TestShortOldForkedFastSyncedSetHeadWithSnapshots(t *testing.T) { + testShortOldForkedFastSyncedSetHead(t, true) +} + +func testShortOldForkedFastSyncedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3 @@ -346,7 +366,7 @@ func TestShortOldForkedFastSyncedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a shorter side chain, where @@ -356,6 +376,13 @@ func TestShortOldForkedFastSyncedSetHead(t *testing.T) { // head, since we can just pick up fast syncing from there. The head full block // should be set to the genesis. func TestShortOldForkedFastSyncingSetHead(t *testing.T) { + testShortOldForkedFastSyncingSetHead(t, false) +} +func TestShortOldForkedFastSyncingSetHeadWithSnapshots(t *testing.T) { + testShortOldForkedFastSyncingSetHead(t, true) +} + +func testShortOldForkedFastSyncingSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3 @@ -388,7 +415,7 @@ func TestShortOldForkedFastSyncingSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a shorter side chain, where a @@ -402,7 +429,10 @@ func TestShortOldForkedFastSyncingSetHead(t *testing.T) { // The side chain could be left to be if the fork point was before the new head // we are deleting to, but it would be exceedingly hard to detect that case and // properly handle it, so we'll trade extra work in exchange for simpler code. -func TestShortNewlyForkedSetHead(t *testing.T) { +func TestShortNewlyForkedSetHead(t *testing.T) { testShortNewlyForkedSetHead(t, false) } +func TestShortNewlyForkedSetHeadWithSnapshots(t *testing.T) { testShortNewlyForkedSetHead(t, true) } + +func testShortNewlyForkedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8 @@ -435,7 +465,7 @@ func TestShortNewlyForkedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a shorter side chain, where @@ -449,6 +479,13 @@ func TestShortNewlyForkedSetHead(t *testing.T) { // we are deleting to, but it would be exceedingly hard to detect that case and // properly handle it, so we'll trade extra work in exchange for simpler code. func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) { + testShortNewlyForkedFastSyncedSetHead(t, false) +} +func TestShortNewlyForkedFastSyncedSetHeadWithSnapshots(t *testing.T) { + testShortNewlyForkedFastSyncedSetHead(t, true) +} + +func testShortNewlyForkedFastSyncedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8 @@ -481,7 +518,7 @@ func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a shorter side chain, where @@ -495,6 +532,13 @@ func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) { // we are deleting to, but it would be exceedingly hard to detect that case and // properly handle it, so we'll trade extra work in exchange for simpler code. func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) { + testShortNewlyForkedFastSyncingSetHead(t, false) +} +func TestShortNewlyForkedFastSyncingSetHeadWithSnapshots(t *testing.T) { + testShortNewlyForkedFastSyncingSetHead(t, true) +} + +func testShortNewlyForkedFastSyncingSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8 @@ -527,7 +571,7 @@ func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a longer side chain, where a @@ -540,7 +584,10 @@ func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) { // The side chain could be left to be if the fork point was before the new head // we are deleting to, but it would be exceedingly hard to detect that case and // properly handle it, so we'll trade extra work in exchange for simpler code. -func TestShortReorgedSetHead(t *testing.T) { +func TestShortReorgedSetHead(t *testing.T) { testShortReorgedSetHead(t, false) } +func TestShortReorgedSetHeadWithSnapshots(t *testing.T) { testShortReorgedSetHead(t, true) } + +func testShortReorgedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 @@ -573,7 +620,7 @@ func TestShortReorgedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a longer side chain, where @@ -588,6 +635,13 @@ func TestShortReorgedSetHead(t *testing.T) { // we are deleting to, but it would be exceedingly hard to detect that case and // properly handle it, so we'll trade extra work in exchange for simpler code. func TestShortReorgedFastSyncedSetHead(t *testing.T) { + testShortReorgedFastSyncedSetHead(t, false) +} +func TestShortReorgedFastSyncedSetHeadWithSnapshots(t *testing.T) { + testShortReorgedFastSyncedSetHead(t, true) +} + +func testShortReorgedFastSyncedSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 @@ -620,7 +674,7 @@ func TestShortReorgedFastSyncedSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a short canonical chain and a longer side chain, where @@ -633,6 +687,13 @@ func TestShortReorgedFastSyncedSetHead(t *testing.T) { // we are deleting to, but it would be exceedingly hard to detect that case and // properly handle it, so we'll trade extra work in exchange for simpler code. func TestShortReorgedFastSyncingSetHead(t *testing.T) { + testShortReorgedFastSyncingSetHead(t, false) +} +func TestShortReorgedFastSyncingSetHeadWithSnapshots(t *testing.T) { + testShortReorgedFastSyncingSetHead(t, true) +} + +func testShortReorgedFastSyncingSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10 @@ -665,7 +726,7 @@ func TestShortReorgedFastSyncingSetHead(t *testing.T) { expHeadHeader: 7, expHeadFastBlock: 7, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks where a recent @@ -674,7 +735,10 @@ func TestShortReorgedFastSyncingSetHead(t *testing.T) { // to the committed block. Everything above the sethead point should be deleted. // In between the committed block and the requested head the data can remain as // "fast sync" data to avoid redownloading it. -func TestLongShallowSetHead(t *testing.T) { +func TestLongShallowSetHead(t *testing.T) { testLongShallowSetHead(t, false) } +func TestLongShallowSetHeadWithSnapshots(t *testing.T) { testLongShallowSetHead(t, true) } + +func testLongShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // @@ -710,7 +774,7 @@ func TestLongShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks where a recent @@ -718,7 +782,10 @@ func TestLongShallowSetHead(t *testing.T) { // sethead was called. In this case we expect the full chain to be rolled back // to the committed block. Since the ancient limit was underflown, everything // needs to be deleted onwards to avoid creating a gap. -func TestLongDeepSetHead(t *testing.T) { +func TestLongDeepSetHead(t *testing.T) { testLongDeepSetHead(t, false) } +func TestLongDeepSetHeadWithSnapshots(t *testing.T) { testLongDeepSetHead(t, true) } + +func testLongDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // @@ -753,7 +820,7 @@ func TestLongDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks where the fast @@ -763,6 +830,13 @@ func TestLongDeepSetHead(t *testing.T) { // deleted. In between the committed block and the requested head the data can // remain as "fast sync" data to avoid redownloading it. func TestLongFastSyncedShallowSetHead(t *testing.T) { + testLongFastSyncedShallowSetHead(t, false) +} +func TestLongFastSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongFastSyncedShallowSetHead(t, true) +} + +func testLongFastSyncedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // @@ -798,7 +872,7 @@ func TestLongFastSyncedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks where the fast @@ -806,7 +880,10 @@ func TestLongFastSyncedShallowSetHead(t *testing.T) { // which sethead was called. In this case we expect the full chain to be rolled // back to the committed block. Since the ancient limit was underflown, everything // needs to be deleted onwards to avoid creating a gap. -func TestLongFastSyncedDeepSetHead(t *testing.T) { +func TestLongFastSyncedDeepSetHead(t *testing.T) { testLongFastSyncedDeepSetHead(t, false) } +func TestLongFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { testLongFastSyncedDeepSetHead(t, true) } + +func testLongFastSyncedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // @@ -841,7 +918,7 @@ func TestLongFastSyncedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks where the fast @@ -850,6 +927,13 @@ func TestLongFastSyncedDeepSetHead(t *testing.T) { // syncing and delete everything from the new head, since we can just pick up fast // syncing from there. func TestLongFastSyncingShallowSetHead(t *testing.T) { + testLongFastSyncingShallowSetHead(t, false) +} +func TestLongFastSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongFastSyncingShallowSetHead(t, true) +} + +func testLongFastSyncingShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // @@ -885,7 +969,7 @@ func TestLongFastSyncingShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks where the fast @@ -894,6 +978,13 @@ func TestLongFastSyncingShallowSetHead(t *testing.T) { // syncing and delete everything from the new head, since we can just pick up fast // syncing from there. func TestLongFastSyncingDeepSetHead(t *testing.T) { + testLongFastSyncingDeepSetHead(t, false) +} +func TestLongFastSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongFastSyncingDeepSetHead(t, true) +} + +func testLongFastSyncingDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // @@ -928,7 +1019,7 @@ func TestLongFastSyncingDeepSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter side @@ -939,6 +1030,13 @@ func TestLongFastSyncingDeepSetHead(t *testing.T) { // can remain as "fast sync" data to avoid redownloading it. The side chain is nuked // by the freezer. func TestLongOldForkedShallowSetHead(t *testing.T) { + testLongOldForkedShallowSetHead(t, false) +} +func TestLongOldForkedShallowSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedShallowSetHead(t, true) +} + +func testLongOldForkedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3 @@ -975,7 +1073,7 @@ func TestLongOldForkedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter side @@ -984,7 +1082,10 @@ func TestLongOldForkedShallowSetHead(t *testing.T) { // chain to be rolled back to the committed block. Since the ancient limit was // underflown, everything needs to be deleted onwards to avoid creating a gap. The // side chain is nuked by the freezer. -func TestLongOldForkedDeepSetHead(t *testing.T) { +func TestLongOldForkedDeepSetHead(t *testing.T) { testLongOldForkedDeepSetHead(t, false) } +func TestLongOldForkedDeepSetHeadWithSnapshots(t *testing.T) { testLongOldForkedDeepSetHead(t, true) } + +func testLongOldForkedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3 @@ -1020,7 +1121,7 @@ func TestLongOldForkedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1032,6 +1133,13 @@ func TestLongOldForkedDeepSetHead(t *testing.T) { // requested head the data can remain as "fast sync" data to avoid redownloading // it. The side chain is nuked by the freezer. func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) { + testLongOldForkedFastSyncedShallowSetHead(t, false) +} +func TestLongOldForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncedShallowSetHead(t, true) +} + +func testLongOldForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3 @@ -1068,7 +1176,7 @@ func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1079,6 +1187,13 @@ func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) { // underflown, everything needs to be deleted onwards to avoid creating a gap. The // side chain is nuked by the freezer. func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) { + testLongOldForkedFastSyncedDeepSetHead(t, false) +} +func TestLongOldForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncedDeepSetHead(t, true) +} + +func testLongOldForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3 @@ -1114,7 +1229,7 @@ func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1125,6 +1240,13 @@ func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) { // just pick up fast syncing from there. The side chain is completely nuked by the // freezer. func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) { + testLongOldForkedFastSyncingShallowSetHead(t, false) +} +func TestLongOldForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncingShallowSetHead(t, true) +} + +func testLongOldForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3 @@ -1161,7 +1283,7 @@ func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1172,6 +1294,13 @@ func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) { // just pick up fast syncing from there. The side chain is completely nuked by the // freezer. func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) { + testLongOldForkedFastSyncingDeepSetHead(t, false) +} +func TestLongOldForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongOldForkedFastSyncingDeepSetHead(t, true) +} + +func testLongOldForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3 @@ -1207,7 +1336,7 @@ func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1216,6 +1345,13 @@ func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) { // chain is above the committed block. In this case the freezer will delete the // sidechain since it's dangling, reverting to TestLongShallowSetHead. func TestLongNewerForkedShallowSetHead(t *testing.T) { + testLongNewerForkedShallowSetHead(t, false) +} +func TestLongNewerForkedShallowSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedShallowSetHead(t, true) +} + +func testLongNewerForkedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1252,7 +1388,7 @@ func TestLongNewerForkedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1261,6 +1397,13 @@ func TestLongNewerForkedShallowSetHead(t *testing.T) { // chain is above the committed block. In this case the freezer will delete the // sidechain since it's dangling, reverting to TestLongDeepSetHead. func TestLongNewerForkedDeepSetHead(t *testing.T) { + testLongNewerForkedDeepSetHead(t, false) +} +func TestLongNewerForkedDeepSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedDeepSetHead(t, true) +} + +func testLongNewerForkedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1296,7 +1439,7 @@ func TestLongNewerForkedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1305,6 +1448,13 @@ func TestLongNewerForkedDeepSetHead(t *testing.T) { // the side chain is above the committed block. In this case the freezer will delete // the sidechain since it's dangling, reverting to TestLongFastSyncedShallowSetHead. func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) { + testLongNewerForkedFastSyncedShallowSetHead(t, false) +} +func TestLongNewerForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncedShallowSetHead(t, true) +} + +func testLongNewerForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1341,7 +1491,7 @@ func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1350,6 +1500,13 @@ func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) { // the side chain is above the committed block. In this case the freezer will delete // the sidechain since it's dangling, reverting to TestLongFastSyncedDeepSetHead. func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) { + testLongNewerForkedFastSyncedDeepSetHead(t, false) +} +func TestLongNewerForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncedDeepSetHead(t, true) +} + +func testLongNewerForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1385,7 +1542,7 @@ func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1394,6 +1551,13 @@ func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) { // chain is above the committed block. In this case the freezer will delete the // sidechain since it's dangling, reverting to TestLongFastSyncinghallowSetHead. func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) { + testLongNewerForkedFastSyncingShallowSetHead(t, false) +} +func TestLongNewerForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncingShallowSetHead(t, true) +} + +func testLongNewerForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1430,7 +1594,7 @@ func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a shorter @@ -1439,6 +1603,13 @@ func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) { // chain is above the committed block. In this case the freezer will delete the // sidechain since it's dangling, reverting to TestLongFastSyncingDeepSetHead. func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) { + testLongNewerForkedFastSyncingDeepSetHead(t, false) +} +func TestLongNewerForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongNewerForkedFastSyncingDeepSetHead(t, true) +} + +func testLongNewerForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12 @@ -1474,14 +1645,17 @@ func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a longer side // chain, where a recent block - newer than the ancient limit - was already committed // to disk and then sethead was called. In this case the freezer will delete the // sidechain since it's dangling, reverting to TestLongShallowSetHead. -func TestLongReorgedShallowSetHead(t *testing.T) { +func TestLongReorgedShallowSetHead(t *testing.T) { testLongReorgedShallowSetHead(t, false) } +func TestLongReorgedShallowSetHeadWithSnapshots(t *testing.T) { testLongReorgedShallowSetHead(t, true) } + +func testLongReorgedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1518,14 +1692,17 @@ func TestLongReorgedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a longer side // chain, where a recent block - older than the ancient limit - was already committed // to disk and then sethead was called. In this case the freezer will delete the // sidechain since it's dangling, reverting to TestLongDeepSetHead. -func TestLongReorgedDeepSetHead(t *testing.T) { +func TestLongReorgedDeepSetHead(t *testing.T) { testLongReorgedDeepSetHead(t, false) } +func TestLongReorgedDeepSetHeadWithSnapshots(t *testing.T) { testLongReorgedDeepSetHead(t, true) } + +func testLongReorgedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1561,7 +1738,7 @@ func TestLongReorgedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a longer @@ -1570,6 +1747,13 @@ func TestLongReorgedDeepSetHead(t *testing.T) { // freezer will delete the sidechain since it's dangling, reverting to // TestLongFastSyncedShallowSetHead. func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) { + testLongReorgedFastSyncedShallowSetHead(t, false) +} +func TestLongReorgedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) { + testLongReorgedFastSyncedShallowSetHead(t, true) +} + +func testLongReorgedFastSyncedShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1606,7 +1790,7 @@ func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a longer @@ -1615,6 +1799,13 @@ func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) { // freezer will delete the sidechain since it's dangling, reverting to // TestLongFastSyncedDeepSetHead. func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) { + testLongReorgedFastSyncedDeepSetHead(t, false) +} +func TestLongReorgedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { + testLongReorgedFastSyncedDeepSetHead(t, true) +} + +func testLongReorgedFastSyncedDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1650,7 +1841,7 @@ func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) { expHeadHeader: 4, expHeadFastBlock: 4, expHeadBlock: 4, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a longer @@ -1660,6 +1851,13 @@ func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) { // head, since we can just pick up fast syncing from there. The side chain is // completely nuked by the freezer. func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) { + testLongReorgedFastSyncingShallowSetHead(t, false) +} +func TestLongReorgedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) { + testLongReorgedFastSyncingShallowSetHead(t, true) +} + +func testLongReorgedFastSyncingShallowSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1696,7 +1894,7 @@ func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } // Tests a sethead for a long canonical chain with frozen blocks and a longer @@ -1706,6 +1904,13 @@ func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) { // head, since we can just pick up fast syncing from there. The side chain is // completely nuked by the freezer. func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) { + testLongReorgedFastSyncingDeepSetHead(t, false) +} +func TestLongReorgedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) { + testLongReorgedFastSyncingDeepSetHead(t, true) +} + +func testLongReorgedFastSyncingDeepSetHead(t *testing.T, snapshots bool) { // Chain: // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD) // └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26 @@ -1741,13 +1946,13 @@ func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) { expHeadHeader: 6, expHeadFastBlock: 6, expHeadBlock: 0, - }) + }, snapshots) } -func testSetHead(t *testing.T, tt *rewindTest) { +func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { // It's hard to follow the test case, visualize the input - //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - //fmt.Println(tt.dump(false)) + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump(false)) // Create a temporary persistent database datadir, err := ioutil.TempDir("", "") @@ -1766,8 +1971,18 @@ func testSetHead(t *testing.T, tt *rewindTest) { var ( genesis = new(Genesis).MustCommit(db) engine = ethash.NewFullFaker() + config = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, // Disable snapshot + } ) - chain, err := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if snapshots { + config.SnapshotLimit = 256 + config.SnapshotWait = true + } + chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1790,6 +2005,11 @@ func testSetHead(t *testing.T, tt *rewindTest) { } if tt.commitBlock > 0 { chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, nil) + if snapshots { + if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { + t.Fatalf("Failed to flatten snapshots: %v", err) + } + } } if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go new file mode 100644 index 0000000000..cb499593e3 --- /dev/null +++ b/core/blockchain_snapshot_test.go @@ -0,0 +1,732 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Tests that abnormal program termination (i.e.crash) and restart can recovery +// the snapshot properly if the snapshot is enabled. + +package core + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" +) + +// snapshotTest is a test case for snapshot recovery. It can be used for +// simulating these scenarios: +// (i) Geth restarts normally with valid legacy snapshot +// (ii) Geth restarts normally with valid new-format snapshot +// (iii) Geth restarts after the crash, with broken legacy snapshot +// (iv) Geth restarts after the crash, with broken new-format snapshot +// (v) Geth restarts normally, but it's requested to be rewound to a lower point via SetHead +// (vi) Geth restarts normally with a stale snapshot +type snapshotTest struct { + legacy bool // Flag whether the loaded snapshot is in legacy format + crash bool // Flag whether the Geth restarts from the previous crash + gapped int // Number of blocks to insert without enabling snapshot + setHead uint64 // Block number to set head back to + + chainBlocks int // Number of blocks to generate for the canonical chain + snapshotBlock uint64 // Block number of the relevant snapshot disk layer + commitBlock uint64 // Block number for which to commit the state to disk + + expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis) + expHeadHeader uint64 // Block number of the expected head header + expHeadFastBlock uint64 // Block number of the expected head fast sync block + expHeadBlock uint64 // Block number of the expected head full block + expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer +} + +func (tt *snapshotTest) dump() string { + buffer := new(strings.Builder) + + fmt.Fprint(buffer, "Chain:\n G") + for i := 0; i < tt.chainBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprint(buffer, " (HEAD)\n\n") + + fmt.Fprintf(buffer, "Commit: G") + if tt.commitBlock > 0 { + fmt.Fprintf(buffer, ", C%d", tt.commitBlock) + } + fmt.Fprint(buffer, "\n") + + fmt.Fprintf(buffer, "Snapshot: G") + if tt.snapshotBlock > 0 { + fmt.Fprintf(buffer, ", C%d", tt.snapshotBlock) + } + fmt.Fprint(buffer, "\n") + + if tt.crash { + fmt.Fprintf(buffer, "\nCRASH\n\n") + } else { + fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setHead) + } + fmt.Fprintf(buffer, "------------------------------\n\n") + + fmt.Fprint(buffer, "Expected in leveldb:\n G") + for i := 0; i < tt.expCanonicalBlocks; i++ { + fmt.Fprintf(buffer, "->C%d", i+1) + } + fmt.Fprintf(buffer, "\n\n") + fmt.Fprintf(buffer, "Expected head header : C%d\n", tt.expHeadHeader) + fmt.Fprintf(buffer, "Expected head fast block: C%d\n", tt.expHeadFastBlock) + if tt.expHeadBlock == 0 { + fmt.Fprintf(buffer, "Expected head block : G\n") + } else { + fmt.Fprintf(buffer, "Expected head block : C%d\n", tt.expHeadBlock) + } + if tt.expSnapshotBottom == 0 { + fmt.Fprintf(buffer, "Expected snapshot disk : G\n") + } else { + fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", tt.expSnapshotBottom) + } + return buffer.String() +} + +// Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot +// journal will be persisted correctly. In this case no snapshot recovery is +// required. +func TestRestartWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C8 + // Expected snapshot disk : G + testSnapshot(t, &snapshotTest{ + legacy: false, + crash: false, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }) +} + +// Tests a Geth restart with valid but "legacy" snapshot. Before the shutdown, +// all snapshot journal will be persisted correctly. In this case no snapshot +// recovery is required. +func TestRestartWithLegacySnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C8 + // Expected snapshot disk : G + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: false, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }) +} + +// Tests a Geth was crashed and restarts with a broken snapshot. In this case the +// chain head should be rewound to the point with available state. And also the +// new head should must be lower than disk layer. But there is no committed point +// so the chain should be rewound to genesis and the disk layer should be left +// for recovery. +func TestNoCommitCrashWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + // Expected snapshot disk : C4 + testSnapshot(t, &snapshotTest{ + legacy: false, + crash: true, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }) +} + +// Tests a Geth was crashed and restarts with a broken snapshot. In this case the +// chain head should be rewound to the point with available state. And also the +// new head should must be lower than disk layer. But there is only a low committed +// point so the chain should be rewound to committed point and the disk layer +// should be left for recovery. +func TestLowCommitCrashWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G, C2 + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C2 + // Expected snapshot disk : C4 + testSnapshot(t, &snapshotTest{ + legacy: false, + crash: true, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }) +} + +// Tests a Geth was crashed and restarts with a broken snapshot. In this case +// the chain head should be rewound to the point with available state. And also +// the new head should must be lower than disk layer. But there is only a high +// committed point so the chain should be rewound to genesis and the disk layer +// should be left for recovery. +func TestHighCommitCrashWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G, C6 + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + // Expected snapshot disk : C4 + testSnapshot(t, &snapshotTest{ + legacy: false, + crash: true, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }) +} + +// Tests a Geth was crashed and restarts with a broken and "legacy format" +// snapshot. In this case the entire legacy snapshot should be discared +// and rebuild from the new chain head. The new head here refers to the +// genesis because there is no committed point. +func TestNoCommitCrashWithLegacySnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + // Expected snapshot disk : G + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: true, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) + }) +} + +// Tests a Geth was crashed and restarts with a broken and "legacy format" +// snapshot. In this case the entire legacy snapshot should be discared +// and rebuild from the new chain head. The new head here refers to the +// block-2 because it's committed into the disk. +func TestLowCommitCrashWithLegacySnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G, C2 + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : C2 + // Expected snapshot disk : C2 + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: true, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD + }) +} + +// Tests a Geth was crashed and restarts with a broken and "legacy format" +// snapshot. In this case the entire legacy snapshot should be discared +// and rebuild from the new chain head. +// +// The new head here refers to the the genesis, the reason is: +// - the state of block-6 is committed into the disk +// - the legacy disk layer of block-4 is committed into the disk +// - the head is rewound the genesis in order to find an available +// state lower than disk layer +func TestHighCommitCrashWithLegacySnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G, C6 + // Snapshot: G, C4 + // + // CRASH + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8 + // + // Expected head header : C8 + // Expected head fast block: C8 + // Expected head block : G + // Expected snapshot disk : G + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: true, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) + }) +} + +// Tests a Geth was running with snapshot enabled. Then restarts without +// enabling snapshot and after that re-enable the snapshot again. In this +// case the snapshot should be rebuilt with latest chain head. +func TestGappedNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C10 + // Expected snapshot disk : C10 + testSnapshot(t, &snapshotTest{ + legacy: false, + crash: false, + gapped: 2, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }) +} + +// Tests a Geth was running with leagcy snapshot enabled. Then restarts +// without enabling snapshot and after that re-enable the snapshot again. +// In this case the snapshot should be rebuilt with latest chain head. +func TestGappedLegacySnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C10 + // Expected snapshot disk : C10 + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: false, + gapped: 2, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }) +} + +// Tests the Geth was running with snapshot enabled and resetHead is applied. +// In this case the head is rewound to the target(with state available). After +// that the chain is restarted and the original disk layer is kept. +func TestSetHeadWithNewSnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(4) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4 + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + // Expected snapshot disk : G + testSnapshot(t, &snapshotTest{ + legacy: false, + crash: false, + gapped: 0, + setHead: 4, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }) +} + +// Tests the Geth was running with snapshot(legacy-format) enabled and resetHead +// is applied. In this case the head is rewound to the target(with state available). +// After that the chain is restarted and the original disk layer is kept. +func TestSetHeadWithLegacySnapshot(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(4) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4 + // + // Expected head header : C4 + // Expected head fast block: C4 + // Expected head block : C4 + // Expected snapshot disk : G + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: false, + gapped: 0, + setHead: 4, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }) +} + +func testSnapshot(t *testing.T, tt *snapshotTest) { + // It's hard to follow the test case, visualize the input + //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + //fmt.Println(tt.dump()) + + // Create a temporary persistent database + datadir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Failed to create temporary datadir: %v", err) + } + os.RemoveAll(datadir) + + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + defer db.Close() // Might double close, should be fine + + // Initialize a fresh chain + var ( + genesis = new(Genesis).MustCommit(db) + engine = ethash.NewFullFaker() + gendb = rawdb.NewMemoryDatabase() + + // Snapshot is enabled, the first snapshot is created from the Genesis. + // The snapshot memory allowance is 256MB, it means no snapshot flush + // will happen during the block insertion. + cacheConfig = defaultCacheConfig + ) + chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create chain: %v", err) + } + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, tt.chainBlocks, func(i int, b *BlockGen) {}) + + // Insert the blocks with configured settings. + var breakpoints []uint64 + if tt.commitBlock > tt.snapshotBlock { + breakpoints = append(breakpoints, tt.snapshotBlock, tt.commitBlock) + } else { + breakpoints = append(breakpoints, tt.commitBlock, tt.snapshotBlock) + } + var startPoint uint64 + for _, point := range breakpoints { + if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + startPoint = point + + if tt.commitBlock > 0 && tt.commitBlock == point { + chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) + } + if tt.snapshotBlock > 0 && tt.snapshotBlock == point { + if tt.legacy { + // Here we commit the snapshot disk root to simulate + // committing the legacy snapshot. + rawdb.WriteSnapshotRoot(db, blocks[point-1].Root()) + } else { + chain.snaps.Cap(blocks[point-1].Root(), 0) + diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() + if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { + t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) + } + } + } + } + if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + // Set the flag for writing legacy journal if ncessary + if tt.legacy { + chain.writeLegacyJournal = true + } + // Pull the plug on the database, simulating a hard crash + if tt.crash { + db.Close() + + // Start a new blockchain back up and see where the repair leads us + db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + if err != nil { + t.Fatalf("Failed to reopen persistent database: %v", err) + } + defer db.Close() + + // The interesting thing is: instead of start the blockchain after + // the crash, we do restart twice here: one after the crash and one + // after the normal stop. It's used to ensure the broken snapshot + // can be detected all the time. + chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + chain.Stop() + + chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer chain.Stop() + } else if tt.gapped > 0 { + // Insert blocks without enabling snapshot if gapping is required. + chain.Stop() + + gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.gapped, func(i int, b *BlockGen) {}) + + // Insert a few more blocks without enabling snapshot + var cacheConfig = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + } + chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + chain.InsertChain(gappedBlocks) + chain.Stop() + + chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer chain.Stop() + } else if tt.setHead != 0 { + // Rewind the chain if setHead operation is required. + chain.SetHead(tt.setHead) + chain.Stop() + + chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer chain.Stop() + } else { + chain.Stop() + // Restart the chain normally + chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer chain.Stop() + } + + // Iterate over all the remaining blocks and ensure there are no gaps + verifyNoGaps(t, chain, true, blocks) + verifyCutoff(t, chain, true, blocks, tt.expCanonicalBlocks) + + if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader) + } + if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock { + t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock) + } + if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock { + t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock) + } + // Check the disk layer, ensure they are matched + block := chain.GetBlockByNumber(tt.expSnapshotBottom) + if block == nil { + t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", tt.expSnapshotBottom) + } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { + t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) + } +} diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index ecd4e65978..5bd48ad5fa 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -17,6 +17,8 @@ package rawdb import ( + "encoding/binary" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -118,3 +120,58 @@ func DeleteSnapshotJournal(db ethdb.KeyValueWriter) { log.Crit("Failed to remove snapshot journal", "err", err) } } + +// ReadSnapshotGenerator retrieves the serialized snapshot generator saved at +// the last shutdown. +func ReadSnapshotGenerator(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotGeneratorKey) + return data +} + +// WriteSnapshotGenerator stores the serialized snapshot generator to save at +// shutdown. +func WriteSnapshotGenerator(db ethdb.KeyValueWriter, generator []byte) { + if err := db.Put(snapshotGeneratorKey, generator); err != nil { + log.Crit("Failed to store snapshot generator", "err", err) + } +} + +// DeleteSnapshotGenerator deletes the serialized snapshot generator saved at +// the last shutdown +func DeleteSnapshotGenerator(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotGeneratorKey); err != nil { + log.Crit("Failed to remove snapshot generator", "err", err) + } +} + +// ReadSnapshotRecoveryNumber retrieves the block number of the last persisted +// snapshot layer. +func ReadSnapshotRecoveryNumber(db ethdb.KeyValueReader) *uint64 { + data, _ := db.Get(snapshotRecoveryKey) + if len(data) == 0 { + return nil + } + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteSnapshotRecoveryNumber stores the block number of the last persisted +// snapshot layer. +func WriteSnapshotRecoveryNumber(db ethdb.KeyValueWriter, number uint64) { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], number) + if err := db.Put(snapshotRecoveryKey, buf[:]); err != nil { + log.Crit("Failed to store snapshot recovery number", "err", err) + } +} + +// DeleteSnapshotRecoveryNumber deletes the block number of the last persisted +// snapshot layer. +func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotRecoveryKey); err != nil { + log.Crit("Failed to remove snapshot recovery number", "err", err) + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index e2b093a34a..dbc5025d5d 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -51,6 +51,12 @@ var ( // snapshotJournalKey tracks the in-memory diff layers across restarts. snapshotJournalKey = []byte("SnapshotJournal") + // snapshotGeneratorKey tracks the snapshot generation marker across restarts. + snapshotGeneratorKey = []byte("SnapshotGenerator") + + // snapshotRecoveryKey tracks the snapshot recovery marker across restarts. + snapshotRecoveryKey = []byte("SnapshotRecovery") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index 8460cd332f..40ff5ade4c 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/rlp" ) // reverse reverses the contents of a byte slice. It's used to update random accs @@ -429,6 +430,81 @@ func TestDiskPartialMerge(t *testing.T) { } } +// Tests that when the bottom-most diff layer is merged into the disk +// layer whether the corresponding generator is persisted correctly. +func TestDiskGeneratorPersistence(t *testing.T) { + var ( + accOne = randomHash() + accTwo = randomHash() + accOneSlotOne = randomHash() + accOneSlotTwo = randomHash() + + accThree = randomHash() + accThreeSlot = randomHash() + baseRoot = randomHash() + diffRoot = randomHash() + diffTwoRoot = randomHash() + genMarker = append(randomHash().Bytes(), randomHash().Bytes()...) + ) + // Testing scenario 1, the disk layer is still under the construction. + db := rawdb.NewMemoryDatabase() + + rawdb.WriteAccountSnapshot(db, accOne, accOne[:]) + rawdb.WriteStorageSnapshot(db, accOne, accOneSlotOne, accOneSlotOne[:]) + rawdb.WriteStorageSnapshot(db, accOne, accOneSlotTwo, accOneSlotTwo[:]) + rawdb.WriteSnapshotRoot(db, baseRoot) + + // Create a disk layer based on all above updates + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + genMarker: genMarker, + }, + }, + } + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffRoot, baseRoot, nil, map[common.Hash][]byte{ + accTwo: accTwo[:], + }, nil); err != nil { + t.Fatalf("failed to update snapshot tree: %v", err) + } + if err := snaps.Cap(diffRoot, 0); err != nil { + t.Fatalf("failed to flatten snapshot tree: %v", err) + } + blob := rawdb.ReadSnapshotGenerator(db) + var generator journalGenerator + if err := rlp.DecodeBytes(blob, &generator); err != nil { + t.Fatalf("Failed to decode snapshot generator %v", err) + } + if !bytes.Equal(generator.Marker, genMarker) { + t.Fatalf("Generator marker is not matched") + } + // Test senario 2, the disk layer is fully generated + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{ + accThree: accThree.Bytes(), + }, map[common.Hash]map[common.Hash][]byte{ + accThree: {accThreeSlot: accThreeSlot.Bytes()}, + }); err != nil { + t.Fatalf("failed to update snapshot tree: %v", err) + } + diskLayer := snaps.layers[snaps.diskRoot()].(*diskLayer) + diskLayer.genMarker = nil // Construction finished + if err := snaps.Cap(diffTwoRoot, 0); err != nil { + t.Fatalf("failed to flatten snapshot tree: %v", err) + } + blob = rawdb.ReadSnapshotGenerator(db) + if err := rlp.DecodeBytes(blob, &generator); err != nil { + t.Fatalf("Failed to decode snapshot generator %v", err) + } + if len(generator.Marker) != 0 { + t.Fatalf("Failed to update snapshot generator") + } +} + // Tests that merging something into a disk layer persists it into the database // and invalidates any previously written and cached values, discarding anything // after the in-progress generation marker. diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index a6b3e4420d..566f7d94a8 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -112,6 +112,7 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i genAbort: make(chan chan *generatorStats), } go base.generate(&generatorStats{wiping: wiper, start: time.Now()}) + log.Debug("Start snapshot generation", "root", root) return base } diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index fc1053f818..2821248a72 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -33,6 +33,8 @@ import ( "github.com/ethereum/go-ethereum/trie" ) +const journalVersion uint64 = 0 + // journalGenerator is a disk layer entry containing the generator progress marker. type journalGenerator struct { Wiping bool // Whether the database was in progress of being wiped @@ -61,8 +63,87 @@ type journalStorage struct { Vals [][]byte } +// loadAndParseLegacyJournal tries to parse the snapshot journal in legacy format. +func loadAndParseLegacyJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { + // Retrieve the journal, for legacy journal it must exist since even for + // 0 layer it stores whether we've already generated the snapshot or are + // in progress only. + journal := rawdb.ReadSnapshotJournal(db) + if len(journal) == 0 { + return nil, journalGenerator{}, errors.New("missing or corrupted snapshot journal") + } + r := rlp.NewStream(bytes.NewReader(journal), 0) + + // Read the snapshot generation progress for the disk layer + var generator journalGenerator + if err := r.Decode(&generator); err != nil { + return nil, journalGenerator{}, fmt.Errorf("failed to load snapshot progress marker: %v", err) + } + // Load all the snapshot diffs from the journal + snapshot, err := loadDiffLayer(base, r) + if err != nil { + return nil, generator, err + } + return snapshot, generator, nil +} + +// loadAndParseJournal tries to parse the snapshot journal in latest format. +func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { + // Retrieve the disk layer generator. It must exist, no matter the + // snapshot is fully generated or not. Otherwise the entire disk + // layer is invalid. + generatorBlob := rawdb.ReadSnapshotGenerator(db) + if len(generatorBlob) == 0 { + return nil, journalGenerator{}, errors.New("missing snapshot generator") + } + var generator journalGenerator + if err := rlp.DecodeBytes(generatorBlob, &generator); err != nil { + return nil, journalGenerator{}, fmt.Errorf("failed to decode snapshot generator: %v", err) + } + // Retrieve the diff layer journal. It's possible that the journal is + // not existent, e.g. the disk layer is generating while that the Geth + // crashes without persisting the diff journal. + // So if there is no journal, or the journal is not matched with disk + // layer, we just discard all diffs and try to recover them later. + journal := rawdb.ReadSnapshotJournal(db) + if len(journal) == 0 { + log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing") + return base, generator, nil + } + r := rlp.NewStream(bytes.NewReader(journal), 0) + + // Firstly, resolve the first element as the journal version + version, err := r.Uint() + if err != nil { + return nil, journalGenerator{}, err + } + if version != journalVersion { + return nil, journalGenerator{}, fmt.Errorf("journal version mismatch, want %d got %v", journalVersion, version) + } + // Secondly, resolve the disk layer root, ensure it's continuous + // with disk layer. + var root common.Hash + if err := r.Decode(&root); err != nil { + return nil, journalGenerator{}, errors.New("missing disk layer root") + } + // The diff journal is not matched with disk, discard them. + // It can happen that Geth crashes without persisting the latest + // diff journal. + if !bytes.Equal(root.Bytes(), base.root.Bytes()) { + log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "unmatched") + return base, generator, nil + } + // Load all the snapshot diffs from the journal + snapshot, err := loadDiffLayer(base, r) + if err != nil { + return nil, journalGenerator{}, err + } + log.Debug("Loaded snapshot journal", "diskroot", base.root, "diffhead", snapshot.Root()) + return snapshot, generator, nil +} + // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. -func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) (snapshot, error) { +func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, error) { // Retrieve the block number and hash of the snapshot, failing if no snapshot // is present in the database (or crashed mid-update). baseRoot := rawdb.ReadSnapshotRoot(diskdb) @@ -75,28 +156,36 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, cache: fastcache.New(cache * 1024 * 1024), root: baseRoot, } - // Retrieve the journal, it must exist since even for 0 layer it stores whether - // we've already generated the snapshot or are in progress only - journal := rawdb.ReadSnapshotJournal(diskdb) - if len(journal) == 0 { - return nil, errors.New("missing or corrupted snapshot journal") - } - r := rlp.NewStream(bytes.NewReader(journal), 0) - - // Read the snapshot generation progress for the disk layer - var generator journalGenerator - if err := r.Decode(&generator); err != nil { - return nil, fmt.Errorf("failed to load snapshot progress marker: %v", err) + var legacy bool + snapshot, generator, err := loadAndParseJournal(diskdb, base) + if err != nil { + log.Debug("Failed to load new-format journal", "error", err) + snapshot, generator, err = loadAndParseLegacyJournal(diskdb, base) + legacy = true } - // Load all the snapshot diffs from the journal - snapshot, err := loadDiffLayer(base, r) if err != nil { return nil, err } - // Entire snapshot journal loaded, sanity check the head and return - // Journal doesn't exist, don't worry if it's not supposed to + // Entire snapshot journal loaded, sanity check the head. If the loaded + // snapshot is not matched with current state root, print a warning log + // or discard the entire snapshot it's legacy snapshot. + // + // Possible scenario: Geth was crashed without persisting journal and then + // restart, the head is rewound to the point with available state(trie) + // which is below the snapshot. In this case the snapshot can be recovered + // by re-executing blocks but right now it's unavailable. if head := snapshot.Root(); head != root { - return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) + // If it's legacy snapshot, or it's new-format snapshot but + // it's not in recovery mode, returns the error here for + // rebuilding the entire snapshot forcibly. + if legacy || !recovery { + return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) + } + // It's in snapshot recovery, the assumption is held that + // the disk layer is always higher than chain head. It can + // be eventually recovered when the chain head beyonds the + // disk layer. + log.Warn("Snapshot is not continuous with chain", "snaproot", head, "chainroot", root) } // Everything loaded correctly, resume any suspended operations if !generator.Done { @@ -203,7 +292,9 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { if dl.stale { return common.Hash{}, ErrSnapshotStale } - // Write out the generator marker + // Write out the generator marker. Note it's a standalone disk layer generator + // which is not mixed with journal. It's ok if the generator is persisted while + // journal is not. entry := journalGenerator{ Done: dl.genMarker == nil, Marker: dl.genMarker, @@ -214,9 +305,12 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { entry.Slots = stats.slots entry.Storage = uint64(stats.storage) } - if err := rlp.Encode(buffer, entry); err != nil { + blob, err := rlp.EncodeToBytes(entry) + if err != nil { return common.Hash{}, err } + log.Debug("Journalled disk layer", "root", dl.root, "complete", dl.genMarker == nil) + rawdb.WriteSnapshotGenerator(dl.diskdb, blob) return dl.root, nil } @@ -266,5 +360,97 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { if err := rlp.Encode(buffer, storage); err != nil { return common.Hash{}, err } + log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) + return base, nil +} + +// LegacyJournal writes the persistent layer generator stats into a buffer +// to be stored in the database as the snapshot journal. +// +// Note it's the legacy version which is only used in testing right now. +func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { + // If the snapshot is currently being generated, abort it + var stats *generatorStats + if dl.genAbort != nil { + abort := make(chan *generatorStats) + dl.genAbort <- abort + + if stats = <-abort; stats != nil { + stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) + } + } + // Ensure the layer didn't get stale + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return common.Hash{}, ErrSnapshotStale + } + // Write out the generator marker + entry := journalGenerator{ + Done: dl.genMarker == nil, + Marker: dl.genMarker, + } + if stats != nil { + entry.Wiping = (stats.wiping != nil) + entry.Accounts = stats.accounts + entry.Slots = stats.slots + entry.Storage = uint64(stats.storage) + } + if err := rlp.Encode(buffer, entry); err != nil { + return common.Hash{}, err + } + return dl.root, nil +} + +// Journal writes the memory layer contents into a buffer to be stored in the +// database as the snapshot journal. +// +// Note it's the legacy version which is only used in testing right now. +func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { + // Journal the parent first + base, err := dl.parent.LegacyJournal(buffer) + if err != nil { + return common.Hash{}, err + } + // Ensure the layer didn't get stale + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.Stale() { + return common.Hash{}, ErrSnapshotStale + } + // Everything below was journalled, persist this layer too + if err := rlp.Encode(buffer, dl.root); err != nil { + return common.Hash{}, err + } + destructs := make([]journalDestruct, 0, len(dl.destructSet)) + for hash := range dl.destructSet { + destructs = append(destructs, journalDestruct{Hash: hash}) + } + if err := rlp.Encode(buffer, destructs); err != nil { + return common.Hash{}, err + } + accounts := make([]journalAccount, 0, len(dl.accountData)) + for hash, blob := range dl.accountData { + accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) + } + if err := rlp.Encode(buffer, accounts); err != nil { + return common.Hash{}, err + } + storage := make([]journalStorage, 0, len(dl.storageData)) + for hash, slots := range dl.storageData { + keys := make([]common.Hash, 0, len(slots)) + vals := make([][]byte, 0, len(slots)) + for key, val := range slots { + keys = append(keys, key) + vals = append(vals, val) + } + storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) + } + if err := rlp.Encode(buffer, storage); err != nil { + return common.Hash{}, err + } + log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) return base, nil } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 4f496b4ff6..6ad4451ea3 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -136,6 +137,10 @@ type snapshot interface { // flattening everything down (bad for reorgs). Journal(buffer *bytes.Buffer) (common.Hash, error) + // LegacyJournal is basically identical to Journal. it's the legacy version for + // flushing legacy journal. Now the only purpose of this function is for testing. + LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) + // Stale return whether this layer has become stale (was flattened across) or // if it's still live. Stale() bool @@ -168,10 +173,12 @@ type Tree struct { // store (with a number of memory layers from a journal), ensuring that the head // of the snapshot matches the expected one. // -// If the snapshot is missing or inconsistent, the entirety is deleted and will -// be reconstructed from scratch based on the tries in the key-value store, on a -// background thread. -func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool) *Tree { +// If the snapshot is missing or the disk layer is broken, the entire is deleted +// and will be reconstructed from scratch based on the tries in the key-value +// store, on a background thread. If the memory layers from the journal is not +// continuous with disk layer or the journal is missing, all diffs will be discarded +// iff it's in "recovery" mode, otherwise rebuild is mandatory. +func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, recovery bool) *Tree { // Create a new, empty snapshot tree snap := &Tree{ diskdb: diskdb, @@ -183,7 +190,7 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm defer snap.waitBuild() } // Attempt to load a previously persisted snapshot and rebuild one if failed - head, err := loadSnapshot(diskdb, triedb, cache, root) + head, err := loadSnapshot(diskdb, triedb, cache, root, recovery) if err != nil { log.Warn("Failed to load snapshot, regenerating", "err", err) snap.Rebuild(root) @@ -198,7 +205,7 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm } // waitBuild blocks until the snapshot finishes rebuilding. This method is meant -// to be used by tests to ensure we're testing what we believe we are. +// to be used by tests to ensure we're testing what we believe we are. func (t *Tree) waitBuild() { // Find the rebuild termination channel var done chan struct{} @@ -415,6 +422,9 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { // diffToDisk merges a bottom-most diff into the persistent disk layer underneath // it. The method will panic if called onto a non-bottom-most diff layer. +// +// The disk layer persistence should be operated in an atomic way. All updates should +// be discarded if the whole transition if not finished. func diffToDisk(bottom *diffLayer) *diskLayer { var ( base = bottom.parent.(*diskLayer) @@ -427,8 +437,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { base.genAbort <- abort stats = <-abort } - // Start by temporarily deleting the current snapshot block marker. This - // ensures that in the case of a crash, the entire snapshot is invalidated. + // Put the deletion in the batch writer, flush all updates in the final step. rawdb.DeleteSnapshotRoot(batch) // Mark the original base as stale as we're going to create a new wrapper @@ -471,12 +480,6 @@ func diffToDisk(bottom *diffLayer) *diskLayer { base.cache.Set(hash[:], data) snapshotCleanAccountWriteMeter.Mark(int64(len(data))) - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write account snapshot", "err", err) - } - batch.Reset() - } snapshotFlushAccountItemMeter.Mark(1) snapshotFlushAccountSizeMeter.Mark(int64(len(data))) } @@ -505,18 +508,33 @@ func diffToDisk(bottom *diffLayer) *diskLayer { snapshotFlushStorageItemMeter.Mark(1) snapshotFlushStorageSizeMeter.Mark(int64(len(data))) } - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write storage snapshot", "err", err) - } - batch.Reset() - } } // Update the snapshot block marker and write any remainder data rawdb.WriteSnapshotRoot(batch, bottom.root) + + // Write out the generator marker + entry := journalGenerator{ + Done: base.genMarker == nil, + Marker: base.genMarker, + } + if stats != nil { + entry.Wiping = (stats.wiping != nil) + entry.Accounts = stats.accounts + entry.Slots = stats.slots + entry.Storage = uint64(stats.storage) + } + blob, err := rlp.EncodeToBytes(entry) + if err != nil { + panic(fmt.Sprintf("Failed to RLP encode generator %v", err)) + } + rawdb.WriteSnapshotGenerator(batch, blob) + + // Flush all the updates in the single db operation. Ensure the + // disk layer transition is atomic. if err := batch.Write(); err != nil { log.Crit("Failed to write leftover snapshot", "err", err) } + log.Debug("Journalled disk layer", "root", bottom.root, "complete", base.genMarker == nil) res := &diskLayer{ root: bottom.root, cache: base.cache, @@ -554,7 +572,21 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) { t.lock.Lock() defer t.lock.Unlock() + // Firstly write out the metadata of journal journal := new(bytes.Buffer) + if err := rlp.Encode(journal, journalVersion); err != nil { + return common.Hash{}, err + } + diskroot := t.diskRoot() + if diskroot == (common.Hash{}) { + return common.Hash{}, errors.New("invalid disk root") + } + // Secondly write out the disk layer root, ensure the + // diff journal is continuous with disk. + if err := rlp.Encode(journal, diskroot); err != nil { + return common.Hash{}, err + } + // Finally write out the journal of each layer in reverse order. base, err := snap.(snapshot).Journal(journal) if err != nil { return common.Hash{}, err @@ -564,6 +596,29 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) { return base, nil } +// LegacyJournal is basically identical to Journal. it's the legacy +// version for flushing legacy journal. Now the only purpose of this +// function is for testing. +func (t *Tree) LegacyJournal(root common.Hash) (common.Hash, error) { + // Retrieve the head snapshot to journal from var snap snapshot + snap := t.Snapshot(root) + if snap == nil { + return common.Hash{}, fmt.Errorf("snapshot [%#x] missing", root) + } + // Run the journaling + t.lock.Lock() + defer t.lock.Unlock() + + journal := new(bytes.Buffer) + base, err := snap.(snapshot).LegacyJournal(journal) + if err != nil { + return common.Hash{}, err + } + // Store the journal into the database and return + rawdb.WriteSnapshotJournal(t.diskdb, journal.Bytes()) + return base, nil +} + // Rebuild wipes all available snapshot data from the persistent database and // discard all caches and diff layers. Afterwards, it starts a new snapshot // generator with the given root hash. @@ -571,6 +626,10 @@ func (t *Tree) Rebuild(root common.Hash) { t.lock.Lock() defer t.lock.Unlock() + // Firstly delete any recovery flag in the database. Because now we are + // building a brand new snapshot. + rawdb.DeleteSnapshotRecoveryNumber(t.diskdb) + // Track whether there's a wipe currently running and keep it alive if so var wiper chan struct{} @@ -657,6 +716,16 @@ func (t *Tree) disklayer() *diskLayer { } } +// diskRoot is a internal helper function to return the disk layer root. +// The lock of snapTree is assumed to be held already. +func (t *Tree) diskRoot() common.Hash { + disklayer := t.disklayer() + if disklayer == nil { + return common.Hash{} + } + return disklayer.Root() +} + // generating is an internal helper function which reports whether the snapshot // is still under the construction. func (t *Tree) generating() (bool, error) { @@ -671,3 +740,11 @@ func (t *Tree) generating() (bool, error) { defer layer.lock.RUnlock() return layer.genMarker != nil, nil } + +// diskRoot is a external helper function to return the disk layer root. +func (t *Tree) DiskRoot() common.Hash { + t.lock.Lock() + defer t.lock.Unlock() + + return t.diskRoot() +} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 238d204745..28a5313129 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -235,7 +235,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo var snaps *snapshot.Tree if snapshotter { - snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false) + snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false, false) } statedb, _ = state.New(root, sdb, snaps) return snaps, statedb From b63bffe8202d46ea10ac8c4f441c582642193ac8 Mon Sep 17 00:00:00 2001 From: gary rong Date: Sat, 31 Oct 2020 01:04:38 +0800 Subject: [PATCH 111/948] les, p2p/simulations/adapters: fix issues found while simulating les (#21761) This adds a few tiny fixes for les and the p2p simulation framework: LES Parts - Keep the LES-SERVER connection even it's non-synced We had this idea to reject the connections in LES protocol if the les-server itself is not synced. However, in LES protocol we will also receive the connection from another les-server. In this case even the local node is not synced yet, we should keep the tcp connection for other protocols(e.g. eth protocol). - Don't count "invalid message" for non-existing GetBlockHeadersMsg request In the eth syncing mechanism (full sync, fast sync, light sync), it will try to fetch some non-existent blocks or headers(to ensure we indeed download all the missing chain). In this case, it's possible that the les-server will receive the request for non-existent headers. So don't count it as the "invalid message" for scheduling dropping. - Copy the announce object in the closure Before the les-server pushes the latest headers to all connected clients, it will create a closure and queue it in the underlying request scheduler. In some scenarios it's problematic. E.g, in private networks, the block can be mined very fast. So before the first closure is executed, we may already update the latest_announce object. So actually the "announce" object we want to send is replaced. The downsize is the client will receive two announces with the same td and then drop the server. P2P Simulation Framework - Don't double register the protocol services in p2p-simulation "Start". The protocols upon the devp2p are registered in the "New node stage". So don't reigster them again when starting a node in the p2p simulation framework - Add one more new config field "ExternalSigner", in order to use clef service in the framework. --- les/server_handler.go | 30 +++++++++++++++++++----------- p2p/simulations/adapters/inproc.go | 6 +++--- p2p/simulations/adapters/types.go | 3 +++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/les/server_handler.go b/les/server_handler.go index c657d37f1d..d3e2c956b3 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -126,11 +126,7 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } - // Reject light clients if server is not synced. - if !h.synced() { - p.Log().Debug("Light server not synced, rejecting peer") - return p2p.DiscRequested - } + // Reject the duplicated peer, otherwise register it to peerset. var registered bool if err := h.server.ns.Operation(func() { if h.server.ns.GetField(p.Node(), clientPeerField) != nil { @@ -156,7 +152,14 @@ func (h *serverHandler) handle(p *clientPeer) error { _, err := p.rw.ReadMsg() return err } - + // Reject light clients if server is not synced. + // + // Put this checking here, so that "non-synced" les-server peers are still allowed + // to keep the connection. + if !h.synced() { + p.Log().Debug("Light server not synced, rejecting peer") + return p2p.DiscRequested + } // Disconnect the inbound peer if it's rejected by clientPool if cap, err := h.server.clientPool.connect(p); cap != p.fcParams.MinRecharge || err != nil { p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool) @@ -348,7 +351,6 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { origin = h.blockchain.GetHeaderByNumber(query.Origin.Number) } if origin == nil { - p.bumpInvalid() break } headers = append(headers, origin) @@ -1006,18 +1008,24 @@ func (b *broadcaster) sendTo(node *enode.Node) { } if p, _ := b.ns.GetField(node, clientPeerField).(*clientPeer); p != nil { if p.headInfo.Td == nil || b.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 { + announce := b.lastAnnounce switch p.announceType { case announceTypeSimple: - if !p.queueSend(func() { p.sendAnnounce(b.lastAnnounce) }) { - log.Debug("Drop announcement because queue is full", "number", b.lastAnnounce.Number, "hash", b.lastAnnounce.Hash) + if !p.queueSend(func() { p.sendAnnounce(announce) }) { + log.Debug("Drop announcement because queue is full", "number", announce.Number, "hash", announce.Hash) + } else { + log.Debug("Sent announcement", "number", announce.Number, "hash", announce.Hash) } case announceTypeSigned: if b.signedAnnounce.Hash != b.lastAnnounce.Hash { b.signedAnnounce = b.lastAnnounce b.signedAnnounce.sign(b.privateKey) } - if !p.queueSend(func() { p.sendAnnounce(b.signedAnnounce) }) { - log.Debug("Drop announcement because queue is full", "number", b.lastAnnounce.Number, "hash", b.lastAnnounce.Hash) + announce := b.signedAnnounce + if !p.queueSend(func() { p.sendAnnounce(announce) }) { + log.Debug("Drop announcement because queue is full", "number", announce.Number, "hash", announce.Hash) + } else { + log.Debug("Sent announcement", "number", announce.Number, "hash", announce.Hash) } } p.headInfo = blockInfo{b.lastAnnounce.Hash, b.lastAnnounce.Number, b.lastAnnounce.Td} diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index fd10da4319..4fc7abc06a 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -99,8 +99,9 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { Dialer: s, EnableMsgEvents: config.EnableMsgEvents, }, - NoUSB: true, - Logger: log.New("node.id", id.String()), + ExternalSigner: config.ExternalSigner, + NoUSB: true, + Logger: log.New("node.id", id.String()), }) if err != nil { return nil, err @@ -263,7 +264,6 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { continue } sn.running[name] = service - sn.node.RegisterLifecycle(service) } }) if regErr != nil { diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 716cde6a6c..b6af370089 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -107,6 +107,9 @@ type NodeConfig struct { // These values need to be checked and acted upon by node Services Properties []string + // ExternalSigner specifies an external URI for a clef-type signer + ExternalSigner string + // Enode node *enode.Node From 3eebf340386dee2f6a008664bf8a1e82842a053e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 4 Nov 2020 11:20:39 +0100 Subject: [PATCH 112/948] common: remove ToHex and ToHexArray (#21610) ToHex was deprecated a couple years ago. The last remaining use was in ToHexArray, which itself only had a single call site. This just moves ToHexArray near its only remaining call site and implements it using hexutil.Encode. This changes the default behaviour of ToHexArray and with it the behaviour of eth_getProof. Previously we encoded an empty slice as 0, now the empty slice is encoded as 0x. --- common/bytes.go | 25 +++---------------------- internal/ethapi/api.go | 13 +++++++++++-- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/common/bytes.go b/common/bytes.go index 634041804d..7827bb572e 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -17,28 +17,9 @@ // Package common contains various helper functions. package common -import "encoding/hex" - -// ToHex returns the hex representation of b, prefixed with '0x'. -// For empty slices, the return value is "0x0". -// -// Deprecated: use hexutil.Encode instead. -func ToHex(b []byte) string { - hex := Bytes2Hex(b) - if len(hex) == 0 { - hex = "0" - } - return "0x" + hex -} - -// ToHexArray creates a array of hex-string based on []byte -func ToHexArray(b [][]byte) []string { - r := make([]string, len(b)) - for i := range b { - r[i] = ToHex(b[i]) - } - return r -} +import ( + "encoding/hex" +) // FromHex returns the bytes represented by the hexadecimal string s. // s may be prefixed with "0x". diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c7d1e0020f..0d6ace9b5b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -599,7 +599,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre if storageError != nil { return nil, storageError } - storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), common.ToHexArray(proof)} + storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), toHexSlice(proof)} } else { storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}} } @@ -613,7 +613,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre return &AccountResult{ Address: address, - AccountProof: common.ToHexArray(accountProof), + AccountProof: toHexSlice(accountProof), Balance: (*hexutil.Big)(state.GetBalance(address)), CodeHash: codeHash, Nonce: hexutil.Uint64(state.GetNonce(address)), @@ -1943,3 +1943,12 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { } return nil } + +// toHexSlice creates a slice of hex-strings based on []byte. +func toHexSlice(b [][]byte) []string { + r := make([]string, len(b)) + for i := range b { + r[i] = hexutil.Encode(b[i]) + } + return r +} From e6402677c2bbcc6c26fd1c401f95f9a79bf65ea9 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 4 Nov 2020 19:41:46 +0800 Subject: [PATCH 113/948] core/state/snapshot: fix journal recovery from generating old journal (#21775) * core/state/snapshot: print warning if failed to resolve journal * core/state/snapshot: fix snapshot recovery When we meet the snapshot journal consisted with: - disk layer generator with new-format - diff layer journal with old-format The base layer should be returned without error. The broken diff layer can be reconstructed later but we definitely don't want to reconstruct the huge diff layer. * core: add tests --- core/blockchain_snapshot_test.go | 77 +++++++++++++++++++++++++++++--- core/state/snapshot/journal.go | 16 ++++--- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index cb499593e3..5a986bf52c 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -43,10 +43,11 @@ import ( // (v) Geth restarts normally, but it's requested to be rewound to a lower point via SetHead // (vi) Geth restarts normally with a stale snapshot type snapshotTest struct { - legacy bool // Flag whether the loaded snapshot is in legacy format - crash bool // Flag whether the Geth restarts from the previous crash - gapped int // Number of blocks to insert without enabling snapshot - setHead uint64 // Block number to set head back to + legacy bool // Flag whether the loaded snapshot is in legacy format + crash bool // Flag whether the Geth restarts from the previous crash + restartCrash int // Number of blocks to insert after the normal stop, then the crash happens + gapped int // Number of blocks to insert without enabling snapshot + setHead uint64 // Block number to set head back to chainBlocks int // Number of blocks to generate for the canonical chain snapshotBlock uint64 // Block number of the relevant snapshot disk layer @@ -565,10 +566,50 @@ func TestSetHeadWithLegacySnapshot(t *testing.T) { }) } +// Tests the Geth was running with snapshot(legacy-format) enabled and upgrades +// the disk layer journal(journal generator) to latest format. After that the Geth +// is restarted from a crash. In this case Geth will find the new-format disk layer +// journal but with legacy-format diff journal(the new-format is never committed), +// and the invalid diff journal is expected to be dropped. +func TestRecoverSnapshotFromCrashWithLegacyDiffJournal(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C8 + // Expected snapshot disk : C10 + testSnapshot(t, &snapshotTest{ + legacy: true, + crash: false, + restartCrash: 2, + gapped: 0, + setHead: 0, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 8, // The persisted state in the first running + expSnapshotBottom: 10, // The persisted disk layer in the second running + }) +} + func testSnapshot(t *testing.T, tt *snapshotTest) { // It's hard to follow the test case, visualize the input - //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - //fmt.Println(tt.dump()) + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) // Create a temporary persistent database datadir, err := ioutil.TempDir("", "") @@ -694,6 +735,30 @@ func testSnapshot(t *testing.T, tt *snapshotTest) { chain.SetHead(tt.setHead) chain.Stop() + chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer chain.Stop() + } else if tt.restartCrash != 0 { + // Firstly, stop the chain properly, with all snapshot journal + // and state committed. + chain.Stop() + + // Restart chain, forcibly flush the disk layer journal with new format + newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.restartCrash, func(i int, b *BlockGen) {}) + chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + chain.InsertChain(newBlocks) + chain.Snapshot().Cap(newBlocks[len(newBlocks)-1].Root(), 0) + + // Simulate the blockchain crash + // Don't call chain.Stop here, so that no snapshot + // journal and latest state will be committed + + // Restart the chain after the crash chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 2821248a72..3fbecdefdc 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -103,8 +103,9 @@ func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, jou // Retrieve the diff layer journal. It's possible that the journal is // not existent, e.g. the disk layer is generating while that the Geth // crashes without persisting the diff journal. - // So if there is no journal, or the journal is not matched with disk - // layer, we just discard all diffs and try to recover them later. + // So if there is no journal, or the journal is invalid(e.g. the journal + // is not matched with disk layer; or the it's the legacy-format journal, + // etc.), we just discard all diffs and try to recover them later. journal := rawdb.ReadSnapshotJournal(db) if len(journal) == 0 { log.Warn("Loaded snapshot journal", "diskroot", base.root, "diffs", "missing") @@ -115,13 +116,16 @@ func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, jou // Firstly, resolve the first element as the journal version version, err := r.Uint() if err != nil { - return nil, journalGenerator{}, err + log.Warn("Failed to resolve the journal version", "error", err) + return base, generator, nil } if version != journalVersion { - return nil, journalGenerator{}, fmt.Errorf("journal version mismatch, want %d got %v", journalVersion, version) + log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version) + return base, generator, nil } // Secondly, resolve the disk layer root, ensure it's continuous - // with disk layer. + // with disk layer. Note now we can ensure it's the snapshot journal + // correct version, so we expect everything can be resolved properly. var root common.Hash if err := r.Decode(&root); err != nil { return nil, journalGenerator{}, errors.New("missing disk layer root") @@ -159,7 +163,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, var legacy bool snapshot, generator, err := loadAndParseJournal(diskdb, base) if err != nil { - log.Debug("Failed to load new-format journal", "error", err) + log.Warn("Failed to load new-format journal", "error", err) snapshot, generator, err = loadAndParseLegacyJournal(diskdb, base) legacy = true } From 5d20fbbb6fb90f06bd5592f2c926f21dc8aa6fa2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 4 Nov 2020 15:02:58 +0100 Subject: [PATCH 114/948] cmd/devp2p, internal/utesting: implement TAP output (#21760) TAP is a text format for test results. Parsers for it are available in many languages, making it easy to consume. I want TAP output from our protocol tests because the Hive wrapper around them needs to know about the test names and their individual results and logs. It would also be possible to just write this info as JSON, but I don't want to invent a new format. This also improves the normal console output for tests (when running without --tap). It now prints -- RUN lines before any output from the test, and indents the log output by one space. --- cmd/devp2p/discv4cmd.go | 38 ++----- cmd/devp2p/discv5cmd.go | 28 ++--- cmd/devp2p/rlpxcmd.go | 24 ++--- cmd/devp2p/runtest.go | 69 ++++++++++++ internal/utesting/utesting.go | 167 ++++++++++++++++++++++++++--- internal/utesting/utesting_test.go | 84 +++++++++++++++ 6 files changed, 329 insertions(+), 81 deletions(-) create mode 100644 cmd/devp2p/runtest.go diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 467c20deb5..3b6dc09a1c 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -19,14 +19,12 @@ package main import ( "fmt" "net" - "os" "strings" "time" "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" @@ -82,7 +80,13 @@ var ( Name: "test", Usage: "Runs tests against a node", Action: discv4Test, - Flags: []cli.Flag{remoteEnodeFlag, testPatternFlag, testListen1Flag, testListen2Flag}, + Flags: []cli.Flag{ + remoteEnodeFlag, + testPatternFlag, + testTAPFlag, + testListen1Flag, + testListen2Flag, + }, } ) @@ -113,20 +117,6 @@ var ( Usage: "Enode of the remote node under test", EnvVar: "REMOTE_ENODE", } - testPatternFlag = cli.StringFlag{ - Name: "run", - Usage: "Pattern of test suite(s) to run", - } - testListen1Flag = cli.StringFlag{ - Name: "listen1", - Usage: "IP address of the first tester", - Value: v4test.Listen1, - } - testListen2Flag = cli.StringFlag{ - Name: "listen2", - Usage: "IP address of the second tester", - Value: v4test.Listen2, - } ) func discv4Ping(ctx *cli.Context) error { @@ -213,6 +203,7 @@ func discv4Crawl(ctx *cli.Context) error { return nil } +// discv4Test runs the protocol test suite. func discv4Test(ctx *cli.Context) error { // Configure test package globals. if !ctx.IsSet(remoteEnodeFlag.Name) { @@ -221,18 +212,7 @@ func discv4Test(ctx *cli.Context) error { v4test.Remote = ctx.String(remoteEnodeFlag.Name) v4test.Listen1 = ctx.String(testListen1Flag.Name) v4test.Listen2 = ctx.String(testListen2Flag.Name) - - // Filter and run test cases. - tests := v4test.AllTests - if ctx.IsSet(testPatternFlag.Name) { - tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) - } - results := utesting.RunTests(tests, os.Stdout) - if fails := utesting.CountFailures(results); fails > 0 { - return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests)) - } - fmt.Printf("%v/%v passed\n", len(tests), len(tests)) - return nil + return runTests(ctx, v4test.AllTests) } // startV4 starts an ephemeral discovery V4 node. diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index 1d7442144f..e20d7c9cfa 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -18,13 +18,10 @@ package main import ( "fmt" - "os" "time" "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" "gopkg.in/urfave/cli.v1" ) @@ -62,7 +59,12 @@ var ( Name: "test", Usage: "Runs protocol tests against a node", Action: discv5Test, - Flags: []cli.Flag{testPatternFlag, testListen1Flag, testListen2Flag}, + Flags: []cli.Flag{ + testPatternFlag, + testTAPFlag, + testListen1Flag, + testListen2Flag, + }, } discv5ListenCommand = cli.Command{ Name: "listen", @@ -114,28 +116,14 @@ func discv5Crawl(ctx *cli.Context) error { return nil } +// discv5Test runs the protocol test suite. func discv5Test(ctx *cli.Context) error { - // Disable logging unless explicitly enabled. - if !ctx.GlobalIsSet("verbosity") && !ctx.GlobalIsSet("vmodule") { - log.Root().SetHandler(log.DiscardHandler()) - } - - // Filter and run test cases. suite := &v5test.Suite{ Dest: getNodeArg(ctx), Listen1: ctx.String(testListen1Flag.Name), Listen2: ctx.String(testListen2Flag.Name), } - tests := suite.AllTests() - if ctx.IsSet(testPatternFlag.Name) { - tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) - } - results := utesting.RunTests(tests, os.Stdout) - if fails := utesting.CountFailures(results); fails > 0 { - return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests)) - } - fmt.Printf("%v/%v passed\n", len(tests), len(tests)) - return nil + return runTests(ctx, suite.AllTests()) } func discv5Listen(ctx *cli.Context) error { diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index 17019aee00..d90eb4687c 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -19,11 +19,9 @@ package main import ( "fmt" "net" - "os" "github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" @@ -47,9 +45,12 @@ var ( rlpxEthTestCommand = cli.Command{ Name: "eth-test", Usage: "Runs tests against a node", - ArgsUsage: " ", + ArgsUsage: " ", Action: rlpxEthTest, - Flags: []cli.Flag{testPatternFlag}, + Flags: []cli.Flag{ + testPatternFlag, + testTAPFlag, + }, } ) @@ -88,22 +89,11 @@ func rlpxPing(ctx *cli.Context) error { return nil } +// rlpxEthTest runs the eth protocol test suite. func rlpxEthTest(ctx *cli.Context) error { if ctx.NArg() < 3 { exit("missing path to chain.rlp as command-line argument") } - suite := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) - - // Filter and run test cases. - tests := suite.AllTests() - if ctx.IsSet(testPatternFlag.Name) { - tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) - } - results := utesting.RunTests(tests, os.Stdout) - if fails := utesting.CountFailures(results); fails > 0 { - return fmt.Errorf("%v of %v tests passed.", len(tests)-fails, len(tests)) - } - fmt.Printf("all tests passed\n") - return nil + return runTests(ctx, suite.AllTests()) } diff --git a/cmd/devp2p/runtest.go b/cmd/devp2p/runtest.go new file mode 100644 index 0000000000..4168f8555b --- /dev/null +++ b/cmd/devp2p/runtest.go @@ -0,0 +1,69 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "os" + + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/log" + "gopkg.in/urfave/cli.v1" +) + +var ( + testPatternFlag = cli.StringFlag{ + Name: "run", + Usage: "Pattern of test suite(s) to run", + } + testTAPFlag = cli.BoolFlag{ + Name: "tap", + Usage: "Output TAP", + } + // These two are specific to the discovery tests. + testListen1Flag = cli.StringFlag{ + Name: "listen1", + Usage: "IP address of the first tester", + Value: v4test.Listen1, + } + testListen2Flag = cli.StringFlag{ + Name: "listen2", + Usage: "IP address of the second tester", + Value: v4test.Listen2, + } +) + +func runTests(ctx *cli.Context, tests []utesting.Test) error { + // Filter test cases. + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + // Disable logging unless explicitly enabled. + if !ctx.GlobalIsSet("verbosity") && !ctx.GlobalIsSet("vmodule") { + log.Root().SetHandler(log.DiscardHandler()) + } + // Run the tests. + var run = utesting.RunTests + if ctx.Bool(testTAPFlag.Name) { + run = utesting.RunTAP + } + results := run(tests, os.Stdout) + if utesting.CountFailures(results) > 0 { + os.Exit(1) + } + return nil +} diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go index 4de0ecf99a..ef05a90e4c 100644 --- a/internal/utesting/utesting.go +++ b/internal/utesting/utesting.go @@ -25,6 +25,7 @@ import ( "bytes" "fmt" "io" + "io/ioutil" "regexp" "runtime" "sync" @@ -63,32 +64,165 @@ func MatchTests(tests []Test, expr string) []Test { // RunTests executes all given tests in order and returns their results. // If the report writer is non-nil, a test report is written to it in real time. func RunTests(tests []Test, report io.Writer) []Result { - results := make([]Result, len(tests)) + if report == nil { + report = ioutil.Discard + } + results := run(tests, newConsoleOutput(report)) + fails := CountFailures(results) + fmt.Fprintf(report, "%v/%v tests passed.\n", len(tests)-fails, len(tests)) + return results +} + +// RunTAP runs the given tests and writes Test Anything Protocol output +// to the report writer. +func RunTAP(tests []Test, report io.Writer) []Result { + return run(tests, newTAP(report, len(tests))) +} + +func run(tests []Test, output testOutput) []Result { + var results = make([]Result, len(tests)) for i, test := range tests { - var output io.Writer buffer := new(bytes.Buffer) - output = buffer - if report != nil { - output = io.MultiWriter(buffer, report) - } + logOutput := io.MultiWriter(buffer, output) + + output.testStart(test.Name) start := time.Now() results[i].Name = test.Name - results[i].Failed = run(test, output) + results[i].Failed = runTest(test, logOutput) results[i].Duration = time.Since(start) results[i].Output = buffer.String() - if report != nil { - printResult(results[i], report) - } + output.testResult(results[i]) } return results } -func printResult(r Result, w io.Writer) { +// testOutput is implemented by output formats. +type testOutput interface { + testStart(name string) + Write([]byte) (int, error) + testResult(Result) +} + +// consoleOutput prints test results similarly to go test. +type consoleOutput struct { + out io.Writer + indented *indentWriter + curTest string + wroteHeader bool +} + +func newConsoleOutput(w io.Writer) *consoleOutput { + return &consoleOutput{ + out: w, + indented: newIndentWriter(" ", w), + } +} + +// testStart signals the start of a new test. +func (c *consoleOutput) testStart(name string) { + c.curTest = name + c.wroteHeader = false +} + +// Write handles test log output. +func (c *consoleOutput) Write(b []byte) (int, error) { + if !c.wroteHeader { + // This is the first output line from the test. Print a "-- RUN" header. + fmt.Fprintln(c.out, "-- RUN", c.curTest) + c.wroteHeader = true + } + return c.indented.Write(b) +} + +// testResult prints the final test result line. +func (c *consoleOutput) testResult(r Result) { + c.indented.flush() pd := r.Duration.Truncate(100 * time.Microsecond) if r.Failed { - fmt.Fprintf(w, "-- FAIL %s (%v)\n", r.Name, pd) + fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd) } else { - fmt.Fprintf(w, "-- OK %s (%v)\n", r.Name, pd) + fmt.Fprintf(c.out, "-- OK %s (%v)\n", r.Name, pd) + } +} + +// tapOutput produces Test Anything Protocol v13 output. +type tapOutput struct { + out io.Writer + indented *indentWriter + counter int +} + +func newTAP(out io.Writer, numTests int) *tapOutput { + fmt.Fprintf(out, "1..%d\n", numTests) + return &tapOutput{ + out: out, + indented: newIndentWriter("# ", out), + } +} + +func (t *tapOutput) testStart(name string) { + t.counter++ +} + +// Write does nothing for TAP because there is no real-time output of test logs. +func (t *tapOutput) Write(b []byte) (int, error) { + return len(b), nil +} + +func (t *tapOutput) testResult(r Result) { + status := "ok" + if r.Failed { + status = "not ok" + } + fmt.Fprintln(t.out, status, t.counter, r.Name) + t.indented.Write([]byte(r.Output)) + t.indented.flush() +} + +// indentWriter indents all written text. +type indentWriter struct { + out io.Writer + indent string + inLine bool +} + +func newIndentWriter(indent string, out io.Writer) *indentWriter { + return &indentWriter{out: out, indent: indent} +} + +func (w *indentWriter) Write(b []byte) (n int, err error) { + for len(b) > 0 { + if !w.inLine { + if _, err = io.WriteString(w.out, w.indent); err != nil { + return n, err + } + w.inLine = true + } + + end := bytes.IndexByte(b, '\n') + if end == -1 { + nn, err := w.out.Write(b) + n += nn + return n, err + } + + line := b[:end+1] + nn, err := w.out.Write(line) + n += nn + if err != nil { + return n, err + } + b = b[end+1:] + w.inLine = false + } + return n, err +} + +// flush ensures the current line is terminated. +func (w *indentWriter) flush() { + if w.inLine { + fmt.Println(w.out) + w.inLine = false } } @@ -106,11 +240,11 @@ func CountFailures(rr []Result) int { // Run executes a single test. func Run(test Test) (bool, string) { output := new(bytes.Buffer) - failed := run(test, output) + failed := runTest(test, output) return failed, output.String() } -func run(test Test, output io.Writer) bool { +func runTest(test Test, output io.Writer) bool { t := &T{output: output} done := make(chan struct{}) go func() { @@ -137,6 +271,9 @@ type T struct { output io.Writer } +// Helper exists for compatibility with testing.T. +func (t *T) Helper() {} + // FailNow marks the test as having failed and stops its execution by calling // runtime.Goexit (which then runs all deferred calls in the current goroutine). func (t *T) FailNow() { diff --git a/internal/utesting/utesting_test.go b/internal/utesting/utesting_test.go index 1403a5c8f7..31c7911c52 100644 --- a/internal/utesting/utesting_test.go +++ b/internal/utesting/utesting_test.go @@ -17,6 +17,8 @@ package utesting import ( + "bytes" + "regexp" "strings" "testing" ) @@ -53,3 +55,85 @@ func TestTest(t *testing.T) { t.Fatalf("wrong result for panicking test: %#v", results[2]) } } + +var outputTests = []Test{ + { + Name: "TestWithLogs", + Fn: func(t *T) { + t.Log("output line 1") + t.Log("output line 2\noutput line 3") + }, + }, + { + Name: "TestNoLogs", + Fn: func(t *T) {}, + }, + { + Name: "FailWithLogs", + Fn: func(t *T) { + t.Log("output line 1") + t.Error("failed 1") + }, + }, + { + Name: "FailMessage", + Fn: func(t *T) { + t.Error("failed 2") + }, + }, + { + Name: "FailNoOutput", + Fn: func(t *T) { + t.Fail() + }, + }, +} + +func TestOutput(t *testing.T) { + var buf bytes.Buffer + RunTests(outputTests, &buf) + + want := regexp.MustCompile(` +^-- RUN TestWithLogs + output line 1 + output line 2 + output line 3 +-- OK TestWithLogs \([^)]+\) +-- OK TestNoLogs \([^)]+\) +-- RUN FailWithLogs + output line 1 + failed 1 +-- FAIL FailWithLogs \([^)]+\) +-- RUN FailMessage + failed 2 +-- FAIL FailMessage \([^)]+\) +-- FAIL FailNoOutput \([^)]+\) +2/5 tests passed. +$`[1:]) + if !want.MatchString(buf.String()) { + t.Fatalf("output does not match: %q", buf.String()) + } +} + +func TestOutputTAP(t *testing.T) { + var buf bytes.Buffer + RunTAP(outputTests, &buf) + + want := ` +1..5 +ok 1 TestWithLogs +# output line 1 +# output line 2 +# output line 3 +ok 2 TestNoLogs +not ok 3 FailWithLogs +# output line 1 +# failed 1 +not ok 4 FailMessage +# failed 2 +not ok 5 FailNoOutput +` + if buf.String() != want[1:] { + t.Fatalf("output does not match: %q", buf.String()) + } +} From 36bb7ac083c31d57519c6e2877588fee01339c9b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 4 Nov 2020 17:36:56 +0100 Subject: [PATCH 115/948] cmd/devp2p/internal/ethtest: add correct chain files and improve test output (#21782) This PR replaces the old test genesis.json and chain.rlp files in the testdata directory for the eth protocol test suite, and also adds documentation for running the eth test suite locally. It also improves the test output text and adds more timeouts. Co-authored-by: Felix Lange --- cmd/devp2p/README.md | 19 ++++++ cmd/devp2p/internal/ethtest/chain_test.go | 2 +- cmd/devp2p/internal/ethtest/suite.go | 55 ++++++++++++------ .../internal/ethtest/testdata/chain.rlp.gz | Bin 244888 -> 0 bytes .../ethtest/testdata/fullchain.rlp.gz | Bin 0 -> 253004 bytes .../ethtest/testdata/halfchain.rlp.gz | Bin 0 -> 126219 bytes cmd/devp2p/internal/ethtest/types.go | 37 +++++++----- 7 files changed, 78 insertions(+), 35 deletions(-) delete mode 100644 cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index 2763c75085..e1372d0158 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -81,6 +81,25 @@ Now get the ENR of your node and store it in the `NODE` environment variable. Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`. +### Eth Protocol Test Suite + +The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth]. + +To run the eth protocol test suite against your implementation, the node needs to be initialized as such: + +1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory +2. import the `halfchain.rlp` file in the `testdata` directory +3. run geth with the following flags: +``` +geth --datadir --nodiscover --nat=none --networkid 19763 --verbosity 5 +``` + +Then, run the following command, replacing `` with the enode of the geth node: + ``` + devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/fullchain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json +``` + +[eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md [dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup [discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md [discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index c8b977d237..604b908687 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -73,7 +73,7 @@ func TestEthProtocolNegotiation(t *testing.T) { // TestChain_GetHeaders tests whether the test suite can correctly // respond to a GetBlockHeaders request from a node. func TestChain_GetHeaders(t *testing.T) { - chainFile, err := filepath.Abs("./testdata/chain.rlp.gz") + chainFile, err := filepath.Abs("./testdata/fullchain.rlp.gz") if err != nil { t.Fatal(err) } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index f70bc43efa..d5928bede4 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -19,7 +19,9 @@ package ethtest import ( "fmt" "net" + "time" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p/enode" @@ -27,6 +29,13 @@ import ( "github.com/stretchr/testify/assert" ) +var pretty = spew.ConfigState{ + Indent: " ", + DisableCapacities: true, + DisablePointerAddresses: true, + SortKeys: true, +} + // Suite represents a structure used to test the eth // protocol of a node(s). type Suite struct { @@ -73,9 +82,9 @@ func (s *Suite) TestStatus(t *utesting.T) { // get status switch msg := conn.statusExchange(t, s.chain).(type) { case *Status: - t.Logf("%+v\n", msg) + t.Logf("got status message: %s", pretty.Sdump(msg)) default: - t.Fatalf("unexpected: %#v", msg) + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } } @@ -104,16 +113,17 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := conn.ReadAndServe(s.chain).(type) { + timeout := 20 * time.Second + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockHeaders: headers := msg for _, header := range *headers { num := header.Number.Uint64() + t.Logf("received header (%d): %s", num, pretty.Sdump(header)) assert.Equal(t, s.chain.blocks[int(num)].Header(), header) - t.Logf("\nHEADER FOR BLOCK NUMBER %d: %+v\n", header.Number, header) } default: - t.Fatalf("unexpected: %#v", msg) + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } } @@ -133,14 +143,12 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := conn.ReadAndServe(s.chain).(type) { + timeout := 20 * time.Second + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockBodies: - bodies := msg - for _, body := range *bodies { - t.Logf("\nBODY: %+v\n", body) - } + t.Logf("received %d block bodies", len(*msg)) default: - t.Fatalf("unexpected: %#v", msg) + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } } @@ -173,18 +181,27 @@ func (s *Suite) TestBroadcast(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - switch msg := receiveConn.ReadAndServe(s.chain).(type) { + timeout := 20 * time.Second + switch msg := receiveConn.ReadAndServe(s.chain, timeout).(type) { case *NewBlock: - assert.Equal(t, blockAnnouncement.Block.Header(), msg.Block.Header(), - "wrong block header in announcement") - assert.Equal(t, blockAnnouncement.TD, msg.TD, - "wrong TD in announcement") + t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) + assert.Equal(t, + blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement", + ) + assert.Equal(t, + blockAnnouncement.TD, msg.TD, + "wrong TD in announcement", + ) case *NewBlockHashes: hashes := *msg - assert.Equal(t, blockAnnouncement.Block.Hash(), hashes[0].Hash, - "wrong block hash in announcement") + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(hashes)) + assert.Equal(t, + blockAnnouncement.Block.Hash(), hashes[0].Hash, + "wrong block hash in announcement", + ) default: - t.Fatalf("unexpected: %#v", msg) + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } // update test suite chain s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000]) diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz deleted file mode 100644 index 957e53bc0fe248e8184d29eb7a4148930910dbe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244888 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%0DQPfc(Nr6})hxron;V!7Wago9>e+9(FDK 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%0A=2~WpuGugl5aamXG&;H(Zcgqky0AVHp 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�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 vRbxF$}z(5r;IbW`2%<=F-02(({tz(z>~9; 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%0Dj$HI(9Y=d)JOOCPf6 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..cd5206ca0bcb972dc9008f0e48e5151812b61f17 GIT binary patch literal 65856 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 z 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 TaqXYT@b4mY)ukV;{^|b#ra#C& literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..c205117a4680801cf91db502c153a154d402be49 GIT binary patch literal 1377268 zcmaI6Wl$wN5G9HY?rwuS49>-2aCdiicXuD$oeK=^?(Pik7k77ec;9ZldR6;&Yg0)l zf70his*{{PttbNt1rPQ=+EcWv(*IoizXk=25X{ca)`Hc{)6vAi+0o5~Nlg_F4ASws z)!=^&H%|mGaHtbVFfb_7|F$Up=Lqw^8TNlV>PhUQd`18R%SQ$SL|(iy#K!E{pI~V{hBuu zf&lgQjlyR!CHTjor}iP&-c|_ak{*rZoN3rF`w!wM8pf-~QqYf%-oYQEZ4O=6 zzM)T8@+tRSH zYY&o*;}@oZHiIr$M5*Gg__P{yg;hnBL3yjM@dn1W7Saa>c0DQQGRp&%-Fjlr>%8Aw z=<~;Ze37IwrK_Xf-ZHkv&9%4#Z>YZ&Am2-}r7hJ%<1$iO)UhZYc1u61_2)L;uCU`N zE=~Y);526$axCe+%V0Vo?aP@i^eVk5GWSPHnZELUE}*sqBSTF zR|*X#94fa%$ggRqV|3g8J7V9r__j20%XKdN$~x?Qp4OlFQ4AwIhVe(Max)fG4?%t% zOHsH(PhzFZ3pAabb7%GWdMQP7cJIV1jMRd{DHuxAcSwodA2f-nJAV^?ESjRvVq{y~ z`0yP!4oCY>Z;A{$#_dav+PqgmG3`Q zEoR5KGK-vS|9#Tprwmq26qf%o_`B!|?Sr=VzY}IrAAENhx5t7H#ujhkjYVn^h(=Z` z2l0KMU9U5Ojs}$xZRKJ6A;bs7=Cu!K4J+P*a=+a6)IY3#Y8H8tX}x4pW?3_O>^oZZ z97hhs_T8uhBEA{9eE*bwE69ab`OP_%N1c2XR4D@auli`GAD?G_E}7xshBI?dmuTIX z0Vo#So*l^%=ENgMw%y%pVied@@sJx3)PpBP&p1JQx>zH^x{~(a;~J(vfaA*GZSio$gAJS>CkrB zg%CO)ue`+e%IX?CDp&f-}9)a5p=<8H2p0;tQw&2TW& zCw&YaepY6zm;)vN8+vDoW5iH)|1_VK%D|BDH|Bl5E66NE~B(Co_@*E z;0Yu!)(q5?7a?-h*5%~uK!|_1AXNugGK;G}=z#Xl8Y*8HjAQugx;bP{o;lzj7YjBr z`G#N;S+s8-SGh@1e3;>1Hm3oNvEBY^w!~qbZ&6-Is4kaIX&>p6b#zoCng#8R>zV^u z>4)yVv%M3hLFPj-1A{?s+L8zxa0vyoACzZ{$uWE!)bjWpg-AtoBinobWy3rg{^G;H zO-RP~c%5NlF3AAF5ttEQta8J2g`~p%RAA}ny&P{PSY;X*&=NVt&gW^#_HK6_1a_t} zLtjkr%uU5Jgr!v_<{8{HbsLp>g`9JtgTG{i$SS~c8{Q%MfUK|uKf0cnk)i?kob+|~ zG!axRG8O8rB<2T6*xQL_JdZKDXSm9Xl#3Yh`7*h__EfGq>e7mIFGcn&xHO!DbLLYk zBipWvlbp|ZOZqLfGnGERQoZ{yU$Nm3_vcB65CSq-RO$LwZxpdS<^%o}uAo?dAWY~1y@&qP4J?e1c;>c7h)Tb#A;JRy|Ep zoJE;4*<~{qn5NG3zpHO`NBrhsorKLv8&qiQv+T84tTpoQ>1Gv?H9UQ%n_(_6>UclI z$l3F^yp_4@;Lm)iHu(yDNCl4#l-=RwkAJc8>==F$hfFB#}ZWK21~1WDG$9AH|R z<#Gq6oP5F8p|_uVnTU>)XPYUS5XS$&!rfw9zM(`l`tGKYk1pR~bZmhPO!wP7iO1G)0KYFN# zke$<@Ved{>uyx`YQA7D~emwPC&Id&PUaz0Z(L&8yxql+^p@;$<6Hlg@y3Q7qmX&{1 zfc(+RO_8nR@9(-y;nxVB^#bhrUWK}X>$Y+_13mGtCTwf2`d}UKV`{bV$JL?)mWZXS zUO0nYJ)CU4)o7}qABL9o4I3MQ+UpR>KhYm3uyRuK_tZu`3^p_T6ofsqGu&!sJJw@L zJkmzBD@Gs>r33T&Umy$|VyhC$<0~Wsl`oqzaABmbeNM8u;MD_`*lx0)l{@rzIP!Gj z4c=cVJan>vl+~4?taOY8awyNr)?GfMtry%rzMxJwF2;4Qv0Z{X1q$F+h<2fR?TU0% z!505XZhU`*nLsCYug_u6^1nDJyW@!3|bnXtr6FGbZ&NEGG0 zEbn)An>UvD|BTHR@7{$V%0T9)hOZO4w(_sLXXbU&D&6O`9n$>MkMku^Kg~*Y>_BT9 z)oI;S2uR=`&~+$y#%3u9l<02T8Y3)C#Dc8&c1Em<-R7~+g`1T%61Cmp&=JHqsZukQeM2D;8UJLLXyhoCp@MLh> zd6JO)Dr=(_Hb>`fZ~%72Zu>pbBhGH<@PPmeUR^QjkF}mFXj=Mzm-9_>JinBa1n$~BDS(epM7@--8t)ZhgG^`$(3kd9t8hap3z{zthomP24?sl4f#Lt z3=TF94s#YRBUTP$b5mv(HVzY0W)oI3HdX*TCyOyVJ1Yl(iy6Sq_J89UCWgO%*vi4V zPc|4>mqwP<^wf9ks0Qsx;e7%g)plnTd?zzNl+5gyt3;ion4bC34` z-Uz{7D@VPA$J@_e1M&m>@9y-@zaG08-zp5Iy=JUR9Bev{(|g*!uU0E1cr+LsK>6p# zosDGAQ|%E~vHivRZZ$nB88f)&{!=KvYsIF5Hg8FgOA?}=?yHRtzUZD--8(Ogu`y)s zOEf()&qu?d3K*Y8jdFEPdqB>SQG-vkoi%du4|;y+P2cGifbu)CJhJZl!BE$X_cMh( zfelaUG5r!_3;!lnM}YYwLT0RN?ehkmZP&-Bvj1R&zUyZppmMS1It@rT^Tx}qO#U$t z1v+w&K;Ls1#qytuua5*^g96_7$6xQW(yE<3eTIXmVF$lX;NsnM{$wn4^|)G zX`EJ-cD@jy5Z{`*Uf<$aYRAx7_xAA(x=o{D@+Zku}W39c5!K88&L*$pQ||9?h2Z(9Ycp|74tcNZR0+YJ)^00?p_}bz3t|;>yn9Vxx3j;VX6ubm> z972){?lTZ{&=PHAX7#Vw)b%sCp3H|1TM}yAu1V(4J*|fvasyVtE!%m}6tG>2PQ#)r zQr6#ZL+e($;k9alpWS|NK9Olrb|0CnpEDnJK`Olarl=`#-J@FlPgpumFrWxVeo0%p3q? zE)FA(|653zfy{nmZm+=H6q}@LI~NOebpF>>QpBZCuM(~lwY8P86+1dR2~LrBB|d?z zGgm#|zSmo97DpR+U_0O6vsZ{7xRk$sT!%oEUA~GrT6NGedz!J{yzr4d)**$LkvYSr zEF}y*ZVL0%7(gyF7;%A7Oss8*_v}x!E_P+FmNAirJutadc}o+sMc&4fJxoOEvYom#i-S03p+r_@GsRydCz*R{Y>#e%rrrEOGK+d+G2z9C=# zr)$kVJ2#gBbPQHZ!-O>`Wl)h<={oW<)3PaEPL6?VOq)s}N_VOXn@SR|v^uthSy;rk zS1T`VF)yG1B0HW`;kz+-u^&xowziKTDfKP(xBi&~@}fi>V74N2#p=S{)&W|rE>vEq z%*0IBhzfj}yfoe9dYmwLQiqw53p@c<-5r5eGa1?zdh~BG0xTz_7eD=wIbn*eYhmQ zh@lz3h0Z<5js*fNc5Tki>os$qSrYk1zmB4hr+nYOS6ci0R;rYS+qf77>&uftp6Y)#OF^N%`nkveHZ=Dn*UBESi_fxf8h%(hhV$aDpwK!e|7c|!+# zr({N;!R2!5FHIqdVrc6(3P_V1z8dZIdw+0Ryh%5FWnz&riJ3g#B`s0=&kMIJe&YcQ zWK%d%&uUo_xh~J@yS8ECHM2j2?!x_>ooA{kQ_LdG0(B^sDb)5k{R<@o+;=Q8{k6W& zr9r-umI2&f5Umn9%NZDPeqGB)9JI}hFMa7fQSy&@lPqrhkw z47O=njPn8_b03&XE-9n6tD{;|=~&YhpE7cPbYwxFDWW5^|9&5DOtc+Kl|Vdv@N*?j zIsO! zvDrr4N&0)yVZ9cBVDSWD zg5Ry+Tm;+JH!cb6-Gxc=Y}n&Tlc-qt88&P=+p!a=2gH^l<}P6&yiTNZmhRs@_t2nh zyi^dG1a0nd0mndJ9fua!h~EHnvR8C2dD&sr1DQ_Ih+XTw#24MW{u+)nQ#KFC7ZJ&H zIgh1m*+OcnxdXNm2QG73{m1M}?{5;AU_jha>q^I2Aaz5HJBO)113DE(+iMtSy1A-^ z0LyGw7VWnt8XG_1MBX8_oVFm)H+n7}b28{sh$hQsMp*7t%ieQTOa=NpktHam>PL+n z&tq1@w;vSm)V&{Cg8;C>oeI}ylV(0iXAtL-RRX}Nl{m(av0B&_eBO)tU((|1JWZV< zHV$6i)Zsbyi5W?#Z`^S5nMS0mQdMe6J~_uMkn+AITgrN?^Y&@>8jXe@6#F_573o)Y zY7{$RpTvTIVn**!Y9T_y47>ZaofqH_mfN{d%SxuC5cdwUtMdu1iRs->s}d!j^KB0Z zpX1d*-T`x(vDN@qZN_j`Nzkq&6$UFmfdebIpB4}kt0|~pg5A~U7OA;zj-Dw*urKN` z)4g9E#dOab2o!!Q81YNpiEPfRdfHuzC?wp%D;JEA_M?8L?UCP-&AP$lRxV-(AIw^O z?L_(hYMCZMQ%2MvHsge{RX4-RG@SpJyqR9QNu1Yo*to%8ESp)1r5YCt{FQu{qaQZL zPxgxdH}_(SlH8fkpMzhw+HLZ-fwv4_`$O3O%kUA~_NSP-rTV)ubas55hg3szir)Z@ zjhuTZIl)RiRq1g57G@dVzdr3ZqwmDV8MJTcedrBgjY8$0%#&c};Kvd%W<493P?J6X z{xWEDBr@U}f4Lw$UTPmAI|OiZHzX!1m*36TEOhtZ2tm5p^}iBf73o#qR@*fl+RPaw zfDtLBqds?8GJAcJK`Lp1}zifh;VD{97VyW@c``kWAcpHV04ey&C~ zCv>U)T%RvtV#2S3fb;!)9u)djb3OXn>yy;9PA1N*a4FcYCp|Z0UYDIdP-J zdlD$={K2^JU677K$VQRB$s?<8*PpmsLp3|HW5}HKtB$UOe2@rUV9HeUqtBpgwxMFzslogs#;JLhF?0f7;+c=7+;xF2VczAf zT$rR^iRXfB-(NH(VZ+;ksict8H)#BS=T6jHn}W-Q<0aZ{Z;dSt;tMcybU=Nqw7ER2 z8#*SV{Ue-ZTF6?^FAXi}fD!TC0RH)_y#2G*J=@&y>>0^uK6svWWH(s|7U__a`T7!F0J_c3?FrGjO=))xX%VX zuWFW&SD}hv{p=Q2Q!n}#TX1gye~%-;rP{b2(+gz)NpQxv~nDD2u7H3Ncow5Omo^i`|oead-MsR+=|I| z!m>w!<2_3B=l$+tBEmn5<)tZt01h7!btG@lg2Ibh_I7^=D*NJ)t3t1mRQe=BYK%boT=E# zof?$---V13+g=9O_9t7S?mW-NRPp=^0>5-L(9PagN+Qryk||rqf5^L`6_~{uKiHuk z&w#eB;3kAxwQv;7cR4G&hAMCcHdyN25EiW>APehWVRebDf>(Q~uK*5-%ENpA4t(9_ zktsg}+3J!v>}e;p*{lz6VcB0YhW+qLz{HFs`@GTH?Nz~GEY~@YY#s{%=+xdERSWOd?Gbu+phW#x3-&4+2gZo4xmVulIlO&QsfNx}3z_LLpILY$ zm3jLjFeLyb`5T?!nxc6sfhfGJ)z7so(0TqNL&x2y2PcQRpumH-7AsC`lxWqb*-;D* zPU!he4c6Je_XM%r8jJf!&|z_YZrDGLW#Rh}HyJ3GzV7u-oNGP0P#CS}ls@X&EguU_ zg)Z=Z^<>GSeIp>(bWQG4s;w%Xlp5RagxIR9O#9^vQ|G~|@$1eFDtGS_ESl>Fmi?#t z@Lm>N3i&J(1jItG$?e~-`gBGQYrFO_GWq6a+j+^ywg50gv0HuR*WD z!3M&gk{ROq=;2J}Cl%G*S+zEcA^*H zBbL~RdLv(%`AP_Aow1~s*;JP_o%Qw=)m5y{ww5rxQ$;w9cqlnxD8602*c!^)yYr=z zVA1zSuGg(No&55#kdBV~cZp3yr6TxwR@@w*F<+_J{pWjaO|J}-&tnfdf9RuOYMfk& zV$6m)R#1s6xX0<^u6Vt=lC$KR+hZ!pI?G~g!|us$c%7&FDdQ0)DcdE`SnT>y$iOkM#8nhd!a%SP(SaEEe~-n5`6 zRWiZVDd%47$!uiIfXnd;i3lG;?x~=ql-1rUwB!SqwRVE|80Axi(fzG*gHQG!RQ3Mx z=&Y~Oi@h&?>+kS@huf=w1Z6Ws9Q%RONbfS`CG#XFTT#|!)Y;&3;Ot-p#GuUw%s7c0 z)@yk>7WnM+L*BwBm`xD|BIq*}W9+8y{n(4|PxDDjytd?bI1(mdJ6sVeC@z zK+K{dH{x%3T5!^h=U;myRtD3q?aCijxyROT$qxuCdEVaq!p>Q}`bWxWy0`YL)n48A zuB-)s6LG?%2bnq|oIi@zb*#7}D6@m`$p_c*u2j*oxGpP3>~fekjBDQHMkNOvF_%X~ zA8yd8tj*9rYXtnFP_RXRu$KQNOqD??`B-;vu8K`Xm*}kK%A&*CCbRn1<0Rqe{E;dt zYa|le%CWP&tz`8>D5}tfiwA9d1+5KVml2P78eh`dzZB+Ub=K%H+EVH8eaUeYun~J? zaAG51Ipf|4Dlp!8h}@(yF0Z)=X&^>mb}JmbKCe}vc*Y>(DoX!_)C{aH1d*>2P^B2Gha<3 zEX(Wcd}0-2YvhOK2$@@A$&HF;f$Gn{YiFK#>>Nl}zCq{3wb5s3E6TYpHc(iRgUZw@ zcA(Ff?wZL5;WEH&=Z=$TKQNNEz1BeLsQ&MhVKRj_+=|_vpKt)N(E1j?CES#gqobUFA zd`zD&&1hHexW;i1p6V9P5di6cJD{U2)3PFZEE(`*bGdo-Wr*4NjE|0y%_oGL*hu>< zHGTfCVAZHnkvE`4F_2~o=WtU6>Z%up<%Y`8%U$>zm!Kj3$F0fk+7$+gM-riY|-|T8|!QZZ`mR9>x?~C`_CkH z&|`jkRn>fK={sP^EHLSUK6(2%1Qo5vD@DQ-{jQ+KvlJGo{8NqpI(8ew-t_TWNw`}e zydatqe-l$!xMJ%p&EL$7bSy zpt}7(6b3e(A9zXSZqI3LNN z>aTkpGtPuGXsN>@P+KsrW3q={F&R1RrR~0}>uS4bO$>nO8VXJ#mWk;(Ka}ZJNA#FU$G@7s1yb$5azd$!olex2pnGPJat*Z0VM^{6i!&%4G z4C~y;N1_gM^07XxNhD+XAy#%EyI!S9ksNhY-*3PL&d2L;cB3vP%pvw6wA7o(CV>B9n6`j2XRk0ej=&kKhLL`fKq|$0^Z3#W2v_AFc&0*>g$6& zO_YfoPo18IExjrWWJ)(G;d=*nIoYa4=dN_vU%6MhuBDYi`dM;@filhPRG6{SUX?5~ zej#I(x%o8zh}qxqgjH+GnHdjY&+B{H^(pWKv56#~IK`Ih^De8aR3*Sly?#n8q&IN$ z*;jLhLWIRY-dMG_31lMj756QfRQKmXuiTBm) z4swMyUB467vnR?7NMR!*yv2ZuW|&-mm2{PFkwDb#Aku&FTBI)>@E+T-N}GM&lbQW9 zh2-V#JA(r(13$1``m?;IF(41_&2i3_sv|pK(i@9BoLI^y#%u?cI;DYSRXHDMyi8l3gP|Ql4OW1l zaB&3o1Bfo)Yn;m)LW`#xK%O)g8FpPrEHO@TFv`H~IRY%6*DqEG!J!ZQ>)b82;s!f1 ztL?I%R&7<1Khu9)Q%6(@bd34ab&${Qey8jmn3a8K;FyZvdyjirNPzFc7nW=WNAlr< zW3}O84yW(`WsR%rR~-jgom_b(#k*2A^zIO-O~h5&mz1@#d{lrBkCsRd<*v)4TsmDA z0+k9O8e|AHGu7nA=#Z{Lk#qgE(Uscs$bM)Y5SEmp1q(L1X2v4<4f(%{ZbV$O01p)~ zus-Miqe5WHVs6H6Zfs^^ZpO{Z%4}?AX2fh_%KBfi9}C-ml7ShQ85g%Hw;9*}O(8hU z-8a!wz!)`Im&acR;Qy_+TbJ5rZ%G~zH(7U2W^b|E_xjv;r~bJ6VmsbgnP!E$Os?(=R@=n^ zh8!$8rz05Mx)Kb+U+E23Y-#GPgD;=j5^k1;u&&L}b5jyKJ@nC5!xrDw*NT^n(uEx} z>9%#TDimQ>CEfU0tQWTDy`aKDkmSq2lqHna>sJ*RkDFUqB3#xq?4^wQLgw5LekX~I zGrBxMc{q;->Q6Y81j68pIyvBBq;S=34(VL=&?Cezw(Xb9NeXb~MB%`QSYJ&NHg5$< zY~MN;@904!lCi?jKt<%zMS%J6%_3pw%n6`Px0m2j?p6DO`_Os5?$7#&-k?zxl zv%iTzE3=@P_whvhBSC*j!~p7mdCm5+r={SbSGmOn+eR2IeHJmsqgAon$0Av1z&dWB z+_Cqt_Ae7YCtNbkjp3`Wa1r)KP(GsV_A;kT+h+mVE%nPijmG-bRe-zVz$khJ`2NjWg?UTkFGHYyT0%@Qe;7 z%a98`22e1^R+!f2h^ui(Lu^!kt)-c+>0ENZIT4#}YGQlK4+e0SQA2YFAo-r{UBpZQ zhSCucLVX+#Kb|G#`wth!Zn0PJXvNE@1zDxA+3&yTb#lj@>1_RLF@+m50ZpbrjeoQ; zc6kwEESO`{rCDkfzd+$EoAlqH@eP_NGP{2E2^N74F!$pFp;kP~m1SU?nOGBbOT6b~ z2m^}qOytPCURboF0ygC09wd8zSP~19_zii%NOfolP{NAEUZuTZFTJEvzNz zx!2Ea*$eg+U!9+H{V)N-j_vQftNy)uYolUe5dY3t#2Yq#-hP&2u>e(%-JuK*4f5|` zaOFa-nANRcRp_BgD1+T8Or7Q$!klqqXY8c;tO-4v1~;`X1A9!CM%4&)MVhB6m$?#@ z_jBJr1qtTb)leeJPO%tqRoFXv&(dKo7@5CjCkVg=m{%D2c7 zMzPd@AX)4@ndQx39aOiN0VNQP_<<7vbO4Y{MWl!m5Y;l(21W4j*0J)rWp`cQ7lgs^N z;{+e(3^}`gv%fW-?)Gl`oEF($xb^3brZY+TsF=vc`-u%1oA$i!U~iM#jAVFREWO8n zjBX?Vx1lFKQgZudOek2hFE)+=4t2A{R_APPGsCRgy_KWyeWs2#@&s|9}e<9-Ce5_)O;JHeSv62y!MU&N@8uzppJ&1J60|n9x`_qbZrI z>kS#y-I*5+OsU)1#q6P97z7CsqVk>RWn?nCra0RM6zbA| zwdf<;X%?BbWIQR9DMX#z{o36<{&GcKL-=T0+kAPjgI$@k@kG7NV6v*Z=-QZ+^b8Q> zx@t&CdIw8ynuwBhv*>n8Xj|f(&7uS7VBewLAm^Lz!U{As{~Sf-h|%ajnqB?K=t%!_ zN9~>me@4u;0=y|+=O<77&b#u1?M*G&8kQUIqqRbrOMlN5Z<@uM4I&TStc}1b_an+< zFV1Xd8B$>O4cXf@@2#_j3O_Y-Dw?2#>_}sb?KBh!6;8uH}OVDl3mjXfW zxfR2XkuwgYe^PX&mP!s z{&YK?wW~uq49;Ppq8B-4DCcZ+tOaFy-8DWA#zb$gV$KS{%T9)vt2$iLpjC0~CQTuU zW8}00>){_P#!V~(@b7IP9a4)HR`;nSdv%{~8`u>6!ANW3l>6vkBwFZFJHviH-bj$q zJ}=C#C?d1`sX%jmsZg1lZ?+JSUz2BYP?hQD%zwli8YO{#CUQ&WQX`8+Vy$lqNIN)9 z?$Er-T`NN?X6!mq{zIgozfvj1Ap>Ib@VtgNd3EwI81HYBM{|4^_g?80gr|(zO3x-f zNW8*YGVgjn3rWM4quhSY5g1gV<+jbIj<`ba`JV9i@rk%z?3CW7x=PuEb3am*F5shz z`6|xV0kb5w2LHu2nYtkX_Y8RJvoJzWc^GwBBcM9-Bj@_iQu|c(Qu^oj9T$IJW?OB= z@CW8sLV(IT?6gH987g5B@75CuW-!Fs&0}d>j0A_t4P#p`;Bn@bzt6^G1ilmU;?|C| zCg^3VAzycK{|4SzW3R(XFLmeNr``1m0ZTOmgj5Z|ofN>OHQeHDq?BeTr ziS)B+7+AWf<7tsOzC!4`Dv_|#9;_VE+90fwm`Vh^>P+2-q3AR@@# z^pDkQ(mph3$Wu=5zyuz@`k%46A<vNbJ=BW$<%fLpOEHTLjt31;GNBpUyOzBxGozR%&IFeOp$>qq$S;D0NzpNQ^Z zOhng0rH|$>)<^qfymn^j;on!@6wC3=P6KGQV|~81qGA)72o)L%A1 z)ul_omwIE_iD zJj`&(^;c_jv!A;!hj!lww4#YE(i%93)3g!1b3s*VO=~~K$Y;e@CcfNJ3Z~4qG@W(j zz&I)VzwF)nOs)|`C_eI-s0PdOaeMT6euIWvMoVAbc}e1XL{C*+NTxt})1%i5Z|0r5 zLmhG>O-nJe;P%0ypqB}B7sI!=?fseeRpGN;u{qb5`=yw@&4O4Bqs(Xz3dqlyiNG@M;=;;8g7REqRGo8Uo<_gPwXcI2fmksOdTG&r`5F!^8(9!TK(nE$ zZ#TkZf$A8utykFkS3-Bdm>ic@kTCASsYlPNE~bFW2dr8Uw;>m{&~Ym~L`0&3nKvv24>eV1BtETzo`^SU`--|Z?sR;g>C_pMIttP-@3 z=jyVc6tlV|de;IkE1k`#M&qKtV@3Za(NC0TG7R**!UIQew=z$G2w<$9X=vfF_J{|y z^)5vw;2a63E(*k5>nQiHx0)TGxi?rhfFy(lmAorHSvG3ItTKPjKR$oQGB5FoCD^$Fh3SEd>g|6lroc7V^ za1|%yEY@otm)%n=%kCaK@0LM0`^wj5n|2=7Htd;muvQl_uj_6o_ zi^2{i6p8q*enN37%nuc7PssXiybFn}& z0?5$$G;vmgc!L~T(oC5^FJ`a+M9 z&v>W{lJ@0|uhqYpz-h_wz^{V(Q(m~K;_&dP{wVFcPv36M=$ryE_%)M1#=5&84wR7N z50qPbF8$#I4~LG_hScMIJipcQot;OQqh7IG@~vNRs%!rJd5_C4=e}s!OXnj&iO=D( zI+HK|9w7+{3dXjhVWB1lrZc{<{YqB>{$Lti{R*~BK0}gA!;$T6-fboOgoGE<)M9V1m?XvhUTY( zpTwBqFBqb>L$c|pf)iPop$S*vvA-sAarEj)lFxFa;s)W=M!D+z4#NpJnU<&*1X zz{F3G*pctxmA))+CP&BT*Xf=M-K%r=dbr^3+JTQH^Me-R#UP`@Uo_2c`fh-;E@URF z#IpDpqpYFxyU<4TJl~cX%v9942^asAD{y-7XV0=iwj};l{ktQ=<|TK_mIlIyYRI?P za>L{P{2$r-%cf!K7IuUvvfWl4J=9V3L3Vw|0~ZwP^@%`SF-Rc-^r#WY_PQ0TT1CXp zZKPA9g=8^x{3*PRn#FkkdUF7@q4KbV6ljJfK`CWDY8s&wu)tes50wi1XEm~j%hC0O z|9u2s-HYIr5ux*eA-tajzV>A(fUo zt$2nQZF1Igm04!mqjF5f*w_@Uj?;84;GvVKYE3E|*reAphZWqTxEALxWDVXbVf=*Q z8$BVM75Ed0(N1M?XWBk~Exy6Qga28`&bx&9sZVL^`_suHJDXBg{omdx3r$jTlY zE&l_VzY8sHPmHC>Jj?ml@D4n_sGBNiOfvr5!I>rF$7-R0;*s&W(zN2vh?bGqh6)}9 zN&EpqWlJJnw+Yjb@30Gqk`oW^RRiWTQWTTzU5l=hnZM3w=@^~vuTfi<>$LtC_3Q@> zx8men;oqhE_ErVn?v?*=W8*U7I%`BoOSI^BO^7%J--Jw9O6r<|URZf@h~Ru2(_Uty z{Ur3(k7%0XB_zgome==GK=}D;*8%2F7lp_N#A^?egej=@qFZPnLPnXl(Yv}|OC&f> z^M^}EO6%rgg$V`8y2TYze|BoG=9IzfvsZ7aQ zfBuZodSW570Oj)t`7lU_=`OIt!Ik-krb7Ch3O0z;yhKiASb{!N(#f>8QyebiPC4T; zRtA}&#}|}RH9uW3d4d+^X!Fz%#|9@5ioe8T;w`}Ol#l`f(&^hnZQ8~3t{c11RHNbJ3& z^WQgn5Bvsuk9JQ^X_N5*5+9On$zi=4jTXk%ef4E*M`dqPMJUSmqaXG=CSb@&38QE& zGKhF3oYu;TGg<+t#$+j}A@fWq?R@(NdLNKFXby}HMI8iKVu6Pf3AlL*ONe78f7{OB zw2hdY%Z}a|@C}1pv_ef1zopqEI|reskhsH7ZHvgV8uAF(B1K7KIo{L%yA9JR`#z3x zIZ)5!Uu9oaqaSHH10#L@b??EDwbL}i!yc)oNJmX11J}J&)0Y018Y{mH3_kz z8&PO`n<+^!vDVLBfV$e*^q8MD!4Xjg8C87X7ixtY@@9>UG{H(7~eHE#g76y7-79rQ0r(- zMm6s`XW*zv<$^ii!gsTt16%#cw}%ubf(sjC^s+>o1%sD2oICH!GGb}8C7p^Q|GG$Z z?&@~hJJrYR#=`WnY{)PEe&~a5^iPKA1-+$rF&j4Ws5$ul9Vl!)z6L(nprn;T2jlwb zMrOOsQ)GoU5E{|bk6v&mRM4~9xPlrjXwdNU$NLr16nNb{lI$LbNWTZ|L32o@p2!@$ zfy$>cxaUH-(k+Ta{s(5F6$(W60;Ek8hK~>rs>r9fQi_AT(%_8e>q8||(&%C+X@HMWC>$pd7vBaah_QL31(3z;~f@qiXp$1$|6%;cc zl14UEewJtInyJJ|GRzVX&f(Z*S&h=}FOxM38dDEb{$oi4Y7?rv`LhSG}^#uZIHN9*3FIVp4&`G_X5ZuWgdxS%$-yoFiW8 zQ&(QEE$wDH&vXax1^7e~^RKN##p>lxA$O7)lI{9t*%)vhL2wnm2^p1=eU3S@^=3Rt zylxN16^jiLgpcxF_pgY${1rrU%{-Bu;Bd9vPZFH{x66zJ$=Owp&WrPhvo5(On- z7&9CWWAZ8xHmx7TiePedGHzIHb^(0S>ZKn|jy2N<^Al@#l`6X*iadED%HUXMHFOdE zDY;woUUr8@5EaCRoE;yTO&tc@-rGimf?lXYklX5$+YLxH1@{9>=EU+H5 zmWEe7W5=8o?Y0Tyr=_k6?i)4vL?_IFG%&fdUR3mFk(F5U@-R*D6N}PspWrMor&1H1 z&Iv+x%Y+bOq@5pX42fs{;0Vf=c{J(&4`1iloCyo>M-O>AS5Jh3&gZ95a&wr$(C zZQFM8M0?KHQ@i$G=*t&qeUABI4$Jo*uAubE2zgB^eYd+>h7Y9a!+I5>DDFstdOz4fKToqkCOXkw#mpmt*l zq_99Hj!h!v3MMHD2FAZA40(~^%=T%8qR%(MN}=8Cvp5fJ8QCsn-C%ti(c@i>;PKgx zgT#sdichAVOl0Vp+eN`nsR4M}GlYq3T2BA)0dRd zW7u^iTG)nW?Q6;X%lX|dNJBuX^bux)VAkG+?qm*TO!ViD4h1kq&ZLB~vvKH#;f;Hm zy$XI{LGz>)Kbv`j?s0T)0OHZDxO#t9_dGJnL>x;m?wSoo8kpX`u($eo-FyT5G>S$} zDw57`pe`ALgCD%KXiWSfH+|@Ufdosy0W15C{(Cpj8TDLy^!(p+Q9={&<|}u(ImBrP zF(sVGMCo+o!&cM%-D0)O)8L3l8zOk@-@;%XfeDp$(2FjSAe#+Y`D>7Zi_JEnMZIUL z%949!MNzm-P*f12MAYm6 z^=j~Y`LX(VwQYy9^_ODF3T!ym6WOy6J103F&z@f)H{a4zscXj+c=_{&VB0DXrGCCf z|wrWKK-cIIR!}q7S{h)tFFIlwL8$RE*13&-fQVU}4deqGi>k| z<_Eve2Ffy7ktgl9)r-br?{<+y2|H*@UlWXB9D|)_Xm__OhtGXc5>zGW&9uK10 z@V?m)OL>g}9|_bBP!avY!UWd{bl4mrh=!aZu=ykENkD**no&6v<8oXwK3v+o2yVS| z@C`P?`tlTfsCWf-VG7Om_9>Q<0oZDdYgdb~BsDsljB_U3xpX$5@+os-$Nu{0AKFOh zl|kwbR&;}PrqvJX_p*=4^@F(BOr4nV$R|H96e!v7%22+EcTHcLn#|-+Yz~z zpC2zGC_&E?Y4hL1SXUK#VJhGyqCoANzLUlg!3am^Br3P+1c>{E0(Y-vE1`0G@N_x; zeW+#jk8g%Q^TI{*h?J>DXe{NLT9 zFsFao6D-hv02^#->$Gmfw4FCFA3M+cF5d;T)aWRr75g7wp^$xNv-23#g`dXxbkI}l zn&jZyR6#VupGp7;=V%J?q);vZ6jtu8G4zy|40uB}#lRV=lJ-YHM=v{9Y4KZ+Q+SvF(+rw`Lz-EIWC}*m8Ga z5^E+ixWa7lYw7B4IP(1Wgn*W|l{iF?IOWz8KTSZlSXEz%nt1GdGqt>{BZrr|jUFQ1 z&e@|3BN0lL;9B6@rXHibfEF2lS+^nP?pTVgD$axTzcd=L8BwXrKuObm$1bDXMT=JFvB`@^29Z4m%|KC9;#N<+wUom%Oh>SIXoo}7;r!70LSYya`E!a{@;)7?w=B{u?|&-&K}xQSdneR6XxaNgYLzsG+t%VK$IZu%e%j+Gzl5E++Ct zRv2A5Cuhr{1 zeCzwU^a|wpm5nCOSDWbGv;422K#YJ-7L>SDHxrMUji_Q8{0&#OE8JI^D2tcfm{QdY z#rr9qvjNhRyFYPbwO3I2epmIvBtMFbn-@tY)l1(x%dFAd=6y84z}K1Fs2nP0<*o{K zh*KF89}e`PZa5J$;ncrq+v|m`)xW?4{f`8+bDcIe{^?CwgfDHGWzmh0APE>z*RE7` zer}ZC#q3(5yb%mTcH8wWu8=C-e#^{XOu=TKjChpFUx*K&+`D`izRa0u)tA!LC(_rp z;+EogbKTP~IJjJ-AMp#Ysu4gWjmV3H^iGJs%9r2lkCFd1A)PQRM|HU7NT0I}Et%e5 z_N#P#*?I-ny&nc)J7PaksU1fRpH1)hY~8_5wtAVTr`8&$gdPUz>iiAc>%KpIn=9Lvu%W*^6vg9WP0EI& zTp-9vRlFkrR5>DXo!IE@z3qw7QO9rY_*^gW^)j zlaDV(B>`trhNRY3M@(_|fm6w?J&cwqzrdaMDEbs;vgRMUN>PDgeZ9TVBctdoCh)C`vS{Amo_R23V(L8Bv#} z+Bsqbzu`BlHSsh$o2+DL?wYlrl3VEi9EGkkjGoO{)0?A9oF`E&L^9}n-4Rga;+!YH zOt2?{G$E~YW3~5~VOu4xGmLZ2_iV7%VsUkgwE2wd)9^V<81@IH0HPF&tFGP?e__yD z&mfHTa$<3g`(8kn%q&HiQL42_d-TCL@$$xX;-Wj&W~E>Bc(Aq+huwUG!z-$O38R5! z0GON*$Dr#&Y$RuW1G#VaxuDJZfi$bNbZN!7FpX{@1u5n8@~H#jB2zV~7|v|1|D6ZM zTTscBTGp;vY$8}x(3?yje_Y8?ISq0+nRfAM&w_qP@23)|uB#Vpla0fXJV8Kg^VVP^ zMya(aA*7)@7ghEv)APodUw=z#1aemWl`Vn;87@s(u5%yyI31GsNm4iIMj9TEjU^>) zm?@&GC^e^9j#-&%LpjN9opyg><}z$v6u2asLQ2%$u%rE=dD)eYesUSRpdvn@4=T*m zzz1~fi8)1_LN=v*5nin^@eCt@4?QuhD`7*szzb;d|kRnl_$dfos z*X8V`u#=xmXduE(Ap1pEpZj-hU?~{V!1V{-%>a$|+^>UO`JT)izQROl^$ld>IA9!H z`DYXrvtZe~5~JfY<%hLEFY4S3U)b9{pz7jfTzg4c?HtXyGRYdEZ55|FjbfI?tNW<080NBt47trWf)IgYw-a%bz*VE-bO3m>pZb^BA1@LH7J^Ct^vrUEVm=7>1hDaxCR zVSdN`wl6Vhj$g5d0))p9HI84{wW%7K|NKhuIXKxDrawnFcz1}>!Sg?dGnyH(IPS=B z7v8{Ix|eB)DlH*|kLU+k)8A%NYk1$|7vd=fhA}m1j;~(#AWYdwe!LNk6*DVQWKMc& zXH<;%m-ms2brlk^ZZB#|$4KHFp?mOOcZqHYj(>!`BBd)fS262V4;}eKEl3=}tsYBgf7>J->$2u)Q^uTE>*s$Q#!IcYpEGfAYhQP%-;VFBaJu6)CfqDa#G1O~ zVX|^RB3qR|^v^P(Nf7-r`F$?T=sc-`TG$`{g2J=>3R+WXJPxwpXiTm<>C!gvWmWO& z>4}@NXLPM5zU8g!iRKC&a#*jz#ceQ#Us2K@WWq{B+rJ3Ij7%n?B;+(W;(dM3p+293 zu&mzPWc_F8iWA3{54Tqvqv?f1l$1Y3abNmCR~uf5LRxEaAkF(tPWc(0|3^wfZpT2Z z<&YJR3Wg2Px3(Jgra6PSGptMPRjyWf@Q6h9O2~{2S+Dt~U z@+QVjHC`F4psn$jWCJLHUt&a(zh|jEWeQHSj7|$x`1lDqLDW|dFR*2Q3@LCt)?en? zldnoIdc-(RU;(L3{}4cd{FKS1ix$7c>0_3K5@iIw3fAEpOwgPhcH+FS+xCXFmA;gNn7lY zXG@PUGW^E>c<boS~vRss+~+{Q(UtTz1cNj3SuxSX<)JbtMR;p@IfC zYFK)T6F_{Sh?dga_gzdQQNB5qp_$y?8d*gV0lX7St6>1KBIT(78hNmI+c21 z+!078r8m6%gIT9_d@RmT{HS9?!gGXl_4cH8N7Q8|klfhooG#3_yo4-6K5zn^_+@w= zkmn^Sq`}7uxjf|R==Vk! zUK2n%h@x<`Opy{3E=&XVzSZiN{u8w(DWFW1qIjhs@U`$zbz)Kl8tz5s-N0J}7N0nm z6Pf!)iqd*UC)a!;sFYdKie@J(yto>j8|8k%ooqg*Bi&pOCCT1}&WWyyCjfE(>=2jh zh*qPz)WWHseuO>cmGs+87}kpIc1k~|!AwEb;JTy;3Vc9!@`*M^&@=(i!i!#;)HcCG zt4rl2pwRfOOd_Sp-EEqTdLWFGhof;_}=0p6dUprwA8s|7Qn?CX#B#^Nuy zy>xb`Tn9fz21smBpxuv}H;USy;xBQcDl%%~5y?l_NeM8}9V8QldS-sGlyUqM=76rWBVy3^ ze2iuzkZZC;ha2(QM`jqPD>t$Qj5zyNft7X|}9+EtBAvS)OqEiUp41(oK^YC|vU`+C@HGHW%0e;Ym@i-cCb2!ZV< z@0uFN(=6`{YT!Gb)}eta^Ws9nTvUSn_Vc5slKWtYsWXN0mv*e@k+%D=-%@D#u%jN=2 zW5i$55|<6EjivC7xJ+4zAzSEFe`J+~5I&9{!ctyJZI(jiHze~s;?^Iu88w=hgY!|I z-e)&i?=|Q{+~gGtiT>67hOi=Va!h=0wY*bL+9G4Q=XPlttG_m@AH*lGOm0f+ztvpP zc*Q2`TBQJd2sq*w-w!SSDM`wz7yf}bs$Y{yE~#D>J}AUn1Dn6Fxw8&kCZVgYtP9w1 z9#50EmdmLO%Gmmt_{<&-lJun8H%a>Wv>jxcsZqL?VWUvv=_8V0XkqbMOTzV%rpmej z(ezc&!suq0puqh*%o~MRQpqm0=+ITblv~(2f4H2&-;k_{;x%%+J~fh_J&OWa&1n9g z``_me_o13!Z;|>?+U%>?U2HnwTT7&JC)%2;=$~;a+6ckTzLZ-+bv}is*+a_60{5XB zpdRFF=!t(#X=Q>=YKr0ztX zWi=?&cwzakM}v2SIL+hb0{ft7CzyFxsq?(E7%vf`NSH7q@R=ZDV-eu1ODXN%TIyk1 z3eUHFcl=F^?O+N@6ncVyV*?vT7NcN(yzu)6OHw94>Uax+OyPyphaE5fyy<^FRG)5y zCVH)@xr%*7=aEP2elLTf`bAn01#fU8=6n~|9AgNQvCb123vA4Y=?sNUp{Xx$W?fX# zJ-gjSDjy>|Afwm}q1BiM3RNBu-ugtTcFSrBERmABieM914E?+bgKAxm0(P9mU(n?^ zrC0G^UGs7H?V$_o3c{`zRW(;|Yo|i$XOHq0OZXkyo9{lqmuB^+fX-ldt+|?cB3RRp zUL}C+?{s$&Jb(xF#7qaxIb@wD5qcB;C@iN?5VOARVGeBjHWWW5aO!Zspc&T1)B|ur zy?`O%8sisd;*aR_r`;Fs3wVawiTIU(wujV>3ROrY19Ie$ZZGo**Lrzd(Q15*6(juv zL%GTyZYR^KLcf)IROWm#vK;FcrQaFt9vr{Pz;? zC!@)xfadFaz+D;CL^%A6D-E50u}d)_POKUqtkOLYa@vB0PR2d};#k{VH1_^Ku<_z4 zf-?s;6<=9U{L*y6+d+iD;qrQxkEHT*bn5}Rsxy-C<57MEm?9!UL`9#so^k&RYHeAo z$@_QKrVdd%4cGzV>&4XOVcspsDoL*!0+flLoaICEFH+5t!Ctysz3=gRG^8pwFugTF}2yqJX^6wYFB^L(!Hi<_KtjlrN;=x;I41kRQEa|q? zrH3<{CTkwrXzb>!-;15ZeJ_QPFj3~481_ty(C}T-CTH~U#92M}DPqfX@M1OMGvY2j z^=@v}*|;ow`ikEZ${O-1xO4OK*l@I6QQkhbVf1+hD}~nF4Qj4Q>~8;FdYXn|&iQ+@ zu&Ncd*U%qV|EJir^s2p`{PrUjPh+)0rt{c2FjYJAaY8Y$XeNFXkyE*cN!bP!Jak9w zmxsGvtZk1<57-`$+W;*MhUFrptB>wRV-3;L?2jQFQ6=;1_g+>15hOqbgjlhf`Qzr< zhIN7Zu4~4SD5qEKwYp=^iCt}Pg)^YZv?i?o0SYY7cRhd$WE&7_-A!^akerg}0y-I; zzBnGqNaql${60)2Oa5oad?THa&EbxhrB(J*HMxNU{6p-&F<}SB+RN!w12yT8zo~aR zdA+4Zvqc3<(|8O=othTsjs#|gUu+&$SFtjcST@d95ElUON%l@6>PXv!V{u}F-%gt} zzmdrlv6ElG13yCbn|zNGrO9q<)$@GzP113PZz^SQ8azv&y9HiUKV_QEJ@Al%L;H}5N zw2`vUPs2h0lN0!m#&&U%C|DW8(g{N`(&TQ_^-)o8=TGcZ&sV#?cfbeWRrq8-+VIgn z%TD~)MivK9^y6i5aNCcNObbt@r2lq>QH9k;v=rfa2Z%w9ob!3ifyBHT0B4J}h65dn zpfo>8-7y6SNuO>T-H+HR0(#Lqqj`N7)+80>Hs&aG)AtH;4$Bh@ewX-jlD-T+0D4owgyNndG*5%}gFGYFt={S|Dd?0m%b zDE0}?sZItlI4wDIEAmkG)8tr!q>Wz?R?fo)H>UZE45S0#-w=I4SvRA?cNwE~^|p14 zCJK%;&;_I6@%S=LT69qLM%ooZ0=v9u;W}aOG?e9@ii!81} zln@s6>N6}6f|ev{0?(XF5l}Qn%bKSb*kYKH7Dg_F9rPsd6+FjXz4BK<)UsN;EgB+tHp*k! znxc@U!R^BSpN|b^ud|e_+bd6-ptzRha?6427tjY1UK$fBnB`5$#4IR8JP&nzB%kBU z9BNq5{}h`rN@+393DS5BXZ^w)Li1-c2*H(|U|*jTo#@=(@P#`rkmcxELNDtElkCQ? z7qAk!QPT=`7)qCOHB43>N{a5M%9xu}`kkD$Z?A+(n6>2#RGMi$SxwEpWWyx1v#78qd3+MCQTj^Mk|oF~Ko<~~D2stnt00 zIp6ZqD$0Z=gV^(ZD}PyONjow0K2AN20T7GqMtv5hn5AY=4VI@q3_sHkh#G75m#+D% z(!3o3R^=bDNo#MC<0AmwGd4TZ25ff*#TS)TV|rcHm}$Si=9`}H*GHsYCf-%$N0cD= zjV`>T5O;6>J4QjV*~GNC*ms4in@U<%d!r7Q28Ms1)FM8kgSV(dBeUimR8d$HTfz+< zAb5Jg-{FOR7yfrRD*wSMo=ys24ePv6qyt&!X6!lV2#dk+s}pj;crjVWTT4@gqy zgUr&7nz)++wh+$!AB1K_f)4G4*+=VvY3(Sg(ipZ{dek;tgEtI|idnt#N*}A7H+|kK zCjjf~cNV|M3iNXJZ6Lu#9VlcyOe6bGv5}E2y1{-afld@_YTN9GJ%{k3beG3!VcRlJVcl^xyih_f39+e@ECL6@+x8N z6d3_Ij)f-~Dr=+Bm)JosbpZ#yn?{(5Ap@gXT*)w~f?4q6uV~x^$R0u*Tt34+ipn zB)AFMe;rxLT6ru-15W<=GPr^Wl3PO7M33_ig%>aKHe)|b72&CqawGheaMzy*Jj`7X z$7UzI6b_wduuy{C@J9len#)q%R|-Jf>Pz+%R0#$pHVRwp7T+LKE)8AvV_OBjQ2-)$kbw+-|r<}b~5=f|yeBBkI=G{_(ugpC;Nwpz0 zrFevE{LgZe1gwWPMKvgygQbj%vgzz^sjhIbI>1#VAeVjh?GNTd1<3I(m5}cele!wa zHt7?#X;W~5mB)u~f<8Cv`t7H`S#rcsYvuUKonK4&^`BF3s+E!d7S!_63zNLdeul+I zzNedaG|XbR*J#V|9Vv-42-_-jg4JzBw!vKN!o`E*`~=UD2B)ds<|{64!!tEcxD(K% zH-n41s6>w~iN60?ve)p@8DW)oLmmzHu^7ap$K|kMF}qPCb%u?0L=vfJbewW64_(XB z&jy{X(oC^9gJLC1C0(DPI&%HO213%ywZWI&$GZ>+)+s#;{5Jv)pbNni-F@yR@vU`L zQK}~CMBe)K=S}oRsR*cg!Pt)@o%nT5M-r@yZ&O+4y7}dAnx&>dJDTQ=3POBJtF2uLJ@M_Wxv*#1~4AJKmKkxBj7!E zKitUB>WaYa9NDw-K>3H0SEf1$PD2P8vHxahD(SF3Uu^lu={G&4HQUgV}H z`dJ6TNu+8TWvE!c@Su9k!kKGy^M$ zBw6yqt}zT!WS8<^mdZ3N-bK- z9TB7bxL$eS_}3dOx~*ix+>-9GSr`gwvTT*xHb6!V3z(b4 za-_ZcF7WgxPulnd{R!a^gombM!=~hwocfcB5D(`~nH6n_nSYGtS21m?+Z_f(N(QG(MGwO)xN+;aO; zT!`t{86LR~m5~LUf`y)E}M{{ zfCaiske?w@$fBQqK5;YIbJGTVvi;`{He0+;a zsjlC>hbIOQV@A(+2cM8GH{^I)s%Zgy5I%zp+4^YABOtg)4MHBY)D3rfxu|#QZ zL)wM2almkQO|{2c?*Aq)EBCjtLHnke8)ry`A1e~AIj1=mb}*8JizU<~jw^A^Pt-0} zEbq)*i(Ibt2;krbTf`Xt!&+0wrS2!Rs9zt5P*SB{7S#nF@ftYk?q>xWlWcob9ae@UGSI|U5+O7%X&P`^9pzgJ zKV=3W&eOb)noo3>!3P3qke=qt_oxpxIO3Vr209ja*^OcGv6mnu{bGS(lV5dIdaEB) z2-PZx-!4$E$}PYZQFZf@0-od?5JLTMjvG|a?S(&J!9H*ZQ}Uu5EQV#}w${JCNwiCh zLoH{vRO<2H29yc`{;4J3evLwJdhkT06JDV0+!p5n3B>qlqC1a`*kk?@5C~_gltm(3 z+9y5)rrg`_N>O}Sjgr-J{5&u4M(QeKN^*=-V8i#c2#w>glAzxwA9+jifWd;*9M;gs z)(kPB7w2|h|7>L$!Jp3$&4m34zQ<&)K?@7%k|T4#99jF}iad6CG+K?OFgn6+l|CHP z@Si(dkA(_^1Z4BC=CZ1V0G?zEcdZ=qqsMX9(Xp<&GsJYQ$Ca)fb2RTE=@+h0GU&if z^Yl2~)n91M!7@RR&PaiEqR49k=IR73sKXpP-U59~Ee8V7B z!g-CPUdATYdJf#JuG@`+3iSP(mdy~=mEVXNIcrLvX#C_N?K}X=!qzK%lyfP^OGK%= zIL1>(0P#38;wMAhT0jG#dRVixzfiXaY+H%*w1wchx*R`iDPB7@)Z%944nVD;PL>z;06MZ|4TPAgg>_s(pm;|hPD z?-JP*&4prG1=NSl(pcL^^37rVnaF~nj!+PPfE(ma>i(?lV|$@>q@F4#1yn|)D%mhB zr5)HoiCgZP4ZtaMD@bR@8D+m<&~vRh>Hh8XswWjlz*0XiWop@LJSBU$7OYW&spe>O z?TtBiRRY5U#?H_HEM;|j{J!4>9uvWAst)sv=}cZ&+2$|_6C>6z(k*J#3Xzr3U;Ho=Kf#dU$15WUQ%X6WVr-tW*rXp>C+++Yu+RSa|#Gp^c-f?%q*LEYNKL6xy3A{P1qz>paRnnlZTO_m_ytd99S-JF6=YH)6Y35V+{@@!tgz`$WX1W)QVh5x@}qOpm# z^LKE2f28b#31Y02{m>R$JMBl){X0th%vF%neYa*lc79^g>I`dYbCsJND?i>Woh%U1 zAM*Y%Kki^kXGeo=_P@B(HT4(zff?fvL{$5T_@!eMq>HnvEA~XujP!Xx$c8y=vCqmh zjKq)Fj=sJwMD^nDVfgtLdu-!My%-RVm0E(J_RA`1C`PYRwrbxyzt2F~5~vS_L+?&1 zMU?60Tt102$n9&0@vzzYu)E%T1}=J6^78Z ziRo9Uv~#HTNwa(h_O=DsHqoH=#47-&2OhVHmDCI@6?~hcZJysJoDfO+mm5JQg!gY6 zKJ;F4f97pc@#@Ro5<92}qEec5kISoqfbgy0DC?3)zVf=1{42XrEB#q0O8bq{sgL#r z=RC0L$@LRiNba0Je!Lr$0USTk#GU(KDK9s!u2Ka_P`mQ7w8sin@zZ@VWNF7M*d--| z=FS`w2}^KUKHy%fRq{@ZdMXbYQEH^f@DbO~GkD{BG~z0{vJ5M17R26oxIQs%hVRFU zq5r)(GPLorrQ$CZ+ zBq%ba#!bXz&mZto--GDp8Aj=Wv~Wte29(mYOY#sw9R0AQCV~1L+!~fB%XEAvN&~~M zA{NjgGD)!p^9`F=wrF(o15>oB76{TA67y(&g_L!$^3;z17Z*ywF+)u6zb$2p+&zg? zb=x$mN{zq&)wQ`Bqw`wsrmq|6&79dGSfh~>9(ySo05|YKFNheKfWI-=Rfe*q;LB`I zEKdTDb{|c|l4^8>L=^W8vRyG6{pO7Dxk_t2{Y!1pKaj|so{wa$<1(zEqUBCsv?=+g zzQdfAmaF#Mii7gs8$yMx%0J4r@>2$VeA-U%sgT~A`kbzl&=~WI;7fmgDo*?+axdcC z`H}fP(3+?jL{{M0J*((6EE*5eqnSPscVp=zYlDmS4|?7Yd{TzF@r<8PMAdxudE zFM(6E8U;hdL`ZnHpA>1)3;sRHKxM>!Xko!&DITq&oO1RMr9;pOy?6)U1oXfTnlMJj zZ#euX`a4j^1)c&#=qIBr>3G-=$1f4P(2i4yn1Ji^UEcGuauD~eamgNP{CY}L!>VAj zlQcHrvUV5O(kY9%38_q$<`H8bjb2&9v z#Y`#zB%E&ycyveP_&?q+$hfySYqe5T9>3}MxA<^HK+ZS!%RyOERJw@t7IQN0n!LZV z)qU1-zIvjvkVnEf_7Y?tS~TWJUd9wR>IHGR`np6J4qEGjm{M1{_#Perj|8ygI)QKB zoqmSn`q)5GMJ+U{ULgXBu~jeS{nFZB$G}OTk7;RFDEF@ZPg3Xy&+OA}&6GCAm$NxL z2lx?oKi2|~at;`!m=WkBx%5oLUllt7xfwQ zE9%PoB@9*8gT&sm#2S83t4v_zFL%)|dyv`b>s7K1o9(BnSKFzzoDy-F!M&d{Mb$>D z&k(6RhQQXtfHrl)JlfDis+#UgD_cd&yGx+aZ@19PML@Ni?JIIgh9mlXru|*eN|ln5 z{N#3L!_D^}g2{0t__(esmNWnNa~bqc(g_GYhwVO$zYZe(ntbK`%H^tm~mJyg89gd2je@Ti_(7d&_tzYig5y}@G z-2JiU?MAc%QYem-^7t(;!4lkQcRO|d2{V9J?iISf->iWZMm6$skzhDA@!GasjQD-$ z+jfGwCL)fDp^FoMf|?bc0=pQ5J^6t7`Y!QmE5*Rtl3XW0csclyw?tQ=hz{XBIDb>F zWwnc@2NfYd=7*5Eo>KPDUEpf9U?&fw`A23rzwtNVUm=8iZrs!)?^^UBWn$!<9XggG zsC$jyjh%sl1S)q-Z~Ju-wUOPA4t-!ap=tAzs`p)tb2xasxy$OWjcC3%Xwj+pJ*}nQ zEeM|7kiNlHjHuynhSjnoMO{QWzJRB7*<3H;OS8g(hr9A3+TJVX4_TDPG+GLBxXF36 zG5|zN&8_hr;=F_7K3NFO3=I6Jyeo22gLN74d!a5s@(lPrg{O#^(J*4o0H27d^@(sI z*a~u;+m_n1(&;+Pqv+Ua#NuB-Bw6+7{lS0B@bM&YD3AKK$wQCPY^nZ94sX=W5LF#i zy+mOwb7y-jkr+JerEM7#0UHxBqwECgQTs%x@C7aEUqPmLy#a9gQ-@AJG(7XK$*uA# z2yg3$T50n9KjYQx>4U)#xt=3*J{HVmAxv|hkO5&%w0AFPi%{tDC4Ju5;B5n(wJ>qk=i$MYAGtBuG!p6nI$P-pNaZx&3R2^4S$olnpqC>EL7Mv%fd(Qn8Lq8V~*78Vleb zBnjGjqtC-vZnw+&@R;u1HjrljRPHf>t5Jy#ds)(){F881y)mTaYOu#7ih2wnv14`e zKDPHvt)k7nj4blcTsb$nmrGIH&mgq!!)Ayum+|q`@k~-iCOFz*&^<+xQ32zDKtJH- zK?)i7oVTL7P8iSP#p9h|C!(fMwv?hotJmX)z@?Ugo|K`)-rJ$PsdJ~*^tm@!5g-u% zguYLFkP+Kig1-*oY7D~ zemhDi8mIIbwiTdi_@{_OGjoQTf9_F_u_QGQJyE?^<=1o3dEb-*j2f-bQ18s77|Vq~ z5i8QdC#0{H&Y5;tm5Y7X|MQTvKt4>P+Pr^6k#v=hR zbkNM|?h}k8T{K);^lK zyD)>eaqr%|;xCEeL>*74Mr?l&GZ-Bj9a*F0v(l8u;YTY!i*dpSB4Pw&AB-Tm%3z>8 z5JTuCLt3|-20P*ELWjo#PhrG;`@Zwol6#slBIS=h7l_$5hl)2#c+vfjQdl56$rBAj z^~!IRx;K)gTq}{AkV7JdAy^~m*#WGSP0%MowQa5rhpGJIMUZQ}j;HY@A*MAk$I z3h$;~xb2avZB{88&}t+F)u`~ZomF9FCbP4knzx(WqhZn0!0Q&+iA`g^#N+YQ&G4i_ z&`#BVq@Zt2egRsfomgIxh0v*u!?AUcrxm}Dv&2NpOvpLTo^M3zyoKdA(*Kw9xeYW! z|5xJehw#7Ve6ldJu^X~5v2$@4vl_FpG8-Ck{HyS0;o#;nWn*JAWnnikGBn{dH2uGF zK24T9c^7wypzfxtVVnp?w!%}iY^h08$N1XKjvMO8IbJ`;ir|Xl9eY(B)3+SQ$y3*& zPUPAXt-=hSlHb0*HfMfjS5J9&Zdc#``|rnBoxv;)6synlS@m3UFPrbd8g-d%@>Aq% zi`lhVWeUqaYfAFYN)5`qJOfokPMmeo;lDBu=4N)M|LB(9`I}<|KozC$1Wno#xZKpd z2Ez?qW^IptVR-vLBh6+xVWynrO5FyF7Sgz56$UIOjU?Crb;bj5>!s(i z?0besLNOxe@%`D({Xq5C`N92-Fd3VA;%8T#U%Wg0Baw!z$u*>1xPhp1#&;)@F+%c8 z6XPYEzyxzB9S259x^5o~u`4IT)2gBC-?r$e0?3W-JE)zH(;cod_n38Y&~9@?+>7u) zUN_dLa1@a3FDPNB(r8pQOm?NKl}(7VEE)SxaCCkt)AY6cs?5ulERgqQ|GDzvY!hzu z!i`<<0W?)=cARg5{Ov0iDQvd2eJz%fY45nuN{iA`YesT? z6UdjtPDG(Uc?Xrq@|5F-Hh+5H>5 z<*;v%K!x0tUT_v9Z)Of}QW8T6o#Tr*-mCai&!8wu9fUjZlZ!rcO<_qaLU+bxhW!UI zJq&WUOBv~eD)p_(a#NaM`oogI#aFg zh-_cqrP+CD=4@2)^{*uj26r?ffvEi=_5#EIF_b;J^a1@41^#iLmxghw)&+q7E56Rt z)~G2l4$ncZWu`rS{~{?FAk6b$BvryAaCHl_5TwTg`T%v%D|A>QIbfvJ`F#?gKB3!4 z4|L%Bw(k^gD39d()Ai;L&HK0N!Ea2>eX1D7CyFU(!81j;lgp2Kj_tnzl2Oe4JqJN& zG80uXwr!|>Nh`wROH4KdEwXYHfLT}0 z`dD`H!=sDy?mGP0`w076@c0ie`?s&iKnL&WieUqzSO%JYjbhRJ0#|(97*hfcU$nhs zkTiL@;3r)_dyY#B={1lU;`3F?*}rQ!V*5a5&I4*GO}#>Z(HEY2PpQ}Hd+@WV>sj>2 z*AjWi7l&b$Ww%ornzylb1e2p+Zlodo7}1=9?9WDTcziZ^iFurdhE(c2UVqU!r%wPU zXD=7V@n+LB2^9J!vk+8i8^D14t()o&ZE-1mQ%Vizp*?Pkx1|3pfKyDn+}BSlr%4LDNeUw9NNaE^Ga|S+{l-0EprNDP;pz=HF5p*H-EqEE`r5n z5mc<^knwJ#rCFy%Ve|L@@O4g2fpZ`3>%u&FzI zK-AkSkD!?kJ}q7=fM99T4y9pA{Oa3gp6z0>gaDT(tB7{^LP(5=@6o$+kTsL39nvM{j^H4xLvU@smzI=%TIQ-HP@)CfRray zn?{Gm`HpUnQ`@I7t{9TJ2}(e&MKgR{pvcx%@@X{;?kLg#nd={+mbnKOMro7*$B#yu z!@AQV$Au(~E|EzmIpaDVF<=xwF?R*pXGtHx*f{v4S$TZv3d(1hd>!#y{#>^4%A*9b z!|l17@J3NwgC{g;U8Y34PMZhDhjYodAjnaB`T$$Znnq4y=r~-wmDNw;&M8QPs1J)` zp_!4YUd)&rzP^_;0y5x=fF3CF82r@8c#Ud@3Oxh*q2B3xi-@X6rn~g1Q1VQWwXbY^ zsu}xW3`8;lsnSyD!LYVDbz`z-uf3jul+yV$(C6L7L2sU~hcr%=(Q-bI_VlVmsP_k5 zRoH3|89yl8KX*_Q!(EFSz&&o9zxV5^Nr{yisyo>25El&oI<|=5S#S{P>JMGXszSx^ znK+l_?8a12Gui$y18t@F z+gyjSX!a|i=5p(f@Vd^&vr z-9sGK)iO&p=XQsK7(lTcr^I|a8Vf%=(F$wQvi4hMGejDYXuY~}a=0IY`Wgd-y-fFbJd2}e(i$hz zl_uy%UKS|EnZP?{LQ(}J-K^UEVt=HdKkDi5_%FN~S#0uN{f@+gg?2+uf!%!wgisaJ#D&MI< z5kJGdI=45hg(=VtNgOaE$CRzySCJ5RN1lx zF(;4dak_uZat_|3U!dZLnY$W^iM6xf*$6sXhnZ5=O{`2h(#%~azWB_A!C-ntk~TWn zwx+Rg=47C@;1b^Uh|9Q{LeN&8_$LKHt)^)DxlJP2io8iMu_Zrz5{ z-wR^*x|A=Gkq`RA*7)kpq7i?=sVmO>@B~ARWyEq&J;!{`*2C)%d7Ia$6>+J zNh1?}0bxbTS-oOAvJaJES3ivNA*{dh|HJmbWyU9;6#px1pV^R!mQi1io`qST*^rs# z*KEy1Z$!(eM@P$GK(B91Z=lC&$VAUZ_rHqpqf7&VBCCzy2CI$gG3$COi+Z}XW~+_# z$uZ{Qbjy{)?URd+WtrBG?klYePH7xKfVUqXv@u&NTFI$(6y!oQh+49qkM1G)hzAS( z94Xdwlgaxpg9Mj^IJLAYa>r{iXtmFaKfVFI5d_>ms7P8-uK4t1KN+j~OZo)Xv%#k& z<}BXe>?9{epw5eoriG||>bxGigM{61qXHzvqtg3k3Vm5+EmV7DF75H%xaSNkZ+zG|N8Ln|D=3sb=zAb|I(q%^C0yQ%mU)_6;%My2;jA1ZCjRSz5_0cC~m z7d!H^FKQg{SrdI=vJt^R89rCmSVBcRXcNf2rmX8S>AyXc3__Vqb3R0>pxAPW2*qun za{+L}NcJD0c!yGy&^>xUtm(?nFPUA49szC|?3c`L zw)sn-&J^m$sgyAn!ve`7gL5t$C%R*phq3CL>(zbWz7~G#ZdQyx{~wwC5Q#MdY{j5)+M| zt<|=lkr$_BBt0Sk)*#!lu`CxkkCDsWFNRcYloeF0#w!fEW?=opWW{E z%iPKl6V^V!F{}d?CEHzY(F3B+i{td2yDy=BOo{}=Llo8Z#b)Y;h1?ZDQDz&adKx-Q zzFX9}wEG6|X0XT4HOGTfd1`AY97j;aeT6do8heY>JSxNsN33Gqvh#$NV8rz zvb|-pU*@F(4k@%fd7DWd>xFsN2L%PR%8?^_oT8YagRM75^6t1>Mj)02LCvdp5^;7q zo)nlEVnLynl#1(xNmm@Cj5bOcH?}hPcMUm~itNflN4z*qMB_^U4+~w=+F&az8<+Q)EF#(9B;zP^yJ11P>9U#r@=3pHCQ9iYA7N<` zlXUOf{}0G}6>1G`f9dHM7w28l&s!)V&F-x1cTqn5@?K#_TGhi;r4I!ei6WB>Z@m_# zEN-Jv`OFf##!I|JHr`%6PPt(Dc_dIExrsMu#G771Wvng5OqJW>^P6Igd>z)ZKTK@M z0W%$ct3J*#n866w8kP4U^R(E_i^c*GUm!bScZnlME>Rk`;HY|KC?C9@^#>iusK6d? zGzmViTRFZlZh1_tM7tY!v(M)wgq>NmHh5ubs=HaiP!aC%DZ6LS{f)^G`vpwK_YgTrs|ZquAmi}P zk)vf+$11D8`jS$N9)y7p9&?nI60l*9pxKgbwMLYgnKj4ktoBbW0&YKdsf=uo6AAPx zkG+W7{W2d}x6KgRkn-#Fy(*b6C#XP$vPkz~4ZAIMG`?(w`Z|Z30+hgg>r|OkVlvsxK$IMYke0BFtS176?1{T)4RtW%TNc@l&ta#d9%}19rC2 zuS0zk>GjXnjl1fH_B|*FWHZgtE?v51}~Dn{|(hpeQCsUS_9RdJzsm8YLXaZCdG@B!0Bp#k86yenwQ95?1JYpPPLQ( zbu|pq_*Ngx@^;4^Ps{NS9m*{sHGAw=8A@zZ*ARvK_ML6n=3!g zg`#-h|1*wej7tkFAo_lPWMg7Hg>q2@M+Ev{ z>)V&}ACBK>PRhl^@^_K&LcxU!TlSI)jcwyugg?0Hk6GK9VtnFMK=4@MeFFrx$Q+k5 zFHQ!sAwbNtc@KqL?&=NH2i%4pM@fHKHmZvjjIA%~Ntksjtnl?!{8appe ze+BSDXwG$~L@MtNhsis{*Z{u!v4?NzYd^jguyGjQ&pc#V)P>-7dmS)J7Tn2V<@{fo z3W5#NhCWuWSH{OVlGnOgw1T<+x!7pA7zYm%tJT!ga+`FZugi1+?2 z+%d2`Xw?WL7!~euxUsHU!;-fXb1j!m16p6s;q)=q#g9}k;f&-PKHAfvV?Lf9bV=bxjzMo(WNfB#uOm%KM#D4a$(mlei zff~r9ufkwbXVk*u&imAyC*UzI$>K0d<+lLOw%zilr{u&vPv4%jNT)gm`?Slw*Q!M5 z)0aDzu&bf@rhWO7Ihg!S;y?Fz zCUggb!jd(bm~4I;4nBN3l4-2K%v%-rX~rC=2?Hb(T(9)P8i}BL>k+3q_?+MXc7+lO z@bG*$LRyt{tk;^>>o*~{pQMeoanEn*b~*Yd4HU zA`j}ut)|&HxBN94vqz;b7wrMfU}!tMFXE@SkS5Y(olGoX34c3}0hMJ6U=4N%d1={Ol*I?f9Q?=m3&-sCS2){4oAOl%b7>>kXu$19yG}R-9LmcmcX@bwz-S!!&+9oz zonn`W%Ly|%E)Pz=vJRS`5(eFQH=XXKE*IO1kN$L`!V za$8{cC;~4dN*;V)h}<0vO)u-V`Y%O*_VaMpB0gCYyg2Lmu@NY4#f!lZjwqmLF{1CMe*27kh1A zeN((VwSZa3R7)C&aYWmjO@s?17ykAFHg?lyvgciTOB1I5Am{Zxrg-jnlqFWXin3Yw z>jvDq-}a-QVeG|oKD#xb6e!>o{Hq0e|EcY9t#v6)(T(vbmC-B(OY1~7YKi>%eQ`{% zEvOs&yzfm%3~Cw=;+>fndydzD>fu5`TRhd2BM)b6sw)T{(;Ym#z(~B%H8+)o5F7@` zwsB_aV;`ILuKun?$FM4ZPG8HBrovIk#h)KQAq(7^i{F^vd`y`?1|~FP2{vIhrmFhh zk2-8zuhiDFhtqYfs*Jr#WYsk*lNbUiSph1%q-&KNDyoVnEupZIlpyg7z^PCe`*IGl zW(?XXFGVWD%i+?Hk+zvMr7r(We_8@ycd4VkI4j<$sIu6GR0?Y-P#N+wwTSZK%rzl2 zsBtHV4aOfv2+v&droy4UHQeyY?`Rw^Sq^z_jf7~*Kt#AKH%_?8wH|W-%zG7`a`<3qkt2UdqJn;Ve z@)G4j+ds}7;I~#03ib+1x>m&dc)4T5k>nQ`*^;WJ=|E-y9p4n&id4-3&LrZSl9d&2 zh9eI*Par{*zh1mtZpq&IvqsWA9Fz~D#0Ai-djE%Dqe9}gt;aM1>5~rPE|9LwPeDT0 z9#~k7b(o`Q!Y&{Wd~N9SFvA{xv$OQC9xU#NO7Kv_>>fM-G>I4>(P`zWCQIzM&*i86 zba&hE-wa$}1Q%#?qGsy_uTyYQ#U@6zfE+mD5F3R|ww#;C70>#CJRR>434imK~kTQ@CZUcmaFF z$|Ls8YGg{nO?;blhucA>i&OlA8L-`8nDi?tC?m@Uj?M#qk`1NK<WJi@bW1JG+4YcWGXR{=%YY7t~o9mFD2A@ew}az znj3NfJna#|Q4J_r3Uz&-d37lF`^HuP?@2#fWr-FjF>;0~hv-MeX&*`HJWeBt(fLZM zfUfK0wS0b5kAMcSKh)fLyuZS)cigo2i9KDw zg2rF$YAgw%l{CIaDi%0FF%E|TT`)WN1*b3^Ua!$$-ng_GLfp6ZWQax^AsGH(MS8L& zXtU(Fya*-HBv5e?+LOj(f&@ciVuCzuJ|tgG)-6vEuP;+@A&U>Co8$KU)4%p!RlN>&CqMUr9MDa zSE5rid+aVbgO54{zAq<~KotQC@7QmV3YUaJ^&wWK@4=>)HEy7|MA(=B^Qz~DK7+8x zJ-)~Yn6T*P4^fok&PsuVEJ$6H#E{t`ggQsokiC+^P_6vB>YSXcv5So6dL(IVn9mhI;AU2~s;4n(tV z~O7=}5&DqjGgn>?-BF|7S!Rqij&oB7Q`ED<|&ibJx+ zlgG(CVwo{Z!r4XB1iURnkcR+5od2Qtf{!u-9iN%qCWCsf**Es#8$|GFa!RM9@K+> zX+-`Jiy}u&&&5k{Ue9&}U|hnE6%mh}IE<~iZXA+BRC>(Yupo@UsKeS?-lbSIzF`kf zHqB_5B=#*t$`8l~b%NU(2RWSccVUQ<5+26jBBYC=zznBW@Oe=+>@4kE5AqPuH9ckZ zg^`#(icyf>4CnBf5}{FPSHOPs@mbGM$4Y>CVh_ayh>%BMc?t~EIn+(rT=4{16v-@Y z*tf{!k@TN%A}x(y2y=tpI<=1&mLuz`|5ltBN_-|>m{+yZ_#(!b7x==9y7!YalQwdWGu@E$_(=IL^87| z4Y>LTe3g6U_Y>($Hn!EPw)Chdrk>69yY&6Hcpz^^2X=oml3Go{iJN?^a|+o;+ZC8C zxiCceT^iwSys|Qg{e-@y$AR{ruADAFy$&>C%$(jDD^#Hqy1YM+8)cuwF`$aa@7ck9 zOFs&cU(m=VEMC682%3L?=Sl39WyC#_Xmv(D(T@5T7wfhUJ6;@qPs37ewaSf) z5?j-rX1Ud#bqC5+!^UIBs_=ixFsZW-O$ngFpcyAvNy>ml!mhaw*v6>PPF*bALeDFg zP&#@yGxRh)EFrNG5b-u`M51>4cq^#hRGhboeA-1~L_)MHyaxfahAf_0|cR zWD|g;i9U3u1UbxO<{=pi+1r!t^}tEzDE_vWz^aVK{_N{6#`jYveg3$rk$AjkgYy;` zQqkO_y#Vg!Jg}O{NEQc2JQXjwx;17l>jR6=?IVB0!XhE%S zO*XPX451FVp0IDpUU1WSTD%+d>`hFVl^mM4q(aKm0_I671R53a@d?Ho1@g&LYPHo! z@#r;+UZe647)}nXhRSP;8-x@h0=k>~cb3GIyr-4wwwefbIM@DGs7AdRF&hiTt5uP? z-@^Y1*7QO?^8&1?~d42NP>!A2!G>DAEl^f>Q1bLv5 zg(HjM9^)@QQUu4u8k#_b+eMg5W6q2g-ykLp$fAA0{zPuxlbfKzNZ;<8bwwV3M^m3|LrbfybZppKtPCw*7R zAbJBy-WlL1K}9Xkn-WD+i@6&R?(qX}9#1w*%iPoxwdv7+A=6ZCzA#q(KzlciBe%;j zZ(}c+_2;s24jv9`DEYweWxfM&jYkqk&FeYUaIvL)k7P)t0MEVewM=vwLjnAK$vc2|9F{O}QTv+Rd)Z$B?T-oMFgVh4)%)B=*O42Ol4s_*^_4Usb&$2EM= zKz0(_x#`PTY>|0;2X6oz765*7FnBL(o|)Xm3S7zdxAU!KZN+TlZ<6=Wp$5rf%|${sx1$}g20&Mn!MvQ|0m zAT@>drvhD}={xnXT}9i3CXH!78%A|XW0Ox{?M_77hh@O{A-mQL!Px$Rz3&&mmlOC0 z$}b_|*!JA~JWw>3i+=hYAjFz+z@A&3+fS>k36+DM-?_p(qftcRpPk`pwy7rme7!S) zIkMSa^I0*svO0%2`V;lEGHgyXRFsBSuQRhf02Mr$19ZN0j)TwZ>PId@3Y7?A6r{cp zMn@i~kCj87I|FI@trr4EWmhYAIn>-J1fn(FC?ETH+MNhX(%0`KM1-T-q)vtWm9r zkEgJ?j1t~k#P6W)yRc2msSmrw*XMk!F}xMb7;h(5&A5&94p%wTD|89QRHCmgG9 z04(CeB~=xCM^Fes%V+!f;=aO+Ue@eH!!!YQJBo5Zg`xsO))QW$+tX1SNygoCd_ikc zkq-2vK4?ZLwrBeQ=vVQD*e3m2yS)HvxJM0&&dEPy3J-e*3&!nvaqftI!kOQWmAXC!0%0bvia9K!&37dTKZgGNUaO?2q?v7 zE<$oIi#g^COP>cUeW&z@GBBf)V~B%H)E|g7_n*x&Mi?#oMqPp8vf3KMxI1y$Jy_rk zYi-M?kOA<#B<~9ZAC3dzOUoB}^iD z;|3yy;8qX(uQq`aIiPjaB=2r!i{bVBMo1_#zc6q^Bltu(x$apt6xR>7mXA@7C&?PIKQB!Y$N6d?HnWO4DX0KVl1>F^M+s`dpC`Fg;MgQDnV7>d?6x|K zb@$Op1KZ*D-)&Mk7$ECojoKj~%GAk5@`CsR>*e6xQc=_b8?!CbXE4Ha;|7$}AJ_G)2-uc~iDaW$i@yf-H>B&5gQ>*7QiD_U6`kx? zX_lC~rdKFazr2$hxfuSaQ@e7;v9f6+X|p1D6OG3USXj;Pzu&V;E>cxDFOixpJeHeK zsJ@!Ke?brN$NeYhj)p&5Enkzt@x_?SuPsNW%VE)1qrJcI*>2fi@d z%unwC+$KGvU1X=)X2ihX?*Hz%(k>1YD5wD?qB97hVQo#2B0;=Zqzp6EhCnaOXC4Ha z=Dt9*-raH@Gq|y@dI7ErKr~ zcisS(pR-1dH+TChRF=tR6dTRB#`uG|8p9i@L4xt#DA<1RmuzaB(ySY)&6wJiDHW5R zJs;L_#L8t13tqRHy^-gGBrz7Q=hVNBb0^z5>hp3=zx`$urIQ|kJ!W4X3d|e{uf4K2 z`_Okd3vH57$_nuNESvbusw-FZW_Zr5-hP*XD^Z|}($6fn&N-I)o8PP0ht#YEl5q1Q zEh{v*H^XriI0Avp#_$d|G)$Ywzr(yKePRqKqtoLd;16 z)EL zF@&fLGDMMdiPz;ys7byT1+RCXe0G`Rw~V$nEGti#BkCmVCP7S>ejw;5O5wjHpgsoPK1xn)c}b*fJ!Cu zf%&f{bY$<3`KtrU`K1R2p1IQ~0ZyVSfJN$wc5ppG?6+nE$FN0T*Ol|-(X&sO5|A!A z0fLkV(ap#OV^OkyR%1~3q3hb9d#wG1C3wANg%W(N@f#cU#Dr0`h&a6oqV9w{afSPC zdn~7O=|{S91HoeMYu?J-7Ayv>X$5{+@S6ClGo@`2-{vOA{8p|DGyw&*#4JzL_$J5Z zZp)rui;vwK$B)?i5cq)2@l>ovxi7fHP&-hajz$pP)xg{eUN(9!BZ|ofK!Qpn<}K z+Ofd3f^*JVJbm6dYyQqVAG#S=ZRp@#5_~8bCSsxN2+g?$a_@)}lu7ei5E)ejmi{@? z3y84UPsDdVJox!qMlZXZ!@*`E=fkp3jVo^`$W8CC_N9IbcY;I}dbMgJK*oxLe)Q^` zi6wFRHaqO*mo@Y$gz0_(^?;apKeL51mq0(0Jgp_tm-X~CWifiMuvfnRze(Am9u<-O3XsrB(vLt-pt0ffTo=V4jg>K*LYS&4Ik;z;1wkqYMvcpJuEgh|?klP69n7 zKDy&s(oS?M{|d+8y0y1B2t;G&OaT!0s7?Wg7lUR?Pg(4*X5Tt27$Ft|8t~gD{Rr_+ z3LTJd$ztd$&4L6JE%1WX+c_3=WZ?|O`USy&9(jga?0Y$|;=pEWOL?#Vi$nB$*6zqS zqWXY(5vvLZwMM`~d7-_wKUA;+FyWLC)l=i(0)Sy!yDqBY3`rPHw~O8U{<_}RV?N2; z%W$NceL}0ao83u))L1`aYkIVCI<+DY5C5__8~5T zmSlVdk4ZfkKykhNxR0{j9nmnLQ)dTpu2R0G)N!CLS!+qcuH1F;@K^kP-+#=SS~ZYC zyODr+A3um6&Jy1?EvHN~m^Z1vvm}+CtAc7G4-AcK|4gnEXt8<93vtmC+L4AtiI~z&2j1M6$9)EU{U6DLYE}8SaK1xa=fV1emRWdIR@H1 zlsQ!}Z)0K&w;=gpNE6ZDlHeF)Pv^SQA1ffH-=fhf;MC`3mssMa>*Q7eh%-Ik9)7om zWgiKWJ2U1>Wh8)oyN&)rFI}Nkh>40Qq}?IdeHC|kb^*=DC^BG9-hJhzMM~DV^J^L% z_e&E{#K{k5ssECkwZA-8NZP5Y${s;tLmoY|hcH=P=_>*!&X5V&T(0P#+`p~)pu1Ep zRI)Z^Lv(C$7w@jH9fLY}jovr@{@Phxp!^auW$@h_rKyufeKG3&Z0p}fKnDHO=RLPl zk2!UII=pOy(t1fd{zzZTalLB! zzLS1RoQ^UUI8j7E1ul*aNj@e|!ufT;y0d9sr#a?|4)oaAbsA$hsipP;58`L;gX0^?_#}M;SIyGw&u ziPw<%_z`T%OXUG1pyHkQj*`wx_$~DZlo5V5gpH9eOwjlyqJb|u-A8bgo0e9}77h>F z8K8tDM+nYy?>ZyQZv{NH)%HViV@G2DS$l#%M)Ta*1-LYcxLtIs!-%A7APc0rs#>ZM zJBI3hes(}aBlzHI&_xiwT7xtGlz+u`CMs{t%WMnF2slXjz`(ux(t1cI)yPh{(p|*^ zYg1D54fZq^Mth-o!IK2m>9;^|`p= zGbhAg!4Pj1Ey*7ZEORM@+S`#ERKjO1w-ry(vW7q9x4f7JfZ_&Vr9<}raXU2{nGb4= ztl&tD;-w`FnprhGty45*-olk|HxWYKBkECz1Y#=S{F)=xba!A@ zz^tzS+a@%n&5*mz`3@&Wu8sKpNDUeMfJp!fJ7oXgvUq;~^C(O;zdobXfdS)zo?RzW z_ctHB)_H#81ZEN?P6{c><0pV6mi%RP$Xtu-Hr%o5&jq)~n$w=yCnCR36~O?Qxt_7U z`)CPkt7Qdn6cs^4FLB#M{@*?mn*GzYg&`^Zs=Z+nY8KJMH zbL3u+p-&_?-?J3hx(&tRW~4sxz2=4I;Fb69$ROW&s@PO$;VoiU4DHOBqkb zAVM)y{ChbdS%9;j$|2(e<9e>@t!x$NQnX=?26#K^V&HBMxf(l(KHrpHb7} zXZ(y@KeewRxAe#SfFk{Ymdd{U#Gw!|dvr^&(NV=c@sOZ9CYsoK)sY*{(0In`yvedb zlsq9g1I|&15yau=eap)&#<~+C_v1FyN2{Kzzuuy}PEyK9ET6P%wh?zAh72HBO?=Da zBZ~hfDbt=sjq32_bbo>ih$tiiR8E3Uf2Lo>n3YG3U_9T*==dUGD$0q_u?S}uad*tTpE4WHR0 z1>SwF5WeXIn44At49|SAi+@&_VtQ%Qdtc3OsR56tGS=>;4Ms5_XuqzjB zm|AhJmuGX0C~=hcxn_1oIlFSq3?;{vOUa&E&opy<6!z&hZ(putuK2 zP+D-?nR3`4F?Ix4rB-l)O@{#Kgz}fI9H#7yr-O0cHG2a!UkK`CT{)jCr3UzgMo~+U zPsF7y08$V4_g%)%YTn}K!Q&K!@VPG-8soREe-nf=T!fWuz+Gn$ zYuFy^J}Y>^unNQs*y@MPw4|4cPP%tr94lhj^2{m?_h~(%@4IcmXD%BEmk_Wju$AT2 z_~9HSk?co?9l9;CfSBM-^|w?>@NIQ$GNNezSX?n7GVce&eWkO^P{j@p$BN&>he*IX zgSXqe7QiYSY**$IO9}aJi;l=qTr$+y<_QQqyND3!(Xv;(n*SVC} zQI@$~E+{wNjhwg>tjaS=;uSt%26vLOt>NUf@|> zwCdl=65T$jQe)K5YgYo2HHt>wU~Fdy6z%6~5FH@`LxOM-+i0mOV&rGM%X+z;ttS)}qso zbCQc`&43ooAi%;SIymFH?85eN@(olIg^DhN_lbu#wvRzgE$>XshOdaD61)#PFpVJc zWsRyaoW*Fsd^VACDrTMBm@J1L>Y1G&VbjHzQ@F9fb9<>4SG9U1$K<16?L)9F4zbW+ z2w#f>`o7fP?qtqL?6Idyo!^HCl85!#?|f%&wfauI=ip)=pWEjIawcH=ok{!eTStlJ zf=6x|7?*Mmi9gA#DXBh19v+R9)nr#krWfd0TBxERXUB9JZU4-T#BK`Tx-e*%qWOIr zc?zEFC-~xciCZ;2gDk0kEKHKPh8^prRon4gI|xR+p) zzp|ci<{(|tB~7zY@daR?WD7YwO-ibO)KBkncQj^VUv<$2fNp?e%mZh4RDxnT7QuA0 zpMK}UgREiX!e7^8R8TuxqVvc1FDjw(PEdhBUSd#(&f;3VqDOJ5J}RoXs!o$@__E(< z{Jp@=J!?wTrWM-t`ohlp{OT|(ZA=!6(IUgWjZ*Cp7A5dO2myjT;L7Dvv;3i&b+3S193bS}SaM;F5~zcHacZnsAF7Z?!&rUMkU`K%QkQ`8X&7 z)Dm8W_UH23CN^yyY7_c}?sHwn2{JlKa^Kx7Gnxju2R_$y=w-&z$sItEi}mVcQ7GBo zj4yN}=0qy>-jzxcif5qQ8{Wne^(c9tLPop9kvRSQ$6TB}S+7tJN*Q8P%EMnC@f*@x z-MeUQSUpjJ z0~vy3vD8*|2JLQ9=rxBwX?HTSd~(*9XktfW0tKVFP_oLRKF6|pA~K)~OJjrOavy=) za|*7G z#yaqINCX}&@1rTB816DZT8hm96_$6zLV_mF@VKZLavinxe3A`2H8zdL_k1%ssaIgD!I z@RhICu~+V3kzirsJQteVhgPl_Gv8X^v0rCjP&lvT)_w2$$WRQLz>ig>{tc4S6M*5W z2)qt$J+M%(b3;C zIP|DcyDKC_WqgcJxi!)5LUQoFoca!UZJgWPuevv-RRv-lS(}c3a45_ti0=5|EW|J{ zzlO&9=N6L>d**I?l%j%=*NHYg|w=|r9jDR4n71-P0=chbt4C{VmM z3u9AlYPllXJ0d8w`Z6#gBQn7G0aI2C09~6kNUGUzY}eWQ^V<~u-N8m#2>6~Wzz^AC z^Q7Bx|3G7-83Ox55MX^f!1E=?_i$OZQ-i^T_@7=4(7;qluk}7pDXm`vtLLBo7AIsK>8EO|z(RxIy>X>jFu*4^G*Th`3LA6jhw6YmzgQl@~ zui{wa_w@FJNs7PxQ_T!E@e1%eL_PMQ7JGIyyh7=ma*WvG*ijy-O65td@M%A22G=2R z(6azMzxi|1eLbl6h)*6wQDGR#1n~4`LgRdxDoJ=zwVz1SS*kK8%<^hi{+UPnU0Low0uxw=hXPAxWi- z@#QX>!#Pk7Np>Has&EM_Lt2u0ndx$ZCY(9A3>`J`~waj_~JmoZyqYya;;wkkZF6L$+(Fd|o z`Y;4;xc~V?2jqT!niS%#X!lk$0V?tY1}P%SWY5~A=+60>usJK~05R$EJtQXiCAS)X z-{87;8G^~r=BmnzG8Wy3Wo4p?(Q}%=r^R3$QJDL8 zx{9{|(U8U~3yU-Q_n1NY9G*xEPjM1l+IPakGi0PKo7Up-2BzXp0mI3pSvp%Agw9lI4UO$X}7;BAa~ou|s3B2Vf~ zOfy^K#qGbTCY+Vgq)Qg;wt>TK`rYd*$B)e3d?9po1-aMLK-zri$JcLK{I!cBuCx1E z&;NSG*mjLdnxNYMxcW8WT04zzdr7vC(PhYTFr(&yOdIB{87t1i$v*S7_|TI;ZKKcHF#s_4`F_4_d6=4aXim7;y& z42RR#810`Ma)Dl$IbK*UA6F`d<_--*U&Bf;q_lZ)UbesMKWIK|i>FF%zLxQ+fq}-P z<%ZhKLO5MBBTS2*wAn`=7!rePN(R!>wThK6@M3QLeafGrnib-o^GJ2}JhPjsnU>tg z>CG`MXWg&s`3&5RTf+3zM6}I)(s|l4mAgd%{cW)Xsev{k2jRFUz{ZeQzPE*~<33_( z`Xs7{?IBFRDgQ&*IR$6dh3htdY}>YN+qR94Z5v1I!4E~&5oVyoXb;i4H^VI2@lZ!Y0lndulP9-kLWP=;c zuLLi19a}tsBxD-!+ClX1lXqEsZjO49!t0rpzlmXqXD5I+)00LD~1i6=dS@peB>zE|I5$$GCTkr^JR_{m~keowjd zLqEFc#dJh_s_E^WgNPs=eh*)2OOWoUw4v8Xq@qt$xlzA6pe+b{iJmU>3Z$3|Ya?8@ zC`zJf9)U-Ax-cFi_x_}wmj529Qlo@b!v@9mJqfvrokXL$E;SNR^QaV5?C0v>VC~>G zDHonG7*xJ!BAh{k-BG&>>9dI?52JO1`Cp0BK&G6)<^Qu8Rpsv7IapcSi>z()ju>^X z+onN*uhjMM4Z2G)!EO)0yMab!Up`wFD zHybb@Q=s!8WNU7+j1*4ek+96LDS0TkL%_vK4cx+I=cAKBe(#(DjSn=ES}rRkfBL`; z>j!~PH$lLIA1;P@XF?9ShOm{$k(0v}9rt>XE+G{s#6))1FL)Qgp8r^}GzURjZ_8|C z%eJ@QsOE;hOUL;iG1byt82l=lz5k|0jDA%40sROx< z_3>3cfxo|3p1$}C1n<)LT=up;utw4kVPA|a=gd}zEoNb3pKPF`KK_{&t~g$(vq}Kh z^JI||0-~D)g+I5Dg> z$^E9o6R(K`y+gLGTp+CN(m=BSjxRrwpe(w*ybO)Ssu2H0IoTnLQuqCgGK!FYFcrHO zypJ7>2%7e+@6y_r?b%;0Yrq6mXd5NBRCt#BY2qAuhXxj1GuF8S9I{&_wk1W{Z@6MSUuF(o-kP zA)~@(6Y`<(Wjy`R_2x}W+KGqXu3ao!TgzAq#zUa)gYk0}mwlMm@D={r^6HvUkmApf zMqs_X_%CF~pq~+{kS{}4UXSjj@)YN1T-c;`j5#LQ9e`5*@lZ3lmNEY;>Jh12Tx^`w zr|D!=bDvm-1UNY(mQ)ye?a9J6#Wdr>5l-!rh$R_D^HO8Ffi1o@uF==OB`-OCufwr@ zGGgwsyBxf~b2?cLF7i}zv0n!oxde)7*$_2=k0`|%m#9gUvK3HkZL1Cg$G7_iHy?&XtS4mEPf)zVJCL?F4$uT z=0+p3!N%O1DR_IqV$&85*v$!k^GJ)(bO& zzfCw8fqw?u-Jz+QyR8qmg|7I2N(lY7%}YHH5I^yJq(F2?L&A}o3-M_6hX#4_fE{^0 zI!1|iIHoDy+j1NpxLeK43Uv*(1Jn(LDhHg?03=T+j|P(rI?pL{EQv#(u?KEQg~Y8Q zmv8_BL|Y&pHD@W87$4*3hvtiqyc7{#960-TnAI(IS@!6XuZVWpn%xL&v4#Wul5#vp zJHD)(!40Lo=!59Q6G;KPV=c4sbXCy)LZV`ye>BE!^NGdp?nbIuO8N2R;|{O~4q`L9 zS>HKQaw?uV&p@MZf%YFu`f?>Juo`wwk015b^R&5 zC-Svy$` z%vyy1o|rKk3(l5yXzjSO9PuyjcUGa25GFJ*bj(Y0UkR}HXkhWPg*<<_S9!76#ZL+l zxu8hxiRloVZlzWoi4)ZjMXgbzglq3xX(t^CaQ!A~I#0&GtO*I(N#4RB)vz-<0E3=< z{Q56c(1ku4NB_GKiiyrrA8$1}=<7SmoX?{gg#(vvYPUVN4IXkMWy$d>QYd=`QcVdb z(Q_*8pch7tt<$u)ABz#s?=b^4?Bf^hq z1txqXDqn2rtcCrJ*WnObE?z^&!&Z)=3GC{Lz$f!jCsMl6;oXl$#qxWJZ`9Y|C#i?R zyv~D_eZxA-Te8=ZcR-qcZGMC`F%RpAA5*?$LXrIL+i@(IR_Usc$vC6NP0-tn2=fjr zBEq{8tO@ibWsf*YdF&#hLL^aU@!7p& zy{an?Vd|Xb6~Y8da^~Y&_4>VFi}`TL3*kqT#H39YD<7@JxtM(V&$-sh%6-Kgr74=? z(ENxT{2bF^L`Ad2ZKh}8e=SfFp@MJTGzW6D1}f7-6Oyf*pV;D%zfMxm5c4)04BIk( zSvYrCKuv2(r6tNnZ8iCvlMy49D~h>KV+(ERn{!8HzR?(F6j#d}_Cu~TJQnV|Kx8WU?&f0D_K^Ck+y;>x;2P6k`JZoIi=h(036Xmk33Xg@sfPxo+W+Tn_^iQdj zJ8!*8)u@nwLP5zqk117#$ce0;G^Z&oQ;=T<0bV*_IRA> zHb)n_Ub+P#*}jt`-Wx%&M45W9$H*{{rCOLp@LHp#Imo&3`_Pra!}Gg}!1}@m#k*Ye z3-c2>HgZXx#@OcXM8a?W4yda9e*Qjjo&dVKBXBBpKU~NlzbZF_)+?AdCUDo%O!^Xd<6rl zOeQGHOQv&ZmmWUcnEj2Xm7Q)f22I|?-dq=Vb-r^;`badJ8$$R_sexhy$^kI4eqW%G zm3%%sAZb{9D{uyjitw;)_zzuRIWybgKH3HA`#U;Dmwfi(0QVg*#&b|%LQYwKkMwk+ zZ}bKHBJ&Nv$gqh*m>#MwPVb*Cr*@;f@;pq6(PeMl(`Do8nqegp!ZE>q);<`F3eJ)! zUxn58tmJ-~&1JHY5+j>? zFHuuUh}XY?88uuH+07APE9LSk0kfUWG)C;S+^O?yVn0f9JjkuiTM}OtjCx^mE@7Gv z3jgt-@aH%8Mv+Q=s0YEJY@BZoX7VY*IP7V&BRe-!9{$#$&?TzLapu$+{E0FfnU< z{6Rzh0o&|b=JvGBQ~IHx=^#IH9f>7=44xM-DMcl0W1hmN5(RH&ReONg9p-2aLNocT zskcsB7bXxYO5S9F!}9o~2FSKo4O{OTJAzQm)QtU>o<|xZGttvUAA=lgbn@G2V%lv- zh?~qmd?$Xkpf1~ZB&djOC>p|H@i_bLbh@Zm3gL=J*dUuOkMzB?dP3q>t5ZUE znGphsu;x^imI*eb*3C4P7eAv{=8N8wU`PHmN!O9~>+odkp3G4w{ZH(TR-v1eYvwI+ z(LhOqYZus7e!Z!(PMb&prdITu`T)@6Wp~C_D|WD*H>biyAIE9#LoMQX`lqzsM5hGp zGB3G1{h#+(s_-G3G}KkSq4~^F{t3RIk%y%V(*i4ZUX+dwef9|}7}(f<7m5zG_}(sh za;rK=BJz~JxC0{NaeW&S3mv~;Z4}*6i`hvZJyzgh8uEhPluT1pWBy#*SW9v#uo&>y z-;N+8oc|C?SgYoyMA@GLo5Qu-4PZrd+4tD|-PJP#K$evLn353{Jh?z5>$9wSaQX4m zt@%Pfvu*HCvz#U$OqorXE7bCM&=#q7({nzFRDz{eY zAG3UW9hXgyPyvwTGhaR9olWlTC$VP`F<$U0G4-lRJ1Z*1mfaBbj>BIgoQeC3{tK0~ z?hmrNPBWZ&pOMciGYlAO935;I(-9JV>T>vjB( zpT){3LHwOBbyvQ3YR9r+grT%#{>|ss&_Dw6hp}_51v;w z0FEGj+{f7b4X78Can^9ywqc@-Yii3tx_#-eR0eVqef<$Vv~GvbuIiWUZgwjR($9s@ zj{kZy2?@0#w7^*P6~n(;3;2EG5j3num>x=**CUyJJSRj#jF6x=4Q?JuLv3f{CZp+* z+e#LWhp}eqGYXEd-o|42e2J8uIr?y)y0#&y@+WJyA;*;_X6HWPh|bQbh@VLD@gpJnFDHHy zg*+eSJ6QhBSFOwbntJmrVp@;x7}zV@Xo1Wg-ahw}mkV4CcgM(GMc0PYebQ4a2+kRI zLh#v7_*<#}#GbZ`GD$q1yMSzDHKGG#v{BI9wI_FO=}>epEB41b&T02WuV z3%)#wvG`XTDEC}Ol+~Flhf`kE6mR@QNxq!I%I{k7(L`F;&G0heGXY|MH)gu_p7n)a zW1aZU?apOQab~9J#S#Qn3Nj#~?hnmmjNx;t?~tWMD$!vbz{oA|R^H+iy6M5v{yeJS zLi^zv$hyGl=*;#h$L5JSd@u^+RElz}8;49H6usDguUGsFpx`dV3Wf>(hZyDEnWYzq znswX6KC+Z7kSCz{ePHXMl7MrGq)@glcnLb86r_e|gZ9`C4Y;0{d`H9IjgbU0m8H8S z`}lm`JXG#FQ+gh8kXH^G+}6oi5?AFT%zlK<>&XDShn&}+7hu2RaozLx%jHsk1(=f{ z0+Pc*^YYs6KP9co=Bb!Wm>UUC)Y7o6{YKYHj#XH~1+AE4HrfOrz}lUJ>R*NSmdi28 z9%cE^4+BDN!A+ZSu5r}KY!hKXdzggJHJYq?wpIFWAto7=S`-$-?U$|uL+wzPG&MFe z%i+Bz1*LzQ*@#p~CKCgzhRh^H-5q_|ey5~)#|Hs$0I4)J4Fc=h>gPW&oi_yvfG(~?|5Z) z8RflVTE^+^=_ByIZE7K8qjR|DnA{D^F36;@u4~tEt!3Nn ziW|Ctx*^3tKF*KzKnjhn&d~+SYLqA|#)T6a>U;*Fs)Oz5nZZJA<>Nm0t|UW{qDxMArtHTg!}2gUNnVW#x>JgK@ResKwtG3VB|0>aDNf%>_-@Xl!j`p0{a95C z>wR1#u1c@`MP(^DerO#t^RP&&PX3{Z!f26kDwJzfz%%P!k|36~Q7N~N-Cls9tHwg$ zdQj{_Q!Km_*_aF5QHn@dJ>TvvY8i~H{h2x(a*}@WG=o1G>GPVh7_Ox#aFw$^?UXh! zjvE$yr82j5i#f{gnbngveiM=|K4%Q*yC@KV{#%V;pC*Q-f$*z(^_MB3n`G81+Y2*Y z3Ep<(p0om8yG`EED%2#8m9rkXS9=bhVDrUjU1LS_5P#r?r?2nBmdC=@5tx|6`{joe zk)U~*t(&A=4xunUOyD>_s-8;*WeQuq$xL#d^IRXiPZmXhVIdT=*{*r^sy=5xA|WyN z)>9f7z~)-8>8KK2O~=PZu`kzFBdmB!M^9!N2U6wOe@c;w3S=BrWedL_Pz>7=Uw>MP z9~Aimrz1ss{5VsyQK`TiTTGlBny<#Bi3x29ahPO^H^zb>Aj3K6jzoOMxA_N2zIu?O z+QpxgN>cyzxCDNsl$_)CH8tRQcwyPL?%}_?-K1-2^&db=*othRgJazYwbt`Xv$2|3 zTiJ3xHk?hXR$r{6G$i`4o{WJmu$RoYyUgJ8sNET!9*3knJdcYIn2-e#_=rQ3ZX8U7 zMsxtaPUKA4Abh)C$z}IgTh)tthi|BX)fVo-z&_j*=RXfH$67EJD#md&zU6DKO83)( zn)=T>^(h|$+N!6WQGrZj*b!k_-CAjXG{n>&@%mt)r>d7}>RVSrncaG+`+WPbwreB! zV}`lZ`Oe?nmZ2liXJp;C1cj)156A6$Ntj)&l$AU9I90{X?>ugH%O&_PdHdA4I3lr75O`k+aN<#RPLvSKdnU-hYUP9gAw*#}JX=I!o+G@Anr?VOpvce3BoS7D> zLO$5>XbU&aF_j9%%F$1ZS+Do@rcfYsV;n@|H^f^@7p^Xybv8{x<(B4AI+rK4XpXG7 zNLAvKHITC_s&BT4cdI}I%c3JgBa2=6lNyqnD6B%|%Y~N*@r;#$!OpYa8F#!YNY(|2 zVqIns5@74wb{-!v^c#XkmZ^mhN%`BPo^e5cl|!R2{!b1E#{~ZU(ACG9xq)T9Y*TP? zh{D?AiOwc$>~Ay}9ox=_bhT!%uyzHx2fZHmtN_n(b&W9|0o-_K3e;mWfAhBDUR)L8 zce00ZhD^wG*vRg$qIq5Sqz={(WW8l(huSWMt{?R*G>*W%6(1H?=6*Zucyody#Dq|2 zFN~o7T+D)WiwG)u?C7lk|;bt3@H4Ymwr;yihcaITWib)GK`Y z;VZ>QX=FEPftjDoj1+0E?^e(`pGiQ*L&d$!mVPayzYQoHAX2N6f^M;4pgqw?n zi}nAYteWK-tVEb?*!po!tVNfHojPJ*6>@xlLKSJ8G>@(dpj)EfzXTwlz2=#knnzj|bwS&lrI%*r z1@eOqBe!f)8-BIii1&PpS~cD+vvw^qjd@>m`{U5$NWM4YM}J`Gb4Cf8vU1(@JJytd z7zxQNx>CH{EHuC)f*y)xB6N{jUIbK9p{fRf!X2ox{0z&Jnra){(#ho;aQ31V4V^fu zXwZw}hbP*_h`d(~NoU#y7XV~s&fd(XKQPUBK(pR@;WlfN_?F?cSc<8Y{@~FwLTZ(N zK(Vf$0cy-1F>=>M5aM)*choW8i7*FNZtGwbo~4 zs*flG(8NBjLQplt2bQ@?F{EJ1v-f%v--%Zk z)T6EGB|Ai;YRik2xU*Ilg(mT&fi=QI(U~K4Ilad{}*;`~9xV zE6(n`fHlK?>T|@q*ib_c?T{{ocLMz8%BdAPkw#~C9_az3GypwHU|WHY7=V76OmNzfVRO~?gAp9mB4_4CF~a@qAsN5~&0;B%RDE}HX4z;Ee+AId2-bJ+NwIZ7&)B>P#-GP$#}k7})y0s1sldV`11!%s z={{{I5KAyk>=Y%k<)<|#ecHDR8JI#o&_6sRljDgOLCZVj=>@~dW!V{1`td5qU)^SB ziVauKOTPDGx4aFgq5gO2WVdQ2=M?`)|06m(*~v`ZF$jw9xJX5f3zq|5l*$%0^!-aD zzJB8IZdca<2qzkdjy-ylX|C#98!m-U6F{R6j1)a5v?Sz``PNsb0vR*&8RXyK$u(Wn zvKuRAM#Et=7D6!zU?^_{T`K8F?n&Wt573y%0({5qEXu&6%y3@SA z`0sCF0qC3O+)UtXE$lJEe_-_q0j7PUnj`i8LB_hb_K%N97HMUa5eHSIF>kwgl8vv z4;GUq6-dUGYOs%=!`2}pR8m;tsVI~_zMRUrB!RUfgaz8J&S(q!LkBmjoCuI-VTK=4 zmu!qSTvgaq!mlxLb}EggIDq0M&RB(2AF!j!J|G2+ncHl zP@lTl)XPb5HslvNI9*`*e~yK&yftHB9l}i{XhxW|2+V#B_uu~(*?RU+Fx8IH%$_G0 zG<_C&km0mits8ql8vcmz9inm)g!(;d+_quRMjc!NeVD&~57n=@C`ZrSPtdkloL32s z!*q52rS^E@j;}v>WAL}enYeVs7ZACmH`k$|G&(#B(2Kg$ayC$kWMuFB>P7PDPW14= z=4$FRRwHpnR}FfzrtZ+UK9=|$Pjn}0EEABrIfsjA_+FQx3|m@wKP6Vn=M{^*9>Bc= z{OtE(%}nV8@t(wZ4Z(nG zu~Yu}1C*z=LaLn~LC2(GsFgJ=Q=f0BJ0uge{lb9d&OBT*;*W|Wxb%AskzghUd{o0>^6$xYgfko9!vQEwa zRrLMgHO1!Zs{7O5VTMlkR?L3oZ#80w8VPI;Od|7i z5m0A|&r?W>pR1sX9D&6d;)M`&o{2NZ&@>p^1H1coGL`%?Nzaj5#{697U9qv_ye_te z+x@5I3Hx5AkH-;KrPIBD<`LkI0G$DwIT`TZT_PmEG)BqC*EOaOL%L{m)|07jY50ix zj2GOgmBMa|in$Rwo)haEzN<(1c|%5SBw8oeEJVo8SM-TkMaHx*mfQ@p`uYLyGkI&_9~$?3Oc}2!z-)wQ^HWbvpE_*fk&lnjA0se zH2en#=ugLabRS#)77@oUmGcj3(^7uo;}v}R$CD>v0HieFyf5)!RGD#vPtr1-unYrG z3lm;+Ya2;&c+7ecd360<=2$LCsow|At6xSTg0GMcr%bfiKl}G~qiguDVe`JbT&hrj&kH=u z@UYhsQ{wEqk#rk=huhQTFEdtvC@ukckJ(5NzqEMIbH)Q94~^OqlCRPJVAM4#OWmc# zkeG^s#N#GCG^;m(ZbMk?4Vg@1UpkmCCx|orhKC~-dAZ1~-8(21BFh-sPM7J)b%W!R zXdd8=sf4wm*CurT7WNX z(A%k1E>6K-Rn=87*$=JItLl(+C#kwyEr5J3Zp`Xj@(JVnf^Z#j!>Hn=j8zcqAr4a10b=`q&ggCj%F5PfRF29I22Cr;ei1`J zQP7(1zckHahqMP-HJHkZnY+?N<_en(KaA$KPsSQ3r~BnG)hDAc%op&LOo#g14Y!*v zBC|O)_sy@TpUtPTw^*-Wxk;OFTcL7qv@RolUi6?52pqzbFL z(&^wJN~MaQ;))@%$>(rsg=ESPU^k6`iFn{PP||pDwy>I6RcL8MH^DAQ9E|(5H#os5Oie;Q4+(e1L$2My*vc!}Xt44U! zm3!9O#tb)*Nm?=VzdJE=v#<;M|MO~~;|r#4yj^njWWR$?jdBR}0&9lvIr`jLeudaY z>j`Zdu*#<5DrVUw@ZUakb^kcNr1jrI}CM-bu_f>Z2kS2E+Z*T?2`wjK6-pLBz&k@b|kkh zP&k~dM_u3MHI*!gC`?#-6f?thSuQ77%}`!mmEo9uBN?$t*b7;|zt*MEv~u zm!+3B`>j0YTO5iJ`fMY?8+O{ z0nVnCp*jmoc;#em6rz?g)bIGJK}|5WKDl*38Mk?}nZqtOF|tlR$X&iDE<#n)vfR}N z{Z6`2rE4#$o&HjlX{$POdPnd2Ls)~0!NFHMXBozHr_sbH^6V!juSd%DA8*-kfj5wu zefHdZNHaHEX?ToD}t*O}oGmeQpRF-@TzS z{dWL7QC?rw#qqR{svzjO80MH4C)W|x0*5iqC#jF|sZJq)^DGbsiE4>L;w`>T8*L(K zi6yBprNyUf6SosA^;@LAka?9;5wPJTBkkUN6=P`5J?am##MHEuHBS8~0e?tZ>i6Vr z#*lP-&jhSXng>5jI%Hu}$`lNb_s1^zsED5^ZV^PvBEOrA+$SuX-|6ZZ(Av8Y=Qoz6 zZa=*>A6)ST@s`gO$Fj*1RUTEBy{Q;E`mmW%`}b#VjlyDph!;mse-S`1A!_J9XPPVD z^ohAmGs0o!@4G`K(`AV+sDQfGN@;uKl~P205sPV(_#^=@h!pfh6~H~QFRygAMS_wC z7j5-lA2D^(v)oLlqNUfDOmIjWY(50UAdV?>ZjAuwuij05Vx-$TthFpp=f5FOk~vnp zsbp#XawD6_>}R40Krl_SE7zAUHY}K*V_XCbNDyw#%k06ix(3wx?=0D`P>fMeZYo`LDE0{HZb6w6F;?EnFVD3k? z{wvkLXlkyv|=YS!3bQK;xfUXaZ^X zaw!p(n2Wg3gbDL7XzT1XOFuE;;){}^c*!yZi3rt2r8{IUKgpvAdxV65>-wA*7WZ6p~mp zJV$&*E{w06E`!?ZccG^=1s(d?2?ZApkve;>PGJ?9pKM5jK(H5GB>1Uj-^G{RofLvq zKF!b{ZtFqKsLOJ zaT$`rvhkbW1X6A{hHJ@ktd+P*j~oYh@yk};ZBT+K3Hxc|K!-KuFE;F-ZddOeS>Zrf zWqnLzxJsXcS>gbaUo5A+zMU_p64W0sbsff_*$kpJnj_MT7>5YBzj51Y)FV zo=>~>&@e|u4*n5FnqI*Rfx^eagmBd|FRuV=VW}*C$ zahiZI2DEQ^JO(zC=>K>n0KXhzT!pguAX7Dn-?{iLjh-I2#e&cYSFPBZ#B!ki5k=Th z?9}$&bxsSK%e;}X^vmmJbNIV!I7Cf?0dHnigqQ*}>?;1?t0>dqj5!P({V9sEF&;d8 zO1Acb!LU(a#U{CT!BszoQH7dR8?_Avy^(1(DT2 zB{Q}S{}v|HzALypfjqZXH|`Ocr)%@DNMaT~qGy%Ge-C}3p}3nh)Z<6)0z16gl}JPd zg5aZgV4xjyzRh81D`Wf-a`sLN4&$YX9_^K#`)0x8!YU~?S*v)29tprCo7W7%M1N-i zAL#38>+u;Z6?F7Xieo97t$6}gi?p*an=75uFg-sqQ2VD(4F9+MKauq!OhMa$?df1E zZWl@xO`AjdvK@YdHKyb+aHM&n#q$Am;f$=LrmzZGhv`+91%bW487h0L|NA-G^yPm# zm~FHn^6*48i>kSjepu&AG)h$xW$sh_Yw@Ab>tMtQ*f&n%`5`ZJYU^2TzaCKV6>YPn zSC_YkmgJ39z!2sBBiPyH!<_h@gZb}=wjRFa;SH*bsWSxqX&9&owMH3|8r$$Vk&zE# za<`Petj!3_P3MU~SlXF&PW{L>6~+%&os;%>i8HjU0Iq2sy~fiT4PvA7fCEQ^(9E=E zKa2F9EHJDJvn_O1lHvOAhEWrx_)_GWL8mZ1xo8atG!TEQ2h}3!b8VyQZ4a2g+RG3b zBLGU~yeyDq#gXtDmA?$|6Qv+nj!iVsNxn--g;gKoMy!hZIHwf54mk%D(LLAdgyX5W zEUyzB}NVFYfoGST8#JZfFL)B4r6kU%f-5)m(r6H67>xscV9bJGbfj}E6Uumcl$?r%xv!x z6EgSIE=uIuaC@SP+g+*a?Z8snzaou5~|04*ymthQTwtmlMII)+;BxrU> zZyq`N>!;q{#168Q|0Ry5aRuVTUG$qcg=$j4K*^cwwMwhDh_Sae5Ok3rMhrU}E0ly; z!QH8Gkx5w8xIF&=7^r5T7=$hG<~LXm2m<#D{;oF1;iD8J2oOrgxViW_VTU>wyiK{~~8BAeuDb+E#RsX1@j}MRZ=dFcNB8`)$f+ zU~eP)eY>P8tvC9`&Q5OdoVx0tLjliNWaXH*{i$5l)?NI*e_+cY3 zheb~w2q2QR@GBKGWo%A!!>WmbWM_PQqsetbF?yN^DbVuq{(5(=-2GEJTsOodFZmFd z5>F%r&~ggm9|Q3fS>Q8Zl6|X#!4&$Q2A87ru-ElM**+vl%;6Hi#T8<;V6XSI1*Io!^`V!AheoxECNVqRvv7#q7Q#ZP`ih z07Xal%dTqRnuEE;8#UVQ!#eLVDzh0b7nDxsII`?PHNN|~hUw74aB+^~Y@|;%w&2V_ ztB9Fk5rfGPjx2V>y`(1B91E`Q$F#O_D}wusXD|VE3m7r4mkWxsENZt$+xh!d{^M32 z*1r9S-?-6KrSVDJe$m>9@5l0qYy!JHb8Z>AW@jTt(tI{VaAiF&6ci)=;u{>O(SbMV zB;_yu6VS7= zzGUTL-{@<{33~e{RF?rWk0ouMLhS99qgqh8G?h*{^i<@kWnI!jvGh2h0>J$IWYdxw zW4!t#_G1WI*4m|;Mne>{c@Tt?aT9*=60(L3{7V{0XR%RYHK&d?DB` zsNOd!F_N+kyb({Zf5Uj}XWtMAVcQ-f-F~Z|(f9J4c`T0>uyxAQpg*O47`D045`}!} z3xT6q#u1tFwMVRfyNezW*5y7z8>wEqR)&egiThB zCym>X^$Z|?x(f))T>(<+?}v#Ay@&!h;vs$dNMpEh*=?@n38a)@RgdwSLyAgwE;wRt zlq^o7$Rl)Qx&&($@S7e@r-eSLnT}$is+9I|y@|q01~y0pB%gX9Y@j9?s-LqvG0>w2 z;WbgPC<)Kk$ZhIE3)1n6{p1wo6MbK&^mbL#tnWXnha$m7NG7#&g}o0oGFZM6E1xZ zJwgm_gm<3~M)%P{o757#(n2Q>;*V=33xZuKwf9mF9Ja?{_&HmxtCQ!GeVYt=pY~WJ z=v!lrv3cZ_nM1s*AlEC6)&%jqv-5x#ypzy^`GTweZPz>cM}?l(s{fgAH$7Q;3!jIn zNI8H!p6@8ZQ{e46h>(c>NpAi<{gX`fuIH!+-8dVb5b^Xu>mFgkO0$}R5m)E#e&8=r zQ209)F0_y`*vF!Np@(+Ss z98IVjbLw-gYbUPdZZLge{14PVd__@)M9;S-d9#)rxo_O}j-qRGL!akmG7j%G0CB9C zqSe{s`CslP!lUf+AJNvpSa;fY|H#*Y+9@JP78`j^eckj174VHeRM<~6RxHv;V+RlE zyJPHbGtnJQx>GEGEClbzro(WO;cct|)qBJOn+w$NDh|^ipoA(c=Xo5{e=9TC*H=s~ z-DYyER>%>owO8R2XN(U0?ghZbkF+@H@iE`@&m+S4grqHNbc{47Ih7MXlQ;8mthCbW zr4~IH@ph%$ZN9mpoBN1u3#@CM2Bo0OK)8I3xHt(W9dUhQPj93fE$kco^_l%tR4&a( zU9&dk>*YTRqRveH%C)1cDl_2dLIqC8XGM=Kzbxk`YAa0svLEEYh;W+Z?N-D3^=)=1 z3xql(g5HcBYr0#0rxTxRgAEHc-xq(%)<5O52d-!a5GbOgDWUdg)f5)juGB8`{*mYK zTktBtu&=lNrCm}BEXjnd+8H1rm>xxS_8uX>ctoCMflW8Bt zaQ}A{xntFbe8s~~Ut3!kUt}qm&;IDT9QHgap*cw})lTw|g4Ik!_dMupj)Bbz9pV^#%S0PiMTj9cR!D+Lpt#e1Xb-&+ON4sC2fr=`?x}~+TX#&M zM!7Fz6Nb15bqJ_Q0ApSBh)xn6iFyVi5_zHVH8&ZA$<$3D-);kElP~-~OcS^^JC|gCI1EF0yuG>=O`yr zu!Avn>x!ml#{PA*58`J}lj@03gy_lCrL1+FR-`fSn3X4&uRQ-qNfGA|Qs2d9X$;rR z68jIxrm2|@Jo}r$h2{3G$Iq_>!3D!%*s_?T&E`nx>h0;Gc?!#EYW#B|`|y?P1GGP< z{mo-u2?n!rh^9l>#?4~S^bu~D)Z4cjYI8F`Cm8W!ljX>GLhUFendT47%PRTdD{cDS?XzJFKQ1w9=J&9%(u6v z!7`53FHs4wPCf6?5F@#VdUNj^urL1(4g0r$s{y{m8lT`><(_anb{P0P#VAZ94<3(= zi~7ZrC<7kpWdWNw+Tn)io9RbCl-8o)qyw?@Gv;1v!{?K~q<;W`^F(-khTgur#1YSF2s&2&wn1Q&w-2nM3FIM`~*Qz0B$P&-<4g718`1-OI4FVJ0+5 z<=`clOEG~Vg13eLV5v0j6m)}6MB(Y;NbN)5JakR~uka(mP&RLuHm&20*GMV_+eLvrvRURXX9RVL+_=9OK?x$8wI!Md!f|xyP;b1ZBXH4m1m86n@t4e zA7eM|Ky=CW98^%=jXU4W*aeyf2|#W$KfF@l&XXx(TDIUG=Y#@{GzOAJ6-e%=;!<0J zi*#|S8qKKuA-wy*%*GgOT=uC0H%J^b;(?j-t)=!O?=t&p4xAD&kyNPblAfI`cCLmo z*)v=lgC7$N&VC}Y07qzQx+m{H&3=KgVyem#XWJxS3qqg_x^a` z{2#u~u{jVZ3!*V5b~3ST+qP}nwr$&XGO=yjw(Vr|b*r}aU%Yqw^l7oow7rsn0aftE zgDJdhd@rkX8?HZ-@Dl)epMYu`0iDE{^*f>!cR(I>P2w)pw44ThiNspH)|R`CQ8z|f zixHdkzHW(W@-HXi`ajQZ96TDq5fRw0?DIj46CQcFP@Q>QMeo{2&}lD6u6f^Nn>b9= z;U`tyCgM}YX--QAp%28qzJZHT=3^0p;~)dq8dUQOHHE4SqCI|pSM8wl-f3*yA*xl= zFs6=K1vn$j5K=iA8$ey+MP467YVtaRvnJfE&LC+el8Qb^F&$Gu(@cK0-K2^K-90sSuUD|8ZeAmQg-@j<_`bEBjJ#j{*y}N1 zaH_Y_4|kEA3m>)@C?7&`&(b@ar0yi2od2DZc;937&|iB0S>_j&JW2|kqm!dgUS1dY zKsvJ@QKB9dA0fWHsSxBwgI)CXZ$#=BLI+}DDLLwsM#Oi16jB2I2r_2n-WrcJ0)tJV z37+FPu!6)<9Q&$~jVgjz^aULFmczklIpK2FvwOJKU<_Jq+_GH>X$hsQc$U3!aM6pg zrf`2(tnF!IdecAP0U+ZAgUshYeR;rJ8XMILbzs6~IRy?k2M7I zsdza?FMW6dB26^ssL;@}hZiva7(1Tb^H1C2s7hq0X>ag7oY$ZHhkNiv(K2d9csWEb zSL6}5jr|`dGVDf}BFCyT1q$S6i#p`HDZsu7jnYXtOBy5gcUB?LMc#@Ho8S-?OT&4wVprt&p^DzXE!(%X)yeJQa%1;TV%oZnx#) z-1>BC3kZL^QGgZo`4=K*r#|JNYi9R<@~EM<`kWYrRsYL*$2 z+|M?3=^vnw^V&}uSwP7BuxmA^9DzMV;#4&-*yV%VHTxk_YQ^eoqo|LEW&dYtF;G8`@7N`r}N)TSB_J_UF9 z$-Z_*lvkI5WXSScN!cQE!Bs-@xblAODpnRUp&yvH+remZLEw1U9Fssca>-|s!U>{m z(1KSZ8x*+)^h-;qhZ9H16^c#PcsD@W#F)~1BIJbZ^rZZ=`r`|_7%HYNj3If0r!}R+ zu>!O070-UZuu!xUDtE#FDflgwzoxiC!8Q-2jvxom8GhymbnZKJ*6Li^)7`F5jAGjo z;-o}x#;3rNxw`O4(i(&L<{xK^hh3J>N>mlPtHjE;lWYJnN@xT}9pCc&!?=MTrKgng zUH`Uk!r3irhu`DbKB9w+o=Ll{b~cKGRP0-%_wXT%T=pHwuuiTJ+YHM4p!!hQ0Je?a z8nAz2uwUuC=)wv8zgo$VjccHe;)%X6nH6O6OpE~ZwU z|3vdciCm6xO`rCFS=j{=U&^tJvQd^vhmKZxhk-w{FCOr{946rM$RQ0WGM(B}CR~4W zn>N47!w5a%RJj^{H41Jw>D$Oy0QP3g8`mSU_^l$}voMY3n&{Wq$XjF~SQUDNz7lAg zFX;ClFz4ehF=3uZ%|EC>h~%4K)UB#c=I~v`Fmk@w$7xisu+>N15EFb=NvJ*PXd-{y z5m{svnR zBUWFl4=#ep5a#Y8d-XQSt5f0^0CeX>rI-a)_f&sh6-puHqS%=)z;1S#Uy z^utvi$*8llNBk#Vth0jm7Gl!(q8J%Bqv4K>qP67W;0UXFJVmr3Z|bbh+-Ec;sG9Z{nG zK0ntddbfG=U3E+;LyxBhI^Ksed|+F~dmdF54`?! zWVRD&UL=Q7U(9@=tz#=XS>cpzrGGIq#efkvqrWPInhE;UfFaRqjoZifdJ^Ve(AgM7 z_EsvU;!a;nTp|3 zn5XI);A!qW7vppQZkyEkiNQ2LOw7g-3Qxf(UN}6V`c9S*=Xv{X9!$5{;}6z_t=72H z?_u3tEo?H$md>r1(g?8_oiY+J2)&ZJB)cukvk?a)-l*0$5c6H0l#}mjCgphho#FEq zz&%KMRQ+}|D^+XApR{-Z+DgQqNHB!6SsTl=HbX8%x`}gZ8~$WC(z+pVh{~2tIqwr6 zy1`V{(ws4{f0q;rs!FNdFag|B_Xu@yu?hDdmu>O7xWW?qDzIy=?MJ)HOOm;hePyyl z=OJsqvIreQo#_1xAO?LzyPvxqyhCL|4&XlmK5wh<*z$ehns0!HSrK03sGfSE9t!*Z z1nTv5E#)fQ;1H??l(eJL4~T5oIZ1>7s2<>ZHf`%WTI<15Us+NLG-YKuJvayN1vokd z1}r)&`W?bXDAUx}h&uw(b9N&b_?>zG`xUd*#lHm2l6hZ-Fsw{{4xk=R@bRg%WFWYI z>LfiM-nhH*Uc`-4thw6@2}HkFvG9MRa+0gV$-O%8*d*jzlCTJ9Wfxh?%Cayka-r-6;TGMUpz}uG$_@w8 z{NCaBP%F|yIZ-ON-(!)457xTaNp*`4-1dTKjrjKY|wB#YhMX0~#DxcZ_Q&}WO;k@q}GFnE@J<6afS zy@+@B{-SO;N%ij%z{HT^1(qm^1o`cl+c|8jYvmhZ(xYFR+lo@^y0d>L*UQ9kE^h~f zKM^SGSTuK`O$^0#d_UUiiIM~7>_Apkv_-|CHw~~4)Vi#Hh9%*6=7fKR9tE;=0vyyt z3w3dB8|Z<_go`nW!qqkbI`IHz-#LeDM+J=A?T;|#f(6oxY8(4v7Go{Q2T=sl5@b5f zmmL>YX|KCum`9T_SlgiJv~<3v@m)7T(V0EI7JZuJb#RP^(lFkQ*C2%g{RsUji#5B+ zTZ)ryH`=mEU;rAJ=BKg!aMkP;D$OCfqyLbpa5ZbH1w(4T$IU`bH{{+yWyXVRMjAm! zv)&QL(JB*F?N3K6mCRbe|FAV?qB^WNK5wB$qBX3LT(cA2t)6USQQ(mC0g@DDpv`G` zJgJkp)AQysWA!rkCArK&t_R$@WujSuEA1G3xrJL{%2dWGR^iTma^l0GASQK2MZ>Q} zWuDjVr(Z%g1amjA(km(oRABco6joc9r5}V&3BZUZ%G%ND<}cN~t`N)?&^>aFah-t5 z79V*aWGzO}L6LT7%4Yclip3)1h4nX3UT7!P#XGFDZ`C(c&9|E+9;DKM!)F9Hk52sv zJ-pR6FEaw3U>h;u+Hi{lI{k*09~vV-AF|66IU+F|twx@mqtlm+Y_*0zieT;ceTC#^ z3VI9rP{%7TqlupN&r*OGJwOd_KBKnF_o$zZD%`)P{fM*^CN@ZyNexB+XQJEYPl4oN zf^39m=K?jQoaEN-3w#*gC+pbhDTGZVLB)o}un1jKImbVPebYJOfZSV2srk#=ce(ay z-s@G(ICTZJ3@NODOL&hm&oef(bzLQirg`(Nkj)pP?c>O--KpyW5e9-?{e85LC&5q+yA1KiWKI)hTUe*{L` zx6~{6A1x3t1c$V<-Y`@nC?kUIhc%}GN<-}yjR(VwAb-J*RrC9C$#f)~Y$%QnQ(@wufgb2aRfFg9fQ|E}FL@@ZmUihN1YxQU)`N9DZqj ze1`Bov=$sKV<*L~@#D6QqL#Zm7uK`;-?6cC(7R5UV*Y`!suD_xltv&K(O}2+@*FYP z;(79fcglLgXYE@tSc1>vIdnxHj->p@4j!C1L5qeEuMFy~OHx_R8`9Gzht+qe!z^Mj z3MC%!H^dkp9>osY{~lA1*}%z1mBTlvnm|HGM@|Bx?a-h?@^uV+jaJ~8y>Us~6H47=ta}$Gy0SrkO;?|$n%i5SAJ;3c>S@k}wRQ1XBd~w@ z;?Ra}Gm{<7AQa)6{hN<1?dw_Qgsfnke?TF$#Tk~HDBqjO|oUtM)oPqNa#TiaF}zdVw~oTqt+ z`lg0IxE^3F_2~ z0!-lsmb; zODK*T4DX>6pu16;!JvB~!b~x%O{CJFM@mXfFk0A;xssWgTGXsQ(I!2UtwuVFH!9ur z&nHKDO?|wdlY@#AaTF~m3I+tcK^AlFIQ0EQr%om*r_-t@qPhGEY;nQu?g_WHW?>hV z8xDYKTojkbq8FCVUN@JyHFe8@Qc%(SM{EsH8^i-FgSi7i@fWx@5i z;&e`J9}=_He3*sTR)YB1c~VJnRFAADi(7|#c8H)ytY7#dpjRE8)09n&^a=RpCE=+M zRPG=w3pJn|RA~Sk#-+sZJw<-T{J^gMHD~>sTq*AzwupLkmt;L)y@tQztP%`snht8b zf^?%aDJ@zmr!jLOnSxcRhr|aeb(~jwJ!ydrnGc5wr%;%8yq^JgLTX_R~!WN~i9xH8E~(`yOE z*cWI7?rG&-k|iBOKgplPvh4wBEgfQ9kqlLHe@}ehFUZ30CHu9NGyt<{>7SR{w!{`G&8%zknedADVOOD_8ZeP`Zg(;+5#`;iz_u~5%n zEWB^bV`Wifyk$rpNzX7Vz|^>S(%}ATLn}b;&`jeNID5+^VUJmSBj*RbS0DD+?dEhR z1)!y`0c!N2Wr$@ez)Z!X4`eN6IKfimgO)r{arbP~q0i|AHZgSjV4#En-}$1c>d7WO zUHQDz5~V=e_6Ny;F#dc4vaxiny}*FS0p6&(i@#7UEHNmRf$9@$w;#NIy zPUK4L`DSoHC^8iYNBz$CHVXYcuJQo)VUK_B53#b>L}biWt^xu!S!D#HH<=D8@(i>2 zn|Oe%3To!?$w7o{kQEkW}6o@g(W_)zmBC`Ou?hlkgO56ly!Gdl{NB?z zWM$xZOXX_PzhV0%hf{{Ii5AYlaNrbBIAh%2QLrx_f>t&NJ`IVQDcfJfM8L0US8>4Q z#|y;nUs2hBmK&KtF7#~Vt3nJp$foIK7!e;jsi`ci!9)FUE>z~yY)JYbM++M=x|8-& z?!v$|yv}Pw$qNPtR*8| z2T!G+Udz*A#+T4dORG@B13SZl_Yf%-139Z*b_`?Myqx`tAVt~G>~2l7DY$XvuRDM? zEK}@`XIhK0g+%0O8Wt!R)dG-;SiZ{cm=J@tjY1_cnF5&;ea?KfqV}K-`(KPYd~tFZ zWFtZU$E0@2PYSKi2-_JNECV+Xk&v?;x@Q7M6NhIBPP=f`$ zlB|$=b!d-{Ge*|JNuKy@6QYCsh^N2kFoAtW7bp=%e-PA4SWWqER1Z^q#VjyF+gf<* zseKnK_JpT>-?xRY80jEG;liHo=+*ztZLw_l%KT}VPHNJ$+yuB+i&~7xgvPQ_+4`93 zdeOg|Pl{-MlUXfzUUL`g&P3QG-+q`1PA-WBQcm}+>k`pujrm%D;#QB_QJJ$$7P+WT zWV&X8Xpf``ZcfdD#8J`y?I-I(xFR?8pGkOF+d^==hzigOFHcIC)4bSt%4e5i$IaHY%pc8@yphUY$_8m!-LjC`gb zAxE{9WXT^iJ3gV?!ySDF!re5Fz|mhuZ;|K34Au}CU*@~$v81&g_iF@eV-J;e>7hI6 z-Gt=UrgV_T)9_owOh}$O4#YU3Ft=#j!K`9!x`5bdJ8BFc6=&G$&WLFPf`Lgg2W zrrY4dmymhoLIypjegAp>5zFl2g%lH?MtCc+XOTd=!G6zi3UmA`)m?9ez$$;V2{^T~ ze_h&FsYk6hh0}=EZx0FMJK?fOUL~wVxAP#ya@*K4bdz9^)9Qa65)gi@Q6Gv9yl?t( z0RqghB(uI0y_A5^&Zp$IQ?{Sik&D!r9Z@6SC9%OZ3E%8Gxq}jet_nNh|NhV`7|a7r z+rl}fjwMpC;qKI>+Z%*D?lt_?^iU%d7ge|Dfr)Q}><(9F*obtY)|vhb=@|4B9m9NN zx4=QSgjVQwXd13dN~*N$a!K{Q%pM%2Cs3bL+4vTTaSlJ&z7c^E{BgtQ9Z1)lo+(c| zLP59epyVqG*0G{evl(}0C?R9>I~MJeXw*^%Jwl4vh(XuR3X8j18a&vhcs2>lS7YU( z=&?=W6i1|=gVa)^XR@j(aRwN2P5RgLr26TuWmq!3YK!lse=dg7zax3m^+2j-x=JN? zL6?IW6|;<+njQFu3(iB)@4j%AGA7D6KcblH^vHXrNV)jC;EHU_1*~9BT*~M{%Je!) z?R6Jc^}`eEw(+F2AwkEF3zZ~v;iM-NWSgelwFZIwVclI4oS!(jgk(zOQ*0R9n@W+V z4D>UN?%x}61KKdhpi-45@=C)@)NHO{mf^TJ*O=NzaJ$C=$tp;nDa+t9-wEd>KmY6a z<}_&Y#3>Y1=29UwoT@@`ThY+zQJ%om+%(`=p=^g?QWP0(wSBf=4_4kM-1Fy|9YK~w zm;ntzuHz^Pg_L-3{LKw`3%D%BW9eD)!Be917y%dm2ch+0g=q_Q-VSNPJqd{aU#(4d z|5e_fQAfECpBV^^j;J2}KGsH%_42Yl6SXURJd&Lu_I128%4IdbZeJlxduWv;z}V~%-SeoxS4tF6o@TyRTpYosO8Bk zJU|6C{z#O!rbfi=wb}*DBFW*}^vL(&K;GyDaP(KeS(9B1ZCq7fcRz9#@}*RV>u%-= zFBDP``k&)SOK#DODQ0lbzNaO#(E}qqj(UKfF|8|%#nQRV;Xstbzz*CL4K1?r{`_6C z9c8w_*B$U*ZZARto@()9=5+4!y6cs7!Wron7-f4iTnvETu_9deC@Z#!Cj+Cvm?%?s z31;Zpln=gJIS2Ui1s=D(Ov<&M#Z3(t##n8h*0vB;h3)GeWc-RjBA|%dR;R)hYi=y7 zr=)Vhd>kO8I(9fqM_iN|hAne7>l!`OetxhBt_u_#h2N{~i6;3__nYRuWqlK-EY%pi zkj&w4QeK#~M@0f9s@q;admli13jPu#%y(l|g#O)DUG4eo!7c?p%B`=lp9n0k=sV4q zCm%h0Jl|adj(*X?cqeu3nV0R@A5NL#nrB;74W|vnJF{vo=Ja!Oq)0EsQ@2lhe%)Az z<->U`hoDiY`WTf-ZFYhH^Mt18Hd!jx)AX61-if1pRp#4?ZB%p&H;J`|kS$RK;D5Oe z7aB&H?yVk0x%P=bS)ScfFz6dI8nGMyKPQXvnlYay_YRm{LZ>&qU)rglV!Q}2hD_lD1pHt8Fwd4i*!-uDk2%~FZxUs$PmZmlHq%ZHf5zuC2z zh^%=UFs4Bd4|rKB{w1OXovbV7Vh_hL=xfOAA66XtL~Lv2b24mZ17i-rwEw#IN6}RL=TOJZ%b_ zGf+229m6w~`vJm+13k6Wt{yb2xrQg`OnBV4@iX{!Upi@3`c3UFYF)e^5vabaz-Q&X zBYIi+qW;7+7?4M(##Va}(mw&owI}-Ex))^A=GWQgIP&WsF9{h!>-!l!ywyX%q0CZW%#xweI;iDv zQajr`+V~s4ypcSYzu*if$VOGs@F-5y=G2V$szfMUt@KLldYFTuPitTs&!U*aJ^LA7mBGkO;@-mihWlm%G3m4rVixV%oRXiQVW*#OjV(|b z6BdEhZ9CVDcsL!1SWt&I>HULg*-?r{79MzR0$fF~GVnm{j|(x1L));^dK-sjig2cF ztD5}+9~XSzEvjbC%0x#f;qevuJhCb?vMMEh@-c*SqIthe3d&bp*Ca3Z#w{h_IOUVi z{41>9cFuqS^Wa-)bMXAyaf?JpsL8t8q4ov%5>t*+xUy%iqTm6hko%@+VX^yD5Q`MZ zY1reek7^DIoL$I@c+Q_Poaltpy_CMC48lVcZEFLhJCUjb0}sSaDofT@iK{@yr4 ze9BQmuTt7XsH)FCB(PJ4yiUToYPKy_G#%pz`WPvT;~c&^L*$JDaqH1bpp);OH#IZg@VK1c zYKWR_vCOEuhuHIOA$jk9iI9COoBy1m!irMdq9jW*E;>R~SAv6Ed?SvGkVM-l8aQrO zMc}h}thnO#Ge(FqB_%ezs@ZvwoI^QDQu=bSQO`C&ef^rWCSK{_+p8phDZ-D|hL!d0 zlKM|GTXWJ#%(60FpCTYMXOQ$*^KZ#3XD~54>?&i}n@ex()9*;274kgtI+FtxBVAF3rn^GkR1_!7haGw>d?xMCuJH%n6_QZj+=wW z6Tty1Xef;S)5%PtvXh@XGE9PcD>(mfh?pb0eAtk#M&zopOefW0j@x zE}P_};TMF}(cPxNv{=+CcM2l0m!ii4(%^XWagB@4N0wY|RUdr;hbTDj8V^Dyd1`Ng zbIwbPFyXp?&G{&?lGX`x5tm^}xEV+FYnO{ks*8j@R@{k7V|-J&E^8ucO^^q(%3i$4 zyRW!4b0u;}g!Tmr%*$=KHHj}$Fge@NMNS#54A5Lt_4lrEIdw~~1OC?Mh?+E^$B9Gf zsZ%$sF|-3e1vbb4UIE&ta7|!e^~CR5Ft2LlZtp--4w>c57I+y9fRR7FM9kX!LCS#?F1uWYwNp}l~ zD!Ux&L}Y!r+`9Ra(OQ$dq}H35 z!=$%T8T{a4!O!B`89oU8qnaz0*UG4TeW1NE+(cb2Cd@0?yr)T0K(N%Dl*pGGY2S89 z!h~F>Ohg4efUug9x>GR-G$e%J(FgqacHY<2`zQ*2H;Ge`vz&6BC4^-r4Lf}=SE!0X zf2#5qvPu~hcZa&A2^mo@9otJnY*!hcp!;E_Te{Sn`BE(0g>*E9;5pYc$1$r;Pcocp;eCY{kp33qb0QZ|8 zTDcW(j)Pme(`6-X(R&%@-&WtHqtCsmXOtGc_} zXP5Ezy{Ma_2pvlxKlsS(#q6&eu_lop-Rz4mmdaW`#@B;AI6+o z_NK(;jg9~y(ql8i!EvS_I58I5)cfJ0V17N+!=js)&z$nW%;uewQvMtth2vP18>q zSct{8{AT>|pc_rJOn*VQNpv@QA`P=YgdRFE4E1nwDJxzdPYF4oR*$tRnGDPZFyYXz zL$oO$^)vr%=@p?vNzA~+&a5n2BEpGF|II(T^Mv!89FaiAJCdlgZS?J?n?X9V&c_xw zxlQu9W^Xj#B;|-{cjJhs0arD;WiX?SF*)GGD$453$z z9tUNFl}4y@o%U9v-MIKfNL4RL8-D|7<`Rj+$wbqxP4#w7)h9gwO}|3gQ%|dj$>n^X zyez;$Evw+4v8qV6%?UuTCaLJ|^qC3=m>=LWS7Dv%P#f>!IJ2K<+69Y+t1+)^Rc$Z0 z%-WVZ>6qXl9#LC9DA<8|3jgCmMTo3x?2ZoWOZ9eEIW9sAV8>-SH8gva6=-kzvp_Hw zf(*386ED8fnh53DLOA)3Zs+#Rt0zga*_+8(Q5}W0N-j~)7z{A5t{F|c#q1Zmd5(f( z@tq&?0Pc29$8Z?oy~s;746(@2@Wole(y3T|HhTE-#q>dZieb^K0cuuPIECE5MDYPI_8cY6%O{NvKF zKGh^1KlX}{(q%D9JIns<9%NO&p(k{??_6|3-LLX9IRCG4nwoV_jr+}Y*h<=mNCHy} zhak`RSc92NBiD%V^*WaU;f3N%oAC;v8zfy;X$a{iqkSLdAK^Fb!%g<(m_!V{Jrtuk zeXZu)7F%XL5!A`{1O42pJ!5i1JZcimnf}_qm)`u0{~u#6O&WfyeEq5T*bPj?tgBEo z6s>sRTc^5B1R$IbZLb9oRbTHO!K<6JhHV$xWgH&H*QJeZh*H4{$#Q?E_02^pTvkuy z)o1BCD#Q=%w7$jw_(5mVoA(1(YsrzZ?RJ}zk&J>I6 zIXIa?u7*VHrAj!GR!fa3Q@mA~lSLye(gjaKY6#iD*Xl-`eQ>OWJ=kC7`d?kGF8B|7 z3*%5bFN&DwX^9o5;vVdAPng{o|Ng8{1uYMi6n(>xjGAChiHA$bYcV6jsOU-sJ@#(X zW~&}o^ck8d@Cvdy_FqaqWUh()h;XNU%mS<@Y}|2lIb-fJLLpHO9)_u09^i8~JQmo| zP>rBAzxD?ZhO{bdW%^3$f(9D^LHAJkoR2~)raj-Ap|J91zRI@e^i-b4<*;4fIBosS zGx{XqYakS}xvUY>?7q+_P^7cpE65lga}uVy#`Vf|lbjb9Y6w{!Ve8jAU!BCQ(*iGk z)s@IrL6X$6?6{tbU!V<`s*9V913CmnIAk zSrK6YzZ-SAsi+w+EtGTEnF9e)P+(s$S+wHkU139uAas56K5hQn+YNelr{O~~_MLD* zGxCLy3}z+?0i;-Kd9K<8IK^wHMOhu~e|NOommgiIn~UJR+R+;thcis46_ZeqoE)z+ zK9SX-Kl8|-fDjLL6aOll+}HUf7Q1y}YY27{)k(nqS!0_>rx0J`>{m3H_;C4C)%$sJWZ{G#P7AL1} zVa12nn19fG^5{u3ged^Gf_T*Kf-cC5iAZ#Y(<NECJkN3WQhU zJ&axd?>Sf_qXz5O-$5)7;4@m##sWb)nPn~Lqajik(w>_qD@8kACUuPETqTBTS#G{I zK8a)$7EHks5PQ-!kboH!GwzBx&P*p1?PnApp)wekv)W>UYf@*sD+5hXV4Ejetr^MjuNBIK*KYdBNj?0INSUrx1>?9xrK<%(W@-L{y z=Pq!19rIiPFNm3he@t7r>2c$%K@?5u_a0^e@Is~2C@X9iLEWRTMNk19+#pu-A~qIh z=@{xF;BTZVu7}$Ze?7hWbh^Qdso3h zCPfPU%4N)LQ$~)v6b0TE@f{pF;wn^1z~i6p#)Jmh&-gcL^YJ2eYGq6(Z$!o)&IVEQ z=Efik%}(w9s`<#9#OcOn?j6i2Sr!2g=p?&U-W@vG`rNwVqps9!;Df=}*=$)YO)A+S zjbgOd&x|3}jHW1PB+u)?e{&BVxnkCFq7P%<-J3yL8e>Hgb)Mh(36Mn#A9nEOm5A3v zP)oeikWd+B>jy!+b0LacijN*OjFjp#z9N*@QJDh*IfcF}6}Iq*`gAv%oN#k*q)+@F zUMt!~|1zsP^a5wb2wd7Q0*e0mjL7eC1`*ZFUr~SBcc?J`6}m0lqe9nyCzvV+mirYl zebmXkI|S7&$6HkQ_cT2di;oXz=YHwzZ^n`q7rGIyZo$uJ^kcBb$P-f;!=8VK{EOJJ z<3%`L<hLT2k8VR6XZ;ro}6XZN?clC})-@5hRN_Wx^2A}%`hNNL-vEvN_$#R&#{2rIF zg~#xrxJ{owhKTy(EoLa%aoHIV4l{T~ci}ht5y|I(>9} zsJG#!TLcZbTyVNv-O9amK3&lW;Q?xH?0d}FU?kC<_OJl7a?Uwd9W&xsrs$=V-A)TY~7-W0MBGTjh?S58R3RA>* z)me3ta_eTc&0{JI2w@Kj3hVGBN>xL*1m0bhzhn$()fo1WWybo+ zf8g74R}K8G%Fr}KXh7FAv04%iam-377HZ*svR&sO#!)4fvmVe&+4+`mL0X3VdUDxJ zetI@gML~rPP)nn%s`;AY;!Y(S&*p#zr0D{0ll5C(hrJ3`;29QwFVPeVWuk-F69o=# z9i1+NJXNI!PQo}8(-HjU;jQ3g< z_%s~zLkbrn^4sX)vftNoFyKbeRu^L99|iz{X^tj4EJkJHaE`@K2w2s{{h0jdE#x2D z2|g;(y9e;`*37Elw7<{2IClX=$3tbL#Q4^Cg~&!mc-+4%rYv;5ZyhcW{Q0DNug>(C zsE5f8VLWfP*?I%v&p!jQbhsdR^(o<-@q8F;o70-ke8it`{=|?uhDBV0cvmtAv7Mje zt38Bpyn$Wy)lx&u0~@NtdCqn5iiDjqeAB(T#GQ7a_-58ipB-A@#4IGK-U7W-k-OiP z1#d+3ZMY{;L!Hg-yjB?cep+h^2oUs|r~X_ULR%C;D&M*RO z?&Lc11g#53+d^BgIkyaZ=W7dyKD`tdUt?}P8w&JN2m+0SOrC|Y?(eNtVt>+aCtVEk zmCkL{E;q#a#X4U0H?*^Q*`6UlM4Vz;_I`*WR=1-G#Q%{k`w{ix%>-PT3Uj-uy zPBjf>3mo6(bEw2g;aDq~-J+;R@sW^@u~r<$%AXmZmjt; zp5eh5l{PP=BtH!&Y`WjoKJJb#PGzQ#ac;5dE&prW4b9(;Y|OZo@hQeh_z-k4&bI(| zKV&WZ$bv)pDd;(VN#{72WQ=1H<4FA2Z=)KAv+(XCQ;>W|$6@fn7nVb~+!AJxm44AJ zu`Q)YcA|@bMxC8@K2yjD`jCaXD_^fep-m!4@!AF(b?cl>Ykd#xcQpZUyUSTj%beyCYqbYr)^XI2X9|5D?qtZ=_wdT-c%Ecu*J0&jeM))NZLERw2echygI94}zi0AIs?f38`{samL{c z7H9c2HVAZehAl+`wNMfB24|cMWvdqjwwn>{gWEHoZEUq|9ARHO*E`S5%uThO(XC`_ z%6*X1gwMIFx5ORrEy*OmJbk>TJfg{rg;~F>2H`$Zp|VJTvBT*CB`axCPEHlZQ$QF# zJr^XWc*h(;<6^IYV+2j=!J$g=)sF>scP)pUPeJv3SzV6{N3uVp~v2fyu_x zd0EJorQ)ASGYmjDruJ##k0fH%$$w0EP;4n%l{-(})r+86OF79}|AqC@IO5C-8Qogpvfs4$`zi;c#jKeZ!HaI5K2t5rfzzIk%K!?J?6 z)82W=7snZUW)Nnxq?JqC(T4Se7(Qb-MJpm*jS!vg(Bd4D3yrQuLuS3FEl4w4D_89I z5nQ;5Og(;7DGytK5t_BO8<&g1p`tiAu|aE#O$KRLIMK=C~^+T$OrxmB^q zJ2GP<_rPeS=$*BdHH|*git6g<&m@GCA8AY(4S_8m$foGulR7ug32ol!5^)@=hLYur zfKN11{(z?PUf<5@yPU8w1rn=)T&^3md*%?$XV)VMRpE?mtiVzvGA9}ZL(KZ1@94*Y zyL)kP=0Rb|u@CQ`QnTIy11(oFOl1pmurWAdNem|4zsMu{pb;_il^V!gjhki14E^M# z+f?j*IS4X!Fi-m?ls&~TM96NvXcxuMqa!{m_Mp@_q*;<|aP6*<62Esz6Gks3Gx*%r z+EtK?&3>r7>OoP*<&s=|fh2L$u>^s~cKF%b*5ya8cyR&b15Uf-f`1z$e05JaD>y@*3WW}Ak$ryR0 z#@+06!Uw=8#yT4Xq#ypNa1{kQUszN!C0*cMn35I^;%b$uLom*W@;7EvsQftu`0}FT z?#u2{ixc|`Q66!fuqi`)#3r>@SC6*nYdYtp-A`}T-qp{K?k>MM5bA9SLu;`E#E7TD zCNTW|-5^(^CfQ`R{8VW-M_=PqPA(Zat|Z>G*`XdGD55|fm{sK|)#7OB+zMl!@Fa|D z7c8E5Pttla3p@^W^N-^Q`phmv=a9^4TV$jeq-0W!re}sFZThoq+bAMmHJsZRphBb1 zRlHZ)c(qNzX)T);)p_co`lHGAKk6;8s8gU<0OH+7T?5b=fD}N6OjxCl7m5Av&SE%X z*TFSNR?MviG-+YF{EBmD&6E#qGPu?FlnCBKaHkl*uH(>k!L2>tDdFq3RWOv=K*{fz zW-w-?5|~q8ww}bW#wEXCW`g<9$&tjjou}`fC}94`t>TWK3`%s2D1V#2;}OCqU@HkT zy`A7B1mTarq3IY)(hp&{3)pcusn?rK-c&DP4~swnB@^_c0_Uk55lqjLpbSC<{9 z0@K@i_%i!-r3#PN`+P8eibfD}(|ggS*&#u#9RQ?}f(#f}jmgl!gq5D1k%gVb$iSF|jsE|| zu}9elhQu=q&xRYTY?vD|EX~%OYpbhjnO0V&hNgxa&DI;OzP7oJIX}Ce+rAbi?Uo)D zm9-VXK=(i2bmZBy4?SlYqKuuAt4#}@Hl4CtYQwWFdILQ5a_=K|u>*avSEfzE>CVw7 z&X>a8;Jy5Dlq1VU7tebIsh)VCj+2t#4w(3M!htUH<{(T1R)sPU;~2Ibrd=0;FV5-< z=~!HV_hlhF80kB(L^!%i0Zg1qjf~Q=MrDCvi-x`dOa{o8poRWs;vk3ovTSawf`(@L zMu7LTD{*5H)K`R}Ys{`DqSnT&(O;p3xJ4!2Fes0puj(S}p<;;{b!u7~c*Vmqw!Cs-s&MhLWb<8DV~ zB#EVd8x(&zv7O`90ls}k!<(*5{ps%`LI?#v0frqNSiBx~Et1C9e^D7(8G-SaK?P?| zHAeWrI0s?&o?C+5cCVJvuovJdv@$Q@R9YYT?M%IHE0=X_(?C8#^`Hzpht#irzIZ20 zp1Dpid9Rj*XoiA|uvn)DN(fL!iBn3|2v60hOkLMUJ zjVNPj2{t_or|{wp4RgMHBxCkCQsBTIAU!Nm>tcVR!+WhhtL16;!B2?ft!n1LpwoVX zHsdq_tmQJkpL5X;(Dkbb>3-co;{=br85!iuTxGmHQyT6g3Iu?9+J_O?7sTVs%)XENJ;|`iV$4j3Tm34@w%|I}M)*Dp}#~n1dztww$si7H!X@!+0aoe}1}83u%>Z_Ht||NVG9%9YH6kw+AeiaGYr z{s-|!R6`JlnTK=^{xp|K!)joI}ILI zt+`Wf0PQ1J_|fLhBE!4nRPY)US-zFH7Zs-R46*A=LaI_c#B;l23s!V4vHh5a4LLB| z#Ht76AzL%+G0b}EC|PnvYR0}n%-cgFVd^Crn9ho{)#a8-FMv!T7x|(FX6INK!#e%} zdOpNo;g?Ouj3nZ}kj&?F!Aqe>VQb5mLQk0&Bi~{G_&~bdzmSsWh*0+KD zCj_(WZTnc^w})(#TLocJ&ZKioKt^l(_Y*P~lz!FcTWngx*;76o)>rmB7bpgZ-Yprp zr){{DPWbFPTk2-~^)Sij(`5F}4IMe>VL3ZtK>G-_RTq?TrV*P(jOMV&RyC>I=H+_O z`Z^z7H7X{!`*F4=?DvF-;)wGWBT^hC;8$V_8j-FC=FL z#pYPo7F=Lz(#eNonz&6L;fedy@nSF5GFu4_vdhC;PT9)`g5&nFaVTNC9 zK=*focYZqqM?vV8l{Rj>Z?&bV-SsO~_!5aUm^Lpg~t+sFokcazW z70|_^v-bH9emH)3@I~a$X&}61;~J31@`G;n zQQ%k`5E@X8$wET(+?U{@X{7X*x^U3J|9zCdyuQMK3EM%(Iwn-X@o#1mAEL>AcQOI1jP$DU=kmw86vL-+%R6OBW9OS5v|nXt zEs)xn2Qp)igg(M0hMRg}DNdv!mF^J1=4y7FWP-*%c1&)`a($0M*F zo;#dX6&}m!ILb{8O@1uuh7~xKF1bKA(@$uJ$-dJ)M1iBeCT<1&aa2Y6Fdbe3w2$E@ z-HpU(LAUHZ53<3I|M3G{>F2?6Hj1HfqLW-PwJiW?vg9bIDh zlLlkNgi!b+cl6hN`QLr<>w2i&(xuqMkzvS)=aBe#qMzcC9vmFeDrG9&5H6V#&e-lp4oN9qw+~#l z3#;rqTD6NW=q>ijqK)$?h@;~H(DUiSvxX0vyd*Z}iW_QQOpvw;C{A}-)5@%PbWI0G z7t|ZJ4E$@{6{T@Qd-9&~D+J_W702)GzyCT15fycKyl8j4R6cKhi8r7$2%2&b4)u-p zMmvdlDIBE6-I!3$ysshvcSU%<0x-Q7FOqHQt=@6E7Qn*9)5A zCgAj{UY8ISEV^(oXxpI$W+!Xuibz& zHpg(*M{YIWk^QL_h%@imHz*V=>zFq1$Uu#O%ldQ7m0hchD8)YwYf%-@K7^mULBhk; zr_di9*a3Eu*Ap^cp@+J~>2yDT$E;iIE$~31wf$E3{Svh-iwCavML-@6!YLybNywo8 z8S68UpQN9C2ZL-c_gP7*gAqNID+ltY(iWG)kffw)utC}*UnnIYk4XKvgQi_bS}WEl zwCN8K)GrC{+LQz#ChfTHHz6AQg1$4&zof{WRIzj1ju6x^9(qX_1lkSpAm6B zFjNq)vMaI&1PI6chq31v`7zzWQ4um)CxHhL#uxKK6BYG z?CW6DS`WDd>tJMH=cWv39|mO8{#u^RtaAModUIEWm8B|Fd0t4U_}1O(ua2#l!?94(ELLYd(bXoC|7>61=aX|lw<4)`Jjyc)_Z{Z7kVtZrijH|$RmvL zke2xklt$_o*|Y~Bk8Ei^*VBB@7bZ2Pu!5^Ve#T^X$<%g~AV2Q4gU5|-7?xy6spg1F{h^qePj0#<^cM>{@3;w^b$~RM^kz^ z>>*~X5|N>|i8SnWd|`eTo6#hRABg>6^-m%XM9FHn35!_9w%&wM7?@>I}1cd%C^DD>!xpXU)FKJ(b` zYnz9tnv5PkhZKHrTOmFwv(~T+$Rm;si=v22j(~Ng4~>pTmjw`830f?;!4gdq5+qdKyTz-QKXt&MdeMzka)Z^Pv`E-@ zjo2Z)@r_wQ6q1*dcX5fMHmOMwVL+RcG?P@++p0JJAwDYxV+>1g|7`pl7}snKXdl|5 zVAn&@wvQywlydSV#zqyT?vr=*52l`7SIL(DY+ZuJw+(CT6&-n&ZA_XtULgO&94%5n zyB(E9d){2oE1V^3d;J{5^OmwjjhtQFe!4ccEs}NMkjs4eLp8l&=-^%k$fHW^J3*NZ z*Tfy9elvKjuGPu>y8(uwd4@=4tPR7%B?Ub!YY4>j9|?GkXYa{=U`d~nNlKz8gy zHuuksEosxgWykTyZqiUI2sOL4No?2EmzZ`wc0l83@xX9njgH?EAwTm@i*-|wJmGnF zg)W7rxwiX*q_tE+Mh8JM5XWW$+Q%){9yDKG-A@#;f+o$-F8)i1yXh0{;k@hM{S~P| zAyJxF&tmK3R@p!P@}$6a013z=JP%DgD&8g_szoiU^)DjZ*hQXD;e?Us7G_2~EM#;v z`9Zh(#B5qo8=>hu{o-H&y8dxe{23!$(FES*FIGR+6~Q51ubiRWNLQ;*aE=+5igF{V zkHSY4glRofQ;iICY(O3-EZh}yPARkPy;PvZVF@-Rivv?{+C)-D>o^V1hlR9r$fzPu zG)AAP{I|q6{{mFlvn%EyKci{fea@ShC3If1avwd%U%C$gebCEFJ6e-~_K^g}>t&X^ z(qT|KKjD`v$mWIDc)+a|KXK?Lu+sjfssAM4smZ7+-FLilrLMmLU0;nE<+a~wmjB{I zB=7vz~HYWQ^=S%fXS9Hm!%_dEVl)C^41tRXO*#K zaZFw2SKsXsI+%mmVCB1XNpK2&cpRwGwiUlD&r%=@dI(jkWGbw9W!+XGaU)v6`m5JV z05#NREAa*Dr);ZrGCELx0!pEm`>fhyCa7=k@3!>SZz)w9kIdN% zM(ZrXj2d%3h6Mrdj(|M$wy-)qNKzCOE|Gh1#c+f6LF{h}!z04a+H57xvW3L?PSs4J zlOI7*dDsWo-$Q}&8whro_F){}xH7K7jMu8OW#UjG`tSJA{gfSKiRl#svEH0x89{Js1k%_^k^gF}^@!R4w2v)mcSXQ?P)p(+ zL3((3l;&kkP$01~u#Gn!%aSm4%i}3nBkCUovBvbBT5HW#4e0v?fen7}TOMD->P~#v zpL*hNrH-d}c?9@4ggLQUs&)Dv6)w}^vfmv9tsKNpLl}Iv z|FVrM<7oasCTJfBxP`ed&*rw|G$=zxylin0gkYfh~t!UcIs9I=auNasHFok zm+ja3I4*(WZxobtx=H#w3)B;JE@ujcp*!P*l9Fm~T512dTeC&+Y9lbw8vpH)3F)m( zp(By6aiHtp$?`6Dcw4@he&tzmz=dDz3NJr_z%mYy(iJdDep6L*@{jrh2j1qo1~pr^ zSq$s0Kj+6b&rfp~C*}?kP!FwHOAq3>dQ{8T%N`5aB_atl{vN-|CZA8YJNih@zT*G( zwd-{*I$+3Uwe=<9`C=z&$O@C~y*9l>1x>wT(FQgb1M)~1X%3~72O-T84P1c{pZ;|s zZQ@u_XbVs}tJG(1q>0@Fo$&n|;=W)`tA1oivyXs0m_0hQ9*xg6UJd)lput>s+3_k$ zBkNe`)qwv~2IA77A2Tm@kqfz4wMuLWjU93<5U=+I|5YQOppCUQ@b;ohftPQQ6=jhU zE%DK<8ltI3sdV|I{d;DXoF4UGzpi)tJcMSTJs}bl`&@$i*)rlCw23CDt_yFo>%W-7 zQxxoP1wi(j%?xtG=-q#EGuoBNeG3zyH8wR$Y|;)(u261dBV(!;Zl3Y(svil5Iae1g z69Rfav2;+0QB!wJ#*W3Su&Nu(A|K07*k?q2<^^qck%=Q1f0Q#yBNhaZ z7W~GFcx^;Uq4%0>BN)Bs(wB4HDB+vvbuab92egkf&S0+cSKaK@)h%0==Yy>g9E~!5 zaRMPjT#qD*9^^CiqxKeMeq;FWvK-h0fBu_^a}yiklPoFQ|d+OPwAj)YLDy! ziht1+F1bv=KUJAJ>bwHs1UasGB|eSAj3GKs7`iFT?ZyOgPA?=6-}t!0TBW@!i=zN} zcpCIU{T~zm@SHbyD^t+wrG7lm6`L7o@z#4a3cebs?kba49?Z{GU^q z)+0VNzq=&(>0>Nl*O!GE8-9L9VD2QaF zE|7zq!fUH##D!$MSNP91WwR+IDWLTW^?z|Gt8@b-RDTSY-w*V%)mg>EHG+HN@`vZ0taVF9qarHz$qR z6${3Fi_&q~|0Y3W{wzqis8@5Q&p4uR7$056uVdB;L+ao{2`B!*R`b3Bs7h3)9owj=46BG z7T7HJ;VDQ>W{wMQG`c;CEK#8X-5;)jHi%JEb3yEn$I+@l$>v}y%hiI_qH1jLA9DNH z=%ns{F#0A}KQ{)!13f8DwgGXJg*1Bc%xx(@*jbxt@#BrYsl2L7O+wu(W>tNLxJgk< z228ISV#xQ#Qzke2b;BY+9(yX=lVrb`dp1AlO2|#2Drk6vajZos%jH#+iQ;aiL=?IV`UUc0$ z+ho%do(McCWJs{WJthgc|59Y=M-Gc?2_?h*G-x-?NL#Fo1=>x++sMg65 zEQ+2CEqOrah2!rM7@++-RD^CP945%sfLM5UNPjjhYTdWl)<0DJ+jB7_{5ECcVEV=g ze?mYFbz*FaR%D;o0eKi{jI8+6I`eT=oT=8T`9U?|yXLz5BC(#WnxRj2=miW+OxSIc z``-yJ5lu)0AE1CdGRS-FxTp4c=hkRBx=Gr&LrGJov`o@Te8bLm?Ope*b*5 zx!gqW9b>^ZF4t84R~$=HjP}@fsO`k`@vffy&9YDIqEQ>@`vu9jTS=8pWIt$K^$sB z8Zbg@x=JH^*zfUg(3`k$7sDh(M%Q0VRPg<4aq2t>kVk7(09C_G2w}cf+OZW6q9m$- zk86KKOFQ|^kll#4zzspa2W=OQJPX0JVijkX)-|I_TuIb8)gHpX08grXBx5^%^^dBw zm~vo0H5x_0Po`K;Vi_lbpxWXZ zaYO<~CO>S+f&9epfBJhS>$MxxAsXUaGqCP5BDoY-$v>3k5m9%JbKtdyVCZ=eDv z$|H5@v}AkEV{|0Ri$B@reyl-QG%<;S>BOjK1+x5j2AY4AUVCxF`0h#r$ZKiO9UJ~N zmhvkCov;&%R<4*X>Jw+v>{=p+qOPDKHP$WvNt0PX9=0pYuIV`PvvGbt#r`;>K+?bJ zenz`*8M54?E-?e7*N5QlR|w<5+4jZ@W}`~lJRpzozk$@LIdL8y%(iA<@QNjqM2m7Y zth|P4K|7@UunC||tRUU%#UIx4HFMXXGq3}?{zO-NSK2( zSVc&}g;|~KknTI(iVgC6jGJG%<;bt+5pj(uj&9DxC`S#=a^hH9Jdsx_gKB*1w2fS8 zcLG76^#d0=Gajr98egIv4Y#m6AMW!w z1t>oX=hFA-o(E$dWK!SuWuw?1{Wz6=BJ?|Q*mc<8_gLsnM3+eZk5e4^u?vd%0k#Vi zpy%r#$(u#8Q&KtH;<0P8qqPXx<_VNuE8koWAy-<_ZO!IkjK$ES3NXDZ}*-=tWp2&C7?0%%F|3`ukAi2 zd(j|Nz@i%@jy|0O-TJ$m_GDn_=mF^d<<759=T+6_ebhOc`jJ`k#Yx&IYy9~dPdI3o zAm=wma#DL^DrGqa%vCjm!ech12nOT+MY5kG(HyW8KvUx~{vj8(Jp8bQXZ;ua!?XnG z{1kbmTJpSvbk%Es`)eftPQ;(~&cu2#mQxGu+pO^A8y0n!c`nygfI?#PP23F6(I(LI zHQePR?Miorp>5b(X=RArwGoJJvyPy~wzrT)uu*?l=vBQSRlvQ!1ZuQ;{^nf+^1mY) z%N1gxU`l!0lBHm@?4)+LXrrZ#Q@5@F$#Sq-uH}Tiv|+QPuLMg7S_I19!GJvI0aFUs z;rZ&lBz-LVw7y)MVp8cpNm@y%mwu6gPza`K1`S0GrS&_BKsu#a59&_3khE#JRrwSd zwvySu<5Qfay-5`Q0Ynm+YFZd`MS(!^6%5JyF+5hg7CL{^xU-(H)<}u`c+%&Fz5122 zfr{Lk7u3dq@^!OM#%ZyxSCk~w3m}i)jO96a3Q1``Aq(@GK6y^qCsZ?FspFSa>fqj* zafWw>hrou4%H6e6L6fWu^)d~}qsZ+vsdOl-=GL*u&|w7{Itoq&K~x|}2qrcfG^^*F zj~yn>W06g)%${jR--q-8oo}NZyGOOA*`X?JC0$S#?+7DJR;AZ?-te^8(H+qG3MD2S@lLAy zbxJGXxJ6r1pz+bTmh92mnTY0mUo*9*Wm0};=jQr;M4DPoaV5h86u+ULOIzz?%Loa< zw^Vk9l}@OoM%jq94q3$UR;b27#2Jtf>dd_=%-M2d?yQwYG)uj!hWQ0H%W1!})>tMc zvXbomPX3aJBypE46!rJr_5#XJ;h>U>`3mqe%#5_}GUb!mVEm`1<1q2w>kEZxA9+uGSAT6AI(aN1*k)Ra;yCw(OV-a z$qjuU=3_vWjC{%KF$c=8!)$vfa2w~ErogR8Ie>uJS8omx;Q9bNYcV^xaHB18L73_@5g^~<>(?p>2Wr!L0H|-&5pY=L@ z#vBi11ccs40{5S!h8e?G%#tpj*=z&#nDb#NRS-xLK>0N+bSmPa4ei2LZai`v zCGQ0{29F%f)*;d@F(v!&2aiLi{I;GV2Jm4g9y7H!X-!%KtbMr2RS3LC`-EAM#c-IX zL&ceRV|-a#0iKR-XO za`~J>xYKkbj~`Tku76ck`am0PbQ%ynDv7af{6f4!4e}s(oSw-JS;tgEUsyT;j}8N| z(EMV24-&1;2*`u&MwWw;3rOmbuU9!H@o|w^#0$HP&UvC(ZgU-H?JiD~FoGpYbgBGq zRg1gNsV5WDb@&Pdfgv~!6#w2=z83Lv35Tg{wB~y74s~1KPi%kJ-$Wr5wF8QIovtSBMy~ z`*H@xw2ca^$*M<;>~D%8dTM&i(y)mz)6}!F&HIAi~8lZPC7p> z#R$@OclPlk73li|LqeL0SE+47fY{cOuw3^`<1M*P`CjyHB~;yE(aYOEB_oYJLrt<8rIZv(UV@Ku{n zPa~87g{l11L~dN=56f%Qz5dd-Z$U!eE}WS8@8KBx&jt9&9czzK<< zcIU`sx1nb$pqA|cEsjix6`y7ez3$QuFmyYGoUDzkJS(tinQP9A> z+JTv=LL?FJjd$MXq=#xX4b9ZHDY!5HsP0~oJ_l%gu86eo1lwNttRoEV780uWX^)Oj zHj&)hQ4fCIaW0*^T#s|+LCLPA^irUILo@#kG`|w3x6qpPwyd|sc`?7&_&e*own3F# zFE&gM^5&-RkxkMc5Pv9IX>H3}{_>yVm%@xN>BQ6hX7}v!seN~=`cBS^eEx=g4KSs7l zkP6QL@^EfYyrK>@N11EJPM4=fbu@~O%9*H*nw{>Ebt6K1QGb%F?>CD^1CegI*w~F* zjezDyT3kR0i%CUT)8^lUy?Hz$rO#}Xx%d_BZgFmY|UVvvRtOj$des%bdHG&g*A?4^{sEJ~)CSG{&2sb%gn{)=J) z;d{rsV}s2fKIR*4^VcCG@_Cd3Y_-Worst*|Xnz16@(3OKYjWb3Ai2H_O#eWoe5+Rj zM&R|YV_C;dx$E?C_J8@DOg(Ogm>rXAE0{p}QAGBe!rrJ>^JN3RE!;bg!PyC$&AHBK z7(pxR+3a)fSQd=mO_ml<8XY_$Jf}R+K=BXum%y=QtCT->W!PFEmBZ4X&PHoC;_Vx8 zM%#3i_9;*4EMp>2R=(UilIVGSEfqdM9*eNO<#RaZ545z5nY9)_{nFo2Ek)7Q=r0^I zlmX9fkcfmGWzx`8QrbHa2uYK)X z^nmtZ{YpaF+~_C7Sn}SI^I;;FQ5SB0S-3YeX&Gi~h$%#C2hNEo5r88iwT4MinH2%8 zuTbK;>NLe`o05TG^dz|Ty2nJh;?~A>2G;Gr=>0iKWlJ|BFGCW5<;HX;TT6+p|j%p|1%Lhwq%3 zTAeR~7*NMv<(m&91vy{+a#N(+GK_Gr*g_2Ep-qZ%CuCYDl)-lNV)bco%Ce`aJ{5A+ zuRJrXsXJ);fil5pqYTGBUTIDErJpfg31}ZC)CVN4^1INY+({);rt0b^Oex3@71wP^ zyI;lb1wrQsRtcPUq+YF}-XrTs`ddKyY*YvJK!-}jkgsWWrkBVdaO|F{yb)MCyE!$; z2*0mbcr=i?%MWsqwaKe!EX{`Ypfzy#z)PnU^ffq^fUGM7nx@ueSOL zN8)2lE#b^lT*EtOSigKjHhJjB&kq!0Xovis5p5Nv&TR&m>Y+%GOP)MB5Ly(`LTT&kOwV~8ak5h84OJlT;JB}GAoHe z3wRm)oluTXq*Iw|^^*mEDa1$U`!#9H#BzPxK(vN5NnEs_EkqSXzqC z9j+{EDTrQYnBg@Dr}J)RDo*<}D9S@F7_;HIsx|RKW60>_yIO96GfGN&OpX*Rl8NOB z9ePDA1Fg?EvZ}Z+lx?}ZZ-&_GZRqb~ZtI@>@s!*wP4= zVeCNqV6bt9?haBS?7mLa^>shW@z6Wdp$eFd+6ez=^`0n&f2eydI=%R9`DVHe7-*oL zK=~)^>3O@;z=w%9<@r;M7Yqb`Lgzw{ggdxGG!Qlznf0+w1REEVCG}?xf|)E1idG=~ z8$>R9yn&`&0m!HO9n%qNqsd)Vg&WrJ8L8G5MOZ@)c2YRI~t-j*xq zIQcOH?G>!Mxp|ridF1Zn=0Z3LEj|iNf%2=kuMS;e&c#7(cZnyhKL+d|yE9X`DoV5TBJ=4T$UN;zY0aNU4C%2XR4V?nya`4`Ijn1*EU}gHSA9b#c{&(v*c#H=xhTGbzim6+KcgEhV~;zygaEh zAP=PsC-kzhbHT!285%*95+G&lU@|F5Y``YUPsrT1y+bGgi0Z5;K zGTOmOXZQ+ZWj#LeS5mV5yi2+KtKys5(2qYecz(f`g~|BP4%k0f_N~6jUFE9O0rKb@ zp*Qc92d!*&!FQGH?r3On+9RD7^Rx93Dk(XY^&A-0&N-^iL#5O;h&hB1mo(Yq^!PYf zkimBOd{3)V%V56-_cUSUS)@f+1rkIXqk!u}ynIozS3XNYUI#5z<*@|`eha#$x2cz* zS;n8EJY-I*|ZVQ=0fM)H>O+d1Y}Hm z$gn*j(PBtu%3U=qMXmiR!I}695P&?Y^VgG5W}tT9lKQuQ-=lg#AZKD`EF~9?V8F85 zgIcCUlm7}z{Z_tXW4pay-US6(zd%6#PUW(jz&JY$BV^=pEplQG<1A_5+pkA#qt&U4 z>D{>fwKw5D7AZNfpKrnIdluH?QseV%tc>4@VHU9#H=l717)GBTRcGd_JnfFa1Jbu) zq>jpl+EdXf4rh|Wgtl|=5=}N(?Ol&OhKEW%vCu<2XD8ZHF`#EqiG5uTFRwuZ@>uxb zc3JW?dkJ%a7Hp?tMEJdOXH4wHUMLArkmR+DMq?UoqHQO>FMc0EmEdS?q<}p9;sr1F z+|{W=$!z+?v`GCAu6C|I;jI2e@_bq1P0eCh@~(J0|17?LNGTp#_^VTZJj~fIW{k-` zt_o&z=ei^w4EGGjQRx;O@SLdvs8HVTt&<)mPE#zBDke(zl(|YM-V5yX0k$yN-q%A| zcgLv|gb9?`r`?52TKtieOzY|cQb6-pT=8VavP?~TtqZ}2K%2?5-yeb0x|7%Dw$~=c z8cl5USl^$oKxGMD(jxr*y#T0w8MHN?pGE02!nmcI5)svbjQ|sRuULoj3SFduaC?$G zc|CIeeCt;mKjvM`6u6!90w9n0Zb!+;TcbfgTR^1fF*L({l~te!cba{~i?n?dUqj=} zE^k){S(me-hUi|%8v^uxf_^0{tKLk((Qx_}LV_P}$pj;V+gOgYKpp!h(&}rcFCw0X zjHXp&ewFNU1VS<5XkUA(!Esq_KqZO&3jcdruXv{#awVn6vk3}MqcC&D1Ze-i7XNtd zqsUA(*L+>-!=E5bNgJBtVOAyYJykP=;lDv95CffGqq!oBFnmw{rB3>$n0{X~HR5qa zY%eqQ{KZoLaLzWfA?pr{{!v?e)CzSv&pdZ`4#=ZqI{Af(5A_N0fugaMR9fEj_lEIn zG+0uy>i~gdyFZf+tUY7R&Gj>6Hh?P2EG-b{ge@nFPbClEW)QOnNIwlniE3Pj{x^n5=&L52jbMZ5XZx!+6AG@- zToMT(!ainxz*0PEZw`5J0g1tE%BROpvnN|mAjIR1I(SD7G$R{YflaTrhZ^^9(qGx! zQK3Nb58|4;Q&`BGu8y~Y8-cTRBXZa{8a<(K$Zd1;U7X|h;e=JrRRjRHyiKh#&5z++>!^4SBh|R`&f_aN=)A2xP-s9nsP={C__;U7b@?;^GQM5#3M3ZVwHkNWX=+CPf-t;+(}$wkU*j5Pe3!4T^; z(t)GyXh`GXC3&jvL7E6T+6UBHQ7M~B$~4am(dj?*Bi0OR+>Ei{=7kNEVC+F zq|&n#r&9uTNvYamipyR> z*_Sz2HYI-SMlS1m-bgp{jh`DWe8>&kO>Xt|-ZKS`!a(|0@UXJK5aY!R4EE%c2a0O{0fHX<`!}E9Tw6P;c8`+ly@49ort%!raWCH1j5C=e?TkX#Sdw7%c;)?wIzbO2W ze|oVvr;gRIF!7T@+=>%g-80oj+p5tgWa8@O|J;q!b!*$k zASppuw>j9{705qd2lN6;sCtKYTY{zAxygATS$VD+limLeH!vzNsU7eAGqhIEdyPwK zSF7+@kWvEVA?fIRsYg*LX6-qMxE{4Pp&Iu8$h4O%LvPidEm9oL+~0!b@Cj)*r2*{EMUUJ`2fhgyx1Qs)i~rN?!Dh=Z@U< z_zxft@mD%SS;`y*$-)1LH#>Op_$1j&`=hAF>;+_DlIRC7^$;SgeR6W13%#`;7l-J-CS%cXST5V&V@| zffO_@mB-j#DtosTt!i5+SL<&f_=$0-RV$lJm_YFd))=+8mG^qF^?CVP6)w255Y4NI zZ-r+lB(Y|Wc20EL9xf~9gVmlXF7C=uh0#8cz8p=tGWSnX2Y2Wo#oL4dp2cA zFe5k_^3VU+`n>=1I62aDDnXE#+K?gB(k7D6 zf_E-u@l@H~2j@qX2hjS8)(Y;n;N46&Fs&d}oQJv52I*S;xz+018nb{TuOgoFCkwhT z(I|0z3KjQAHI4@p==pr}&xa86i!d)YTt;%SmD)S(bA>C3#|X zkcwXT9r>g#36wuZJfmXPR-_ojb4gccWz7@6m3h^FI&yWNhoOH3*@$bln95K${MGrv zA&1loLn;K6KZipTKlXRYSGWdmWqZK56TIN{3d+#`Js01VCXsTbNKSJ~=3f=wS{3s> zjLhYSkp(BSxP-IUx%P;NXMn^c3c6@I>W{B+=yyfBzlYz?fWIFo0fVwnBRX>}CU?2; zvzC;XEz()FBeVwPH+ghLp+r7ZY>cj@xaJ57m`m$35P#zUdFXL^R{DN@rER0X)p?jS zxcHw6)Nv2Bwj1!PX!{A_HE6z$Vby2t8XNg0`@b7c<{mUwD&UA^fLBh0Q9rcn87S@6c zi6EM-6D2_T1zgWaZiSai?q$BavK{18rO|q)L-Swe;yT-mZ((*P?V0?wx(x^}is6e) zwMI!=K=tzw9RD^mGN7?dgT!=4UzHEHAY0TCPhOUb>bxSPc5940N~WVknWS3mBKZZE z`ig-1lc4%_Kx&#maH%{u{5d@R_{whJW*~gA04L}iq$Q+$_aSRC;l{B1bw6@_?(2&J zPtAU54kji8KWn4cLkIJ);$}w*L4h=nVPH>TMtm(z9}Q?9=TS=HB~4D?G^n%g@HgU; zb5lv)9o|A@b(`WMdDP4{Y3hpEhX#5?V~HzLJswjMkpDR^wSpuzw^XTe?a(iBOk0T; z;*sb6p_%kU%`083eOD*Duf#I$>WU02mcH>j0NNj8`-A=NGF8wGUt+_mnRR^50uymQ z^4f2MymkkF(wRLU?=bdQ!g2!HGBm<$p7g2!;sf0svX*12v1(eG-7Qp`#Ey6Um?#(8 z3wd>o8^EG!2}IdAi7e~Zn@qnk{`^RA!P2jXR}W_S^?cL!?Eq4+e21gm5=)7@?*JU2x2HhjrHtx~h#G)`WG)tHbmuN4YdHl2cWQ6)^~Peap@fisX+A6CEzo zUmzYc98+0-;wl}G#;W=XAY4MLLql|#YPHIEHtC|gh&X!hNK>L_V1qDB_T4i4Q+NJxE7l^ewSC3M6 zFoo0EsrA<3&)}s;&+y~nlS7c`G|!qR%VYt0v@E-=*2yLq$sx!g zLPIT3*L-it3MkhGgCYzhgUZtYS)l%7NZt2~Sx}fP%M_UFYs!8cY>qQ&0|)lz;vIF` z3}pAMdzi%D&b!zTa^Io`@5&Tu`pq^7NWj(B*tb4NOoz2Jx*q$5aDrhkU)EYCS31Fg z&JREZWr zC#59~^Nht2p8X=0FC31{d4gv;n}W1Gk6}ZQ_VzD4PE^t_DRr==?lPE`uZXEl(vBB8<*iYz9-k zyjCgNZ4N6j_GPB{RR0?Mx1M;C?e=e=_z=8}sMnJ814IO|H3-U0{j#ByNni32bQiYl zC6cR^VuF4n4m7g&{AsrKN0*kaji- zcoL0S^Cp?k8e!=m{4!5{c=6E#$m95EQFoYmACO

    fBeAX&1aMOt_oh&J^JGC7kd= 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

    jbF{DeUAjq&EZigkVhI#^Sq`BesSD&b?v2F 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\+8q=X6 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

    r~dFfK87I-WWRyMp1 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

    e!QI&E3ms*TLQ4xAz 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 z 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 Z|KH%seSpd3IQZKf=AkYZLQhNXe*iNIRvrKV literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..70382fbe53db97124a7cf3ea50f9ab51924d2f1e GIT binary patch literal 28135 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..67adc5b5e8d6875a6cdde7046339660a699ae210 GIT binary patch literal 31393 zcmbV#30O^Q`*&1A$&e(HWJogXHLtCxgh-|aWNh!X_9jUwWk^MaB14HXC1lF1GM5Yy z5;_`4rjker$xy2A-simEfA7_K_xjfVJLfv@IbBzm-*Z3fexB#PfA{^n-zgI`wc2Ya z{`wKMbn4&#@t^$6 z3x7UIzl;xC_jL5?(RF_>QfcZY>-U+81^%~1%4kX~mXokV6c$FXhzyk=jFg3+z1$RwbA2j9wc4&J7>%dFLy z7!)_EQ%;d$%azuYcRzGkoi%cnr9>ny$K*6gN*I|;L`VplW=RqJH$x#JOoS5@A>y;9 zYk#9pZhzYMI1_YZRzgTm-&@j&Rff4y#RpG5`*|^T-h(A)+FAwH9e8zUWrc1f)>~8S z#)%C>My=QpWU}$2?%kpC$o=<|q81D{oURBMW{@QuqcX(aIr*!Py{NKOA#5LHQGb!;wa~?QxA5z zclP15yIc0v>AtCrn3I(yGga2HJU{xctcs$1?@{!hehY0ft0M;9_G-*ciQaQ=(;cog6_(3?sk4TXNN8Hb zur!N_2vo*OB_e`GWGur-2~14kILhJ-LreIqG4dEkdz#D7hHiOk^yPcVRqAhbKJ0bUWPbL;fb+6YVUY8NBQM#J23+F{5vt zSN z!=%&j`_eDwTnBRB7_Ftw8cHrD7lQK;krWPeAcw~ z-A-@N=w%T*x#{EFRUe1$PY*0~9iIALXP`ryC5aOkq8Gd}Q2UK*Sj20LGrP;kXyMDq zjE1PfV((vi)^4!m*O;|AA(?7P}W z>S_GAx+FL{ZR9x-Z$G_me>4m=N)fMY%~3fZ{D%c2iLg!xS|yvFka0*;uwy~ z5LzZhQ39bzL=LwI296E8ii@R$NG>B-xdg}gtTFK0y-c)WYTe7Y3x7tvnCnb;+JEmv zz3p_@H(Tr(O^3?D^ve6DUSXGhUK*OwXjeAPd7bH{2QjV&53u>KQkO6FE^fR!hiZFV zr$umZPM2_JsgTnm*&Mii13_=4G#V{#}Nl3nBmpz;vA6hk! zetbWUFyHxglc{5orS56Vl0Uz96&x-L{#;Yzemz~j@P2u~%xm$5kAvUVjo5$%By>b7 zjl1`$Y{lK>AVQ8^Cr10%vHgrfNt8++U! zJ=u7g=~eRP1185mHE`J_qrgXl_C2`pqv>vjMU$zu-|~pmuYWzU|rv|Z;xeyhJ%*llKF z0?l`B*YX(Hu1$h`)nlKUlg~QLu!;!oQ79JS6YXAF^cUM%T}(M{e*ejunCv&3*GX2u zq6d4luqM*nAV!@vv;;0MiV{muibZj;2*Knefv_w|kP>)}L=uF6uR-z#VciZJ+uOdm ze9FfyE4sPQ0YkFY&7Rp~kG2YLJ$TFJ8x!_Ydq0M{3?VZ6eq8$M4X!;yyN`eGH4EA% zEbO(q-||gkin6R6Lvh}3!~A~G_{cA}PPc+Q`Do>dL4My) zgnxXQI&1xlH18G1f)VXk5>Er7B7D|#gAQ;D^)m* zG6z|IBXOu&Yv3Uz!D$$rA>l$2BLvDq5+!D3w3tA|tVk@8N(huU=+e8r^{8K3gT}$Z z**^?=<~qL9-j^`tPkQ~DQ(~9;LfPCQ`p@*QQ6iCim&>5Op%HN8OeSIr9sQ&C(J+ZB664|L@+Ls;$*H6Aq~d zg|ku!MQAZ1g}6}yCjf<@7cyE*LXfOPiW4aR^BPjzy{7r`^59iZESuZiZ+!X5_1LG7 zSvNi376s$QlhQ0K-hH|I(LjGreU~7|(7L*8%L!w?p6uu_y<>}8hpJmsp>Er8DZHF= zRszpIBzOo(F&GI6AC0rLOeTT*NXClgG70bP-^wM?#e`YfIZ{9F)ZF;XCW|&5Ia*DO zym#wjeIFwJ8M$J4p7epuxp6__DpRm58mTG8ZM?K|-Uzmr?!%*dU1lzy#&x2+Pow6lvj!!lIL*L|D?%ap6T#CZrx{X& zAyOJn0!c_|hK5&-H$2d))BTK`DthuLwf~;-{+;d=m=P~?FBojF3hA&j(lV>_#iv%= zI=wDg;ak^t)#S}VPc?21zF}%^68a@1ZrlE0$+1(M8^vd)Sz#0URj(S9)V+CnBHI4C z!OJK6OZsX|9r|Dc*P8lmWpCA4!=fxHqZvr|;E|I8|A9$xh#(+0hXfX5SPW8GIQzVJ zXh&>0-J9Bayi9+xPSpBWlD3{6X_8rw27mPUdAnf6`nb!5W{>~;`Q;LsvUSEAB;cp9 zW5t~1lWQg)oIU8^ioz=c62_UgAG!}QEBS5MBm4P~S?9jU=k#7>8LPA7&GEc*kv+Lb z;^|+P!|c@@2?~-XmZoW`T*2kAxQLO^Kr0ZaoD`uV79I;+P9hBNkthq>-LT#(_ggiY zJ-O~my@|g#sZf?;^?Bv>VYkOpw=>d8T!hKtQvlb2Q4}MU^JZ8ETasNeE;OA@{N*jaV|XcO@#&o(9wZoE zEU2z*?(cr>-GrZebRuO-7unvax$B5!eYC+#WIj| z(jvJSu6GF17r}qN_C$f zAyEk-6+yzmkWz_6&WI721VT%AoJ2BK1fDIGi)1u!Y-v!w;@6*f&-SJ^*H62A?y~bf zlj?=@y^E^$eUzmRmt1+<&-+vM(^Fkf&vo1(cJq0C?eeziJN`wxWUqSDU)_mgC>aI! znplir5)tg4gvJ;~CPAd|{*$a&OkudMYiM6{upY-+9bqyQd5(ec^ZE$+SS>49p~5Fm%( zW|NY*R77JaaE_1?(h#vA5RFkdDWOn?Aw>K)Mb|4!yawmJxZ7%MWA^LNJ@zs7A?}&G z>8B6ErM`bUlLdpbW1hWVHH=)`%gT9>@osaYjftJ!k8HuA#!^%Ctx zT4QZKJ(7)@WxBvb_F!t;kgTWEouYM4MsvTys(z@px)dCv#6bT-J%Jz*3I|df;$#Y9 z9tgx_aLpi;9BK@tg!e0yeF?exA^ladGxL6OXYbZ?BfZilj;wF~yf)=*LUzJWqrrZQ zFYWqSZt&@lUYtSh$)P>Q3@bI8I-aX(8VrTf0o>wJc+W**NS@$C)3jVJ!x54p83rO4oJBC+ zhrZQHt6XBsx^)lJ?>dS0Um9)M+^hVaO+ku{NW0g8$K$_>Ipt>&{|!GnEWJxSIX6+8WPJm3?Z(w4&$;3_Zu7xz8$RtY9pK;fpwreRF<$l?w=N#f z^`L!u&9?K@onstlA=AR8gp`s1NsS^9662^8L8W3DaE}ncz@e67d|4I~Hv5#J?+zko zSp6>f(GAZVSYnNP$jOT@@1@~gXOq>FzMk)YE!TEcb;tZVx_+BTZk z+m5|Br|^APa8eH9+w}O(mdsVPTNX~+SZP?abH_4l_08K4v#FtTAHVoeo6Qb*9N%V_ zeq?<_b8}Jiy#6y!>A1AaShj37vRCI`tXvAUDv_ARWekA)es{M~^KV=esKje*gQ| z2JT@w;yo)woi$Pjh9x8_Mxo?DL*a^*0V^T_hEoE(mRJP0msko3J?|GWs`$3T@oW3y z8#Q@8-zL4P{q{TEU4B#R&HBah@td1s4`}Bd=wW$n?B@>|m!~$dUc)-So7OFBQtH`H zC7o|(w!dTL8u(${x+CqjT<|}DIy@R(bK#9cyxv^2eVwSec6a`rckLEtG zo_Kz;!`HWl9U2lk>JP3rkIw2DRQ-5**-M?76)X0;dxjr>yhqEeCVXCorl#iF)-6s% zyTomG)I~;cO_3BVl7Q$(tXR3C*oh(%QVdiOt_b}VBqGCm^_F?XkNA;MT~`zLJY!V( zBfS?Zmj(KSRwkVM%zpTOroJ+w&DbGrV(x9;dU(S7l8W48IsLvy|EKhzJhm1w%X8Fi zs|cbXaB4z9+Dm{Olt`rviBWKXX&EXeq3#SW0{J_i%a?@(5K9kDeZIX;=f&sV{$W=K z);(DI_UzW}-GMev>j<|SI^kps4bq%p-bWwEpiB`wE zwb)kg2!jjil05_>V{mOLMDAi>xgo?9Q=|w2O_8FYER_P=Od^nc@U7Tj?vkRao91(l z+`FoGzW<*QKSvv)XD(bhzsc`?`i|>wsDdXp3-0v4|5xFIA=W=H5KYcG@=k5MZzQD^ z=dA8;aHeH2e;i+ZS6!F@w;nKOa0QBR_HU9Nm~a%xKcGPrRR)xm0C#|irF_d}CVV{o zqmlml{Atbr+t!JL9D7d7eHr*MA^KBH_`GesZ7v&pC}HaVY|k?|;~JaNWnxO)BU#^n zg$e6kJKa-vSBPl{1f;l31}*@X9n@-oqL)GOSBk(-EI0(TMgS?uw@RbD11sC^U2W8S zc+KuenP0&)v#5tl>n63&x7s|)vBUWMOn03hk+mjwqy0I*n87YE5?+27h#01Kl?X_Q6V?ExgZf)kRqkWz4dzm*nSQ{D- z_skW0$~zl|HnA0lO_Q**_4_oR zPk-fed5Zk-i1Y4QLw@WFbhOrN>o@eY{)dItHLkj&3({I0YUlA!;HkV=x-LvOtB$6G zNFJ)0GMXmkA~`E#DLJ7anlKTDBMc4IL6IU}lkwJu%R-*KvkHDRsOH?0Ra+;o@z}fW zQ%dQX_JNC5{#m%^a`g{aeWR-P4|ZfEJWX?`3xD;aWLf5Nq~PZp*MA;;h2YrYM zS3MfuZ8~+@VB1(XvO4v-=xo&=hnt4E^Bra_wm@1H%x_gtQ!zBOt=r?x4k1>~Epv~x zR#oZhjv#&VT9fzg*~INenm(CoPa?XaW@Y)<R{QY>(S3`ZH`Pg4Jhw>d~Pdh*xH_Z zoM&3B(rjeh)@jkxMl1*LBj-b%`Kmj5gdLh+F!WAjEhdNxHxxL6+Z z)p|WI^S}k$8=k$h^?cvAZ8I?Xnemzlz1=KF|Lck#6_z|o&1y*~$cQKscnV;PD7aT3 zyT)Y@`vHw7qagA5n=0k!p_y+P7jm`@am@N{97ee65#cZ)fUGKYFtL z)}cGq)T0M;;zzPaL)N|iGtSgt+z3sBNp&@EDtC9cYf0j!Zk`*Uju&Ma=zNrkL{Q#> z*H12H#W;zf5(x_VGGqgo6sp=ZPVxFm?5X_ybvH-)6@5J7Th(D6ve)@_L7%|#*WR_u zI|O-uPDoros`96y?99vn=j)P$!m3jS{q^*=d-nR3KY7f*a>>2NJU^>*Y()=q^I17>h+EOaq6t0VYgvt2^kA58)9hq5=kW}+-3$e=F@xFp_!a3@i7n;z@$$$cipIe+^7;>kywOQWJRJ{xZ6^CA2LRjln2^q}T( z$`QBbz}|BYS?e1bRL!@Z(kAeS%OGPrhk28qt?zhl_|Md6-{a3`U*DFJ%e1;v_AIMx zps~||uv0b%Q(F+IM-GQg8Lr0h2{|f(f(3?4p(hZh2@$m0QBbxcdD~6@ zw>3wD)gf0bgRV|Y42sLY?C7?5?Vc{|oNxADdRlMJTNOPCS!>c~UAHMaoTNp>x~mUw zVcg4cR*dEkwV4nL4c81*2oxP{44eZfyD$={&yk9Apy(u|pb-V(y&RbpBbL2R3!m5V z)`g{oDeZ@N{2ZS%{W7)GJYMqd?Dh2-^31|Ss#Zcpg&KXP!54;@WScCx2hn5rc{}H#Q_g=K@^KWaN z^PL{2p8d?e+G`vfcrE_eJLdFQ-;*xGGDAYPXFh0{yZ_|{-<+Z&4Ri8Obbq1|*57LF z^O%?7=OG5uedU+SuGkcRFdEl5`*_QiE2l}7%w%=T$K(TS^7Kq4e$!i)rZnV>Zj zY8?~;Ppkw^F_b^}5;^AiGpnqxzbMC&hPg$Q?er?w=`(5q7uJUNGPRh#Dz!^c$=kch z@ryEZcCVfF$3L`zEo$AyWOc&Y^Ir;ImK+M-y{(7EL*MdQXKK3~pL_P7{(NOH5Z0x; zj$+>x|FqnDF@aG8B8Aol65;|Jh0qZ^0(yMFV@O)UN+H2#QHGa{=(^^`z%!ZA4a}bB zmmURA8);puKhPs+0sP`%SCCmAG&#s9w|DnbLmIx#fwgF?6$e0 z@#4vJ+dYvp1KW8;ZgT$+u(qE^kN0N72CnRT`_BWfhRA_=nzkw2J9E<1;K}N&p%jh2 z1XOCENgRVhD3qLVXoiIZ8@PD5ejuwO5uCqKqxHey6Ar^CE;OXijPFV_}_Un@G@C4Ov4e1^vQsn+@%dZQ~71K+>2$Mf4d z+Qk(P7`yG-&0dui&s^^gs6O}9uJ*{I{Vl9{s=I%SI%^2ne*$|qk62*bDEqX2kFlLr z4@Mek>7F&-EdCAmF8XVQk6I-NK>{Hl#i4%^Qfrb?kOELVqZP@COb$gU9A}`WAmY7? z%H}Q`d^>yW+oumFluow^(XE0t*)FIn#3;!Bu+`2<)is)7Q%Dcv+=II%q_77;gwdPBF*M8ayPj0yL*e&;hV?ygO z*>}zt^g55fFG<~3cGF{OpNjcwBrA;<7sRZtEaX~~Fc~A%9S6n31qliR9>f9-Ly%JF zK9K{hL_xbV)I4ws5*xUIdA|+QZl}p&pAkQX*Drq3wCrJI{Ep7K563OgH|bIGdD8N< zYq9&1(p;_2X20?^iuTStYP6=$S3g(VdAnEKywcEPNO&=E)!(RIR#Exkg{Nn#&gVfU z`K!kiSbFcc?rU6WdTlJ%nlo+(bJbddvfyTr0T8qzeSwezg`eQPW`VtgLxD&z6nZ6i z!@K`&pwZ!UWBa~t?%9>5-F+P&)y&Vap0gk}>ioFe_Pt!qqWi8hG@GB3oNh7vOp|q| zXb%EH0>|XM)qd@R-x{2* zj3}G5`P+paPVGddzwwWqLIc*lHm*FIxbsc{ad6a-BQ6`t9fuxtoZTzL(zwX*X2h-; zBS+e9uX3BbbNJ-(tF}gF*CwW$J^K0iVrkRsPO%GS=J|y5HJ@lRsMpasTx+8Hu0E#L z8fck@WD?5#@Ls@WOM*O%!D$vKAe@3o6_rC42N4RNry2YA9JA1S#d_y|BQ%LcT8+ zNm!t0q0$DeH$b_`;7fr33A|1YeX@MkFz5bYcj}ki+@DND^c!saOdj>y+2>Wlb#cYP zroC4kniKE!^E18DSan%L~$23to^{n*^JntG*kpPKV#WS@eqQL=N3%AnL4v0vQf^1}SK}?9 z=M~jDPMH0`u1ELgEqe7M1{c&#KR*3k!IHr1AFcjuez>RG(Ozd(tr=T#wIr}JvTuD2 z5nG^7bhB9gcQ&eYqJ9_F>8PV@DJUj`Z~(Mx(7{e4BDkBNP$gIJN0fpmg{D%u;$`If zHf0IcsUNR@e%QwT>*2s_i+m=w{_%Ymxj2NFn6L%27*-GE$w70=yJqk6H7xeR=4Fzn z-u3^_>#ba*%h1M|b{hXZm{rtG;CwSnuQ;p`FxEN@iFF3jqRda zW^BhsdjzRtwSX6b8&NK&5eP?R40N)$mqjsmeb^G=wG9du_J-aq1V(@-4E@LI{B4!+m(LiP(R0h4^AHL`uj`i^TMqUxwm5c`R{Yo zSwljvBIGfk+Euh!ig36(pgI*Ss%yn?C*{ zUEM5subXD1Fv(xrJ`V|*Ip!VL=_96hiBjiC5FtRjJybYB835}B zb_MF-5OG7#DDW5*_P3EFKS#%`NDn*Vy(j*)^J7=XA6I@gxqkjye{fzvE%GVlM%mb& zzaF1|xUb3kR+s&0N5fLaP}s3y%$k!C$8Vgx-qhSuVD7aXut~p@3Ct;5-`nMTZaW$J zw)6-+GuQG&;t{Epl_@)?H~CPDU>?n~7_~6mXTr9^){wM92o4P$aC<_(9rV*fWP{-Hzsq(s1r<-Y zOi_wAes1H@_e(@}rwGHHb8_vI*0y=`SC61Yfp4#2_je3%HVPl5X%*V3qR^6l6yUw0 z%ZsoujgMIamii8nH$-Q56fkx`V;~?^rX;+<0`t;S9rZcVd~nd&}M}k5M1hDyt#15h;{yEC!vJpj#3{CJ)~LTFx*jx=r zvb)Z><@sw8oW*W|3lNsd3qxchcAtJ2Cri zQcSc&?|61QzkJqkdY%8-O%H>6rS$kgbTz(QWAremaHJDC=g4gT@$HePjb{g@gzgGH zvWk18<-cd2QfCch@ra_vgiAK=Fc*=F4y(+Q91{hy^Xm%skB{F8ratE~J3X@9zs>rWXE?~hT}L&CyZ zNl?aM(BL2k$ARyJfLaM*KoKDVHVFr5D>NhVCXrqOWdD>~IXQE4>J?;LGZ{@=tz0(@ZuGW~lj_h_N*nYR}&EkhQi=zuN zwf=7PQhL;1zoQD&eFG>HU@Y{FLN~QkOv28~744T8TWl>CyJuVeHqBd0p?-8F{~wbl83B>bABAeBN5_itRSC*3t4u z$@lQj>5DNePr*tpx#FM$t#{O^1X3MJ|^ezr_5I)o}_qv zYV)*h-R3=EcwwtRvy=bmJe1!ez;lV(Msfy*=0Ncp2Pz1GelSqHfdmo03PI2yjYCl= zd6ROQ9oPr8TPaERs}^T8c~udL%HCh9p=al7{&%G&KAmG6fp}--kE+==|5ssB>Y9 zVkhWF6s=5Y=O620SG+IplUvg1IY%Q4DRb8iE8YgbzGC~}>6=G30dI2*+}nH^m3P-! zC(qsQ?D~U)tx~$D+thl;Ctk5zb4juw@amuyAGEEn*bjWB`2EdGxz=D7H`EedFgXJ1 zX&5a5@)6pYpd%gurXD&zAhd<94-maFtPJ`O_&lqPZs;vczS`Su=C9ce_jgw}B6n)f zd9h(rYMruzi`*j<{qFpnAs$lSBjMD;U2_+gYMn2bx@p6=ra3jQwN)W%7m?2n`$Z#1f1mm`Abiu`+xqslSZ|oIkm-rD(!y; zRQ+o>mEuoSG6vl7RR8x+)X=D2qX-35M_vgZR>g;`w1qo!N)`E23uNIFsDvSN=R>I? zUung)S@^UlVaVKBPO8Wet+~j;Cp!s4=FVMGMRwQZA`73w#D}c(Fz$>bRph=}Tx8)B zllYL89>yJfq>6m61+wrlNWzf0LyA<9ZEPT8WAF|SB?(iQ~+!)#QMN9uBs6;Ye8 z(KdX@O5f%Vo>4{CgSlah$dJAOX<7D`dmEV~=2}Tv!w?hk?1M zhRmJXqKZ61zXh`387_Rt%J0mbu%e2r+mVYbeBKHlvU0b%BU4n7(^?`69-tx&nLFM@ z6*;^U*Jk0vPJ|(IN0F!^PwC7>7CxAS4_W!0xno0AkxjdBk%bQt;X_t_XYK?LRpi_j z$inA=@F6R0=1%KSMc!+`wORP=4q?dL5ge+>cCb9v+f(>J4q?dLc^az7)h&>PPuCEJ z%pH!QioC~=YqRiC8GOh}593a}P(`-w#zhuB|AG%$>0#XI6spJ_jJU|cXI1batJut$ zFQJNjrv0-L*`24Rgtf@Ko%~T=R;PWDz5BZ71^^7*Jj}& zcwxv~iMlHCP;)M_aKSnsvhq}M<>ac!e_9|Dg2m;+khzj@RpgYuT$_ap#DyVqHQuVo zv-)w7g)6>=A#;V>s>mAsxyZt0+)*i$~7Oo5CLsouguB=xTIi&@%aFMStWUd5P6?wH4*Jj~@ zTs~x_hjHbys>qWDbCHFMXZetoyUi8Asv`erfvgy`D@+c{hpe=jD^^uS&KknC8Tvp4 zN>_y;a|NiX$P0&Zk%h}qg&}jbqpHZ3!&)EW_^FEgz6G*y8K^L1u1HfA zIZed1S-50V7&2E-sfrwqaFK<}EBTO>zReXssv=K8TObRTLh>OizcW|SsESVz#3D^*iP_NTeX!i8(X zkh!ulRb)Fc7g@N-j1O74+gu5lDzZLI_E6nz;es$>$Xs2QDspNIWZ^0=K4j&0=4!N5 zktHChRJB>SVv7%1x!YVll`8Tu7>T8dEL>eB44JErQbm5z0$I3LiVs=2+gz!WDstoq zuFWFB!Y5(KTv?JT^0bj$WZ@zuK4j&0=1PcEk^6zNi1VEV3yS!VmAlQ=1F0hCwLliG z4ibjU)$XVwC)#ms7Ov#sLssrKS7)P&>;pZds=F;*b;F0O+-3R_f>zqCLWE_2~SR@%&!r>G(ycHr79T&yAtnJXnxMfM-fMHVhB;X_vLHdpqc ziflKgC9+Tv2p_U?x4F^{RpcKnkcA65_>h%0b7dB)$XU=vt?JvtMHhU?N}IV730359 zkkhFm3l~fXL*~jERFS8E>PHn>xVS+WGFN(_ifju1ACRiZ!i5NY$ja}`l?kXKSG7PE ZE*emWtPNeW8X7YAyB_$67$?O~{|B67zPJDY literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..e24d2b0a52fef50af7d4ca615ea281951f61aec1 GIT binary patch literal 55784 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 literal 0 HcmV?d00001 From 6b5840961407960a06ed20cb5dd1b782080653ff Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Tue, 24 Nov 2020 17:33:58 +0800 Subject: [PATCH 153/948] cmd/faucet: improve handling of facebook post url (#21838) Resolves #21532 Co-authored-by: roger --- cmd/faucet/faucet.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 346c412acb..eaf0dc30c1 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -732,7 +732,10 @@ func authTwitter(url string) (string, string, common.Address, error) { // returning the username, avatar URL and Ethereum address to fund on success. func authFacebook(url string) (string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense - parts := strings.Split(url, "/") + parts := strings.Split(strings.Split(url, "?")[0], "/") + if parts[len(parts)-1] == "" { + parts = parts[0 : len(parts)-1] + } if len(parts) < 4 || parts[len(parts)-2] != "posts" { //lint:ignore ST1005 This error is to be displayed in the browser return "", "", common.Address{}, errors.New("Invalid Facebook post URL") From bddd103a9f0af27ef533f04e06ea429cf76b6d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 24 Nov 2020 10:55:17 +0100 Subject: [PATCH 154/948] les: fix GetProofsV2 bug (#21896) --- les/server_handler.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/les/server_handler.go b/les/server_handler.go index d3e2c956b3..c0600b3686 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -610,6 +610,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { var ( lastBHash common.Hash root common.Hash + header *types.Header ) reqCnt := len(req.Reqs) if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) { @@ -624,10 +625,6 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { return } // Look up the root hash belonging to the request - var ( - header *types.Header - trie state.Trie - ) if request.BHash != lastBHash { root, lastBHash = common.Hash{}, request.BHash @@ -654,6 +651,7 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { // Open the account or storage trie for the request statedb := h.blockchain.StateCache() + var trie state.Trie switch len(request.AccKey) { case 0: // No account key specified, open an account trie From 7e7a3f0f71d60510afb5cd06282c1db729c1f60b Mon Sep 17 00:00:00 2001 From: ligi Date: Tue, 24 Nov 2020 16:02:53 +0100 Subject: [PATCH 155/948] github: Remove vulnerability.md (#21894) This type is automatically offered by github after changing to the new style and a security.md being present --- .github/ISSUE_TEMPLATE/vulnerability.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/vulnerability.md diff --git a/.github/ISSUE_TEMPLATE/vulnerability.md b/.github/ISSUE_TEMPLATE/vulnerability.md deleted file mode 100644 index f6bfbe59c4..0000000000 --- a/.github/ISSUE_TEMPLATE/vulnerability.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Report a vulnerability -about: There is a bug in go-ethereum that can be exploited -title: '' -labels: 'type:security' -assignees: '' ---- - -Please do not submit these in this public issue tracker! - -To find out how to disclose a vulnerability in Ethereum visit https://bounty.ethereum.org or email bounty@ethereum.org. - -Please read [Reporting a vulnerability](https://github.com/ethereum/go-ethereum/security/policy#reporting-a-vulnerability) for more information. From 59b480ab4bf49e56d4564be9a1b5173d9612aa74 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 24 Nov 2020 16:09:17 +0100 Subject: [PATCH 156/948] cmd/devp2p/internal/ethtest: add 'large announcement' tests (#21792) * cmd/devp2p/internal/ethtest: added large announcement tests * cmd/devp2p/internal/ethtest: added large announcement tests * cmd/devp2p/internal/ethtest: refactored stuff a bit * cmd/devp2p/internal/ethtest: added TestMaliciousStatus/Handshake * cmd/devp2p/internal/ethtest: fixed rebasing issue * happy linter, happy life * cmd/devp2p/internal/ethtest: used readAndServe * stuff * cmd/devp2p/internal/ethtest: fixed test cases --- cmd/devp2p/internal/ethtest/large.go | 80 ++++++++++ cmd/devp2p/internal/ethtest/suite.go | 215 ++++++++++++++++++++++++--- cmd/devp2p/internal/ethtest/types.go | 23 +-- 3 files changed, 284 insertions(+), 34 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/large.go diff --git a/cmd/devp2p/internal/ethtest/large.go b/cmd/devp2p/internal/ethtest/large.go new file mode 100644 index 0000000000..deca00be53 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/large.go @@ -0,0 +1,80 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "crypto/rand" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +// largeNumber returns a very large big.Int. +func largeNumber(megabytes int) *big.Int { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + bigint := new(big.Int) + bigint.SetBytes(buf) + return bigint +} + +// largeBuffer returns a very large buffer. +func largeBuffer(megabytes int) []byte { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + return buf +} + +// largeString returns a very large string. +func largeString(megabytes int) string { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + return hexutil.Encode(buf) +} + +func largeBlock() *types.Block { + return types.NewBlockWithHeader(largeHeader()) +} + +// Returns a random hash +func randHash() common.Hash { + var h common.Hash + rand.Read(h[:]) + return h +} + +func largeHeader() *types.Header { + return &types.Header{ + MixDigest: randHash(), + ReceiptHash: randHash(), + TxHash: randHash(), + Nonce: types.BlockNonce{}, + Extra: []byte{}, + Bloom: types.Bloom{}, + GasUsed: 0, + Coinbase: common.Address{}, + GasLimit: 0, + UncleHash: randHash(), + Time: 1337, + ParentHash: randHash(), + Root: randHash(), + Number: largeNumber(2), + Difficulty: largeNumber(2), + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index d5928bede4..0348751f88 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/stretchr/testify/assert" @@ -66,6 +67,9 @@ func (s *Suite) AllTests() []utesting.Test { {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, {Name: "Broadcast", Fn: s.TestBroadcast}, {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, } } @@ -80,7 +84,7 @@ func (s *Suite) TestStatus(t *utesting.T) { // get protoHandshake conn.handshake(t) // get status - switch msg := conn.statusExchange(t, s.chain).(type) { + switch msg := conn.statusExchange(t, s.chain, nil).(type) { case *Status: t.Logf("got status message: %s", pretty.Sdump(msg)) default: @@ -88,6 +92,40 @@ func (s *Suite) TestStatus(t *utesting.T) { } } +// TestMaliciousStatus sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + // get protoHandshake + conn.handshake(t) + status := &Status{ + ProtocolVersion: uint32(conn.ethProtocolVersion), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + switch msg := conn.statusExchange(t, s.chain, status).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("expected status, got: %#v ", msg) + } + timeout := 20 * time.Second + // wait for disconnect + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + return + default: + t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} + // TestGetBlockHeaders tests whether the given node can respond to // a `GetBlockHeaders` request and that the response is accurate. func (s *Suite) TestGetBlockHeaders(t *utesting.T) { @@ -97,7 +135,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { } conn.handshake(t) - conn.statusExchange(t, s.chain) + conn.statusExchange(t, s.chain, nil) // get block headers req := &GetBlockHeaders{ @@ -136,7 +174,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { } conn.handshake(t) - conn.statusExchange(t, s.chain) + conn.statusExchange(t, s.chain, nil) // create block bodies request req := &GetBlockBodies{s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash()} if err := conn.Write(req); err != nil { @@ -155,34 +193,158 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { // TestBroadcast tests whether a block announcement is correctly // propagated to the given node's peer(s). func (s *Suite) TestBroadcast(t *utesting.T) { - // create conn to send block announcement - sendConn, err := s.dial() - if err != nil { - t.Fatalf("could not dial: %v", err) + sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { + t.Fatal(err) } - // create conn to receive block announcement - receiveConn, err := s.dial() +} + +// TestMaliciousHandshake tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake(t *utesting.T) { + conn, err := s.dial() if err != nil { t.Fatalf("could not dial: %v", err) } + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + fmt.Printf("Testing malicious handshake %v\n", i) + // Init the handshake + if err := conn.Write(handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check that the peer disconnected + timeout := 20 * time.Second + // Discard one hello + for i := 0; i < 2; i++ { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Hello's are send concurrently, so ignore them + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } + // Dial for the next round + conn, err = s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + } +} - sendConn.handshake(t) - receiveConn.handshake(t) - - sendConn.statusExchange(t, s.chain) - receiveConn.statusExchange(t, s.chain) +// TestLargeAnnounce tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ + { + Block: largeBlock(), + TD: s.fullChain.TD(nextBlock + 1), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), + }, + { + Block: largeBlock(), + TD: largeNumber(2), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + }, + } - // sendConn sends the block announcement - blockAnnouncement := &NewBlock{ - Block: s.fullChain.blocks[1000], - TD: s.fullChain.TD(1001), + for i, blockAnnouncement := range blocks[0:3] { + fmt.Printf("Testing malicious announcement: %v\n", i) + sendConn := s.setupConnection(t) + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // Invalid announcement, check that peer disconnected + timeout := 20 * time.Second + switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + } + // Test the last block as a valid block + sendConn := s.setupConnection(t) + receiveConn := s.setupConnection(t) + s.testAnnounce(t, sendConn, receiveConn, blocks[3]) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { + t.Fatal(err) } +} + +func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { + // Announce the block. if err := sendConn.Write(blockAnnouncement); err != nil { t.Fatalf("could not write to connection: %v", err) } + s.waitAnnounce(t, receiveConn, blockAnnouncement) +} +func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { timeout := 20 * time.Second - switch msg := receiveConn.ReadAndServe(s.chain, timeout).(type) { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *NewBlock: t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) assert.Equal(t, @@ -203,12 +365,17 @@ func (s *Suite) TestBroadcast(t *utesting.T) { default: t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[1000]) - // wait for client to update its chain - if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { - t.Fatal(err) +} + +func (s *Suite) setupConnection(t *utesting.T) *Conn { + // create conn + sendConn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) } + sendConn.handshake(t) + sendConn.statusExchange(t, s.chain, nil) + return sendConn } // dial attempts to dial the given node and perform a handshake, diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 69367cb6cd..a20e88c372 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -304,7 +304,7 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { // statusExchange performs a `Status` message exchange with the given // node. -func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { +func (c *Conn) statusExchange(t *utesting.T, chain *Chain, status *Status) Message { defer c.SetDeadline(time.Time{}) c.SetDeadline(time.Now().Add(20 * time.Second)) @@ -338,16 +338,19 @@ loop: if c.ethProtocolVersion == 0 { t.Fatalf("eth protocol version must be set in Conn") } - // write status message to client - status := Status{ - ProtocolVersion: uint32(c.ethProtocolVersion), - NetworkID: chain.chainConfig.ChainID.Uint64(), - TD: chain.TD(chain.Len()), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), + if status == nil { + // write status message to client + status = &Status{ + ProtocolVersion: uint32(c.ethProtocolVersion), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } } - if err := c.Write(status); err != nil { + + if err := c.Write(*status); err != nil { t.Fatalf("could not write to connection: %v", err) } From 29efe1fc7eccf3eb99a69c52427efbdb30de1a9f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 25 Nov 2020 08:53:20 +0100 Subject: [PATCH 157/948] core/types: fixed typo (#21897) --- core/types/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index ec19744881..177bfbb70b 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -213,7 +213,7 @@ func (tx *Transaction) Hash() common.Hash { } // Size returns the true RLP encoded storage size of the transaction, either by -// encoding and returning it, or returning a previsouly cached value. +// encoding and returning it, or returning a previously cached value. func (tx *Transaction) Size() common.StorageSize { if size := tx.size.Load(); size != nil { return size.(common.StorageSize) From c92faee66e8823de50fbe1dab4b0c4549802e730 Mon Sep 17 00:00:00 2001 From: Alex Prut <1648497+alexprut@users.noreply.github.com> Date: Wed, 25 Nov 2020 09:24:50 +0100 Subject: [PATCH 158/948] all: simplify nested complexity and if blocks ending with a return statement (#21854) Changes: Simplify nested complexity If an if blocks ends with a return statement then remove the else nesting. Most of the changes has also been reported in golint https://goreportcard.com/report/github.com/ethereum/go-ethereum#golint --- cmd/clef/main.go | 6 ++--- cmd/utils/flags.go | 19 +++++++------- console/bridge.go | 17 ++++++------ core/headerchain.go | 3 +-- crypto/bls12381/g1.go | 3 +-- crypto/bls12381/g2.go | 3 +-- les/benchmark.go | 9 +++---- les/lespay/server/balance.go | 3 +-- les/lespay/server/balance_tracker.go | 3 +-- les/lespay/server/prioritypool.go | 3 +-- les/odr_requests.go | 3 +-- les/serverpool.go | 3 +-- les/serverpool_test.go | 39 +++++++++++++--------------- p2p/nat/nat.go | 3 +-- p2p/nodestate/nodestate_test.go | 6 ++--- p2p/simulations/network.go | 9 +++---- rlp/encode_test.go | 3 +-- rpc/client.go | 3 +-- trie/committer.go | 1 - 19 files changed, 56 insertions(+), 83 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index aef3cfba4f..3207042a6c 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -747,12 +747,10 @@ func DefaultConfigDir() string { appdata := os.Getenv("APPDATA") if appdata != "" { return filepath.Join(appdata, "Signer") - } else { - return filepath.Join(home, "AppData", "Roaming", "Signer") } - } else { - return filepath.Join(home, ".clef") + return filepath.Join(home, "AppData", "Roaming", "Signer") } + return filepath.Join(home, ".clef") } // As we cannot guess a stable location, return empty and handle later return "" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1b86771db8..26dbb9faf3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1695,19 +1695,18 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { Fatalf("Failed to register the Ethereum service: %v", err) } return backend.ApiBackend - } else { - backend, err := eth.New(stack, cfg) + } + backend, err := eth.New(stack, cfg) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + if cfg.LightServ > 0 { + _, err := les.NewLesServer(stack, backend, cfg) if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - if cfg.LightServ > 0 { - _, err := les.NewLesServer(stack, backend, cfg) - if err != nil { - Fatalf("Failed to create the LES server: %v", err) - } + Fatalf("Failed to create the LES server: %v", err) } - return backend.APIBackend } + return backend.APIBackend } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to diff --git a/console/bridge.go b/console/bridge.go index 1a23269194..21ef0e8e7b 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -144,15 +144,14 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { if val, err = openWallet(goja.Null(), wallet, passwd); err != nil { if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { return nil, err - } else { - // PIN input requested, fetch from the user and call open again - input, err := b.prompter.PromptPassword("Please enter current PIN: ") - if err != nil { - return nil, err - } - if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { - return nil, err - } + } + // PIN input requested, fetch from the user and call open again + input, err := b.prompter.PromptPassword("Please enter current PIN: ") + if err != nil { + return nil, err + } + if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil { + return nil, err } } diff --git a/core/headerchain.go b/core/headerchain.go index f5a8e21cfc..7c1aff99d8 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -369,9 +369,8 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma // in this case it is cheaper to just read the header if header := hc.GetHeader(hash, number); header != nil { return header.ParentHash, number - 1 - } else { - return common.Hash{}, 0 } + return common.Hash{}, 0 } for ancestor != 0 { if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash { diff --git a/crypto/bls12381/g1.go b/crypto/bls12381/g1.go index 63d38f90c2..d853823cd2 100644 --- a/crypto/bls12381/g1.go +++ b/crypto/bls12381/g1.go @@ -266,9 +266,8 @@ func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 { if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) - } else { - return r.Zero() } + return r.Zero() } sub(t[1], t[1], t[3]) double(t[4], t[1]) diff --git a/crypto/bls12381/g2.go b/crypto/bls12381/g2.go index 1d1a3258f7..fa110e3edf 100644 --- a/crypto/bls12381/g2.go +++ b/crypto/bls12381/g2.go @@ -287,9 +287,8 @@ func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 { if t[1].equal(t[3]) { if t[0].equal(t[2]) { return g.Double(r, p1) - } else { - return r.Zero() } + return r.Zero() } g.f.sub(t[1], t[1], t[3]) g.f.double(t[4], t[1]) diff --git a/les/benchmark.go b/les/benchmark.go index a146de2fed..312d4533df 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -75,9 +75,8 @@ func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error { func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error { if b.byHash { return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse) - } else { - return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } + return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) } // benchmarkBodiesOrReceipts implements requestBenchmark @@ -98,9 +97,8 @@ func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error { func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error { if b.receipts { return peer.requestReceipts(0, []common.Hash{b.hashes[index]}) - } else { - return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } + return peer.requestBodies(0, []common.Hash{b.hashes[index]}) } // benchmarkProofsOrCode implements requestBenchmark @@ -119,9 +117,8 @@ func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error { rand.Read(key) if b.code { return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}}) - } else { - return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } + return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } // benchmarkHelperTrie implements requestBenchmark diff --git a/les/lespay/server/balance.go b/les/lespay/server/balance.go index f820a4ad05..f5073d0db1 100644 --- a/les/lespay/server/balance.go +++ b/les/lespay/server/balance.go @@ -588,9 +588,8 @@ func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { } dt = float64(posBalance-newBalance) / timePrice return time.Duration(dt), true - } else { - dt = float64(posBalance) / timePrice } + dt = float64(posBalance) / timePrice } else { if priority > 0 { return 0, false diff --git a/les/lespay/server/balance_tracker.go b/les/lespay/server/balance_tracker.go index c1ea3c6496..edd4b288d9 100644 --- a/les/lespay/server/balance_tracker.go +++ b/les/lespay/server/balance_tracker.go @@ -261,9 +261,8 @@ func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredV func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { if neg { return b.Value(bt.negExp.LogOffset(now)) <= negThreshold - } else { - return b.Value(bt.posExp.LogOffset(now)) <= posThreshold } + return b.Value(bt.posExp.LogOffset(now)) <= posThreshold } // updateTotalBalance adjusts the total balance after executing given callback. diff --git a/les/lespay/server/prioritypool.go b/les/lespay/server/prioritypool.go index c0c33840ca..e3327aba75 100644 --- a/les/lespay/server/prioritypool.go +++ b/les/lespay/server/prioritypool.go @@ -288,9 +288,8 @@ func activePriority(a interface{}, now mclock.AbsTime) int64 { } if c.bias == 0 { return invertPriority(c.nodePriority.Priority(now, c.capacity)) - } else { - return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) } + return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) } // activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue diff --git a/les/odr_requests.go b/les/odr_requests.go index 3cc55c98d8..3704436a03 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -329,9 +329,8 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool { if r.Untrusted { return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId - } else { - return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } + return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) diff --git a/les/serverpool.go b/les/serverpool.go index 9bfa0bd725..6cf4affff0 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -112,9 +112,8 @@ var ( } enc, err := rlp.EncodeToBytes(&ne) return enc, err - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") }, func(enc []byte) (interface{}, error) { var ne nodeHistoryEnc diff --git a/les/serverpool_test.go b/les/serverpool_test.go index 3d0487d102..70a41b74c6 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -119,31 +119,28 @@ func (s *serverPoolTest) start() { s.clock.Sleep(time.Second * 5) s.endWait() return -1 - } else { - switch idx % 3 { - case 0: - // pre-neg returns true only if connection is possible - if canConnect { - return 1 - } else { - return 0 - } - case 1: - // pre-neg returns true but connection might still fail + } + switch idx % 3 { + case 0: + // pre-neg returns true only if connection is possible + if canConnect { + return 1 + } + return 0 + case 1: + // pre-neg returns true but connection might still fail + return 1 + case 2: + // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) + if canConnect { return 1 - case 2: - // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) - if canConnect { - return 1 - } else { - s.beginWait() - s.clock.Sleep(time.Second * 5) - s.endWait() - return -1 - } } + s.beginWait() + s.clock.Sleep(time.Second * 5) + s.endWait() return -1 } + return -1 } } diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index f47534784c..9d5519b9c4 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -219,9 +219,8 @@ func (n *autodisc) String() string { defer n.mu.Unlock() if n.found == nil { return n.what - } else { - return n.found.String() } + return n.found.String() } // wait blocks until auto-discovery has been performed. diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go index 5f99a3da74..d06ad755e2 100644 --- a/p2p/nodestate/nodestate_test.go +++ b/p2p/nodestate/nodestate_test.go @@ -240,9 +240,8 @@ func uint64FieldEnc(field interface{}) ([]byte, error) { if u, ok := field.(uint64); ok { enc, err := rlp.EncodeToBytes(&u) return enc, err - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") } func uint64FieldDec(enc []byte) (interface{}, error) { @@ -254,9 +253,8 @@ func uint64FieldDec(enc []byte) (interface{}, error) { func stringFieldEnc(field interface{}) ([]byte, error) { if s, ok := field.(string); ok { return []byte(s), nil - } else { - return nil, errors.New("invalid field type") } + return nil, errors.New("invalid field type") } func stringFieldDec(enc []byte) (interface{}, error) { diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index a54db4ea68..9b5e2c37f5 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -454,9 +454,8 @@ func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { if len(excludeIDs) > 0 { // Return the difference of nodeIDs and excludeIDs return filterIDs(nodeIDs, excludeIDs) - } else { - return nodeIDs } + return nodeIDs } // GetNodes returns the existing nodes. @@ -472,9 +471,8 @@ func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { if len(excludeIDs) > 0 { nodeIDs := net.getNodeIDs(excludeIDs) return net.getNodesByID(nodeIDs) - } else { - return net.Nodes } + return net.Nodes } // GetNodesByID returns existing nodes with the given enode.IDs. @@ -1098,7 +1096,6 @@ func (net *Network) executeNodeEvent(e *Event) error { func (net *Network) executeConnEvent(e *Event) error { if e.Conn.Up { return net.Connect(e.Conn.One, e.Conn.Other) - } else { - return net.Disconnect(e.Conn.One, e.Conn.Other) } + return net.Disconnect(e.Conn.One, e.Conn.Other) } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 6803745197..418ee10a35 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -39,9 +39,8 @@ func (e *testEncoder) EncodeRLP(w io.Writer) error { } if e.err != nil { return e.err - } else { - w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) } + w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1}) return nil } diff --git a/rpc/client.go b/rpc/client.go index 91e68e73e6..9393adb779 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -404,9 +404,8 @@ func (c *Client) Notify(ctx context.Context, method string, args ...interface{}) if c.isHTTP { return c.sendHTTP(ctx, op, msg) - } else { - return c.send(ctx, op, msg) } + return c.send(ctx, op, msg) } // EthSubscribe registers a subscripion under the "eth" namespace. diff --git a/trie/committer.go b/trie/committer.go index 20c95bed08..33fd9e9823 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -267,6 +267,5 @@ func estimateSize(n node) int { return 1 + len(n) default: panic(fmt.Sprintf("node type %T", n)) - } } From f59ed3565d18c1fa676fd34f4fd41ecccad707e8 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 25 Nov 2020 01:19:36 -0800 Subject: [PATCH 159/948] graphql: always return 400 if errors are present in the response (#21882) * Make sure to return 400 when errors are present in the response * graphql: use less memory in chainconfig for tests Co-authored-by: Martin Holst Swende --- graphql/graphql_test.go | 52 +++++++++++++++++++++++++++++++++++++++-- graphql/service.go | 36 ++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 5ba9c95537..98c8622ee6 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -22,8 +22,13 @@ import ( "net/http" "strings" "testing" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/stretchr/testify/assert" ) @@ -61,6 +66,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { t.Fatalf("could not read from response body: %v", err) } expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" + assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, expected, string(bodyBytes)) } @@ -90,6 +96,32 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, "404 page not found\n", string(bodyBytes)) } +// Tests that 400 is returned when an invalid RPC request is made. +func TestGraphQL_BadRequest(t *testing.T) { + stack := createNode(t, true) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // create http request + body := strings.NewReader("{\"query\": \"{bleh{number}}\",\"variables\": null}") + gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) + if err != nil { + t.Error("could not issue new http request ", err) + } + gqlReq.Header.Set("Content-Type", "application/json") + // read from response + resp := doHTTPRequest(t, gqlReq) + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + expected := "{\"errors\":[{\"message\":\"Cannot query field \\\"bleh\\\" on type \\\"Query\\\".\",\"locations\":[{\"line\":1,\"column\":2}]}]}" + assert.Equal(t, expected, string(bodyBytes)) + assert.Equal(t, 400, resp.StatusCode) +} + func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", @@ -110,8 +142,24 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { } func createGQLService(t *testing.T, stack *node.Node, endpoint string) { - // create backend - ethBackend, err := eth.New(stack, ð.DefaultConfig) + // create backend (use a config which is light on mem consumption) + ethConf := ð.Config{ + Genesis: core.DeveloperGenesisBlock(15, common.Address{}), + Miner: miner.Config{ + Etherbase: common.HexToAddress("0xaabb"), + }, + Ethash: ethash.Config{ + PowMode: ethash.ModeTest, + }, + NetworkId: 1337, + TrieCleanCache: 5, + TrieCleanCacheJournal: "triecache", + TrieCleanCacheRejournal: 60 * time.Minute, + TrieDirtyCache: 5, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 5, + } + ethBackend, err := eth.New(stack, ethConf) if err != nil { t.Fatalf("could not create eth backend: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index ae962e5b36..bcb0a4990d 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,12 +17,44 @@ package graphql import ( + "encoding/json" + "net/http" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/graph-gophers/graphql-go" - "github.com/graph-gophers/graphql-go/relay" ) +type handler struct { + Schema *graphql.Schema +} + +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var params struct { + Query string `json:"query"` + OperationName string `json:"operationName"` + Variables map[string]interface{} `json:"variables"` + } + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables) + responseJSON, err := json.Marshal(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(response.Errors) > 0 { + w.WriteHeader(http.StatusBadRequest) + } + + w.Header().Set("Content-Type", "application/json") + w.Write(responseJSON) + +} + // New constructs a new GraphQL service instance. func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { if backend == nil { @@ -41,7 +73,7 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) if err != nil { return err } - h := &relay.Handler{Schema: s} + h := handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{}) From 810f9e057daccda9eb5c04721b6dd4660f001cc5 Mon Sep 17 00:00:00 2001 From: Alex Prut <1648497+alexprut@users.noreply.github.com> Date: Wed, 25 Nov 2020 21:00:23 +0100 Subject: [PATCH 160/948] all: remove redundant conversions and import names (#21903) --- accounts/abi/reflect_test.go | 4 ++-- accounts/abi/type_test.go | 2 +- accounts/accounts.go | 2 +- accounts/keystore/passphrase.go | 2 +- accounts/keystore/wallet.go | 2 +- accounts/scwallet/wallet.go | 2 +- accounts/usbwallet/wallet.go | 2 +- cmd/clef/main.go | 2 +- cmd/devp2p/dnscmd.go | 2 +- cmd/evm/compiler.go | 2 +- cmd/evm/disasm.go | 2 +- cmd/evm/runner.go | 2 +- cmd/evm/staterunner.go | 2 +- cmd/geth/config.go | 2 +- cmd/geth/main.go | 2 +- cmd/geth/usage.go | 2 +- cmd/puppeth/genesis.go | 4 ++-- cmd/utils/flags.go | 2 +- common/hexutil/json_test.go | 2 +- consensus/ethash/ethash.go | 2 +- contracts/checkpointoracle/oracle.go | 2 +- core/state/statedb.go | 4 ++-- core/tx_pool_test.go | 2 +- core/vm/evm.go | 2 +- core/vm/gas_table.go | 8 ++++---- core/vm/instructions.go | 8 ++++---- core/vm/operations_acl.go | 2 +- eth/downloader/api.go | 2 +- eth/filters/api.go | 4 ++-- eth/filters/filter_system.go | 2 +- eth/filters/filter_system_test.go | 2 +- eth/tracers/tracer.go | 2 +- event/event_test.go | 2 +- event/feed_test.go | 10 +++++----- graphql/graphql.go | 16 ++++++++-------- internal/debug/flags.go | 2 +- internal/ethapi/api.go | 2 +- internal/flags/helpers.go | 2 +- miner/unconfirmed_test.go | 5 ++--- mobile/ethereum.go | 2 +- p2p/enode/nodedb.go | 2 +- signer/core/signed_data.go | 2 +- trie/database.go | 2 +- trie/iterator.go | 2 +- 44 files changed, 66 insertions(+), 67 deletions(-) diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index bac4cd9530..cf13a79da8 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -202,12 +202,12 @@ func TestConvertType(t *testing.T) { fields = append(fields, reflect.StructField{ Name: "X", Type: reflect.TypeOf(new(big.Int)), - Tag: reflect.StructTag("json:\"" + "x" + "\""), + Tag: "json:\"" + "x" + "\"", }) fields = append(fields, reflect.StructField{ Name: "Y", Type: reflect.TypeOf(new(big.Int)), - Tag: reflect.StructTag("json:\"" + "y" + "\""), + Tag: "json:\"" + "y" + "\"", }) val := reflect.New(reflect.StructOf(fields)) val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1))) diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index 48df3aa383..8c3aedca6a 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -255,7 +255,7 @@ func TestTypeCheck(t *testing.T) { {"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"}, {"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"}, {"string", nil, "hello world", ""}, - {"string", nil, string(""), ""}, + {"string", nil, "", ""}, {"string", nil, []byte{}, "abi: cannot use slice as type string as argument"}, {"bytes32[]", nil, [][32]byte{{}}, ""}, {"function", nil, [24]byte{}, ""}, diff --git a/accounts/accounts.go b/accounts/accounts.go index dc85cba174..08a1f0f2b1 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -21,7 +21,7 @@ import ( "fmt" "math/big" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 89cdf0bfca..dd4d7764e4 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -230,7 +230,7 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { key := crypto.ToECDSAUnsafe(keyBytes) return &Key{ - Id: uuid.UUID(keyId), + Id: keyId, Address: crypto.PubkeyToAddress(key.PublicKey), PrivateKey: key, }, nil diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index 498067d497..fe9d2fe49f 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -19,7 +19,7 @@ package keystore import ( "math/big" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 85fae8c114..6476646d7f 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -33,7 +33,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index e39c6bdf34..9f74e5554f 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -25,7 +25,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 3207042a6c..273919cb41 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -54,7 +54,7 @@ import ( "github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/storage" - colorable "github.com/mattn/go-colorable" + "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "gopkg.in/urfave/cli.v1" ) diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index 13110f21c6..f56f0f34e4 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var ( diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go index c019a2fe70..40ad9313c5 100644 --- a/cmd/evm/compiler.go +++ b/cmd/evm/compiler.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var compileCommand = cli.Command{ diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go index 5bc743aa88..68a09cbf50 100644 --- a/cmd/evm/disasm.go +++ b/cmd/evm/disasm.go @@ -23,7 +23,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/core/asm" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var disasmCommand = cli.Command{ diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index d0be6ca1e1..4063767cb8 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var runCommand = cli.Command{ diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index f9a6b06b8f..c4df936c75 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var stateTestCommand = cli.Command{ diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 6b51843aa4..bf1fc55b17 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -24,7 +24,7 @@ import ( "reflect" "unicode" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth" diff --git a/cmd/geth/main.go b/cmd/geth/main.go index aa6881bab0..0f50989208 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -42,7 +42,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" gopsutil "github.com/shirou/gopsutil/mem" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) const ( diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index a9b6f53e72..0e70451ed3 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/flags" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) // AppHelpFlagGroups is the application flags, grouped by functionality. diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go index b3e1709dbf..46268ec11b 100644 --- a/cmd/puppeth/genesis.go +++ b/cmd/puppeth/genesis.go @@ -152,7 +152,7 @@ func newAlethGenesisSpec(network string, genesis *core.Genesis) (*alethGenesisSp spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) spec.Genesis.ParentHash = genesis.ParentHash - spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData) + spec.Genesis.ExtraData = genesis.ExtraData spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit) for address, account := range genesis.Alloc { @@ -430,7 +430,7 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) spec.Genesis.ParentHash = genesis.ParentHash - spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData) + spec.Genesis.ExtraData = genesis.ExtraData spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit) spec.Accounts = make(map[common.UnprefixedAddress]*parityChainSpecAccount) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 26dbb9faf3..56880768f9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -64,7 +64,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" pcsclite "github.com/gballet/go-libpcsclite" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) func init() { diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index 8a6b8643a1..ed7d6fad1a 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -88,7 +88,7 @@ func TestUnmarshalBytes(t *testing.T) { if !checkError(t, test.input, err, test.wantErr) { continue } - if !bytes.Equal(test.want.([]byte), []byte(v)) { + if !bytes.Equal(test.want.([]byte), v) { t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) continue } diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index aa3f002c0d..550d99893d 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -33,7 +33,7 @@ import ( "time" "unsafe" - mmap "github.com/edsrzf/mmap-go" + "github.com/edsrzf/mmap-go" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go index 1f273272ab..7f3127d0b8 100644 --- a/contracts/checkpointoracle/oracle.go +++ b/contracts/checkpointoracle/oracle.go @@ -65,7 +65,7 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log, if err != nil { continue } - if event.Index == section && common.Hash(event.CheckpointHash) == hash { + if event.Index == section && event.CheckpointHash == hash { votes = append(votes, event) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index fe30f595ed..ed9a82379f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -318,7 +318,7 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetProof(a common.Address) ([][]byte, error) { var proof proofList err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof) - return [][]byte(proof), err + return proof, err } // GetStorageProof returns the StorageProof for given key @@ -329,7 +329,7 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, return proof, errors.New("storage trie for requested address does not exist") } err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) - return [][]byte(proof), err + return proof, err } // GetCommittedState retrieves a value from the given account's committed storage trie. diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 4fca734e65..dbc49d4f9c 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -1139,7 +1139,7 @@ func TestTransactionAllowedTxSize(t *testing.T) { t.Fatalf("expected rejection on slightly oversize transaction") } // Try adding a transaction of random not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(10*txMaxSize)))); err == nil { t.Fatalf("expected rejection on oversize transaction") } // Run some sanity checks on the pool internals diff --git a/core/vm/evm.go b/core/vm/evm.go index 6c7b2370a5..9afef76643 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -525,7 +525,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} - contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes()) + contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr) } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 01249a5388..944b6cf0a5 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -96,7 +96,7 @@ var ( func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -135,7 +135,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -183,14 +183,14 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), common.Hash(x.Bytes32())) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) ) value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.SloadGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreSetGasEIP2200, nil diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 4ded0239d9..1137505292 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -341,7 +341,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { slot := callContext.stack.peek() - slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.Address(slot.Bytes20())))) + slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) return nil, nil } @@ -517,7 +517,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] loc := callContext.stack.pop() val := callContext.stack.pop() interpreter.evm.StateDB.SetState(callContext.contract.Address(), - common.Hash(loc.Bytes32()), common.Hash(val.Bytes32())) + loc.Bytes32(), val.Bytes32()) return nil, nil } @@ -817,7 +817,7 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { beneficiary := callContext.stack.pop() balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) - interpreter.evm.StateDB.AddBalance(common.Address(beneficiary.Bytes20()), balance) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.Suicide(callContext.contract.Address()) return nil, nil } @@ -832,7 +832,7 @@ func makeLog(size int) executionFunc { mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { addr := stack.pop() - topics[i] = common.Hash(addr.Bytes32()) + topics[i] = addr.Bytes32() } d := callContext.memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 41b0549c51..191953ce5e 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -73,7 +73,7 @@ func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m // return params.SloadGasEIP2200, nil return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return cost + params.SstoreSetGasEIP2200, nil diff --git a/eth/downloader/api.go b/eth/downloader/api.go index 57ff3d71af..2024d23dea 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -20,7 +20,7 @@ import ( "context" "sync" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc" ) diff --git a/eth/filters/api.go b/eth/filters/api.go index 30d7b71c31..664f229a04 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -25,7 +25,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -292,7 +292,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { logs := make(chan []*types.Log) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) if err != nil { - return rpc.ID(""), err + return "", err } api.filtersMu.Lock() diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index a105ec51c3..12f037d0f9 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -24,7 +24,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index c8d1d43abb..b534c1d47f 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -25,7 +25,7 @@ import ( "testing" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 16a8c7698f..5d806d9026 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - duktape "gopkg.in/olebedev/go-duktape.v3" + "gopkg.in/olebedev/go-duktape.v3" ) // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. diff --git a/event/event_test.go b/event/event_test.go index cc9fa5d7c8..bdad11f13d 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -29,7 +29,7 @@ func TestSubCloseUnsub(t *testing.T) { // the point of this test is **not** to panic var mux TypeMux mux.Stop() - sub := mux.Subscribe(int(0)) + sub := mux.Subscribe(0) sub.Unsubscribe() } diff --git a/event/feed_test.go b/event/feed_test.go index 93badaaee6..cdf29fdd73 100644 --- a/event/feed_test.go +++ b/event/feed_test.go @@ -27,8 +27,8 @@ import ( func TestFeedPanics(t *testing.T) { { var f Feed - f.Send(int(2)) - want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(int(0))} + f.Send(2) + want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)} if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { t.Error(err) } @@ -37,14 +37,14 @@ func TestFeedPanics(t *testing.T) { var f Feed ch := make(chan int) f.Subscribe(ch) - want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(int(0))} + want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)} if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil { t.Error(err) } } { var f Feed - f.Send(int(2)) + f.Send(2) want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))} if err := checkPanic(want, func() { f.Subscribe(make(chan uint64)) }); err != nil { t.Error(err) @@ -58,7 +58,7 @@ func TestFeedPanics(t *testing.T) { } { var f Feed - if err := checkPanic(errBadChannel, func() { f.Subscribe(int(0)) }); err != nil { + if err := checkPanic(errBadChannel, func() { f.Subscribe(0) }); err != nil { t.Error(err) } } diff --git a/graphql/graphql.go b/graphql/graphql.go index 559da8aaaa..16a74b4037 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -77,7 +77,7 @@ func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(state.GetCode(a.address)), nil + return state.GetCode(a.address), nil } func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) (common.Hash, error) { @@ -116,7 +116,7 @@ func (l *Log) Topics(ctx context.Context) []common.Hash { } func (l *Log) Data(ctx context.Context) hexutil.Bytes { - return hexutil.Bytes(l.log.Data) + return l.log.Data } // Transaction represents an Ethereum transaction. @@ -157,7 +157,7 @@ func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) { if err != nil || tx == nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(tx.Data()), nil + return tx.Data(), nil } func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) { @@ -410,7 +410,7 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { if err != nil { return nil, err } - b.receipts = []*types.Receipt(receipts) + b.receipts = receipts } return b.receipts, nil } @@ -490,7 +490,7 @@ func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(header.Nonce[:]), nil + return header.Nonce[:], nil } func (b *Block) MixHash(ctx context.Context) (common.Hash, error) { @@ -564,7 +564,7 @@ func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(header.Extra), nil + return header.Extra, nil } func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { @@ -572,7 +572,7 @@ func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { if err != nil { return hexutil.Bytes{}, err } - return hexutil.Bytes(header.Bloom.Bytes()), nil + return header.Bloom.Bytes(), nil } func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { @@ -907,7 +907,7 @@ func (r *Resolver) Block(ctx context.Context, args struct { }) (*Block, error) { var block *Block if args.Number != nil { - number := rpc.BlockNumber(uint64(*args.Number)) + number := rpc.BlockNumber(*args.Number) numberOrHash := rpc.BlockNumberOrHashWithNumber(number) block = &Block{ backend: r.backend, diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 3b077b6e08..e3a01378ca 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" "github.com/fjl/memsize/memsizeui" - colorable "github.com/mattn/go-colorable" + "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "gopkg.in/urfave/cli.v1" ) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0d6ace9b5b..030bdb37a4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -793,7 +793,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { var data []byte if args.Data != nil { - data = []byte(*args.Data) + data = *args.Data } msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index 900fec454c..f61ed4b689 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -21,7 +21,7 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/params" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var ( diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go index 42e77f3e64..dc83cb9265 100644 --- a/miner/unconfirmed_test.go +++ b/miner/unconfirmed_test.go @@ -19,7 +19,6 @@ package miner import ( "testing" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -43,7 +42,7 @@ func TestUnconfirmedInsertBounds(t *testing.T) { for depth := uint64(0); depth < 2*uint64(limit); depth++ { // Insert multiple blocks for the same level just to stress it for i := 0; i < int(depth); i++ { - pool.Insert(depth, common.Hash([32]byte{byte(depth), byte(i)})) + pool.Insert(depth, [32]byte{byte(depth), byte(i)}) } // Validate that no blocks below the depth allowance are left in pool.blocks.Do(func(block interface{}) { @@ -63,7 +62,7 @@ func TestUnconfirmedShifts(t *testing.T) { pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) for depth := start; depth < start+uint64(limit); depth++ { - pool.Insert(depth, common.Hash([32]byte{byte(depth)})) + pool.Insert(depth, [32]byte{byte(depth)}) } // Try to shift below the limit and ensure no blocks are dropped pool.Shift(start + uint64(limit) - 1) diff --git a/mobile/ethereum.go b/mobile/ethereum.go index 59da852397..97c46ddca7 100644 --- a/mobile/ethereum.go +++ b/mobile/ethereum.go @@ -21,7 +21,7 @@ package geth import ( "errors" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" ) diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index bd066ce857..643fbe5711 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -163,7 +163,7 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { } key = key[len(dbDiscoverRoot)+1:] // Split out the IP. - ip = net.IP(key[:16]) + ip = key[:16] if ip4 := ip.To4(); ip4 != nil { ip = ip4 } diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 19377a521b..3bff1e1f20 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -506,7 +506,7 @@ func parseBytes(encType interface{}) ([]byte, bool) { case []byte: return v, true case hexutil.Bytes: - return []byte(v), true + return v, true case string: bytes, err := hexutil.Decode(v) if err != nil { diff --git a/trie/database.go b/trie/database.go index d8fe45f444..7c2f207097 100644 --- a/trie/database.go +++ b/trie/database.go @@ -100,7 +100,7 @@ func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") } func (n rawNode) EncodeRLP(w io.Writer) error { - _, err := w.Write([]byte(n)) + _, err := w.Write(n) return err } diff --git a/trie/iterator.go b/trie/iterator.go index bb4025d8f3..76d437c403 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -173,7 +173,7 @@ func (it *nodeIterator) LeafKey() []byte { func (it *nodeIterator) LeafBlob() []byte { if len(it.stack) > 0 { if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { - return []byte(node) + return node } } panic("not at leaf") From 429e7141f2f41c1d66dd4dd711a47ca9e0f0c2cb Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Thu, 26 Nov 2020 05:16:36 +0800 Subject: [PATCH 161/948] p2p/discover: fix deadlock in discv5 message dispatch (#21858) This fixes a deadlock that could occur when a response packet arrived after a call had already received enough responses and was about to signal completion to the dispatch loop. Co-authored-by: Felix Lange --- p2p/discover/v5_udp.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index c95317a005..9dd2b31733 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -454,9 +454,20 @@ func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) // callDone tells dispatch that the active call is done. func (t *UDPv5) callDone(c *callV5) { - select { - case t.callDoneCh <- c: - case <-t.closeCtx.Done(): + // This needs a loop because further responses may be incoming until the + // send to callDoneCh has completed. Such responses need to be discarded + // in order to avoid blocking the dispatch loop. + for { + select { + case <-c.ch: + // late response, discard. + case <-c.err: + // late error, discard. + case t.callDoneCh <- c: + return + case <-t.closeCtx.Done(): + return + } } } From fa572cd2971fda0bf773d2817acb23385bac66c7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 27 Nov 2020 11:13:54 +0000 Subject: [PATCH 162/948] crypto: signing builds with signify/minisign (#21798) * internal/build: implement signify's signing func * Add signify to the ci utility * fix output file format * Add unit test for signify * holiman's + travis' feedback * internal/build: verify signify's output * crypto: move signify to common dir * use go-minisign to verify binaries * more holiman feedback * crypto, ci: support minisign output * only accept one-line trusted comments * configurable untrusted comments * code cleanup in tests * revert to use ed25519 from the stdlib * bug: fix for empty untrusted comments * write timestamp as comment if trusted comment isn't present * rename line checker to commentHasManyLines * crypto: added signify fuzzer (#6) * crypto: added signify fuzzer * stuff * crypto: updated signify fuzzer to fuzz comments * crypto: repro signify crashes * rebased fuzzer on build-signify branch * hide fuzzer behind gofuzz build flag * extract key data inside a single function * don't treat \r as a newline * travis: fix signing command line * do not use an external binary in tests * crypto: move signify to crypto/signify * travis: fix formatting issue * ci: fix linter build after package move Co-authored-by: Marius van der Wijden --- .travis.yml | 26 +++--- build/ci.go | 54 ++++++++---- crypto/signify/signify.go | 118 +++++++++++++++++++++++++ crypto/signify/signify_fuzz.go | 148 +++++++++++++++++++++++++++++++ crypto/signify/signify_test.go | 154 +++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 3 + 7 files changed, 472 insertions(+), 32 deletions(-) create mode 100644 crypto/signify/signify.go create mode 100644 crypto/signify/signify_fuzz.go create mode 100644 crypto/signify/signify_test.go mode change 100755 => 100644 go.mod mode change 100755 => 100644 go.sum diff --git a/.travis.yml b/.travis.yml index fd31e3d506..d37458792a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,22 +67,22 @@ jobs: script: # Build for the primary platforms that Trusty can manage - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch 386 - - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds # Switch over GCC to cross compilation (breaks 386, hence why do it here only) - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross - sudo ln -s /usr/include/asm-generic /usr/include/asm - GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc - - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds # This builder does the Linux Azure MIPS xgo uploads - stage: build @@ -100,19 +100,19 @@ jobs: script: - go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done - - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done - - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done - - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify LINUX_SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done - - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds # This builder does the Android Maven and Azure uploads - stage: build @@ -151,7 +151,7 @@ jobs: - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds + - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify ANDROID_SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds # This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads - stage: build @@ -167,7 +167,7 @@ jobs: submodules: false # avoid cloning ethereum/tests script: - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify OSX_SIGNIFY_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure - gem uninstall cocoapods -a -x @@ -182,7 +182,7 @@ jobs: # Workaround for https://github.com/golang/go/issues/23749 - export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc' - - go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds + - go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify IOS_SIGNIFY_KEY -deploy trunk -upload gethstore/builds # These builders run the tests - stage: build diff --git a/build/ci.go b/build/ci.go index 0cffb903aa..951b21f910 100644 --- a/build/ci.go +++ b/build/ci.go @@ -26,7 +26,7 @@ Available commands are: install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables test [ -coverage ] [ packages... ] -- runs the tests lint -- runs certain pre-selected linters - archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts + archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts importkeys -- imports signing keys from env debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package nsis -- creates a Windows NSIS installer @@ -58,6 +58,7 @@ import ( "time" "github.com/cespare/cp" + signifyPkg "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/params" ) @@ -396,11 +397,12 @@ func downloadLinter(cachedir string) string { // Release Packaging func doArchive(cmdline []string) { var ( - arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") - atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) - upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) - ext string + arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") + atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + ext string ) flag.CommandLine.Parse(cmdline) switch *atype { @@ -427,7 +429,7 @@ func doArchive(cmdline []string) { log.Fatal(err) } for _, archive := range []string{geth, alltools} { - if err := archiveUpload(archive, *upload, *signer); err != nil { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { log.Fatal(err) } } @@ -447,7 +449,7 @@ func archiveBasename(arch string, archiveVersion string) string { return platform + "-" + archiveVersion } -func archiveUpload(archive string, blobstore string, signer string) error { +func archiveUpload(archive string, blobstore string, signer string, signify string) error { // If signing was requested, generate the signature files if signer != "" { key := getenvBase64(signer) @@ -455,6 +457,12 @@ func archiveUpload(archive string, blobstore string, signer string) error { return err } } + if signify != "" { + key := getenvBase64(string(signify)) + if err := signifyPkg.SignifySignFile(archive, archive+".sig", string(key), "verify with geth.pub", fmt.Sprintf("%d", time.Now().UTC().Unix())); err != nil { + return err + } + } // If uploading to Azure was requested, push the archive possibly with its signature if blobstore != "" { auth := build.AzureBlobstoreConfig{ @@ -470,6 +478,11 @@ func archiveUpload(archive string, blobstore string, signer string) error { return err } } + if signify != "" { + if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { + return err + } + } } return nil } @@ -806,6 +819,7 @@ func doWindowsInstaller(cmdline []string) { var ( arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) + signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) ) @@ -867,7 +881,7 @@ func doWindowsInstaller(cmdline []string) { filepath.Join(*workdir, "geth.nsi"), ) // Sign and publish installer. - if err := archiveUpload(installer, *upload, *signer); err != nil { + if err := archiveUpload(installer, *upload, *signer, *signify); err != nil { log.Fatal(err) } } @@ -876,10 +890,11 @@ func doWindowsInstaller(cmdline []string) { func doAndroidArchive(cmdline []string) { var ( - local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) - deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) - upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) + local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. ANDROID_SIGNIFY_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) + upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) ) flag.CommandLine.Parse(cmdline) env := build.Env() @@ -908,7 +923,7 @@ func doAndroidArchive(cmdline []string) { archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar" os.Rename("geth.aar", archive) - if err := archiveUpload(archive, *upload, *signer); err != nil { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { log.Fatal(err) } // Sign and upload all the artifacts to Maven Central @@ -1001,10 +1016,11 @@ func newMavenMetadata(env build.Environment) mavenMetadata { func doXCodeFramework(cmdline []string) { var ( - local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) - deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) - upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. IOS_SIGNIFY_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) ) flag.CommandLine.Parse(cmdline) env := build.Env() @@ -1032,7 +1048,7 @@ func doXCodeFramework(cmdline []string) { maybeSkipArchive(env) // Sign and upload the framework to Azure - if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil { + if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil { log.Fatal(err) } // Prepare and upload a PodSpec to CocoaPods diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go new file mode 100644 index 0000000000..e86c4f09b0 --- /dev/null +++ b/crypto/signify/signify.go @@ -0,0 +1,118 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// signFile reads the contents of an input file and signs it (in armored format) +// with the key provided, placing the signature into the output file. + +package signify + +import ( + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + "crypto/ed25519" +) + +var ( + errInvalidKeyHeader = errors.New("Incorrect key header") + errInvalidKeyLength = errors.New("invalid, key length != 104") +) + +func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) { + keydata, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, nil, nil, err + } + + if len(keydata) != 104 { + return nil, nil, nil, errInvalidKeyLength + } + + if string(keydata[:2]) != "Ed" { + return nil, nil, nil, errInvalidKeyHeader + } + + return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil +} + +func commentHasManyLines(comment string) bool { + firstLFIndex := strings.IndexByte(comment, 10) + return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1) +} + +// SignifySignFile creates a signature of the input file. +func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error { + in, err := os.Open(input) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(output) + if err != nil { + return err + } + defer out.Close() + + skey, header, keyNum, err := parsePrivateKey(key) + if err != nil { + return err + } + + filedata, err := ioutil.ReadAll(in) + if err != nil { + return err + } + + rawSig := ed25519.Sign(skey, filedata) + + var sigdata []byte + sigdata = append(sigdata, header...) + sigdata = append(sigdata, keyNum...) + sigdata = append(sigdata, rawSig...) + + // Check that the trusted comment fits in one line + if commentHasManyLines(unTrustedComment) { + return errors.New("untrusted comment must fit on a single line") + } + + if unTrustedComment == "" { + unTrustedComment = "verify with " + input + ".pub" + } + out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata))) + + // Add the trusted comment if unavailable + if trustedComment == "" { + trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) + } + + // Check that the trusted comment fits in one line + if commentHasManyLines(trustedComment) { + return errors.New("trusted comment must fit on a single line") + } + + var sigAndComment []byte + sigAndComment = append(sigAndComment, rawSig...) + sigAndComment = append(sigAndComment, []byte(trustedComment)...) + out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment)))) + + return nil +} diff --git a/crypto/signify/signify_fuzz.go b/crypto/signify/signify_fuzz.go new file mode 100644 index 0000000000..d1bcf356a4 --- /dev/null +++ b/crypto/signify/signify_fuzz.go @@ -0,0 +1,148 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build gofuzz + +package signify + +import ( + "bufio" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "runtime" + + fuzz "github.com/google/gofuzz" + "github.com/jedisct1/go-minisign" +) + +func Fuzz(data []byte) int { + if len(data) < 32 { + return -1 + } + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + testSecKey, testPubKey := createKeyPair() + // Create message + tmpFile.Write(data) + if err = tmpFile.Close(); err != nil { + panic(err) + } + // Fuzz comments + var untrustedComment string + var trustedComment string + f := fuzz.NewFromGoFuzz(data) + f.Fuzz(&untrustedComment) + f.Fuzz(&trustedComment) + fmt.Printf("untrusted: %v\n", untrustedComment) + fmt.Printf("trusted: %v\n", trustedComment) + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment) + if err != nil { + panic(err) + } + defer os.Remove(tmpFile.Name() + ".sig") + + signify := "signify" + path := os.Getenv("SIGNIFY") + if path != "" { + signify = path + } + + _, err := exec.LookPath(signify) + if err != nil { + panic(err) + } + + // Write the public key into the file to pass it as + // an argument to signify-openbsd + pubKeyFile, err := ioutil.TempFile("", "") + if err != nil { + panic(err) + } + defer os.Remove(pubKeyFile.Name()) + defer pubKeyFile.Close() + pubKeyFile.WriteString("untrusted comment: signify public key\n") + pubKeyFile.WriteString(testPubKey) + pubKeyFile.WriteString("\n") + + cmd := exec.Command(signify, "-V", "-p", pubKeyFile.Name(), "-x", tmpFile.Name()+".sig", "-m", tmpFile.Name()) + if output, err := cmd.CombinedOutput(); err != nil { + panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) + } + + // Verify the signature using a golang library + sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") + if err != nil { + panic(err) + } + + pKey, err := minisign.NewPublicKey(testPubKey) + if err != nil { + panic(err) + } + + valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) + if err != nil { + panic(err) + } + if !valid { + panic("invalid signature") + } + return 1 +} + +func getKey(fileS string) (string, error) { + file, err := os.Open(fileS) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + // Discard the first line + scanner.Scan() + scanner.Scan() + return scanner.Text(), scanner.Err() +} + +func createKeyPair() (string, string) { + // Create key and put it in correct format + tmpKey, err := ioutil.TempFile("", "") + defer os.Remove(tmpKey.Name()) + defer os.Remove(tmpKey.Name() + ".pub") + defer os.Remove(tmpKey.Name() + ".sec") + cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec") + if output, err := cmd.CombinedOutput(); err != nil { + panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) + } + secKey, err := getKey(tmpKey.Name() + ".sec") + if err != nil { + panic(err) + } + pubKey, err := getKey(tmpKey.Name() + ".pub") + if err != nil { + panic(err) + } + return secKey, pubKey +} diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go new file mode 100644 index 0000000000..af77eaf227 --- /dev/null +++ b/crypto/signify/signify_test.go @@ -0,0 +1,154 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// signFile reads the contents of an input file and signs it (in armored format) +// with the key provided, placing the signature into the output file. + +package signify + +import ( + "io/ioutil" + "math/rand" + "os" + "testing" + "time" + + "github.com/jedisct1/go-minisign" +) + +var ( + testSecKey = "RWRCSwAAAABVN5lr2JViGBN8DhX3/Qb/0g0wBdsNAR/APRW2qy9Fjsfr12sK2cd3URUFis1jgzQzaoayK8x4syT4G3Gvlt9RwGIwUYIQW/0mTeI+ECHu1lv5U4Wa2YHEPIesVPyRm5M=" + testPubKey = "RWTAPRW2qy9FjsBiMFGCEFv9Jk3iPhAh7tZb+VOFmtmBxDyHrFT8kZuT" +) + +func TestSignify(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") + + // Verify the signature using a golang library + sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig") + if err != nil { + t.Fatal(err) + } + + pKey, err := minisign.NewPublicKey(testPubKey) + if err != nil { + t.Fatal(err) + } + + valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig) + if err != nil { + t.Fatal(err) + } + if !valid { + t.Fatal("invalid signature") + } +} + +func TestSignifyTrustedCommentTooManyLines(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") + if err == nil || err.Error() == "" { + t.Fatalf("should have errored on a multi-line trusted comment, got %v", err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} + +func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} + +func TestSignifyTrustedCommentEmpty(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + rand.Seed(time.Now().UnixNano()) + + data := make([]byte, 1024) + rand.Read(data) + tmpFile.Write(data) + + if err = tmpFile.Close(); err != nil { + t.Fatal(err) + } + + err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name() + ".sig") +} diff --git a/go.mod b/go.mod old mode 100755 new mode 100644 index ae1cf64aaf..9a35f6447f --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 + github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 github.com/kr/pretty v0.1.0 // indirect diff --git a/go.sum b/go.sum old mode 100755 new mode 100644 index 10bec96411..dedae7bc7a --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= @@ -214,6 +216,7 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8 github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= From b71334ac3de38338e618aaf8ea6b4a884d2d80f5 Mon Sep 17 00:00:00 2001 From: Kristofer Peterson Date: Sun, 29 Nov 2020 12:43:15 +0000 Subject: [PATCH 163/948] accounts, signer: fix Ledger Live account derivation path (clef) (#21757) * signer/core/api: fix derivation of ledger live accounts For ledger hardware wallets, change account iteration as follows: - ledger legacy: m/44'/60'/0'/X; for 0<=X<5 - ledger live: m/44'/60'/0'/0/X; for 0<=X<5 - ledger legacy: m/44'/60'/0'/X; for 0<=X<10 - ledger live: m/44'/60'/X'/0/0; for 0<=X<10 Non-ledger derivation is unchanged and remains as: - non-ledger: m/44'/60'/0'/0/X; for 0<=X<10 * signer/core/api: derive ten default paths for all hardware wallets, plus ten legacy and ten live paths for ledger wallets * signer/core/api: as .../0'/0/0 already included by default paths, do not include it again with ledger live paths * accounts, signer: implement path iterators for hd wallets Co-authored-by: Martin Holst Swende --- accounts/hd.go | 28 +++++++++++++ accounts/hd_test.go | 39 ++++++++++++++++++ signer/core/api.go | 97 +++++++++++++++++++++++---------------------- 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/accounts/hd.go b/accounts/hd.go index 75c4761106..54acea3b26 100644 --- a/accounts/hd.go +++ b/accounts/hd.go @@ -150,3 +150,31 @@ func (path *DerivationPath) UnmarshalJSON(b []byte) error { *path, err = ParseDerivationPath(dp) return err } + +// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component: +// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N. +func DefaultIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[len(path)-1]-- + return func() DerivationPath { + path[len(path)-1]++ + return path + } +} + +// LedgerLiveIterator creates a bip44 path iterator for Ledger Live. +// Ledger Live increments the third component rather than the fifth component +// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0. +func LedgerLiveIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[2]-- + return func() DerivationPath { + // ledgerLivePathIterator iterates on the third component + path[2]++ + return path + } +} diff --git a/accounts/hd_test.go b/accounts/hd_test.go index 3156a487ee..0743bbe666 100644 --- a/accounts/hd_test.go +++ b/accounts/hd_test.go @@ -17,6 +17,7 @@ package accounts import ( + "fmt" "reflect" "testing" ) @@ -77,3 +78,41 @@ func TestHDPathParsing(t *testing.T) { } } } + +func testDerive(t *testing.T, next func() DerivationPath, expected []string) { + t.Helper() + for i, want := range expected { + if have := next(); fmt.Sprintf("%v", have) != want { + t.Errorf("step %d, have %v, want %v", i, have, want) + } + } +} + +func TestHdPathIteration(t *testing.T) { + testDerive(t, DefaultIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1", + "m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3", + "m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5", + "m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7", + "m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9", + }) + + testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath), + []string{ + "m/44'/60'/0'/0", "m/44'/60'/0'/1", + "m/44'/60'/0'/2", "m/44'/60'/0'/3", + "m/44'/60'/0'/4", "m/44'/60'/0'/5", + "m/44'/60'/0'/6", "m/44'/60'/0'/7", + "m/44'/60'/0'/8", "m/44'/60'/0'/9", + }) + + testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0", + "m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0", + "m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0", + "m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0", + "m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0", + }) +} diff --git a/signer/core/api.go b/signer/core/api.go index 43926a75ff..7595d2d484 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -322,62 +322,65 @@ func (api *SignerAPI) openTrezor(url accounts.URL) { // startUSBListener starts a listener for USB events, for hardware wallet interaction func (api *SignerAPI) startUSBListener() { - events := make(chan accounts.WalletEvent, 16) + eventCh := make(chan accounts.WalletEvent, 16) am := api.am - am.Subscribe(events) - go func() { + am.Subscribe(eventCh) + // Open any wallets already attached + for _, wallet := range am.Wallets() { + if err := wallet.Open(""); err != nil { + log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) + if err == usbwallet.ErrTrezorPINNeeded { + go api.openTrezor(wallet.URL()) + } + } + } + go api.derivationLoop(eventCh) +} - // Open any wallets already attached - for _, wallet := range am.Wallets() { - if err := wallet.Open(""); err != nil { - log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err) +// derivationLoop listens for wallet events +func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) { + // Listen for wallet event till termination + for event := range events { + switch event.Kind { + case accounts.WalletArrived: + if err := event.Wallet.Open(""); err != nil { + log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) if err == usbwallet.ErrTrezorPINNeeded { - go api.openTrezor(wallet.URL()) + go api.openTrezor(event.Wallet.URL()) } } - } - // Listen for wallet event till termination - for event := range events { - switch event.Kind { - case accounts.WalletArrived: - if err := event.Wallet.Open(""); err != nil { - log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) - if err == usbwallet.ErrTrezorPINNeeded { - go api.openTrezor(event.Wallet.URL()) + case accounts.WalletOpened: + status, _ := event.Wallet.Status() + log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) + var derive = func(limit int, next func() accounts.DerivationPath) { + // Derive first N accounts, hardcoded for now + for i := 0; i < limit; i++ { + path := next() + if acc, err := event.Wallet.Derive(path, true); err != nil { + log.Warn("Account derivation failed", "error", err) + } else { + log.Info("Derived account", "address", acc.Address, "path", path) } } - case accounts.WalletOpened: - status, _ := event.Wallet.Status() - log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) - var derive = func(numToDerive int, base accounts.DerivationPath) { - // Derive first N accounts, hardcoded for now - var nextPath = make(accounts.DerivationPath, len(base)) - copy(nextPath[:], base[:]) - - for i := 0; i < numToDerive; i++ { - acc, err := event.Wallet.Derive(nextPath, true) - if err != nil { - log.Warn("Account derivation failed", "error", err) - } else { - log.Info("Derived account", "address", acc.Address, "path", nextPath) - } - nextPath[len(nextPath)-1]++ - } - } - if event.Wallet.URL().Scheme == "ledger" { - log.Info("Deriving ledger default paths") - derive(numberOfAccountsToDerive/2, accounts.DefaultBaseDerivationPath) - log.Info("Deriving ledger legacy paths") - derive(numberOfAccountsToDerive/2, accounts.LegacyLedgerBaseDerivationPath) - } else { - derive(numberOfAccountsToDerive, accounts.DefaultBaseDerivationPath) - } - case accounts.WalletDropped: - log.Info("Old wallet dropped", "url", event.Wallet.URL()) - event.Wallet.Close() } + log.Info("Deriving default paths") + derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.DefaultBaseDerivationPath)) + if event.Wallet.URL().Scheme == "ledger" { + log.Info("Deriving ledger legacy paths") + derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.LegacyLedgerBaseDerivationPath)) + log.Info("Deriving ledger live paths") + // For ledger live, since it's based off the same (DefaultBaseDerivationPath) + // as one we've already used, we need to step it forward one step to avoid + // hitting the same path again + nextFn := accounts.LedgerLiveIterator(accounts.DefaultBaseDerivationPath) + nextFn() + derive(numberOfAccountsToDerive, nextFn) + } + case accounts.WalletDropped: + log.Info("Old wallet dropped", "url", event.Wallet.URL()) + event.Wallet.Close() } - }() + } } // List returns the set of wallet this signer manages. Each wallet can contain From 566cb4c5f098a132d485a375cb7a02883c88a48f Mon Sep 17 00:00:00 2001 From: Pascal Dierich Date: Mon, 30 Nov 2020 09:03:24 +0100 Subject: [PATCH 164/948] accounts/keystore: add missing function doc for SignText (#21914) Co-authored-by: Pascal Dierich --- accounts/keystore/wallet.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index fe9d2fe49f..1066095f6d 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -58,7 +58,7 @@ func (w *keystoreWallet) Open(passphrase string) error { return nil } func (w *keystoreWallet) Close() error { return nil } // Accounts implements accounts.Wallet, returning an account list consisting of -// a single account that the plain kestore wallet contains. +// a single account that the plain keystore wallet contains. func (w *keystoreWallet) Accounts() []accounts.Account { return []accounts.Account{w.account} } @@ -93,12 +93,12 @@ func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte return w.keystore.SignHash(account, hash) } -// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed. func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { return w.signHash(account, crypto.Keccak256(data)) } -// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed. func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { @@ -108,12 +108,14 @@ func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passph return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) } +// SignText implements accounts.Wallet, attempting to sign the hash of +// the given text with the given account. func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { return w.signHash(account, accounts.TextHash(text)) } // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the -// given hash with the given account using passphrase as extra authentication. +// hash of the given text with the given account using passphrase as extra authentication. func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { From aba0c234c29c72860c369ec97553716a3fad11cd Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 30 Nov 2020 14:43:20 +0100 Subject: [PATCH 165/948] cmd/geth: make tests run quicker + use less memory and disk (#21919) --- cmd/geth/accountcmd_test.go | 59 +++++++++++------------ cmd/geth/consolecmd_test.go | 93 ++++++++++++++++--------------------- cmd/geth/dao_test.go | 4 +- cmd/geth/genesis_test.go | 2 +- cmd/geth/les_test.go | 2 +- 5 files changed, 71 insertions(+), 89 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 2f15915b08..e27adb6916 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -43,13 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string { } func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "account", "list") + geth := runGeth(t, "--nousb", "account", "list") geth.ExpectExit() } func TestAccountList(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "list", "--datadir", datadir) + geth := runGeth(t, "--nousb", "account", "list", "--datadir", datadir) defer geth.ExpectExit() if runtime.GOOS == "windows" { geth.Expect(` @@ -138,7 +138,7 @@ Fatal: Passwords do not match func TestAccountUpdate(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "account", "update", + geth := runGeth(t, "--nousb", "account", "update", "--datadir", datadir, "--lightkdf", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() @@ -153,7 +153,7 @@ Repeat password: {{.InputLine "foobar2"}} } func TestWalletImport(t *testing.T) { - geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. @@ -168,7 +168,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} } func TestWalletImportBadPassword(t *testing.T) { - geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. @@ -178,11 +178,8 @@ Fatal: could not decrypt key with given password } func TestUnlockFlag(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "256", "--ipcdisable", - "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -202,10 +199,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") + defer geth.ExpectExit() geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -221,10 +217,9 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could // https://github.com/ethereum/go-ethereum/issues/1785 func TestUnlockFlagMultiIndex(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--unlock", "0,2", "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--unlock", "0,2", "js", "testdata/empty.js") + geth.Expect(` Unlocking account 0 | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -247,11 +242,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagPasswordFile(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--password", "testdata/passwords.txt", "--unlock", "0,2", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", "testdata/passwords.txt", "--unlock", "0,2", "js", "testdata/empty.js") + geth.ExpectExit() wantMessages := []string{ @@ -267,10 +260,9 @@ func TestUnlockFlagPasswordFile(t *testing.T) { } func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--datadir", datadir, "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", + "testdata/wrong-passwords.txt", "--unlock", "0,2") defer geth.ExpectExit() geth.Expect(` Fatal: Failed to unlock account 0 (could not decrypt key with given password) @@ -279,9 +271,9 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) func TestUnlockFlagAmbiguous(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") defer geth.ExpectExit() @@ -317,9 +309,10 @@ In order to avoid this warning, you need to remove the following duplicate key f func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", - "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + defer geth.ExpectExit() // Helper for the expect template, returns absolute keystore path. diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 913b060361..b0555c45d7 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -35,16 +35,25 @@ const ( httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" ) +// spawns geth with the given command line args, using a set of flags to minimise +// memory and disk IO. If the args don't set --datadir, the +// child g gets a temporary data directory. +func runMinimalGeth(t *testing.T, args ...string) *testgeth { + // --ropsten to make the 'writing genesis to disk' faster (no accounts) + // --networkid=1337 to avoid cache bump + // --syncmode=full to avoid allocating fast sync bloom + allArgs := []string{"--ropsten", "--nousb", "--networkid", "1337", "--syncmode=full", "--port", "0", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64"} + return runGeth(t, append(allArgs, args...)...) +} + // Tests that a node embedded within a console can be started up properly and // then terminated by closing the input stream. func TestConsoleWelcome(t *testing.T) { coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" // Start a geth console, make sure it's cleaned up and terminate the console - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, - "console") + geth := runMinimalGeth(t, "--etherbase", coinbase, "console") // Gather all the infos the welcome message needs to contain geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) @@ -73,10 +82,13 @@ To exit, press ctrl-d } // Tests that a console can be attached to a running node via various means. -func TestIPCAttachWelcome(t *testing.T) { +func TestAttachWelcome(t *testing.T) { + var ( + ipc string + httpPort string + wsPort string + ) // Configure the instance for IPC attachment - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - var ipc string if runtime.GOOS == "windows" { ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999)) } else { @@ -84,51 +96,28 @@ func TestIPCAttachWelcome(t *testing.T) { defer os.RemoveAll(ws) ipc = filepath.Join(ws, "geth.ipc") } - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ipcpath", ipc) - - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - waitForEndpoint(t, ipc, 3*time.Second) - testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) - -} - -func TestHTTPAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--http", "--http.port", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - endpoint := "http://127.0.0.1:" + port - waitForEndpoint(t, endpoint, 3*time.Second) - testAttachWelcome(t, geth, endpoint, httpAPIs) -} - -func TestWSAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P - - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ws", "--ws.port", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - endpoint := "ws://127.0.0.1:" + port - waitForEndpoint(t, endpoint, 3*time.Second) - testAttachWelcome(t, geth, endpoint, httpAPIs) + // And HTTP + WS attachment + p := trulyRandInt(1024, 65533) // Yeah, sometimes this will fail, sorry :P + httpPort = strconv.Itoa(p) + wsPort = strconv.Itoa(p + 1) + geth := runMinimalGeth(t, "--etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", + "--ipcpath", ipc, + "--http", "--http.port", httpPort, + "--ws", "--ws.port", wsPort) + t.Run("ipc", func(t *testing.T) { + waitForEndpoint(t, ipc, 3*time.Second) + testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) + }) + t.Run("http", func(t *testing.T) { + endpoint := "http://127.0.0.1:" + httpPort + waitForEndpoint(t, endpoint, 3*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) + t.Run("ws", func(t *testing.T) { + endpoint := "ws://127.0.0.1:" + wsPort + waitForEndpoint(t, endpoint, 3*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) } func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index 6c36771e97..df7f14fdb8 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -115,10 +115,10 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", test, err) } - runGeth(t, "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "--nousb", "--networkid", "1337", "init", json).WaitExit() } else { // Force chain initialization - args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} + args := []string{"--port", "0", "--nousb", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit() } // Retrieve the DAO config flag from the database diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index ee3991acd1..0651c32cad 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -84,7 +84,7 @@ func TestCustomGenesis(t *testing.T) { runGeth(t, "--nousb", "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, "--nousb", + geth := runGeth(t, "--nousb", "--networkid", "1337", "--syncmode=full", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index e4fc2d4d01..d2f63ac7bd 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -159,7 +159,7 @@ func initGeth(t *testing.T) string { func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) t.Logf("Importing keys to geth") - runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit() + runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") return server From a1ddd9e1d313553b0f584d5d3aeb2a4026a1b0c4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 30 Nov 2020 15:23:48 +0100 Subject: [PATCH 166/948] cmd/devp2p/internal/ethtest: add transaction tests (#21857) --- cmd/devp2p/internal/ethtest/chain_test.go | 2 +- cmd/devp2p/internal/ethtest/suite.go | 38 +++- .../internal/ethtest/testdata/chain.rlp | Bin 0 -> 1585630 bytes .../ethtest/testdata/fullchain.rlp.gz | Bin 253004 -> 0 bytes .../internal/ethtest/testdata/genesis.json | 2 +- .../internal/ethtest/testdata/halfchain.rlp | Bin 0 -> 527009 bytes .../ethtest/testdata/halfchain.rlp.gz | Bin 126219 -> 0 bytes cmd/devp2p/internal/ethtest/transaction.go | 180 ++++++++++++++++++ cmd/devp2p/internal/ethtest/types.go | 45 +++-- 9 files changed, 243 insertions(+), 24 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/testdata/chain.rlp delete mode 100644 cmd/devp2p/internal/ethtest/testdata/fullchain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/testdata/halfchain.rlp delete mode 100644 cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz create mode 100644 cmd/devp2p/internal/ethtest/transaction.go diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 604b908687..ac3907ce8d 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -73,7 +73,7 @@ func TestEthProtocolNegotiation(t *testing.T) { // TestChain_GetHeaders tests whether the test suite can correctly // respond to a GetBlockHeaders request from a node. func TestChain_GetHeaders(t *testing.T) { - chainFile, err := filepath.Abs("./testdata/fullchain.rlp.gz") + chainFile, err := filepath.Abs("./testdata/chain.rlp") if err != nil { t.Fatal(err) } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 0348751f88..5d0cdda720 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -22,6 +22,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" @@ -37,6 +38,8 @@ var pretty = spew.ConfigState{ SortKeys: true, } +var timeout = 20 * time.Second + // Suite represents a structure used to test the eth // protocol of a node(s). type Suite struct { @@ -70,6 +73,8 @@ func (s *Suite) AllTests() []utesting.Test { {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestTransactions", Fn: s.TestTransaction}, + {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, } } @@ -115,7 +120,6 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) { default: t.Fatalf("expected status, got: %#v ", msg) } - timeout := 20 * time.Second // wait for disconnect switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *Disconnect: @@ -151,7 +155,6 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - timeout := 20 * time.Second switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockHeaders: headers := msg @@ -181,7 +184,6 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } - timeout := 20 * time.Second switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockBodies: t.Logf("received %d block bodies", len(*msg)) @@ -257,7 +259,7 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) { }, } for i, handshake := range handshakes { - fmt.Printf("Testing malicious handshake %v\n", i) + t.Logf("Testing malicious handshake %v\n", i) // Init the handshake if err := conn.Write(handshake); err != nil { t.Fatalf("could not write to connection: %v", err) @@ -307,13 +309,12 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { } for i, blockAnnouncement := range blocks[0:3] { - fmt.Printf("Testing malicious announcement: %v\n", i) + t.Logf("Testing malicious announcement: %v\n", i) sendConn := s.setupConnection(t) if err := sendConn.Write(blockAnnouncement); err != nil { t.Fatalf("could not write to connection: %v", err) } // Invalid announcement, check that peer disconnected - timeout := 20 * time.Second switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { case *Disconnect: case *Error: @@ -398,3 +399,28 @@ func (s *Suite) dial() (*Conn, error) { return &conn, nil } + +func (s *Suite) TestTransaction(t *utesting.T) { + tests := []*types.Transaction{ + getNextTxFromChain(t, s), + unknownTx(t, s), + } + for i, tx := range tests { + t.Logf("Testing tx propagation: %v\n", i) + sendSuccessfulTx(t, s, tx) + } +} + +func (s *Suite) TestMaliciousTx(t *utesting.T) { + tests := []*types.Transaction{ + getOldTxFromChain(t, s), + invalidNonceTx(t, s), + hugeAmount(t, s), + hugeGasPrice(t, s), + hugeData(t, s), + } + for i, tx := range tests { + t.Logf("Testing malicious tx propagation: %v\n", i) + sendFailingTx(t, s, tx) + } +} diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp new file mode 100644 index 0000000000000000000000000000000000000000..5ebc2f3bb788825e2c5fc48ecfb85a697f016506 GIT binary patch literal 1585630 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%0DTvBvUquo^vS7 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

    NeOABySp1{ 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%0Ai}13c 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

    nbUZX{hpjpZPUAB$I%G; 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 z z*ZIV%s(j9n)lu7%tgL_T-g7&73~GX*2EYYek4v=>0gVVi0H_&@r1{uEDk0Z#K^VXB zBK2vQZRK3P9NMmyS!gO~q6p~CqKl}7vHj|%2GmIC*z?((f8rntx2)JWzbsT>pAg(# zI7@ZlFx5=OZijIckR0wsXKa!^ZwP9Mh!6^-{IDtF=r#GWL82PtQ1(!Z3TlF(2EYYe z3#fw#XnG_70DaS>d$c!S`sTb=f}H64GzJC2?90J*Ox>m`k5{jhN3DQZe1RXnU?To& zhC<-w+H%Wi7;f_mW$T>1Sab^mm)zMUN-Jv;)XsJZVOW>+O}O7#*L?c%LMKam(e?|VT4b%if4S)-{7Elin&>VyW06qOxu*w~#8NYcseyVmvjS>LymwoJaM0oGjW&P0t;Rt>>U5yE6OF>y}5ErMM^PkLL{ z!zhXPa$u|h;WI6qbC(a1=VqPp(Cn$m9Um-}l2%r!N8^vgu?<&pLQOE#0Jwl_0SyoV zEghQx5K;844dt~%a`LTNPZ&QM63zKlJz# zB3TCM(U<$IZH(ZHIwSlSwyQJ2Mu>nm^xpuG_RqVVzlPNLRr&{Qc0w*+beZ;sGi@(Pd!EtOls{rRVI?l6yTuQ$a?w? zMgexN<>Bu%wofl}b$6yD17>0e6S~WN(W3$P^7f*uOPiq{;!p$NHQ>4iG(iM>lBWb; zvB19KDzC-GQB9d#(k$lW?+!?gOGipwj>7#Z9W*|~2*lb4S}pJu%H0S6#IC)HJpi=R zKi%uh>G{d9X1w&O_}5AsiQC}wEWA=!KMN$gW7<#JK7Q~O^~pYogB|L#UQOPM{;wbV z3ANSw*KmG9O)%5|xPWT`%@6_YuZ_SfI&+AQ=98M~NtCs$eZ~GrFg8_c?T+}3X-L9+ zdz20J0!U8JxK6tz^#&~9s$Jy=9!2hz)waIqTp1?&*o(oo=$g^1zhgTJ}bJebr_-d9CTDwYbfi!%8v%}l-)Xu~f)IULuj&>x40!X|G%MZ9JzV-{sXBUeyz602-}D&@?h?yJsTpFupd95Lfs7<1o# zZ{9Z4x2(`^2FvbAmqccB{7@Kc(HqvOH2zlQsl)zhJ)Y-#f4V4}?{XNM#C}bV4MM8( zo6o&pNow)NF(4%z^k!z_7R}iM8*eoE_NZgBxz803%ow`zXE1WfVhhHUwm_O+!;Z(< zrk(w5O{eCGlzkbc5Iz|HAgJHpMLN^Idt3sU2QAq?1|+@rxV!tHP~us&Chrkv`igpU zhS&Xza7E+6hB+A3FQOQ^278#*j)=TL?2F^vT^!B7L>0y!@8)DGN($ytT zsl*1=cy}K4<{SHDp|qj`sQ`n>m8-0dHv%4Kr}Tcp%G_Wqv|vZyFl4YKWVPe_Ublho zr?aT`dp-b00shBveFv`-ia2?6f&@1I+^=`TBWF@(9Zi-u)&4ya2LD|M^l=S(#MQ|6 z2_m5DZ3+NXojwsx~ts4Q> z!}1^RQV^Jr1mU|z@5vcfr4u+Myls5z+Vi03mzi82i~^1+G%}54rr&O5vIlD~FzdNd zW#xP^qOtv)YscbT&JDj-4|;x~M_dVLhY0ANas_~_ls=`CCbU)DF^ip*w~o-H`5^W4 z-L7iViT2qOlxgADK+fk;v;*f@9oFw*9Ez1aFbXiTsY%^$)2w?Ont(27^Jr{MQhUW( zC~v*IkCD)~HUZwQ68aGiJ>secbU*}rH4XrP1m0*HSY$mgt5|Bqa;$#A%8l4j5MPuU zJIz68sCpvf3)FFxF}qc)eR3lpmRDO_Ld@!=KuZW(lD*T;&WtWG4KANM#jTmP?XAxx zFba4^N-m>lZ0He0y)X7UWi5S#3G4JUYieQO>~K|TwQ3mZAr3VFUIVUcz!!*szO0V` zQ1Glgqdg|+ElXwXRa!!aXE;cB`I^SfJZnm=nZy{Ze87j%4o(V!8b5CYh}cO)-10)* zta{t;y%LzV>BwG$kWeWJ>^ievpr~1ab6Qn@yd z&>ABXs1q|8{QNvj_eMY@4M~^oOA?2|I#W~2tnwenZKCuNO#T%-+LDFNBL@~R)_|mG z^tI@W`Eu7l!h%xJ)H3qyT~z~D{2%B%jRbr`k@!#(3^f2Q;95WzM8F`6CwTe3AHJU2 zTPpf!^miV6<+hm%xf47}MbVj`dTd$rE<;-ec!yW(xUoun7cAhaT_r!(-Air9X2NWN zXillLAoFz}M&_YX2Ngn^CP5elL<;V8tCuD}u0rsY++6P34Z|%tI4gdD!@;cJ zUBQ$e0UDqXA$ce+VjZR*K(dm%_+UgqW^kHMpKmUep_4qayp3(P@^byl^E*30o<%Eh#!<@0SM4fE z{+R{^zx$1k19^g9sN5@;QCmd6eF^#w*_QBs+Es>wJQA6Vgtsp~<>RR>D2M|WkC_Se z*t6Q61YimjDP{aQ0m-aAH04SUVg;Ic-f#JwO?Zd9DyDK=J-hiMO%gu|vN&Y=@x!;s zZ0q~m+=MZ#wry(7`ZS)A9~57C$IoZ7d9ziu?ZAjlrq}>~*H$WqFAfl!BvbWHRjHvQ z48UkUT7)|!fLdG%wVFT;fR~%=a?=ATH^XHW;Ex@|Tyn)F3fQW5E!ejQZutcj)imiR zxL7zJz7uUv`Y8=$s&`tQ9HKgXq^g;xT z{PqSGu1S?GHz$hRsizkQmo@bjcy-V+H;_{ICmcSW0$)#vFvRRF&fwqeeus+HC>hZA?C~;f5==}!o`i+2IlU25^DmA_^ zJ%SGto-`l1qoHbF}Hiu;{((LLk)lnxE9b45irpj1po<;o2<`_H5@K%PqJq+f6Nj`iqT0b)cjSbI8YUk z`27OtZZ+<4B&UjVBY^w4j8KhwO$G`3N@TYt>ZJryey0{FkWq=3*Xd^hx2r9}_2c%Ngg7t{np4S)-{7BB!2FoiM*01d^5^+(mN z=zS)+qdqA`W1iB}tP$a_ynUf$c4?BrCIK{hf*b4h7KnEvK+&(!B9lDT5Osn}JC#A4 zdO+-5I%;8)>);ea`%j_+Ul?nEz)vwG4BW_#C}qvVOq#BAkdy`i4@Mjimry39&0bo7SSbKS=W^j5eUEO|UpdsWL)C5BffD5=5Fa!}W6IluXrTu7L*}w=+7p`8A zZA5hrD#oH(SzG#I%M+i2d2xvO3uy9v5t&Za<6pO99D01sH8M~|85r-wu|ofi=Lr&b z$qEi>dSjFwk>Z!*1Y-?Y+g1)cK((Jw>>1CSmBjZJ@gDMOtkzQ?j#;Ye!=SH&nqa5_ zZ~@l>h9LrGhvmVm=Be&`o@NbQMpdotprrirB+_|W)2dqW&9&}Ja`czYY~XF-WLd?> z1^--(f7PxMsX;Q0Fwd%CI#dMpg~sz!+uT+PI)jV~9Ie-nEp&sXU=$$K(ce^x@cixN zCj*-wt#r*cU2l9N=Q(Yx&VR1^^@=ltdN}X*-a^lao|RP5kku{C8Feez)@?}ABPwM~ zJ$ZnX1aDV~0Cl^X4;c9?;Hq8acXAW}sMxZI@Ez#`u^luP8&7p1!M z$t3eT%s|Si-z1icJ)u|aDoLTYs|*8Ws?zj)7GTW7!$bIpV4SP}#gsekJhJKGv$2Oqqn^NO@r*njA&}iiDIg ze{lIM=EJ+Zrm$5xeqj0=ob~qMc|Q$^%@@EOftp~b0dN7=0>&T$=0U^YHdTT9JFEHO z2d%QVG!mq~JMlaT*}Bzj`6=R^)p6_>s(GOBkPnBefX25Q0qo`P>hBlbZx3N7F!{{8 z@o`&waUOGJ6if!943nOqi`b4n&%->+ zgPLHd0dN7=0>&W%ek`v5Krs>4O8!pHrZayyB14drOelsrzGG;`V|aUvP(O_Gp8#rW zAj&x<_%GcEu(HP?bo#dE{Qgngv(8=7`{>zR#*CP^Kv)be3Dni^@nIA&z1+K5)TOV6 z{=M`gw{)wv?+hZf(&L6lb6dB=acg#%pe7h<09?SefC-3zMK%fmD1booK-(qi4t~1p zD`o6`m0`n)fhV(sE8B=%#HG&|Jb=E+d8|7MJ%h;Kw{iY!@^+|%q zT!n!1cSmeb2TfFD3XB52sXcCrm7=`w>x=X9bj~;69arK{joAspPFd6xOm@*Ys0oG| z02gpAU=ku=$a$y2EwtXG3)x%9xIGZ28I_2FmZxXFAQFWPn&qo{!im z8NS{Ks7iE8co{J!o@mA)M%EgAC+B4ER?ZUH*jL>bJQ39QwqO*HD%ji*FizN>cIsj~ zQoLc}#I9N9FshF;d9;(A%bkY^HNj8=-~z4%OhE)JSC0cg11Oi6MxV_&*vEx(>^bo% zks1vtGYUR^lsA{#bg=zK2}HKH!!`54rM(e=+Z*}41etT&b#Kf?s=kHTEF3xfnPg4k zS8=yaslK&4FbY_jr1p^>e)zmfmanqY8ed4XAzI~ftfMg+H01huNAM5S1Vasg3%C|A z4H2+%Y6@O5>28p1o|LH3bOFFObOKZGbO6vxL+jt2cO;3G=^L{SbVl*~^_-`$^+v$z zS1BfI6xn3ivISO5^}%EN8tt4 z_uT2-20vqCj&3jh*jwM%76uq^j*1N3pEN7ps}FBX7TLSaApSuKXkR7uHs=@1AXvavyGjwo zL1_+ZfWe;G0snJsgNMFoc3ek-C|JBGeHn&JI^(2&}#tbe1AGk9+_7lJTt-` z&j>9t+gW~pwJu!oR**ZdLKDI2!|sy(a(VvFPNms)-cO0e8)TQXubYWh_2@w*!=4A} zo~h|mi~-M12G!M4#=}U-4o*>M_3Ab7@HZ9z3ZO**Ts3b*fNTKBlYhQS{`)uo1@yJClQsjg^JaKq0I1@{kt%l6cQqR88{$9?D`I^mU7E)@<(L@ zmdD~}N*jGy^jl5V@C)<7jQ->8%H;PT0C-Q}W`&zX%>dBHH!3j^IL?7jna1{o=Z~aO zJN8e-W?3?0EwkPk2ctCtdE<{?*>CyU+>A@JZ1z7st9~O&P#iilo0b+#-A$!AIZ`{M zP`Ag3+VNq5QGnP}rX4dJ{MgiREM{uTb0?{A)OW6HWb3JYxl2Rg(p6B09jF2Dap`(o znu7@Vr85rzCE5}9hxE66y-kJHQ=R18BudMC=s)M*-R>q#EzEsO6i8l5T*Z4>Ab2An zTD>-Rhfa&|fapWmq~Dzg%8j&yA8&0w+N7Ov>wR2!52JwJxlAi7JU=}`DN;7__dn_g zy~Fe@mhwXbj)i|4%<{<*MGH3xHgC^3^WR!mLDM!6`Bxpbf_tC0t@vwRMOqDQC zX&ieli~>YROM&;M$;R`)sL#zL)gewJ@a#L{?pp7bXgGaJJ==$xV5k9b0oMY4Km=?r z`vO2rzqiWpyy(gO(6L{mrMDz^Rjr>c>;3*?Q#aIWut?AaL`m3=C?LQ23ru4@hJsyrr__i)E3NTOK(8r;RMO&!ESH3_$+?V!=Lf9av zq4iv$2x)SCK>;j?Z0VwsdT_kldy- zEQ|+0Uh6;E(ZH=O{0&8fvPiX_^_NwRyV#i%h6Cs+F~QM!n&;Y z9@_jL^;A|jiJ3%l6)Bw6!0#Dc%2v|!EQ*ytMh_}$s0oG|02gpAU=bo<&-o1iG|e%r zS!wx!r(HMT8;W(roYLOKvI(V!9cGLiTEvh?KG1?_E+b5@m+D5q<4VQ-+~-JVBezAF zEbinhy>l2d)8TwBqK&oLOu6zd4aOQk_p6Q&_h6$y{`Hq#w(0a}9%AF3>{lsVpgIqF znO2rys0oG|02gpAUZ8?0B3#8TPKfLh0p+rdudNFYA02pCKze}T)?$}Wr%rA>lF*ksO>>T7dWc(I_Hgcve+*G6~l>RkBakJthSAJP1)0- zCKze}T)?$}6^MX8GGqXdpav+yvnHmyn<1_S4W^`MF^gxlnlZ1;{|mu@TnILY$)>thY!ijJuxa~Mb{UeUQ9}% z%2kkU3XTPp2JSW2{|vpS{M}j7g5~6C?vcxHP*LyZ-jvO|FZ9^`Z~@l>e*P5@h^V^} zgmxHp5BzZeR|WCF`Ob3UvKas6{GXTd{ToZ3%*e??nVwl|6J*Ljd%h-f3zIedt9F&- z|KHkGrZhQ|+C+VKbnc6;cSUIOv`TsP+d0agO1PxN+ggRc;lN9*F5GtPJ+^yITsnWE z_1*NO%1e#WwZFyp zCO37V?RQN+x*q0IHi0H9h9{A+WHJ|>#(K$!^&hr+12vk7o*{qz{_#e@uZOSk zc0xzj#U#W^A`5VFa!CjuVs*0PbiJAY&6dTN!zkbn$ewGj1W&p9MUn@f{*Ha2#*ZV> zgc)^IrLAsaWiDH&35FT~7jP|L9U|b=oCv(Q_;Od#JNG`$=9KkHGDe2kubCGZaVmqp z*l*}cQ5@MBf$Ageu^&et0dEAHv@TF!`gW_C4!ga{$ooK1gijk>0r2;^q zyX%5v9@L>_G~P_Tn;iVZCd?tdGFJI}^@|U9t9eX;3j7vGK8@q9Hv-bnF=pbGI)e7& zafUeU?1?Koa0dN7=0yZH6&iADOAkFqaO9|CS>WhZ53B# z06-_B#@|LYGySRu5#D z(*aP1vPI9CX6%!dcHSH23@a2DAxb@Vn&JenA9WieQ1~L5bfzuSj|m zT1cO(yI`EEV^sNKHEux%OSbzZPR@Kz4G>>(81v%Y=ja;&Y2jm8z!m>o#+`s!vM9%e zgn)NGp}I>Qy4nNHU0%{zFxCLXfIe3|3SY9uqBMlm!1_{co*^6TUnvB9Nk@POB3e37 z6AU!~F5p_g*0lh1gn%t@o3cNZrax)Md#ra-X_ix))`Le<&zd?KPEb4;f{9Pxz6J6= zi*V@?3(taW_ zZ-DXGv^w#9sV{-NG9Eeml!O4NaOD>KcOeM>h3)F9m~Dsv#GV}h$j3Dj0l5-I*~ZFp z{<3tp@3LJv^mX!~DeLU^O#kGt0MJjmN}=oCkI)+dN*e)KBM%8Goq9OrtJjx}Wk~>2 zm_JFGd9yGbxwdO&U=+}5o)_Pt9^y~~ z;5Fd72JAotAQKY+K!d~nj6v_Qh7J|~EDbF-;dvALtWL~-o;3FUfR{0m5en4M`tWtO zrJ5Wp;Hq8au@fjz#>Vi?<$;&sSU-->#-4k676$UFuEl(kV)33Mi~>rn15;}$98g6+ zsdW+gsk0>qtF-y9GTe%CKou9GF+v1w38%zli}(^xwvpHcOFv;o>)TYwth1D_x9w4> zk$=H2@m zF+#4Ly52|VH&XOC!-k%$z+G3y<;KiUd>|t(W`Qlqf(J|NWIYqFAFjNee1SkO+HmsY zajrn91$P`|`f+o^|9lQlW`1ngWc-yob1VCc399VY`abAt1%d{eXj)C+jAZF zmcHdl3Vc1Z>6^1AH`2+ii1QugAgdro1Zp*b8UQah*X3ppQf^SQ3jrXn_x3|IoCOc0 z8Dt~A%wPu7&z7vu+9KA}cu3*#jL;*WHWq*wD6+zdERM@p@}38wdc3hF~TDWgiT z-6+92u^22ziFsMk?Ud{Xqkv_6oP2N1(yqATnhl**Vfm&wE)_C~mnG&$RtL)NfhABA z3^f2Q;99^wL;%_*BY3SyJFgwGlHMWO&yrEww~p=vhqL6rw?p<4&9F!2yNGoH%9&~X z7K$Bmxe=f&&Yd5be=E1SnCh{*^qt%5MfbFd_eJ<+#t?h_<%MCLeqdII0b47>AwPHv-yc-=>dzl;-knROx&3;)&*v?CTd^ z!pxaSPHdA0uRbusSObg&(XjSydA}(8Yy6HQ`EhE2KlZ{cOaM7JA9=%J`xg<^1Vasg z3%C|=01<%c;|u_mlZuRti{=Ybd^Jp8#$*;VKD)~FZDv3^f2Q;99^RhybiFPXM4E5#&Lo+D6>DZT3~6KE8w< zHk&l^0PTinnPybxw${BsDQgB<>+$oQ8v%F@&njK0KmKf{SFzDD611pQ?M$OG%@^@P zm|7y+2EckINb7@KBtC6!^(?-R!%YkzO*dfC`p~x8nCS=TXekA7LP@R1~gQy5Jct&Mq8$q@4;9Dh*s2(i5asr+Zv;^1T3=!gVA4> z``H+^1>H40EXEv~fqIBT4S?5x>l$zb5rC^B3jpEppf@dJif!QwihWpUq)ntrI^25j zgR58dZD`Ms{~(q*Oeu`NxM1Pn?l5tW zChmt(z`YuZ%t7HiD&^?Y@_6%8`%(Y0AFTT9X3wQW)CHf9^FU27)Bw1EYXQd)0eG42 z08q{|9tk0HS{^6iZ+ed<+K`N$KOqoU)e(@fl_EYluQdfC#xC-&mR$U6&9rtLvh_;L zg`InEau_468I;mk+RubrwF~LvD_?;|; z{HwCtovan;^O{qQJj?oC-OCKJTM`m~fdpUej;&lgzink9CCK zV~fEkV6(_eZBmvxwN!na3;C^qNh!MM3ZFZ-m5id(#r?550O+FLt8O2YvNj{!;JvMf zRJtnur&g5a%~d}{wELp()BEA=DiNS=SLXqze+B$)SBXx@#|{APywAwOmKwElr!wv)V;zYO$yVl#`@Ix580SyJ}Z?3wpcCa8MIUYONc#2}_l@|CnTN zl*{4@uOzV5B)j9A`s;w5&nKXl^1BsiKnodxjp#V0H%neAVd5G*9K$}W-;ym`?}y)9 z0BJ6#$mZ_j-Jq(O@+`eE!ZdhlLvO&|z>{`Ud)%iy`v^vFJ9}o~slN(K3J5Ixo>=i! zC1ltV*KpB$ah@G*rC1ZYK`kgy1K<_sy5gKcDh`oP6#%qd5UVB^)%DfEl5G9g@5^Q- z^^tH)O1Hdu>W;7m33LP?Mtcb0ypvn==B%SI^w;JQ2E(ilCd0QcR^QcLRrN*OUV5>) ziX32gnFgLJyz^CQ^?Rq`tD@j)%a312DMyGN-+mMBO1zW#=v;C%UgpjJ-GKpm=y(zi z6xf~5Fl=#+n4qZ9YQzT;50;?myZJD?cFV9pO)%5|xPWT`=MVwJoq7OJvUrZd9R5>& zPU2aQPmC$Lz4F0@F~%}(pH)iP_RMxTfv(E9XnZe9OKt>I4S1jus~_~?bU2>(%@<*B zAUWnDgS(Y2(wX-^;R}2KqkzY$>1vV=-~5NnvvnNV2XkzZ^FhRA1P(-$OLn8_g1Ar< z3^f2Q;99^1L;xuvH2~ymH##Jt!nuIgM0rA`B`1Cm-gRhYnYVuON8k&cGP@j5(LRNY zbZ&d#MnE-#KL1ddfWTVbW+oG4yQp1K z{>@tQL4&Gu+p`CmX%fj|91nH9-$6|<)Bw1EYXO%K0c83J01)FE!tjePmJ;F^@{U5} zE#BiNb&Ka#99=wfXljxuLK8q&YB!lUg+1OI0UBS6K6#Vk-`an4Yoms^=vaxe-za1k z*Y?FoXI}3Uj}90G>_=O+2-qbRC#HQ)lU4e4+NaM@7uG@GH8tu?#Li=^1vSA?1Ka115K*pUv`r^AMtM^tRp@g|xnNHpp-3mSp zja&lK2^azvy&DKb>?eYY+8F{_1gNm)8=)T!dzL+AMy zv^fbIbTAUY@J{9A?Y7V|cj&pE(C0NKvJ(Furw@aX&b9_shANe>p%xgb08YRa0mu*n zD8j(s1{qPm`c0kL7P%$Ap?N%fGnPo#RCdE!B^-!%PCL06{1|wDh11Hd#USjO0Pi7+ zd`e=#T{i8Ioj#Ig7QW7GF?F?6M!tM5{APESIT#7p-+G`oDn}LIX6jo=XWl}BKFIzh z9?#3dTUeIrNL1(n)B-~lzzMh_00lw-km7n`G@1MT&2)(Bo!;?{- zl$MIGM6>XI^fge^@KK>()cqG=0xsh!3;RD=-J#pAC7}*mBv^kx?E1sL-$I{WRTO`Z zLJM#JYiMz_&nv7O>Y#C{&Zc9v-HHbz2^a;W&6F9sS`puBGmk?+Lwo&!M-5%fRd?Sx zlc1tAS{LFkYb&IbhORUyPkH!12Ho2dTnuK`~KO?Hv0@*Zt!WJ1y1)!hVI_}rD7#6`NXs@u+)k%?&&eewC;rBbnxS|WC0ti znJ_U_+tA8c!dAqzehIi=d z?bmaG(Um;RYnL^wxZ;H3IM6s3SvU_e-XkH1EPVfjf~H55u8jrNbf600u5`sD+8-tX z;pjBh;9th>_HXlk54Z?@_pOaSXB^WonP6LsZdxuC0hd)X=`=kasM4Aj;Tc*pblop) z4$7G7DgZxI*5G=XoKUL>*C#uwt=VZJjb7xCJrLA|k$~@(<^qltd)n_$Wdd^5+1KW3 zdqDhmI81Df`_9rabLXHI7^(nHz!d@L5CUk+z`u?MNaO^znYYYmUKnK=;vC_NA-jC8 za@)`R{Nfq+j{4p(kVQl_l~PE2>Y9MmIC=vkT(it^d`!TnB8vI`;&+}NjE1aFSqp=l zr7d8Mt1KKZ?!^gX4tclPnSHLK6BkKRoML~AD4hN?*Nfa}2O+2hhAMy)a76$HgaEoD z@C+G%dQ|3WIqjNKt+w+{<8aLGKt}+7G4lyW@jmIQpkD>hl%X6e8^_f5ngH*|N&B?f z1hRNY;GN&^3Ubi9JeL`rkkXB{i zPfeH48F);J3SXuTKAHHxZkJ~!H^KqncLMhU407OEa&i=DaxHm@aWMzOg1!3 zFeZR!%C;{QWiWJeKhZn$)~{9IzUQRCS)0VNZG zVTaH6p0OQ!c|1?M>o3f)&WZ*)T08 zoEBz|w2355eTy5r73DfN2suTb}Yo{??$PPKE1k+@e}m)CmROh(eF> zXZyCi&=xB$nHC`J(Qt!adF`!h0%$U-{YV|VU5Xvp7nFMn$9C#*wf3KyQhxl^#Fgabe36&}|E&sa-2d0@vKPP(2Y}xR z+zYTUgICP(sAX!uKR1e=4esdTC(Jg}AF5qCG*1;VII>adDj&!M@@LFw(T`43UlUMU z$(?A6|M{2V<5V_rc5`kE4_+l*D!=(PInjCl#h5-A3FyIA_#nmfUGOYQ=(Wg0qAxk& zNjrsgI;%aeXEiUr+ATvp#Gwk{9&qIWI1mC@-NCEobW;;|0;@7Rw&^Qxx2G_c`l!&d z(A@Yg)$yr4Mn)(W9ccCNEzdZw&fnWB9%GNYpJfu+;6<>j{_Jm39Xa$i)SsT-5vV8g zji}ngfsp{+8V1Ue(+(_$jIwd;VK?sr%tYE!EHZn}p+ z<}j=WEVm8TFXJjH{#jgQ58(cK*1D^C_EQa{RMEsC(F?~}!&OwOy0-8K#>Q3!W3x2h z7Se59^6p_ClbY#FFjN--tmQkHrMo6?KHDh3mqm6_keredCcXuFffVX z9QX<`Z1=MliWk-^@d~%ok2oW_`lwhYJCsHURJ=D><8M6{#Kw|1)%z>1vJy=LQ-EM5 zy5C_gebDa3(hrtzFJ=ecEEi^4A~iAx?tyGn^ncUIQv%vc4g!`n!t@P=Nk&n_f2fvB z;wLiX*BL;@L5)L=;~hpi%wI3Pkfk`qWP;CvW0PcHNzK8#Zdhodqhb}mvMCy@pe>TgH zCV{g@W;I`}##3(+e(w4%K=#Wmi=dX6pdZM06kFJBjz@oe?sIzas)D_<-58CSQsqs{ zClWlHij?XYn{$F1UUrqLyLK=VU{hbr*~Cqf`72n^R14qii?g40dZ+ad<%)xzSF#<7 z*iZ`$RRAa8ih!FC0ysh7kfPOFl|$S-Dmoo>w&V(SvD{%Bz@Pi3fizZ3Gwzuzjy5ZOu>5ej_E@H9(68qZYrxLmpw7LbxTFns0V08ft-| z3g84>5kLqbfJYP@R%rcUeBx7mRnEPPc&}Zvvz(YJ1CPu$f9rHh^cVIR;P6OHiM#;B zAbhiH0uqC2S?&Q$QkF*vxa`jPB`qq8DG*=GBG7#A4`{>wtqvmr&KJKp1BzN&fw~S3 z!;|*v)6@sHFP+#re904H20tqjLM<><0i1v<0*D|4@J4_GOA8B_eQ|S-?YJwSoMd}u z0dLF@5#GH!qV&#qsI__@ynP=FC*w|karUEY0v^6KS}W_o=vE9e_hj!g!wxCl&n&Fp zmT#|~Ust11ps(GDuSog_OS6zc4tX|Kxori7qRN;!a(WuZmg@)B-~lzzMh_fD}T2 zpc8oWg6N3l1RtsGsypT$54~ze9z6yK#?e-k{|@5ae4XW;Gz~Pzrr$9Vd1ri0z{w}; z(;N8cFFRemA9&L=d;;N&h!Biy&%B%jF_yaxWWz{6q!EJV`faqGG*6JYgV_@xUZc&O zW5(3;oA)yLo)F$PhFV~#0yqIz1du@p5PA>Zv1OtB;qUJ`T9)UfD`Jl-Ftp|Hv26MDS}D@0Aa4}y&3c&CkwZU z8FJFDwb4OX!b5CRj?S(A(lA6TF$+{#m22jbkzc!vtE7a)Ri;ZcC*S#%{z30PqKwx> z(=R3N;Pb!YDx*LSM5uYP<%6BTH3ZXMF|8Mc+2U1-TLUxsulA}=dm36nGCI8$EJ_Zf zD4Sdp4^fV(jM1k@!nM`=c4R;(%_ePzkl{zYj(WWvv}X$_uSkC@-=x}7YeAFk!8=$X z%}KNFbokK@BW!mvI%Md~U4BoXoNe}G_fE)x2p4d|>Dd)ug+4Xa=goxbCQt=%yScI( za){lCYJ>NQ+YG$<4nxv> zI7-U1f+7VW}aYy?)6KyH4 zii|(fJmEvoJ1v~dYQu+NPzwxI04Lyz015~JVp(bcP;6Q)8T!iRcFqvai+wggHll>g zN4AGb)GZUreB*X>%RpIWv;#}BuSwSgELzha@swB4tI!jRA?Q-u1^iG`QUzuDtsV@f z5tMTK!AJmN`z={-BxL+dm5@6U3N0>}K&Kx5sGA9lb!mO}I$`jiAfWap|3Glr;ZQ;d z5Z?sv7i*AuD}!5)@7o7QjYfMp8XsAZR!|Di7oX^Jj?%91M_ZuC+Ir)&7NWl=;YrY- z7?!0_Bv1f28-UFd#Bm?pxhLMEM=`(66RbLC!h?|jw4$dsBYe{*+f}@$_m*WV8r+9+ zHpn#}`ZFu9?!9}y0QC@uDu8>yl?PBk2#^##27uU{6`f0?-B^7wO^3+8Bxb5v%Ob}P&a?O@Wz~eNgD<9ftf3YdssK*F6#>)`0`5eD z_l>V~;;@};&D~l^tz!RRFD5@G!Y6djet;g2OS9Jioa_ZMQaRiQcxXMnCIFKpwv?24eoX5r zQY~uRMnv7C;!q0=RRAa8iU1l20e44e0U*~j{U4a3D>#LcH4R+#Pfghh&)&>`YLPzR z>Bd;c_E-esH`YiX=)3&A9Ap2*c{+LfmYTg?t&qh5a#vB&G6hBt zaK5E`08p>lMq)WeYwonkk|mWBHJ^wXeJE`88n+_x1JnXT6~GC&B7hb`fHeIac+a$B zEIdM`=a2fo^L{h-%L&y()BKsb@Z^K5FcbFR(%vVa9-Y+z!32BtwFh*1Y0+iOxBZ&f zZCL?k%%u#c-Yqh=aa1Nf;kjAxBdP>O0{k?FnKu%8e$Um$jrQN9#qKmlQ+}L0g)%l{ zS{!0)0}m@i_!rvcnIIj602#NB;N5blgdc@2pSDCe zKr;rCNGxy+->$2@CcsmCWyFMah8(%Y?)higaL#n#4M;tC%}D#73v`lRRH&ZD-WQD5Fq=p9=t4*?xiPQ zd%KsoLaWFHra4lfoqg6v)S#uOBzjS{s$X<~zLj4TLUE1Izyw^zRi@;zx-Ay5g$Xyz zm3r~U64F@Ni#^obc4R%yG~m%$JcW^fN5GYzckM-s`;v^N7#R)hN5{L;F+*JtRi9Gk z95ES-f+~zgYQkvoMk`Cq1=*X(htm7Io%1z~36;=zX%+lKLGZXr=q7RL0Stc#xQwfm z#~ucNET!JKRLC2T(xH9cYE zjRF-%j08B&>99XGbio(JVllEnF%|02idqWL>c-pK zxHnm4Q4QZN1-hR=S6n&{6NCW8LMs5MWRj(s%3HPYN8Uc;d$P&?MQYtQZkFMiG%r() zkvefhfYNaieSVIwORfpMzKunLGvoA^r_r= z_la2`=1qyG*c43!BcJ2Q&$YE{ird%$6ulbv0)a--iF@=->nzs+LCyWHlpW={DHJV)*%nXFN)(45*G}Avk8k#_MUkl2g z87^FbT41OGI007#us{es+_EynUH9B*Zsb8 z0niBF*Zm<$9^EwoE4xPb5T@#iBX%All!XWvrYA-$M`zh(LW}d*3HK zrfJ*R@YgG0(bHxAYUeNzC=f;hBI-XIw+NWE^+@ES2)eSUv4%UVciaz6R$E+HY>|6O z4)qX+Du8>yl?U905TLfW3|<7v=&WRiGJ0#zObUYtiArg&I(S-nT^rp+VBf`S(KR0U zNJ{j*DoU)(wFi(&zroT}0@R2s$x`=7>rI@{OAePk`7%4?rA_G{OS=do0Wlk+xI8hI z6*ov4q+0SC5$+xNnJO2J>!UZnN=W0AdI`0_Pz7)Tt_Wa*5O9z0D*&`Jx$O_Mc+v4{ zV(c34tsHq?$LXo{M}?qwVI)dYXZ)0SjR{0>)feI4Z@f++vh2o zNOE|LFrwPsW^Y>S>stXM0k6?#R_KoS9%wedR4q9eY{jFHNUKvQbob6Gj>83Az&Ff+ z?(xtSm%RWxgaD0@X#nVhF^A1XPw8_l@+-t5dDw|AWt2Qb^B3Y#@mO9@K7k|^@XmuB!B}#faV|y0F=GR_bnwuWQfkxO0%-! zZsCI$shpMh1qHlE;^Hu;WXfY$ZZG!JGK>T$>b>2p?nGwDUeh*d{6cVcB$P>h@wF9^khOHU@8{$Q2uGtf zNo@72BX5$th0Iq*Psi-KZC{lIlZqdhA@qbVkUgY?y?LHld(lBrr&ZH z!=5V?<`PL(qa6ERdVxPNk5|!>QkVaU;26lOTYugEkI2VAPh-&jx%yAZp9jE+l2@UU zf3E(#<<6b|0zv1#NmHsQj67U~y zA@$eoO9D8<0e{{86IZECs{#O(1u_yyIFqnFd2`w|vG&VNVGs+&C9v{W4eestkEMf3 zAeqx(N?wAvbnQ;n8 z;K9$BMYczsdux1#2GiTVItJe=%#!Z#&xijjHkWosfasE7W6Hn)F)2eH8KNwIL zmTYRNnVHZ&evmF7Hk5%isEkoj-j*rkZXc)hEQhKnyXx-q0(OltgjU-770R8_JE%}k zJfI5x7bd~w`G2NeGRYMV0RP3{W!gHfW#A$4nZjxD`>KIIbPVn%p(K@5Dc;Ve7c?GU zIeIe3%W<3lybaQbRMNz$y6%_iqm0SwJt}`A-zLalGfAegFC%<5=IHM|=Fr`;8uA*} zU&aDuTQ<|4AKNbf_}u1YUT)-SVbtw5cWlLMNWg{>o!Skxz)%Hn0lkq_SOGR3YZou8dH24de_>1#YJs5&-~?O|zyl#bA9nz}o#)o?UimZnx6`$951O&Q>~h+kD7{^a`01FI zPYs;L2nXu6llh9iO0l~30Jg%6h^M)&!$j&-wNBYL?1qW@rL_fx-m@HMlv4dp=!cPj zn4{RPfeEB$^3u=L(3g84>5x@%}z`#5n04h47 z&U)Umi$SHGr%`kKKs(rQ{dvqS6XniU4CA?U@>@VeplafHQrzG3Bp0+@rCc4~z7cqU z9K^Ja*}b6MIA1L>LKBNSni0fdo&c1oUo?9hgpnHQvlgEg=2p0r;$YzZXw)R_zm zaLEOJgIZvy0yqIz1n@x!Ff4lj0Ewy{<1e~wecmBDTr>0{L_Czo@a9JS!7ypzpGUQH z;RsZB{#tb~&~xY71HKa~C+Uatewv-W<9LkVOM8r-u=>P2zzdCfINbXl53I|xZaO+z z1p3O5-{CEj+{8S}TING1j5zP$Uwayrhs45E2DQLY1#kkc2;hehV03N+0KE_+P3NKA zNx8Sn%ZT=rUTK*$$|>x2j!*f9`EWApgJhs!Yr4EZCZO<|fQ;jinNJHpD8>?Z>Rfsc zo0+1mi0NM7`}Wv}H!H_r!5Ues@uJi83Ey(tVg#G!Ga{kc_np*gB9Sd?Sm~(WJ>%-( z8*C!{3+?i|fB=L5lY4>S4K)bEoCF=-WE~QwJK!B<`?T_ZY!tA%&9}QmvCr#c6a~~E zc^T-^^kV&*0Jo?orOlPXeqYMOL|&;NQ%V{YoN%V)xx{jD2AZoOz`CVMsE*HPF|T3+ zib$LE(0*r%urS@8ulCUjlJ7<-XVOt4pdR8-1#l0z@&G{y0j96sg6FIh7GU>i^V|@5 zfQi;0;x%75AI;CG&t9z!RGsf^R8a>KY!1Yy3Euu|OBKY6BgA76@j$@RgC`F4{@-4d zI{J)Y?{UYh-@V}`a`BsI0NZi}8OHMf0PBIEU>1HCfFb(%X*bg1;IFJ!rA=hwFWHtf zWN+@@hgx8$0yqIz1PDP0F#CxH05v86dQNVRB07nh?B;RSx7FCx6Ah4BF2#K0+0M!d zmj){DIK--DeGUW@a2Z$m6T{#wk8jC+U3YouUkjF%q07l~PZY$xd6-->5Bl1MN%=E8R9$LWtN$URpkU|iFBok1HaEr|pMK5A(XN0Ka7BRd z9|FK}mBr!c7I*yspydG84GD$72!oksg?a)(h8JC`1-xdr6B?Ii?SJ8_8UX|2t6ixU z-5f9DDrx>%T%}hGaq+arq?gurF&gsiAmNvL9yzW2+Eq$3dL8Ym9XGVaB@%{cR=CeH zYa>ARwGUzxZZ%9Y+>Hto*G-uS`4(a5XVCn3)%$=C;p84+)2}=(vn_|o=si3$?TtMu zrj#ype;rGSZB;8*4}`@Fm8^DC2Ls)y^{=;l3$g;24HMcHT&cem#xD4I0T?>nyK{Kdoa&uQaGAxeTa5+%|azaDJ? zA-;F>PLFR3{VSC(eP*U!KAbdmPqu`$P|^~O7&tn-DfPKv^gSn^8!z4w%(djA=*rUT zEDW7Ax;Pt}9w!yyaz{(@(I-Q-HK+nO^;gu3Kz#3hA_f4&i&{j(7#`RucIw@U;5kEo z*71d{TH14`u z>-siF?K)WJ>iK`nuSop%D22w~RN=j8A}D|XRkQ3(+j?L8k0ipDby_s3N9op_`jg|hI!2!01 zPsM15ZRTu4vqmh9N={T39)L|-6H*{uv)LoUv10&cRBp_dAR zPb!L=DDho!BJ#-hVmbY)zZry@IRsZ|wZTXLa=Y;3*mlw%85z3NnCz7`r-)>)&t%Mp z)yS|V8fZe`t2Pk+g?2dxA^{=5cE}3=TBJ2ECGh)_<59d}fj^U47Z&Mz+}*;i$-&PS z?`BKOzpG?x*HJJ}P2B`91PH?ATH|R6M2URkNQ5 z*BA@@*RX%V0aEiar62e1y^1e2`KQyLNKSw zkzSTeupiWD4g*F38!yf zAbC=rg9*5ds}$Tj=T>~XF*X@5xVe%)NKjARz$8)WjG%AYOMRzc3j`wp;f9R^j4r=B zIn$HBpSjm&I+xX#Hpu1l;9qz?d>w>o2XY-=(pacY@Y7u=@0q0i;^i{N+qR;}A~vc@=Q?r4*`GTDxDvg13)HOLM%+;`*Lo~VU*P_vw2?^tN2B$@n+SL z#Rj_wBggMt}8B^Y$6$Qs@)QXi?)4L2kc2GHzX z7`^LgqOGy|R0oOgMAznI4fi7F^z+$e2`fpfi1(a$NF7qK|kOLH3u){{=u<}@tj znk1@=$fuj!lFLhNu{%)*y4Csu6Wa{brYxpxZPaYjSR$Y9yXE#Kz;hB9-Vc} z=@{xHDF zIdd<5Axa;=)59aENaavd>KfS#g-Z z+@Tg2ssK*F6#=pk0$g$S03Z#a4&&u>WicI_oCt-jT($w1r833H<)Go|0n^D=Yd#=b zQ4j@H{XNlZ0;D!=Hn!%N6YedSW4+`f`qnPW9g1eCQ1C)k_Vxfu{B0NsK%L^dS!yH#M0pHX;?L_=Ud^jIFZh z=66iTN+|*W%Qa_74`Gd{K}4`Fck7D%+*oJk3(Oz#Oi_LlIa`hwMcZMFM1grh1N9Jx zDu8>yl?TW}2ylOk1OR0yOY?W*vHv~|*Ni7CT0c!S)isXaz3@~I%N#^2Wy}WBn`Sjr z>n;7gTWCewXOBrcURzdb%I_v)q-(F`>#096-Zr6mz=vGJuAKy<2hafVBra%*?EN< z%^$e-G#oCSq2YPoUt7IcfbJGqhVK;W^4fP8~plSXhIj#Bvri?C*n2){yhuD8{0` z3VTZVh)6xyzG*T4CYz)lzEl|DUuc)-0ZI@8ybBHiAo4x}43Q%~XMB);gZ zKJzG%bW(j;@ym^e=oXN9xt~v>Wclv}Ihcdb#>hq@#8SJUQG$(qBWlBG~I;%0=w1>oUZa(xR zW%m8Ay}wNr-=ctO@=yivUf_xV6$k--_xQk}#r~ur(sY3=IgDl6oa`MNvGv=Qofr~5 zdj;T0xCn$eK+^PS&G#K6ieLgR<0?HiKg-mwM86QRat@*GLXk5`R+H9@Iy*1%KMuvM zd{zx30lU6s*+#~u^v{$sagQIaeVp!G3Ub=(!|{6dSmuT8?Jdyyps}AqWKEobx2qeC zv1Lb$Zk& zg%RC0j4q|7``WpOU>Z`2?)DofQJs83p-TJVGOm&idR%1`C@JG*V2!kx&t(7G?l?q2 zhy0*D3zU#Sh?Jjvy>fkAXyvzUqybpyd_amCP$<$cHq zq$%z~YsI*-tkp33c|Wk}q@=+Gt#qq|aN3d zuub}UrP*L~9C!bzpVl<_QJokl^LEi)T#ri!i^Wk94vl}hybJZh!-ZO4r~)_vR|Kd- z2nbYI2Y>)k7b6y1NHnj)cym_yD!NcuStA!|Gn|OYZG8vs3e5wF2lqj4$$hxj1US&W zOl3pOXD>QhrILJ=MK|9OX2{1BhmuvC;ndc_`V&S19JNkP;*S7dt880rqFzoLmmz6` zqzI=DxwuAAyX2I=LM<><0i1v<0`5Tw2zrSN0Cgt$Z~F!FcHcE3dnUdoxgURlJPRCN zDM@>5$_^4hIR&ajJAT45NeR6sU}mJ&q)v&ocWp6X18v^Zk`eUHjDHgEG5-mU{VS>3 zG8hSHM~kmI^y43YF|Mn(!0>Tb@uh$J3d-S+>L>QO_0NMapcWXa08YRa0U8hj9{r94F~|zWKFM1#-DotA?aC_dc;x2kO$pT59AzdI=6!rkfSIxq$E~ex zTQ`p?qki?ISTyaubssE=&bs^bU!!A?U|k5_;(LxQa{M_CYMh|#G z#QzG1d=$88m0z0sJCv-Q%@1Qq`*Ds}fVZQF)y-Q_4{@jhxCdN$fEI*+kYp|ZD84KS z2bA>Nm(TyBTFOzaMPpKNuXDkUc@44H#6#lWr9k5f;JJ1<`0S5oG^1R zKj{mf9r(y_nmu;G`HWBKa>yZJeovsyVDJw^gFlY@0MgokvUY*A! zQJH0h;1O!0;U=e>?lM-Sl+w?EKzv&$`87}r3{?Op;EDhp2m#^XB@rM1EmO_dZD~+i zgxC29vXA&3lUwPwj)e7w+RX)ptDo?I6501(gf>UJUlX9%7C}>h5YJm~hoR0(DckMe z7W-t7ZW5#~(72muJG%-a0UgQDbTNBdb#((pb_K%>-{w<%81&J@@f?pP+ZN6FP6f5V zPz7)Tt_aYD5D<}I3IN@{59CGYz>{Akep=t=fK`NrNiPx~@Vn?;FVm{w*Z9jHCpAz<)Q4|M?+Okmu?DO1+e#1>Xfg9zwYM_x<%j z*Nyi2*5wVrf8Pgd|4V-P?B$uDUie?)%ecxXi`oEC40nJ-%v!%IUBQbl^yZ>KH;UW| z{7)2ovX*=wHTF(*Ij=m5;`A@`>`g7xRCyr4S9V8Lhe? zn3Y8Az6>(gih=mUPlXfTVA*h2Y|BlE-beEo6J0`VYeH?;}Qr?jLphxsJ zcnbT#>zGWdZcOC^0#aK@WqSJF7f0y>5)Bxc+=(Yw4o4F}#N^!>wn0vPS z^-Bg20-|D40U+e@h~8Uoab=Wj`|{Ntnd`SDx3}X8s$S2Zs7>kLVJ-&JPF1@c3cpgl zCg2lCdZhQ6DZ5nu=*Ao|-X0OVOe`E-&miEev`rA*ST&hmcC z$Sb6$C|jkY+cTQRM>Rm7Df(0+Lxj0&0!)hr__gv6~L(HP8dYvU7)Jny)$}nRPL;-ao>Qf?B&i|e#S(j!s zU}n1E>FDUwH#UoSD6sp@@RaFLGCyP+ytm%NL>LK}YxOy7?OUOWYdi^=U`llR1PBC@ z<^Ry0*qX`IO4(+HT41OGI007#7()n%11=hc>sxIma|2q1uCK3CIAg^YDWOpL0qm*}@ZinFB0Hr%&9O&+Y$ZXh+%hGVTIp;`g z*eemOMpdB}7^(nHz!d?e5CRhUbOE5W{QPbzp+`uwvpvFX5mFcC7ArrbX2KraIFj18 zXeY}A@^<49G-ZYgT@#S{ADQUXLH35+txjP$;`8;uZfNk9Q zvGa^i^}MqS&P%j?qD9H0v>=u2vKQ`4FjF0v_sTlRjZ@IP7)EdoA~gxiXma zEQkSZ)3b^a4<1*E0JU9?2wD6g;4-cbWO_*6cr)@9n(#6Rx1hTFwshgC`iJu7 z$AYMDm>#4*^8yBxSlgbdtOZ=gRWkguxXKY4uRA-^BP``SyvaQ4i24ZY2&x>PKFyzf zc6;9FyqiCE+GZgcVWQg}|G*z|T%anBeZ(Dn zw2F7{TYI|BXTMVp+$LLHF331&n*LU~>Q59O_g|{N$17dNcjnFK*$mnmzl4wTQR&Et z!$^JBOY;M%yhTb&iXy}GD1tMbk@}(z_9}hePvgFBqgLloZ4Igb?t53hcmEId;BnCB zPe1_B5w8d>g6(qXn7TU~dsVqliXr;=qdjC>4P{O-1%IV|;EklvShMbFm+PJn$MM+R z@mY*a&X0WmG}`JIcU<(7IyF0!tk}im^QOfd7zt2ZcN}<=&qNu9JZ#o7mZle76)X>u;8hww9Q~ zZh)0Ur1cPDAa>wLnfJrmzrX*q`B0!gC)z>y!t0+}L8{yBL^qXoUtBVA^i}&qcJFmq z37{2aMOp6UNkRG@ZNt&G=i*WDIuT`nut{OoC)qKb)D&ugp$gyxToGUeAt04E001Jn zaK0^lVaI25PX=GAhry#{3|r;=LBX3FjE!4et_$lx<46Xg3_s1)YY&j^!|XjX2Ox|R zs+W5-hAOHmycyZ8=rF*A(4z`1kp z=)P^8`sY~E#)e-9YunOzx07&xErsrlWQq*Bf@guO(vVX0>g8R8$uB20AG(+*%~7N| zmo_6>aaS)|SJ6Qgz&+r~18g7!yqxX@{~8vwNGM$>hkSQh067dT*ZAa@BA2D!m*?|$ zdZ{VMzS9BGX=01oZp8>*6L7!av!7Pjjg}CjA2Rvrq>Xb)QzF}3<;|;{uVhAkt2V+& z0QSwFA)Hvjn80E8%HJh6@>WfqhD|`{k^!sS`HCtUey9b8Du5GkMSv}YfDFDI07xe@ zO3V1o=FnEIxeV5K=_%~{OXO!3b=hddRzg)660$(uLa)#-@BE0a3Fy$#$USk*zL~_h zjm{sNW=iL675#OLMu+}TrI@}gy8uQ4TncR{cz*A$f{r%>sqZT0-JWgB?Acg*b?06~ zU1R^!8q@+q6~GC&BESwpKql}707PUq{)nTe4%a&IgDcK0t)RnimTrdRO+wv@o4P%5 zXe2-aterCD_L{%v>^}&35UH_lkBWO1x1z8!rE@X71aOZw|MfO1ZrrrRpBqL3mMzU` zVsP*_0flNZQ$jybc9wfIzw{GoSUKJ56Zo090JXqS1#kkc2(X6`@M^XI08;J3!dqet zRddO|bVXYFl>BYI3IO!JGMA=gwFWl{0xH2U9M=L8{iUM%SZbVqy#6 zsod#|#2ASFgOi2E|GlJT!$Q1Gjh5zjkxL@=N961SB9@E2KWy-hh4dKp*g zcuYPUsERIe6n2!+pTKO=@NzJfD*05NwZz@?h12IckOo~hi^ttH%HvLSf-h>bB2jUK zDZF20jm#oU9Oo#-njpiEJ`;%ORnu>O*~NAe<()LsO)c7QcHp&(zE^$nq-g$yE{t|_ zpr+ag7-pikKjUDIP<_Upib^OFuOIx_YOa*D;WQM!Ob{wb|3GjF+k-!_frlS+=92)R zJ$k+G`jo^JfEodghF_KZ?L=%*`~spq)cu^Ur1rt?K$U&#t>wd6Nh!MS0c7yRe7zr5bwG#uVeo+_PaaJ5Om&+{5N+9~YJ@={c zeWjJ`7m@;~{Rvb7yu-QbaGW3nyb+-U6A(@ua@_8;E270aV7Rn4@e-&L@Y>%(16Urz zMT7qM5zs9YD_WQS;>R@s+{L3ztC$z4YR?S;h*P!EG(Kh?{ZlN?d@V0tl;Bw8!bm{b zYaZ**E7R*O#nr{sZ%cQ#lqMwAU5s0)EoU_hdrPUI78t4kPQVob&JY4}d&IKnyXC$1Gq7$NGu#hD^=S?9EI*B3>gS)01-J9-qOU_|2-Ak9P@)hXaE49p}3ev z`R%2n_|PlAoap99D;wTl*>}^YVI;sN%ST?sun{|df!3SuOt2L-9O+(hha7OBpo56R-+tY9P{@uyhq zFW>0OM-eV(H$VJ%Dd|0Gsg=pJ9J(fyi~Qj?JTe4odH;dnk^mP70R<8+0Fa~o%awg> zgPBHcqirARAMQ0O7=re9GSD}A*!|GzH8Fr>gdBG6vya5C2{`2+YdxJ4cKyjbmmvLF zNSk6SCtv_!Qm~x#TVvnGb66LPe@1;y^_|VKYEdD3TC@0d!(A`?T!oN!J_a&OyKE*i z_+s&YP++K%O9EUW1Qb4V1Ar#t_jL&C%W7qsJfsgzy2wfIa6RBiC1&nuY5SSqI}{7l z6FHSh{qXkh{gSW>5@+swibMO6vWCov%yb9!6I#E7z?%taqujnBw|Es8J>ZeBee#<= zf}4)Bamy0|644UG8}@v$*365zjEB7%g7Af+P|N!d1eXN3K?o>XPXvJak9uc@TL3Am z7bWTpfuhgEEbFPiy83h~uynC+Dt2}N3HI@A8j&jruRXwTmD%DoZUfaiMQlSqT_8ea zWtowr7xfB)P85FHA9EM)26` ze^6kkl1l>IAq132e*uRT4n-0^4l?>Vp@^2ed%+xgUzRScvi;MwngL$h3!8xekzIGV)u(=m8Sc5g1vu&RD1s zVT&64tf`DX$|t2ptt@u@4+eWfzidK1)rTs8e;2s=F5m$npfrsQ0Qwx)kF&&``qTE% z;8S+K+0rsfRyv-vSJYB&DpQNKhdwYkK_dvI%f%Q>z-3&eSJ}^Z*b0vF%}P|J{WHIY z?9&r>wGAW`=Q&5?b7!hmVI-hd!jh7@gIhYI?1-O11%C^jwj{WGQ)ag=Yn9u9DUTZ@ zjbB%sxgI$NS`Ptzr)1HdnvuM?;cMfzY&YJHNnO4LkE?`g0apZg{vqHpuCi?V4FF_c z)DNs0EhYF6QJTT@H3(HTAyiru50zz6hU`7nlea=Z$~RcCsCv1RmvNO$f2Ls~VN(JC z&+j9e004UT5fP0oEer6i!T$s#1BAaOKvHUj*}nJ0dde%VXc|oVB)ld`J+gQED^?0% zUtkU|P#=Q#i|W522JL0p z3_>k1Q~{iTD*}8V1iby-4gfXDE=k85d`)sRkR-AD9% zr~)_vR|NP$2&m5Q1b|%BI_5kD4-Y5QTZvHUjvL2t!sFI{7z&-<>Mn9_&AXa(%2Ji%D}aTY=N zqM`DRI%SviIU0R(QL>F3&`I_)GSmV?6~GC&BETO)K+VMg00d+rJSpExrCRuy=qxC1 zYOa%_G0=bJKJ&T#b#aSKG#}8zZ1^V0WBko)0`{H_KKCo{GQC@~rYqpf@WrZt#q>49 z&LPI_~|v0GixE@@ZX^j{)Kiq z{uBTq;Ju*?0Q8%9(?;ReiWWceZIpDQ^(I%}^m4sd2O)`?HF#lUDRn?0MGIhXlN z)*d_BXonqZ&_w$2Ew|R!LOsNx3g8}ay%9(1oy1VNNCSyxj@9fa)3whQJl1AW2$w7zt2rGQ{qoG4zuUsX(UZZh3CR z@*|@NMYeX%#TB$c>Aw$ZjgatHAp2^dFNsReNHDK(>~p(gHdS+|jjjrd1EB~5bnk=T z^9?1%nj0C2b-&2on{0W00j39Ih*0Ku{D1A8Wmr^g`|gKsBn1KK?oOps=@KNQ8z~71 z8Knl46eJ`DBm@NMMj9m~1QF?yMnW2aJ&!Ov|M%JZ*t3s$pKp8C2VAq}oW;oXo9nvg zTK78d97Sf@zwIio93}rZ6G;F7^&hzw^FK`=FCP6<^pDp->?Z%5NcumH(;^dGIY=V^ zc}DuL$G{8GKi|sbnK`acg}ydL^f`f>;Qwjs(thfo3JkY_HcKtM3?uYDwB z(_oAS^D!WpmZ4VTjr2$G!pYanMN%a`B5d?l=JcU&?}|R_l*~Q>fy7-k`aE$k<}U0i znJ;bxXZ-{O)`-fP0kq8kX0+Nyf3+V;|N6nAZ)8SmMw@xxoQ2>|QxfYQ0CGeN(x-&z z%%sf#v@!b6Qo_OhzZr^-IkJcf7HskC%T11qx7mdxtK(-_@ga(G)nMc9j`y10eDgyI_CZG=S)B-sD;cERM6 zU?Aj7ka?1gb?FehN>;7ReYA%+@YtiG1co)zk?BXZ6UT51!(Z8G!*VteA)fpsG#8wl@}6%z7r zNX!)hJzx6cZ%hXg_>~!Jdlz@Vu=M@CB=sW)uo|%~(aDen@3m3S)fUk&IRvSe-#)HN zGXigL@dmTLI!D)~Qui54lDfwSGr%wf2m&q%cnT$;r2|q0OlG2wgd2W@$B3i(a+^fVw|DGZ9Y=%h8;^R)2CejRBo@kuh1UTvhHvpfo zo2rj?DYZKh&Kw~2*~;~Xc^>z5UCOUxu3?XfyDY2-JG|9ez^$%nRh@`=l90sxUvC#x z;8`#bawbR>XrqPHEb04ShaYl+3j%_IfslO&0qs7J@2BNSxt$Cn4V;|< zCzzQ%X@i**vr)4V26n#1?WJ|}?JOWa_Zzj;^-)V#1OzU(_gkY4Dlt^pf28LPOw83c zA;1vAy8fPrjURY_9^OTr4c61A3q4wYsk!34`Wjl^n&el0;78ze$G9Og{{v>k4$LGE zQ-G)fmjnbu3FsJv^r(0{1pOFFUElRPcbCp?X7}`&Q562eyB;q5C~t#vSYQSirT{^}B>^E&0zPs;`qbZ19vn`AKH~qvs537Y zkc362i4JLH`&i(t0;960WR!t-*uS*tdDhI5WA$j_iqjx>=IWPnv)M%3+Q1AjOaX#` zO9Dcn1bhmHT&oJpWb>vQr84gt&vEEFzxIPyFibioLAV$PGhC(1Zd?-N%&bK1!o_V4 zA>hKU(vfD+4xhYzeS&$CWV4HdXhQ8Ey;^33Nh|EBJYzjJDVzk%Bj*i#J;C4J;Q79$ z7qyYTr;wqr_>R?RcUH(*rv69;tYt=Tl_g!K*?iz%Z{Slw>NbawfoEt9tbg<1!O8yF z4wxH9#hSLfzOy`#kmx*0Kt}io?=V67u}S_|=rwL^jRG;$KWa?+|4|eL2;DUUh?xO?84Ze7`!3W8SU6V#6=SP@Q>!xzwq`Y!H=L5lhR2!{mNt?esoOo(pS{?v$qyEtt1cb1_Gr&1l9i4y$fuiD-4=I6Aaluk)t09| zn)numRN7Yc4pR&fDNx2oJR;u8ls%OmeyJ1>`8)k@8~8wnDca!SpkX@svAzTE?b2?q z6OH~51tVckk3u8-X)c&)4Wl17Q=r6e*+2|cEc=UE(`LkwM*m$Om;r_1DcA_U)rpQA}r^HA_J$5K~$ zJS3*8dsnNOrur%0LH6)C9AN2o)b)!0jC~^2aj6S~{Eh zZ?oO`O-~%Xk8cf&w=aV z0{WF8bM{H_AJf`6d5TKk^_98KdGdp8052>>Il9IhAH;JFQ91Cw17#x#ID>yjc=VLXeM(KLLdf-%q zac%Iu;|PGZwq=vW5@vv53J?Tb67T{_z(6MC7NNnrl$AU3Cm$iEE^~^cLT)k`{2E+S_uj;^p)J~kX-`V>+GHc0hH^m2pCCEIQ_U^ zw<|1pYM@VOTF+h#s5zDJj2>u>b_!BW9)pvB7J{Y?wxTYw=g-)_>)}`iY>6_jA5oPV zvjvkb;0t;nu7&&y09@vPC@29#`jESYf3?L`Naay(*V;wmuFQnd-~B{rqwI9{kts3g ztlDQ^1{63-89`IyVsJ%3x*$o)qxc#UF+KS(pY|~V8_bhm&Ces722AC>gq2HQ!AZc~ zf};R6zK5BOe%Daqxi>7Xvrqqu-W(X7jk}dWl>$P8ndDLMukfM_TI!=(_%fYD_K z)#V2`PF!~uzmfd6S%gTTs8%RhJo&m&^-v59oH}r%rI0Z!)H>0d?ogoSZ|1(4 z{`A_j*g#NTU)^px!mbi_owyhW#Qq`Rk6q%p&qU%n7=BcqcbN4Ml+m9S^*S|fB(p3&u~Myc#73bxY2LhtiEkW+RGAXh`9I;8ztC^3)tfRzy@hQjLzsu5T#KoQU1N4bNjg9$n+! ziVvJQn!b@LEimf|OaY?cTo#;{Py)toLd*)a5(T~7g6V#S{(5s9&KYLi#Znt(rg?Zk ztaL-STqZ*o1aLonM2zAjb49>`unxeCSpbQmv70GM{F}%JQz-yuqpO2Fk@F%l^N~HA z1W;oiSuxanLGDLWbeAh}_Dd@EjoW&;UA;bVZP|XG5esI3VG0lgToMouC1Ct9#IQ6! zTKmm%)7#~p=9J%t-EaE?1v)2#d-(zu&a(BkrC%R`T#M-KZoamgydogwM{b;TZ&Gg) z-q0F$&qgS(A{UeU(IDfeN0Qm2miOb~B%rjb#RxytLAw7sp4bO_{8?ecs=+))6mPOG zb%Y|1ZmYozFiZi0fJ*`rpae_|Lrja$v7?oSN&*8qy-{ZfpJ(8?h7{kuS6)jUdp}n` z+hk%4^vEE(<6F$cvnvABi|K}ydq#)HhZxGT1@GZ|#nN=No8l`!HzR!mIKYJ0G$xv` zW~bje$3K4*%UWY(Rlih`W78m&$GKi~CMymv4PjIU_!qW|alk7m0aLsXhjalIJM;Cr z)sfCKP=$>Mj?A<9NJhV!d$&h6s;@~*rILX7V{~tE>P=8x5g<$RxRPmvdiABb90_j) z7XN@>o0v!EI@ieF(Nm-naRWGW0JfX~kuQjlhzw66u$TD7OG+8j>q#WuVtZC2Pm~@X zzlZq|hbchhfXf_^2qj?pIb@xLQ&l9%{qKbtr)T9iP8-MzTW3O61K7SwyzN8#&}cxl z3(~OXUF0L&?7AXgu``GXt2RAbBLw$lW+~-|Y^4}GR#GJYKwtH2I zoME?KiZOX7CEn?I#%;VhwuZ+n_ZsnS5b!T-7vq2wC;{^skTuhg*ml1wG&(lfg?%Al zQ%xLk(fZtH3wF<34?iC}NAc+f-ILULQXGud03qPQuCmdN+VaWS1Aaxbg4<+IRGOVJ z91jo0+L()^q`4SCLCJ6u@LG9}($_xFzjZ#1^CM#e(afr>r7g~bSmah5rlnVA_F(gS z&cl`%sE?H*78YvGrxN9!P+{NY1ml1{zjycrZa_xZRl-K(f`He52)MATTsVO&omh-s zktMLSIF1~~mto8O-Pdh8fRJICIkvQtKRJ4|l@fGKk_bb(#$Eivu96M5U1d1K59?a&QFUfUTlbmOS+>lZ2XV*!9cP=jGDaGANPa zY78D0G{v8e-9e><858Z~5>q!^ztc87g+; ztbYu~L;UWJ8nILlivo)j*(f|+b_Sts4w-qFB?YDcQFAV9PAW9xEEk7;;M-9jLr9`Y~@cN8)OjQ#iNswvRCdyPCu!yA`i8`b^y@jLgHqneS@%`RlWXP4e)u4X z&i8Y7$C??E@Gnz$XNmr#Qw|1q2dNU&v*Z7VD} zZSJ@J68j`aht$dYyNB1N@nwnitNlt50Awo1$U&198Llrp;7vj$=78FBvdMX zROa?y7^S^#ij?oB@rH(*u8ss7W`JP|5CmKj0EQB<)@=p^tCq2&2(#q`Xw34&@;pu^ zZErKUr4+2Upd@iJ@Vk_J6y!p%r{bxp^OsRR()kh6F<4ZUM=;4e@>P0)e5rla3`jpc z;@e6do1J>R02&HC7yu^$tRtN$A;zUM?2H1UU+@RB7<%d3v+?jJlMYT_>j6#xFar!z zfFR(KfJ`U>8!VDQ@WvTtVPYq$1OJ>>8nc8+V4N>Hcy8jQ;4psrCd+sEHjttpssfAs z!iOsY9!M^UGFLB}bgi+4KWUh&HKUsXB!&oA#VqSKj}S@8!AXF(xZ&OFy?$1*4?Rgl zqghjS#0qVXm<6?BY(5C(ug1{93@}Uqf`Cf`vY-U~3>F81fgAf_e8xyZdJ1XJNxQxL zpAx$ra~suFKe3@S?6B)42jRp}uw#E?eRxH{^%kVaucKS>{ibGsH`p%=nGWZ++0wp~ETqbRml~`s#m}BC&Mq}+1d>emM6F?6O1dII z=gcsusc+5ez{Kx3AdEhvF$Aec!s&yje{d4on0f0coCHiMRY@)C&^x-d$1FFx)g*;m z5=C13I#>lWRKwfg`wz%T^}0xk*2h7zzX`2q+w9`)ug#bxciMoS-&EU@h`F{Ao8 zGL16*)OkrfFYK8M=#Cj)DN3vIF@%5%yUMwY)A14~i7e&cupiq2y`K7{`}Y@?$uqCZ0Z)hFrKgAOpf^k3^mS>`vcm{SWPwbsTylr4nL)loRBB2 zJS+Em{(}Hi0ss*3f8Kylt4GMw|EWR!#|!>;jimka%wHap1OTWC>1yvEFOvoUFzK(3 zLFHEm{%<=-5H;|R0J8Jv*GoelUB2T>i%ZCX3p>m|I>29^n9%K_1DO*HgdDiAtK4}# z2Lz-1a{q0!(Mz|(Fw5Y8v;98pPypZBKT>{DUk)ewS7lW0aLnM!bHE?-f|9dOm1i z^;_eGI&*{QPsH$cHrvtCbNDGvN?-%EZy% zR_d$sS;ohQ<88>n1@3YCs<^MH-G61DYc`5I28ot=;|Rc+m7*AH8?hzp>@q}-GLckm;wX=mvt!*O2D4}GNdkLq}m4?6elGBULFa1@iz+1 ztCSq==Krv8R7+k*7n=hS1X&|>5BTI>jZ6Z-8)!Aw*S6oGHL}TZtCIEhPd%J}GddJ8 z)z*VWG@uG60kmc^_mVdk*aen0i1i|h?uUh}p@zJW-_rgJmPO)>&4C$Um;wX=mjt|p z60l#91hHxeZ1WRmx(Nzfzp?7(_<7y|<5d5-*xFl5)msfdG|ve@ca+8#c3QX=uL#iE z)hbTu7mfNF_aINv8sn~dE{lj`fyB+5QGtgQ%pTov5@65X&m}&yZs}W$e;Cu4FwBxr zaAHq-sOr_mgq=FrrUEm-Fa-z#E(yqo5^zXl0|e`{R@n+N^s}HorKo;v%_WJ`g>B_| zWFM>$%KLt5RZ#_mj~XO@o{rjnMS!S;mb%baD)(*ckI~rqn8C?TQTN7siU9OYz?Zr4 zFPXXNyJr>RDf?9WN9wDQlHaouD|QU~o#8bt-bW&R*+49(80LRZsPQQ0WK{U35j)V9 zz*QzN;w;JD8i2v+MA(r~m|J=P(MGTJoQ9;4?nn^*PKm9hKQ z=95s9-vY8`pG8)|mArRF!0#KZRYqOTsw^PWFD=pDgV=gW_D_0BuxZ>=xlm{?yma zZ#9@yrb)a>l}q}+MTsS2A2d7Ah{IV0swh;~bY?K|T0$bF&d<=-uZxj@vlaDsw}t#l zyz*RM!u*KC6d-cIWezBU5^x&P4FtdH;KWZ`L%+XgVq4mcmDZ3hOjfk^@a>iyM^#zt zv0w{`+MqI} zw*gw?zr-JX&gzQvSzQCAtKaN=wIrg(24Z&XP%a{N4TKPIVONDouP zhA}%zpM6`)U!fyr`JrKNDC*YXBw*qw7v&L&vFvBQ#F7!*r`gM*&a}*(F~nuqjRbfj zOX%PiCzaKcIg^i2%CkNo_ba5-e#6TiuSEBpv@3jO-)1<0u&ad4$VC-+|A&A-c9oa_ zr4k^xh-Z0NqrNT`GxdHP=c3YQ`)Z7y)@N}9eWUcChf7`8K$35Y3Vjml+b`@YIsV`5 zDuo1PB!=q35^E*)_Q$If*Trrgyd7=5M>_SOvO2uNCX4yHG`FSk?0}>0ubTIhM#dIZ zBza3)cR5)|!@5ngo^mpq&W<9xFCY7$qWm_z^-(+7dT4rLq(qfS$j>5uKIm!aribDB zJ+}yn4?A^+S=HrKX}Ccq+cEuWOO31s>3bx9EiPVMQu|3&yGMJ9>D%|+ z)umPg7yHuLhEI&ZQcuSMd_It5Vf92mOO}B5Zejja3`HF*7kZ5?A`#GmAAhv%+o|-r z4Lo-pSP_Wb6HVN9r3Mi?*_bH()dxj z9_&bGtYSWBNC7ex@m#yrb=%^q@>Sn?`mj7$R^Z!y0=4H{jGZjqkRQZlAnkDrIsw+1 zvJ9MgFZ{NtZz(j66$qE4ptn#SEmd`)+zc$*WYjX+>J>o0F$3E z_%=2ai7CzzGqqsXHQ0)a%2x&@0M&98(g&qhKh;Rgt6m>{L&HeiT8_k@>E@a2T$4e8 z$?Y5_VG2S?i0k5|e7Si=0E@}$mj6-p&5o6<4|K703}u{lwRGggnvOS|2;%*(#lT6x z^+tc*?MlLp-U*|VIz^l+Vi9~h^?Og;e&6BEe(?-aZ1k6D$S8aDa?gVl7UP-^9~vpo zP$oMSH6vN@RL)ygz@Es1$N`r*pd3m7TI&K3{FHM0#O^RE`vCdTvh`6ahJnUFQ z!P#5qH*#l5RBJ&a#rM9OYXrLw1)f1$5}Y}pg4Xs;NOR#t>-$K{fF3*vcl0iHq0q2! z=29<=SLADmyEb6Q7q;SpfJ!I^rPl}2Kl?o*k3~0#1+T_9lBs-sy+yKrXZGv|8~G09OQzfHV98%)89m z_rWes1)=21Ss2}ELdpxBOQHICY^~XF5@0lP-7>kH)Jzb7jMQtqOyG$cb?3l-(%VdV ztidm}ln_Ax%=p3+e25C}$X{9W6Xp^YDdCASmKrnzL@+PBFtX1Ix?jZdJl z`NI*UP!rf!`7Q3o6#-(m%rJDUz0tFGF?3c`pJQj+=WeW=a;crpx@XzyGCqZqfNwrX zNT^xuA;o&tJ6RKK9GPt6-&!WV4vUa z?9A-EcckHnh!uNJf7S2d*RlB6?fT$Dis;znnq+3Vv#o4NSWzRUy1>r5B%3w3LX&7XupneWy? zcim5hxIypyjr`g`ngC?Lot*4NjQ}tx+lKb7>R8z4T1nIjNPyWlu= zkcgyQU5v4yiYEhZ-F0z8g7_BW?K#|v`$?=c;M#$lB2I~>&n$1vjg7}Ef7~4YxZut+ z(~~enK{-a&8biS>maC$4A-58aFZ_%zB1n1@u<%bCGdUns7c*mSpClk%8QV+dqNGHl zMi41r!PNix{h!5|0N#QZKQfTZDb^R)mi+M`<3z=ez&nj4W8v;DvIRtcc%1^By-T;X zwWTjSkGpI=q`#gVS5+EC1gp`3`uiuC9%9F=J>pv&5}fiq+=a@bkE)cX3$ED5{90C! zIP(s-Q>$x-EAfVkuj(oEh0SuSOo}pwp9o3e+D|KAT0n*+hT(?^&p;qAes<#nOtx_E zjfluOE8a;M#>P|pQO;EetxWzlbJIe>70}{Ez}JAVi~6J-7cea;;^6xM z!%uGy1TdOjr%xL$JE#5)`4i{fP}bB%qpv-yn1!Wu0-p!QRJs2?@7>_+*vH-?zO!;O zP}JIMq%Hqu4!6}Z5vEL5*sI|)uX!O$&3Qn$5+}?Y=(cNx!|+nzfDM$IAovg^r$zOn zRfX6+$As@`X?1dc)u%=XDPVs+003JSy^G%FQm_IMbgWFM^xXZHRM}7UcX4Q#ZxPg{ z&rE9IlBFq+I+Zp?X#f6{QeuZxBmcX0#E8K*m5@Y5abC|Fgw}w6i~>Xe7oxLZ zBv}i5b7(I8xLiJ(izjcBx)7GzY8XlKVBCDlfX8Mk=XO*XCX+*g{z2y<1d5+B!F#52 z`qdW)cBc338$>-)T|&Q2^itXQ@PUbkcJ==L%Ri?=idIj+2ieD!xAFYs$nb8BNvd2KS0MY(!K!?XH0v)1&s+f zED90c+x>>BKjgaWTvr!IekBmc=QjGkIKoSDNmM^qs_W`jR{ez z=gaxhviDZlLxh`{1)@|>y2k+zJl`Kp|A|!wn&g>^Xyq#x86;lY$lW$K&~DG~%~>4` zDcsUhUQE=)mz>_v*QC+)Aq%S0!1gu0^_k;AsMM*XtwyQnX&SqwMJ!VUz+n6lD&qz) z48?zwin@@IZm{riU3J|2Oku%Dv7eR@##MP2O}t2{PP_CkE;b!CN=S6YGQw{%%08v2 zsP!jnV+n3=#zd3zBhqG>OrYCCbV9z6)E>=ZKQAfU^^w#pjy_{{?&W&^N8+6CBbsNU zCXxLa#gLBwiz7Z4e@wswOh5UV=JJaPCw6ni9YT)`4FzZs)|@Ip^_*sC24k zPp1vi@Ox(xFQkklCoz@Ey2|EigubY((1{p}1m2dko>x$$ordZESimn6$>TU<3|lNV z&F>hGYCYo#xAJkUZ5gZ#r z)4rN)Rp-^2aSPWI*<8R{;u1B89AY&^Y?lz3Vcdo_4KZzK*xQDShJ)35wu9!%)BoMj zP#GEHU-H%8aw9-JhU(%E<|-?z;I^L>6FXUJcqzXM*tiK0t6>X~L9h416G)8Mip`r3b4 z8m$a4ArSP5pt{@&2IPYVSn486&~3-N9g0#o~Rw(u5%>@TY&A!p)0(t1rA zxcroy@GXKvm)h~B67RDN{YAzPu70Dk;-D+b|7*6>AL38;xVwniW;sL%3zFfCj+rc< z0@jLER{9TSO`oVb3o<`#A)qHnqLf|CO(>{iIjin838}%(OP z#YZ|TdjKII07o>zrvAhkG?B8yIEI&h#)iZ%LY3iVS@J4 z@;MrnJiqSX`^96Utv4xxcXF2mq5Zy#pc@4XoD?S!otR0*-CSF;{1%=4D&vqAa1$iw zX)2bVn!WaW*!}Dha~%=#E8Nsx;5N|$Fv8-A)t%{=8kNw|M*zFAl$RRXs4lEepyV;{ zpnDp{uOQc!?Zj?PsOgLC) zd-DYIDm^GKac3)rrA(I+&X}JAg@UWBx0Oz&`hb!zAi)fNY>ut4P*ccrcI1=6*NqE%j6%n4 zzp#42x+M^=?IFs#^5E&b7HllK%1!~Y(9-$l2!W^{8k zhrV~`Wz-;ZR)3T%yX1K`ib|qxh2%?PgT{J@^Fh|tBQp05gIX>;XxOevaS+66V!L?N=bsQ3uWkYTjUcD*yGD4dGm^ zpwp&2F&8H{)Z%8aq9#LjxWp#^!bA;TNsv{adF!y>QDQ%b?2iyJ;^)=dv}OPbUpv)K z<@K*E9p6N|kgtKH>z|W=RpZNyQSmEH+u)BXN~GGmnbO3H)1a&S@gx5!YI4m$Ubi1` zRjsvNg+WD)rh(CCixiBZBil@&oTyAEW*%u$^@Qkq70$$TNQguZ=<@EzuGsgEkKlh{aOVSuEXr49 z9yMg=L2+79%fb;|UJC8Po`ZTxU1=fW4Tsehe@+7i*|P)!3F2x7y*PeT$FP&kgTpT5W_ zQB(!IO>y)F4n7Cp{&cMT4pdH4vs*eZ{E*Xoq+ys){R4DK__}HlFRO;U2Vjke2g484 zPoPlXX>u2D=(-vKKV2Ql!bzsC1VCJN>mXvA@Ey{E)9KC_x6|~a=NFLqv9}HF#|3i5DWE8c_l^>ie_-J4fysxPtqMT& z*V=L~wZn5^SRM7lM$wsRGhnF|6HAWtA!8r;{f|2&2(qqCZ!A5&v!klfTrXBiEY!kh z2|J$3Y?V?bTy=N+oBFro&fmLO{9Bv_#d(J8cC>nHxsR-nG%fhXoSj9L`Q4UyQJbjZ zn+F-K)?EFI2z7jdnVBj zuU#*B>H1*B8&|Q253zsB4UJ&5&H7nMi z=TG-O{&XevuGO&|E}=!_cwJ25nO$D=baM-!75js56xicbRooLyzkSm*j89N6`)%*; z%X0E3Err;O<`9*2W_Jv|e=8z8gW3xjQLUw8C7efsJ89%N_`(fLEqUdS=VFULR^oU{ z2&Es6WiIGFSny}Bw-hW?GlU$k*?yk)Lx~Q5i1eUaCj_D|zB+cx7sqZ`iRyRin(oF6 zd~|h!M1=k@-O5?hC_q3^*Cl_@HP-vBNhz+O)B(N^UxXG`BR-re%~2|5m%YcnTaS+4 zj1I0p{nJvfOEy#83+*HS5E-MtAhzH#^YQX+Wyf3i(L1}9+JVm<%jf0FOr)Dd-62b5 zxl{l9x3@2AKIINL*s}%4gU`Jc^j)5>*0N*!w~`WY5I3UCMol=%8Rh`Rthl4usB}%I zJdE3vc@263E{h4l0EC9X(Y6ZljyaLvJ~x`Z@+>{{4j;Xx_Cm0h7DCa;O8=69a4d%1 z-Pp*;1dN)D=uUWZ_AZjyw8>sA6tBp$tjv6PW5h@3wkscdcHgT)bpgt`h9K4T@?D6r zBNs{#Xt1eY_h(^%iGOpwt%m>p$g;H`aOw26RK=VHfxh~as4wXNE4NYMyoVIql+{~3 z49W|(Y9edG`}uI)u*9{dKCo;TovCGwq>140F`Ms~>|(Z{7Yp@J{DQZA%r_MTQS%3D zoG6fljjm9w*li)xcJ6SuHZHE3I56zr*VEN1Y)|$s>H5L!9{lcIU>p*Npz7f zRe7EGHq7(HS5AZgHf;62DRu+nt4H%L`+CXU&5QEcqjDDG-?UsGmcgiWmIbVN)v>)w z0&hfxF=QW28!At{mb*9faF`KKOWHDj?~Vx5(dZzPwKH9PR#~}B4Jc2`TqgF-D)^j} z!aKj!O_;Cv_?$K+5Ief}?ufsN%IJ%cXzhOwP{lJvTRaKb`hLatt)u1`_fL0q(nQ@a zHZ+;uhQ3zap#T#F<@JVT$GfnXOv zNTq#I@$#r5ij$2KkL7pyWh)@)nrw^+lg$vQYbsw7`$-SU>%cu34{$7CL=pr0Ck;X0!#9RPt%ma3FJxSWPkH|diZ|}@C_8R{Pp`vr+&2>QI zT@S-At>o-~*Ik|zfY63@gTP#1!_?>VgZ}awMulAoI`*-E*p|#38BNYm@TIK_H z|2G}(rRl+FeiG7e_&x9Y)ux2&5$~6J^Nq$jiCsyA`t$QzF@{pZEtKl211i4Tn#_Za zZ+Sj&lTe$=VZwj&^o?pKJ|ZsJ1IG12I}G!P?;bR{%~qh`+dkr3EJ4UT7WlzGfP;JA zzAZCJYkJyU*Wd=r&Vbenftx;HANq11+CyO4G=?*@5tVI-fGG~OC3(q7wHzOgznib9 zem^w%0kW~54B%r%;6IGH1CyU`QY=M(2^c2rCMmI;E+_3aG44hp#9JARb0x|3NwthR zU><@0cg`~5rY}otyj;?@{$O&vTyStR*NsNDKutX13>wR*2-30iR#ORkH`_t$#m?2Yd6g^ncGSa-U)>_b zRSdbhj;F@vm7)^jde6K_f|R1@A);b)NOoNxD(l>~Z)9J)j{Gul(p@F18(7xt_gwr5K49O^GIgF*0@D@W?4O z`O?fNjPhxYC4%@l$#>9AJFTv*flnH35DsCNV7q)P^Aij4;ZR)g+m_t4WC@pbCX?#H%@ed(C0!n0@+O1?KYyD6^BRGJ0 z8yGyylCfO#;96-w-Lqxd$K@07 z`rr87rt$~@-#fUl{MB^uPQ73OVi^nCbK8OQZvlCZ;qe!CYyZa9Qjg1>(Cch-aGr$R zzt8Sr#|vJ|B=Zv?2YuM+FAy|q|GdL8ZHHDoaZn;+!yq`a5_+a2S+((Jbzy!G-RJnH zkmFx*H`iW`&s(^dP3Zbz8Ybq!Hg{e{c$wLu3|%e7Bg5fIfR=dV=L@m?1@2&5^tMLV z;&F|iN44*#QKNY8FG~63^#>te{cVldzv2Bc-QE1bvtCeQVKf~zbywYdBKH*1Sd7ZhIFcbt}cP(gjvbiH;ad!>U@7zj1aNnW)Jqi1R@ zZa=vieg8Uhsux)qMTavQ-}3nF@&_$C8g(PF&xBjy6kqZ?0G_euSR*o^YcDsCN<2pc*cM&GpPpWnPELy&5TGES=fPBFB>lYU8 zn{>;t4H^1cHL3<=GHh4AL<1w8|3F%dilf`b3XsvDUZQm?fPEa^k6+8}=^H+bCdcbc z!NqCUswXUW!6G4(v0?gajYgU%p(1SgJ%*{_(PXY5`6iy)tvW+f_g^r^g6NVyUW`3x zK^Onku+O|KS~)7Lk*>Zjw>lR-{X)s$NN=tX1hv#tWB$(U3u}*D&Ti=#jW4a6D!i83 zXyMH}! zIB*J2gKtU7zNqbPq`|5&wBKuIxj#V!n@7A36vi%(<+~tZy@(g*f#Z!`)8T5v^(FNJ zfr1@9+%XRamUy-(5TmbYsiXvd+?nEn)`Qs&SJNGJ0DX zqn5j)47$sX1i`S@(n1U?-V9Q2k3f!{XBYva0vJy8c-t#w`H;Zrmb>Mn_uk}Px#xDs z!3j9hT6`c9Zm!obI;@{MB?)zjV9zyXviF2#&Mqzb46piY!D$L+Kkq+-01x3)GN1Nhjsg z%-my4PG=z4b7dK73%JLaw97Z|H)fgARVvE*n)Za&!kqhb6KPVuACxGhf6}HaZ(H_M zcw=|!OOv6>mY95I60Aki+~^ycFRpg>Kj2g-R%$Bsa@MTV-f-l81 z9?Mc@$>VP0|DFE)tH6~Ph}#HJgA0;(T!6+NW!QPN{K<-n?nv-{b*>=n7j|Vc1Z?dG z)>Tbl8S!@_g zVJQ<j`OEa zkf2?pd{^T6Lganvx@8q$)>Pys_fHSeK>swlaLFo_6@Ja`+@w;MT+^72BG0i70YSlK>08euWe^}NueGSVF2-G zHmsI8?7u3f=C?N%!jG@I+7azTuN5KrgDp6&x;bm(?A0K`l>`_XTRCkx?aCMVp zE;t2O3BPU{?#shh>=BGu-h~VvZHeZu9kzI@h1Z;-^jb7}+HWVqD-3@{zbBRp;#)+@ z-|X(165Ej^B{kNvL|#*^qnGH@MvsvOQvRbeB_3`v)xX_fudsAWtE>T8Z+Js1kPsg8PxDAVeTx_u&0HMk4pq!N%4%r3b=w?=|sD0Zadd*z(%W zZl|8w9>W@0Yi0662vkh`Ls+kH>W&j3ADMC!Z3C9#X%WBM!5~4ekD>|V|7IrP^UKF$ zq_^R3{LJUiJY~zk7)o3gOWC#RaH_;-&}$rmd(OltGn(rqA<=Sl64Glz(+jfea$<`z z%kTZWscXWQRYkwkZ+BM*Pv?txIAvtRpPJ(s;G`d;5~Kg%uxk!bxAv`{Y9&?$dt%a! zbb*BCGMR~uJ-|z$ThBK#(ER!9@fr39Km|S%<8@eOH}MvR>r!`ED2p+;aglzYs5}YX z4#{|JgY)woK0DXKK}H_MHMWPZjTcl;g;*}Z+r%pk;(9v$4Z-bD*BKsfQkwIIki361 z-8bG^X@6V3X&smzv>-p_BTXs8lFkK^R53p1TQ@7qyivwTGlYXBOS;F=1q|(>EY7l< zt5eA(IW(#Y2tIx=?FCXI-_7AaT=uTw`&!a|#OEM!o_RwM0M`U3>ZMsvA85wO{ z{<5}O&^;#FRO56gh^0-#Yesq_trGEegmWY`d9HKAZr4&+&>``g?$EB3vwhj^-8|U) z{qXaLrTZc>K*NS*7pxJ?)v051lrsg@_y)?l$pP4H<5zrCbGucNXWydmO1l&~u9XLL zDPrxAFJR?`pu@*%Fig8e+_IjK>ZrP9!Qi80ZD7x7M?RI*8NSu^?(l>#6YlJT zu4t((#;NYt`&-v9{!;kvRT{eMKT^K}abUMK-@fUi63)i~N1A1mkQz zrflF5W&D|)CU1!GylKmez^2VJ< z9QpGbE${?X;126*JCT%f){+nhdUWCER8>l-s?ub&fy$2u&fK~gBd{-v;UT{k>=jZg zL2y)^N2pXDV#2}ikx*KijQB}verUJE*mS3ZE)A?-dAq#ZVn2y}szQJNR8dKCJJI*J()ji9iJu2Tjrf`Q#=Zx@DmvAP8<|ZJGI-VJT^30;9aJu2R}yzkAIsCLK{_3SiG-O?r>Ebx|Vr zNf;>rYU2;sh%;3G;CDl}s4Wx!ge6cTQ`mkZw=}jX@}LY-VLDU?3t$TM?P3@3a-goX zd9iW@KJVKBOzv?gX~u(E?B*a6u&U5k-Hwl$(wIKzQn_3ZoQ-8TPeA{#h5CJL4C8~E_V?nzbk{Y3E zN8?F;a~`Spsa@@M7TK-N}yAN3c&vHjLUG5yPcI!rw%uvc+t0UsJc7o*qzM zxDzs$i+IwaG~K;XUWmSqt}SLq@b8e7eBJb!mraKtzXKp|99V!Nmf9^lcI>v@*{sB^ zPw7OLyE4r!JaJvb`;Ewr>&k}zWO=f(KE9Y0LlinF?t z;i(_JJEKl{bAxCHfC(_2xvT%`yV-4ne$aFuJXAG*=Sf`D(wI#O`U1@%u0bz)S_ThV`gIXbZ-JX-QdJ*{q58Nb|1GMyQxkLuAx25!bjnr;rdiLh2h1SSOJx zZ6;oBi^S+&{H6Gl?F-9#4LW=lR7s=%O+z8yzo-xj<@dVM=s}qi(aFMyAN+{r*JF; z4c%;B=pNc4^oZ|^v(`84@brJxkT}!)xI@vXgqgMicww2b4!}VP;zSF%akHhQ=t>Oz z4s{)L>h><}%{RUWf3B7#O7%GRkf?=z(T#c_Ck-z6bnuB4wj0l&<>4rx`@!`X| z9)bC{aKmJd+D-3(x)od*PjS7FcK#p)UNhGCo0P%}Ra#8^b#Th5NJ?v8auY{9Ip}{7 zxZ-$-IykN@w-tIDMIKjUE%}fbm^#ovHCH4E>fR2|$NhUxi#P&8cxgKv9^fE|%5>2+ zsJSq*|XLymBwS(e=t8vBPDKyT?)TtPl85RxhBO*^BQ*1Vx_`7ygOgnYTU|KOjM} zg_iYVCsv*lv7~!@lsE51q<+uEtj`ThjVhMzXF$lUeAs@u9b1}Usk+ahZ4NdH7CcFU zsy2`l)L=o*AG8=B^J_Wyp}KYA5t?L;g}%SyC_9 zf+VALj>wt0ux6M-DPl+@OSSgF5%!5VWSp-5Gjy*U2Zl=5)OR2jO=%KtiJTFREHqe7yYh!k(Fz$HVmC!QR6XLQDH!*Z8vHHX6Ln|ziv}Fm z*X3TGj5b<0TCG268L0Y&`3fc6tc6d{zU9*+hu7PVLmMY1;W@Un?X9l%H*GPad5n=e zRo%y4W^Rx4g11&qz$J6Hx2Afn^-QN{L15cfof%Y7c+4|Zo+3g<&{mERWV3TYVL_h1 zLcE9wyt}*OBU0Z(kOqNqt1R^a48yZ4dTP2&xik{TqvkdRWc`Lab#9KiS|E=;^OOhO z_8FV_65$2Ir?+@xVaI>PpES>94b@<`AqX$Um9YWS;23OkWm<1B`3a*zDIV&8FF8_w zHjJIOm3bAi0|Mzde#+5>f7Ow>SaE);H4|tpK|1?YE8K#BQkrP>#`uDVDZy< zTrW0KZ`eMv0}l56 zCG)?o%S%cK1moiOMj~)?Hm)>XIzVDs;fnHt(o3=)%ePhDcj}m8-lO*&h)0~xj>M-b zusMZ3ebXiFpTQvLxq}Sjow;SfAjE9U2+)S@*FEv$-vY3^==)1lHJN>NmDS@DA|41+ z|9IoJ^glIDSnO)HX*$=bZD{>oo0qv)ZcWGDRKP+Klq_!eo(O=94S{tUiu>PX< zGm6UFw)4CnuamYUMSp*M=}t7*l~RcsN(NqmE>p6pXA{R#PC=rw>(3|E?& zByKY)tL&ZB89y`o&D|1d7m@+UEYT)cOd3xpoOiupG|0B%au!M6R5pupQMPmEI$YafUW|L(=_RO`9hQq}o7q?$kE7tZz!{B`AYOnQyfY$r=JYJ9^a``8NRQUPRFCZEc zC7wOdqsNyW1>CrM$h1)2^u83&#G;it+SMFwzvl^e@*Iko)X4cGS8$E$r&lX+_yV|c zQMfj~MB$$zP!gjCW;13ROcaufHR$CI9IrvL-obXq^b|t~mPvl@P0d+9#r1>SH%<9N zgLK)@8}V&*sJYjn8OqPNvtPT_P8iZQxa9m}@8&7?1harS*bo6O8Kd(hmI~k8#|@ zacGv#Ag-lshHcvJDRGH`dlFb zuF0A_YfY`VLSM}Vl8!=>8LV@%iF6Llr`D!P=GT`Kzr5Vu0MK@g^`VEan`=9s`R7jx zDXMbHvl8{)z4_@C*-=k7cUGJCd$?PmK^v6g6^EqT7~ywGiL+B@f~I)8|*fQM>>y zef-U@5uf(+f2oMaxOX*%*!Q!^s`}YC6xPe!f&(1*T%J6gnNjv-^`*-6vSdl1Gl^;7$$pXtH7R z7WuZavkXdpy`TRd?bt8HePsZ%T)V}Y!X(_m$(+9wiM>z7W$v=()sKur7G|BSiLri< z{Pjk%@$LF3{*-ZNTe$GSE$blKM>?S;YKGX`yRubI%jJNxUJR?KOMD(o=4}m@7ZN*A zuPn|EO&z|fqO%bID+J2f`#eNCwSj-Cy-|NXSKQ&Boy1d)BY?|Gaqj{_M-P`jfG-|J za_~whyIda5`V$GqMe8hxs)--)Tc(5a2LbfDzT5c_+rY6!z{W3rjV#pQ9z}VmrSHM- znyZP7xDoMmAeN8vW_>*=Jt;pD7*Yi0`qj*H_%&JgQF#&U`@Z}xAkq3z74>!I`Y$sF zx1qLKgcjv96%nS1%GU%aIiT>hS-5W~Z#b>1E;kMK6sD}!QwO$$kj;_R2T_Pvu$+}`0UbKv>;HW->dT(1 z>jMtNSa{|h6M>uck96j}LCxuGU6Bp%C=>(q2NJmNwnGqU4M3J*7cKK3n}@CAhoOq$ zUA1A=c?!iCm-Ytm&e`o^nPWUz!c;r-XA!FO<8T8yswP*sFuaY$5!QZlf4T7%6f`*( zZ28m7@Vx9klKyq(OE1UL%H?~%mO}D;ZGEf9FIvYgkoETnqmXWVa{Mogp?8^=qh?P7 zOtcpZA8byHp{1Z|-*J=5@f3j{K!IiSqbGWUq_~9wUeM%pzsEWo-0xcl#AhoLNAm^! z&;EIX^+v*n87Vm0DRujMN4YmwN#lZ|lqg&_Nhw0P7> zXRJWWD^(PsdSu)ShOi*Dlke-rge8kx+m0>FAHEZ%Y}yF_{?T28 zD+5xP9JRNh=wA`M?x!+3zR{mXSlBlMYrTIkV3BU7bN?H+YI7_~D03V>GF2#NyY}@y4%7sK8X7#`SJDU5*@LzcbBu>?&*P#^l%hoWgG= z8MkL(G{>tE5*6q}2OJwxT}VOK*SfF^i%&vT$M-oG8)Qegi;Z;AljX;%`3{TM@D1Yy zjm97Kb!5pt3XcZ93OdV6aTc1l6~Kg|UUSfRQK!W>3u=UZHtEz9y=!EYhvY8iyyzA2h0iE92mlTUOyjC@YWM&_ok51!c(l4{LqRBXt_<(O-ZzC~xbl zq-Vs3^w=P6@xESacYeSeM)Y8xdsXm^adp zmZNk(>BpAI7x2D)RNd4-KGuNE7xN?U$0X=*EIZMsn2o4i6RCZnM^p3Y1F7xiEYsgt z$4CGEewDP-@ly7hx_ejH)5D^(Klb1iJEJ@w@H%qCmyvTU!y1X9l-yFt+3eaA?U=(> z<>c=buVES1(xVk3?o3wQY-)+k9XOB-;|#QjpGXR%b}q0M&XMwuekCVC?#e9;BwTLY4g6>jOtZyV#O$mH4JKTsV7zwBp%hrX1-Ym`)2qmt$1- znAKUEV?+y3yvF)SCDei9Xv`5W=a!G!BH_zq$49q;XzHBV9qxs;}i)`jgk zz6KG7e=m0X&ULSQAYo-Q4GoG5hlG5urghbrB?*6sSM`AT$Fz$f&`);7_8h_`i9P@J zC!eoA1!ZyE^&0yFqOXbL@^SOJj!ycWn2h3n@GIB>FI_MG1jD+?()=#Cn+Uu8r~UNs zuhZiRTc>%15P528-8n%`296t~mIS$^4Ufq9lNC{W>7;<@{gUtw77{()JeA+FkLnF9 z8i{LFab^>~-C;mP>BsLIwKoy))6~C(L%V^+E@hR-7Tl0QCtTZA*~p&mkdU&&Bl8*tG-uje!RI;VfGviJu0oz?$5KOyzvxm z>@Xs9vcI6wET2v9GImDQL*6|*)I)lhZMj*$az3{vea;5ZU#tuy9}Ccg`K?Im*l1Mj zbp4(ds2ttL*=RkT%`sVb;lc1nR6B;)+A&Mvh=9TCX{B^!d7Rs#xNgaBh-oayyI8#t z!%8{r=coUF9`C^5lJ|jJHnXUx@R4 zGiRC;ULHH^l|~5UFK>VOU;CLlkEt3}MymRyd0dwpzXOLPl*?X1&}+OSbe1 zcoqG#9kzxdr{gG=#Dq}vpmP|i4b}n+wgqC9O?d)h%aC?E`DyDr1?k+=i%*P<$yf5@ zH{)=(oug5XrN83bD<_52pB1&0?pvp}%xnyeJWh*CHfL)36iZRSpoKl%OtAFgXY7dA zRdaY*wWKW=RyOhuM9-=7X^s<9T8KoXWT!V<%QoKZHgfenTBM_|tdhw`nfxSSF?5Xk znPh7%i_XznPtYM6p8%nk+&|7FDdql0116Gb<`kawr~`slbl0DFK1)ID`iZJ{)V8Bs z3w&&Buvw^tFz@w>e4sm>de4#b6XwgqiQWM4fSDjmYJ~Y-Vus5 zxc(M&W|p^c$%yE#_pFuJ3ak zGYJ~Tc&}A$eoOVypq7r?`P?PsQOcz<`p*=@o|fWgsg~MyP$Dyahj4oEY2?KCZIMmJ z?MyJGr#90=9@u+GS(uN*Yk@t%i7_^PpH?vZj*_3@mp&_>$<4^1zEe5=G=3`66@p~P zZ=F`T97P97iKIu&XH7pRy*nO#8m}&NWJ(;7(X`N#NX}7)7HMX!*qdkLuQA-2q&la8 zGOeA2Eh2eH63Fij#d+=2QB${L7=H_$_)5qmH(+V=V{%%;#UXdhr_a{KoR6w>C?zsu z(1w1`y@+YLUpjP@4jT7uabvfk?4_4?#}{0N!_~i)d`A2@=mydRAu>_?Z8pDLl48xP zZlscHjw{vEkl$S7QA8;8m85;L7fxMH=*V#CTg~TdXf7)|s>TmdN*`&IG&lP4s+s9M z6;iG*Cg7MAoCrr$$?>O;8KoYmo9-00;J|n;s_5iY?;B>ZLi&XRsOAno5&7iUBkj(( zFw#rkAy3}d6pCe4Ze3fCVo&BLi8@b`zG;5%hpP*=u{~GN-g(s22WDRNUXvUmQ@^9Q z@146T&%ncMZYG#D`MEo&O9HJwQ`g(ouWSWIF#Hs~(3iTcN8DIMT7TztTrjN)Vf-g4 zFVhY+e%fCe<<_K)$9QDR!g5)hU zF88%7(3qvZAF!tgJRPXhsdX|`5LE#F6eA-&7~TJ;I+cf<8BaF?Jw@!;pr#_A(+CfYkv z&bMXW?kgYfH+8*8NOlV3IapwwHJp8KD7OB}$!9kNOJASZsr(9TBa3^yu1i?-G?%2| zG(Y2mktGh_mXIzpD_{4){ljz}-&7IzfAQ!4tR+3p2)$uVHRsT8U#)EPIyHx80!Cy4 zi1x86p#pUDBD+IZ$n}a@^&@;8m)8s87>~Zf%anq@!Jx(22^ys)&!n{36QNm z9~Z6*g1yXU?y#Z0PW2cc9*XUOIdrq%eYi$?K0mnYcgo>FKPamx{>Rc)_|yG=;fooj zyQWQdPtQzD%`n|HOm|*2-G=FAx;w6}={DWneO>qd@csP-=XLry&pGeU2N5hRrs}kh z`Na6=gB)l8hvYJ?22m`@dmG+{_O|ZLbhKYaonmXf6tC^GrDLSz-b^?CnyvY6D2BFv zdMm2kZq~Djq<^|#W@ky1hnAM6Cy{!Z?$B= z;bK$cS0iSSN9ilhn_s4M8dQ=?si3yz1lfAus#9{h15LwpBbIRFC>^fumx)u#r z)V|Co8o00jV0BVqwPc$^wj7}y_>m{WTEK}foV6u&+0_J3^j(r2)xOvt_dG0dG8)DL zof*n|w(QL$eu?g&Sk=}Pzf>u4eFuam8;V11*8yl3uJLeKj<=BoRrLx3ZGA_7U-dn% zV0|%L0&cAsxTo*=4IS(Mi=h0lrU{d$rXY_1<&yts$6Q6<9CZHf)3!k9tdYY?tdG2BmIFA=4uCc zecxRJ=y@$=ZF99-nU86z{a20ZD?NA3y0FVpRXeq9Dm9nPQOk#bjGeg)KOhqOV`@Sf zht7JYt6+Vn;|kuv97(cano~hCoXZaUCc$|dqYPN=>uiP=Re_qOlIf#o{S4P(@jXr7 zT7=2>xr{nruL2zJJqmn3NcY;4;?bM%SQNSzTm_S-Xu2g74h_DE2*aSYB&65)@|XK-Ls@vJJ7oPe!{ABI4*9vilw-IXRP*>_PPu> zb6isf8`U;mqa3Bi7Tm z*$2&3&lJ}b4zmcj_Z~G3KqcE!qB~Q1qV_%wH&d+%L%Am{TxYI9$3QN@8OUAi1U=OuP&nf5jX~bUD)DR zZ7H5xdyPC7xoT>5kNKlWMFpkXJPwb+Og`}hQXZHK;?l$;qeFssHF7W!%o+;4W0>uOGK*j#JYs5WLZsbXNP_=B3ypNcnu{ zdVcDt3kXr@fPu4-QG&SUXU5nGJr#f79@fM-&DFdwn99!4co&iCUg)0(^5g zOT+9#iKwDtygYUTF6&a%i^#J0y)XSueIHunV9|EkOH57RX#mM-LSw86%CyIrc}~ zl1!b)oPubTR57}5CXIXXb}Y0~X)Z{L)E3r?7Zz+NvcRBKDU$ghx{6A?>8Y-Sx)xil z_r*#6yC_24uRVE~yXpS(dBBtEnVclzELPxBU*f)u&tW89=SCjN8`Xt5{&8pxC2`y~ zD;^da$b!Ir@4arqPP_d~t$Iv7yB2qf-zru;js38F@A-kx;PtLKweQm0YX<(!6}+GIR@Oqo^YmvCJi*FJbHB41GKjo=YW8+vf1WZ8^C{EB#7KSbunKV6Ux6$B zqD-0`K$@>(YonTp3e_1DK5(aCGe@kxq206d?!-%!wcn>u{ul(SM@;&;JJHstU9J=E zp+jv23yOpAneum@zwAL5hKws$qW)v`l zo(cY*z5|YE3+M?i-bnN(DFJ&*6I{ts)O+u6y2RO8+x&ruVvo`67)<7NB670(aIzpb8pWP72D)K z`6JystxHh15{itn*|Z8P7Zp0b7+opwIo>|7`Ti!IiOe>|u|URTYn?fKJ&AlU2Bds5 z*a&;oIp6o;$`E0CE}g)=w@1rkAIM5ynp78*1+_eA)9lOwNsowW20f?^+f`p(Ef$u$ zn$zrK7H*@%XdtP(cg^AVQ;FU|6x^vyVI$g@Hs%L;SVQ2W+(%`s0hq{J-w)7l=Ky~J z-`pQb4EJWoi+O~8L+lix4tkuO?N|H-eQZsl6gN84 zTr(zm^=@HVoQ2aptCSDrIW{}WiS6{&W57N!RKp;`uZ1HVkaI0^Lw6F=9V_+p!K)*t z&`ifKC>|rI&t~s@GCPb5S6pC>F|k3U%nW~sv-1>GRc{nv{A+Sf(bO&PpE7AdUE^y( z(4t2YJsFkrKKqxkYIQwMj%q5a1^wLiG=G)jS{Z3lj5%EZV5^!N-U`2=L1 zjL|(UqU8IH^OGWumNLg5{T`|#KNDtW{s=ibQca&xq0aoa%u3=0-dmiU`}AL)7d{7E zraU6$u>QS{*m26p(Ta_}Q{%P0?zU`H%OKM_v9@bX+ZDkA-k(BW1Xx$awAViSK-I;X zdc?dwQ^Mz*uPsFb@|_kD_NimemVLLPf`>wxl>7RN2Nra!gjY`3FcnFD?RceL`N~cC z!~#=&aK-wor2h2(BJSr?#DNA-#fiv))wm~=v1ZxBZ(5K$xC$#Ra(;XqE^nlVf`4 z8}G+UnW4q*ydmE_%3RxTke0Yf)n)RK43Ui4C2J3-$L`EWVyEO+0W~-8+_fy+yFJ{p0P~}*VItE5y1z{>i;wke$pIB z1V?#d#3>{{)WszaGI-o)XJ%N|==}4z$6mL2yGZ``A~^Te-JkLLhm(TRk$m8`LwL64 z;%%j|0_2~ksS=RaAW}HWdMK4xuS|i6xEt)32m8cIr`~<}CU~~B&><*rDejh2jye-D zZd&(rcCOdClU@8Fmoi2S{wd&KnL7Y@?r-x*uQ9ysVqFgJRKjX1wV`QqaJ4+O7XF_O zyFsyj1pKt;zfkE{^}sFl>=W80<=)O2Zh{vVgpSjO!&B{_BU~9{vt_w34D9IRpo#IP z0*_wBG@H4lKXx(I=qPrK%vwY@&M^&aM&GY(!NOj^Bbopp01~|gkR(K!k~@e}oE_Wv z(WUca)RIQ1-4+zK&=9q-AkreDJB4^rp%xr9q5Sq~K98%#C96akwoi@fJ8FFJQa>!_ zlasNxj5JlfJNnldadZpP9@}=}09WY$d5xR@?VVfG(XU!EviMM4EAVIugxrQK;6=E+ z0N^20j4d0_H|x8PD@!vAd6g)D zQNRqdU=d2njg)5EKy!v3n7Nf^qQQ_zC86mwq&JxHwyFe--m;h&8nrspl{z{V)hz>l zAh9|#mr2^+*Btu1{`3;3akSD038`fE{w6+&4=Ay^EcxtoAi=Y6|5Ly#$M;?gcUnSC z3jm@ViPNrxv{K$&*=Ab~<&+FN@5g`mG08lY16>poF8gD2-(`T1RPf2r!~1kv0lGmx$ePLc^t(ClshtkD2e>#WpAHeJb1NBUXDSNWIg>Nr z2XfKXpsNfq^a;L0SDLJFkig(0IV(>uMe_hr zg5;bJsn_4m52gF-|CU+4Q;moeiP{TQ9J9;QU|@Wn@{zY-*w3Z?K@DJN>6F%pfBwfQ zGHN^ARq4<8EneS^Vp=P6`nOUb?v@kHhx0j6Xbv+ zjjmH0>^@(%!G|K$l*!Oo3whpb`;(-domEdUAOx|R8Dv-Gk9=WRV*#oPR@frz=X5Rj zOz+U6-3($?G}52rma2eAe4>O2Kc~5I20ZdY5HoJ9{*c1tIT^CzS~|50Yo&8r^VC4+ z=nq%N1s>%yUTW7&NkAS!f zH0kNKwaK7f9;^FqMT>7_u}&%r;Mb7s-5_^TJBgRU{Qix4*o39X*vF`nEuN_Sr*Go?V>Uf9Y=~`xwXw>lq1kqyGbr-%IDXaZ zG5h|WeTFXw)S#R4Nta&rzxpQeY;~kHfPXROD&eh?b=$T8#%o}!uQ!P2r%snQlaBQa zWz#=THt(!~@8MP!yMv%hm`)}pbEGYI!Bmc8k1kd@GMai4T zQ}NWV;YdGytg4*K8jqZ&R+=%SdD%X)bW)C?5cJhhUTr*WOfJ}82DK_A@d&0?_j96} z3g*WqOV47ZnS{jJ{fHKy5+3zqTc$|s{yE=o{M0Q-A6Er*z^8Nw)Md6-BBR%tm3SVH zd<0W1(M{xOE~{LV2ens0wcK$~R4}%E!yrvjSweM>HjzYq!;Uk^`?~jS{HE9#<$~zP zRhalwz!|;tl{?dQ z^xoHRVA$^9ysF$Fu48i?J*2!%wSFC$X(~W4^9F(vIX4pHw9#Cfb^Gh1ZCA< zx}6H0TWgipW*JV28E(_BoOP12Zt_8Heb&+N2rM*_Cy)U8zGJu5mQM`2$L|_H=jZ#! zaS+BlLiBl)!@UT3T07F4wq}6Cowec=62*q@nXZ{X+_Q#<6G>jA=RfB;w-rLj5R!#x z+Rmxh$Sf4?kC;o1A$@zx+PBuSCV^J-T+^F=)L%or4-;8;4Cj;j#rFp+OS4}BIWqIv z+4F_zKs!Ma9_Nwzn(T$swcQM{>9Fx>qDf27gwIZjd8bq8@s{W?qU$dQ{^`318$>?8 ztinA13-{+qO+Vt5ID~g7 zPP&Ctk|raFZxrf>Lz?Oy4%!ogq{8Hf-B~YEV__{)WE9K+myN3ds{wkaw%@QA*{`FEO!wUHZl@(4z)*a0b&9Q?#N4NR+QQsFd@#ZsW7-3Zfg_Tr%%bV17xEN;^J{X z!4torSLPQ2PzVvrOt-~;j{7)S_tQLD@uU_&Hl1FJ;{78I%~+e31ZaZIPjRI^`nOi} z7W5c+UU}^!>UJHZZTyejbFvS6H$G1@KI4=6N9wzwz@eFb8L-grV4&lT^S?>G<9#0@ zq55~c7)-|l!WkzWD{)x)Nsb?0%#QzT_G}5j#;66}yAleEz>lSKIHG^?;Ws<^sYJ3j ziVhAPtH=^XN3%rnr^-5$Z!uvbz&w;&OY`fqv&8Q;INQp1oj#`56Pf3`AhcYzrSHck zK2-Zkv-U95Yq`{VeP@$;^pDBIiUqGVG^`MRCOhZJe^9~LyRUef&-KxtnJ^D5UxBhl z@{I_YEhp^j#4Yg85vy$Q$!X^_$K@iS>1)%T*zK%gKgu+<9iPK|AZ)4D-@vo0XXe%Q zq~FRVn?;IalY88R71s)xP=SU!stmY4NKC86+hne|CY?gN-)pN9;)`bJ)aeC%rp;0J_Sm1FINC2N(9r zkeTVjeT1ofRzfDXp%tIpjH`f8T}?sV7xvvl^vv_qt!guj6+(kErm2mnLWMq3r-R^U z+$I0g(C1#pb0l2xFOY)Z_hpH1NHLkydvWiFMD+h2VSSQR(aBfonORvoLDW~W+X7-D zo87xYYi4#73pL>;rfx5#-tDdnc*&k#k1*ir!0(f0EGxNgf0~5#Gw;MClSPqVl9+4Q zOz`TSFFGWfWvxd_P8sNOUH_E)Pk8q;6LyZw5BJLF9&15^7sDdXn@wevp9C(|8$VZQ z!KFlKw4aTYCsXJHDpy%!P-!+O3Hr?&n;Mf%@V;wy1uNJhrenujsKQWG!Db`jCa7f< zw_m~q+Ar&nN$;uThc#!D%e!uV1+jf}(vhF5l-^-Azc? zZ(aEFzBrxq+387H2v`?{)FVVl6?t$k~`HxmKnDVO+> zd|sz8Z&;>r2rqDXBzfi=8O!L`d#*q}(`lN0j4|8E>VDS~osT!rLe?4uyUme8X?6jR zg|LXAhX97(w6&{@qWI}mbpwqW6W% zH4-gx19cq=udKrTlD(ha>Jqmu&NC%~P#l;b@8Wv>s&(yZb^fKT*#9JoBaP5L@XxHKFaW0j7cFp~f{@xNZ)vXTkbN!j zRzXK~L;cE$Uc6Khgt6Qsd!jxaX`k zXbX6}jeg4W%xAOTwxd&?Db_z$)Fmc0ZEFIj(9q3Wc04Kt`SltY%(KrTb}*=MOa#1i z4_VHg&nJOUDPQXDdWD7Q@PcI%@+MdPCa<@b1U^oZXmVn|fd}7z<^lKAiU#5eGn>#n zR7j(3GN!n`j=pw&y)ka3Ylu!h<6|52($xr_yIO8LLP%U3!!o*#t`W$eovC4I(b9*Z zsOi&~bEnALA*B4JoMZ8{t!`fJG=)Jq;BH=vhT&4-_3q_k=hIeRRjl>Ws3p_=daFFG z1QLv=V{!{L{|kwGKiWZMjK2`BfmXqvNZ3M`J0Nt$mj}j@N}`H_K1e^IxNpzG9peE# zLhAiY;dx8f7)iXd{USR*CV0&T3x0h#QqRmYwyhRO4G`R&tnbB6fbD=Jdysu{G<=*@ zbL+_!^!_#q6X%^cj`WTTO^!5Z3hIEwSny zUu-fp`DzaC1xQ&x*H``)IOJP>xc-K-&;Iiat5f0st_%x=>xr0wCU=gP+SQ5%oBT8} zLtv;9(+>#nl7cJ=6e893uDGPrqP8KC3GZx$(fmOzlhC&2>bDXXA3v%ru-7vYpUU`< zUx`1VJynauWR;a$_fjyC8XK*@tO8@7|7A>t5k9Nl{}v!=nC6cgTUA#&a)y#|NX@F3 zXOK5#%~s5o`!!ss(rAhCi-i!Iu1+-F)+9y4^)6nypLX1jpWw)ZF(gN%YwU$j?!Y3J zNeZxSyeef2j6ZUYEBUVQwtB^n(YY^^;xAmyub6!PNovbDMnGA;YAdO#Ur_oDtZqmw);6W4vK1 zYYtr#-~SMF-F(WJ2-?r?2#goik(0_l2=`3GDLD(rf2r@TT~E{Em@g7#RD;JKd@P&Z z+Z38#!vRf!f`X0mNo|G^kLjZ}UjCTL;=)3R_lLxOV$c!bu^J`ks&S2cSih#p#*a>+ z&wsz?(u}0!4||>US?q3G(LN@fR}$qPE9DXaZMUC319vyXS%KQbWq)MRz?1oyM#lB& z1xXP;*SIcnfIpBuzkf2y6i08L=D_8%TtcPTkFWV$+#d|>&DWge7o;#XOu((gms%oW5a+lGzQZ0qHH>>Vy3b zPiWJ{{>>~F>!-YJ(k{$;rlUZl&*TKU4q*K@=O=UM@@7n2SFcgxVhtUXNl9h0m8O(w z0K(Zd1ta@zHWa^=VR{NVg}8olUGD2OmvTlDj|wbRmkf3^;CTZI<@;sDx1s8qj3o=3 zB5L0y3R41Iyi+8LWe|X zq`l{2$El=O=<8Q1)LX&TWEt|T*j$BKou}DIcqZkr6JT&ev3Z8D{y^`Bcc*6IpXx%Z zN|ov|gXChMI{YgxPK2kSWm7fA{E$FxxSasJZ)ZtU|LHn(aSrUo zH?-UH=EFhB1zcm=OJg~Gro6@ey%8T?CGqi_a8HvikE9AvIm`N;@39v@iUfJ1{(u%w zbaY9B#V>za;+S^4DF+jBC6r5!#!XOU~;XyY^Xf*vtveVX>o)d>{bFW!f-%V_>hy8n$^ zUg2m#K$vdooq3t}aT1p22zI}YRUN-o4F4kd=z7LGH21+rcFYIL+Uc$xS0xtp(pP+* z>zj5roMchfgw|c&!mWf?T@zC4{Hjg=N>5yk`SkIju;bNd<`Dp*aJym5C~~SUn8r;^ zU;reKOp`L$G{1z0e$gU(=cVu-!a0Sio#Im=Yx1Z`@*dr$dq?%#-;+W%;}x{@tA|L$ z`hwMj7<)QhtRL7fgf|TEoMtKk03*5JqNC`|0cU91#4q99@C?8T`{}nidu71!(e4;t z?XiyV{I$fO$^y=Y5hk=hCV50M8&*toa@>R;pHm!0`7HYZ9?;bRaeWNdRHnujz0JS= zf*Z-oyB(!u7LmsI5o7q+bf#yGTOj=vEIw2z399_YsNd~hd8SscOGKA~ErD466a|OF zH|*Ghne}V%lAwPcu=eM8k0F#)4`a~-8gMDox~WcgrE+5k82hXNe|mq+{1R;x{s@(@ zdt596gphuBZoMa*&4ExCGny<$Fu6C2Y?*eoM9hYwk4^NeN2$F!1#+?Ai3P6qFq6UOg4#HDNq_5n_U7unesV)4sgX^+RpK$`w5be5+q(_! z3shXM>rnsO-|U*kj^qpXtBkhfJnKV*?mbG4xV^&;BC{8%r}1mvZ*8S%K507!`oIF7 z5ZvnXZfPzZ4y)nB!yzBfqEs833kg!bS+X#LZKKZ*y+o%uHnU;rpX;ZTeamM(AX9$@ zdbE|8hqR5UTp+X>rClz_rl8FWtjy}qFeRxAePJjxijkfK`X5?)h9O7)8`H=pWMk|t zy&sb(yW2Er)G*h6Bu>{wd^#vZ-V%HB)XYJf9KPR&yGoFMQD$DIV$iiO^X|WYjN-p5 zyv|;XGO1yfTceb!yEZO3}QF?f#?baJ09* zZBxoO4_HxMN#bLA|J4Vl{8o8jJg9Zb7OAZkF(GT^lO)i&p7Uj7=zrE6Q|~nvotCw} zv93n{xfglA52r?)!&-TTZS7>Q)5g_nlAP}=0XzB2%$wRJZb{ISNM7E0LH$U7AOFo(WcK8yg<#7IS{Q;M!g-%nqcZ*10B7VG z&%H~ECuTtG8N~o!qx%ZD%zR-~ylJ>k)tQp&(N|@VVYk#d#y#NTuVuuQXw?8{H9FjB zXxGgra-@Y_7rRRpGZ;n8DGF;DA_eE^c_Ol#FQ>*0T(nhs` z1LRWK(P;~D$I)Gae>_y>r}#JPr~l|cq%#ptarBE)X8%Ox4Oj-{#`zb=@*S;h*}dU1 z=697d83R%%=Iwj@QU&73<24N$bM9WfQ_Z^Ze|0IpBUK#V*RQ?(lINMvt9zgcns}?_ z{pa`EOU1f5&JvdXLW6J(nl&TB6d0vT#m0n0eO}1L8WUy znw7LHO}?cQ&m~^PzYZy)@p9F9w~i7AjS#h|*D(mv``n<}tNYe;9|O9{X}q1L9J?`) zB#J&^^`Gn)+V?MxxL16&a7=2UqPFah?FG$w;qZ2av?8H%WTCwT>L@?N20?~tr}YsL zuZAb8KbMR{XXRb{rk8$*T0}yQ9BSbneW!@y9S4PTCa?MWn>Z-hL@kXPVQF(B912iY z_v-bpi*EVuiecIbL~o@CLk+1h;4|xM@v_ic$EOB7gXyxr#g&mqz{jOBvdjAMbd8BE z8U+j{^f}KvZIXbjRvzU|G-=VJNl-J1nMU2TH~)GwT|Uc#G}+Js;P~U{qS9#mTWtw6 z@_WjC5dVcxt&6*IbeEqOe=bS4a^T|pKOYArkH2f?R_a$icrU?j{v7O}b98Z4Ic{%v zox~yGIOlW)tA}qDsJ2QyNuQ%V_t`5*Z|VP;`kQSdmbo^s(&~aq@iK&E&*wR`c8#GA z&BuDGc@b=VW2@zv^&dmozj=5g#)`nsC3GA-tX=*Zww!WAO!D8=_I5@GB@BLepg)wK zv*K5JiMX+6;L5IsL`k&Af*X-krBjY(4ED@Bd2EVX{QAI2V;YngCcf47LzeRnAa?ls zjq7%AK5)|W>%lFUYd#E)Z=a?%dRpU8xvV@2KXGVuXk*V*#nZ%atQ;S;r!+ITkR?I9 zF!wE6Pi}=ZyjDS4VDRB!uxR~3l4&YZ8)Xj8v*pyuRbt++&nAp%qUECX) z)N#vb&ktp=!j>zG{&shEbx=z(32OAQ?-m)gDv#`K^jR~SUc*-vu2<=1uIMM^w^M{i zCJRqEW~D$TX1kN_ucCaG5-E-L*Y7ib2Ja|Y#NS;ww$PT7fuZbA=Ne(l0VIE>0Aol25(6MZ9_g=jk|ru@o+&Gf#yNM9_>S@VyYvB}UpLZ*0bg1M zS(wCg4*Eckt4H^TKi=d9LbQxh1hC)Ydm0=0mjsIQ` z4xs(%T#QomMRFoEa4cj*V+NyG8`W+J zw>-8Ad~>J^k4j!=n9441XOvy$*vpg1Tk0|Nm8y7wU0$%TUw;oba6?&M0uJ`F+B$zF z-XYr_l91uzGZwDN>82i4IJxK*g01bM#Iz6HQx}<%M#kc^NoVWiRkhm}!!5a%-vzhd zIkZ%tbt=CVZtu}Cu7CJ#trBpyL*@R7wfa`<*49RO9Wi;i*+O;b9p4tevC69BYD);Q z)6}OuS9AQA`yk-+CKVQ{0m#;D7VTl&2|Gj)+}|}A1kJ7{putj9CStI1n>mhwPfv35 z9L6{mW$$hufF|4+pb7H;8n2K3=YzW&6jXmHc9S0c4^~}*$49Wc9AlSZwSkXxGj!&h zhis%#7e|sGDsI33uuTFIw9QT45AePFkLZ8*3|((e;X)86`s*=K_-JvaZs9Xd@2f+E zG};n1-WVPZkJ{k&6SN`-im8eoRkeP2IuEkvGB_xRJE=C7*S4q+V zSDeSjyOd><5%^{Lro+AC-K7*coRusltip`btE6uV)a*f7LutEt5c&n052O^bmdc5* zQYV?ep>mcRE~XO&7vzVu>lxB)2#>te;Nm;Z9|l=J z<2DaviD!VPK-AYp_FS~GSINo1)P&u_ZGA73cfI$eXPiSZ zeo?u{4LejAHe|okPmP(|MM-vl9H{5V6?>_#>d*C6eFK0c4ASmfvK+&{pxsqIgvh-@ zr0;3gU&3?-jVv3O!ojnO3pGKHzS;~$$^pOZl@0b&FmdT^G)&!g&d^CmUX$`Y;Qi>} zqjF7YKBbC-+6K`dL{xgqpYrjOqo2sQ!kbCPGFdk4q5mswhK|A&XpeN9zkC9l~v+@z2N#__jMLRx=fWPKsIhX$Cp+M22sZ3tbuFezvCB}E#d#1b_`n|l99XMpn-7#WiHR#)|NWY|(M z$VQhmKk+}K^FABB%>f|Uq-LI~3C_fliZHXF_+o9qhqUvx1c%VEB`M}S@0&g;?H9rf zeSuooEkw-pzy|J-wMwgrWa!=IGyyk3JC?R=@>y!VP$nPNLYS!xc#}=c=hT&yLhkei zKQ6Us@t-7Ekar1B`xsjITKjiGcjQZ9*MGiD63c)WU*hK?h`~@?#5Qdxo+MgJcKkb> zN01JGHM(Wma4T_(C(Z^|ic{U4hpoWK!t5dhdw7LV3Xi^Xythm@u>Ff~a?<36=;TAw zd@+r?0D{>rsq>doyynnAbUclP$#z1nm7RSEQ%BWwLiTkL=P<9JgirNt^`*kFAc}ic zclT&-#Sv?EG^~Br*z0Of`E&a2*du^csB7&P^36Wq9g$}!EcXWlbSnn`TkBuAqh*>p zv8@&X*rdN!#DMiuRm&06WZ=PrbIbP#(x9Giti`13OY0{tec0_)-ug;Wh>Gs_pr%eW zuhaqU5%Wv5@juf7rz-GRS1FBJ`DhiH~yz-Wk=Vl5lS$HX_wV;g*T^i zTwXkHk*$}(H`|Z})MjaP;+FYMW9TAGNU`8!<arj^C?)o2!LKW}PZf{Tdrmi_Oh4Q`n|d*Wa{J)4 zwFJE#Hwn5bG<`$gJf8}f*gK5`uFPz#P{+4ftDMi*IKQzytKambeT=kM$P8(MMP4%c+&R{0_mpSJf%z_up_J_YJU6Sq%Ee-h&pkL7%TkIQwfUw`iemncg834=dn< zm1NhzBPhGvsBq74O5Y(9$zc$}zi$hfhavec&bzw4T4qC`7(gT zp`9Zoau2sk=-zFEWywglC7=28x%BaZ>*M^l-y2fSuvfS@nTNwpVCX&9GB_}gJcvy7 zNj%O*(A3a3qdJc3vDesI%-jcaxfHQi>^^Td7;gBEce^%jKhme-cfo^8#Kb&s3clRf z^89f}8hs%Eftb$(;H?+H&?%S*N!pXYetRQvY2rO|y+crWeccyu-K*V`u}t3k%o1Cf7x+DO(QNl`m?KNliP|`ajN*4y?e$N+L+6$&WOe z*_Zeea##?e_InL9879;ZcH_9~PP)f0LC-jf;<=%J{|1=IXn%Y2_x!+hwNy0rXl8EN z5y;g0YH~jzr{udjF*WD*w02lgD-56iRHVo}+!fznbUe(RZ$%V2}~ zZU~aLu%X96Tc;pAKFocrsR0hCIfni!f+EW7f(qv;g#j2N!4a9zm(>!?_SxxA->81& zqKNJ1ZVxaUQvyn;JMYJV&?VS2cR7%XqO ztx2|v7+zp%i+m<;(>N!1WY>8?e8~4A9QHh=f{NiONAEYQ8uX9N6on-jdO2c#|ND#P z34PV2d8)*-pE`*=9N@A(n3=?d`n0&$?z+@BVsk~e=J>vWQ@?*)^bPmVKl-mtQ#J&p z($M82((23APn*z!%_GE}!85l%rklmhYwH^FPGNU+Bl+=Q9}~&RY~f zC)K+LzHAc1>wvFgj&1MC)Lnc-7C0oU=r=dIimSE-(P)pIsL)gKia=LDLW7(<+(_z-a-_-h>)O+j6o0@RGr>E+5wFE!Oc8Q*z z$VWxb8ZEfJb-|_@mcqV@xyhJ1#yoAQYJ-)IEX0P@eX2KOW^MI1 zAB7PnBa)z`V*;UI+43N#Cb`LmKw=D^A;s@HGad{!m66QB+XE^Ac`)AKXsZ+?XB3GL zbMNb%`mIr??>!%_{z>xY-k@hk3Pq>B{(qUs${em=r#0I+cB$acTrh&x!GDr zyIbDD%U{LvW-iX0qz$#8B* z{%JzA1tG#`{nw$Uv#;MOa}jkWy}$|J^HVh#8=5$tIj%3F#MnayEPKzB^N{FY_c>p8 zL59_hx{<+{$JhwoxYK>9us#FFH4wVnPt=cmPa093fk%vaN71cx>;W}dW%wJi+TbZr zCFy(3ue7^Q5C?VgVS&rPE06EYi0g`LE0X<1I=6V-MzLaMHsDG0dp>aG=~4zVwn~BU z`U%r80~q&VglqFmD9|`iaFxqF!)segm&1gx8~N7Zl7QwgcFZQm<;o8eJ?AaMLidMB zh?S2`z(gH&=0qNWv!cfYFf!e+GrocgUOx=W}M_{dHygNYUg4+dQ3J zf(yC)hHJSZFRXNtAobwj}= z5Q`*q!dXo(9vTsRzAEa?%brkp|53(Hv*V#h_kP_|k>K_xSfM62^Z)Xn=Ef}e?+}=J zM{?K%kEPAucU50N>{N3Qbh2VSpL-r|m)1Z1A~8s+T7E-Q+K9qKq_s(i49NT-;>SJo znO`kijw$}H`BSWuvhV;cCjld^r@Hsmvb3mYyQY+<7Wm$tri7OOh0#?{r`Pz}6{2?- z$W>Cxdo^O%p9len_Z0w*rx3*NxPbu?j3ymz&AjzCY@Pm*QyRFr>ylgjF`YU=fO13B z^_G3?kJrLGh=?5Xi62ejejSzi(@QIgEp!xL4g$Z4uq++z`kZ+mJ2#+>V9H8wyrD%M z7YQo#=$MCCPwUv(u%1Y1^uI}k`mk0XkIfHDL4p7IY7;0_4i^37@t) zS^a(@7@!;rd*h3MWbg&fK?|;jUB9~d;e%(9@mCBkxe6}4M7wocghvdc)hgvQxi)~9 z?*&S5tLc{6LlUA!2u$>{>v1NL>YX_NiaA(#Lbh2aHG?WT$CohQ^nm?ShQQ#+zy7%M zM}&WJix%lM8P(@LF<|@LQl!8sxmzUuPM$$H8{o}_KAhdtIKw3VAP304KfyRMuG1ry zJ$?I47^>xR-=4yL3u?PFFduII|)haTLKR> zJf_{Myq6 zx~gv~6F}lkX=K#+ue%x4UpG5rf9xP`kPSbGo3q6`3v0CwyhUWUhb&7E#9Y~fYQQip zO=g@dhYoc;H1XZS#>N(Zv>YZ)(}9&&hgE7h><_bdD<>Z>DVsJ(%KfD+ zhu77Q#_b`*s55=0Y#~u z1)yZkLphY#VbwVIMeT^mr9PK=LCL+bcZ|R0;&*Tc;esoi6P#MO$C=5dZN4iiM%5O& z@hf3mYYUnC`fkFlxIeui|Ci4BEL~;{39HV}GCaN&dB#e^F!SHS!u1}F952lfQ9zVp zAYRh%p8n4MIGTt{*S|FEs2!N8##4I^Ve_labh#4)a zfwZb=?y|rjx63tug;u(vb@z1?9JY><9_s?PWA8Fl^Z%ZQfA)L|FAOFoj&M1hjx{G6 zjVBeVQbv&}I;h_^QcrVABAYO?LtCH<$EEq&+d3tYznP_@02>3J`Xes;IVTOF`z`ul z!@CrY3xsV#Z^6Yr;SsWM83IZqA^e~6~30s9-?P&RSY(iqC!IA#Hyu}3OR2!&L+KH$wm;+ z0f~A}^#}}>&jM}ydm*QJz^k^;igSAwNAyr3MBMwQ=XyND>b!qq(~%7NAXBZ?ps4QY zP4L}Fbv(!K_QH5YmiCJtYq+S5Nvr>wo-f*S(@TTFW9e+#dbtETfPMs8qg{|xlH-pR z4XwFrpY%fW7z*Yehmi!?i{knvPi6E8#92byaHb%y6{~Z4!Jo@P+mD17Te-r-`kJMM z{1sT`mXw;ln*VHs>}Lxy!716KLpr^3_;IvoJr@LX1&jDai%0dO|8F@@_vnumfGzn& zKo@;n(8y?4N9ovri01!Tx(2pPyDs|FWMi^zYjRC?lWp6!Ia!lklWl9VYqBQWuCJ%} z`vvb1$Clid*2CYHuDf{0#pGgxSt7IO-*ZDf ztr%TclHUK_|Dn9(LH?5f$);U`rM>U|D-jW;%LMkcFZyO>=b#)p+_7`j)6 z*|{?9BS2EXyUj7N^Rj=YxVzWVY?-59|3k`m87g;_@pq6neLbSF1pi>XT@;l0a_lvf z@V0H)m&j#t8~)=go{xuQIX&CJ$^Z~l^ zA5$SEz{XcVF3wK+`viedrUl{0+o>L#800^2i&HMYjfGK8#Ev%GppjOK-;LJPuC>z& z{16gzi*bh3Hckyh6tq_?S%|~$%v#(!BAMXc0zI2|6Gsp?|d(PdmoD7w;m?{xGtpJDBmmO0nHE~Am0F( zoKRAhc)Vrgj>H{%COU&zur*v#uX)DCV-H25zIFz}X(O;BEU{27I-wWZ0LB``v2Hn5 z1+tc++@G3lLSDrgS7qm=Q?({7bdTT8s;d6T2Ak`q8by=U5qNVPU+JL@Jq8yH-At4r z_Uzy)lS}9Ac$onxp+Nj#Ktb&Qr@@3LGCv(}t6zApb<;{w*Wi2d32;Ad6MO?rysdPtmpQwH@uG@;BMf|KJ2#V7nlKoAPBNJI#LWHbfsoyeFx93hxN zGt8j2tG67$9_p;LZ#{OGSLiWDJ!U031a&sTFI8N~L}&q2GiDs8IUA%&2H%F7s-gY% zI-r&Hz4jDRSFBj-Z0E#)hzHmuqUJSN>1f1HSsop?UZARvS z(SdhN=IMr0dk)G$GRE%pv?gD1=K1UzvQ?Bz(*x9-{hexa z>LD?LMYoG*dVf$jqG%->s;|u)ffv|o8W>Nte z?QuTc*3D|Th@%AED)=R`p!phPM5F1{N=;(_ONLYv1K>|wroZFw+g^e|n4p1L;=ibL z!kueIY_WN+iw4RZOx=Pge(DpcHw`2|cj0=@(^6x%H%kOOc(II zqE^uZ>`p_s-jNq0qpqHA&a|mlO6LI1P}LQx7j*X~u{pHffOr7@kC!{QY~&?oQlHFRa8TlPIRW1^=9sEzFoU$e9-oM<>kQW7pd^wYUHG8z& za!8}w^P+S8TbPDgfR)l@Q8Ee!ev}5~MT~n{wZK}+tV-R>3p_ob9s3);qnX@8N(yRk zALY~x$K6$1(%X8m(>(UEey|p`5krWy5^KA-Z`&*Yb@b!HjqTLU-dj(P7cVyHrq#oh z{rU+LR>0RFKsOkO|NDmzCIYHYSxL}B{z`fjorC^3;*d6ql0GPf!z>K_H+~dsCWdXC z3nKz%JZybq%K-QnDnpC37I#X$wzD!5T8 zYpasZt%=1?PMVC3tJ&PdDQ7XdsM0JXrWx}Y^Qpj??_PxI#b2p=Pc0zRRJ`lvWn+>i zPmt^B!6xP{jU6sDw8C6P42nQ9DEBhWYxaV>AS z${bW*;|xxSIHaa8zPe2W7Ei>Tsk)2{LUgO1fG%;t)oI2l$L1cQzXd%>n|=cvz||be z9486Z*YP|V)YWoh-#{a7Uke`O6m02D9D5*u;@6}*1RWS-3nSRBw7eD;%ZS*H-gbE} z798Q*xK9-?JSEmEj|>W+0mfXrsDRqp$x#xGmTC4{b+*8+y1n??_X!@Jk)Orwj7!-+ zD4{$A;7zPI){ToXo7=JeiS{z~SQxyi9h=vgZS_1M+A}~}X^So0990ojgtJQjIfL3Y zh4Nvac|LzGavNjrbG{Qb=g`Tzq6*2!0ApCXBi{G~c>p^w<_eGks?RA_UDY4O8X?D} z?7sfFTx__1RrAI@4EGps&3}^Q(a8Nid6HeUpdt($CJr`X=p7b5ER?k_5smT z=;5V)DzBf4sBnL87;V~rVOhGZE$F~TnRNPjTv%00=t-%xa&Q(+s9EvMd(R}fD?@zX z>r?Ecq0S1^_A86Vk*ove6XmOgK2Tn7n;WWbiVSbTy0xw~yIbu5`wD~H60V_T;)}w~ zO>s?0IvuF|{H%PHKH=#SxL@&}H8K~I7BAZ(zR#_L9txMo3K~}BOGV`HuWGLfSj45F z?qYdckyA9Z->kK^Ps3yhbhV?uEjO36+|%+bE5T1pXg@X0rk@`Gx4V=DYE+L5iNi^S z7CmJe8~sY9%xBEtu{0keUE(yzHSy4Qv@-JB{!_jCf9Hw z(5{__Ac&Nm;B$j;%~8E3LQkfkG%R1TPDNkY%)7m}{8VYqi0)ZF*S-um=1!fOzu9F! z|I;+6prG#{IY#Xuy;?*fF#S7LPA;7P@&n1>T*j%Q?=%Zdtt(V*xpzcuLk02HaQmrF zT^$(!CD}eZ7jf)fcj|vT*rdXA+!#;#SDj89`XERCu{rLvXu8WUnJU>+Rz znPYyE79a{Up;fm(6udv|v+dKt{RcJRKt0!DjFc>zblEhfK&=N&Vqbth$f3j)fE?9V z92%3 zS(ONyuvWd+cYf$BO~9Dcn?)DO(|An|MmK5K;RAlvrIID~e~)t`>>gtRNBiVKK{;}Yrs3KFgzZk?=qVvx?5UN;6H3~N z2l`hA)07a9F}(%7Oo3UUh!OzA=+M`Ym3v(SGy|C$m*C~EH0M#atVh^wI4>3!TK04z zpFqO{1R6JX_+D0}FoQ8-iiz~48QBIy#S@SG7Zy@7xA*E2>z_^P_k8{+2=w|q&TW}a zAe(=S@2z5$Z*vP{fkKPWnexUkAA2RAVA5*$K zh6f|%{6#C)ZU~MbJk$G;%BM`sBD?#?H4-%elQ;JdQS|#O8^YaRL@6nnQ--1b96>8z z9+TMr{+h&pR9fdoUgJ`n-4jFshzx6-o>IoAZ?87()5cpwirV_^#rdxq@>d>f?L)9k zF$4lZmH-GeZ|&gvD*s}1z=bHSWWFuK445PGI;OyS3fl<13Y@r#ZaF)8PYJBFt^B$n z!odK1|9G-#{r3dpkGVt-dM36u1MA%0QgkWeyfD0jkO@6>)CA43)UoI)i_0E!DQuC?mYC@qc z=meEHDy0Ou8tnrl=b7l$_d*(37`1M!?9oR+X}m~uh3;Q1DXxI%>xRp-_9-e9N8NpD z?BU_t({t4iB`tQl0_)`VeAR5q;Sg_bL^w&lvh?0w_Tb)rnQ~D@#0vHab5NgR)(R;I zuj3C@t;*>R*Y4pK!w#<=Pp&Z({KA-w)lqgiWiRC`)I5@Dv4cufM?2QsSxvhcYa;Ty zxt;@umf6cn1*B7&0=MB4LnY&`=;8Dp5p<5p$X4_}-DU3TDPCU|>sb{E8*|WHN;I}z zHgR&fdAKJVLhA1Tq@WL+^K<8c>YFooQ+HStlv;#$a~f>&@w6LrC!P;Dq*m(m8)5Mm zMTSYF3*N-J(v^kb9o{m+f`)-~(}BevRHI(~+Vpi>f+{f?MVy)uQ zE5WPu`P$`!saJ$DfdDuA}>dwDk9(=+*(c^~4 z;|0FUY*}8h`P8jKFd8+97KK82Eo2i`BlwnSkF()T67t5mOFgkHao`=2%%Tm+eUh61 z*eAIW_krs#$jF`Wn=pR7TW^HQ{vae;`aOrG`X*c(vN-kF+ac!^MX8*Zm4?OdeGMG2 z!QiikwTr4?Vx9K=@NOoJOE;p<$@WzuHq&K}P~pxk=wtw?2FoV80aTH5vS`!t7OyFg z*N7uPQWe|@b^VggVp8$H!u1Ex?@-Ty)g>Th=f7#*n$dO~q-jdaRcc8yeRYC^g3b92 zkE$04ECR1|X<{j{Xe2t@@!B~snMwUM_j9mMx!5`+@{9e^7=76|5>MR=k2ZpB>%~D2 zuEs}a27V2-c3ew}hMC7G@4|G{ zA@)4hSlvm8=!0cV+4jinsW@me6M###4aaM9H6+mZ+(lmT1WO+jv;2c1bYK zf7XG5im(7bhz%j8!?a=Ss%@`wlwu&3TI2zw_#diF(b?d)24kldB0Qx|8<{^A%KE3E z-JzH$SVkQcaXa=vxGpQPuJnk=w~F zS+}p$5FmF#l8i!Wx!N9xpT@<3Fh|G$7bNmdRP-$&K8Im@o$ZvF1zNNF*AIt-F7hAd z_vssbQ_gt5(O=poq58tEK?Umm=$6SxMfFXZzzBP8gS*8X(m~$zcoceT;%D@g|f%S^JSQpPL4SfyejB&U`~6z3ejK-?p*g1q}zYJCPYizMtm0dfI=fug2FsgFP^A)>k8UeFZPR zy@6a@ar4Hl!Am5I3%L1T#ntL8}0@cAS07zgu=OFqisEVNyvrlj@MLc6c& zNl+uGdD8(<3IY%UC6ymM0n#0oL&i@uDLkCF*6|SfpXCqo<~TEO$@K5W7K)P$ujbIV z5=uK?N6)HS0F0y#tOC>acc6`H=~urbJrv((<1@?~B^J={V0a_lp=I%HEJ4>$AbzD9kv)ab!)et?8vh>AGqd=f)NKV%u?a&{dW=9kmmu{{ zNVvSFZaW~(+vMI?5L#{;*s$<}0}2aTBf(T_mYT(h`Hp>m-`SOH?Ug~2RY|v}i&RTF zLIrT$uk1CT1A!_j{6=Ly4j_8NLFp5ZO4|PPc14{74}tBkj2&z~gfY)`bw^``y?Nh^ zLPf-cyn^@``!ms|AyU?x4B8cHGS7WS-eYOUr(KSI+S7gVTWs(LxeBOpLzT_`7_p0H zAUglJ#FTDQo0)Z0xf$z3KFhw7$Htz4s{(o?ujY1f;dpSj*^TJ-@dWXoY8!5&9^C$gvTI{J=3A2zHu! z#6mNKS(YU&*+?H@TJ}LmgZMv?#|L()UReptx0_<^G1ik2Hx8cdARK&D_#}ANPopYT zi;1SLZ?EzNDIsOFF?E-_Q%0%ubBFj6@<46)>m$_i78CRtH9VFqVDk7K?nTd`aH7|Q zTx2(?$eo)j)I3IPVTg~Hj}Kqc?#0SIJ2rS}x!l$@bi6}8n-yn3l+|-Z40^=+@wS8i zPRc?O=OY}z&-uhBtm`~QMF@3YYWz~ilWbP9&2j#vE&n+I1Ax6qI^hd#LH^SR-5sH{ zmf{uN{FloMi<9fvqdREYk{MY%AOzrC+D3torm2hSGP`Jm<%9T?+3=%Yav;!XpM-4K^X&dOmTaog5AAn!%VPTea z>-^XArzd-T@T@)xp8mwU1k$JS8ZFoO54c|-XbcNvuNAM(!AfP$8BIrSVuRA#dE!F_ zw%2-3W71(97$8$FLG4Cz;*00pyiz!Z!9d3#M@CiYnon&F^?jrS)GM^#hpD?2*w4VApPU>4e zmgHpKfy)I*vlh-hBi}Rhc_{n6nd=1kg2Kp-F-pXeEy;rI8SUlu9mE=by zA!38JA4+7otVN!6e>_rMA6aY!?!8tLSndTrvn>Y{m~BTwNR6xRH@`;c#MU({WF`36 zMLPCwXOl5@Cs!CXu+lJi1c*vp1|oy5s_t*&8UI+8BelIuuOW+@jsw8J#OD(u8MSn@RkyjOiT`Ez9P0dcT*Xs4>RbBYPs8Tv4W0;6fL!kk%3p~a*Hd`+m;1z< zdtZ4ba#hO2?ORf;u<|to;cRH(4{rShtRgPd!Sxw>b*aKI$sNh@ZLqb{E;*QJkB{_~ zv@B01Y6l-PW4sXt$fRr~t>#KP02kf&2U73%++z=`6Wg_`49cR<%f!#w;!>+j%_!R} z_b5xzM86zfErKEE1&<|0qOUF``2wGySH1-bbzrgAc-w|0#{b8y=)e!JgB)00OGz+t zc7E)tjT_0ljN7+=f4|EUjox9kl#ptRrIHXL*pOX4v$&xf*#(e~rs4r`@#SJZe><%` zyB;2&$U9oGYt>#q@7)r%xlOer1@V%PIFAbJ$eVJx4Z7P^pc-VbD!*7ua&I^i1Meix z-n0JCIy=xh-;rOnjXg%hH`5i69-S{mI}x6Ld6^xROFdUW;~#k4_)^CvbGCYhsS7cPfkBbmpIl!h)hWMi~Rq94=_M-!r4FA1y0M zUH3JhNT3M`L&+L9H0ilY-YzGjt`#D#*BjoV%Kz;9$NRsrtl%j8N3Iy36%^6_7k#2! zMY7*8!c0sAdn~^L6L99RY?YmKhuN|w8&OvuORb>Twqn+X_bOX>P!D41f8QcI?y#q; zYmT0^^4h2r6mR6uU4Ny>J!rQqa*h%Q?f%CQvll*2@BNxdHNp3$Z8HUiA#;Q{j{(P- z=a%2Ha)2rbJUb%r?AyGARw5><|BfABOtacl4p3}zi z#zH5Vc}(Zc8>%{UQaUJ-#+^D^uPh4bx9@F*6zqa zC17iB`JpK>-6-e&Er8T7kd7qfCO6?CHUj^)9IteJ{@1bU;y;LXs-0~Vo4eMr|B80Q zPl`d`A%nV>;PRE*Y`1kp5yD*%P=_n4no4VFv?B>D{;O!M5%LUsW4^Z|sQb5fxBi04 z?yftR4D>i1Rg|+W^X$-|-8$J3B|IuhVaJHTlcH8A5AM(U1z*7pR`C%sau$z9)iPE; zR~j`4>rrKK&;k8so-d@p2h5TKVRrKgyw?Qu#4kIsbu#4rX`Dd+6iqVXmF?KwhW>i9 zobcLw*n=$bO)oiZZ>4V!7En5R+0PZX!PPeJ5FDw}msTvBo#Hi6OW~~~hIDYq-|0^Z zVAs#K2{bJ0MrwFu%B`kPZkHj#%XFdZgz90ycMEz0l8?7uzJxNQ>0y&TH=_hg=>2D*8 zwJQ{ly-zg@BEGpYPn`=xiXY=49W61G?rK zk0RA@4acM#Hi|j*X|Z~*)Ah4mf24t!-3exVu+{|z1lTVbRLP@rucY?pUIX_;<3^ag z*WXdPHB&f|-ebuMsF|$;&RQ}n>1-j({Pp1pM_*4G8g{jCKjkE2C~yL#`UPnrL z+|>4-fOa2uptmq&>yE#a*56yAsuZhL#dG=I7OGU`I7m+O%TM7i3FSuecCMqyW?8x> zi=X~lPcKiP42HiPy@t>oE+)$8qxf$SlKi<_g4eh9&I>`$t7cR?(8S8Yk+x8y!j9TJ zq4i9bdV@NN7WoDO8vsUWjNosfCKWah4v21-^Pw*bZJ=+1uc*J-35aqsI(J6fD>kFC zE37i#Ao>`!d$FSD)-gIOht(a*(Ct_80udNMOW~vRn=;lh zkidw1M;vYA2qveQ%L6G3{5m5Se~c5GiE{Tx{#~&TbtyWOK+7c6nofA5ZUOw&J?45- z=BKFym=o52)`4xZ?k^C%)!!02zX`V!4iChlYSgxp@ITp|Si@41)(@+5J^rvSg0Xlq zAP1eT3qAntZYD3?(#Yj6U5ydi3O{+U3gyD4rwD@ zGvqIB+*5jsNkAk`h=y%%!n3{(1%r0`PtL*v`EBOizxqn$PO)@blN&5sEzqY$OO8}~#d`5d*(ew$jq(Co9KTctpts`wxFt`&E#9Ld?YAgtrb+AKPWkX-AR9r{ z_O*&lrBxhMoCs-}u$>cg3pZ8g9Vl`G5&;O z7lQ&~1+9$i@7d2V3LTgeAh?+mLTlF;pg_6F7o_v7v*oE^d?{zY2kPj*CGlHaJ| z4zdyrP0{S*8Xifmr*$jp*wC?!e@WtGLt)_(kY5$P_x{l@E@tvf9S?AtPHdw<;Wj4_qahMAI3%B&Y?GDdRipe^nfvSV+;A*JbU6Zxn*E=77tl~(b zcaZbC-Rsm42j}SGduxMlkmY}8r;L7n1NG~yxhzpkl9o-Msed$6v`2W?5wlTId%2#F z4d>X&_Q4~8&iQz_>);7}F&E9#Vv#jE&Moli9(5qO%~EmE2fs1gj8E`$0EYl1nb>h~ z{X?abLh{z$6FRkWI?YGwjXCBz86reJk7qfzJ09_Jf{^Vl`y+!@?XC>OsVY>@sJ^`% zy#?;ae_C!ld{^YYmuraSj*czlCNAMi~`^+w-2{u@Gl16GjTY-mAS?|+Qu{AX6zZwyi-#)?(xeBIf^X2?2uiKG6`&Tykc zQuEWdoboIsxNy=wRW3hYIuVn#)0Z7*AlpY~b+aVQ*VD@@-DHXF+TWOxf@It(mWM@B zb#~C74EGoNY!`V>S_~#F2P$?{VP4T?p}&3wGF%3*ZE%r?8MMWx?a@CfZPC{KMn7*{ z$L%$k)gKGf@Y)o4R z<}!QO^ZvwcXZTbashL0pTbnLaGsAhK^L%%`8Y_j>8NB-M{0z8r~O4C1*nQz8u@FqzKmyBT%x!YZE%ZE>uG;?|0S)8~7yUA=N7qdVQ9G}?fWo#!;DlCTUH3-Zp-e!SMVCvy{9 z>h<+s7|+)Dh#s(4%&?EB!~YEV7Sy2NE@(2x=SQhohf({WWfEacA&t5DgAD3#bYIxDzZm3FmW#w0J5x5FEg67}A@aokonw#ya7nTS45g5Sl@hv6afKD3U zjpeIb`I5)>>2uix^kBrnP13sD<3`sZxY?f>p4!mwGFdDpMdKsGNq)bT1@e0D2isM~ z{stw{1_(&$+aT`HeNxOO0+3=h@CnOr3tE=*HV-IvQM0$f`IhAFNUGa^HG00gkz85q zv|C!`&N<;N=uD`>(ldnOE4WoYB~(~*Y!L{N_EnyLD9V%bb9zI zwsWu$5TQQdk@%iEFP8mYRu1ndPj*askUxr(r3m6ryQ4YdUpcU^q&KUI`O?+N)bmpW z4YW9X&AcpMgya5J@EF~LGsYf=>3wCAWC&|h~*QFA{P{+J! zQv1;37vQ%g@pAa@4TDAczYqU8gMwToJQ4FE!JAda3Rn~Gsu5aP@E4J_H{jPyVu=Ug zQEB|yZadXhf!pk{ZWZmj26SBm%;g6E7s>8=48w<{fqs(y}p~TSL zWl!iuLPlkM66(8MrwCa=IEXR_vA+;|AJ)v?`gv`j zltVGQOc-w98;P^S>k|A6gQyQgY7bgf1cswh!pn6%TXX@Ge9HYW;c`V+DtQ=R-vm5Z zOPM3cR_>p}GjdTKM-Way;{k3w4?6}q=U;cX;aNs}>I-~ph;>Js(S_aPIma!vhC<)K zfCU^MkXsL8LG^>__uZ`IxK(e6QjdxMYG>XIzkL;{cWUjS-5!4B({0RM^(+szO1ACP z0mLrke0x=Ir@c6MeT9td1W@FwNFKJ%&Wbh5okvEjAEgPo=nC*M&|496TD&hH-t9cP z#Z0bq!TBm>MRfG`lA<2P?TdXfdneHP-@hRGRbx0*>T13C8eB2SPS2A~W$%{xh}5@s zIJ!mIugE3s5FL_Cl0OnB1AIX?wHpT@r0R!aq%Wg;-eT^5cFxXmxbQ=kS}+M%1UA!C z#sj=w=A|G;y=1IM>QIzvs*k~j1}d`;;8CNG%I*ghd3!`Zjr)8;JgPGWRGY?@f)!1& zoou@zg&9@#JetZQimSCYgSzs^OP!+%3Aof%Y2@*{7Z(nem~kYWm8Jfu!4MbW9P&R6 zcJjFnPfTZ5=xB(HXloM5b2Q$6b&zjhh_0p)NQ(h@lGp*RgK1Qh#18ecX~MX!sJeS9pa#v_2SxpPCP^nn%1qZr?;!; z%so5fA2_>Sil`H`gZXXY{>yb>0q1AR@v$CnaTLgbsOhsRaj-I2M77dxa)QaTE1d-@ z6Q?%}8y2GRFP`dfv$j|Z(9K^*kawlEV6n6u9`q|4ueG^#1bid+tj?CFm*ZA1@uPT* zj*gCHtAq>{juFMUErhV^&TyE$ajKd-iEq#Wsm$; zWl0eGpvqf{i}^5iOCwI=s;Nfe9ec`&@;$38)=RE%pm-aq;?N%O8W#^>TPTvB6YOeYnBFOv&%Wc_;p7Gc=f`r0m$Mx>S=SV@(^sBbh zJEL)Z89l-rMHB~tp_ks-JS`jxGkoy!^B*1f zYsVPU?nkUXm!j^h9jljnzWo>5$XagOFj;#xt|%LwVd2ksmm?&+AOM&wa1)-m1kA{a zbCfPNzDb34N_D3h^LvzbS0_FzotmG ztO`e0dW5NYbVuj2+vKL@9Q)l#2U^xrR2#BwUmkD_|GC`E^N@m^bT|#ZfM2goFTz}) zp6QZxZ9QDsYQaXg)vfp9sgxIRej+^5=Maiwi4cy4KrX=2sjVQb56voATP|c3Yoc@h zek(8xHutW;wHab=n&VPYBmL?D+;3mR<4dx0gGs_oUB9ifF0DEDjtSXqZ+hUcXa{Dx*Z#38^{ktvz=FlL&I)#_ux2hU7W^o78U~D-IW5fgw*u2xVBW zQtd86bW!8YPkt6*{7^=#yV3zxMNma&K(OrN%UO?DIO?&2NAVTS5k$|_VGK6_(|9*DNg@zLDj(#djydqXeL zT|D{g1qVNBKAO@1V90mhzk~dFT1@-+Gqk+)P*T0xndO#dyte48LInhVPwEDbqI%S& zJohPH2(xqN1uWo;KK~r{g0UKrh1zeaJ_V_6mi5CYQ`<(qQ=d!N0-wbIQa^0!k`@^; zcQ;+;@Y<~JB7&{+edgI+ybpD(J?`VGqjaA&y8tC{udh{4t4ftZ=P=ij4DFw~zsQ#1<$)CG}u? z$gjC_C?T_r(^o-{2eh(?Z^!iUrEoh^dV`E0FcR}vH*(*Mvzk|lzL;^>YWx@T`aqOL zde}(mj!RBGDAA2}ZgO$_e(nu0hE3Y0!-;}gBz9-1ea3VM&;7V63UPSh=!V`ELKz0u zv*B=Yl?RpQePz(PGN=A1brdZd^{cZVNh4qMN%qL1RMhE?WVDkyHyYKj4&gYS9j3)~deAX_U^^?)j0ppSikbo70J@kPt-TsB0 zoXWmQ`e z<=$Q7KzufI@@nG?J{iHBOlgU#SpV$>S$~W8rY&<_JTVOYgg)5*q5{JGi;Cy#r;-*1 zj{kCge6&Z~7B2cy;EspXMHNm~FT>xO<{s~>Fnr_Py^ri}Tp<*4l(IvM%8ynH=mAKx zqSZ4UvElQaO%WV73B~RU#MEb_!ZmW&G5SkK1nSK30G~R^VwB@UO8oo1*OM+(7-cK2 zE@(XkDGHZ4Q@eUGhyRuc#ep>h?Rj7|q{h5hG&`;wK4X;tn@3n%=yi*`_mKEMVFf!3 zbMP3-oG2|r;?eot=M*^*72`Y-A3C9-p=*iY*z>(WnPWwSAmPW@ZN7>|BV{97NF08| zw(o|4qs;vJX#jW45upmRG-Zjl!VaQ$W$WBi8^}+$yFmc|N2ykD31sY@!W!UZ5va&g zNVXthF%}k`W`W5`E`au#W!LmgQ5SvC2r)o#uM{BqGjd01E-;fvW z->OR1j->MF89T;Y_{rTxKhHq^9t0L=M^boNFG~yFGRi29O(Q;>@5wETT)M&Z&i2O; z2=IwDSMY?iWh*XQdM^H|0iv(he51%LF)m6|mv={}|7Z%)b)=;ba|Qix)nf?ib@5?0 zN{T}zZ@ZqC{yJLIHa)nig9Xpqt}fwBoR`dCYO#8A#DOukPT`y3pVPk{+UEa~n49y^ z-e5zL+qoFGb&zIwM5e!41|l8O=YD()JyKDk+ryM{omC6gz<_^|b3oes)a+zO+^0npsqu!s-BOxw)Ljg#@y;;8#JS2A9 zYYW9rdlj7>P8VK|Q4hyf05rsHvsjQ9hOxYPm;|m;0ilg*xN6=zD1cm;aPwAOb#%8U%w5!JWm-`U8 z_zgRY@;^Dt^eLtjK=l(fa|boLsoqI!P9}6UN7BZJbsH-8uDSlUOT=gXIp~BZ`C9hSrE{UvJidNO4MKh+T~dwJh7-#yM726M4b8A5l$TJ$b; z2@^${;=o?O8V*#2Y%E>%h2&-JKYFDF(yQ#1V5)@jy4YdgaQC=F(!qVfxx}9S)Pjq_&ye?T$k%6& zaVoBLhNinnDhA_;dVj)0J)w71Umvd)^*g#fm2~!x%9_>s3HVJJO_UzwM(RIrVzWQv zuoix=8vAu(0J~({zzRxl2C-L1&c_C0mV0t`t|kthpaLr&>V3vMStmqffDPiqj- zAkZ4&zm^YV`P`QpNXeh*;|CnQ%l_)dQkv@mRFks^gpu^ny))OS{yV!xWRt*JwEUZ` zbsa(bR|*NbJ5dOwyU54oJ{qxcsgp5IgP3I1g8#(z%QkLeefDL}Ul&uYh5s5woIt-H z@e2GMY0Z>hjKt7x?f7j=c2yh(x)8laUfSyG-eo#kqPgn}O;cu0Zif+#N=;EM9k5 z$EXvJ<(zbF>f*m?1nKt=kk>mzhB~%s&1}2>K^~P=!1|Qw8Bs>O(P0=alw*T?eyMes zJM3E0?f33YF@LcM3&^jzraSc@;rBU`Vpvi3%68DJ8hbD3@+p#4J<9qK6nPS!gPc;^ zUb(dB-PH*lmm=}dQt6bY=U`puGE*!R9~oM_{vXJb0)uXo+?W-L8!i79bX}F*W1m6; z@!ywtC*I6iC#l%f3loOpQsbW>l!6eP*Ib?@i{Jn$G~5lCBpJ-gRE^`5810TH;zNS2 z!9TwS@wG=+b5dSINC9!GPT3?^uMzef!%sRcqm#e6(4 z=p!;Z(0snVx*{V#+ zMcHpZ&V`J>+Yhaabhg`r==qO|Yfj4MZM;R)_zCXWG`@+k)C|(v z-(5wy;#G}3CD@^Mh2d~gfXOhNgWUI>BcoPZB>H@2|^2b9qe7_MeVqrv0##;_+0U3 z^)h{=ogq2`XE8SDZQVHux>q#bs;w>O7zGOh!gVbfZ5x8tt=@a$^O(!;x>c0;aU}pQ z$@CC-n$O5*90})cTfEr>*Qgkt)B|Eti8R$}sjM{7pFyVsOgMpR%pkhyuEaWAT<%kg z;U(%7oCEuQA4M(<>w_Y!K$ci6i?6UrTs!vrOV+;jAURjx^nQOow09X$7F{5jZDsiR zx6!JI;=00vP9Q+&ce>e*Q#Myo=By>Z&{+$wH!_&|j7BjE9LOqN5a+H}|W_Grz z%q^!|Z<@sav2+dWbudlz#!hY|UzBq0miI3aku zPIloq7^$lEZEb2)$p=;f8gix50wsL|Z6?54OHIH%UDZIaeZVb^jx-KWaxZC zHx^8W=VUcY7(qI*E8VvAv1O>(>LCx}nqwzZn4V8*XR`ZX3y(!kqL2Rho67FLjaPb) z_W3`MI_aVL^`>~IBX>b(F>#DFqVqRcEm&lzgQKKlRNn_!A_B;_f?<$CB`-7p_sktQ z0c1_77>{#BwwmQHETrj44NI_!@}^utCLvpwU!wusy?DF!32|_=AtdmKDzeD>v`=== zzoxmjCrHj0@GWBgxxC7U%Y)vFd3%KtWob_T2U6PDzLf42*rdQA-apnf$oHDcwM`;ksIjrcUif<6*J5SH080_ zg`)aZ#97`q!@B59*m`A9A^>ji*u1m)iJ9U7} zcC60D>v=IZzmZ*6fqcw%IjztO)o2+-$)+W7@`>1tDERlk_MJ^*c74)G&5pVI7bkCh ztL|-b4{u<&JhkTV3$6x6WygVnXq9()p_NB@sD1q;#JDNP#~_u_)i=~U347tugJ(}2 zvn}K3*(u$#Ym0pk|Lp0e^7AG!l5Kedsr!Bk|M8DkDtssaTn89$2LKj2a}v?ZB}f9c zCB)Uh>hE3I0B@rF@BTk;(6enUaofVtET22jD}Vyfuu|TA-LL3?nsUCDGPcdTEse!5 zm#}<9XP~-;#&-AFzVx-vPz3-pfz9Vkg^eP7keusgOJFQx-WWEqKBjGNO(TxWTF9kT z?w#3+$S?d0fUOOHgeZ4I@lw1jp5~#*b8{s>D^#awlD-r)+f<}n7)fo9_|$PbK!8N( z7!&9TL=@6i2dlrxydMafA`yW0Grd&fxK0H=pUFz|aet`_0Cy{5$?)U@0<=~6UUIo5 zWlP+0pE+DJseeR_7iaE%XU>M0+IL@2AVLFtg@lQOgXVn$ThEDv%w)?k_hI(yc2)GK zn6;);PR_CFSfzR}Bl}^%ncnSEcgi0NZ##$Su){m|1pdWxiquJaT0d`EoNMmnTy*MA z2=gy=xcVNsB^IO(2Qh5>`q7}UaqBZ?K!;>|sLFn^a6MAru|QImy27ApevbtjKp$ir z3YwP|KrRi~*X-t2YM!lQIgm9;NS@(~(w+@TgDdcenmhi9#W=EB06e}3m)kr3GDZPl zv$60x$pL9f z+80MqQ8A+XZRJv58-IikjF>fL-d0|5Mp_GLjfK) z%Hx~+gJSQHbp}&I&m@@~Xrp|8GpqIH0(N7T)wcV9S=R_SOddN&exJ5@G-sZBp+A{m zF&Fj~1&4dSC`5TCn-134aFbUcC=&7m0aAxPn1izXSdy`nJR>Tc(W*3>{fQiy9PEz9 zLCwybqdwqXzUu&+caPUry?gTn^P^ih`IH#%EKtIZE4!A)c@cN1z8r+=xrUHmT3y-< z2F4|7IeOPuR=AP~r9Dh`?cO)G!>c$JsaFdv7mzV3Nu+2rKpG7b7#?ho=wgsx zkS;5@)npePGI5#iUui?0((_97iBDr<$VBm<&%u27+!tktOfAQ+^U>1;E2KEhH=8fQ zi=E{eU7<+^{e&94Rr>TYVMuhi6MaSLk&UnP{o+LY)R72JEGQ-~9HKWzPr2$VJYR2W ztVokET@zGeh#tQ?VU~3!?T1TJ3~a@v`8uaa!U>Pi^orMAgy1H9X0ywcA@z>@gv-l~ z1?T`Qi$i86AaF?Ki|@OH=0T~7RO&?ti85yFhi;(?;;7l&#u(%Z&<91i-FqWHlF zqnH{W(Exkq{PTB~oo~Bq3RPI&26Us*AWYC&>p{~@fO4@l_FkM(i%;-ua+bp)(%ysI zFtE22Zu5<)ji>|8Qehuk58n>j`gK1f0D1};w~>27=_B8MjM8^4?4pL{Da)7)Tb7_V zW44ok0!Kbdo8(1)p|QC#fN=^{Jj>sW+tGL_&#u_~UHU~wMeQA>S={pDouYV@JzgL{ z;VG$Oiq)*^{AX~`ibhFo#EvG)Pl!bE*gFb-J*3luKLA1^RxjyypN}j+;+0I`B1sMe z-NTKs{cL{IH-O>x?ZALG?rs0SZZC@C{vtTKA2w?3&*e-mP`t!Pmk zn&u_C7d#o8`P~J}aeks&q^Lzi>bTZkD7bVO<}}@t6{JlT=tjVo$HzM^xuCPji=feJx;77O%X#=hayg25y0WO}>3eL|9(-50sa!N{zYs&S& zJ0AHolP|ODne(`9u49Ci$_h1#mlkt?VN30qffW|Eu2`*6WaY%oVOM9%TKBj)#O55~{ua=xX(gvk@8Tj`!Utj>R%W3wCS}OHF+ZlqV zm$NlxqziySq$!fuI%b?sGyi-y%KM$6>zMbO+gygfJN`>p(&7fwWbjU7ZLUz8a@S0c zi=I@xg9tStZ{5NHjW;0Cn*ZcK<{xNYPxoeRTqsZefim}q?>qmc4eak2 z$DkD4EUgE6Uo|baFbz>iysR`llihB@6f}gV?i!o`qhwY7;vo@BSsT)Ioszn|HGNWU zh^UzA(|@gjU4qGln6A6I=*Fa^(FW7Wl4oc*grBfyJ8r(#A9*O`Apvs zt7YHelF5Fo9qK*L&QpPnR}H%yZ&_ZlS6xdseJSE1AgbiV*U2;Zw%_6(b%yFI3A8ha z&f8t1K`8}XKSD414ZPLgnZYmD_FYds1-$V?kRq4P@8cZ+5f*`hjxcSwN`$`Uqj{1` z++tl0^V}hG1w<-bD}{SRFUBQG{uTv5j$A6cIqs|B5Kdl;3+6!O`VeIu)y|)M zxL8$@;gLU1fu-SbgqPKbFY{xdr?OP$S@d`F3u*PAoGPuEzW@5O9P`PZ5c1vl@ApjC zt^p9UkAHB%oz(X5S(}OdPD^4JoZ@i}bw)~ukb5hE;%TD^%N{b$_}EiCh3++uW%w;= zjJ{#O_wy*i@t;ZItqPiZ-$9}c^7ENzV~db;vaH+0?dPcmi0v=mh+19$_% z#TUM)E;uTm4-qercM*%-udt>|IjnnL*Ne21vu&>WejuFbd#>T#zJIIxjc78Eu#zM~ z8mq&C%zxe!yL6);FOkfMFQUuBAoEv&SG}XSV=ee;RUPbJ78sd?%`kFDLmSTeJjSS1 z0#MW*cfMZ>9gUR?s>OVQ1z+z)y65i8w&x3NT`{$Fek3p5?8RBkAr523+>H7X{e}61 zT*x52yZkK>&`9vNn9$FEKv{Au4;Vsl4^S#)DbjAqujg^hb&M(Q$K7nW|NX6(pZ*#i zAeeXVm~^;-av_q?(^kHuZdxGfO~%4(Z+{D})g%DE0B2X$QR!Hq31jjvsx zB*a#t``K4~$z7&(@u3LjBi{Z(yt6uaS-|2sUfmF%iUb1#!`=Lk3(z0vIwEj-;!RNa zMmq7p{|xcE(MP(|+ahKz;k^h*+=s84b_^fs#zO&)>K=<>m*Be^2JRkiqok1VP86`< z+A`L|JWIe`T00R>Ixe?!zV0Y{>OV(T1&ICz4mB+_WdDrAyc;L71Bi8*$7u-ZV!Zmu zcaCN2Uu7?WUvZ!W2W(OKb&$QT1>XN3JPvr9j_;-LP zEOI6oq8F%<8PWj9d&F`wL*H;)o!2bk_NEc+Qt3CZW7qs9$OyDAbDgxYLh00>+CD{4 zY{dV(Geji%Btgk~oO!#F9mpTNuaC%K8qc*!AOJ-d!2PjeAW?H2xL#5{;d zS1&>P<;EMDg?{siV<@0cIzWzZvWN-&-?TJj5n1|Rnp%N~@ymX+ zx|6v7N4zD!lX=1}NUz#Z%OzFL#4x9hKKV@sSJsCV%wc?P{a59F#`#f&9|$WJO!_@Gi9?Ok$0cK7S_wKrCtX&ZAMkJ)P<$VA zOb_QK_9WHU8O*_2x=xKsxLKf{4X9h**G%*RB|d z%)6lG)x>Z-PpKj6yTFr|acS47rWs|akvsIJ&}HwXLC3(DqLOw)uAv(@uLKl~;=|ar zC16v)8j>xi`%6<*rea5%10p4xz@Pf5ko~{PpZSgymux-j(q;sAKfxJ6llN9mF7jZo z*OnD!`83+KW`3|~n)h@6Wrg9jNyG3ncck$iQE|Dp<_W>X>NYls)T&~%ttrrOd9;`c zPS%4Vs%)9C-5QFwVh+i(tLTyB_uxv-w@+f6{H28xGvRzI_=uu-=I^i@0*IDVJPds3+JZ|(y7hSi$!)jI{#H>hqTsxl2FrCxcFJv) zJvh{1rszrQVekQdMxY^E+VXb>UAZeM$gJ_Uw)QpIb2j(xD)$QU2l zB89^GhLk3c^3OWVyY*pKFqt(0a&JY3$@>Y=w+g;Kdp9P*uC}#NKfmrQAJPxeoVyc2 z`ts7xihEtd^R6pi)Z#1MwrS`4@T!DW@hW)#r{~fYC^}K^p|&or!s!jr7fr0}G}vNt z$f}svQ^RdzjXf7O7<^)}i%~<}2+hlU1%Iy|Nk14}1|`tzGyKv$AWVYmQhIq%PqZG3 zz)CU4sm23X*14C-MExJ3fB!SAP73a-c|EvH0eQNswnpQqe*-l&Mf)aO-vU2tb!lIe z-lClfcJx3nA%Ih(a@`6OJYLwbTVN{yXH-hhsCyQnCt2(z{8XEjhl=+j)o8rCT;@+C z-kr-DMcqkrx6!Ap{mLp~b91WePAdWL2jq^@T5`<#W#Gu2dW1NhIcr%4gmT04=X+DT ztf}gDXG>+(=BDd;N4u}yx8e1C%H6T$B;J5ak@0Vm=iO)0F|y-SQ&;vYX@`TlW%(2b1DL7g(VxaDow!mK&&17NC_Gxr&=!El z+>PF;tFMbk34CSU!}}?ZY6-vkf@Vxz-_lPdamG~(hk^2a{2szgyI)`l1#c7`UAO;+ z04jogG#g^m8CFJOwE2(DX35-VH z675ji)2dkTaxWuDv!J?5DzE)^hs`--fWmkn|Cn-d8MfO=8D;UDk{DgiJnaPwN;T(5 z@78C_!HoMOPGZ7-ALudiJAzl~ZK>_s?!gSU^9}oV^ZsD-dO`Z4eT&l$5fUVL?Z*S=ui>Q;tQH>Yq9j-M7h_40b+}; zms6>+@9z5!xeImDRR6#g<{exKdGD~j;+tA`_~lgUmu^?H&h@0su?`?EF6#-daKkPY zoS{XQ*nm(^19i{5vo}MT^-}>^f`=w3W?sJ-cO8w9h1j_l#Z%`a{!Si%E{@03e1UBj z)nlV&Z^|KN&!`wPu3_zZ*ncibNJbA#c+QnONHhOX>Bc`86}tz3@gDn?)Ql}dTAZod zn5)>T#`R&_4at6|amX{kAKOh##rM}U{!gHD=))^K@2r$bt!7R9MAK$;=|X8cDY+SD znUz}EZk>Bk;X2Cdh#(N)vc8|_E?p=a`Qvn|=Y(7rtPAwmM@qzNI*!~XC45;HbHEo6 zo8^A5-(V}iGhv!jY;k}Zsb8Q^Z$EcJ3qf2>pac(_b-Oz}K4x{Rj}amI<1tW^y2dC0 z(xC}FakSdc&tj)_38de$6fYeCThbwG41*oRZQlk^fbPI+Z=}m*kwMMStgnm^&qum5 zYJ7ERxCC`Xb=kJq9GKoAAMn=d^Ik=SKmgF|pNfLH_Jepy@%NTtqpUhcHWtB?j2DJ@ zm_DTov%v`hT`5YFdqW~tlmMGGD8c%Ql1o^g1Z9wTwhb$PIy^t$KHtRnoe!tRT6NsO zf{W6bsX=Gy^Pim3Q=5m&P^2TwMhk^nj$b08WSsA%nq+_Ai^u!YSwjJtUehV@?P~mj zP5d@igJS0gBqBV9X`%NA!!!w6pkJ_m_ydJs`8hmyIz6G*kB^&%tgp`bp0+2i&!Q&< zbCd9Du+uq9?;9%4UX01hfRNhx`rmM$2WJL1CzA^N`$Zu4>f;RdPPUOE?nsAneA8|| zoK73?;dK36u)Hz4SP7I@83l!UF06|p<1%oHcehqO(+Ff&(@C{cNl1L=*u_>HoB5Jj zU`~0w^nk*f#Q(H-%h0XIwx!n}Qm$=?6>#f#uFii!t|0IMARhg#s(I$QZl>@JiXgc{ zd2Y+*&3i~*fdg|!)^$E@cpqXB;zzFW0{{$Uu}UwG>k}ZIZTVtGvJnQTo}hEFNVgB2 z4sHk~rF-DB&~t?^3lE{U(7mMn5kJSe>>^ZhjzsA4VdZXF^12sno$jceBqb6aG7t#T zfYT^FS=Pjn3<{~aZNTz4+#Vm)!rj{&bJCGOPv;ndR5Fmu5xwubC?!~Cio7t1U^CxF zSvTU(G{I?oI^rvm;4vDk9tgv#){r9yetbs76Hxzox)pr9Sfj6a*Q0fxEb|*M^4>b8 zRUudPzmcb@Vw2U+;w*a@Sym8t60M7Z;(bmpUFSN398Mjl#g$v!=&G--ttLhDyPL58 z7XI#X?Oy)@a*-v!Rz)_hu#;~l^KJTu#f7mYi*@aLc|P9n-nE$p-2z86XGvpOo%NIe z^)|X0usFkD-7A8-4CXRs$m$t`m3sM7Z8uDng13@_EdHqlZ6V@U3i=FlaO-PX2WIzh+cpwngH8B5@VevnJe^pY|iyEEx)j zUva~S2S2ODv=Nd>{l_z{l_#hHR?aoV6&OA=qWGW5P;D5`t;3&$?D#qr9EUzbj4kV7 zCgE!f?G6i7wTK6jsj9KDPT1YEvD*FqZ)sIJ>*4ynl8d)&|Kv&8_l=Kyh2zan%D;Me zq>>{z+o^omq}S2@N}UT2H5#Gg#8HK*Sc=NWXdg<M*CExdh?{Q~j!i0mN25SjbSUc!+js~_&cTx-rV+|yjSto{(b%^GcWZZJp^ zPx}ypQamm6dkt~g1-OpmefhomR~_$hYnc^K0=k%5XHE1g- z!S`^}XpcU@%rLf7B$3YkE;MF%^e@x&zO(rCDR_W8J~8h3c49nSJw@!eFUi%#?4}TV zP(Q&yNbvM-c2_PCHYK2#P-foK!TpQz8&2S@)@)2ha89I=tSSplbzhaeskM{Zf*jg8 z$xk32MhmZ_K=5F2i!|h(^b8~8(}-Si6=ETaA;TyisUvRoqvby)3-`Y9%JoRRU0PQX z{=-n;+2hJrCOeYgYpZY=pJrVgKB1BgZ=_{PBW@arV+H(O9!#ub)<8Txh{8Ucq>tZF ztGN*Q;50lRPRB3r0AQqc-|V3cj~p;_&Ra4HFQU_hq4WFA$zpnh%Zl&IakvQ`%p7OjC|t zZM+{aBrMi9`1XD{8eA-XF8s^h9|~Rj*LN;y90oD|n(qqpICx=xNMp5{1T|O2ByswX zVpV~lW9F~cQ>NM^puaM932(!cS_T;JhJHZJ<3yTn`J=9XW7IC?P3F_|6hf!Dx@ z!09Q-gN(O6Q>B`YWui~}JUMq=s8dpRc?Us>4%A@hQmZndIivrm*%QIvRpCG=02OPu z!m%RV6agOn+7a4Qada{S(|-!fFE;#>Kk3}yAeV;CFGjv*p&pWAb0om$ zbXOdvukaw1O5y|DD8K+UPpYY+}@+7Nd0UVF#3p}a9OZ_(E$v-?_(lNM*1q~>F zyH_?Py}|=}E7K64I*=H@r9Wp^0DLH)9FR=hTtl6HVb<&(M(?RXg3bS$SfkZ~Yf1Dh zAUM6`&ZyL0`fW1We-gN7I3eSjQAPJpnglf-X#pbHfa#LguZ()iW|F0lZpwsAy$_w1 zDVy?~mc~%FYC2wrB>-Q3XtWfv_(E5xb=`8a;9P-@fO}nY@iPl5P(#+imTfhupY%W^ zoxLy41r~gNs+ z>pJD#v5;?SS@G?+e}ChmxPg&R#@Ri^h6SH5cVn^bCKG%Zf3~4xT<$sJ1ik9B_1Z)n z{WvgR#Y1fL4_Yza$)tG-n6Y^1@3=`!pa@spZ(J%1hn>6WBx|VULb1l%^UtZv@ofD= z(s2694RVZ10J}pgW*926-z8*Q?KywIjD*hE6&Py*DcU5>TPxNKEd+poo7~>gS+F0e zigQ|KGB1xu`glZ@Um2M2+(s-A6(n$xKco^`Q1^`%aBu~2@!!BNF?n#RJ8v;;0Trd-}$*KQOq4QTlmfjW<8va zZl8_rcEPh;z`jN1I_Jqx#8=W%Dm-#GQ}l<59XaRMs!wiZYKFL{>4Gr-R*wI^at~T? zne;B;GgZON0Lc*)qI7y`VutBan*n)0?j^Y9YKR?=C+x-@sqtNSsBIH?qzRQq6099g z>q}tNz%(_>7XHC4JAPQb;NaoFH|L0E?yNnq?`v%abPq*j3FzIN2vH1kkM}-?L2|!; zT|QGPmbUNV|MNLezxfReNaT#fYc=MallOgnncp*`=h<8)5%kWPXqU7IbsAxFgk=X4 z4cZr%jZ$y}S4&5zcD7y(0ggMzz`4@yr+vs*8HVfITNHHUHkLayc`R4!flrXy;xlLR z563-yJ)4gc>5?d^B5#w83DDp1td~R`TH5eb{_#1e_be;J1(nG_uxMxoH+Aj0h9g(! zh>WaZx7_h^vj>g&D$uz)OEYZ_97-VBY|^h%o15x{LRvM7_1w`c-Zt~x*S7}l3UGa& zKQgu#kqC+hNUCG<0r=tY?dk(ccocobxr-#RtM^>QMh&ETiYxPm}O7BPu^w;bo5r+6=}PXUl1{lOE{>c)?+l`=__WXU`whZtK^B2kM zMy+R4|4PBThMXUlU4HOX6t2b&XxxS{)B**<(B^( zl49;6UTMGGha^rqyYj3Y|vEvqc!m#{q78GFAP-;EKgSlx1ynV_l~ro%1YT8C@bQGd5mJ9hW=@fG?{GZhsjWXih+c)LLFo@=PZp?$~LVG(N znOzgFP6L+N39?cIzFzr^c?e6QG#`A{p`;olVl z-q-+eFZK}^ggvzMYkJtEd>r|k(3x$z#i1ih#h{(miazJGc(@DG31|K295*hK(MGQ@ zv9TV6YiH6ltzXRUk*De@Cx2j>Cw6q^#eDo5akC=4$z0DEhAykmUF?x+eo(-xjGOu3 zB8A@OeS1v2lK?P$gnRi%^KCX2a2>QR#N}`ATV$xVlG4{x17<}V@1gs5wuLYRE_zrq z#XMlXdB-rtv%Ase#YA~q-6~I-r`?`|Vwqb&ULcKeX8!;~N$vB!L@e!W2;xK-k+2@p z?c+AXZ#N(_N?q=*nRZ2Cg6#iJ;*+Zt-2&IZQd|5InwTj3OUV&&exIFhW2r)YU4^aPG#n9k!hwF7DX`4h_|X`dxX#R{0r6hLAbsSTCs^LOR|^y6)T#qWD9&=1 zoVqb1oJqQ82J5~moS5W(CQ^0S&1AMWq&>FnZFt^vjrp{05~UuUVG=8f0zLx?e4}QT z&M)9*awHhMG7}b{?i+`ZgtAJ8BGWZnTC5qfqas!?68FlRwpNN8wFR!BJMw*>!ib+LOl72zPtGH%OUMIl4d`97r9e>t@ihYhmQmFe=kB)PsS)D$nxi zE-389%gL8`g{1QT#^0N7ybSn&lHlQ%X=7%%>aJ+|#j--$)ELo*)H}=--^>)7W4EaD z-(HX+GjI`E;9pn}< zmj6Sb%^g8R_veTh9+Of#!eZV0DSnrTU`G4T;hEok4%Y%grhvvx4~Lm3tuO(8KY%Gs z^2H7oR?fhY~9#! z88x%MkaBMdM2*}YLhMHp-*t|)nsh9!mQ%e)rT}M4W)VC&9Jf)?G{!KQt;oWlSu&3I z83%xMhXw%t=P)TGKk3v&TCMYN{Q~6i48|NJ`N6N>WIqW8Y}jyMS%e9851~*%ea8~x z+!NuRfiU}HA<4zo;ShtKs%LN(VcA-ZgJ|niW>D?8|2=5P_WbAP*(HGG!|a)#=6OLT z)N71rhpAYy7*JL4AB}JN=z<7>^AhwmxGlhI`ZNamE^=*1;D$-SX&}BZe%kLK*_-Yp z?uY0&d#Rk^a1^!YBCF=Fn+V%n&9mAJ9DwL_MI|OU@3LKN@naMBw?R=w{gn1Oe%S%; zdhUV?#bdEELAbB{)Y3dpc9w|h-sZKGj+o2<>L0X2ytA5y2!L_zGd$1D$x1ngXNHV8 z`T?*&2XWi16*qUoVn+ElW2hRx!~?oe+fBBD78q6pNgZtWC@EaaI0Z>+4o8x83EEwy z(>RiDyBQ7Pkcge&@G2Be+Kb*3^&qid?}&UTpFscLPTq$Xphvo=Nki==)<1AVc?Y*$ z9a^&~DT_7GE=vAVb#l*PCv z9Bh=dGr6DDBmZhQ_xE27sSf|{pnJ>QA4Uw-q3A46#cMYl}vZJvlc8a8arZF2prZ^{0P0yj_X7z z|ERdOZcOT1u~X$y=G!_aJrb<0XdH&9ibcf>d=zgm@27rV{mF|Pbyv(o=VV=%=eXn! z1493;uhe7wAs`z^CmwkuWNMxWR!c>mKRkls72WGb&;KW9fLps{(_=kN|1YGGYMnmz zUV7hN@mYJ73KSGR03lwD(iYkc{2Nxq!v zXZXaRPkAPf$P|r-srHjDcY!t*M3CAlyHiuKZAyS!LGyI*UX@jCfkDr&KCHPXt&m4) zgO(9-&8wl9C7Wj7SopVn3t?fJ#T) z_tMd;nH(^qV0?T>a#a3Y%RVHup2w|?nyI&l*ZgvBqS>=;fC53(9kc~G3>gpGoQ^uL zE2)=|`k(M^!Pqv+z;&jFpKLxcj&-!0FnRJ@sl+#L0Ic0COc-9%ZJdW^l6rJDVMdyC zcmMoR@V6OjTx9D@-jZ-3P?jV8NHllwUU~lLqHfEBmT#f2keutOiE5JBSt0vq?Nk|Uf>;qA^opGU47Gp>V9|je+d@_X-OJda0l=M)R}ju7(-#`lsTWAVKv)wC+dzWL|QbG9tG&SC{>!Sew|7aq9BV_EC1 zNBxksOoVFiTy|5_=2q9}o!=kg;AqFYIQY{o4#1d2+Cy!Wz>q}sp9N-Fh98B}ao!Nb z)NjF!X}ADLw?U6U1W(Z$RprGg3c$*wT)6&_-FB29Px(^QuNg`qzhC(Qpl5oNTwEjO5FZxssRFNEgFMZ5S(inJ}P`my)ijevab(uX~M( zf%=hZ+eKp8UAFx9o2d1Fr|rj=+Frh?hzIJ1*^c0j>!-$eqd~m{kYJ&&!LmhP5O5H< z(|79n(>zJA9XcCI&?>3r02=0~4;|hg8~^$-P6Q6?MGu9xiPR|TnApTg{p-Z5Rgb&> z_6b{z>2@|g%97?vmH@J7=t157b!P(IE8LhMok0~0%)d<9T@kxaRW~P2>7hAMWXr(b zH_6`10urovsO6iB&S+KfsgW5ImireGJ2j;4QR;ifI0hYlJq=uY5+HU5Ak*hVo}0XW zgVF9pdpZ5ye3?xJCS}5nB>by!@$VJ4r~}2I?@3?7UNC~-k1NLmdeo?=o5}B!BA``u zk)?g@{uDnvlsZy4aN7edjSPLDBWnHALbql#FpjFJ!1=60}?`qK|*?7lAXnK-zojMO@3OYXm`gfdLuKLKk&L5@aLc(|``6+Y@|<;G+j zI$?$MAQyS&7kZFqD_&7wvGVxNzSu|Dvu{62WRl)oN6=-OjjlQ!)*s-=c`LpRytHVw zIvf6W@roK_F@OYP{DfqlSMkRh{$6#^h-HAwHGSYObH;g%x%^Va@lh4?biETuk@f<6 zCpBGfZAh_{R!jOAAxN^>V+cS`I7{|6z;A zM(4u@I8CL*kV$Z0Xn<+ElVE+Fn%zr%a9f|veMX3G9Uu+}F;{1s%B;^AVQ^Chg}Y8E zaxdJ159RdWAJF%t2P+4!SL6z#X@W@lc5jMyx7;82*8ZW4-Y^49r?pD4P*oU7O`pkd zBmS5TqyD^OT+B^NrLbOmWd08dU66!RK&NEx=PtaLA{*4gPbaLYV)Xi(@qML2I#A6{-<{nU_f*^~sQFhG*Gqlg88n7c{Kf z{xPiEPXP#iVdphiKnuGahV2T*PT&-|iV5sri`{|y(p*Lw>ayg1#Q>rH`>e%&Mio!dWF!}^YI;|1`%B_3S#Th8VwYV!GqEj&cA_^| zBL)2(60lUeM}JD^TaJ37Jo~7gYB_$P8TI9_*|#njWlt`fQ4WQ&lb2g$jFiEO^8o>> zlE;`f=z4~cR+K~+SB9FoaX*jYXAw_Kr#MS9)99x)0PjnOdgm3(0+=wFo~~aZ8+sVN z@YInz3r6=QCDD{3?ZXDjmh2#1RKJd&dIjpaI4meW54M66at~v1jr>HZAVWBN##f-` z@Hh0>v<~p5ZEJb{EB*|TWdyDQL4}HOYB5Hg)R>Wp1RuhH@_AYHrQ3ypWd=-u9Xbv0 zQ8_LH-ot(231DZ)avr1eExVgbwxBS0B?JS%G`T6wAk?koGfSLeQE?^VnB$^`U&0n* z8Gv)0zO_to3|!W7OqKZ7<)q&&X#)F8k%2k+8>44syd~xYnNElY?lLq1jdAZZyfw?3 zDefUm4O4Wmzrks!>Rx(D>FjfF>w9Y$;DA?DOD*o zmEP1wGL6LMzbx@odrAECa1)dXSyS=#vrQ4DW_tN8Jtg{NM`c8D9MiK?o!RnL1S73K z5c1da#B%?s_)ABAr4|HpXcmTGPt2Kvr(BTWFf_Ak4!X)m_Y2_m!7R)mGmoSMw_M6N zZ4;ztM9qk|c;iqv)!HQ3G-JXFW;6IxI)J#jjXd1L&jU&T>WCFSy|KT{L*giOf;*$l z9!v_NBr^K9Y^mQ`oN-F9Kw`;p1PP8e(cRgP49z^MtQ|Uds8pzO-B3w%T~?DL5(=q* z#=+l>v+6=+aw4ltF*8X{n`3ZAfAxDzmL#=mf1I*=V<+}*nm85rA;N_&gX)n~7%tO- zzA;j^#3b;FAMVTL!F@5O3wFCy8W}Vx+3qJaZd}a(Nt48tziB*E+fEp;kge({obci> zmRJeb{2irvFt&QJ4C{zLf}IdF)A9!BhxY`}oZ(5QRcS8K5X3wwq&8!Muu&GthENU?2g%-;-%GM4kGP8RX94OOJaWJl?$}JN-l$~i4}X| zf`*wU499eD6FEc&Oz${;dZBnUd33j;VyS7TlI3aa#9(}2J1^vWnzaPgj_PBVixK_} z+{lZM*G#AN8Jarj0+e^G3CU8`Mn+VdB}60*buN{RrzV{G8J{?9i$Zx_19XFhOUXRcc|GAV`t^I@Pw4!gYVJea@~cq6Ls9GR-glDj;rigL9bdmDcrf>*KyMY zUSc!;H6$k%IsrNm#{DGu=>h%2<0HL59^XDo4ZuU2GcHtMKdQe%XE{L+$(sPZWTm8{ zjg^VfFr+PCmx8>}7?=9`95Qxus626|)s|BTwbxTrJnF-}w1$*|{`D#?t(w{qKg+|l zS|)_hOS%9{cy&c_=>iWku|yHV#odso{x*e#c0JA#Ax1WHS73#GFOr*}YUib} zNYob-j)ps&agNuuzSEIBhbGjmHVD-AJh6c%$scs-;Gar;#fourhB0kO;t(uIdo&$e z%jUy`daq$3KY|?sA8;duYUORyAe?t4V4#H4Wowu<$ih<=EsD7xJ+ynQW6QtoW9_Kc z4_m4Q!_#*=V>Wt(D2n|&*hnlW{-WkRO3Y64rG2h1c>>_1?!W&#a#U3&+1`gzju-S~ zI4sxAe;WM)gP#{!#8voAYq{_={;7i`??ir^0!*fe%}lA-7E#=Vr0!F4=#mm<|E1l zWO$%XL{{L1iLA2CaPg9b3KPA^_9f zRQ^1ATd!lZt4$krG;AJ-3%v9;zyFM>?OwEG2CJC>1Dd|tD0)o8%vE_qe6c))?dO)M zzbE^;LiaLv+~Yo*nK7o;lkeK9`j=Xs5LO#0r{d+Dzuis4!BVKz<6g%%HLDxYsu3Q^ zRc{1!6!ow9MhNo}>{U@cN5>J`?hN=Ox?$ zjWg%brM4SWpK7@0PPmo)3df-fXxe=|jB(F3>C7W?wMBddcbmF{_!fUNm%Y?-X2ZzcPE^$%OJr z)HRwcb$0lX$vV!Cy_%SWgRL%TKv%zmr4(!P=&5u^D`+4UX>c7b}XojlG(FSvCx zXNKXdF3?Qr=lxUjV8sVzA_{6oauLYnAJQlQ{X-D{9R=>?akZ$eR*nL>|GNE^#wPBJo+bfYh$!ZQgEO8$#)V9Ry`+jwi&U;YkD=U2 znLm%CJb7du3wBKJJ11L3k^`e zpV14-WJbR$qc}P|@Q%`hu$EfeuivBY9B?O3z0OQlf zs~z-^Pr0P%(F^dGpETqe8ZY2!lFRu8zg+&?c&hhYGu#a}j*qz^P_sGs4Q@{)E`D+z zJIvsulekw)U$Rr(0q20*g0EPTRO>mmqm>(Vza&S=#rc^6C#|ihbIBx3V*HEy!IJdd z9lO|%Qe|{2x=)b6XWkcIn~{jVVb(uJtZ|15BSw?EIzEXBnOOmEPUTuH{2v0L-G?SE zv?vEqy&)^OMmZ7CTw6W(o*(UukTfQ=iC}`v&18thWTj`2Oo;Mk%;0Xk+L8{OB-dK5 zp->We8Dc)$GK|eXiS>C~lgWQ{F$w9rXOftb19bx$(~fqY>3$f_;)xtcVkbxmj{n_o zrwFjy;gX|V)#Le>Z8G00I6(FsVETrVHMu4Nnup7DE}dqe#j!$x(CHbkCtO{He1b#c zg%ntF5Og4c>>qU`-G$i0yu+_Wg3+C}D4bxSr@(WUa~NT~B`?RHEss!;GVC(~hka zNsoKShX1k$!_fQf&8>xKQk^N2@af1BrE3>0o8 zRSks>(zVGmqOeB8+uZQ;4BP5s!^!)yz7k2kt&t!pnj*hP!}wCwnGE6=2c zS2R<3ZH)J5qIF%~laxAZ1;qzAqt_>46EICe(o)Aycrr8!qy5IgkqYc)({AbbX`U%G z0{ecapYlD~MAF0azSN8Ky!tBy?vJh+zO<+6coVI2oScPRn27G$9AEt~WcF1SiY}M@0@evy02Tp&1j1*V-10bN7VEWL2yOk2eQv9 zi(U$21-ddqC;AL2_(8B+I)rdN*NilnO?_hJk`t^Oy0m<-sT-`$k1knZKbo&D5u-ru zmK%hS{F7eD5ZRbsYN{n3FedUBb}RKLP!26)tseSMz6;ns;h=R|?`+XvH5MRNn)Y_) zg|Oko?ieq}SN>lvj)>=l(R2{VrK)C4Z~`1IY?SYYTSH>muZ zINdo(;zi7gZe?Z`GN*q1Jg0GRE22{TfI}zmYVIsA@dWdCNqF9pwHsE>8PyI>Z#mE& zcASq!*SR;DZ#Tj7iLB0}{KaefU(pwpp;k=YV8s^pnHj}o^yr^G*IT)AFabP|XZ9#( zBaJKAdKT`cfrM*4LIYf4dp90DmG{W?cguj$=#Lf5p$(dF$Wllm?|a zjDP<2;-`^Imi*G0OvvgrfCqGVHk9k*?Alf>94;Nx4SW9Yls+lV$y*~bn860=UQ5*X z)a(}#pkpj6Mv#)%a^66TPeceblE>^a)ER)?kEd0p3lAtLtpNTk`1P$*DNC+mhx! z97N}3X)#EHffD_iSd5twH6iCE6ke3Cc1C6rZnN?yb}-rPZM*|Q8HcwrpB$UT8wLEp zevCuE$58F>r8V`kZQuf5#^?x67AYGg(wV}79oT=5v;CuMcFQ&BsLKpf7bdNTXI9+u z^wMV#+=BD@kMCr%cC`QU(bSap2Z7Ge>SKk+=w9(?u`-MGUE-3AD2k6~dxSelYvypf z`44O5nwrSl>}F#4ie1kv+*GU*SY0_|@MsV*j{k=0nDC)8q?Wg*Ct5iu?k>f1fXjVc zh{oZ;1TA^#THbB0W{boQNSUg+rZPNQTcGah+GvvNL2qnfvkqmW=j|8nYZ7x;9_GnA zoR*OLZL#QA{nOUf!lze2Bq6DIEs3Vl-CU(=bj!dBw|k81=C~!ckV@YQ%iv&|I_5lC z<<~zJJ+~?L+E1X}h?}l2h*5{(k6vxMKnjuB-b|dQKB@_vW5cUwkHeL4OFIKIhpTyS z7iGfZKM^mQUq2G4M{)o=FaR5TyOuXQ0-ni{$VIj5?%nU{lDOgjyzUX;R(^Tf7S+~( zk96eAB=YWz)dd23do*V^O&Sv*s}m}w%4r?R0yMXsp*UgWkV-JuGfLb1+M7)md_dd;RTOy2q%2r+!L0BYS3ddDN+2NSpv`r$Vh zMyr!_(9Vy$EFqR1R!Qag6b#d5&w2ndprb0o^?^81uH2;9LhKkuspdR~U1g|Y)n2y~ z-34ux9WqW`j#d{cJ~knO{KK4o3JYi2x$#f%C|hoq5twhr$~_Dw_G~W{3)rBV82>c@ zM+RVv5WorbAE36LSAA8JazS5ZyR6*jwM;B9$2WoaG+QT7W(i~cxKcNRQPlOH-UMP+ zF|qvXJ;aL+>43m0%@Q7Wt-@BvJ#KW(lf+gm_8;B<YpD1#RY6V&Ph>ww+31Bx6Y_rfdeG&;1;E$=RbV;> zZ84PwwX4i}=4$7*Hih9)#4>8nVTwUk~R<)aJ~zwTg7wycz`@ zn>Y#Iy|j%mSU;rCbQHj1SvpeLBuE~@WULUgDEVb*YFeP5KTn`J;Y%6Q9|dmY74k;e zDqL<4N0z;3hwmD#D>jG&%j)>OMU?-bJ<)fPAgr^GV%bS%m@ubNA%5RT{-d<$h_Y|F za(Acl?;W#I5&gwMN&c#uJw|&GYvB4}Gd@8QfmolaUa;~n;0%^?FLRHv_sZGf;Sv0X zQjJyz9DU;)w|H&d$!+K`m98APj|XR-^$_DkbrK}sXjfOqQX_sbk9)_;my)_F-o$;q zZy66wkG$0(g~>3w`O#NdZ$1AdYCjz89tnPP7uoKh$(|pUf3}KGhQZ-O-^{V0#k5AY75uDMutsyrFgwMdqwY1XAFkOQP1RDVu{YdUm$wV*$X=RwsHN~`hma7Sn3Q76}S9^lc^99qy zJw}@{^1`?ud5twc@Za!&Z52RC?mwH!hiSdzuHI|wD=Ct{feT#`x=7^o{|K}<^Lv2% z;Bgu-ZL6>3cpj|8BE@RPsSu3RrwH=P;E3RTW+AQHiODlQ&oLih(>*16o8DS_ic@d5W zKj#T%HXx(s8z(&`0ET_?!R3t}*sDpHS!-VXY!#Iw@WoKaix{0CB;BKEJ5V6j?f~df z^MAhuAtf(PR~SBWK5x&)GYpH~&O4CmER3f#~(d zQDvuxO?Rm?2>foD#&})2X?unqV5y(&exNytmoLXVEJpeDrf`Cp06Hp*vmJ*N!JaP< z;AveiF^0v1>tavLJUZwTu~Co#QTl=ti7p-W35ux+0Hs0gHe5)3es}`TeYThRB=YZ) zC>|7kKNyJQlao**5$!v`$JmBsTk%#)nNpEDY0N!<2)n77yWd|t{J5BwSX<|NIJVN>1j?o|sH@abrhU*ml9A}wxi zAC7xr0=0=TL6zGts9vp*x4$!D0gsO!zv%vl7>c{3)%sSelc z6I|RF%FCHDm7T3joUAHeR_ZJ26^pwAR+N|ET8?C=9t$%DhNv=XB4n3cE?n-YckfE} z?p>#zUcDg{zNVX0L)UMj8V)NU3L#TG=LfX%rjFT?ZYMl7Kp#RSoi>R&D9|sra$%Ld z_;=L-%L|3S;d28#-M0lisC_o|$X-twIj6yK>u@2!$e!`1dXdT|!C_Zqv)6|ybsn$E zDZjtsJfwj!Dpt!Srw!O-*~My+4CV!{z~8d1J6BhYfhIad$;-133N<%?d^yj=2c>JBquL=SIQ{eD*d*~wcO zyi5R?Rp54^+o*9(=VqyG0R_k1H5BQmlC0B~7wV8z{|wrL+NlhDS5K}ZflZiQH+PWH z&ryfn5&`ef;tb`@#y4jH^qlz-ok}@V5jJx}JAxyi2v__R=n#2L za4@vcm};-eR3i%S!QSb2;mq5EqINP)X)oAfY;mU=weA}czlK= ze*{q4X^UPD1hFnAsh9KBz}5#jn-_&4u4E$#%Y!PDAbCmUJ_5miZ`!WsgzOV?wmfq@ ztv!Xu$+SV{dX_#NeW9d!3v_AAKFhdN9T=vBlih<0+$x8-m2%HXZe)f z9R%x|AcLp##}l*rdk;eNJTjvoO1mh+ao86WL!;ky48p#Go2U5yh6Vo`{QeufemCYp zX-M@UbRa|L4N%%+1Di`m1Z~BLL*{F@QwYIr@mgGO%jlZdJiGkf1yjCSKJ*>xJh>q| z_`wuN0NKPi_zw~NZdR1i@x)S&np}Aa=B~j3sW7hSP+E>59wabOy4)kl4l*gLAi#rOF^ml%W(f!$lo3cr+{o{?F7M6IEw zi38wITz0py4vxmIMngitRonU20HG%?{wIE=%5-KDt^Mzb?~g)hlDLubiXG9I#NtDx z28rOk1&)t6nXv*{+As@hiMo!7wI)sxhg_MA82)FCtsQ7MXqE9A?Y_>Jb05o~Et^*qATdG7fk zus%DVAtTTg`ys^^W7%nDo+B8stLKv-fLiLF7!ADiN!kMsx6gGa)qD_tG%&GF*KXoU ztIJVb6dy(q#QNC1|2jN`UfI_rtCLAC*{GD!p`f4!(@M`jHQyC%MN(tJkiTHdHVBS7 zJe$lOshvv#{^OGf;D+@&>L`!7A!?*^KD9BGDa$r0)K>W%xFp}mnKj$hmv9-{PR@MdW{Pf5A-MFOXOw@h|$Y+tDxwzI*S ze9K$}OTk#K$Hhw3U8i=`j0}n8`70S|Uf>Od;8b1&`pga9ZFVDp2|q&tU}6qH44a<^(b zZyWqyTbZ%$obIO_?O@1&rXcpvCv-XnFpkwxm8Kt?EGa{!)gnZnE05G!2Yvuq95{g&Fu9m} zq;?S<5xEt1CspVA+WYXl#x3W<-L>y_{oemh^T|)BEoTk=z}?x~P$KlOh_<)pI>QT3IYG=YIJ|W(MQp*MZ^%V}r7SE%08x=A2MsgKb zczONQnQ#onVkLnB8TbN$x7e|t5Fj-@_>`)kV>9-Qp-;NP)ifzaFGs2%`{v4k6;fAyOI+jl3l z8Z`l`yG|i7lo$M8fS!$!qeQ}-Fb}UqQ(GNy{~962OQSW%r?@eQ)~gB3@&zSqgS@VI zSG6lx8eP@`hD5$*oq~gt>+m{yr~0_-e;%6)jC0G`uNhJgxR3nrkt9G%qQwyW-Ned) zf;Vr#ly%{!a=cLa1Ai_z5?~(OpTi4FHFD}-iOlWew*R4G`omClJ=c0RWPGslBC4N zZ!J*ISoU~38UNz30;o6r;v@mICYOe?FxZjHT>7}d1dHD7KF4O_trFqpNLy26<+F*P z@X2ajztMOE7wk@$a`}sEF`AjTw=x^U4&wfh4L208dH%;BMcg-iTVtgJknRnbH7wgr z0u)0yD;(ljsb3dm&l4E1Og7@no(4a!SZ3_*!8DcBgr1TE%1Yv>8pU&Q-B{sGe%J{Z ze`a2`K-A%|O<7{3z|Fn22W1gW{O4N7nnRSgekvP#qS4l^Fc$5$koOx(^IqUH?Vfzh zKS_Q+{1F-uPZ%7pEj4rebM)C#;Sp(mqzlPPZhaHF>J&92drW%-b$sX18x^%hy<4>_ zYq5(vj9ulvH`!dPmi2Bw`9I4)30-5EOO~bK$l0IFbdWAk98JZ% z$oi?;vpnYjKN{|1iE(S#L8XvjCW3DP46cP7V29=vqBtt2>fmtRAm2#$DXfk7Flykz zJH+>u{=nXRRTJ{q-Sbpt(Oi2OzQD_w<6jdFzV-B3;NV};S9IAQfAHv%6Pb=>G(rhI zuUJI#1CZzKqtsiD*l}X5)?$($d&-3j&bP)P+*yn&Rip)r5Z+}TG@`H{5}-;7P$7Pu zZ$n1UN(y96LcQH(8S7P`!(hR=`|&cVQ|SSwitIsyqvbqTNazGB0KDC) zHmtg-@)WBLC)hPlwBCQk^Z2=ll-(C_v#Pf~kcH1mD2VR1|2cg#&QB(6VsxI}8|hbb z$Ls6%2Tw)tP8HQ90}$BA&m6bb2VDNt1gkAFOA9TwrejUWmI?Syn*%4eD-{IFl)go_ z;WCOcfNcSj_>c9aPQI{GYj@_^xuPtAIw6T>1i*LhU1fbvfiV85j30XBPWW7A%mwOTR>eS3Shnu!U zkE(K$RXoqxhW`LB(6|Y=X!7x!yIS~gyr=G8Z&zxDI}G~c)2ST!Wn`qZJz1rk2Saow z+kOTiP5=S-exrEq0rVrx=&NMDNxCuCc;=N)=_%%krB53=@nRo64vp{5b*D=SVC3|? zo*KbbLa+#QYU^r-PTIT-Nn7C<8ex2RY7rRzs{waK^Pr*3dwy@G{~dw&C7jI`42xGg zPcm=+#$M8HbOH-alfBoTa9LsB{0J1k)rOC$53wseOA$9&9sT`t8_`hzIi{7jWH9$! zytWVAOoa(lG%)^76|La{4m}ghlJv7klzeZNfR$V%MFVB0xXAOT%&lA{BL)EtIg#aM^{M zl~dg+C}%XR39*sdK=w4qk+r`{&a5A?qvxAzXUh82ZlLd#?W{`SxnFZ7=>#F{T>}M2 zsOoK~|M#NBp0rv^8?{@WH)oCL zoJBIX1_pfm^*Oewfhwam@h|^>CbG_n-{5Il^sW-ga@$ zvx7lb^l9-!reufW8#U2cQ-|p|xBc2%ZtoY_X5`m(-i5UfeIEb&Cld9S^w~(zs!B(i z{hdgTl=(;Si12P_ z4?{{_d_rsI93sRO3$o@3)v!!nxX7clRH=#k*|Sv9DYr7aq`d@c5;u7W_M1ITc6VtCo8gdn$V`A%vs9m^ELwvWn zE~7T7p(Y!c_(7|qoR_|7oPH!@a6&q|;29A{T>7um{}co>OBW|IwUo)@a)62Xe{>RCY@fL{^!UHku87k5&S)X(nEkG!UlK+ z1DMwTK&r#HD6$|WS@M#y!Ph?AmJW25$-B%PCz|`ht!@Y+!@e%EO1`>kfoE`JRhOU8 zT)+IDx6wA%yq-WIQ^M{0+tZ|36iIu7zCz~h&N<2z7q}5i|*x=yLWKxmzAv5=L+>C$vT1d(6xMNpWdOsfRBne2~{eS^bM+MqSa#tR=r?O2u6DWEoJM@ zy-Ti-s!pdg(C5t*tPKY>ogto>aS!IfLgE}_>EN>b& z4);k<87yilNdMM~RlDvrTSH@RMf7U1+Li3LfwsFXlD;F~CnbKky)+~CE)bp_VW9TL z*4$T@qRBS!EsoTOoTdNk_e><)mEDjxLs$X}k~)tyCH(};P~9e(N#diI7-0*_EFs}j zrhWAXlbQ0*tOt&7wuUL_4cQRrr-Jy_0R`ucUL9NOD7Xdou~nHI#q5aVA2D(Uir1v#Y@g)&ziUfjjVgl)ksk~DW85x=b_wB z_UgAbJ_k4d+pG^MfFj`i#{kSRKl?7T_r?hQTovxKw1UlM_Em_C6Lk^kL!IS^(4oC4 z9N8+ko~%1T6dL3e_!t?T>D4WQhn3QE^st#1v=7xUffkb~Rw^JsBTw5cT8 zK4~jsdaMGo0!A#dcdJa3$*pNXeR}QWQ&r2&F_XwEBo*%K8gVgl)@zCHq97`z_h-tX zEi$rguOI_2pssoH=XdX}1_Bq5J`I8ZHW4p5H26mjJ$4Cw&Bx)xYz8s#T4CF@m$215 zlRLEwMRp3>K$w9zdKAxOMwAyWsyp zOC!F8?KTondcbs4uE1$K3@n@nS1l^j}Ianp?v?Ba?E#4=rr0##zRXD3dw$sZX@{DT++UoG=& ztxz~izoWMzu0QER<&{!CZyq{w{VU zw)W3HDLfa*b%?LAQd-HYjnsNT0^S8T_FpV!kAS{3M9zm{(0|J4_ydfi* zFFSYL>xbV#8)Lp72)1w!WJWe4KXP)?R?+u&)7OmSee=48m3s_+J^ABfm%dNbFR2~D z$jU45pfr@hAZcU?iGHZfn{^4+T9Kp`AIps{ioaMTqeCD%*M*C#LsC(CgkoMgad9atn0W2;%t!b8M6kJZc`aRQ`{ zXLE(sJ6s%FUss#oL7%%XH9zmPF3J5fUegn%5ZOmtC{Xu zBql5M+jMFUQ25FZaI4k^3|}JaX7`bv5J>FQVcM?;c@P_u(-hw8vL9zvJSxA28Gs#I zPlale&NSho#i`_sUq}8j&1Pyq6^8=8btg zlK;5ZRX)Sk7hw$C>O-n^pT^CbFhKYc4GA5(w$#xVZXbWx>ASXmJCd;i1sdoF0L{Q@ z0LUF9wCfyrP%66HYK^HQ~PaqIZ>RJ=*s4xBmHc$DFI(i$K|8LCH)Vj9cYbg?l~9aC!Ec5L5pbRR%USn zpYoHZ5s5Ma+@nKvE^W$nlYHrgaFN3mCmKC^2O zo*vx?|Dy4W)yzTZxlI7%@ih;2&hOu)u&$(JF>CDGo$^WoTN59(S-wRccP$zaqCkuv z3<9bACcpLG2ZmSsvkKgy))XWu{t{wd4DtMiv;Emj?HkCw+6270*Q?z0Pvs1ke}ss| z;82Th?n2BRc6;XREKa);JvzC#eQ&&{G;4esr}v;v_~4%s07%EK8hm+q0~@tc>P|S) zHEtfSj{&A}Y6$GU2HGUskM1JI5T=1sQVaT)cq{kxAz`~jHhYG`Pbz9-BzM|}*7I7C7Viz;XZ&PqPUVwRt9^4hsW6yZh9{90|& z)dBxa({4AbL;|C=I7RQmS@ds)D>ER4wnw)v%}rk^;}kS#f3j1vkvBAxhJ}Jgekryr zlibK+eS?^B%cFax*&;ULyZc8XXz2}3w{v^}46WaXMBsm|L2s2e;Fm%{1vf3Uqt8IB)Xs zzQmpQ5{<4$bDTTh_%x%sM%~vlRxlm(p+$+sy3#65{M!e)4vN^QjY&k9L+{K*N zJ>KgVp&zgv)}A#zv&POgcCW%$Qfo7!tDW)@jxX}jHu#YIuo`B{)}>EcU~a(e{MpaC zV`m@7Q@%MF7ZRxUQOmlU%wEEm86>ra^!?mZ@m(073P)d z^tH{Q`pt0D9b?&SSkz=|O`$Q{%_-~!-HB9>Z|)~#-M@;&4-6T#@d8usHx4Yskhg#)zdKI|22@g)vhb7vSeo9G!cn7p=)NeAE$Oq7q)!_UT z+<0nuBhJ>Qf<4zH&%TKE@D$wO4wXKkCHlBbKd#2E6W1(fc!?&k!J3rIL86(UsW17Q zTkqf-ByvQKx^vrarOM1263Sx-vb3vnyqjOcb^F+3PM%~6`(`g`Bh%2mA~WNJOk8*G z6;M8Ci?er~hhbhzFQS&ET>9nX)75Hx%z@A%ar z_KJEEWcS^UVc-cc68mGyxeTcFjCkZaAHPD+>F4;zvT^^#2!-;5DxUPt7A`g+Sx1rvuIwB;zHp1!)SAI9GA|p2_VpUFSoh~l@h8uWUueL#f5{1}%a(JG&_myUNUdlL^XxcJ&2gJo$$}HW~Eob|x9%?g1kMCQj<;K+0EumWHJK zF%m@JE#cq(*&f#?m$&^{VV$W)Qu#4Hy;zXywpVm(({!4-OSEle;WJ@0jokJTchh0N z$2>V80lfd1B90j6|FsJ7 zbF2n`b8`pi+kKbhpyyd|j0DDhB%=i1NLm4Ucpv$`-2Xh5sZ&TbCu}}^W-4NFj8}AomX(0A5y`gi*UGl~=72<^J70{@W>; z8T(8O0}#g2r(H_EHmD^C+owV%9L~5hMkc1G;~)K=wWibw`$5)qyd6%22+#|QRVQx3 zVp&;EoU{!5f^LWdD{VHcp^HWm7os1DgaZNEV6~6b`lt)b2zG=j{lh^`jC+vzwO!TI z&Z)&)G4p9O9^15-Z(=zUgY63_h;!hCJY?c)srd27{e*}Yzg9x4KVc+8J-b4r=vH(8 z`19-bFkT!Vsr!GZgsgq;nvDHls?ykZy#*?#3bfRg$*uOM6kQP(de;F~dRA{YF)4=+%KNYTKfdaM;fJtd_y+Rjnl)_HxRI+wp(D=5 zdh!3IPk17y>SUBZ0yA4h)gWTCk8FSd$T()Zw;64b->Dp7PwHJZP^oKg@kI3TMkX+? zo&6dHWhJ&g^%B4}8C{Wmw(Q_DB>e)MHIjn-&&%2WVwBx7jvL`4@hkUc=GR_d34d*R zUWmm^Q~0qt@}*!f?B_EysIAlag^&8#>uaFnsSy>ZP43=l`QMn?lKE|Pa~eajedAyJ zJ?GnM@aicdi{Atz+W3OHO55v0K>z{v)RDoQ2}RUE{t{Mu62nIA<~|$J&avch!0MJ6 z(=l6c4*-P{L=G~=0 zcJ_;rOb{_%%$BfoWl-%4cvUV%ll9uA#xZ0b5$GX>PSnKjp6*RFfQPr~Su7|{DfhG*@Ppv5`; zXlUww=EvHc)_wdt%sZa<0JO)R_TY2v1{ohKG|Cm(;^w@_xuzlD7$~(>RnBR^!AnCC zw&>MSHcI}sfd#x`rcZ8YEoXmg<*t&X-E|xs+fU|rrcjS1u%O&gV3s6-$r#fly7;}5 zV4D013Hxes7cHP7V)lj+aMK03h!c`!n_xm=eGDsgkWf{0vKE18`;Lr7bH~>`M;|&-l znEJ$ot5^A>Ci`{k-ggWgDb!T01&BZ9jMdSIHc>ShlzyuDtJi|D4ToS#YZV@lo%+G=#YGjofTLr!tqk__(tSYL@ht>;W2WhU&|P_QA@uN~wpH2H z7yyS+V~N#xxu`aXaa;5*+^*nEJpEbn^cZvCPU-qVL?CL^bO{`vInA*GkBBZ5Gmo!m zqm%pNLK6$zFG~B@U7H&PV>MC+y%?)KP51-d_p^Q9ihC5B07rQuI#*4)%FKvQn99f+ zR6H~~vM=hldd`^EzRNq+dbI#`H_>D2Yl8w9Zx8-2?eGse- zf!!dA{keUZ%aE6;#-7*mi9Mxi{pWn?ng8rJuwWg;L-XrplOg5>lGl=A50k}VC(X#e zn}=wT7TLy&gAUXOMksF&{~kh~xB`xtv?eK^mglwO(!HuM4pdmL!7W-p&q5*canLtX z0iA7p+#Vj%wZ|~a0!5aS_96VugCN`VSKcID~z<^H6P}S!kfufEK?q-SVixX^4?k`Gvdg1fWL{r6Ms#l`( z+SIzu;RU+J&LP8`k-DlGR#0y)&gIS4WZdHU6ecA$jnE+DH0;){#x3#55%(Lr=!nT) zSuIXa)W#Uo5ma*lbDOsdoE=H1Bs-Q9G)`7J7jIXF0@+pfNVVeWPj=`P0ultJ9$_Ed zQ9SW2xUvm4UJb~?GoB6qV}$ZsQMkH`fM$0xj2I!1jrT+T7$|A~cKYvAxb|;pt!W-& zva8uIvEmx!;S7^*2GjxCr>r$qMn!F2nf(0yX>lxubNG?2uxklWCswPK(!Tp6!HZyq zBWbnpKX*4;V?@DsvNO*&lY%3I&&wTE%c_v>@%7d?E_?so4-bn-UOb{SA~(T7Zg15k z^n6%4$ahQU2&MNE!bLWI3IAlQ0^N+yW#vy<_B87*k4CS>+`Qg}LF^2q!wype{e-lW zBSE{dxxft$Cz#4ZvjpoiDa@jc@yf7S&c%(WTInzeQ`Fw!nqtsPD3vC(6eIqL8p3Dc z^uN@KHuJ11^241L4PVPD72h@PH{ea<)Fec~n69jZ%2M086lzi%@p(T*5SB1pB=?#A z5$E3rzb*kBVVntSEn8xqEly{zjebt%fP`(`&9q8lz2kMIHr^R1-Myu=wA}g5S9CH- z%tabZQ@n6~dqdbL7KM>}-}%VSCqGbYny*I&pAOr*a>`Nj0VqS>Fp?E~VcR&XL2O@c>4XImQY$BYkvc%kyx8qL@Ug-s9cpBD`4mS# zJHOytz0uihQ=}R>7v?7EH6jFYm`Cw`ew{dkmt6gF}&ds?6P3hycHQc9`GTY+djL8rrBjEHO?^HbQ)h`^N z3Aj=i;Pvj|wh7 zlpx^vs4ctj(7PM+UdjIlXvc@LQw&mfP@RmPtb!9Q4DaPfy~V)W-P^-(d_2!J0`UhC z(7aJIZnygXs8(*b*pCf1`Qh@wyxz2DT(K1}1daN4U0(H(1H^PAv6dU;q<=*mybjWw z6c`1r%ySO5^&E(aBYJHkABsB28Nj8v<=!_r9LU~@SF)E6s$}7D#DY6EcYZzt@}l9G ze&8zVx6?E9(R^61z#^+1Ipx#A$1a@qbOg}9`{I;zf=bWJResQ&3Wo^Vw{(>17|s;y zuWRP|C5@HO;7pTLC2@%)>whnGYBJ>%+CmmyG)H3!K|%INGtE1u1;`Hap9`iv~ zjZGvAl`S`Kd7{%Dj_#B0>N{=Wqx25ylFMOyU=MF4UmAeA-|hL)$1*ZV|1uO78|r<{ z@7|bwkTnb4a~MB;7!Uc*y+0k2!GT%9u6}aQ8CrqQZr&f6T81Om6V0W%>l5mNC8W6Y z1qNK8xT}=S#p<8o)sP%Rkoj;T7bEaLU41)Vi#F1=XRCa`J5TBIPT5HPd2K=RIt`}e zrw~ysC~A2<+edF{Fn2G09Fnp&j5meki@TCtB026Kw(`?w3@IYa~k#v ze@qSj4fO^o2kk%^Q0;9a{h0hocWt=B-o3c=DGn_qI5lY&<)~`72}N8l$FbwkXx3unBgty;y|%<4M-w6NJ#=-Pfyu{I5a{x+nON z0a`tkP8kyr>fb{VaoGKL-OoN|!+!@QA>jd(egHa)d?)`@b)ElWV&>7%V&wc)7Za}5 zUGiQm;GtKjEzs)jSpY{tL+ND zPBJm?D0Mg}1G0eS(mwBnR_~_-dqKL?^HGkS5sb6~U+uo-oA6JC^46EcYw+vfTo!KR zRAHgM|9k&Cs-yM-(3ux##L!iF^OUu`5?#P+-eOFV(;ONE`X8NfrnCWtzU}!)tNxu= zh;e~kGo7N+xbeyg3@aOOtr?l=z?~KLb7`s#X=9>%qz+z%^#vedDt+N!l8~I9FkrR9 zeiENV6aLQia_Npv9^EPOueF@v94hDiEx4c}I0~7hQnQNBPea z?h^ykVijIvgGk|Fc8G>(qFxD+ltw7gw?EygeCMW(1`QM-$0Y&$565VpQ#(&C`Y_x) z8Z|omuC@-$3(Mn?TGNp-NrhZ>iIv*?m69g>`}t#rm#TR&9N z%fB>>o?R9-RpWsYU5j%22AHlQkW_>=18S^c3VIcpJ!)ze*k5 zeL|)95IRs!1RhYf02o#Y*@I|G_qPG+kzH!ewG?1(bMAM+9i3`OiW?3DJr8D>?SEI& zFmqvfy9bb)F!R6zlK#ty#MbO$<_p6Fbf4DLnQvd{We)NTHVR; z_4;+kL4X!0;Wd3?!!q-&8ddG(W58ekdu>%IGk}hv0DJfXqm6RSu$ApP*GwpHE~M~c z8Vl7h@yhlRUlsfJQgX{!`{0jjwDz%SLAN8s6vh|%snUi_h)zV{Tt4({`WM`YN6TwB zy#ZnvJ+Al1Ot~C|qTc(6gbUAnN@G651ZA9m;_8+SoFW%DbK9qN100eiL zA8HJItn1O5!)fKtkogBo=?Z~*NGHvUy4tvd%asBVet@LYNh8(-fK^d#pSVT@qXJYezX zQwH0E=@uU*dSvXZQcy1p`HRzG5kkajsd6|*gb zj!@}#4m4F#bNDjeO=f&8Y;&_2yn!-XbcVk?rr{=?SHsDxM0k7nHh4 zCzYBcw-GBmV+n5ElaiI~K%&QjXda~^&!ECD$XoIK9>n&1-q&Q~Z9W|@1Ql?y9x5|? z-Xx+uO>?ZxisQ4}PP+Y_)0_sV%LI{y&uJtBI*A^nFhPP%c%Utm#|tqnh00G<+!?Zi zLPY;xgbvbne!vY48$hblH;cPq-_f{3M&-5J6DWE>z?kjx;>1ODIHS8)w8|)#vt6fw zqRux$?qe#%I7#ZYIJ@}YX6;-9WBQS`hYD~ycz(zEi{+n*>U2;}Vo8S$F<5f|)ziRQ z9Fu#&>k*(R%;8?sVo6Oh2Auf$QD_qTPEAWm18x#bWvL|{pAnfYQ83od zYw{`<%x5E9&Y&?eUMrPmNviB&+fSi!9vsc@^EVEyt3-m2{EGBfZ*x!x%KhRH8;JmVm{)p3!fM~)OFdLL0a3=bV0JbktyMw;=7uAR5V%x}suXn|y#~K}BdBC9q-ahEBThS$< z9RKN9B3Z^dVaQ+iKamrGJAIXK7xyoG@yeOvDQCjyHB2x)4c_wrG&m!;>7GYWMPP`h zEb~hT;v3cq5|`Qcsf2e14s|P)eVmT>Uhm)q(0iFClWNR5Jf!y9Z?U^2LmX79U<*`2rhC$7^)Qjp24pMCH-bUu@*GSH;v+rTco<`E{U%JLM^`clg#$Ha5R^ z8MOtkQHQ%=vdO3ylbyn2SEFJ1!L=XWRK>7j{H%)LRFlM7?(%JRu)ur#n79j}H)@~Q zg@aE6|42I!f@*%s`Ex+S%~#fj!frgml`wq=(PuOzZF~fl>rxUhj_5G46VaMZn9nlH z3N^{{(6RpOAVA@<%OdaRIgN}6Xjy}bEbaeTx(2q)+AjKJ+jdQuIN3GXc;jT-_N2+S zZQGh`*JRuF7rtL`UuU1S)?OR;IXv9C&dnzWs^m521?G%aORCImrX&TH-Pwz@)L$P~ zRQ%j`2v7YFV6H-L@>8W&R(UnjnC$mHn48p7M*oxd9;jqDCv6(*ez+1uQoXf7Ojrzc;Ov>F&F#sF{JE&il{gm<`i+NYd2*f-KP76*5c8 zjGgI1H!l3}@MznUzo*8dL;6gwil1|DZ3jRA$c~r7uklnZvKO_=$sHIoOD@DG<{Wp@ z0w(KAx}a3NZ&hrP_%CAjrawm%%G@6@m06x3{;NPK+jjkqLgE!#Uvb_?Ai=5GlD3cM zv%kcbAsszc&w)S&(csh~PBqU^>Oi)iTr&1 z^&!>5&+A~R`<{SG@N5x7V+YBaN|{`-G%iqs+xZ66s>xmK=gTwbvF-Xz@0mvn%J~W# zdoAGg@4@DMY)|`w6hrn)9^<-|N70W=m&I18=Z%Ue*8q<4o`m&^LNOXtUr-U%tH{Njo))IR)HFzxpH2)ado=n@lBBD=RzVGl3?)z>?09N$6tTW$Ymr<&N+bHYI_aa&@bJGOwGQ$Ji_hv=7UrXl;dlj9){Y~r zs-<7-J$G&-xOvIy*{@uVSp1_W6&aUjIy^`v%sQVAJUx`F9M_dktV~>J*@%pe z3Rv2EVgz-G6$}G8ZVtz6DudAd!vmd?X^1Z0Uj8V)CUW;vUVIa`ICvdxa zk=}WOuzn_Yqjn=@@;h#eZZq1YaEo9hIqD13)7KYa|E9V~f`p7k6g6@@@>AIG(bKLx zWNqBRlWV+V!zRrx!>(hkLisOn$7>(gDZBT<6q|DIonts-h52%5|EY1&^PV`iP}m^S zt<-D$G7dkN{y69yCz~Gl_iseNArT@Q zh&BOOw}xo$)p~W)tn@`fd(iY;aqix-9PNzPGG)wI(It-&D~{oHU#HQ|2~Lh3AenQd zh|Ke0jm@RwD5JqpTRDrKy zdf?$!@jL_yn?YBqiEn1OgUiP|#yEe8+jkfCYM;m?45jeRN=~|l2+JitnyEL!{Ro!y z{dP6K3%G!xslCK8e9o@z-yFP?$7ymgD6cgGZHc0ir!1gMLQwB{M960lYXk)FZgnvb z`E;xwTqQ5}0t*+#5x-?s@xNbfiBs{jPcj0U#i;OeB_^t9SX#3tm2rd5a6;QfVKJa<4PMt)|F zc6$fE9m97n-1rzjoqSJa2F9m@S;$|5cbmKd)-ngct36HFQ^eq_#889}D;@~M6oe^% z;?%hglz=Du53rG@f+^$7|J?sO#u2&Bl~wcuoPz`VFaZn_3AtFd#^Y#SWDZN=8mMlk z+6-Y$E)IT=LP}LBE>K}4ExDN{W_Zk^Ud)zOXJ-wDsG+&qMn z-}h_STSoIh(ZmWP1IX99Ded@tw!(3r*{C++7~R~)W~gc7GZET@^52^9n!h4X4hcF2 zczyut?xpT6^u&u7p*p`~Z*zHGn=X2Wo=OS?0e#~pJsP^KuJQ}YJ01eFuX4)(IcM;c zGAgLYHc&FST4Bz<5*9w;c4;}<`RPirh49}g1|>0+woQ!tMOsYeNeBR6q%B^(<#&!4 zQn|QDP|SVHf{f2W@+L@UGvBVxiC_1iZd+Y7kGJ@K0(DU>Zm)Eq-q!;bZmTj55R>j zr(N#g#3$omJ{aeC_WL}MgSG=;WaB!Hl!M?!u5VT+FRITb1LFNK-dGxg!eMkK7{gCz6BC0PuiFX%mn-F(th(-V@KqG39-^2GsAuj)$ zrl%xa)jeDlLh<6Gq?VymOpKBm{#W3YJ_3KPXbG}WlCa_LCF#H_TyprwkTYd#z|Je! zZGQY`>v-P53PN<|?;vhn>{qr`XM1?>a$HB_AGqSSUg$Dck5pG?fArr_Oy(skC2cTy z=1Eb=qXD&BT!NN%$+Gr8`gs5AWZ5%!k|9({A=S^dgd`s}be50!l)8U}?@|{@hR|)& zy+?H=hM7?Wbe4V!4)N$EMH#Y^nS8P5m?Bk%e2L*lxp?uEr=XeDx`2yayDIGBcGy*S zb7*xB)*^&5Y;KFi7TVyM;NQROa#xEf1|U%~@NZ#tU?Yvj5HW=eT=uj2FePtt2+UWz$K@#h)P3dkrO`p z`(pT?t#!b+1eztsDLJ!wskAv(SC->?c7KgFj?dGL1Nqho0CbBKgj~7QmDQ9RGU8#c zcw}{as>zr~EqQi57MPWzcMuE>)IvJNqtaDn=HM9p-Dyl$N{zv3x%+%44fW%D^qlwl zifd-)&Go$8x6976Du0gK0IR)k%eO9CmR0b1q;=s!+VN-&sq_Zg!n<_}cxI(S*`JKN zLxfBP61@f5xO4eKLXMvI`?y)@Auc3{#FXdTS)nU5vwb^X$_W0`3oMfu)EoXoZD^W(&Lga1d6g-2KqG=YN)n9xq$s50*;aG& zS8j<$JWP=BWZ~@YW0kQ`df41-*ijQmiH{9mRtWd{4l=xHdT2HdVvKxzDLU}~R z5pxn7{^on;i8}_LSRdh&sfn-7FCUqm?9ZeW9LrzL^N?DB{bakn0O2`;cY_KWBt=V2 z-1>Qzs_bDSYs}VbKzE^g*D+*!bGs?{>}9O@%eUjK>OJ57uEp#x+TG-CQdHfmBJ*C#ASLY#qXCx@$>y3#Mg0iTvibd3)C?-7fJ!pPZR>AU}>v52} z;jNZ#W572^p!LrLt9!)HmKB(2Mb*zErmmy#LbS32jV2^lS7@={(rb$Yo=zggDtS78 zwobRhf%U7qwcej?Ns{d6pe@V9Hv{Dhq4r`8~y zLu_m!+inRS^1|rt^ljO?9f2e#P8s4OytQ4t?7c*EJBxGEXWPa{DEt_%w0oa7L$n+KX~Ls(YQ|f$PcZ1}o2d zIYwMPQrN6={QepEOo)#J8^uQWjNaALaA?Tdl(@z*BQ;5m6g4Z}=8P(Z@pv**+)K?D ziegB$Pfht&sk4vUX|cN!Gp#J;6aa@aos&;8sOeBf#W1QT{~Rqhb9CPoe0-u^>eQIK z>{XgwZt3DR6=M}(g_i154CLmWAgr0)@<~>%Lidqe>Do}ch}#ieo|-SM>*3>7FYshDy^5Wrcrip z+KSVymQX{d*G@@{>(!*=17adzwe(=p1m&9{@(Ow3S7cXdA>XlYnOf^{&|@z0r&~R#}5g3`OvxhP2lb<61SX6 zG$5P0lW30;0$mpprstCs+vOKJ9}+5HTmgWveuNIr!0V~5iQ>8;G}P_J2@6j)^t{#Z z9kVLcD+%{A-1^cB(fYDzAW;h|{bu>vI=&{cjLwgI5$HH-ek^`b7MeBw#~JI_Xtg6nt-uLe;`^Ts3(>`jy^5orXHQwKsYTaw6s5f6d>~BheyU#(*K|6RRq!$r6tM9ZNJ$N@Yg`PyU2nCb zU*(qfb__0}^~o?D-%Rq(ir^D+_n;s7Et4Oy{QYKhR+v_|$;A%>j@V!4*1brfeI^Di zz6r<}oROtDzzWc%IH@*&o7m84Jx;2o`~5}Xhh|uHlUeUY@8;(Wa7h`757!}R9-DUU( zG2*Ajy9c}j-W%e0hw#~9t0qE2TDARB=33~6t^%SpsGN+*7I32Cwko+iE;Zr90IH}B zwUMu74!2ZZ;@O4Du31e*I|Uh}9KC*VxFvRN+`g)j#8OXE8TJzyur{(*`dCEbk2Jg3 z`n>~(e_mwt2>VGH%E2$l+Uv?bM+PkCJ--osd#qSijAPyk=6VrDb5Dh7tdc zBZ-pv&ID#IDpB;(L61o+mTc3!cG|2-{`m6=IC%ze~GvDgM-+ zvis6&2kgcU{}pIt~wlzD1f5!8zk7oHEf)9x&b6lpHN zMNj~U)P5`{2$fj=3?X>g`+!~#Unk;|1 z3QAXM&Nj=H7K@2s_3T>+aOb&iF9+%5JP?uf-Zh||5V73(9AKsR@R*oOTca)>x7tf! z&s6_~rxQL=Yp>v+*{&Ud?{Zf#3ULeB+)#wsSP7C9gSmqAzZh{4Y<(rTijTwhwSYG+ zI74`U9(Aig)7kCizQ8|aa*UwGUeiyKv^5N+RE|7?9EVryCX2XTulKrm|5X)FSRz5P zZO=nd`MnENYl#zumHh+k;@;(|sZtn=6o_lTO9iQTmIThad$$TjxAl_m;3%)8cIszy zf~o}sCH4Fl;l6mI{>Yo?;+D&fm=V*)1*~@SA3@p%mLdy<2+nQq;m_GKs@D<6OLU z&c8qzq70b#ljCPkAB^s;+sep^4vFDAm-<@x=f(8&K%+mxeo+!r9OaNj70mQ|2n> zL&$+y1+04t49Z?#MHJMB%sUU5f^=(%7&zg~ED8s0r*ZvoXc&2`jVUN`q%Ng8L#r38 z25BH0IX6|=1xGj-w*&BU#p^#BJ`{kd1S(07$QFHh3Up>RY?FaI`cMj;GK4cz1jL5UW%cJ zD;JLSW1bxy0rOMO+Rmqt0-_=G$GC-US zB41od&)Tgk*B2gFPy!#0G|BZW?>u+fYF`=`i${-qNWIDLHStc^o~fkda6_Q=T6p-$V2M8fpi-vGr&tLPOIW8P|?<*A#mmYgRHjcCZTIS z34YN%a*1W5QQ6N+s6rHaX7^6$yFmfE4<&Y&WTX=tBwNFKv_cOpZr8~y(~W$;yJ#(l z%;(zxx9bpN-iNTm+AG?S%)v_z|MM?|G-_1c3nrn?fQS{Gpb#Wr`h)P!3&7nMYB0?6 zz+7J6omgR;o4gwD8XpY)4fg0Sioj{{luZ26^4)Mw+FpJ+MY4v^lvJi*8-1v5yWb=8 zl6?9N6gJ;kjno7R!u0ICVRw9NK?-!9%bS=@uy!+y`Qty^NZwX~o@oAQ{dJ41KG))I z?)l(V?0|POZ}@5E*$#=1=w|^ z*$!#h+ygjrYstYIK;yX$mVm`an_NlCW3?CV5T>j@KNAHCXLF-1>P!+PPnB4 zc|hSCTkr10tyPF59O6YXi0Ik1M4_RBRFm6UB#%vCkk$N3Ty7~DHw59&rWO&a9@Bn2 z`w*XhJZ04)C1VzWsPUfK+pC!x$&5jy0M*x+Zb*bjH3_@viGLb9-9CC#-E|wb$VNnU zknvWJr=Nv{HvUH;(IZ0mOeewBhZ54(5sPlHsJ<3@CG=f&fFMYPM*l&=V@suJS@R}`2Hd}XjOiJ&Qw z^K8(Opw7S?#r@LoZvzW1oXj+(*+`PWXM%(IXpth0q(OI|_@6Oz$C=StM5DjD)9s%H zoEtjtsmX*e8&W@GT%`mO%QA|_WZQ@@jbZ|Nh@7&E&mrNp{N;`ZV^=j5eLs}T`x+-I>r4rFh?@xXoO zXb7EmeQtVCfNQpOZ6|xs6m7FNs_{mFuQbpOD<7TF{%oKpv1q$EEodRSHII4|UfMK1 zu2qs9mP%KpZ~sMirw?BhTIFqRMZ#QcJctm|mCpMgth^?yu=sBRRursN&$$)1m4i)J zEW_2;)O+6f*$^23nMA7z_AwC}l(NiD6qO(d57uuV%NP3+Q;B0EQkAEHbjTrHUU&Kg z6!zHkd09nz%(1G+xgzsQa>nr{`^*9x%?LP)=0hxGwF zxl}@25(#b-trU$>m6X=l0G!zxx`nf+wJoD2p;m>we`y7JdmM3Q* zc6G(oMF_hmo-tpCgMA#nCdt=0cTiEt70uF8!rtBqS0TuT%{f8Y}G+^Opt3&(| zTlFOtT%lEpK*PZOx4VArCQI<|?DvDfl>4Jug^)nnXuv*R00Dm({`)8Twg1c^wLGYr zP8&+23`oCgpvN!m75bHi;h8xc$LQ5;%~!7)!c9IjM%I%)o>{PDFVqaNzwxgMwu|5A9eHC%c zAE5iI1-=o~)qJ869^4Qyf^5YA7XxM->UL4l+IL5>_&u~jA~nO00nPnl1tFm^5hXD_ zEx%OONJCIn6;ooN=w`8Nd_;MzV)dGTaGns`tF%QFcmWfwJd{1lgfWaD{{@Ar|A#3| z$565U#ulyCLw3ot2=N!_-n)dbodWQX7=4gM02!3I@RcVHY}dT~6=7fO;|9A~kolEz z%e9JUco-Af8@FXMRtI=(@xYZu<*3^{3p*d7`d_jW3Jyd-nbyJ&H#p>}h+~|8MS-o6 zNrP9+CD+utqU5FACpfEza}%ut=_gOe2Gj3qWtzS;{vODulNXQ~OAKjboeYgkJ`_@= z1n1sFbbxfrR!riWF}=h*3Kn7`l3IpAejlF7+15+Cjq7dBm>=m$)RClJapl;Tpmk~G z?k;=^`zsz;YbJM3@9u^OsMmpm$I+`U=xJFrCr=B79F2Wb) zKt3=>2}<~EQ@gaXH*@S%=z}vxa!l0#v%f()`JcUGO4()y`b$Mjw_gM0nb_U5)-ue% zAKJJ>ZwX!pNe*c<-I+|}5iU^SH7cml8uFH>iTOXoP?;$d9M2YxlB=iTL$pjyzq-`c zt~r6@S(c66U}d7x`}dc+-Z8gM@ln3cdHz2CGrST(cD-F|Tg|&_HmQO6av<}fPm-aj z6qBzYU@OLkBFuF&MIWnozlr6Y+Nm;u)|ah1{Ox-_XowqSJ>Z+{I?(EGZ)kiZ zXP2^4gAxW%dUn+pL_Y8-i~mSG$0Bq(rkDvzz2bdWShnt1lFOSGux1uU`mwlU&xi=$ zGRMPwusGdwv;1Nqyk7qm(2|Wne%J2+c8p$fM;78DwY){2@$;&kO>18=gB=37XdJQe zQTxey^(C|DAJ1Mr$jA0lAiA$ ?*P;xLSmft+f-sD>q#W*}5Tm^{-48pv7c-AM^n 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�znTxNLIBD8&TY8B41|8NenD#Qxb9AN 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�JFOi#k#23}q6?4pV)2UaJ1Zs{BY zIfS4N4D^nPY?&hpj8c92IZBvUMeHgdSD@k%dp9J?+VCcO5K9;gi5KckyyVa9F(#g5 zSLTcn4!%=$E*sv~UCe$Of>3_@32CuwVi@uAK2tRDKe{f-7&eFM9pz9-CdT zitGi7e6#qnNYt!^++|*ykVP*gim47%!f4oOvw$^jqjv5r*l0gucU5_7eg3h~{!{)eL&57>tK78`a4`LA}@tV-4L4 zDJk`k!DQbq71Z0s*=qTdkgztZd#zf&3n2{l>6P-qwhcZ?bF24WBiaH*(6jD`z{PI= z`ih{DHb$))>c@{?XZSdKL?n0z<;eE-9&BXfS@L>fwfO6nkzm><-`!Q`uVXZ zbeL&$R}UinvD;HiUuwE$`PC^NkD6mw_kniVlDyeWkLj|(?DZ+?esdFOVaz}oW5sE# zfwZ=DPdL1`=byO_feXBQw=hm~9o7fBj2VIte~13Cd2~H00D1kc<}R2$sRiyDIlIDQ`wk0}Z+%t=m#a-$ z;>-fkjWeM>AW3&NWXQ9$wbnH$Vx!G)iui3br9+ggsl1E&n=e;OnH|15GOgx^5 z_}Q+Ngm;5LBgB5GTC>(CM$MWj4;OG>Uv zcj}FjZNx9gcW!DYB=kq9Q&}g9RPn44Ko?y(N1rrfe1&qyygPd#ohPg57Il1T;Vvgh zo8Hh4^o(Q6_YtCJ+`A{-4eZWBd9I8VT$$X;W7Yrucs{q+foGaw|7wnz(X?|EKbhub1b8 zn+@!H`!k-uS~ZWhf1HM#NV%~P>*kfAQA%B?Xt4 z7tbd3J?Ia$kqq%Y`^_#De>F;jB!=4zSmNiEnQ5c&G-QMg(oc-fWDKnjW@V)Ghbm zZsgJEG<+D|U_9k+5qAOBPLwPY6&6h=e5%rFgJ}G!ny;B{B?|ia7@ZS`#^jRbmUU<9 zrvDF{9&=W@@`%>2+H&KYyiOdfA1U2$|95;EM|2zvxn$#^IjZe6|1rO)0u4WMCOuv0 zm$kcwd~|kuCAbDk{piBI=flrF4#wdrq8*}V4%LlEh-jg=LM2%R5Pff;e8xG5^=?KJ9)+2HdAdD z6OZ>k!ruU(`FLH6c3;}{{SItz!27gae5o~Z{Z##0zV~1A_Im7=6-Fo1m7iEV zr({HUg@2hYd7HDRO=gkOlcaI4S9r!C%LXp~MAPx?51r|9R4~lEzi+sVmtF1Q9)3`- zK+v;xviwkA#3Bai7bF0yHMaqFlJxjjl$GvFgAbD^M_5TKlrfd&aE41+{uK~(k^{x^`QQIIMLfF-iHdwJdZSw}&kuFX+YZa`tc)Ea^ab7~IbD<> z&5)-X3q-%|~FCZ;B|kgA~bheDL4PXAf)ej-;VK+@ox*u2b_8 z>CQ0Z=!Eh#1q}Co1>StA4~-q0a2+<>DMK?B2eQ8|GMZ!vny>%xc=+ zZl!40nvU?4bU5T&#mAI?Zw%ObQYU8tc21hS8rPZOP_enRDJ?$vO?W(_m8_S>m+YW< z_XtYc^oeQl=YTqS#2!e2(B-9{vf&4 z?RD=&2X;JuK_nzj_UA6O>jAnwb^&~bm_l0rshHcae=hgs!{t(b0DQ!h_Vm6h80I@v zL4NNVx6(1U7ISd+^o|*tucPoH`I_p7BQR5quvs$Qgo>Ps$zsEz`g`RzJxC@s%F%U0 zUz_qI02uqU8QSnexfaBpm(z$Jx8bA4M!@Uu3u{q0bRAdd|4nY`d1|0HbwS3~_%F4f z{^$rP7~=u_#zJdS9Bn)&b}Zr6;duCO!ok>c$bG?AO_qv&Yc+CqEZ3O~4o_WnS3n#u zc$p-ezp;-AzDQ_u+^ZKvEc{n(=V4!5=J25HHPvDd0m%n_kLNI>l2yhAM$F%|AUtPV6f0J1t^sEz=pkk`x;y3kurn8v!=!!yv3Z4ZwL=Pw6pvH%3nRPeh1elTCs~F85jVO zPRd(hz-2j5mTFG0u0BCm@%oIt5&d5-n#pfQFyLxk4QuT#Rg(Wya0MrlnMXHUH3cA?t@m^t#e`t&U6IQ?!O zX26XoH0Xsb{KzRizG4Gri&{sLiMt2!UF#KxDET)5apVO(RHqiVrrB9Jyy>3|?d6am zb%1Z7v|tG1UdwFFS?e~OH1c~&L*=vP{aD7yjK7)6LpnL{vk50_x_*=+{gQ@|eVVV* zHPg6H)S;}arks0w&2{iRJQBPV4nh6MXqQ1sw+%kUMu6)}ufs}JARHX;?4LJ68CsQM zGNItKRSOsz)fW#!+9}9xq;f60Ro7K-icITp=8AfU@7aDH6V9&D|Mizz+}C7pUAJvs zoumgC)d3+O9R;rN?Z!GHBs7*8j*8p6v(1dY64M*EjC6n`QL{rFetvW%{(fXOKTXL2 zmi9RI@NoUcUqY0VBNSuUr|evJwsd0UTB5#8<6P3a!|`GC`&G0fuJhsIwf-G^@o(9=Av6u969U%?_vk8 z;eqf+T13qU%?jZ3jvUNmLUic+H5J6TN`_$vcr8XC% zTO!cKakA&rD8CWIlmsG$zD<1yvs3n_UXm_$T;|Ai-#=skjntOHnhDDFDMlr zJ|oVORVm$AiG-Tiio)g>C53Wjs6X#^x1IohDL2&`QL{>SZ@3Q|3cjdT86%& z2PU)$&ZrONH~(90{ByVLTcp|6jW9k5-~M9ReXFSMAZT$*R$utqz4*{v`=_KvQb8<8 zPDO;#ysCoNG^?F^?5iJ}D<{3+oq5Fk)hD1b%0)~yxuu&hcnV2I$)pTKGf5lG8T$GZ z88=L>?e6cS`LF+M{?4ZrTB8JH*AvXOr;oE{ox0*yTh8Fiy3(#bRFn0&*fh%7Nj;|+R#iUD^6G5zBqEa5b>h+1ulR3Bv?2l z?zNZBg?5H9Cf}xuX7qc;2nqi0x`TmXZ{`>*8Cio*(7U&1&i_OP^p6X3L|8_LiXv2> z({KJY>Dv;Ih=lI7CU)kt*71TA?fRt$$<7}6Mc+0(et!q=#|uwWscri)cFZ=de|yf` zE<<-E$I;{~ovH^KCP)Ww)pf*?i9)?H42>mbmFz`c;wo@iKgZbH6YY-4x>dc<))$0R z3jIUNI%hw1NFx15M5~>I*Eq5!QQ)}+S+;ZA#)4Gixw!pJGo-gz?xc}VU@x$GNVTk~z%MaJ{)Jn>A z)@58IxjV;M!+msf28I8zMNE5pZ2nO6Hng6fPGPvNlAF7NC}95_`$?=Ocg$j70_Je5 zugH}bKdHq$98;NaSJj&pv;`2&#M3MpX*6^L=KPaUCk)0Pm`cB{vY;zBl8L8^ z5rUqPY+|!7XS#}G$`P}R>XrBJ^w%G!w*U=zR=EacZMVNZT?WrKCa4H3D8EX>CAAkG ztd2j?l?06raxgg@P}iwD+&8HMr~2EfE*bn2uGrdT;;ciwAfAa8i)`cO$w&-vu@R%4n8k z6Hj`ImIE&pQl3{L(M!eaGsRSJF3WO6Tb@%1T7EwnUCfv7R_9VazGjV~HbdBKo58I% zO>v%ylya=K?1+h!zv-spjM?!(N;y;f;}KvMV4E|o4fbm}_R;=xPp+{>$l+TO5STLY?wg67~MI~Vy@<-^5d2I8nR0eG?u+T zbA6CV^H!5E)}=Z00*g68fAYBj-*ZdGZ-|L_(b@T53nqLZs9_R(Vv4qHvxx7{6Ae7HApa^{VM~Xuyj3QARR-l#v*T2Y`9L zdS(@tEbZYSDtoS9Tg154y5%pSlqAMKPg4VA{cCW31ldYm+nnh9y@|(RH79uVmlGMT zMV`35cKT0g3pTK1uLv>x`^c|&hhDbqad4Z=P)hrD4R~BFwmaFW{WJYnPg zmuRI{rYk&{ainde3x%bPFWHI|`Q5}8S=C^xBwu;1wF&I2b2%$yN-YO1XpViL$;Rro zEg+Vkpe;lx8`&>>B8 zH^0IK-T8*>@g&IM!`*yn-Yc2H5H}bfmW)qFXk^5Jqix=jBM8H+MTI7zby~P(Z220P zF+rb?4Pe21Ye~2IK^lHzW&b%61znB<>G+*T(2euZ;Q7KuX2O(ljq7}eg_-XQl|c*$bSzfQ${Y7|)i zzcB2G8>YXuUDBAyk)pWBs5{!^V;)SmaWxmbu6`gYzMjpmnE#hzq_bNa`C~lfxzFg| z;c?#6`Dp4GKM7X{q$|4F(A_PVwpdSf=$i_tiZ)7ZA{G`8)ejcwa* z8{4*R+fH6``wQmVb7t1eS~Gi}s!amZu_@wEPV7y(EDHSOwSrK?|=_+Tn{K&nP1X?*a9?4+ryLzsB-Mms~8SkS)Zi@sL1u12s ztPko$6~jF|JuEa0{jz%WaIq)fh9BNI<(ruSpwOM!*JO@+r0)^~@^(@Qb|VOHDwA^XHJW0Pa9UiqL~IS^f!6RCKSEy zT~!iq-ru8+o9UBStQaFt!d2pW_r!aNsfe{oA0w)d8zT$&j1-D*^O%HxYI9c9p6Z4c zNT&iFdp975%@(Z6xxD`whxk-3j+oy;2#Ka6^F}z%T@ z9Xroznsha48R%JV3DRDY*_c}l67TqTw1`sHr^_yE;CKK0qj!U^t{*^6yrWHVyO#{p z0`^fScY{pcI0WN3q4+6%*Vd*AW0p0g3`IEePlKBBaJ8loG7i?=g(ZMq6MD7-r{!goQ7T9h{6b@TQul?N` z8g)Ul$AgNfxwpC#!5KYg|EP>6u(;E)f%_sX&Cx7JIkk_pXMA7wZa2^PSXSJO8gVR+ z!T{&xYH5Wo{C2lAHF>smd&|_xN|G@SH}Q@$SR5X+fUCJTbghzh_@W;g^K|03x8n`( z4_ZASy#$IyLxm;c5j<3CFlkFkZ4rO`41+*NT>L(w3N8z+T#HV-|avfdX|stMlU zS(w@;lnchO08FWo@A9-pDaW!gCYj5OK3XT}*YdF|M`s&m+#i#7jOup}uc86;fw*B; z4eTlHA@h@>AL;407h)1Vx))^BM}g2E1-2RpHxV(a}r^ zNViuc+aklTfE~72zIdd@e3{dF*I9{$WJ1*QSl!+fYz(>YftHX=1x-t10H_NCZ^KqyF~r((bp_DM^@?6 zBG83^A=%r=%J=>?ZExYl+Ap~H*dY8I5D8}(asnI$@_bi?&HNynC?dGgvDQJ+kOj@) zW|U^p)-aT2u31UyaBbtMQPiy`?=m)O!_dScIz+ML+u%T zvmjI`qsa z)VT2;&5TeXKc7V_#&|NcnNcuA*Mu?C{_o7OCzKQxh%33|utkoY_s8m!YZ-UVw=1ES z)nM7`qg;IDp;YNhJfmTjf5xF8`Q|}OnM?tueS*uqT~NBx>#EzkY=#;~`cI_h<&QkO z+~n_f`gISyBEiMMH-qGzg;UGy0$7lj_x}+avIV`f>`QT$=GC`5Z$b}KD$DywQgwy4 zgBST0IPEjW(x_;Mrv7%JL1bV)=j;y%^A}DJrb?yqCon}Of*>SJ26)f|nlR)@rXl&P z&b)3C7Tg=Al{qZCurZcX0M1{bwG1%F;=uBLwf&h3!b*-V(W0z>T!V(T0eFP)d5P}!T8+5DOu@@Xz%m2V2JIBJ);J{|A|sX3UE^PSDgx8RK%k+? zI$+>^DX?E)UezcC`L#8Xli7m2S}-R{idF&EqGJYun`HNt2d^MOm}CKt_e{U$Hd9&n%LgAo<4i@c5Fgkr;x^27&v$^lTAfL&_nn z0DA}cm5(Leen8TeCwu3%KArhgVIA`El${>vN(TU%Ec-j0FYOn8Fj6i5+QzGi*&9O? z%`LKpb5?ZKaIV~Z0_IEDcbLsliudoA9GlA#0LC?b0VLL)!JnOq^2U3Ylxf*JvB^Kq zgsjT?LgMm6vTR`|Bq=fDyok6Szv4QTo`nSq=75J+sycwivv;&cEtqc z`z6h_ik7UBy^M4GzO`$+o38OS-M#Kwp=sC_*7bX9=MR^jEDmZLxpP5!-dpbpH#94M z&6N}u3-XmT6J$yNNGG_fFUUl#FIhY{0Jd|FT{)7eTQdhS4Ug)AN##G9WvahdaXf1T z9f;pFi#9&k%_=l$$72xo8Uu$iMFDtyb{1c1(q#)EO91#34<7(|iA=fzK*;pKE!5 z^6%(=pGP zaSHcQhk$sftm^0fY$lQ6GRb=4DIZdb~kHH~BLYC^Vpa=NFEF6Jnudg9`($8Uw#xQM+?yGj|oPKv&oyqlER?ID0?Wpuw!LT{8?puS%C?+JIxEA z8WD9;y2RXN%@2Xddpv?BU-KH4KlF4zj9I$BBN*QqCK`Wo?uL70Q?LQw`O1s3C;6W- z(lXjKHizfxseW-n@$i0bcY#lAOvB4MXgVl9P1ko^n`InSV)|2i>Yyts-pT-GE zT>IZuFhatjfEm})2{_Q`xbIg0pna1jWGxL)b~fv!(DXS}Vomh?p-1mLe-$&?%okZy zMhLyue$Ux5ixJ2Q8vmaZsrXpLq~@uhIZBz9Fc@zs~7<#F}B;Ww&I6M=g}AU(m$+@QSBKqKk#8w1s+?@Olwsw55XHF4GJI`F??7JUZ0P5I&UtTxGxGu; zFBH>%tXX`5-K;Lacdg9pE^m49AW}!gEl&A8z#z_5r7c+-?%GkJ2ozLWo-EiX?d>qB znWC2!w2kAWuBL7I{!%J?C_$xTY8NL7z07SPu~%a)Z6avt7^=uuin5t~Cdq$uBEeNy zbwrJIDlbLsH7el=_o-#mA~_};i1ZuZumIX{rg>T5*&Y!Ku3nh=#+}Bf9L;k5jqN2g zWA=IGGNrOXQSm0(K1BD(6KfA_}#~-#{F=RtY(_=w#zDBAt2$0>AtV5Nnyf_`bQh z5T={68tN8}|32VO6Rxd^8NQe7M|}V5e2mI-L&CW;22(0?DIu{O;cG~g`#zKCnz&l-!E{oUd?o5LBTL>p&KqMhPv7Jw{mev| zt6Su@Ih#e$u2F$-3l6L_2m$Y2DBI2SC*>k*Y%_{?t%e_|7PbrPacMSo0|G-tL3L zUxEV?w(Rn_tNKf7tk!A-qL_ikwKDvCpY)pY$78hPM1JhwPD;S2D$2L(js|hV>x-z7 zla_fdj$Wx`Z{5jdh0>&4J9+VcInVtWc?a&a5PaJHmODj9pusn>EOw`b;5ucCE%o<9 zBWc&W#$wjQxo%LGn5r1>XRGesBprOg)$9H4NF6H3OW}%$7fdERsAU{S!;G&l;rLt1 zQ0L8^ZwDlBWxu3;S;_bupVDYf8FD-eJJAo{C?T@PjdX2W0kaMX{4?-8-T|gsx>dgg zByh&mjXV_b@*uh6Nt2oG@0%G8`>kf58aV2ZDh-YzF38ZjaX^}rE38t?{u>V<)`58{ zwQeSu5LwV%mv-XQJ&WF8XAZcZRWmMax(;Z@KUQvx^1h+UX@O?ZhCnMkO1dohiLp2S zzCZ}9bcjH-<;+3#ZIfgS`r1Nn_6Pl>-GKucH|+I4~`rU&tJ1Z~lzf9IwTnu>|PHWa8`2)$!xI>TctpB$9Yk*BfYr=G?3t2RrzMMqSTE zq|+|r+Z!fU5p}QKNo4)`st${Uz_SBDz_KksxVL5jH~0|mt{38J$D)6@1$mnGA^DJJ zN4SY}N|{vM$CC^uFp)^Vr9->bM=`R3*4-?P92IbOTi(SL@Lsz-G%~#Ac!}3~oAW1u zr1Iprffo7JSN3TUsH{qq9{+ko^=UeCe&iC?Pk2PnZlW!4UGD*N69oT^`hd>{OMw%h zjQFa=Nba35aplsa$>6D5K~K?KFEefzg|PMe;{kYD&`Gr{xAl$a*DH~Zf>SR6F%4>! zF4WsW#6NWmVFbFV#xC2b$juwm+&gurzKR`3-Mp2K?iqXSnXQ>UaBN~OTPVgmuM)`` zsl{vI^M-HWz?2UJru&0!OLcoltTosq1cJGgBm<=fE1oU5KvV{TMy4 zM4wm27XREiRcpk=TzWOxCMqnMmJFeXuSn&~pm`W5zRwL_Pw@_HeBdV9#O77%L#t?} z4JrU5<~GJN?&=`}Z*2lm5&jD`DfBPgFF?F|0J2H%t5DF1VR}OIcNb=Y-h#C8yQ)sV zzToW_(N|$B2S1p_VGutIB;B%R5u5eXtQF{J*H$u2Wu;s)B&!bmlgUjG6*YQ$UCo9O zgRw)g>OZLdioWr0{<+9TxHeN@ardBykPQ`Un?9>E{c1FiGwm;0+0~-JDxaeV07s=y zgy4Hs@OGI)Sm$@>ikLcbtFwuFd0o{YxE}$1$Gu%OSKmb#kSvbAGi34(oW>;j0 zi8{XQBdA3F?jYWpnvYbu0gq78b0^dP%|z2r(O0qD)l5IHU!Fx1r1;WkLRi&%yI>yP z&TQ(Bcj=_~o%X+!gZwOXPcI9A0~>r%yWT&N<~nQjuCRT3Z#&Pvy|#@icQIy@knXA4 z*g`kL8YWAFWy@~<6k$`ujd7p`P`KS*@08{Ge>Jb*BnH(}UR~`dhW$m1;Lgh!+{fsK zQwX+evbElV`hMyLG1x1JJ_f8MS7-IPajX?#ON`%|fabKJa2-Lbsemg?YU2BicJerhh3>!Q_w(P+2Q?2)^A-JOHF z*@=la1|sEHr8q7y=z_sN^Bt^m4B%KhaSfx=Ho#9=b;_M6+E9cGk%1TJB7hkWYVdw# zG%hR{s4lq*FNsgS0$t5z)cNuEJqY!4BB!iNFIjwhbpOmr0whyB3|u5UTouS7!16n; zq{Vx|^JZVN6uB%nianuZqmSW{zSr7qAtiL20X8t>JV8Ir2gFAL92GQVJ1!I6vHmNr zQVUMr&x6P*22ss$$Eq2?E-t;M!60tHJywW%m)#cus8CQ9(IFPO*gXW@PU!h{6D_W@G;TtJR*i+_F$Klh6mJ z;Y0iMJaZ6W;-`CkkfhtK~2d;G27dI7W~Lp2_TtvdS9^h)kSyBWg{s#J4)85Es$*)_K1-co~pTqr3Lz<&ZqYNAF(c_7zI; zsr@RV-MoAVf$uN(X2UT5K0EVgjl=qF4whY-?# zaqB&DJDm&OwJ{-JHqMXi@oa9HuU4yH%fASEeFVq-%$T(u)Qd@m$mYW>7gfvV{r8!m z#9uqxF&19?_tSe5>cQAuh`$GVfEfQ?^B05h0B=nA67Tlm27!*)^cE z2nDK!zG|OrvN#+x`?y@E(8LH#gC%G+l^_g!{_k&Yb49dY-xzZht`b7=llX>XBl zXW}dC5w2D^$zS<-qI)LQZ%RQW!`9$4E)4H9Jt_K=1(YZ2Wb6A*7_XCJ(U^>{F{N&g zINzAiWI&9S&2}JddTQmC8Y9WMwe4TuC=y$jqZf?A@NUYWx)!28HA_A&;D_(Tg32D8 zq4uUUxQgemrFm}XC%UU@O>XAvRNvPe-{vN&>boa|Jp6^ibOcfF5U4VaCnhU6ULp}Q{WbXtUOHl`*q55LGv#Sg^+SDh7g!^*k?$``2am0N zPm4ju;oBKe#^XGkEv7|m?a!jWM!e{>7AwiI*hNJKj2=@6M{PUy0I)5~v){IuCEHxaMj{zcu_&!}q{0Ay#a zFb}DNhr<;t$kVMjT=R%-ev!Kf%op3=ive;vVXK?Wgg(!x-&H8WQ3s#JT8@; zW7tApLy+ELipoW&f+}u9acUFu?D!DMebz>jlv7Nukp9ZrX`|&Fs4Tjevfqc_gB!g{ z$MQ=Lcm)JQlM(*+saS@49YSO$@kUC#iFtMREm<_?{(UXawMF=5_Hf%{bls?Kjc^`B z4DEHY*0p*wH~m58^1kpmLcA*fcE=+~>1^TjU(0iLEr{9q_ytS98?A^ggbWBc+3UzI zo>m3*vfy6UJO{jzH&0FYa-4eEmMf>zVt8Q$kjN@@d`cJ%R4@QipH#&{x%WeD68)OM z0AY*Rs;=)%vF`^Vdd%~dmm*6@vdxW!Jur2JTG=ryc&_~$D&5Rme+#SB6b5idO~#g z58?974Q#?!*o;@>cwY80)k0?P=8N$uy{>3WWhx+FKh~QhTP*ji-#CVn-4<1qivzL4)3MxLq^4paa(_ zUu=_bK&0!aDaJm>4avWt2m35DsAfrkzvBVVPQPZ{n&MMisvZg4R8a(>#8+ltyH}ly zT;v1z34`?<3%{=Ue4Akfb!SqxvVw<8WC?Qrd-t@hO9vBrS8zw(yx(`nlk8>>eg3Ux z>(0Inp>Fq-JaYZ$w*s!N76y|mJ@>k7+R5v-=3dkt)W0eR`Ds4xN)8YjMY>})xE-AM zlieR=0sk|X5{C^%U&mM%@!&?G%CjOBVB0CvYGwWpP4M;#)FCG|62VYBh2eL z!F_DJ?UotRAAVmCGX2)akpYo!`XO<6H=2aOOvjtQXO^T(*5e^Q&OJ!lmg`#aSRDip zG|$)hAs25{!2n*Zj??09OLEwFw_Q>l6Pd_V0=hioN-^nI2>*L>f4X1@bbQJbWbi(m zjPM0eJjwnBara{69|Q;E{=)t2teDXG`c@h9yfc-Psvv9&dyp=E+#dfXtlW-56ft|X zE6&(Q2*T1NFM}g0ro~rOhVsw#P@iX44A}zAN_AJOl+ii9Ln^_nmt8}`*ul>LXNZ z28mLh5rbQlRIPr>^R?!f0StHW3p@Ge{d;7pkBPrkONR+Oj&yzm)m_Cjx-8=r=5#3K7Xbq zNWH);{NfXRK*h@D2W?EN3>$?36TKX(-L?`|1b8n>HYs`mY88FvPbe zd0%rpr5mS=rCYB-aQ}SJMAY+y^=@WSj1b7z;ByFjE4X!3Y3e2!I!%Yx#?i#=sSNox2@tqcb~YBu7OXdDgBEUea^!4fYLEqN#S> zsUkxi4hYZ9(}u>;KI6mLQZ6oC_2~1fkI2;P(~6*>c-@4K{s_W3ZjN4~Y3eVSsd#Tp zg|T(A??IL;SFy)Mm3#NoFnITwUjLLcc3|9nxBudMbPxN*>v;XT)7Sy5ev)eVhn9IcrbJF}%4J5i`~~+qUg^04N89Yq_FTDlnEsYdkF}G@dPV$mYVxeA1ECj0r)r_m zQaZ%q3iLxDH!D9~RN-SIbyL*|{j0ugy?(`T>4 zN`pRa*LTR^+{%_h3VCnC(#-2r>(y#wJ+`S_pY6xqbN-k0TptV3f1U^V^t>9VKYOpw&dYI5gb2RbsKB!ba>0z%atzVoS&u`?Yu`4np<#YI zY*8}0EEr~sz|hUdj*_I~&A2SauOdscr%K6*(o(y>37 z{+OWegrtESG&|zAmz<~F_ig5LY*ItGBsL*LAI6D4yXtDJr7wIw?}WYvtSHi0Vu1$p znDG+ywPvHOS6nXwFAa80d#YjJfro!gIP@|V>`68#$qC9tcN}-)6Z(!D@k4Y+VN@to zPm*RNMAd}+Qma7NAmUXULUI1K7Qj|-mdNwj1-r1sa;-gSN93wyU=^_#Xa@lEJrp>h ziABN$jOKQz&3${vQKJRp`s4Pv-aUVkXy(KNa)|k{oDG!hsLZIX{X6@`Lx6 zyFj>AVH~UKL&nH#ezlyfnkKJWDSiuj*V|diFl>?UZ+UJF`@JhVz9(|X#2OmX$9an% zD95jB=9Xs?qvvPS|6;xhsGhuk@h#^!V5Hk%rkb`+ z$O4I}wvv|q%goY>Cv zCq27O{Z`}chfRHfwtzprmzOWi-n?9)Lo))l9Kag;08~#B1!qA_c_MhL)&>zY-9Y^^ zXgHtE8Z8ZTVPRDE6XrxO`PY!dgK20_N!?q%-$nRxWxYvM`sm|z8{*+_e;-crw3by4 zs9J08n@lPwqZ;b)pq)4w&S<*cMbM3gz(5qorv1+CGwjR_#vJFX*_bND1^Tbf!~=mI zoyZel{yu?u{TDj^cXKh@M6buBl@Ox$sAwrH5nThb$?K*%jNLxmX%m)&YAf<7;jtB44STeaFR57&3P(W ztu)7LWbz@Gs68Gq-{XNJ*%wH_>&YtL%I2-cywJ zj({9twZd6D?@JbhJLeVel60({wc&{J*5ScFCdmkldF&=|KB(R`n%h?TMVWnUI`-MB z@D1zkK{gHjO5qZ30v5)eo-age9Ce9*RI=NV35QC0nH71CXXhM8tfS_9g6)KHFy8mB zO3VsrrbjuU1sCHziD@05^WB?dV?0yP z0e#KyovOxEDQ9%qPZTuB8Flw+#YM}|Fwh&bu$wa5i2FzkwfR8p7+fc?0xL8RDa0}A;Yj~oFLD#?+kh8QnePA67+5NQeF z)&sgv@`pJmIdV7-2rMyf)tUD$nWLgx2V*$8dBd@dh*$3bV(b|Q%jK;Md%%6-LUZD? z!CKUwf5k&!A5J~#C7C<^`G|fBs)UBbIyX1GAh5Md0G3#8E@yLmWSI59^mnn-0_e=p5dYPz2>c$MmH_g_N1k9JzvdqJB7CoC$W~fS4og zMYi8d#PzGS4%91){dilH-#3Slv?8w*`(3=LGuK7Q7tCn+`|9`IA`StO@yOT$*q`yJ zRYvc(XBEE~X37MMfEQiBM{lPD)X5paeKDRFQKK*ux489R;-L|SP+&dQb1xIV@*>m^ zv#YsilYnkzlitLe7e5b205I|x%Br~GG#1hNFJJF`<^_Ts5Q+RvyuYsh>Y7LpNBNM= z^ylVV*o4V8Cb3`mv*~#iBDAMO2*5FP8p=rL#|0k%z?7Q-COmHzKA4=APlibZX7P!% znN$r!Ar-rWg4cQKnkM&gBJ=*t8CADG*KV!1j!?tt2hv+7`3Zjsz92D!FPqEq5qGJ- z|M|r0tz^3P77JrVVLGISL>^h++oE4h6NjjjPX z`Y&Ui3evNPr@ww)HpHoV8dKo&#HwA*q>9PXrS&Ye>pg-E9&!rj=#DKb+JSd_)wQf% zN&J|)TilT9YOi<};~fgS^ygr1#_w}Pu|x*wbwisCN|V8+)6LuZHZ4El%*=R{ulz!G z3p1B=g;9A26TG70oc_1JeF+DIL|UdUO zm?)ui6$a>XJl+C~>yxD@2@>+}td*961O|5d+e|C-tMyfVUyBZ{BzAbn3>s}LzH$CA zW2?o{F<;n{HcI<3+h}RV(M3r}{WZf~?6quTFDOHpgPdkGrvLADWKgj;d=CYd`Q&&^ zbKj?4`mt@N4Y!b(51Jj(x#FZbEZ!2O-lomH3p=@3US&4o>5_htLgx}sNEQYz@UdOg z>xlU3eC>EdYB%hOcNy7oISun$l@@ymxl9UjYbmPlhTd&56mv6!yttTtb`HDpb~}#a zzX)h;2Cs}_`{stq7TLn`XC}(gTrc2|VB%vyLJy)T0{B}blO&rA(2euO8_Mr5XZ*M_ z5ii(8cw>fACu&sMRmME<2V&VML0NVT2PAp>vZ35;H5oG{%B1kJucFaeb4WK*;OW}Vd8NWT5YLA`65(L zz0LLRQSGt)C18hp)^H2+RKh(f^Qu*<*Ct4@!-HJ-N9_1I>>1sc^acbhZi6y!en{v? zAAulTt`Hfin166L`U!U%RVz#Y?05rb+vp~Gfgjo=FVlw=xx5}z6F;J8cXO$SJu@ub2Vfh2e9qNrQ!(j~I^JA5R-QV8z3E z#b3s6AUtTUO}blgMP!p*;1w*PbplcIUMDj${S`Z|F-A=msUsq+1Tv%&CA9`m?pCE% zz*2{Vh<%UIYOsQEYhRVTaBy2aeF=Pc@ zI|;J&ly2H@ef)938TJjs#tS~k?q}Ubt1khpyzF5r+a{W?1@ZIVPw9oUJ*Hni$rJk{ zHtvGZzm3}zR;*-@|0C1e5C+!xQ((~Zm7)0F>9kpBYRh=!A!*87{gGu5yaZ2J=^L;N zw^QkSWZuh$7cje@OETqS+qf$<)gM+fGuaJ-c>YcnvK{M~p;xtexKO|3OkVS{=}x1& z>BYpHL!?+a{In&zCo7{et3q@qP2|j=dy4uZrd}_WzTxl>Z$Lh4Jc4{RNHJZ=b6H$U z#rk1SZrt2q`G4$V(I)f|W zI{YV}lKCWa6y9I>eq3Sb-Xw>Qy9Iu49(-~7TL_==$v?k(Z7rB!4Ww$JeojvAyIEcU!z6en9-J69%V!`?}Iieu= zS0~Fe6hh zDVTIngpjYbOzE1+&T>4w%cgJFX$_=?BxgZ(05;mrG>g#ce{tssB=LEi1pww@I^1+} zp?Z@m( z|F|6fr{jySAhN;1coUZF-PKhIFWrZ`UDXGfs!Y4uOfTn?(^HQvz>=f;_cD7Vc-k0P zL4PHr!#8xd965OjtY_KL^ZbVweCy=J-cs}&v#3iu4ZSwMeV<90NuU?dqq{fjm2OsZ zy|>^>%O@u-`rinlGc4^g%)gLt1%~`W7q0J941>kvyGHomT140cJe%S&Bi8DhZiBTW z3%J|2`)+!)E^58@s@*NbHe`Cu&PUm2oLRo05%NgPB@S#@ObD#Ud->Oevo}!uiIt=e z1wl3qqfNbXz`;n@WJ{YwxRAX&z%uFCko#t;X9yl!Q409oH^@J$92-#`L^fP6T__2v zt&>84=B?~iRog!8&s@*v!~(_(0;}&L1J`Q7Cz#ny4AgJV^J?ZY`dfAG1v#8p0Z*IF?&+Y0+b83nemw!8h1b{7N1$~u8Q=hH5o(>OtAf52sA481bemR3+p z|H-vppDcO;1%Z#ihe46<$HNcBr$>k-T)}(&8CUpj-q-7+dHb9S&(-@$qJ{os%&o60 z!mOa!h-9_O0Z_sC%+^(L&+wP9zfWA?+KA>oP{QRs-{uVwWI&3v&_^N8{Kwi5`z6&{ zaQM_ncdpEv*fP@hZsAHw3cmb{d4kWFPv`gon3(Lad{N_ll3d)mgec1GuBI9d@%{C z3|a)pRAmlzG1o-=kw_zwv{t%*GL>Of?$O^|!lQNCE(RKX4)KBW9l&Kfw!B@CY{sf$ zUVp676wdY72^6wWLqli{=y&qe!lboV-EOt(1h}45XiM>fvr;z@3+RA;a;uxfT{D>Ea>)r$E^0hm)YRR#eEc=l)f(HdyB6A2&TE= z(dKRxot1C{bt8|-lA&H9z5HFvkt=k*!Fr&-fU~K_C=|dK)4DiMP#BJ5IL>nK2qU$y zgvdOjMA6rEhO>gSyx+lOfp?Evq(ze#AEU9~)+(J?cO~~Q#VxXlC+G7yBXJ?T-qbe{ zkdlWHN2QW9Eu10?9o9bxPK5U2m|lsbV{O<~2#lij>aI3?V4%*blYZLd}@a>(BSV{0PW30%pZu(Kz z<$Bi1%o0J8cIWIL-speg4OSLpHqmU7PEOR=^B3L}{ZZXW=%#F_aMvG zLX@3T=IMJH(OnN#&@-F_?8EnzJ-me#_phl7s{2$T_DyR=5!;ovE5qD`O`zwNmtWq` z`HuxA)dK_L3-(=zW`1f~!!6+Dwgm}t`!Pfetb>2_wH?r(8Q<_i56*{Ltph=2XNpTe zz@Xl^#Ttl@|7h@W3*5zN6xwIWY>g3;p(=jnS*2ysmV|Hu(iDQUdXokVgX#|}O|BCB zrH{jNo}5#rVOm^h68(p6)>mWvRBLU*kuzae@p@HckTaFXwWK1pn+jNT4y#dG1i%ldioK=C8vSy^G-BIxF>49Y! z6dd%BpX1oiv#P}Cx^bfUYuZd?lx~<#eoy6vy}y`K|65}flqH-p%s0;vG1WcilA+J8 zmuG0jxhF_r&W}#taJ!2nCk4_eEU?dEI65pKwai*LwG(w9N_Zr<8z+48J9aV0-yDwc z*hVYw-)Mm%FmC#WV~aro1&+gV>|Jfgj;!P#Y;2)=- zbWjpTMh|%X_v5r4MxG?4Pnl}L{}QeEdFwrT;i6N1SFq3nh&Os4>dl)jfQn)c+gD8h#@uHgY(N zpNr5i;;H9kEaOS|Yj904OqpQ+%{QIkGv&#Eqp=NMfP@!WxG-+JAzM;fewWMdfx9;yAB#WQaD1a( zVA-pSU2M$OOTO+84J3s<{`Wss>G(lb?{OLWb$MFJv3Lnryp4Z$c$H1!fMCVEh_JDl z#0gnO{*mI~wd>`L(r&0niG&=E8Acz!?ELUf?tM#*UQRLNsZ?Lrlp)vAvzHYE2G>|b zkc+|iLxkffa7fSr-2CC2#)iy}nxkNo^-ouH`iE?E&^Q{Dcllmj?4z5%2`9i6?&deL`_%beLQ zrb^JbzDjkJKkygv@wq5GacLrKU8al8c%H1UTjc98K>IO9mp?_A9ZL)v185i z#<|;7U5cLC4|2G$P6^55RI$W;(~(~UMJEW)X4R2UEK4 zyp}xT!mmXT>%41Ps|N$CoH6k1zZP);c#T^=I&P+;`j*Rb{8~s0+I>02gqM0KEi%6} zhwn*+p|_SD_}50NUtdJ{p*-g`=`Pb`$Lj_*g?50c~)+0h#B6MEpDn`pW z~)lP zP9b_r!&#-5xV)`z)sl}dLD17d>2G5HG~Rkt4`q~`7og8Mvhy)|jrS8WCkotnV?}$V zE@uVR$Qp=uQZ;tlnud4Om#yl*I} znF<02gx8f^Lvz);5xITSs-2;zqTlqkfI+r|Pc8bE^33fzz4`rVSWID%cCFJxgC%fOe#8x1{KwUsc1#MBX++ z7Aj-^c{SKAC-5BvM50h>Xn*F1*xvI)eyMqIf)S8D;xYn=Jzu;urbxcVqSt-DAR@ED zk^3Pm<8aTnp~@%JfOlsJi;REV3tLTGOozhF}jn zcHV{;zt2}}s)o@p&5ffvzs)yzjhT8j0dy6jljwxy*pERx+L6_$A}2%Ue6_qFWDT94&P#=T^%Nra<%u(qQ9?5)I%eo} z_+wdPMGam&uI1k$dgpAOR9DY^P`xhX6O;c;*HzI$*hXlI4#`wM>F!Hl;2t_+5Ikwi zkivYR-G9LF2_p!WDBxPFW4%S#72_VO`8##J&BRn5y}aAjKO4E)K!MPs0$Kh*;q83` z0t}C4=LDzHM>?UXb{zNR6%#s>XlxYP!Z~qpA5p1X2t>sIY$iKcMh~SNTNY{DL0V`2 zh8hE5H2Fct3GA+?3{Vd|Du8tkrxO1IIe42BI@@*t(BIXUEA&m4SSeU!8Es`s}{ZT&(1rXg@&7g zJT|NCV9h*MY`6dJm|c5c#QX8u6vX9imky*;O1h8u(v0dz#FTwfJG6IKay@kbFu}r` zz^?fk?oR91$qdFY05 z^f*Fx9KZJRp78yHdp$91x(XR&> zqUx0h%zZLcrMN)J?FUt&K1si6^K(z5JJhHbv;Bpd+p-mXB}H1%Tc#$ zNNd4heOb}ty~%^~&k%_LtKTUoJ`8&n*pdHDo$|=iOr5*f>(?*wnF&*B9zvWa9lQEU zt~HTFCmNCulRw9Lzjx%SWgxF~i-Kh(cRrd=vGif7+8nsV8FI=#NS4-cg1JsfF|rlZ zRrSJ=f=^&?Uf~^`clUCZE^BAvL0p*1)yqrhf9Ta7LEAlZWjHf{OoynB*KDiB#QI#oPF@Z}J7-2tmy zcS*0gO6oMulLS7`##y~M{hcY0moa~$ks?~@?_Ac1M(0&xku=6TFP{DzC(Mx$uw@Q| z63D)XV-WsjYOxX8R@u)2XFl~6-y*HCK@myGg;m36Uj;%`>qPytx|Z)9 z>;%zMkOZkVyUeyz>?FSZD;~79EuM977p2e9bV|L)jhlOcFzeD;e&>lk#7#{8AC6y!$wwLUVvyEk!f;@St(_U zrY6GOVkw6`cI$Y?ZnQ-PrYMd-z|H^$5+J?90<9AVi(pGv)FP`(^7tZ&*y1L^NR?gx zUvxczVK1VvOR&3~dE<>sOq|#oMF5cFW+R6{+MihsB+4h_kOBvcc(*G*yn)~eR8=t# zC6pewx>m)~u~fOfE2U?X{H0r5K=_zu!(Syb`+@#iU@GVEbsax7Q4AsUZXv#Dc2nRMLr%jV84<` zz~V3sT)sxqC>;J7LtvW^m>Shx@6M?gAo|jri=e={p9^l*&%l9j zs*vQ-m~uB5UO+-}9BCN=Ue>Ug8ZVbdP8n&;P;QRKRET}X5ugN=>IMaZ20+PM4A7)0 zuX+^QO2sbrttL&*_2_8@HZ+|UzI5~ET1jtE!jKu6EP*@wZq22*975rD!B()ZTH~J! ztq%J%$1khC?y?6C`ljWZse@b7d8iRt0Fz&fyBO5V=7oaX(=&LzUj{S3ljy}#G#Uh* zbih|9(0)uZUB`Z}P9Crm1KHo~qUvX%rkmrr&T3tCJL7pOYiB7kR z+S(m;(TZh5N-22dvwtZ+%|!5U*)Fnj@N>(xV`3uW3gRG+H6sHp&+#;@S2g)bR8YJsZlZ1N^84GKLRSvVGOsz{f z;yHTky$eMvh|HzKsI?gv~#80;G?(sKV7qq=BgYjPjDoYZQM@GJ~D}F9c_@3EQ z5cdYndLdJmeQPZ}OrH6I;gIYwtbG1ZdfD>pg^{#m;OD<#cBoCS=DFx=UiE9u4*C`R z2Lo}!9PSK?=^E_u8^8NRe^8s1EzqIh7kacjX=qrpP|s8$eEGmZn}~icVw$q`uJAEl z#Y1Fyj`;PQn*WM|KBoK8zc;q~NiRbC(Ql2eCZKWSw@|uX6!5%=x=D~I)``NkQpJ!v zW08~{f!Mp^E5@9dG$m8h2o~zOFA=w1Xrb?#MAm;#4R3yA397cE!;tMiZ%+R%1`Rv^ zO&3cZGChi%KVO@xkQ0s&UkGk@#FzJRzrwu+Dm*sIGZ%eYUL2 zPP{oDXWe13_{piXpQKv<(Zb<0CQ@twzsp6Vad+9_{76%^|qwX+NYE!y*k}&0OW;og{P~5vY}Xn#feFwkbm}^%b8*hDYWN6 zb!TSy2{_81tqF{meNs37JHK68QlUzLdaLbK&AYfO)&UOl91b#3jB-g7u!hx+$0dC@ zznShn4Qc%)fHHd8R!O^6)Y?4&O%8zjXfS3Kf5Te0{`c~_*AowVwZB#70h=IbTtA5~ zX+8)={WpP!9`0M-AJ2=S(Tpj>o;lDI_%&Q)wi_+vx)VO$xZJJ7hw_wwhiIsmlH?(c z6;0%vJ#YjT#Ga8c_A2^ez4f@E6)CHK`&(dtDgIQYDhl?|NF09*Rs?)`x4Y%`t2e!$ z)fNoFK3KH8b;mc(-6;@rbII_l6B!juR$mlSGL=gpk=wC;-!&Z9*w{qP$-1RB&0G!6 zl+b(f55=D;wPfvK6wkaV2}V!Hpi!d)17|B13h>uM^KPD7kjz(GBB4_lH!7ooBIbp2 z`{1A{yM6f_D`^8PpwSkbd&>kDa1>a&N7^2(SRl&A?O?K7mK}UVjT@TY&uySljmIK| zbMGq}enMQ6+S3%Qu@-bSmH0uuVw@kE;I#F-lfHK{09$U7&GF@&Iv&{)sA2kpiu3$_ zi%S`&d8Un1oS3dpfti6JuGlJ>^gf!B26^oJuMN8PYs`=SDCi2yRqV}T36Jtep~@=hwUFb_ zvMHIydJcs{T}9^e6mOs775!k|2?m@Fz=_(YdtzbToDv3CT>I>_)0l+=SLgeG#y#t& zLm4OEjoH2mw)a=;vh;0Pf)Mf$yrOpu-y7{`b5yiBfh&2+}ri8&!7vnMX@GZ zyUA~T7RF35W?1O*(5zIJan6DTfer_F@RMrRGk7)s+yszHf*e+Sm z+k^@#p+^iv!P~m;bt^qlLi@`F6I7$QgPb1F9V5=Sc47$v-v-}{nb>D}T^;&B%ptDaJyq8F+U z#OZi8q#TF04{ux0Cfn1qqxCj=nNHL~xjpH(R zn5t(7E7v?DoY=<+LdOf4pnc*U!u!D|<^WjtL^kea`VeSpfmS&@xrMu+PGjeeS=YPl#5NV=L28o|SrZCdn4q6vsp6 zqbql5?kMYhQ*+z5W7}9lD6DKmpWfG+_kFM|S&{*EHGWQZ#CxZ9`u9c9f@iR>lX;@@ zDG7glLNnZ-?%bhQ_#N9KsS+f)XT;0h3^0YaagMXUC;xM*wq=Z`VGZYRdNAi@jcv=} zi4nZr7m3=P^Bh`3iX(E=z=Z~8b;W&SF4uU|4F>@|R_rj%m7n8N72nD<+|E{GA zzX-g)x%KxNT|jO+58%a%O)Sp6VaQkwYjj3lT=9>uRjokK(j1k8r2p+?PYHe(Xgn%B zyho}Foqi)0TKK4WdSN>+(EnBWsS~?qo)Zg{@XSjo?!L(*08X(TyC8k;+xNwI%5e|&TYK5zhxbaz$69@-sWZ)XmA|DV>hwm`)bMpRi7A`DkZ|bx7Fw z+FXX~=<+N zxJ}??DtpqGuH!7g-u>4yW$XU!qOR*@^r)ilbJs-UZFBvbVqeU*63YY!i5B0+BY5ZJ^U7(WNm!5rQEV?Rwm0L}}k_d8Z51a+@A7pTu{w{NEA`;MfT&E!Q z!s0WQ>uFp-nK!9~dSzfoKsqyXL@fLhG3@?ED>t?EO7PSreBrY3be+$+GQlNihO*T!m@#Q}HQ)1Ux(JitG^W&41YUumsjDG1{-^`s2j~ z^MN5hPQ(8^UU2BaxdoQE)C4#S>_k4+@Mc3uW#iQrTYBtRe+wx|TSUV9#tja^IBL=> z18%KO@WQV9`ZDiAN{w~z5f^i}g!jZVyCs!?{b6;2@b9s=(gH0&jpEvi&mj3I&vmB? zDUIw_A4Vhxi+kE)Wqjh|((TyqG~{@y)1L$Je>yBdu!2Rt1o2A0GE}V%z0g%?NgDSY zdLR$XNtP~uU&NzS3EoG?8Fxo2fD|Ginaq^$AB=-SoT$024V&&Ii8a@2}{K zc4sm^@ihu1b=4l1us?r7{C}RQB%cg_ek8CdX>aS zu8DXINmWiO&L&y>NsdeQ9Hs^T@!!A@ldPq7dV~@AFzp z&*WiQet#qTBh*KATR<~aamgl3Gc#}l==L9y4ht43R3zS>ey0%q1dfUyc+V;ORSy9C z5H%Ra?2ZTThIHaJmClME6aUsiH>3HmPRfA!P+yUOQWn)E zeG_ZVGEn)cs0s1TtL>8i164q%O_n7UiRL|8`QEBmay|OjH%2Gp^-Dt9M5?w5*OMfK zJNXlJVU*8-$sMP6HcV$`=ddjy+Kf`eO`g?+eHZvzjWZcUNM1XwbQhPHf?s#A&J)uR zmKKeN{{m_O6XpdCmpQ6Zl_%I<<0$MiT@fznJKUkZLw&C%hp&UIRoKTcSwuQ+h zvqJi+e8=-oV~AzY*a95f*02Dw|EP!z^V)4{XpGewR7QWJf(xabSg41pw=@je0n9=f zJ4LIyj9S&i*IQwQ4t(AA`!LDKp>|~iy;tsu6$~6g&D_nEYI@GM=4u@3z(xKj;Mo@e ze_49{5D>q%-$fbR2chb!S&AdZg!03sn_M&~5E9cZ;0M${JpMon2NJGTzVuEx+-U;- z)eTCC%siE+=~4_hRMq7!3#8RJQiLnr=3g?lcHY+mec1>;3Exq!-`&Vt`Qc)t_5a<) znTD{JeAgv6K8^Kq*@ygKS6Lt2E`w1O@Hlsk|7AnL3i+ukQD^h#$&c9GV{v6fgdInl z{gJitdMMy$0cAD9jHxjP+IO)<{~Ezt+E~`X$-NzaH|{J%m}qU2bVGBufR}p`h6faC zcg=_E-R5-+Z}TA#?8E9O-m9Yl73!#f_}p-7+nd>( zGv3i!HRnNiQcfES-%+&D-m~=-T-Px$K~Z4q1uKnmX*tBmVXTfZpBYd{Aphn*E`%PZ z#`w7!JC&E6DZ6~DLSm5+972?_%8Q@BhOAu%95psQoT0v7JvzH{qVjp&ojgf#-0xEo z@z6IYyL@sh>igb8mKmH@Uu(Tw;|T4?f5=wH`#pJ61yOKh)r+fu|~lU)Ap+I z93iV;rlW7nYYkE!H<6#V#!aM3bd+zwumaAURp>Hx%b%BXKIe8LeUQ7g--v#?4KIdr zBRh<2=-q}-`Ad-AVpRp-xSd=M-MNpP#TTccFUE3EsE^QN+Z!45*ZPvGY%*OaR9w)m zvN}6PSpRWxcZ!J79$@YEJJE`ohCJ7w|94!xkNt)2B88t(uj?ju9Zh6XpDsk9e@^d= zmbUTJmBxSHJ@g+2(20V+p^6La!z0INn&fIT>Bljp4P;q|UnT_HLH$8#B+h?KabIPr z`%sa}AmJ|UDFM%NB@J_JSPs&%T;h=ZB_o&Radqz*5~@ci!jKEZM|o@S zn_WAS5XkLMugqLLagyOTADF?MBH#K{ikq+A(li;922mB?(EBkyjGpn`=>7DK&;YHe zYj&`Rr1^0ia&|EGe|*o1^oU7UWDB-r$9;6+~Azjjo&7h#98q|@=plXF{2Lxbp) z;rd^mLI7K$u_r_CzmG0yvn@|#EY24N=?)$kFq30>Yb3n}{v4kh$}>>w0{E zvm`i|P8vrfsqwOwRE$#-QUEO?$!;IPhlsLc_a--e&ZjOd*LgTIgjP&P*5t;`0{1aC zm68*ZbFAA{wXk)h^#1>DSpqwjN8fS{Y|012~KJ3!RKG_ZmEK zjMV|^#4yDVJve;=?*R9^bT0ZmZ;PCXnYA;dOoNq_zXopUy6N@Mo^+UkczdHWyp@J%->zAoz= zfh;}V(t3x%kO2-Hq6MV1#x1g1`s-Kk##Ry$%IVs;|5D4jX^%(O(8F5W*FOi=mwu2- z84$s;gO|Gph&9FE_+0T%249`3T=&2ebXQgn(5~bvp^b=wpmppkya)CEwP9wl(^dbc zxU2MRu|0Zg@+%@PO`yBdqx2^Fk-K^!a7h8mAGB>pkAAyI37uSux)xa!ZM>4w`l5kR ztuZ8YZo2iQn%M8dmz{ryJ3-nF4nRa^_J8Ug-Ki|c?hDE;B=#f{JR~-A)+?AV(fFF1 z6Jjthi)<40%>%ftm=hYDsoFJ4mtU=$B*(t1u{~r0DuPcEr++D(O>JO5?!tgWz&n>N z>G9%vH0)hi8EHS)`03BOj+AqY(Q=HAwAV6$)Y_v0{rLahV`qN%)AoQvw;BEaNZNPH zRg~{#CJlJ63>5k5o_E$-G6Q_uoU^f>gwP`vdmL5XMz!JAzc;A5_ZlEbg$`6QIaueX zjuf=cI|e|mMvPkhd8%Cw&+>bUAWquE&1T6yTiz7L>)mE&VanqP$NL61H;rUnGSu<; z<~|~4Um15n_B%}<2A74pPq=tC&-G{ySKiF2tiHPxxH3TZ3TwD#j0nw`YNgihRpVDc z7?ItaTR;0e#B=;CW;9u;bP#a%d4s_WdW-%Z2i={hk0rVW@!L`xr||ciN<6F$FCoAA zN|a^{IZFc{LC3A#pDlB^!%{||M#K6?#F`x*;4ybbZCSsKX)5~9(6~gAp)z@ThcvS- zMMLhj?COvh4}Rcfd@wyrGUsd#l*kP)^*LPy*^>$8ivJ}V#Xb}KcAoN$RK~&9q)Yz` zGdP4G=nUBEy>~G`1<`M9j$5tuQaIv1VEIj1V>!O`Z`$vlNKgOnQ~bGB1Qv&*SF>yf z471cstr?(MFQbcmU0YNp;OyOhDE~R0pC3XTbClT(f0|>d1NUExL}k!Mi)r$%CZQ%? zFK!iWYI-JyAYROaAsSfRBgCY#!E9hF=c~r^RjhR5lcoRa!bPZ$^*?yn?toLJK_(^-XKcfn1QG99+hDg(J4Q42( zfU*L@siB1di9*1A_-jTSZ$Pre_y0)fo+lNLE0YRbZmC`%ApAiO?{TU7?<~vc+JYkW zpRSr?AL1|_8t~kyChqan1YU&#K5X9Yoms9UF#vWDFFLu89+^=0X@|Qo7q6b{s#fW^ zV!Jp8=VC3>ns~R6$9Vr%dX*)ISc2$osRgSGP#%L63{pI`^GJ#w3dFnLFR-@j7^fSL zM8*U4-bxppHeBIpYF~|N_L*sK_kBa_d`K%)d?!MWR{oVZBMCorBe#NH{ z8#WJ^4tSYEL!(FP4KK)PoP>yr$0yOs# zz-sd3GjPZMe(!EZsBnT%qW$AyC5ALqOxL$7fnu)41>2^uF_}{8lqFcLcqH|K=LL14 zBaOk4+HeDA44R6tU$i~=cZ4QyBI~-g$*SFZez>}X&hD4gP@>R+LzJG_+Auk+YV>)l zVc$nTv)gw(5M!Z*sz07?=}Ek{`}m4{1TmgJHZO7t5bRI8I1zYkYCoLjO!HAv7E`pd zCCZ!nCgNF1>eqe&E)Q+M8wi8HLEF&CQX|$TQc{i@vAW=PyXL6zN*`33m-7q%x-Gz^ zj&cFmqUVEOPO|TY?-!k~8aI|jI9=P5-?Y+m%y#0Q2M!z_xmW1$si+G2fwU$Nf_!%n zNf34*AD-W-8 zVH0*4-cs?f{-7>)Ayj50EK713jM=j}FC^hl&ds z6QE<}`fJAVx99>)6UZtAe@18um;Cn8zj$1EnY5BNz9+~Hbsq`F4!y^R-L&+G>y+qHq$ZS;LV z2bg?xnFn1X+&^L%M4<06Q@J+4jA*%#0-1J4qvt_Dp>CzZ1JRw-(N+FZ+%K+4#FVoV zY@+%~nO@D30-IMFJV#L{72ccdxZt#3=?jmhsj}+-1v<$siEdFv4ykHkbLDsj&w4rm zoN-(D>8GiJs{5p>8ha>PI8(K5;r<3Cwg0XDKs!ImkCXRYfy`R;GQTPR*VIYoT$$^e z%3AOnEybU*)^FU~TbeTCh|QGi@Q zk~MrVh_#2K6)aHA_P;;S$9ZPsJ)tVBA$_8}OdBL;lvFR=KDLMyS+)V7(}W+iI^?~* z+z(V9ACS%z!^WyU(gXz$ze)~AHgv_o&85_Rv|P3f4DJh(WWh9QX*bu&$m==N5v9t{ zMh_OrZ7F(ZJni-T7zg=xe3K^vT5lHVYv0Zltcik-Ji_sMSh=lgp{uoJP4%FUMr}t8 z6Lym%N~vOK-%|Ykxa@_MmFh~4-`dVH#cc%1h23_>fgN+2LqCWDXAeXNcx_ z_uY~zN)awHsG$x&G`Q(dQ_HL2E)xUXkuszY{dc9CsLdVCa{oaR1U3J}=?%Hf?; z=sj8@PQ|o)xQ4lp8h3EXlC@n$R}T=!@UL|O9%Idwc!sfDw4R?A|K;7GA%6qQ$}{96 zVttD^uC3R+GJsLfOwHs}#D{m#fN~Hx&FtW@3t)UhOhLFnsBOkPkGrA>>hJHbb1V>p zQo(~kx?Q%d-yOOg8>M8qZYui;EJ9h=*QZcAHzo^uxp%~`sA^+Lby*9)EKSlmFn0D| z^dQsuc<|@Y@3E_lufTdqRoR%q%xMP@<^`lW7FmdVsx4wRN zvS(5;VW~mb_GN>r!mc1I&<*)y)EXA?1jw&!473#_sd30cpmFRXZZ?iq8M zPdGS5O^~tqgLd&(yZt->i+|%+b2ihu!(2(kt;H8;Da#}5-~O3l5%f#D_90KUIDvnCFHW&=K{PGGP*Yj1w?PCHbY%b%9sFU(kR z6Wo^2FYf$Ol~R*svJS3mpZWrO{|W%ub&ZGX7Z za+eGM)tE_(q36d*Fx1B|wOj9pUl=K(})tYG9U1&D9ct4Mc=d#vU_BcXhP ziyVyiDYWE#9hKa9G{hOrm)Bam{+qQPAV*t&L=XZ2;frCIfp_XZ3&z1;ry9mr>TYt+D5V_ithwr5_jZ(u;OvO4D$S zPQRTGvap(_xWj7S{ps#+g-}C@#cqLrZI1{)acHt@VGI+;7&sGnXeglFs2$Rg^g$dyXG#wi9?CId@^^_-qR>0Q_d-s;l83w9#Ov$E4(E87Uk zBdXBpElZ>lOP629;Nw2=1{tT^|AY`Gxsw&sFoX1>(_uSmm}2O+ODV|9cs4eIdnz2(8aVa9gX|c%en9V^Bfr+ZQPOr^~8p z#5ghTm$p2Saa`+UEBW^yhitTlcQ%hv+E9KunA8n0ihv>OyNq5Zx21nn*JjXOn_)Py9>n2R&KkZRq(06l45AmB9k2~+?m9~l3^uQj)2 ze@*#=loBbU`n7p>UelDWtIO;zD{?Vb9r;oHQtRGw6m$;s@FB$1)J=~!1A4uw={}Z7 z?$$&H>7+yIW#K9_ZPiUQ#3lt*gQeOl^Y!VUfhM?6y33um__!}UJYA)hgTd=!0ER=% zC)fkUv-Cl0!X7~Ob*vhhs;=0yf+zAJ=YAQ>MWWN(lrrqs#E!w#+FNv?B0JvgM=v>x z&dh-xM;tSW7Toi{-j{t*c%iz#$i*rxwfgs!v@C(?Z3glg87P#|crA}!VH7R}ImYLF z&2ZTcP9J@e15;-$Bqj~#=56?yAX9#KY9{KQp#*&$!PBAMrfj$5Z795bkCkXkE(WEN zDEOoUHV(}?YfA|n)KpXD&R>tKEkWkF4iX8)$xP6e4xt|)Zkd&7tFX8$iTKfI^$PQs zN_$Mn`1d?O>^H4}C*N5l=`&aspNUdoHu61fy zJH;=a!-UavrfLW{L&0=}ua`<1rtN{mK52aA41@}iO6LTIlSR4AE;A6+cGc1t!EF&( z?Mkotk(&enJ3{>7?k}MHhLDC5^Qi6jv87tpJQBDj=vuTHrEb?1beta%znnx}>PzZ> zon=;z*tr-5hbg}o((YJgybJUr3e1tIzCmSt~Z<0|KNtSwQVBMv!slGr+_p?X5Z+O|>@JRIKo~ z;se9NF*rqLdt(LGZ-B4q#x#T^zav1NU-&5;=+>8`e{hDLKS|(l-5O=u@&?hY(Ns_W zb!%N^@BO-orN%+*;W}RsQCU#OeAsna22?$iuF+4-CpbDLs`MQNGacTdK>qmUvpyO^ znjljEtd`P;jKa+{7MvZ^TbLS6f-m=l5)a&WD%aoCf8(^sgAr|}KC0vVE9FQnIERBM zA#;VhvQT}imd;cbFwq;}crxl!IS@1NN0z4Y3CZ%iN2|5_(aT{Rhn!xolrrgCRo43w zh)NdPm?3E^u5RxBdGc}}W=C2EnN=xHU18|Q5KPZhbAPZz(ivuiIH5%*jk3Y- zsDgYUL{R^9tg`^4!~jpA6K~*! zmCZwdGSb0bh@%i-+=G5Y&I4x9Xh1H~;_Kn4`CK2tCZ#>x37q`8T6fzUn}D^*admGBXasxIB@73nnYHVdm7KD(vRiut55133>3+1T)EpltQE^H;uS%cV( zjb)L8CgFErWS_uT4_;R^!Mi@c#R__`t+SG?Y0hdxBqeR|GkyQ&^h0dn1vPr^{)T+< zU^+-E^cGJOyE-3|qr@bO zYptJ)2p?wAgOk?e+FDl|irna>4c9cV($)LP-|oQAgMD`l?!<3@ z{e_Nv08N0{xgKiIhmlbNeL{>P{PP(jo zALHoT{!8{HeUC;xb$HIV6BGX)hOIHYP!+_@oPmtjHXH`=FSUsc8+Sdyv!MS?dy4*Q zLSA9VD&)3KA-x~KyrKX`BOud6#hu*|puOE~%PPHhHz5wW-V49>KZq?*X&%6ROv&IV zE>CyAeB%f@ud=g+Mjw$T@c@z$mWwj$o|e6YoI)tRGPWzd%dxcU)-`~ihY4v0evlV< zu=NpB2cgQUo3sgde$B`98!aC_%a7AVM1KD%Z-FV z_10~fuWn>aW$IK*k9RjPf{U(+~~33+~r#-$L4tm6JgNVe#w81XHQsQ?RU zRcxf)hCEwrr8ibr56UTiYL?*yzf1A zqaKUmjm3zW&hpBwd(eexpH$YTG;OR=&0Wf>0?DCeIw>wewi8^Xg#!yY zIgcuilfh$8;1hm2*srHSCN>OLE3VL?{%{D$U;Z!lWN?ce_T?=Ex>>n_3xUcP*|!_7 zr^z?4i7yiP;4?n%9WEIgK#gm0yS8Y#Gqx6umsgogBa7JCnMxm=2MX3pb2$WRh=7dq z)?n?lBs^#d^0npF;NmnKQW}kx7GN=e*r4u?=W#tIt#H1X^Xzz{ zZzX{pzYF#7QOs9bPx*iF?ib?b$0hP_WtJfPcnY_1KbbW$o5H_}U5c#*3Cfh6?F)2l zIEf@8BrUu^!*bI64`((RVB1*qBKdj? zV<>vY9^f44wgdV`6N%2SQBIy?^2c8yNt)ar!T-#Mhiq0qg!WcBosB(B7h+7LO{To+ z2^{-KEd5;q{h)U1A4Moh5K>?Y1ja)Gi&ksU=Eo4Xl!2BrtIJ)#__xmACZd#EUmuh0 zIYdE&H~y}chA-9vCcE-Y%N@sPSEE&Y9Zwgj|1z35@iA&KMHJf;>O1qpi91CqFxno* zubZDrAiW7ECrlj1cD#)|h#e#D8ngZ~yk)`q3|msY2hWmZQa~m$HGKKQQYd!jb8CmB z*KUmGh+0HWSRL-%PDPCVtb3GW22RsyrE!@N-)jPrR2Z$;y{JZ1x7_n5et0hWHa^ye zTt)Ch@cfmFlkCqRYWhL)#jxL;(^EFOZN~N}yREW@gHGNvcKpA)c3*p&Iwg;f-FE=n zN!CHfvH>$=*CPxO%wFn3n3oZ7W@jdBTv zNvcinFz}>9Ah_BmLA*lF2q4HZ?_eG<@9OIF?kR~Vbo6-h`F_u)n)clZ%F}Uk&>abZX~pGsGz(2=qV{6t@G}{lMBuCm6vuMI(XxM_n|Yr@i=1IC^Fnen=cu?)LLp zTCNMin+C;nwa80_LKrXg2D9^q#jT>EY> z+x9?@%EBq%8P6=gfg*9iXzf&tbLuPWueXD{KoXGQdEZvW_Rp9x+q>DTbL@gl{EV(R zD?>MFtvoF$beYI#<}Vk7^UDZTlZAGi2(1=5B7Xj9Qgh!`%pvc;MK*1j$$Sj^4MDh4 zIg(`DVTTn%m6{KAwcrt7+i4-4l+q7PA&IT3Jy&fK=b=;@RN4C_`CEDHsFcqYAG_(3 z%N^VY#n1NVs8YEFl?aqEJxQvkHnu=&ZWu z2lN9d+AksDOxc^`ZG_^5jfZm#!5?3Z)q4b`<9jK%-{ZsBiMLT5wy8#W2fNoB&1FAn z*=m8SqBZ%iv7bCx36;n>(_LdpW#!qHAXzQZMbU)v1c(JuC%lbMe?p=q6fgL#yUB33 zXWi`W2MGK@b(PUY+ODA99>zzS!vz%&Yxf644+QyR&s1RTt@@l6;6E=O7(N1Cyg6VI zeX%a3OP0P&mUog!aqpn3!>Il-WtpWgV$85Q=>`HJpSk&h<~iKU${8H1lAZ+{gNo)Q+dGs z!pO`2IE0SKIwe!Bqu`6`;F2ESOEUWPGeL&<0XS!w0KpxCFuEVflz)?j9Fy$wXv)}h zcg&zhyb~_o@2WqB|;;Wv47134WGgDgzsi; z7!hXJ4LcB13ML=Py~tQpyG|iVUOvRCfL+bh>p}@Z;iy9Dsc^E6WbZ-|kv0G%-kl1& zKUpAIXSd^*EY-#>nnY8o_q+^*LuZwY|JYwmuhz{5CL>Si!Fqm5Sy)=Vncvm=&GeQl zLYmo2M=uT3Pn^-h6S7QFcA8MU8r1$>l~vy7VV>UWkS#B+hi5GsS|a zEyaNKT&HV+SRiks5z(gI72WJtnfynGAuG+n@u{utOI&Saklq9`?J&h*QNm8;b}mnj zu3odeTo5J2UDLoa;SDW*&%>X-ZP%DR0teSN4fll_=hK+Y&Ua5_K(q)cy)%SjG}{>ywGbc3|>ws%+kefBISF zyZ%w%%@B|VQnWglO2VyC1;-ok>+Fs!Ge#uiMJ+LBLvZcEcrWH+aNjg!4Y{oE$ffJI zC5W56yQ~zGc0GXb#3p0QJgsy;j-fvYLh1YQ(548(s~fOsv|(cZHI`smA>zy)0UUNj znMIR8psio$^6TXilWBht+Gk!G>*MNUTwdNB{me!zuPYAb2N8v~@_V$@qu6oh9ldU$ zNFAu@w$F0H1%>_jCK-Rb`dNYqREIXdIMwGAZV&Ja^%)b)tcGHnq;vF0x*~gyM4bif z*9&DMF+(L%qh*lR%=2PV1f8OjLy?2fwUMW#w!(Lc5xRY{3>qNZLTI-_WU)C6RhYB> z=Anl!B76-l6s0ZcLYXv${2up^Mj)%1?2eU_;@v;G+MT?iDR{Du*~o;|*p36H#SkhF zev}I(1*%HHnz0xl(8TIm8;F8VgzUrigm3xVM1tWuT;A#rik#kDFH}x1=Ejge9GEo~ z;s@NW1E=jXf{fwo0g6(^HpZ_{Ft7Qq*BjVV{>Rca@CD*D;p*zL?YeB6SC_kN+qU)3 zwr%6GZQHhOz2W@?``g)Po_S_=c9um@b%Ny>ePqcEnOOv}VOsY!vR`f|Iod)tY#!)s zc^auKV;L~~KjA%%q)1p#!hgi>QmUHxbjBeL?{Z3Dlj^#~4-N`4q8hMkyR(*Yl{Y5$ zY!=ekpwYXIuGMxKI_2MG4973igX@B3o-uL30K?)OoY9&d)^icPm@K{2o+`>5TUPj$ z6DNzPBM0h$Rz-JMJ|~+38dmIIt6NC+yOqPwH&jbchQQmSbmpedL)BF`BFL>rd`NpM zaDj~R8u4bMQAPLQ^iEN9hjyo%ORc9upl=v=pQM^pUhhYgs)X-`9!KHs?(>2EPTj;8+olRU6L~A#WXj`N5xtpVp`wWJ9yy z@Q(tX=T5qOpF;XS5%WKuAjzXtl~6Y0hi;{7%i9Jp%NLK5_N5UeWmLg1XFNJ)-^K$u*B&5x)Ucz+nG@5qha@I1%ABXU_?HTW;kVhrM) zM6kj2J$*~xSb4E^-*Ygz44c^Mwx1K^J<%}xEGiBqu*v^>F z1pK`~J&x%CU}`;ZT1q`_i2@g&^&WKgi-q^BrXdCVXz%X8{0ucK`0fBmE1Ocq;)>et zzVE}?)vwim|Lx;7?i`*%@zLPW32qfLf#s8>-Bf3|vpD*j`Gu$o%pDii;Af;VFTUkC zv?BUS0ER^&VV+%(ttdLrqUoiR}hDRJB#I5b9{n+<%Halyy6m z(IjN`*xpwEgdB0&+Vh;KGA|Gn*S2=JqS|vDi9Ad*TdfY@|DyYFYDDE%oJUMYN(kPF zM7E@!X<7=gcCK}17h$>cg74?_1s1S?eBDe%9PV&9v%V5Dm!4%=afLrmn)4ihpZE!+0qK z3-ntYY=rcOsZVjcojQOY&vj*kig_mJ5bH2P|A8)XDC{Ieu#V|d3Gg-DA(Nw~kbYne zUd@Z=Q(8Lp4uAZ_tkqv5W^B;#cOR8MW_#ZbMsqtgLQyjIEEgLjq`ajC1RgoNNA-D| z+m%Y|NpVIc9>ou@ZlpfJHlG!KbDl$79sO*x5fC3_6`)4F!;~~k^6l48!z7H-X9{tfuy<=qYqI2 zw4u&=TU0+SQy^Z&TRWK;rPxzmQ zWPo)kj^6qNZ3ESOqj%8K(jfrl*DuO(s~9g`01AIQL@G!}3XeQbR_{6D-f=rFmj`g7 z;^N|xO3mV;NGYO7e-RQas;<~T8hlI^VVm!bSU+a}nlJ(q zaoliz|LW1cMhX#F>!|~NrCtewr_vcK|@n76$R8+M2YOjX7*S?D?7m>w*t2Yh|YytS9tY7$`f5iku(m-Rl*-e9GETV%CbV~nCX zvTfGJUgkEoklMJedjy_ONEmPp4h#>tf$PV+E9$`{v<8hs;2Oey^HVaTmq>zRkVxw| zO#i&WZ2!p>cy+(F#6UP0wLnaROW?L*f%f&dtN5CHgaykJ&_uaW?_ zOA!=jm3iS9ueI}sD*87umM#>q!<)h40T;~So|Q~!{M)E7IIv6C*Ff|mn^4!fC_n0% z2%U^QPcl~A(9VqVh58Aml|6=2da!4~5g75s{yzL74xuA!Y#X0bPr*FEUnP2C!$}2A z_{%MOwfAj#Trl8BpuM+enhVMEMNCcS@e0VUB|G1k=6d&=J3i$7Y?_B$Tyjj=481O4 zKk5aP_4r|qk1?#5^lxue33TXdL_+eG3q`_}W9`~>(ln83(RHr;kh4s`=SE4!I_H;u zYWnssv*PNJzTG_{OFUQBlPO#+ZwpjNfrbJw`XXJR00H^}M1;Pl1M35aRNQS}(zVIz z7-Ju$g>Q+**q8Cl-MhU$-5bsOrx1-Wc~$OEcNkl>iH&5{FB3}1($y9>7Uc%dQih&j z-aF)(>=ulLa6-te;YuOV%Ba`a&cCp=R_#{2EasgGG7XJe;tUR7c@C7}-EYF-b%OEo z3j?-&p1hOF>pe5+((7eyMAC%)uPeXFWiHDY~PDz-uCyyZdO)eRjEu?ju>SrpvdwrBF$S- za9AEu(w>dEBRJomO2~9}Ar@jm zHznv}lwup@YBM~}YTv}RUqa7mWxZCJ@w00L)q32_pgW;xCWG57e(|)sx>}<09vA%*0)4xJ0xe?WdHcErh7b;&p`@NsEX=IWlJ`#{Z61rK+>8Wts)Rar0;R7 zyDWAg0_aCVLklzkC*#Ef3y5g9Bs*>Q&oANampX;_ac?lJLqYbB$@=f`Qmf?jDqO z`z5a-Y=1IqCw{Pt(7l6#;Hi`1h*aA;ze=i)iRy=~2^2Oy2rw_67u?3_97NZ?2iM8; zx|WM8JK_2DU8R=9#@UU|&X=#UWlG{wI)+Dt(LO3#;y+av&+h%44|QAGdpVaM*n z1r89F;76a2Z`z}0x15X5wkr*DNIR0nSEskC>y`!T3PNR7yKNbu+BI!Dowz_vUfBO! zQq6yD9sueS_ZJ2rU^FU{(9VXuf)!G=vle{jC$dH49}`G^`jWHWmM*r|m3kM|HsF{9 zC|#z55A>X$<7sKPnXBz3iffs~%H3t}#H7d*I4m>RQa1@au9HG+Y)>37=^;Wmvi?(9 zT|1gW&TAPjN7i266FYcYmgakh`C=R}UtR$G4$u-1-+-W!XpuNk5^0?{V-8KyKN z>E#PyeWF>U9d*3QuTujHbMW&45!h);ql9v?A(HbpYwKyn!F1%o(mh{Z_xr=^Ko~83 ztor77?)b=v$iQ_lbY)n|K=p8WYO?96DSk~TRN@2|2l=XV{zXkou z?+yvGh}hEbad|hwbE;c!i)E^DG&}C3Ebo;K5aXsX>AIkmsFg(ZyQkfnoGArG(0hrn z6!BT2$Z88z^UL(Sw4b=M{7;Y9X=m9Ox|%RS{m4_POan(A2wDKd?hkmc1tg70eP>CV z2(7|$C{A=ZYVE#bxC8uXZo_k&keb|oH}T;Eo@Tf&WY2V=_Ro7mq9E5~#m&9v#h#S} zHG}iTj46YxW%r@#XrykVm)ALq>7x$^ z=o+)~#~Ay7i(6APDP%45RG;yhQT_PKKivB&&U)wnB?}YJqf5XLPYPvw+|baS108^o z;URs(?f`)XtU#lYOZyE(=cRas!U@TlNPMi_x@hjg)jxYPK5TvqjBaF8*1BDV_+&82 z>&mOU%$uc&?|6PWaWux^TIF40O?&^AOPIzQPI?74fmfQBetsrJe6%a@_-U?$ea1e$ zi=I(un)1v?t%A$~#vr7N?$hIW(SU)30ePB!vm#cL>5G~+-Gr8tYV_L=c)G^AQuf1Q&j|@f#ER z@OL=kJpWsFREA{6)C2^|1|)i`eN~cnj=@OGzi;9|QSHQEn0EsGw7VuBE0`r4W!l>d zo_b}Btn6(*TK~UUPX2`xr>bgD0$$eM98Ip1P+xFtjuA zhcPqO7O`(O2F0~?!TR9ZKYVyz zNR?Q?U7m|VBO>(Yj}O#|i+`W06UID`N=61oW^yucu|k^BY(e|_HA$X)oNVr1*0fQ% zVzOzT@^?n0W7gNwZergz{2AtczKF$j+|!<4{jm?1T9Md@aAkWfCJ?d$f8zQJTGSz7 z4n1xV1sAN~;D5Mjhz4l%fq95e%qwXlf+bj>yQdWOrK%>)+aShwq6<`gyp%F-yFxwd zw=b72V(?MsfQnZM#ZApXt3ox_B>cCxrt%kXg#c5Hi33Wmvm)xHe3SfL!T$^-GMjWG zh%2xB7@6$0`@)l(I>E3i+=@LzKrH|x7rU@_(>%WTdvT1<0MBbE2xq~890l&;4_+fV zy6gYXr543(QF1V_QCyuGe`g?SBhg#AxfGZj_(d)g#Cs1o{vL^gA45i?~<2LhYWEQ78E@!TYFbIS_f-{D`zty zJ~w=p4*m~fLe;O4i0N7K&&j8yh*{kNG9m_x zx|X^Eu^2uk6F;xvfqhs@SLAb6U(}U@nTtn!Pp17Rk0oolfc7#4AK8?v0#|t zJdgCf_1~XcM~DXRQeT#db8_ojgA@xK<=Szp-Er#JzSUEuo8ZxE$KCh##R}!O$NqT= zI+hCV^y*ewH0nMozFcq~)WNTj$3;sIsZmytCk78%rJg`6c-UawYbb$e%r+%YN35IH?DU)U`r6p>;U4#AeVE|N5y0*$(*F zi=Y~FMdW(ZOf`tu15D>$1b_8ktbrEiqGik|=3V8phx_=O($C_Ib$WuNgMKky4bDv{ zy1zuTf}T9P;u*48A8XR0&#zm4eca&m+7TV#ila*OzjEsrgcKvP;YYot+IzRqx>TM^L=M( zDA~xB_U^bUywX04L>+P4qx?hJNhIBM%46GDft-HCFrlZG{M4WOC99Y&idSi*iI^eI z)&dP6Ra1m9ZAnp;!FQp}^(4LJkN*>F&?w&}e|#N1;is&;pbmrZjp2%vKEA}6>$V`jq_Bt~ z6dEhonVTDvC5CX&C54buCCG&vTOu~9ggV5CXy0+@#~xX7myF&hLpZ_LmhATVqIvEw z;92a#bkUfQE3%MD-9?~cA^)V%z|k>KK||FqS<$EYVe#%#?h3{*?$Av~Ya+(>&Rp1F zeVL3t8?jdl18FW0MS7ad8H9P>M&vhWfIN?j>BcAR)#c|Wfy_TFAcSrOkZ@yYQPo?a zpy+SZ3<>C@bNW8#@EzZOeJU$>J`z06ciUnWF2Fm)dl>9RFU8Bh7YEq7M7GiYjVSba z$_1@EYZ>&TpkUv@$QuGdEGVM7&?_ZkpO482T-v)R1ghm-JL1x1TriF^>*es-xgDV@4{f}j08`9=37tRjKE0c1M3v3&38hGgk8-U{Ic zmqAtk7#~!=D9R8aFpC1;JZ=N^JFRtEFkw$7SucpEf*(8_$yUG;F_)eh(=(l}Y zy<#es++RlYlzDsU%dQa3tg$Y(3S?@l?o;*33Cwo$KLr4PdL-|iGpaQ`{X#4)e;i09 zKieVgal`9~Y30i_M7kk|{s!HrxPaUBg!J{?8cLinIIem!omjO*BwSg}JfKcdT8x~a zwz?@5U%$l>pA*4hsl2^-@ek_LKHCY2-Zgz;5!Z)0&3PZlWqS?l2{utALtw1F+vm6c z8##OOpPz1`(>%qJNr9XfmB(NFl`TR_%afa!e$0Y!(CqIR2l5%?jF`ya-j44D&7$@4 z*5zLVpdb{Nern$+=x5bRSjbWpFUt1x(IFs~P_ivJIs&sT+5d+7fwK@( zYSMP{?a7CQfFmlxJB|#;>D%FCJ{E^Sg9Y5w85feXE^c|zwG8mU#l6!=V_n%GS1H>* z$M33Zed&AT2xXb0kL2Qd?o0BzJOn?g>D%w||H)W?wmFyIi%T)C=$08VCRan{#82@% zARC~*O0^tUQ92D}##v9U5j zti|o#!NA^8<9<&Hnu`L=?zE!4W~0CcSzll6I!jaF{mP5~3qo`Npv&)X9{BI4d!A=8slSJH& z;f?M%AF3|*6LlABGN8UZ8|f7dk7VpX`*=Mu_dRawVt7}yq9>!^)X&XCmdsSn3&WGK zK=p|nCWb&eH-TEVV?VN1TNj__R*o~&U-p^~+Bk`^tBQMO1D446p zMjoa1{U}GG7+$k^N26%OpHu`0MgFQ=c|m=8+?Ma#%RQFV6v+)9yyTsMaCPuMxJ|7I zbAjv(r#(jOk=1ykXrspIg`yB;}bv-Q&jG0p+AyI1lbk04ybz{f>? z-QApSUkQ#%AzeH@r-%Z+YA>I^&A`JD@TG({3(VS#nzdAC#_hF=z=Q(W`65Mvf09i* zE8N>%!pl^ZFYWL2m*9o@`Rf|&GbosVzS3sYfJ#sf_Oqn!bT;;?cI^3P7mw?j0O;Ql zltIE850{$J6N?_?W5_ljI&X%qR~DU1HXs~@ZO{hj#yXuMe+!vN*U%A|>L`ZcQr^kz zS*J3L8HR^@s=B_|M)|X9)x(nLZI$ef^Plc(l&f~0e#vNl-mgh}izzYK!~K2?aK+Gz zl6tK_p)DpSEN6Wg;E@p`vBD~ASTe|6sNu01{hgBkfy!RnOSx4tiCpB`3)%}9Ne%n> z?$5N&c2BVy`j_7-4~;mot&durY?rL(*&TKTexr{06Lq*ixIR`Y`{F1jKwZ{x^qP|M z%rVLTaHprraMyg-p@WfGt4y@~)EQNx*05b)&R%*3;wr>v?GVnH#g&+$i!55W(`grZ zkkDC-0_U>dv1qXTvb`dgm2lBsBZy+&TghA7u1yHCXYUN}J1Y5E1a`0^vP>PnqYTW~ z`b@XKS*q+PI<0Dr%>!7qm^A&U`CFl3+Ui$T;B)>B2&}% zDRAVt#d_+dPRvoqTn*jzA(rC@sxDFHG&P$ONFlt$Rtc5vz29W2O{Xr-iQY=5CgZvd(549qv;5@X%|N_VgcsRz5fPSC{y+x^GjY0{KpGGrqzV)``*>s_x>NR zaR?8;-ndHU9mcAld(gWXT!ycdbynWTcS1XjnK7s5SdGyt8lHONT~s0dUJkAdCOMu~69cac#2 zV+f=J9!DR<-i@Z0H9({4~^;Pp{4;>4lSc`7a{Eb@;SdxpCc6E0oWumcIdVB zjiqFaL$>Af#K85Vc|5g_11HUs+qPC{L=n z)OV26{LJ6Iwxhi-It40nM;Iy7RpWzkp#{Tm+&5hgRc8AXv?5z9D=ztvK=W7L;kB}f z+}%F-=7h;7Y{+@-*q)7kw{(cesp3Qq8i>)|lBEx2{B)5ilXT;`FXk=56k9GUDrUBx zWIdd7BhG5aIH)&#csVWhx5san%@xQGh&o#PwHG?AVA=beQ zs2e>eomCEp0%GoRdl=&YwIJ_#} zNV&r#KH|j3h`#qKr?ee{(>FSC+s4z7(lx!5EOn& zgRsyHb;+X5SH%jWzvL8Mmw?3ylm}lUj@h~Ut%|#Vlr^@s+7V|Cp6lzJOe2CTCu~LC zObmJ4_Pymd59$33isN@0@~|shqtEdbu#s$;kl=1v4(DvU{?*!z^~qV3waPD5qDGgS#n`yeH;c{s{gp~(l-_% zQ<2yZ;TSmVVkdj%iRTrKpDLnL^cQ{AS|#5_X2Tw1-|jk}j!7XJ8W!f?hm?r_7E!;U zrW}>nV3lAn&Bj8`NB`yDP475*`nEjTC+IU8{eH9NyyYHm$zN-mj}q-sG-=bPANl{K zwzgv74hMxEPd(AoIwO2F*BHg$GX%PrI_D?{^_zLEJbCV|E6a8u`52L_r1AIskhx_d zxA`P2+Y9iR=SpezgnH8M+1jF*xr`UdnMyb!CV65J^KnR45a`SDu^;IKQspLW*XYRH zV93+btHn7n2F%tBPV|eEbNJ~G>b(uL>xd*H!cIMgn<}yL=J0gzKo30+r`S2QtWwv% z3b!V$^)m!}iWWJuF>U0ozmqKA*fL4+mUKJ)W*aj-=w9-qDn(Rl@tAIfVctq|w4gzL zFa1N~`O@=JFrS&E$t*XRrw-20Q-D}y_U=|)RdET&GYCMy@oGvzG5jAvXhn(k#BL{f zF?Xyuv!M@lQ?R026pMHWEe-WJsDX;Lb*ubes7`tWBWYsx1Bv!BUNBI&@?iUedveI7 z`Y9r~Eb2S+;}1&b1or#u4TF5c+|U)>@8tnMm&XA9vFRF3VT=EV$JNh}U^G3Yv88q* z75pXcIq#@kQc;U}O%E|tQ$b}uBkyqc(fvQIs(?91Lrb4V5*AY{h94?vLWd3uLz*wG zX1yh@x*GrjfPD2MJ31bP{ak2~5ur`QWll&harsvEW4tpv&2=^w+X!EX(tI|>esdzH`^B9cv-O7cIAI>t#r3OSrqkYJ9+CBzAb0^|G?bgzF zyO{2I(Cn*$(IS`>m8VNNEMs!V!XFxYS)`dj^E5_;H@~kYk-JSHaZh#6@Ayx+l6|Ks z7kHM`Mq!1&sTTPYb?9~rcvvZ&F}sdFdogV+ZSb4qvDsCkTY!FQp?xE}XQ+o#uy zL#=U-#^VwlO*yq!J&LbLhCj;`!2I&a3XcR*vlFgIQq83oBuVq}H+V!Z`qOgT*xvJk z|CZ1@wg_>={Z}fL`FS1cMQP{)=fR*rtK26lkHJ#6jqY8j-nJV;LxT!yct;~RL~7SY z_yQq&w-3gcOZaR{oOyC@`%|ct1xbh)T)@g^3l{-#CfnWnm!{Fi@XL5Wwv04VW>w`d zeLQqCH+OXnF-e|auc}+;L(1@)>2!^1F^j|6=(nh@_zZG-!`weMgH5WF%pE*EY^-0( zhM3W#^ek{qwnY;aH|R+B3!NQ3Q5-UuAN}{cLy8T78dS`6&roU~cTG&;IJQY~>Ms%Q zHQXs#7NXoE=ET@@-~<3^>4-^}C?ZPjH;?OkIIA8OCi3N+&3{$N6=G>hi#4Kt(ap?f z^&*mA4z_EU)E845ksIS-G+DregD|E%EXC)aA~n|ViC=x0#i4x)zkj# z#gS9=V4$NaV?$@OG^ms#caP$=jaI!Qhb)qvpZ2xUi2c>YV>$$1lr!;BJL2O7ynygE z4B2T=F*e~GDF_tp@ngC+)G&QB#9Y2r;7k4U?CrM-5XvC>x9U6x{xHewLaX{=TC%PK z7iJPy<0MI016+~KhrmKD--P+4Z0>4l~1td8gC3mDl?mA(eEs)Hr;6a=@oAfSvm>#R|PeDS`|Ze4FK zs^(tcQOz{M%vkcg2)+Aw5!zH=IZV->KW}-~IS$-{Ha+`a$n)Cf{X*z^V_zftfjV~i zChj%kqMf*0&OjNW03a-SA16tK#TE`qp}(3Z`t^My2Y{sKXamc|qU)RC#vh)q-$EChIu^tq=5>iv9~im6F-xqnFqH`eV%Y)o7!cHS^DKQ8GTm2 z=--50zEY=u^DoKgbrxkf{elN@$M0hU7dvkRq@W*Jg)X$%j*zGhp#QuO5rx;wsrdcU zv2#K7@L_xUTJg4^OTiv5^m=!zfnjDT9)Vl6u;a-v|d zksoUstizBHQRA$mfSJ&FR+D1G7Jibsu7Aq#^{vE9bX8bIuxF)BdSJ(b&=2hZT*KL& z?=a8t8RmQc3gEqPNVH3`E~`)v4E&VVFlAC04ihWb!&=0EkaoqtBW%I$tDU&?F!eHi z74N9l-#>)ePcF)+RaBCrCyeb4wr}b+Nrd4ZO~Mf~2+4G5?+APHKW%3-aa5)rE%hX> z&DXYhe5N(p_51xN*e=0bSe@eQ;150-FOzTu(f7VQ!)|pi8k%-qtyo`?DoplKyxK-b zwkWfL8>tSch0LdDP0pjyL{He}jS%gh*m07gTql%lqsM==j?be#_cP;SvhTzaJx8yl zTu;(SR$2zMp;RwgOqpSX4X?#IN8<)hCr2GWsf#xSL{sTBF~42@qk_Z7ThvAd2CL5k zMKq*Y_P8yQnwMyg>nhCtKC!H9O3cvDLJhn! zp?=ae@C$ZLp9vl5b{+8MU;=pdl~vRfOwO-n6~%UrI~?j0hZyc;jS+BbR$j7sz+sTb7LZ{@!bxpNITohpM4Vb0YC1_5K|F<`aCGV`(fHtJ!O#(Q(+oyVJ*AcWs2Z@!!n zMcZ@t!W7O(AdIe)Elw=T}~d5GT!|3x@0ZK%H6 ziE^NA^8QJE?z;ORcmc^>GX);qD6KzRwtKG>#h(Eaf5+6XBea}6CwU!VfuJJ>>_Y$a z!071*}4pLvAy4MJ99T%{VPqaV6#n=}VyzYV(qyJM|-Y%SuRzkJr%(})u z|Fqj%063C>|LJm&p9e2chzB-*yHO;mkQb8LR95qrC0OxHE)5&5ENo<^7W;rHV|=cW z$Q4y2Au_=*&H$LRC|C37W%wXP(YABC9o-?gyG~zHKqAC1FnO-48yqi%v>`Ir?)c6h zn4W&V46xSZ*IYLTyIVe(M7vE zs@Fz+OR(o4Do7wF&^Emhm|%Zhl6+s>R7MUXIM#z1YFpJ!L#{XHukLHGzflkSxq0M$ zcepk0ghvZ9l@|Puak8LY)0pStYO|Tb5h11ZEipw`G$lX=)jtoJDqq<$@TyELtu}H! zf>8Q3^c5CCOK#6e3f6C5!da#M^W)Mi79uSM0`fKYFvLc2Up=SU&Zw2bhAl`8wvZ@f zi@Sd;!IgM|`O zHv_3bMVOkqh1s)z;M{-4i1qH}&%ouq&`dV&7u$8kw-^wbS09z|l2XqQ#c6TbC5_g=>tBO}v}bCvo1lE~dq)2D$ty{EgkERcN<;u-n%I zp$NtLy_f8@yM5}97Kpn2PB+pyl|;W5P4wBkh5{6WWt5)386tho`G+C04zv_}ADuT^ zU!8u*ksfes@!7aB|K**l74wKk-tu!MXPi9A=Cd2ySR1aYTCaESYr8$si)*es7-WD4 zc9ShfQ)I52!vw0exv6mDX51Q@d)xKmog51Q`qM04l0qzmZ#gwa57&ijzO2m?Tx8~` z$`T6QE$o3A_6)SMTGC*CoBp$gB2~fy+huMPj`p9LnnU{{Aat)2bB&p;vz#P3DTWHA zqPY-8oMnexI>{@1ebI|m+ppYqjAQ=D)=Wpj-wq|4&=0Nb+LTNz1K^6Rnd{fOy}C#s zdLxgQr}jVRBv33}*AlxTuHi4X($1p6^;$55O^b>8&-&J;DmrHLqVG&XG4**i*QViZvzJ7T1*?5O!zl8om14ENve zOVRE%N_=%^-9`^ng(-Vg+}}KMmZ^-|!q)kV^;XTI#br{q&-*I&O~{DJX@8PZ7xt55OTDuZR2i5)Xpca}hI9nOH1>XKIezQ*u`k&@M?TTJS zdD7(|mvLaQ_f8nS(vAJY1DA@!?1mPcUN#M_wx0NDu8*#>{%i?+T@c2PT0h6DfY)#1g(nu^`pqL=7aoB z*DTSoZNOw@SGY{m-Oi}iERq2A0)uT50!Sbmz0S_0*8%1y#BTx+WEd6aW)9 zf{>eX_zThcZ?yshu}-tgaqZ0@+G$v~ddaR1iv5CqA)o}GL_s$ zyoTSGU{`hzVbJtXNLmAjF$ZqHW6=YM1p?Dppw3~X)k zdl)HhvILin=0@^aEukP^4ln*$$G9llr2uQ6WjekKsV*T{SYA-Ayo9{ouh)4yr6zE3 zPq24nIktrFJju;{zvcf)zF*y|i75wFTFANz5lj=sYDUi3F2w)DqQ2&|u4BlTD0vPN z3}lhf65W3Nl)g`@i6@7=;qx42rT!K)+|jl&55Rw2@|NiC^GSCIpC_MxKn9mC225Y0 zN7j-zW;bLiIlKt`pEh*C*hFp4fXFc@Tc0Q`JzFy{?VrQFBri|+upo6J?9-YPtXtEj zb+uqC*nSNTp}B1dn>%0Z^`76-9qq@-%)BYSeW!AX)8>#xBZ}&jp_PF>|B%}nh_zOog0lfAr~Bt>Q|CQ66C(8W zD(P3u=x6Fp#?Pw}-__hgYH%&VzD@thwG+Q~K)O&J2RcoLuK2AWa5zPo@PHn3F&g0=g*0>xIUaA zF(Oh3IDhya7E^|Hxt#_k`J0!AWOMkW!Xuv5I$tcz*UfN~kzlxwrmw^gaYeRq1K>Lx z@?Jjqh$EWRZaf*#YEQ5Ztm^RBqA@+KA>u)a;JKT7WZ~KTx01uwBNf955$dD=Pbx#y z2I>Sg1TKB?jJkRA_6A4jzAnA_SzMfuY=G;>aO$Uov*8HuRghYKnu^_c6S|<}%tX(z_fVspMaoa`R#Ai1hrtNuh$v^%E0=`7VvyOFio?UkEHSoRDw6S^wml znpT}3n{gQ&5L823$DPBHYfdOuvlw(9Ck7~Mp72SO%8CgJq%gKqZFEPKc5!=d!BC1N z2As1MGLQ)S!HO-6C*Y!*k{Nl@)q0gxT3l*KX#&y!sI25+CFy0n0TJ!axfe;)ZHB4T zjD9y|j|}I5R9BVX1#8+bY1hmM4c16iV5WGD`1_q zZoS|ciqPSc|A1XO6qn2_Pj;>?{^RsrPZu?(I>Z<1DL)Bk;&%^pS3&q`(_M#UlynlG z`a%{<0(8D^SjaisRk<}27qn=T6JY*Yftc!nORPONPbQ{jM4a8So9sAXp`5e)6+cV3 z7>feY(SJL;zv}D(<=Ua&kgbW-8-Gq;>bx2`_J`djrS-c;NH6syY349{^j;Q7ev7-W zXDovDAFai`zT0IimD3$;&ck~7=R?%dEA_fZi|4bgxv>-3dw$(=9O+^cXOnCh@2C^- zpz#{JATJ&ick1oI8M`GKe95IBKUK5nEVb`Sp4>?xP1v(9V2$xG*_tj+>QLRQO0nMZ z)RnA4B+=ymqlRU4YxVld=1@M12nQqLpqrRyrcZy($s%>)M*^CSgGH{VykeTdCpWqv z`D@wyQB)K0(=7DyZ~;Fu8o|{9Oe>)vx&1j^xMhNinE~SJfKHBtkK1nm)G#r;;&+fK zkTq9$9tlTg-CZ5~9V#S=2KK^%lMCam@)503>JnGNuXIYu=QEEUjzjPrwpHNSmIQNx-%X026vPR%qMisI7c$Kin^?+OuV{j(rOSmyQTv^ zlMol4u=*p4vt`@B$ZsE*?d94d?=rC=X%N29i)cYy?28g)Mdoi8=PhdH#QX3nP5zZo zR6>0w6!kJdV4BxePWDXUe9q5FQTV_6$HRV{U|x%kOyZ03pp@{vtx5V_);Pz2vA~F? zg2Nq9+AbwUG_rDWx(pJ_T-XZ`<^&6M8cu7xu*upN-AW^Yw0IafZa8ptPbE@25xi4t z3h?!;Ue*h?^EbD$B0)fLzkfGgd45?wtMx}0WOp(56@~qr+#6eCo^Ygv81h9CLxEq? zMp5V^a7LLA=t=L1IzMuE*!3eIFfqlt1%xG%c_4eMM5Hh*=R{OPuSG{0&HIVEn$P;L zVpreXRQAHzY-3R_l=oFPCB3qwN;&G00ed1-Xks1GcKdnYFVvTQr8K<2T|WZVk4;K} zgBjbbj0g2itcmH39u*AZ!5WZa1M0x+=eo{FLqxlSp&~w>u*}X!^_Fqacwm_?R=o-k z7wsog9Ph~r$jA<==aKi?f(iga-URd|-Chp~oTiSigQa1BTh$k_tMYYmC-rYl+3PQ2Av z#t-TST(>hk$gSq)-vWR5(^Q>bh-C!x`Ng!wT+?W&`(S7Hy%vaFx5j%t{=-a4?pwgJ zAbFE4C*_g0M@^Wkm5tgC;r@QaVfwZDW5s_~C|Mi-4mB~b@^V@QuNcblVL2=lq!~Ef zONYoF5ELbAq3)(yT3H1}Z(feAY<`BA4-RK;fF)}uoz)G9Z>c zez+Ve*)@wS2w@_vsVTB)%kO+XTcqk)R}cy2xf(W9J?|uvzz?dQ`#N_|H+bI&N|BGR zwy}UJ7Y6H4-Dr|R5?3l2#IdOIDYqYuq>oOlDQrZBzwzm$=3|E}8g_XW z{qp-(7(YH01|aqA$GbH#jhWpsu1N7;sB93P_G$cq%8#R>(5DSDZs!3LOOWl__j&QA zcND))#(?dj-w3?vG*8-K9?qUjS6hXZxE1tqqeU-`Yb;LyMR1C*fn`C@p1H41uxhu@ z7(1^K-!>=+u}VfSysm3jZ=2h)y}x^W7d~zW$+T!Nea9`_CMk9(A;mg)$ciEtz;h#< z^@uOX-W#pG18e8uQUML{>6)rIc=gWgtlZj!-|?1xfrApE5;vR6_d}y#nns% zLlKtAz=MI(H==2`Tb8pRHGO5<=5H2vmJ9JY#{Q!(wdP<==+ijY{*u*HpPg4fRoQqS zT$kS*qT+ppbC4_WK*=s53_^Qh_2-W=qNAE$7TQGPX(5pF(|sYMC-<*^&;ygTVYs(u zcNkIPAHVm-Oe8X%(wy8-wHe*{h-QF52>{a6ajwiZ-48@G^9uf z#?tGO516-PPk8Y;C~K1J1|O=#us_uvdWr*43IZh@?Jy+IrBMM!!32$8qb+}dW2;A0C3RXJm_q8 z4vNl#u8W7>E_GpwyPar5{TQv`@u_C?6}+nG?~8(kfKO#xKo2%9-a1>RcPXcy zS<|xX+AVj;n05c~=Wd=x{7;W3Y(kdOW!cEtQfXEi-ZxBlN$`7za5 z=yo)XraS(wYKQ*W*;Ur`!9Z4J=bQ<896=m2gc}A{0C)O(e+bklD(Irg`+`hU)Ns1L z`hVWP_C9L+a}y&wcX={no+JZRxuHx}N?_`52ckWCfF0;RorWV8iOc5fD}i;S;`{scixQ`O*olV`5vftp$Z)|^~p z7eeIb^5B0YU4wdFTM+$YH%?=tv2JWOwi?^E(V%T?+qP}nwr%4EPq4o;vu4d2?0uLf z_q|uFtVvBVdK7t(5iaNLmgZ(=*Jc0aM7K_D9qCdL#UA>-2Ri|6iUsYP@J(|&{z2bCBjkJW)Im1nAaIeSxO;Y{+bA4N z62gv}=YYvji>Pd<4|OPeav@IW9?Cb7O%@1)7=b($xf_0Z!T;1K!)Vr}Yk7%DYo*Y` zD^0SI>!NeyID#u214_&(sQZs3vU4W0&ZCWcFJA|)3BP6^RY9eE?<#~OZ0PGc1@^~W z_&llx)GsOcC@AW5nk@Slt4D#wH4@^Cv4p+)vx}2JcVeEek{J3ee*;-sM5^kg1fZpT zv2NUdE$nQVY*g99ek?)!Pxq$7{lC*a(&YurI*OmNfGHBU=Q!oFRb}zale)3&SZP`x zF6~#YNZ{{93Hh5O7GDyh?>*6hnR5YcD1PUDqPeXaa9Hwvzm>TmF5q+~A-}^WGs$eha87>%iwcwMc<+&)}v7C=`)M$~2`T?FV#u zB71n=IL@sHiI;Msw*tqZZnh77-P$(%Dj4Dlpjd4Km}Kg>7N*F6pyDqvHwOojetW-{U3e=-=Tf{*J|R!1 zvRdodih0T1SlKWoqysjy=6$3~ttqD(QPkQ+t^7@Wa|itlo`!@xV$)>zr{HNI$Lg2F z$ook063r02_wb9@@?#J$bTKaQJ01oveQWnzsmrZf^_-aURx*_eP7uNdm&Tr2zfPao z(p7wwk1N8i8D~-UV4LM1j4sB`BAls5er%zyR%)XTXbH-;8%Oc?-=0N79mSV5gM=xT zyn-&Vo?bA2kC9ZpWGR4v`@o$az#Z_BXGMeFKw05>m+2nN6IqRnC>B{ony&~qeRDCD zL?UZ5HSSie)o6nEI-Y2*Qo;wX7B;$+;fjuF>?ZyEUqUtx0^fZcAuoMX#6yoJKHFhSL6F=RENmrT0V@8 zC9&-IWU2N1P_T53UK5E2y-V{2fivHN_dV_ie5aGg`^x*o!n&=ikMqG`|DCSQ4ZSA! zoM%Y7?~9{ge}=vGM=OC&@^LL{eS1D1$I3M(fz_Ant*N^>REp|8rZ9KXmhszf=2m|j zx%na3UV10w<13HflyxD0LTWj*+?!nvm^E94yef{me7uwhPGSsFtxnHGy#ZbWWa&NIC+`I+hg*dMp3NZ;JBwu_cMZtWq~;!Ezb zs=cK`qmd%&E`(GN9jQtTVXA`Lk>f{LM>g;TdVLsyNPb%e;|@| z*;n8kiT$H>uNUp4uhZ2( zSjRB1^yQ8?zILh5!AeP2kF1K}wXw+wiVG1#iFRaFu`?AGF?HBr{suXxhR~NGlM(3I z`tUCnvM4aIM6SP5;1zh}>%8wxm&2F``6I)>T-dbCAKnfH-9^q1NT*?oaHp5|1jXn2 zs9_*bb&6|q78O$#3Pqn9Zyop}@~UKrAk&zSAF0r_{!2G%yboa_o;^_T`u(m}?#fw# zoN`mG9WjJetB?Gjq6oJoydh59cj4>1|3)PR*ebWTb)QFVW;CT(+{$N}n{n$f9OS2d zujjuJ?Ktmc)WTR{9uzcea3s9l%}Iw>#BDa?vV7fw`aB)*IVWo98^!0eM$60mhjw(A zCWp1bBdQYngtcqZ%Sn2StBaB=lm?;Ij_V-@hsLrgeR)6u^Q@?! zGROO)nb2;fX@IawG~Q`iKF0s3U){z$qbvO~F)@*%m@#1$`%Kc@a%6!(FNQt5?K~iy*Hcm{z8846I*OLSF`0PZ+qfp6)a1lu$aJsoc!BkwA8BR)6Z6d~- z-dXEf!Rs4DC>|w;{?O>E{@MG(&qgAK`bx7fwCt5XBgO|bV@@b+c~YO+#!n`XZrI26wg^IKW%NJT2zPQ{WPy@gSK-YZ$pakn_ z!~o=OnMZ`Z9KB`%#P5&Q>T^^4PrM9^Z&)k)im#2MJU&D+|579v^u(>7`P)5U-)vn; zaf^!FTXwq{ip!OE|J!7@3Zj}8+zIU!{?SH6j(_K$y{}hUxF6ElbnR5B;h@ZaKOZ&* zLTc!Qz1iNqsb391@k8*22S%v(p3!jtppn@gDJf5DW`(<)b;WPT1%6B8$n_ZM;eP&? zjyp@m9rlO)@t|QXICi`q0fEX2v+JTMt@i+r+Vz5E5P;d%A`5ApT%yJepPmbX5Q}KO{Z?2vWUWH5DE}q@sVo zIt8<_Lkz=By?wsvn^rQ zX{~4h@@cd8g%G{ZKdPn_Zcq1!fE+;QS_{T@!vc3u^uP=k!_lTnx5pbd+TB6uJx3sH z{*3-;y4q(lU9j(ZcQKKAtg4>)R*w$O-3}eRcdBYI+D=X;PeO-S$iGYu|hnTFwso4`CMLwoGv1DQ>oB{jj# zTf4H80ZHi!8!#bF9PuEK2UiZP{LXvdxP2U_&Wu<_*Q>(Mfm*@I4;6P z{a3YpXPzg}KmvF9mTD=#3;uSzm|#GKMicdv-r);*X4xdwPMzkz(;wiCBCWQ>`QC&a z;=yGBiq7YY{75s-JHRRSQhU?JiN^gppxYa*fk&WeaF4gF6z{MD`hcDAcXEJUssej9 zyNhfxPs~PY=ChS$s0($RmB(>e`djnxub&%G(VWZiG+~gKHU#N*8mFd+@J*v3C1{t* z_NCiiTsg1wsokzn7H&~PFC-{G<#>%=m%F+KOl+24y8fa${NxepyF9b<8f7(NW;8{l zL>SKkOUL&E$RT*QgP9wegU}86pZ`a?{2!^W7QKorPXSdbq{O-HOfyfVR(Jn+hi;ZX zX2tfL*rfzdBf|Ohe)q0XNOS0LLs!USM;L)JMh!Q1t>eDz`mCA}#zrntlZ&eN&fw2X z$yH-LORJ!1X*3sH>Z58yR~%MF%pQz_sQr|zd`JRsSWtXxpf=80fQpw`Z>UM_NL;t( zZ;4#)%(S|NGxfUJ19H=vD82tIWea=)97zA1naX3RL+*=?H#g1?_7K;Agmk@iw?FEK z5}w0!pZvu1g=(VX*+3=+yJzxS`?f?obv-QwvRvlD1uhb-uqxd)yBrhtBdNWI_Y6}& z2oh+H_Z@cSIf8(8>(lx@&B7ujy7JfrxjcDCdbtcT_~k4JYcZC2nXWB^HyStfPlRUZmJA9otxI+e_6hF1Nj5$CsD0D zv^>J!kKZ5SY{cVpdA5jQ)E9Ywerx-_bzg@eWdp_k_OE05ZVNKS_g56n8kEfSaT zYzoQo_84dKJ+Gs_vC0#LjgytwK|RX4Yq=EkS(eiH;T%5fWPExa8o?VM^tm5c+rWcL zbelE8@g>@=CQN{>@|CV1O33*K91C;Lx0>p)eSVz7_N71acxxJvKV0ztd6bU-K1ft_ z^%a*aczsd+9xRsm%t$$?Or(Tr)^C~cpn(0C&4gmoI;THF9(B#tPT(A??73_ak!aE& zibYtK>oexOkv<&1gjWVcSEl@5sw#c59>rUB{mgw?A0_QPk2udxIZ98al7up3xR01{ z+t{cb>T*2-9kg8cFOLo7;9!a3wkMFQgu_37ZWAWCF7$dKfO#QjrQ!k$wdWr9bteto>t<*KB+|81 zyW3MBo6phTC$)%Q%mm-qg98zOjXz;eIRw@Y9#!MXP~4lCi|&|fF-*;VjVU0PEhQPY ze<%1{xXNdjF=u}gr2GEz8x5%bbCF{*4$k|JK_l_!17e=i_!OdQ^s2a3l1S+^bS#$m z?=waMo7-^N0uRx}mwLT#AkwrfC@mnJ+eBUMQgiCeJS)yT zNqhnC8z?>`P*E|!ya-Nus6H8yu3%YyNi>PpI59^ZuK`ITXR-<}Q=?TCq~+n3Oo<~E zcg`Phb>8XZ9s!r=qDn;tY!srLIXuT}rIhbRqSkc&fN?+^;W` z-yJ_a5-?VAT`EIFw9uuwPW{F8_1`|S>Rd9s>%TZ8Pm7nHSh3RDr*3muqY7F(6rmZ+p% zV!v!asL#dmQo76K6RYTj_rjgwYu^z+ghvW(WsxYZp|N6l=4ZsaesKM>q!@@cn>gA% zbs+jA{YjpWTJgPjB##MdNPO$a=HiR1?mARYvhhdj!@Id~*CO+aKcz!Iu9$?DF+SC9WpxK=BI?y6gmd-=+OK zH^u@T2bMwL2Y^8Gt^gZTFuxq>xttano@5MK!#3Q2iCVHf-XJJx6@Fl{{I|H#{@3#*QjHDpUU zfbBgz$9P_e%2(U1W)#xPbB7^gH0F*v-c@xY!nl5Hn#ZVFBeeFZ;i@G)ckbefkLui1 zAf#?tzLD4d#ZNi!EuY0=O$xtO9Y3?q%H-Wu9|ugMiZkS6=Nqt1lw^f#30Va{g!WH) zMZ{nTFm%ypRO92tqTeXRp>}$D0vB)xndNh7Ng!(SAU^xuL5Cu3Yt`mWI(Nj+TPaGA z!?(4j93sIu!~%8S_ zEK6*+&-KFh&3~rfKgC@;EHHwf_Y4>y)^zenyF$gOhz#h>!fz=bF$plxARglA=75o5 z*Pto|P>`GOn#1qKzj^o8UJ?kI_}yj^o+S8);mgg!qdS|WCw_;uRz&L#ww8i-Hh^F& zXX-iX&PGX$LHN!$v*uy?*2EFIf>|1MZ}~^e=-V9+oof5rHv(_453=u*!~;?ZT6vgv z4Nn?DIURZiMkXCjbsIs?74>^e?cySX2AMj`AhzGw?$e}mm4*Bmgzm zUG^*DQ9n*6BM$*k6}uR!b_J^8)d?sO23JVC224d&ywP#`USc(LHtFvQ{-93-e^K1i zJAnbJs}r{F<|(g_8%aLuZVF~PR;F^sC>K?_+-LAkt$5Q7M_E^>tv-xOY$$kpS&GAe z2|kDe${wXXhbUl9&5me3W7qh?9m@yVS|2iiw@Wd8^Rp8M>5V3={z?4n3cRMv&`W-g zzCIRoRN0`r4ermO+2cU&GjrQA2S@H;8Ct%N1$!Mwq72~{T$9< zC&d30C2v3gCC+ZO{TmltJPtsz3#d%WYnX?;nrs@#=M~_3m2Y}-C8}+hzbP*P^I2{E zJ;qG{_GNmU57Qe(e*+AkFrQTAlO9Vta;D~8I1P$N|Ly+q2!W3T2Xp6dTG@uq_b-#4 zNTVIz{X1|qwWI^;X79ixx8kUfhRLj_`bD_n;NV3X+k89W*l685-<|9=@)wkyT5fHd zPgDG`w`cjUjGHRNp&49(Ar3)QQ9u2Y@4kH~9P?lhI$C){@B|Vyg63x*AFdnh42;7} zJ|h8#0m>`cnWfo!AsH9!TZ0nB~&#Ilxn#G(t?cbde$eaZZfq9jrYtEoQ#oapShq!ZTf&sV{ zWXo+O#+9d-Aj?SJ0-P>g@-?D#1SKYPX0DrFa|31a_f~)~B%nIaz5|PdCKQlrfgLPq zxQ#rlI$2;9Rn0sK@X7)nT0j)h@Et)#fu040;8QOAP$RouAh^eddu^t*jr)}cK$$70Zb@swMLl~p}3*^bck z1=f~&Sf)SU|5aqjY@jVoW#1_-VB?l9tPP&wtq^ zvBKSa>L6w>s$AhHbXq^MA9(YwbK$Iu*qbCae;E`FmJ!peWG6V*75rgYm_R0xMdSy- zf210^nV*38f%nu#V6W6wuKPTD11M0_-C^Kx{zk=Mi=1_`rGZ%~qd0%2U{j10cLhrB z7es%tlZUaI*gE25Ez%R@nMP;Pw9n1S*vdgQh_3J_8<2@a*gV;mooj_=r5QH&wsZgc z$Af{8JB1a*1Ni_&h%nxl@4?=E4?ydp0!#&|73;MeJ=dMV?GIYOaCotG3@YEA6V=6lR z6l)b7s@{1hUkfvCXb_mBu_FeXhn;G)!g&A<4MYjw3qX`qS##=fld6B_RiA7k=b1*} zl14x!06q78XB zhT&I!j+tCzg5-2UBKu?E(fUDM388)gALs*k>)S10+K6uK9T9$aHLHkgLk-+1Op%k8 z7V=!|7<=AUWS&=xF5v&|9|xTqju~J2d(Yc7F4_amSbGTl@3^GK>yLnhVm-nWD#*q} z2*U<}EAPN?Uk~GmYl>eDbVZN_*yk1JK_n%@m>!F8wb-c^sh^X-ebO!DM;B*ZEF1xF zu7yPCI zC~(6Rrg&>If}bLX|3l=oGhu>j^CO#gMrx_B;XDTSbv0p9P$v*G0n*IIl z!y2jH1goE?a}Uy9kb={=%{WY6Pq2jpP18k_=-QqH>!}XbWWISYS_#+6TuTx$!|K0f zOD-jc5&2wOf0At4kn~H1hkGh*2_0UWvW#Ua_jZj4$)i%}^0D-`Tsu0%;WT3UepXe2 zADPxW>;SxdxII4Ge~n`?AQGd@3(}L5s@cUKc8{BQmPP2n)BV)p@AY}B{Io?Ud(Se9QNEgSnlr>%x<7K z!7Ea!_)+BUUg{VCix8nt)3fjaek0JGfVN`>md>?`yG(RK7KvCmU=jJU z6ZL;TM27dd2`WI|BAu(bwQF$TWEBbYs$?TIFD0$yWFXsk2kwxPx@=&j)KtAhsh!8iy*oS@ow zJ$YVbg|?%59-`B`54O@VI_@*{l8O&|<5tuWB)^O2WFpxv<8BHvY`+6*22*jn4>J0C zkeO?_ckOccILNO#T3A7d2aa_WZJ4O~_CvQj$@jp!A~8D@-zCtjgV;G$hgQ6{K4KfJ zw!4$O@6&zen($AZp$N?hP~wG9IKtq+oO@^#a`s3D-_=YpjoLr|UU=95j*9(lozJ86 z_A-!5_oNMh_ssQGS%t`!#)$nOVmb(urC32(>?GpgzXSb8Lb7um72S#+_|h5@9wv#;YZzt z0inOG*v(y#&a=xk8<@|~iHSCl-KYtPV-eO?j-D%XqIVCLyE=sCA=<=@WhF=1sFER5 z2;;nYGy9hfz*__Mc)^@BbRRJ4S&KbS&fkf~mv)_*=aD^aWY1&F7=y|r^aGfD7(U!y z2vF|Hlk_d(XY>_Uv&r6E{13}gWI=9*tp02~uU;g1`;>dy>Axj~7jQni{W~puF~GE3 zlLUQ=WSUtIGQ5j|j^rs#Ccm4L-Zb0`5nK=>fg|I)t&BHNk?Ppqw3VQo^vvHoO@b9{!Yros4-SZvOXZ{^EO&)i?GND=5J(majNHh_a1?wG z#o($Ls`BIcDLg!VF<#G4S%19?oPums)hNW+Dcv?fMb}5vLR8Uh^JeH zgA%TYld-8t!)fXeyIeBM<}TNQ2t3J?WG00|qntf~wn$r}{>iwMc5;(Ca}nJQ(}3p6 zA6Z@8qzJtzytT7~Z9?*N;~Z~+tCifbe zqF4jX!7>1;>?|7ZU9}IB*3EmY{^Ylk+lA?9&Yo?wYS0N_A%f)ko?+X##Tfl->;1`3 zKh62G^9T1xq%n$6Q<&4&Q)OBnmqoUfvCDr!<6vjQSfW6;Qxk~C!7%iA?80#)tI5Gu zH!-@5ay|<-qFg`6LW=PBT@V)9QB!A?$Njh%Au`G6IG(rbSQcqOFRKI7YPs0Tf^e{!{ z#pQKd<_tzz8JIIy)PVWSS!pG?7TL!2+(%bmOr%w~k+hhLK<~d;DEB8LoO=>yVoPV~ zUA_(;%|9{PuVOcCqEaUk@mqd59_qvK(GIl$Dq9v#S1Itcp0|#>B^q% zZ!?lE4lYx{Mly4{kEgATqZKefBZ+&8=4eaAV_Mf#xn6Lw?WH;6ZsPhE^P~4GkLJcV zaKvE$ohHZVdcCbWEphB9Y;8!9aa;ZVDUTI4P%^CXdsNT?_0#j|p8}(hm>u9a7f)!a zI{fQ6xnMI3+#o`n$%?*&!y>YT(FB^9eL5X#u)OuBL)qimPHn?W^ZoZ`5U(N;cjMYC zFsaQ-kWcQ_M;7g4QB*#bYBI&VpDX4+M zkUwZaC01F-VuOq+Nnhs2DypbcFG```YUF0LbRi$sr4*l@fg^#b{O3@Y=E}8-X!yW$$tCyZd&js?VjGDwq z^!rcIC+GrJWeBb0wN#wZaZYX{BKO^Mi1zSwG>&LH@ZHN0zDy7P5p=4;5`f)d)6q^P z%1Conl+|r5xu~Ig@nv73nLJSlY%QAPvTRwO|MLnjT}yRjhy*q+EtdQ9CP6CaDt^Rr zq#t$qNEzhJla(SlrsVQ%dvzRWmPUs=Pz8*ua%jgJ!82U zt^^%p5QG^f;bHj|L;a_WLkx~wELgx=oB;m1Gc(l?g^fZe16 zuEq*7FT)+`^C99Jdb|KtLEViyw{M$Rm++}{s)f7)TfJLn7QV>&ZOn8VEhkVchP9lBEwUtQV?pX4Xjr1b{W!C&%?XsEXTY|A5z1_InTrp zBBl__(7ulU5Ey`$C}Y6xVeg9LYs!;xP7A$^U2RoTq=1dMq=J-xT?mr*aRQewCNms_ zS0_Gx3qrZw<{q0(w-L92lt$zA&YV5r!?Q>?mLeh$izKj%7ZnsSSpG=xNj~CXio0S| z>o3(AOI9$wy6|h2r(BX^j_;b8{;jkhS+@Ek%eJrM1Gn!AgUyp`p=!}PyhEn@8ggvTlxTTUM~YZOuRop06Bp9vc}(X0 z+8VkMTNilT+EIIc_uq8omUD?k%?^>}f?b3RL5g3_Fal?3^^QM1!S#27_qJ_1+z04| z7vuqkg?pvLGuwoZs7F#eM-m3jmxRm8EsY9NAbLPU8n+Y4GQOAHvk7`5Q7}LR0;(N0 zP>Kt*w)L%cewQR~>^1xh3{pH1r*mSzW znIBu^=-jhXuzmOqk?}uh&?72cO_G4QJQ*$x1BbfNKvc7I+u=74Ec&cz2F-T74DL8XtM)_!(4tqI+IgMs_rCPxNOF z^s74qOR#*mj__eJ0|w68OR1t6?*YH{^kZt=rPeEZvp|KuYu0p2+U5kDB;&UP{x$7g zDX|5}DUqB(zqFvaa4w?mc1%c0uRFv;{57fq?fpmZqV}Vcy5sKiPVlx^-C^0UaEJIX z{BB1PfJ*ZQajFiY64Ci>nE3f#>WC}O0P3}YMh^luC7L_G*#m;2bCp#uBm>QbP2+WN zLNpP}k`MnEgfG`0Lp!XkF2=~bouOLJ?Fdkm)&5kJ9QNMCL;C!u>#m2PO5fU;HJpy^ zR4BE)@!m?xjCegl^{+IC_mSqPNWcNGap_dl{Z<@)lwn4aL#~2!dagPw~sR{MamP*D7ii(j_xDi0hB60?V0R-nWmb5cr*Wb z=_Vk%I$53X!*RDE{lvb9?z{h%a)_1xU6J4+A!=#CAK+ouYg88C=`fpZu;XzJ<`?3G z=VjH44?S^s^z?H79EDnG=O;mYOYF&_0|92T6gZPO%9t8gO(P|$r-6!3)L`jkhxS>` zp?~%;0$KooCf(mDYfTPsiLj(6mD{Z3Ietn^lx2F2b$(8pk0r5!I{A&0MLLIvLM<>r zdLSI_mk*EjlNzU6iFR?YKrAY~i+jMe?I%m@wAW#+gdEEEQjEJ~T0QzDLWMG9GN!0@ zLgV`*a>N3)l{L%--uQ4|9}oQT@ktVR05`hj07`^e^b}6{`=HXbH))Y0& zwnk9o{3$9SN!OOv0?*>z&~X!w!j^My4D=2?(OJN#=$$1 z9r9kx>*zpYENaCiONsGd&PzhNr(ngY11>o#JdM5u*?dq0I^H#1Z4+yy$V(c8WjkwD z*7vp|Dj@F*NxCgi8%nNIEN+@7Ek=ED=taj z!e65@j>3Odpw03X@&9dzORZlPSxc?NG`=eSOvvBOILKp@h6B5WrX)Q@({?l{e#5W2 z>k_!_?bkE8IP{^!olszv_U&KJDKjWMT4p|;nMQa>Cd$-N4g55{#HZ3LeVi4II-22jQ@(}J`jKoHKY;tAoHxan=ydb8Ov5tmEg-JNRMl{4^v?0I zQ443NEDr>#iRCzt-WG-76z&w7=zSt#X=~&mDg?s zRGfRfUb!+;X8XBl=Qb?EYC0B;dp{xbeG{e~`-5_pLyrIw79CC@JI8%@XMX~>_Gm@? zCzFQfG@`DktV|YD;=7l~1%90nCe+1rh0uUD-zay0JKl>sC%=-baWkco-jHuDxy0(} zoKHG))?I8NL;?%mJ|<5apBfL;l!ehPn3DNdSx{M_fa?xIjrU)BkoJ5P@@OC8a$WX6 zFl{nLb|P3cm*EA0KUiQ8(gYa4qxUw}c93vPbRw5J6^CU=q1{WQvm@fvjCQz_mRVfu z?sMZ8lsu7XgC*nMz?t!kB)WPJ#=gJY&CG?rSd@F_bo#wU+z}-Fjp}^n%6RNh z6WA+z`||H(vtgN;tgkqWv?Hu9#`rCBV_IM2aDRM}WxJcC=etUa%os&wCgaNklw!(H0KF|C~YO-+m6qs-|ihL*H-oYVUn&^q3wda&(AS{)mY8tK+0+#<^= zQ_)!u8;niVj-IxIP#TKR`wkTIr{~LJ!L`d^n*`aB%aW@)xG}i73@dc7e>+xrN#Aaz ze}g=|xFHWP9YE-A87FtOCTL$2(-+&ZWQhnvfNH-ilDT4qFFYT{YFbZRn@fcD{n`7z z2@`5>&wa0xEyS~xHi~u^c_J3v@gJ(DO=sOK!4!*N%KazfwWEE&9$54e0L!sV6I&;J z_F~?_l$$Yl%t(K5#C-&!Ue2=t14}a}F`B_Y^j{Q=0nNYfz}Ek@YbH7=N|!gT;%)yr zt+zgsr}y{2X3(|*XtFRw!bG5~pKC-zW99-pqCG@x(Ri&LGNaecWYJ&YZE#?5xAK7h zeVzW<$M~Ik3y^qC_>cT_?RPS)xeeT1`zlx3Z37hP4&IoV6ZOpQbq0hCRp*_2vMX-) zcVK(N%NN_Y=wmhaJ<&!~EL@}HK$C|#>djv~$DM6W_kp+z1Cz5g760h#TTR^~TwPPA zRy*g_49~{E_}O3}+8|9j0g-U+?|+Y@9WaD6RyM7x(feC- z{Yv#*&iLO};+n53&CvX_p1npJ!g_hAP3;4T#+|{-Tok40$?~3RbC_mq#BnkT+`*#c z1!0o`X&|th;qBGSKZ@0nm70Hz5Uvp9B(F0tR}Km?$5tAI=wHd^Q(pS9jRE3cv7{z2 z^#*14y0NU1y0LPRl+#{{c2W|KPj8HM$PUv33JU_Nd-eU0G*Z#98>oS!iHDP1V{;FCTA%H68O59EBj#g&0On!KoZezafBDK3ArGAm&eKG4 z^@Pf=B6MtWYXU*EJmJV#<`i^e<9$zn0fInTW>-l>n&8L#4f@1bV>?aV5(xhH=kIj? z8U`yU79e6UgzHK%TA{*YlJi!?HcILrx!DpTz8uT7j|?iorJ<6b5TA*b^`l?DZfM2> zKz8v^5RYF`fJv!+dWm8pNo}jQ3)JwWMv2A}eURWg1e%e|sfpLFfNMk0PI9uL@2@{n|t}8`F??1)p?3#g`#Z1Di7;zP*CS}c0Nq|eJ{tdpJ zI$vh29P9NpBXasgU1AhWJJL$c9(ve_`1>=N{vs@`74K7~IkKRVzs-IE$jE1p-T{~su(@1i7?HsHpcR(R(C{PD zkA|}I)&u{|dL3c4?`O8Qi3#{j2(%J~d}2QN^K`UlY;P_VcFI`lM%}YJ3UGA~#1PpG z%;a-wh56Ufc0I&7W)sDHB&kEV4m;C87@$B4?bWtS+z0^qMHp!eTaq#|1a%EntA!Qb z4rHZ=s-&P?`^E9gtfk`wjg)VGCDGAF1nSA&1TtLIy}weTDpL8$b zBeq7MI#gyJGlwn&(m~l7AJasL+dBwWa;#D=nrS&?pG(OXbNeT`To(dBv}v;buWO1P zSZUj1cx~M0qQ@@qEw+i>IV|GB?8Q2x2oEfCnW>o(c0X|Dlb^=KI4=y4F79PnGZ9U2w9prdjhHI8{8qVD%&z${<3*a~i_1`S?x1^wo!7 z2Q`X!k|0_CQ>?6ByRMts0L~@<+O~_>d8wl#=}?VjIj7lxtdU(=QPr+Ed#uzcTej zodyQ+qq}>zKr^nzHcg5;n8f~*ooLt?Z(@5L5omN|@q;9TPT6&}br###yNU6YZ3~qe zB7v&~HL@(N6oJ++s{dR?k<+^d4C~;=o_o)~zChh1foAm{p}CXaJB*k0`A{Y`;qsJu zS^G3%`L&!nsK_=WSl{9^JP2qv)-9}O^dTo3NU`OluxLKN{byFIHH2BAQbS0-P6Kda7vCxhBeyR zrlb7iHDxiYC9zn3b06S~2Tc2GGDATHw3BIy=Dn;bJ6xlhdiqvy2VEiGcB9@C#^$Vx z!oNJG5j#U`ivl^f z;gS*$teliNq`(>7>y?Wj>|I4B&s$0o71QnGr*nSYtZ@EdTVAO(EbkU)_0WSSdsWl( z(?T;oi=&05$98m_JvKfWAD7Sn%u z{1Qh_ZI$6xc-a%828b_ItP3REa+}uz_FMOtebN|>-#@IZVE&4TTsE0M!Pla zP6|vP2zI0TaXEkF0kuW+9eelqd&l)L4N)SO+#q%!c2V9_0AJ^Mf>b`+JJ3G(%Ew;; zF%g3`dpzi&T5NK~Y-Ld|8 z{k?H@%On_;DJS*i6j9gWgI@t&Qab1KVr@*Lf-rw9yVP_F*LN`9mqE;R?+JGf8Id%T zivHHdm3EVkCCGXXB(Z8c>-63XRKz-Bl zZ0cGKUkPj4oQS{`1*&1q`wsKaZsQ_nAH&CIGgZsxHLdd^*%FQechsP{OmRg^8xT0& zw-J_xL_qHCCzvYq)_&~lM*ugZ`x}dSoZnh2lef_3oOi~%v(f_N>-3MII4_$cg!fGK zz;Mb&cuZrVZeVNTRKIHr#zpdQ(C`@vs{c$vBOi!@aU+*pDyYToZP*f}WXD8R<EDp!V-T~=Gb-PF_HhORst(b%x)2IoKsO^CJ~?wnYDjW zM9j%984F%WoiB}D#v9^~wM1oLHVOJ%KJ(`NV7q)%mghU>{Od6iK@+6QLe@MjHQk1Z zi9bINGgjU7<|zS`Jk=hMgPivikyLaywr%ZuB^}{A%PLIEJjeC8EPDy1C09md`W&3C zvyBB0mCvZu?;(edGTZyKrCYSuSxcpGxP_yt(AUi_kcdOzX-0XZ>HMWJ2tG8%v@;J- z`G~Y0B{j?-M|kpSzVh2FPm7yI=XfrcD-s!v12~KxssH4I1XR4h;WaJj-~SwP{+Rjr zEi{03LCIt8xPi}Ev75}TIo`%@rxC~T-Lmmw6WRT=gsi2O(Ya%k3Hb4R_fxg%N;V~) ziyaMfXN8mbGvdx6KjQt+&kJZiWy@<$#r>9=-jDIz$TY>$3Vy`ZZka3Lu);p;S>f5@ zyBK|@$-pYLq78v|Ev597XlMbIZ-4o4mur?{u*zQpBu9n?%BCOdo39HfqCg0n!jOAo zJ|VlFWw|LdO^J(?`dX^IAg6|<0_OS(1UI5$f$ueQ=N{)+6wsWY0 zR;}%N--0VH32EA7vl*OrS>D2qE~cnsSD5*P>iR$|a+H#YL{G%}mY{_c*`R3YLP?~Z z0ca*Fz_50D%L=541Quggh7c1~l{^JiRW2P?ak&3$8;S=F<2*Yp6TP#dQX>4hg2VR_ z@AWfU-`WTL9!^-9kwbrMrLJm~{s&!E*2Qh2@}{7|Nmd*#Nnr%2X@87ecQz_|UvyUc zMN`vU{)}j;?;N-gmN*s5j~oZ}H!jX@eKf6;3e}7l>{{@z`(X2G7^Rew&2U}~B`LfbbUh+01u zb-RR#^wZ~~Z47dzxqp5K4otIGL$vrt@4lNeOF%gr_w+0g&NUeO!_2r;7~}nQ-UYIb z8e0Zcu8U_jr#M+eXuEyL!72U#BuD$m{cF*tZWo=RIxtft5Wn9JryUM3) zaEL=-O!C>5$-Lg$*kF~(c3@-#+zjhE)Bk0;h6p!FEtOQ@_V&I}CSj^wJY~?bkwXw= zH#0P5BS%iz3bLle{EuYy-uB(6!11R02zlcp5P&&%3$s&88Q4Y3Dt z)0JH{ts6OcV#q1~LDyhS*B!7hEvQQ4y2EY5nDRwVSv5_*hDrNq1}fMLTvFizdG>El z&-nK%EjpAxiV8-Wa&c7uC>~zrQ;JcZb!(Z6 zaq?HaTH3J#6bv2F(mWiJ>`vfW1+ABtj;Mt&FiAP8t^_99+`FVfESv!~Qg$4|!iUwh zdS(*6-YN@FmKlL7vk#5@0aH;gRiXdt93p3t4)w#b_RR{sWSEWxG@+5be$#V4vmkD9)^oiXspb}l@c!G{(A<H zfcert;zA6z&0S+JMnIwd>|4~WrbL@pjkJE3O(znoFLA%VYuacI^9j4ZADu(*)p;)~ zUwXV$4sI@4GU=G^s*VP?-nq!-k&y20l`D_ftT)-?rx-{ zn+ww2jWn0;l5W0x@%sh)?99$NGrO~MUSg{@$cSb%u~lB~)JodIyb#p_PpVL$yy!fN z_o^=`3_USUZ0ws$WaXkxW#c#LQu=`%)tUBEF6o)JI{xPNCysrAhwG|V*=^(jQ~bFC z9SnlrV#Bgmoc(xj%L5=4Rj#4#3;53Z>>0KUd7QLu$Rq0}@zAutx-OJR$<+IB)S@aC z%hp8{&c|GZ?(^qSy=-iu3AZ`%`9qZ{!p7t~4DK#X(Jz`UqolGs{>;~LYo=^wd&nRP zH>yZBdHOTtPosS0+pC6Nv}&!P`tC0O(Ml<&%n-l1UILP0bdu`h`Oy)TlsM6_Ax>5BuOmg z_wj)TRj`Lr7gagD6rMVnJcxVT7SEs0)yT+S!mIt>-K%{2Q|-MPln|6rFr2-m`9^f4 z_z6^b`q6`lrUF(}Uxw8WcSA6XY_%|SIf5Y2kveQqZh+16J~ zsEeXMTuq$q99q`TGW@Ax(X0G!Llqk0 zDvL^HvB4-J!O;~L8kEN^n{usv|~IrnvWVYU(chDd5yU|qQ`*J z>WP0(E9w(@K{uRht)jIvJKsyv@_A_sGfUn5>GV956*Gtm=^WQ)OUs6jS6Fdo9vHX{ zi^HnUiI3cH?I`EWezZMZW?P;aA0!q8wYBd=5|A9E)7?*UOG8@B-#RY)YtaDmo9oGw zi708Esz=UGkA_=e_G{}wv!T44pE%sbb7GK1bGL8d^@6e&AHi=m+s1-21(jS0Vvnb$ zid%9>AKN!E#C}R`e&0a$@qJX3qJ)zNMS7w-q|l<=2I`Zn#6)4j=WO!#>ekCDOK64r z>G`LC@ZxRXfATLR9T1=6f6jC3iudWRj0R8A+O)e>F0_2YHg=p9YA0sIG9&m zU?1CrvADWbS#ul3NF4n@JQs87<)X$Hu-0fv^E*rbvebM&CJGq+?^8=J_=S?pNEXKr z&f{1}3EyVw{*jmMu)^QkS7vLXthg?IlCp1;L|tWQLs@|x=fsw_t}_@Q&RRQDJ)F;Z z@0RI)cy=#9G!xeN|9obwSy6^kh z?O+oc?)kSSAU_@m)1(*$x0gceG zH+y2EFb~>S1ejd)9V?woC2>Za*lrkBw=&3J(VA>$31|9ckq?v9?H9@D)~g=~Bps}t zV$=(d$gdQfA68r|yR)~wkQ%c$DU!mzJpQ{v+I+LT<|s@jp)mCA<_?dRYbZ5z<3Rcu zT~&%6CYwV%jUqh`X{(kpf(A{&_UtR+=rx9jmcR*ESnCg)JKd(phw~k zdzZg`AgR&snMkZM#2M9p5~Rtr-Y;8oxg-s~L%9zyD=? z%;%&7oqur|ioYdTv51g}N131m+|d7Okn>i3SLe27Pm#<)<^Esl#Z1yVT&fbv(JBiU z#j)}rw|UL8uDt*YY^%wptVZCq&CFc%&UN$F0PnI8w~Y=e*Gp-G($g&`!eU)_5vAr) z4eXq@&C)89pe=gd(!#+hoOPi(Tq#dC0kHkw$5GO^*0*H%H2~@ZpWbNscRx=?I%(5D zCY?X3@%7Z-s2*eV9gBEFhQ3qGl6rWUq_N4Jigm^}WjY6yFvf*Mi4<<5F_an#$et}4 z;260sp+*bz^4f^20%8Wc2c=C2jy@pGpZHE>PM6tu|4ehIiMG248r9n|#1(tXZHFM1 zuWoTC@sssgjo85c+Bp?-syO9Chj}yiV)#pTC?wtaZ@M?HFDmw|2}nNc=Na86Bp&8&HQDBwgLqGv( z9FNCYw%oVdacDzZT5<&byCFq1DE3Y zt#S&FX33k`??`Y<$?J2l^$Nw#s5u59L3$wibf!0VVe2nfLP;7dLS-!03e7QjY(WD< z$++#?tvS&?i`SkUy(Av2LuEsCx9@2m-{y|Wz(V1Q`3<280^9?69y*p9KX6dS{7qJr z==fC4a&q4HI9e4&^GSESx7GhhbJ?sQ2l-Hcg+LxP&+itFfJ=LJW3+b{?f&KKG~7gdsA5^}TP>9{jOGPutwwMUE~}sYQTAZu*>(KNFsXx@*LY zcijc?iRV6Wj3k{U(A4eO)f>qm4C)GQFK;G}R^gu{p$rtn@Y)L>0VY8Dg!@XNU0tq< z(x`r@k-GEjk=;2ruH+jZg`4L_CN>Azm5&TWLCdCl&;ih%RcUgUJ4J@qWISKTF7ngR zg~&D2NM6*9XGXfKgAXmNqWfSuI$jb8H^lH%xwyl^b?l0`WfyWLzSwqzDRw-0{u_3y zDhddBZ}d`+=!d8#@mF$zs_l3bO$#XW$hL&YDmX)isA4|zOja@^B8zZ);`I5KylmSY zL8tD-eSTVGTE-hhs(FJCMGSppuT;?) z=XUcCvX?O*yL|4vBAU@=y8n%3;zI0v56wtS38Q?36(QDpBld1SjZ5>ByxYr7;F&uW z3?D@wMd9_A&3f+<8BVw&uD}nn>&Ddz6+=yeqh&rS;$}`!RK*L=xIaA{<`90 z)aT5c3wxtdtf@=W>Qv(k^%-%@ENb{EG=ftscn zDJLW-`cZ^j=DI7Y+6pH?{Eyzu>q0My>)|lZ&+cB|tk=RKcfmbuvV|1}f7xi1p8`L7 z(O|)qC+72)cxAGh^MS0U+Oqv zurJtAW|m|=aClG(7ltBU(H7~CMs~Ez{(4e;9@>y~IIffT%KS;DHO!l4QQZa$=5h|# zpvraosrA`~+L2(yp!;yh1;rEwtPlp|S>z#b&MK=@B93wvc6?!RIMSH1C<6VLoDm~2 zhAhMSrnQX&S^itgH~`VN%$b`%P-pU%wW15bOG$WhlX^)uuy?FI@m9-Pgjdz23tOE>FSU=#`ce4&w4R$j?<$Cx&534n`I{dJirG67?p444Z~N^d$=lus;UGk# zW<>;;o}}`kBKkYu{wkzcHExMuF7~I38uvwnu=RvT2*YI^}VZ0eYZj#~n z->N|pwb<==lF|6x-bw7sEKgQjemIq4Y+w@(c+zhU>&VxS&xJ+|hEz3FgOx?O_~3I-7#|0oN9&neGYi)b0?YAX8r;Ibyv# zhc}(lb&w(ZlPgtDY8;NTdIG*Nw)nGrITeGxEo1SV@9b@9WRUyyIFDwn|AZ7)I5hQl zCzDeJ{Ee~xHWo7^937+Pg6m%y3QHPkLY`gf>-S@TD8gO#-jg8mE@GibbB_qtJ~a)T1Dp7UbVC?on2^ONOsL=u&=}<(7f?X zE&$d@28zG~K*>X~E6T(yLL!!|R*`Nrdpdx3toTRA+J~ko;zhVnRq1YZw!$ofDsCv^ zcaIx|iH{e8K_`VfHoRM(4AGN6Fx!H6=%5sEwBLer$6*v~zw-u#jWkW||XJixXq^GJbzq z6&9V{GH4Yi6goObWu`u3g_-lILWVc&@KZB@tdKC?)z~Yr$VP4ISVqpn2))cu@3D@NH^P<0T_BM z!OTIeoq4@4Sp%20DU-J$l=<*Q^e()8u8)}Pfat@!Zcg7Rr7lM~64anNmaSmD?w^+@ zLZ(84X67al(kgyDF;Q;38!Vr>!x;?XKIx$y?wSg-ys9&HW@T38evIn$B9H((z7XPg zw+$my5o_e{m}4qq=i`pqt~dRUR6g63tUiy1Ju_1r*in>9W9y7D&!GOcMoj*Xu|Olt zM_YOBecc#h1E1!Eif!E^&=;3aq*+<$Z5IZs!s)??{F#L*U2vNsElSJ2DY)u4yiSu-b#3bitjlUj zm8|T!ujB-T|5Os6fa7%6W_p|j-EFnC8MVIXUr=Np5A(YZxFyez#$K2)^?(P9n-H&g zM@wrA$No;VKLHDM^#oTuYb~!=i37cwzA~HMnZFI4RDK5mdhRJ7z~Mvgc2@*QYpiy? zgVDuMPim1$QGUW=fW}g9Vew0O*ay3GQMtC^^a3a5+|rOI+Mjf^VaknVAJ%x{1w&B! zt;hab*I%lHUV;y(%%uf@%0gc+SHxJQ8^6c@J*Q(KAvqwy|Khw_Os@4VK*=LjK@}`r zk_!!Mga~!YeFqry(deHsxI+0DjNjKeGhNRq8&DHHSvT4iAeA*hbyAaezHGb@5=ZwX zau3?B3jNUTGkh(zZ_Exb@y=_B7vXeLT#foS#h|WYwb@2wzMrEpijS1gD?jwly8sgA zB-C|ZO&Toi_-^4PlIqMxm+^no^z9*;-MYU%l8B|6V8S)1n4=xsEo5K5_rB(953xf+ z-6)H#TN|Ed(nlDY6tv+M^^6_Y8fh8TPmwuI+elMP7jqw005}O*w_eLuk-&h1&>qa@ z(Gtb5zZFCR-dP0SOoX3q7ej61<`JMaJ@^fI#~tlD+@H!hkN-*Xm4ItG)y`5J09!iS zT#g7dRl2nJt&sl50Jj3%ZN8FKG58&It!Vskn)TD6pjwbyM9(-}?6~BJj6kw)DeYp< z7kS2}UyBO3kJcvO_wA_A`tizEu`HW9qsE*}b~pFwlkmbf-^oEKsEp zv^qs%id^pLf)ol<*9(?(46X>Kl_ubfzDEN~<{5#1+8-2YxtXTpN5#2#|8PngVa2&W zvWgKCNJ4$7H>Bx#wYX>OH#tik^UmY5I20m09N8l1wWJ~7-6`sN8!OHQiTCqyoflA( zs8;qO`uC|WbdA5_%m-o3*zkt3A47umUJM)rb4KOCb1L*F1Wa~2o&HMD#u8^TOdgb? z+U-4&yI4raQS*uqP9<0F2xXQrl_JZ7Rq66vS;)-d2&JtBTjV{$UUkTeuVZi>ch>zn z`xosG(z5@V9tUE2p}b-M=W~1W_N_4bNXu?kr46&Lt*h7auu~eMI`E*fz&5k3F3>5` zn~UV{J)1K#X_ch@f?8stO}HJ1E5p%lpnJau0@iZsYqHXj=EDNrA#TvB0m@rstyYZ&+LMmS0pQkOI;c`UB3BaBWe#Sl?wjKW2QO z_N5mDPYGpUFvryj%ZBSZsigTjo?*2n^wEf&pY!!Rw<)KAw##B;d3oDZ;h>X?d1Q5> z8G5vxEwra{_ddl z2}IM?*TEeVtn`m4kK%sr++ZXLV2Ht{ctE? zsKu3gI^*NJThS|bVvf*a*LU)5ij)1*E#ud|ZI`Kv_#lz*Dkd<3 z%u>*{WjZjohN(>K5P>j32+utgMgxApk4B&qYJ3c{IfGL}16GYEsHyP_MF-;Ve$sUS zAq;hrw2t@Y-8?#ccTN4>eEGv=IO@rPXMp?{aKGOIHEmbVYCs$Nmt@Q&*9uq(5>)_+ zr_0yM=JKWuuXXFu&3Be)GX|wT56}2TdgvlSL+c;QzO4@ZWdlI@lhefVb(ogiI5ov( zF=D9iYVpMtVPQGNakv+KeH@wCA>^;kw_Z!;zkAS99Nd*oG25zE1q$v+r>GT7TY5P7 zZKFb%m--eS;TseB>P^ERc2wPKfU(&3s@kJ{U^$zlCuE=4@jx4keDbGx7qB+ z;Ir7*GtLmO)K!KZgKq`;-7k#*XURb4eDER5*5ITW47b(A&aX4Fd+!y*cP|mo5=4Cr?rRwd*OzM3U_TMb7ohXZa|J)F#tc^q7ajefA`~aUvJ+>AF^c=tV zwO5(*%qY}j7Rh&#)Am3i{lkTNx0A(>;NGbRt+v)5=x-#;2_ad)UE@2Ensmk`bjpt< z#lVYe%ZD_^-^YWD@vml|eV)W;4tq(ELP0Bx56D5O z18yx|eG%e3(0#|&NQHGQ|G^#8+v?1PaJl5+0V2iqPYcDE@u=t&ri!4Oj`$@$j}6)A z`PttavT-QIM8e%9Hm?{y%%sVSqN?`ttu$T=42ol*uIq7^VJv@{teG8FUsiEmoH-gX z)Q4{SFkg*2L&fT4|9RB{7M1! z8`^z^)N+9RWq6TI*NilLy<7Kq85AZWNf=^G9!a{wxB}xgTt}Q3k8~E4JwGjtwoS|8 zj#R897ea&=4vme={Ng@#%jENEjo+MSRyf4HY2;4VvU6bVj&u0S*}DuHcH!?{m8ySu z8W<*=Efxuftfu|!QEq5!j9_!Y_tc*tSQn%-1Qq){dvZ1KWx{^NT_gQ6E?VLIahYHl z(|AYpdgRyR@b^$W2WSu`J4|qSrV|twm-;quoM8XG<{7i0FOil`Mdyc-ed=%O>B%%J~pTNu@qf`c(J0$B7a_0zq+~d$_ncdKw1P1!+nqs^i`Fcnxw_fhKo9hd7(RDUZF>4eiHHOAUD*o~^kuRY{WD{H8AqL{v?TQAO>?D`b0fMY0Xd zuQ?S5(EnHt2I6*Oaw`C53&HxVgn`dVh4NXKq12jy&y>ABT@HC;0?rSlTb(+u4W;AX zu1xka&Dkhl=Rl@qIFSm3+3!kR);Ij_bXyy3PhK%XImUa@_}OuCX+=iO{zB=Bp3U7iVHL8w^VtcrrY|xwUl*~O~#~|nD&=-=b^px zR(ONvAa1vITI9RGxXw$n+Mm0FousbRlFgn=W}oz~V)!rahl`p*RWwoF4&6SaloPqb z9(eVBhuanBzemOSDp##k&fIy))OOnv%VF!96i)1!p{R!(G}FVl=y9Y0GY)rrw^R~K zd&Pb^Y>3URkoE;r`!yn|vh?aY>Lq1vlLldf7FoIUej;;fJY#i&>NO zzQPYxhZu?~GP>OgHi+0V_~NR-ufhnBU+wimDmCR-`l(Nz{QGnuk94p2gq(fwAI1U) z6P|6Ysy(`vb@4h?ZwmAZ-&1za7ZllR7ykkZ)BhSs^EMzERcB z@5q-QW~bRz-+ymA+!KYPZXYc*_)S#?>T0T&%VG8Xb<2^9+-Dp&Mau;{{f)+qroFp= z{L1pSIDZv%>`~;`GkaUHrp>X(fkOW8P_ z*Bn(lYKUWCKR0OwXqG%$gW4<4T5HI> zmGEyLK)Cso#|ZRFe1_n72Wy`SLE^oY{t}w>ETES2^a~Nw!~55k2ln53cRS>#J@Vex zC(^$iHME84)-P_OnThxce&wJb8e%4*ZnrLKC2b?AvAWl(a0lD5YD(emS z=L2Y7DY9kxqFfAAYnQ@!+P14bw^j_(qml1`^51+aIZSU5^T22m1A+M;pZno*r3?ms zCYNpaNk|aTDRY$K7*UVZ@K(NRP?fAtL`lFi0m-5wBwi?=yPN%x)~;-tOfP8?Z(%j~ zeI@x;BR9~S%$bpG&_=iEqWEOMsv$u0N}!j}GKz8Y;A1;T{*lJ2Pd;D}<&0o5XCu0( z&iIquPw?tKkiAPFXu`x2gJqw@J79B|Y#(%=nhi0i>hK>@fl~pWdIyv3l-z*O#CPnk z%Wl{teSKb?NuzVa^>oH~AnD!dX&kij<*omg6_Bs-)mxy#bBMcDu+A;qJ*~ow*~Kbe zrB%_tR=>~XY8&?5O0M9xv9`0C&>Agg1;_gxEF`nR^%MrEZfWfhm#F!3VsDSi(H}Su zti}Xz@NMo{4gVKBkKXdqi6m>A$N;ajFb=nF?Lj!FCs$;u{{!T0<~5KJnX~vge#}g4 zkzJa)_Q@3d18SCk*hK5>)9@`}2A?Nx9o1$1 zWY;mq$@iy8#`(x#V4jHk`}l6A`0tF_UCokq?z>Tus*SiYm=9JEKuL7kC9{f0`&#< zrH1Iw79er*-Fz;OJofQOS)X{P`$1;0#crONa0sclo7wVbavMB#5&1yBJCZbblq z8&kBqx?BPux~LVkdK_{LEEl!57tZ4|LPdYppZ zF@v}8Y8zC?M3M+~*sK!LaNL19@L@v1({+GFb`Lso{2}bKE@8U>FZMm$NR>u@*IMt- zm3^3{dBIQINDH8RTBB3!oRJsV!4QU9<(F&n9_&!R!J47GA22iqYq`yRkE(ApV}uAX zJ(5`upunijth0{khS0OgiibV9&Ib^Gl%i-nz6QrdX#oJa19_r8c+RRnKlCNx1&;Ge zyxwbMh-c5XM06dqaT4O#EdKe@D8|R%PP|>RMtnA|tdN^eb|t)@b%H#16sAAkx)En+ zarsbk%^O$o3CH3yDiIkRDwtvE))Ptmwkd)3R*m8Bspe?Txa4OP%h?(AvG-rA*^?9M fE|k7bqWi!tZ-_(Pl*;KYzd{j(AF%)ag%0>1K8RsD diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index ed78488b67..d8b5d22502 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -17,7 +17,7 @@ "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xffffffff" + "balance": "0xffffffffffffffffffffffffff" } }, "number": "0x0", diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp new file mode 100644 index 0000000000000000000000000000000000000000..1a820734e10c39ab39cad27671c4fcb6b9305943 GIT binary patch literal 527009 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%0DTvBvUquo^vS7 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 diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go new file mode 100644 index 0000000000..4aaab8bf97 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -0,0 +1,180 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" +) + +//var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") +var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + +func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn := s.setupConnection(t) + t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) + // Send the transaction + if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + recvConn := s.setupConnection(t) + // Wait for the transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + recTxs := *msg + if len(recTxs) < 1 { + t.Fatalf("received transactions do not match send: %v", recTxs) + } + if tx.Hash() != recTxs[len(recTxs)-1].Hash() { + t.Fatalf("received transactions do not match send: got %v want %v", recTxs, tx) + } + case *NewPooledTransactionHashes: + txHashes := *msg + if len(txHashes) < 1 { + t.Fatalf("received transactions do not match send: %v", txHashes) + } + if tx.Hash() != txHashes[len(txHashes)-1] { + t.Fatalf("wrong announcement received, wanted %v got %v", tx, txHashes) + } + default: + t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) + } +} + +func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) + // Wait for a transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *NewPooledTransactionHashes: + break + default: + t.Logf("unexpected message, logging: %v", pretty.Sdump(msg)) + } + // Send the transaction + if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + t.Fatal(err) + } + // Wait for another transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + t.Fatalf("Received unexpected transaction announcement: %v", msg) + case *NewPooledTransactionHashes: + t.Fatalf("Received unexpected pooledTx announcement: %v", msg) + case *Error: + // Transaction should not be announced -> wait for timeout + return + default: + t.Fatalf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) + } +} + +func unknownTx(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, txNew) +} + +func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction { + // Get a new transaction + var tx *types.Transaction + for _, blocks := range s.fullChain.blocks[s.chain.Len():] { + txs := blocks.Transactions() + if txs.Len() != 0 { + tx = txs[0] + break + } + } + if tx == nil { + t.Fatal("could not find transaction") + } + return tx +} + +func getOldTxFromChain(t *utesting.T, s *Suite) *types.Transaction { + var tx *types.Transaction + for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] { + txs := blocks.Transactions() + if txs.Len() != 0 { + tx = txs[0] + break + } + } + if tx == nil { + t.Fatal("could not find transaction") + } + return tx +} + +func invalidNonceTx(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, txNew) +} + +func hugeAmount(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + amount := largeNumber(2) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, txNew) +} + +func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + gasPrice := largeNumber(2) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data()) + return signWithFaucet(t, txNew) +} + +func hugeData(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2)) + return signWithFaucet(t, txNew) +} + +func signWithFaucet(t *utesting.T, tx *types.Transaction) *types.Transaction { + signer := types.HomesteadSigner{} + signedTx, err := types.SignTx(tx, signer, faucetKey) + if err != nil { + t.Fatalf("could not sign tx: %v\n", err) + } + return signedTx +} diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index a20e88c372..f768d61d5a 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -100,13 +100,9 @@ type NewBlockHashes []struct { func (nbh NewBlockHashes) Code() int { return 17 } -// NewBlock is the network packet for the block propagation message. -type NewBlock struct { - Block *types.Block - TD *big.Int -} +type Transactions []*types.Transaction -func (nb NewBlock) Code() int { return 23 } +func (t Transactions) Code() int { return 18 } // GetBlockHeaders represents a block header query. type GetBlockHeaders struct { @@ -122,6 +118,29 @@ type BlockHeaders []*types.Header func (bh BlockHeaders) Code() int { return 20 } +// GetBlockBodies represents a GetBlockBodies request +type GetBlockBodies []common.Hash + +func (gbb GetBlockBodies) Code() int { return 21 } + +// BlockBodies is the network packet for block content distribution. +type BlockBodies []*types.Body + +func (bb BlockBodies) Code() int { return 22 } + +// NewBlock is the network packet for the block propagation message. +type NewBlock struct { + Block *types.Block + TD *big.Int +} + +func (nb NewBlock) Code() int { return 23 } + +// NewPooledTransactionHashes is the network packet for the tx hash propagation message. +type NewPooledTransactionHashes [][32]byte + +func (nb NewPooledTransactionHashes) Code() int { return 24 } + // HashOrNumber is a combined field for specifying an origin block. type hashOrNumber struct { Hash common.Hash // Block hash from which to retrieve headers (excludes Number) @@ -158,16 +177,6 @@ func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { return err } -// GetBlockBodies represents a GetBlockBodies request -type GetBlockBodies []common.Hash - -func (gbb GetBlockBodies) Code() int { return 21 } - -// BlockBodies is the network packet for block content distribution. -type BlockBodies []*types.Body - -func (bb BlockBodies) Code() int { return 22 } - // Conn represents an individual connection with a peer type Conn struct { *rlpx.Conn @@ -205,6 +214,10 @@ func (c *Conn) Read() Message { msg = new(NewBlock) case (NewBlockHashes{}).Code(): msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) default: return errorf("invalid message code: %d", code) } From e7db1dbc96fb366c13e05ee9b3b0a57ba26ca49b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 30 Nov 2020 18:58:47 +0100 Subject: [PATCH 167/948] p2p/nodestate: fix deadlock during shutdown of les server (#21927) This PR fixes a deadlock reported here: #21925 The cause is that many operations may be pending, but if the close happens, only one of them gets awoken and exits, the others remain waiting for a signal that never comes. --- p2p/nodestate/nodestate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index ab28b47a15..def93bac43 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -725,7 +725,7 @@ func (ns *NodeStateMachine) opFinish() { } ns.opPending = nil ns.opFlag = false - ns.opWait.Signal() + ns.opWait.Broadcast() } // Operation calls the given function as an operation callback. This allows the caller From a2795c8055988ce231ff4abf1731514c75c4be5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 1 Dec 2020 10:03:41 +0100 Subject: [PATCH 168/948] les: fix nodiscover option (#21906) --- cmd/utils/flags.go | 4 +++- eth/backend.go | 2 +- eth/discovery.go | 5 ++--- les/client.go | 2 +- les/enr_entry.go | 5 ++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 56880768f9..051bdd6308 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1584,7 +1584,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) { cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name) } - if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { + if ctx.GlobalIsSet(NoDiscoverFlag.Name) { + cfg.DiscoveryURLs = []string{} + } else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) if urls == "" { cfg.DiscoveryURLs = []string{} diff --git a/eth/backend.go b/eth/backend.go index 01e6cadd1f..03b0b319b7 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -208,7 +208,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery(&stack.Config().P2P) + eth.dialCandidates, err = eth.setupDiscovery() if err != nil { return nil, err } diff --git a/eth/discovery.go b/eth/discovery.go index 48f6159017..e7a281d356 100644 --- a/eth/discovery.go +++ b/eth/discovery.go @@ -19,7 +19,6 @@ package eth import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -65,8 +64,8 @@ func (eth *Ethereum) currentEthEntry() *ethEntry { } // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *Ethereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { - if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 { +func (eth *Ethereum) setupDiscovery() (enode.Iterator, error) { + if len(eth.config.DiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) diff --git a/les/client.go b/les/client.go index a2f7c56dfd..37250d076f 100644 --- a/les/client.go +++ b/les/client.go @@ -112,7 +112,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery(&stack.Config().P2P) + dnsdisc, err := leth.setupDiscovery() if err != nil { return nil, err } diff --git a/les/enr_entry.go b/les/enr_entry.go index 11e6273be5..8f0169bee1 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -17,7 +17,6 @@ package les import ( - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -35,8 +34,8 @@ func (e lesEntry) ENRKey() string { } // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { - if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 { +func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { + if len(eth.config.DiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) From 908c18073a4df12866c84c4ff5f63430e28a62d0 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 2 Dec 2020 16:17:59 +0800 Subject: [PATCH 169/948] params: update CHTs (#21941) --- params/config.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/params/config.go b/params/config.go index ade81408a8..588d848f1c 100644 --- a/params/config.go +++ b/params/config.go @@ -74,10 +74,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 336, - SectionHead: common.HexToHash("0xd42b78902b6527a80337bf1bc372a3ccc3db97e9cc7cf421ca047ae9076c716b"), - CHTRoot: common.HexToHash("0xd97f3b30f7e0cb958e4c67c53ec27745e5a165e33e56821b86523dfee62b783a"), - BloomRoot: common.HexToHash("0xf3cbfd070fababfe2adc9b23fc02c731f6ca2cce6646b3ede4ef2db06092ccce"), + SectionIndex: 345, + SectionHead: common.HexToHash("0x5453bab878704adebc934b41fd214a07ea7a72b8572ff088dca7f7956cd0ef28"), + CHTRoot: common.HexToHash("0x7693d432595846c094f47cb37f5c868b0b7b1968fc6b0fc411ded1345fdaffab"), + BloomRoot: common.HexToHash("0x8b0e7895bc39840d8dac857e26bdf3d0a07684b0b962b252546659e0337a9f70"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -113,10 +113,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 269, - SectionHead: common.HexToHash("0x290a9eb65e65c64601d1b05522533ed502098a246736b348502a170818a33d64"), - CHTRoot: common.HexToHash("0x530ebac02264227277d0a16b0819ef96a2011a6e1e66523ebff8040f4a3437ca"), - BloomRoot: common.HexToHash("0x480cd5b3198a0767022902130546854a2e8867cce573c1cf0ce54e67a7bf5efb"), + SectionIndex: 279, + SectionHead: common.HexToHash("0x4a4912848d4c06090097073357c10015d11c6f4544a0f93cbdd584701c3b7d58"), + CHTRoot: common.HexToHash("0x9053b7867ae921e80a4e2f5a4b15212e4af3d691ca712fb33dc150e9c6ea221c"), + BloomRoot: common.HexToHash("0x3dc04cb1be7ddc271f3f83469b47b76184a79d7209ef51d85b1539ea6d25a645"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -155,10 +155,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 223, - SectionHead: common.HexToHash("0x03ca0d5e3a931c77cd7a97bbaa2d9e4edc4549c621dc1d223a29f10c86a4a16a"), - CHTRoot: common.HexToHash("0x6573dbdd91b2958b446bd04d67c23e5f14b2510ac96e8df1b6a894dc49e37c6c"), - BloomRoot: common.HexToHash("0x28a35042a4e88efbac55fe566faf7fce000dc436f17fd4cb4b081c9cd793e1a7"), + SectionIndex: 232, + SectionHead: common.HexToHash("0x8170fca4039b11a008c11f9996ff112151cbb17411437bb2f86288e11158b2f0"), + CHTRoot: common.HexToHash("0x4526560d92ae1b3a6d3ee780c3ad289ba2bbf1b5da58d9ea107f2f26412b631f"), + BloomRoot: common.HexToHash("0x82a889098a35d6a21ea8894d35a1db69b94bad61b988bbe5ae4601437320e331"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -195,10 +195,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 107, - SectionHead: common.HexToHash("0xff3ae39199fa191894de419e7f673c8627aa8cc7af924b90f36635b6add375f2"), - CHTRoot: common.HexToHash("0x27d59d60c652425b6b593a882f55a4ff57f24e470a810a6e3c8ba71833a20220"), - BloomRoot: common.HexToHash("0x3c14066d8bb3733780c06b8165768dbb9dd23b75f56012fe5f2fb3c2fb70cadb"), + SectionIndex: 116, + SectionHead: common.HexToHash("0xf2d200f636f213c9c7bb4e747ff564813da7708253037103aef3d8be5203c5e1"), + CHTRoot: common.HexToHash("0xb0ac83e2ccf6c2776945e099c4e3df50fe6200499c8b2045c34cafdf57d15087"), + BloomRoot: common.HexToHash("0xfb580ad1c611230a4bfc56534f58bcb156d028bc6ce70e35403dc019c7c02d90"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From e9e86aeacbfc810016c2451e8b21616a3ea0b927 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 2 Dec 2020 12:49:20 +0100 Subject: [PATCH 170/948] eth: fix error in tracing if reexec is set (#21830) * eth: fix error in tracing if reexec is set * eth: change pointer embedding to value-embedding --- eth/api_tracer.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 804d26b0b9..2497c8d95e 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -65,7 +65,7 @@ type TraceConfig struct { // StdTraceConfig holds extra parameters to standard-json trace functions. type StdTraceConfig struct { - *vm.LogConfig + vm.LogConfig Reexec *uint64 TxHash common.Hash } @@ -549,9 +549,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block txHash common.Hash ) if config != nil { - if config.LogConfig != nil { - logConfig = *config.LogConfig - } + logConfig = config.LogConfig txHash = config.TxHash } logConfig.Debug = true @@ -576,7 +574,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block chainConfigCopy := new(params.ChainConfig) *chainConfigCopy = *chainConfig chainConfig = chainConfigCopy - if yolov2 := config.Overrides.YoloV2Block; yolov2 != nil { + if yolov2 := config.LogConfig.Overrides.YoloV2Block; yolov2 != nil { chainConfig.YoloV2Block = yolov2 canon = false } From 0b2f1446bbece9bb4e7abef0bf8de1a267aba654 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 2 Dec 2020 16:42:38 +0100 Subject: [PATCH 171/948] go.mod: update github.com/golang/snappy(#21934) This updates the snappy library depency to include a fix for a Go 1.16 incompatibility issue. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9a35f6447f..0f47809ef6 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.4.2 - github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 + github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 diff --git a/go.sum b/go.sum index dedae7bc7a..d1bd608585 100644 --- a/go.sum +++ b/go.sum @@ -97,6 +97,8 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= +github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= From d7a64dc02b788b9a77904108b49d19a7b4a1a224 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 3 Dec 2020 13:16:20 +0100 Subject: [PATCH 172/948] cmd/devp2p: add node filter for snap + fix arg error (#21950) --- cmd/devp2p/nodesetcmd.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index 228d3319e3..ba97405abc 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -95,6 +95,7 @@ var filterFlags = map[string]nodeFilterC{ "-min-age": {1, minAgeFilter}, "-eth-network": {1, ethFilter}, "-les-server": {0, lesFilter}, + "-snap": {0, snapFilter}, } func parseFilters(args []string) ([]nodeFilter, error) { @@ -104,15 +105,15 @@ func parseFilters(args []string) ([]nodeFilter, error) { if !ok { return nil, fmt.Errorf("invalid filter %q", args[0]) } - if len(args) < fc.narg { - return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)) + if len(args)-1 < fc.narg { + return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)-1) } - filter, err := fc.fn(args[1:]) + filter, err := fc.fn(args[1 : 1+fc.narg]) if err != nil { return nil, fmt.Errorf("%s: %v", args[0], err) } filters = append(filters, filter) - args = args[fc.narg+1:] + args = args[1+fc.narg:] } return filters, nil } @@ -191,3 +192,13 @@ func lesFilter(args []string) (nodeFilter, error) { } return f, nil } + +func snapFilter(args []string) (nodeFilter, error) { + f := func(n nodeJSON) bool { + var snap struct { + _ []rlp.RawValue `rlp:"tail"` + } + return n.N.Load(enr.WithEntry("snap", &snap)) == nil + } + return f, nil +} From 62cedb3aabf5db260b74830285975116ba464e86 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Fri, 4 Dec 2020 09:54:07 +0200 Subject: [PATCH 173/948] core/vm/runtime: remove duplicated line (#21956) This line is duplicated, though it doesn't cause any issues. --- core/vm/runtime/runtime.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 8abd378cef..2586535f9d 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -118,7 +118,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { cfg.State.AddAddressToAccessList(addr) - cfg.State.AddAddressToAccessList(addr) } } cfg.State.CreateAccount(address) From 7770e41cb5fcc386a7d2329d1187174839122f24 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 4 Dec 2020 12:22:19 +0100 Subject: [PATCH 174/948] core: improve contextual information on core errors (#21869) A lot of times when we hit 'core' errors, example: invalid tx, the information provided is insufficient. We miss several pieces of information: what account has nonce too high, and what transaction in that block was offending? This PR adds that information, using the new type of wrapped errors. It also adds a testcase which (partly) verifies the output from the errors. The first commit changes all usage of direct equality-checks on core errors, into using errors.Is. The second commit adds contextual information. This wraps most of the core errors with more information, and also wraps it one more time in stateprocessor, to further provide tx index and tx hash, if such a tx is encoutered in a block. The third commit uses the chainmaker to try to generate chains with such errors in them, thus triggering the errors and checking that the generated string meets expectations. --- accounts/abi/bind/backends/simulated.go | 2 +- core/blockchain_test.go | 3 +- core/state_processor.go | 4 +- core/state_processor_test.go | 152 ++++++++++++++++++++++++ core/state_transition.go | 21 ++-- core/tx_pool_test.go | 4 +- light/lightchain_test.go | 3 +- miner/worker.go | 10 +- 8 files changed, 179 insertions(+), 20 deletions(-) create mode 100644 core/state_processor_test.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 695d81bf03..4be98adfca 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -479,7 +479,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs b.pendingState.RevertToSnapshot(snapshot) if err != nil { - if err == core.ErrIntrinsicGas { + if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } return true, nil, err // Bail out diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 7ec62b11dd..d60a235981 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "io/ioutil" "math/big" @@ -468,7 +469,7 @@ func testBadHashes(t *testing.T, full bool) { _, err = blockchain.InsertHeaderChain(headers, 1) } - if err != ErrBlacklistedHash { + if !errors.Is(err, ErrBlacklistedHash) { t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash) } } diff --git a/core/state_processor.go b/core/state_processor.go index d992dfb9cb..cdc86a694e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,6 +17,8 @@ package core import ( + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -76,7 +78,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg statedb.Prepare(tx.Hash(), block.Hash(), i) receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) if err != nil { - return nil, nil, 0, err + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) diff --git a/core/state_processor_test.go b/core/state_processor_test.go new file mode 100644 index 0000000000..6e63975ac1 --- /dev/null +++ b/core/state_processor_test.go @@ -0,0 +1,152 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +// TestStateProcessorErrors tests the output from the 'core' errors +// as defined in core/error.go. These errors are generated when the +// blockchain imports bad blocks, meaning blocks which have valid headers but +// contain invalid transactions +func TestStateProcessorErrors(t *testing.T) { + var ( + signer = types.HomesteadSigner{} + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: params.TestChainConfig, + } + genesis = gspec.MustCommit(db) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey) + return tx + } + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + }, + want: "could not apply tx 1 [0x36bfa6d14f1cd35a1be8cc2322982a595fabc0e799f09c1de3bad7bd5b1f7626]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", + }, + { + txs: []*types.Transaction{ + makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + }, + want: "could not apply tx 0 [0x51cd272d41ef6011d8138e18bf4043797aca9b713c7d39a97563f9bbe6bdbe6f]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), 21000000, nil, nil), + }, + want: "could not apply tx 0 [0x54c58b530824b0bb84b7a98183f08913b5d74e1cebc368515ef3c65edf8eb56a]: gas limit reached", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(1), params.TxGas, nil, nil), + }, + want: "could not apply tx 0 [0x3094b17498940d92b13baccf356ce8bfd6f221e926abc903d642fa1466c5b50e]: insufficient funds for transfer: address 0x71562b71999873DB5b286dF957af199Ec94617F7", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(0xffffff), nil), + }, + want: "could not apply tx 0 [0xaa3f7d86802b1f364576d9071bf231e31d61b392d306831ac9cf706ff5371ce0]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 0 want 352321515000", + }, + { + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(1, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(2, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + makeTx(3, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(0), nil), + }, + want: "could not apply tx 3 [0x836fab5882205362680e49b311a20646de03b630920f18ec6ee3b111a2cf6835]: intrinsic gas too low: have 20000, want 21000", + }, + // The last 'core' error is ErrGasUintOverflow: "gas uint64 overflow", but in order to + // trigger that one, we'd have to allocate a _huge_ chunk of data, such that the + // multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment + } { + block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } + } +} + +// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be +// valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently +// valid to be considered for import: +// - valid pow (fake), ancestry, difficulty, gaslimit etc +func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions) *types.Block { + header := &types.Header{ + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: engine.CalcDifficulty(&fakeChainReader{params.TestChainConfig}, parent.Time()+10, &types.Header{ + Number: parent.Number(), + Time: parent.Time(), + Difficulty: parent.Difficulty(), + UncleHash: parent.UncleHash(), + }), + GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: parent.Time() + 10, + UncleHash: types.EmptyUncleHash, + } + var receipts []*types.Receipt + + // The post-state result doesn't need to be correct (this is a bad block), but we do need something there + // Preferably something unique. So let's use a combo of blocknum + txhash + hasher := sha3.NewLegacyKeccak256() + hasher.Write(header.Number.Bytes()) + var cumulativeGas uint64 + for _, tx := range txs { + txh := tx.Hash() + hasher.Write(txh[:]) + receipt := types.NewReceipt(nil, false, cumulativeGas+tx.Gas()) + receipt.TxHash = tx.Hash() + receipt.GasUsed = tx.Gas() + receipts = append(receipts, receipt) + cumulativeGas += tx.Gas() + } + header.Root = common.BytesToHash(hasher.Sum(nil)) + // Assemble and return the final block for sealing + return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)) +} diff --git a/core/state_transition.go b/core/state_transition.go index 72e8b02a1c..a8d1936c1f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "fmt" "math" "math/big" @@ -174,8 +175,8 @@ func (st *StateTransition) to() common.Address { func (st *StateTransition) buyGas() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { - return ErrInsufficientFunds + if have, want := st.state.GetBalance(st.msg.From()), mgval; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want) } if err := st.gp.SubGas(st.msg.Gas()); err != nil { return err @@ -190,11 +191,13 @@ func (st *StateTransition) buyGas() error { func (st *StateTransition) preCheck() error { // Make sure this transaction's nonce is correct. if st.msg.CheckNonce() { - nonce := st.state.GetNonce(st.msg.From()) - if nonce < st.msg.Nonce() { - return ErrNonceTooHigh - } else if nonce > st.msg.Nonce() { - return ErrNonceTooLow + stNonce := st.state.GetNonce(st.msg.From()) + if msgNonce := st.msg.Nonce(); stNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, + st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, + st.msg.From().Hex(), msgNonce, stNonce) } } return st.buyGas() @@ -240,13 +243,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, err } if st.gas < gas { - return nil, ErrIntrinsicGas + return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } st.gas -= gas // Check clause 6 if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { - return nil, ErrInsufficientFundsForTransfer + return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) } var ( ret []byte diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index dbc49d4f9c..246b3977d3 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -242,7 +242,7 @@ func TestInvalidTransactions(t *testing.T) { from, _ := deriveSender(tx) pool.currentState.AddBalance(from, big.NewInt(1)) - if err := pool.AddRemote(tx); err != ErrInsufficientFunds { + if err := pool.AddRemote(tx); !errors.Is(err, ErrInsufficientFunds) { t.Error("expected", ErrInsufficientFunds) } @@ -255,7 +255,7 @@ func TestInvalidTransactions(t *testing.T) { pool.currentState.SetNonce(from, 1) pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff)) tx = transaction(0, 100000, key) - if err := pool.AddRemote(tx); err != ErrNonceTooLow { + if err := pool.AddRemote(tx); !errors.Is(err, ErrNonceTooLow) { t.Error("expected", ErrNonceTooLow) } diff --git a/light/lightchain_test.go b/light/lightchain_test.go index 70d2e70c18..2aed08d74e 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -18,6 +18,7 @@ package light import ( "context" + "errors" "math/big" "testing" @@ -321,7 +322,7 @@ func TestBadHeaderHashes(t *testing.T) { var err error headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10) core.BadHashes[headers[2].Hash()] = true - if _, err = bc.InsertHeaderChain(headers, 1); err != core.ErrBlacklistedHash { + if _, err = bc.InsertHeaderChain(headers, 1); !errors.Is(err, core.ErrBlacklistedHash) { t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBlacklistedHash) } } diff --git a/miner/worker.go b/miner/worker.go index 16f4c1c313..d5813c18a8 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -793,23 +793,23 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount) logs, err := w.commitTransaction(tx, coinbase) - switch err { - case core.ErrGasLimitReached: + switch { + case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account log.Trace("Gas limit exceeded for current block", "sender", from) txs.Pop() - case core.ErrNonceTooLow: + case errors.Is(err, core.ErrNonceTooLow): // New head notification data race between the transaction pool and miner, shift log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) txs.Shift() - case core.ErrNonceTooHigh: + case errors.Is(err, core.ErrNonceTooHigh): // Reorg notification data race between the transaction pool and miner, skip account = log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() - case nil: + case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) w.current.tcount++ From 15339cf1c9af2b1242c2574869fa7afca1096cdf Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 4 Dec 2020 15:01:47 +0100 Subject: [PATCH 175/948] cmd/geth: implement vulnerability check (#21859) * cmd/geth: implement vulnerability check * cmd/geth: use minisign to verify vulnerability feed * cmd/geth: add the test too * cmd/geth: more minisig/signify testing * cmd/geth: support multiple pubfiles for signing * cmd/geth: add @holiman minisig pubkey * cmd/geth: polishes on vulnerability check * cmd/geth: fix ineffassign linter nit * cmd/geth: add CVE to version check struct * cmd/geth/testdata: add missing testfile * cmd/geth: add more keys to versionchecker * cmd/geth: support file:// URLs in version check * cmd/geth: improve key ID printing when signature check fails Co-authored-by: Felix Lange --- cmd/geth/main.go | 1 + cmd/geth/misccmd.go | 27 +++ cmd/geth/testdata/vcheck/data.json | 61 +++++++ cmd/geth/testdata/vcheck/data2.json | 62 +++++++ .../vulnerabilities.json.minisig.1 | 4 + .../vulnerabilities.json.minisig.2 | 4 + .../vulnerabilities.json.minisig.3 | 4 + cmd/geth/testdata/vcheck/minisign.pub | 2 + cmd/geth/testdata/vcheck/minisign.sec | 2 + .../vcheck/signify-sigs/data.json.sig | 2 + cmd/geth/testdata/vcheck/signifykey.pub | 2 + cmd/geth/testdata/vcheck/signifykey.sec | 2 + .../sigs/vulnerabilities.json.minisig.1 | 4 + .../sigs/vulnerabilities.json.minisig.2 | 4 + .../sigs/vulnerabilities.json.minisig.3 | 4 + cmd/geth/version_check.go | 169 ++++++++++++++++++ cmd/geth/version_check_test.go | 79 ++++++++ 17 files changed, 433 insertions(+) create mode 100644 cmd/geth/testdata/vcheck/data.json create mode 100644 cmd/geth/testdata/vcheck/data2.json create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 create mode 100644 cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 create mode 100644 cmd/geth/testdata/vcheck/minisign.pub create mode 100644 cmd/geth/testdata/vcheck/minisign.sec create mode 100644 cmd/geth/testdata/vcheck/signify-sigs/data.json.sig create mode 100644 cmd/geth/testdata/vcheck/signifykey.pub create mode 100644 cmd/geth/testdata/vcheck/signifykey.sec create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 create mode 100644 cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 create mode 100644 cmd/geth/version_check.go create mode 100644 cmd/geth/version_check_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0f50989208..e587a5f86d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -242,6 +242,7 @@ func init() { makecacheCommand, makedagCommand, versionCommand, + versionCheckCommand, licenseCommand, // See config.go dumpConfigCommand, diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 0e7ee96513..967df2ada0 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -31,6 +31,18 @@ import ( ) var ( + VersionCheckUrlFlag = cli.StringFlag{ + Name: "check.url", + Usage: "URL to use when checking vulnerabilities", + Value: "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json", + } + VersionCheckVersionFlag = cli.StringFlag{ + Name: "check.version", + Usage: "Version to check", + Value: fmt.Sprintf("Geth/v%v/%v-%v/%v", + params.VersionWithCommit(gitCommit, gitDate), + runtime.GOOS, runtime.GOARCH, runtime.Version()), + } makecacheCommand = cli.Command{ Action: utils.MigrateFlags(makecache), Name: "makecache", @@ -65,6 +77,21 @@ Regular users do not need to execute it. Category: "MISCELLANEOUS COMMANDS", Description: ` The output of this command is supposed to be machine-readable. +`, + } + versionCheckCommand = cli.Command{ + Action: utils.MigrateFlags(versionCheck), + Flags: []cli.Flag{ + VersionCheckUrlFlag, + VersionCheckVersionFlag, + }, + Name: "version-check", + Usage: "Checks (online) whether the current version suffers from any known security vulnerabilities", + ArgsUsage: "", + Category: "MISCELLANEOUS COMMANDS", + Description: ` +The version-check command fetches vulnerability-information from https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json, +and displays information about any security vulnerabilities that affect the currently executing version. `, } licenseCommand = cli.Command{ diff --git a/cmd/geth/testdata/vcheck/data.json b/cmd/geth/testdata/vcheck/data.json new file mode 100644 index 0000000000..e7ee2bf7e4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/data.json @@ -0,0 +1,61 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*" + }, + { + "name": "GoCrash", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552" + ], + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] diff --git a/cmd/geth/testdata/vcheck/data2.json b/cmd/geth/testdata/vcheck/data2.json new file mode 100644 index 0000000000..75a31677a1 --- /dev/null +++ b/cmd/geth/testdata/vcheck/data2.json @@ -0,0 +1,62 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*", + "CVE": "correct" + }, + { + "name": "GoCrash", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552" + ], + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000000..f9066d4fe0 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000000..a89a83d21a --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000000..6fd33b19a3 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisign.pub b/cmd/geth/testdata/vcheck/minisign.pub new file mode 100644 index 0000000000..183dce5f6b --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.pub @@ -0,0 +1,2 @@ +untrusted comment: minisign public key 284E00B52C269624 +RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp diff --git a/cmd/geth/testdata/vcheck/minisign.sec b/cmd/geth/testdata/vcheck/minisign.sec new file mode 100644 index 0000000000..5c50715b20 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.sec @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0Iyz8kmPMKrqk6DCtlO9a33akKiaOQG1aLolqDxs52qvPoAAAACAAAAAAAAAEAAAAAArEiggdvyn6+WzTprirLtgiYQoU+ihz/HyGgjhuF+Pz2ddMduyCO+xjCHeq+vgVVW039fbsI8hW6LRGJZLBKV5/jdxCXAVVQE7qTQ6xpEdO0z8Z731/pV1hlspQXG2PNd16NMtwd9dWw= diff --git a/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig new file mode 100644 index 0000000000..3d5fcacf9a --- /dev/null +++ b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig @@ -0,0 +1,2 @@ +untrusted comment: verify with ./signifykey.pub +RWSKLNhZb0KdAbhRUhW2LQZXdnwttu2SYhM9EuC4mMgOJB85h7/YIPupf8/ldTs4N8e9Y/fhgdY40q5LQpt5IFC62fq0v8U1/w8= diff --git a/cmd/geth/testdata/vcheck/signifykey.pub b/cmd/geth/testdata/vcheck/signifykey.pub new file mode 100644 index 0000000000..328f973ab4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/ diff --git a/cmd/geth/testdata/vcheck/signifykey.sec b/cmd/geth/testdata/vcheck/signifykey.sec new file mode 100644 index 0000000000..3279a2e58b --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.sec @@ -0,0 +1,2 @@ +untrusted comment: signify secret key +RWRCSwAAACpLQDLawSQCtI7eAVIvaiHzjTsTyJsfV5aKLNhZb0KdAWeICXJGa93/bHAcsY6jUh9I8RdEcDWEoGxmaXZC+IdVBPxDpkix9fBRGEUdKWHi3dOfqME0YRzErWI5AVg3cRw= diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000000..f9066d4fe0 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000000..a89a83d21a --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000000..6fd33b19a3 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/version_check.go b/cmd/geth/version_check.go new file mode 100644 index 0000000000..2101a69e98 --- /dev/null +++ b/cmd/geth/version_check.go @@ -0,0 +1,169 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/log" + "github.com/jedisct1/go-minisign" + "gopkg.in/urfave/cli.v1" +) + +var gethPubKeys []string = []string{ + //@holiman, minisign public key FB1D084D39BAEC24 + "RWQk7Lo5TQgd+wxBNZM+Zoy+7UhhMHaWKzqoes9tvSbFLJYZhNTbrIjx", + //minisign public key 138B1CA303E51687 + "RWSHFuUDoxyLEzjszuWZI1xStS66QTyXFFZG18uDfO26CuCsbckX1e9J", + //minisign public key FD9813B2D2098484 + "RWSEhAnSshOY/b+GmaiDkObbCWefsAoavjoLcPjBo1xn71yuOH5I+Lts", +} + +type vulnJson struct { + Name string + Uid string + Summary string + Description string + Links []string + Introduced string + Fixed string + Published string + Severity string + Check string + CVE string +} + +func versionCheck(ctx *cli.Context) error { + url := ctx.String(VersionCheckUrlFlag.Name) + version := ctx.String(VersionCheckVersionFlag.Name) + log.Info("Checking vulnerabilities", "version", version, "url", url) + return checkCurrent(url, version) +} + +func checkCurrent(url, current string) error { + var ( + data []byte + sig []byte + err error + ) + if data, err = fetch(url); err != nil { + return fmt.Errorf("could not retrieve data: %w", err) + } + if sig, err = fetch(fmt.Sprintf("%v.minisig", url)); err != nil { + return fmt.Errorf("could not retrieve signature: %w", err) + } + if err = verifySignature(gethPubKeys, data, sig); err != nil { + return err + } + var vulns []vulnJson + if err = json.Unmarshal(data, &vulns); err != nil { + return err + } + allOk := true + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + if err != nil { + return err + } + if r.MatchString(current) { + allOk = false + fmt.Printf("## Vulnerable to %v (%v)\n\n", vuln.Uid, vuln.Name) + fmt.Printf("Severity: %v\n", vuln.Severity) + fmt.Printf("Summary : %v\n", vuln.Summary) + fmt.Printf("Fixed in: %v\n", vuln.Fixed) + if len(vuln.CVE) > 0 { + fmt.Printf("CVE: %v\n", vuln.CVE) + } + if len(vuln.Links) > 0 { + fmt.Printf("References:\n") + for _, ref := range vuln.Links { + fmt.Printf("\t- %v\n", ref) + } + } + fmt.Println() + } + } + if allOk { + fmt.Println("No vulnerabilities found") + } + return nil +} + +// fetch makes an HTTP request to the given url and returns the response body +func fetch(url string) ([]byte, error) { + if filep := strings.TrimPrefix(url, "file://"); filep != url { + return ioutil.ReadFile(filep) + } + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return body, nil +} + +// verifySignature checks that the sigData is a valid signature of the given +// data, for pubkey GethPubkey +func verifySignature(pubkeys []string, data, sigdata []byte) error { + sig, err := minisign.DecodeSignature(string(sigdata)) + if err != nil { + return err + } + // find the used key + var key *minisign.PublicKey + for _, pubkey := range pubkeys { + pub, err := minisign.NewPublicKey(pubkey) + if err != nil { + // our pubkeys should be parseable + return err + } + if pub.KeyId != sig.KeyId { + continue + } + key = &pub + break + } + if key == nil { + log.Info("Signing key not trusted", "keyid", keyID(sig.KeyId), "error", err) + return errors.New("signature could not be verified") + } + if ok, err := key.Verify(data, sig); !ok || err != nil { + log.Info("Verification failed error", "keyid", keyID(key.KeyId), "error", err) + return errors.New("signature could not be verified") + } + return nil +} + +// keyID turns a binary minisign key ID into a hex string. +// Note: key IDs are printed in reverse byte order. +func keyID(id [8]byte) string { + var rev [8]byte + for i := range id { + rev[len(rev)-1-i] = id[i] + } + return fmt.Sprintf("%X", rev) +} diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go new file mode 100644 index 0000000000..4afe8ddc83 --- /dev/null +++ b/cmd/geth/version_check_test.go @@ -0,0 +1,79 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + "testing" +) + +func TestVerification(t *testing.T) { + // Signatures generated with `minisign` + t.Run("minisig", func(t *testing.T) { + // For this test, the pubkey is in testdata/minisign.pub + // (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' ) + pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp" + testVerification(t, pub, "./testdata/vcheck/minisig-sigs/") + }) + // Signatures generated with `signify-openbsd` + t.Run("signify-openbsd", func(t *testing.T) { + t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2") + // For this test, the pubkey is in testdata/signifykey.pub + // (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' ) + pub := "RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/" + testVerification(t, pub, "./testdata/vcheck/signify-sigs/") + }) +} + +func testVerification(t *testing.T, pubkey, sigdir string) { + // Data to verify + data, err := ioutil.ReadFile("./testdata/vcheck/data.json") + if err != nil { + t.Fatal(err) + } + // Signatures, with and without comments, both trusted and untrusted + files, err := ioutil.ReadDir(sigdir) + if err != nil { + t.Fatal(err) + } + for _, f := range files { + sig, err := ioutil.ReadFile(filepath.Join(sigdir, f.Name())) + if err != nil { + t.Fatal(err) + } + err = verifySignature([]string{pubkey}, data, sig) + if err != nil { + t.Fatal(err) + } + } +} + +func TestJson(t *testing.T) { + data, _ := ioutil.ReadFile("./testdata/vcheck/data2.json") + var vulns []vulnJson + if err := json.Unmarshal(data, &vulns); err != nil { + t.Fatal(err) + } + if len(vulns) == 0 { + t.Fatal("expected data, got none") + } + if have, want := vulns[0].CVE, "correct"; have != want { + t.Errorf("have %v, want %v", have, want) + } +} From 581c028d181cc4582c8435ced5c0b104ac63346f Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 7 Dec 2020 13:04:27 +0000 Subject: [PATCH 176/948] les: cosmetic rewrite of the arm64 float bug workaround (#21960) * les: revert arm float bug workaround to check go 1.15 * add traces to reproduce outside travis * simpler workaround --- les/utils/expiredvalue.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 55e82cee48..1a2b3d995e 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -88,8 +88,9 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { if base >= 0 || uint64(-base) <= e.Base { // This is a temporary fix to circumvent a golang // uint conversion issue on arm64, which needs to - // be investigated further. FIXME - e.Base = uint64(int64(e.Base) + int64(base)) + // be investigated further. More details at: + // https://github.com/golang/go/issues/43047 + e.Base += uint64(int64(base)) return amount } net := int64(-float64(e.Base) / factor) From 6a4e730003d4adec28fa14baa78020c8c8b53887 Mon Sep 17 00:00:00 2001 From: Steve Ruckdashel Date: Tue, 8 Dec 2020 03:47:56 -0600 Subject: [PATCH 177/948] crypto/secp256k1: add workaround for go mod vendor (#21735) Go won't vendor C files if there are no Go files present in the directory. Workaround is to add dummy Go files. Fixes: #20232 --- crypto/secp256k1/dummy.go | 20 +++++++++++++++++++ .../secp256k1/libsecp256k1/contrib/dummy.go | 7 +++++++ crypto/secp256k1/libsecp256k1/dummy.go | 7 +++++++ .../secp256k1/libsecp256k1/include/dummy.go | 7 +++++++ crypto/secp256k1/libsecp256k1/src/dummy.go | 7 +++++++ .../libsecp256k1/src/modules/dummy.go | 7 +++++++ .../libsecp256k1/src/modules/ecdh/dummy.go | 7 +++++++ .../src/modules/recovery/dummy.go | 7 +++++++ 8 files changed, 69 insertions(+) create mode 100644 crypto/secp256k1/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/contrib/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/include/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go create mode 100644 crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go diff --git a/crypto/secp256k1/dummy.go b/crypto/secp256k1/dummy.go new file mode 100644 index 0000000000..c0f2ee52c0 --- /dev/null +++ b/crypto/secp256k1/dummy.go @@ -0,0 +1,20 @@ +// +build dummy + +// This file is part of a workaround for `go mod vendor` which won't vendor +// C files if there's no Go file in the same directory. +// This would prevent the crypto/secp256k1/libsecp256k1/include/secp256k1.h file to be vendored. +// +// This Go file imports the c directory where there is another dummy.go file which +// is the second part of this workaround. +// +// These two files combined make it so `go mod vendor` behaves correctly. +// +// See this issue for reference: https://github.com/golang/go/issues/26366 + +package secp256k1 + +import ( + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/include" + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src" + _ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src/modules/recovery" +) diff --git a/crypto/secp256k1/libsecp256k1/contrib/dummy.go b/crypto/secp256k1/libsecp256k1/contrib/dummy.go new file mode 100644 index 0000000000..fda594be99 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/contrib/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package contrib diff --git a/crypto/secp256k1/libsecp256k1/dummy.go b/crypto/secp256k1/libsecp256k1/dummy.go new file mode 100644 index 0000000000..379b16992f --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package libsecp256k1 diff --git a/crypto/secp256k1/libsecp256k1/include/dummy.go b/crypto/secp256k1/libsecp256k1/include/dummy.go new file mode 100644 index 0000000000..5af540c73c --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/include/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package include diff --git a/crypto/secp256k1/libsecp256k1/src/dummy.go b/crypto/secp256k1/libsecp256k1/src/dummy.go new file mode 100644 index 0000000000..65868f38a8 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package src diff --git a/crypto/secp256k1/libsecp256k1/src/modules/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go new file mode 100644 index 0000000000..3c7a696439 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package module diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go new file mode 100644 index 0000000000..b6fc38327e --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package ecdh diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go new file mode 100644 index 0000000000..b9491f0cb9 --- /dev/null +++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go @@ -0,0 +1,7 @@ +// +build dummy + +// Package c contains only a C file. +// +// This Go file is part of a workaround for `go mod vendor`. +// Please see the file crypto/secp256k1/dummy.go for more information. +package recovery From ed0670cb17a96aafeb9eaaeb9765a42fb6bb5663 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 8 Dec 2020 14:44:56 +0100 Subject: [PATCH 178/948] accounts/abi/bind: allow specifying signer on transactOpts (#21356) This commit enables users to specify which signer they want to use while creating their transactOpts. Previously all contract interactions used the homestead signer. Now a user can specify whether they want to sign with homestead or EIP155 and specify the chainID which adds another layer of security. Closes #16484 --- accounts/abi/bind/auth.go | 92 ++++++++++++++++++-- accounts/abi/bind/backends/simulated.go | 2 + accounts/abi/bind/backends/simulated_test.go | 12 +-- accounts/abi/bind/base.go | 4 +- accounts/abi/bind/bind_test.go | 34 ++++---- contracts/checkpointoracle/oracle_test.go | 2 +- les/sync_test.go | 6 +- les/test_helper.go | 3 +- mobile/bind.go | 12 ++- 9 files changed, 127 insertions(+), 40 deletions(-) diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index c891b0a3e9..1190772676 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -21,6 +21,7 @@ import ( "errors" "io" "io/ioutil" + "math/big" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" @@ -28,11 +29,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" ) +// ErrNoChainID is returned whenever the user failed to specify a chain id. +var ErrNoChainID = errors.New("no chain id specified") + +// ErrNotAuthorized is returned when an account is not properly unlocked. +var ErrNotAuthorized = errors.New("not authorized to sign this account") + // NewTransactor is a utility method to easily create a transaction signer from // an encrypted json key stream and the associated passphrase. +// +// Deprecated: Use NewTransactorWithChainID instead. func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID") json, err := ioutil.ReadAll(keyin) if err != nil { return nil, err @@ -45,13 +56,17 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { } // NewKeyStoreTransactor is a utility method to easily create a transaction signer from -// a decrypted key from a keystore. +// an decrypted key from a keystore. +// +// Deprecated: Use NewKeyStoreTransactorWithChainID instead. func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { + log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID") + signer := types.HomesteadSigner{} return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) if err != nil { @@ -64,13 +79,17 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account // NewKeyedTransactor is a utility method to easily create a transaction signer // from a single private key. +// +// Deprecated: Use NewKeyedTransactorWithChainID instead. func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID") keyAddr := crypto.PubkeyToAddress(key.PublicKey) + signer := types.HomesteadSigner{} return &TransactOpts{ From: keyAddr, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != keyAddr { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) if err != nil { @@ -81,14 +100,73 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { } } +// NewTransactorWithChainID is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) { + json, err := ioutil.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactorWithChainID(key.PrivateKey, chainID) +} + +// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from +// an decrypted key from a keystore. +func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) { + if chainID == nil { + return nil, ErrNoChainID + } + signer := types.NewEIP155Signer(chainID) + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + +// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) { + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + if chainID == nil { + return nil, ErrNoChainID + } + signer := types.NewEIP155Signer(chainID) + return &TransactOpts{ + From: keyAddr, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + // NewClefTransactor is a utility method to easily create a transaction signer // with a clef backend. func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id }, diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 4be98adfca..6e87e037f0 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -74,6 +74,7 @@ type SimulatedBackend struct { // NewSimulatedBackendWithDatabase creates a new binding backend based on the given database // and uses a simulated blockchain for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc} genesis.MustCommit(database) @@ -91,6 +92,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit) } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index e2597cca01..64ddf8bb2c 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -39,7 +39,7 @@ import ( func TestSimulatedBackend(t *testing.T) { var gasLimit uint64 = 8000029 key, _ := crypto.GenerateKey() // nolint: gosec - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) genAlloc := make(core.GenesisAlloc) genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} @@ -411,7 +411,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000) defer sim.Close() @@ -888,7 +888,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -924,7 +924,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -956,7 +956,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - contractAuth := bind.NewKeyedTransactor(testKey) + contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v", err) @@ -1043,7 +1043,7 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - contractAuth := bind.NewKeyedTransactor(testKey) + contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim) if err != nil { t.Errorf("could not deploy contract: %v", err) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 9e6d898eaf..f5a6fe22fc 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -32,7 +32,7 @@ import ( // SignerFn is a signer function callback when a contract requires a method to // sign the transaction before submission. -type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) +type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { @@ -256,7 +256,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") } - signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + signedTx, err := opts.Signer(opts.From, rawTx) if err != nil { return nil, err } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 8bfbf30b53..1a8a17e45e 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -296,7 +296,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -351,7 +351,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -397,7 +397,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -455,7 +455,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -503,7 +503,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -598,7 +598,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -648,7 +648,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -723,7 +723,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -817,7 +817,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1007,7 +1007,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1142,7 +1142,7 @@ var bindTests = []struct { ` key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1284,7 +1284,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1350,7 +1350,7 @@ var bindTests = []struct { ` // Initialize test accounts key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1444,7 +1444,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, _, err := DeployIdentifierCollision(transactOpts, sim) if err != nil { t.Fatalf("failed to deploy contract: %v", err) @@ -1506,7 +1506,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, c1, err := DeployContractOne(transactOpts, sim) if err != nil { t.Fatal("Failed to deploy contract") @@ -1563,7 +1563,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1632,7 +1632,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 1000000) defer sim.Close() - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, c, err := DeployNewFallbacks(opts, sim) if err != nil { t.Fatalf("Failed to deploy contract: %v", err) diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go index 817954d11a..1218481929 100644 --- a/contracts/checkpointoracle/oracle_test.go +++ b/contracts/checkpointoracle/oracle_test.go @@ -178,7 +178,7 @@ func TestCheckpointRegister(t *testing.T) { contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer contractBackend.Close() - transactOpts := bind.NewKeyedTransactor(accounts[0].key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(accounts[0].key, big.NewInt(1337)) // 3 trusted signers, threshold 2 contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2)) diff --git a/les/sync_test.go b/les/sync_test.go index ffce4d8df2..6924e7b43a 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -80,7 +80,8 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() @@ -162,7 +163,8 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { + auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) + if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { t.Error("register checkpoint failed", err) } server.backend.Commit() diff --git a/les/test_helper.go b/les/test_helper.go index 5a8d64f767..d108a8dace 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -112,7 +112,8 @@ func prepare(n int, backend *backends.SimulatedBackend) { switch i { case 0: // deploy checkpoint contract - registrarAddr, _, _, _ = contract.DeployCheckpointOracle(bind.NewKeyedTransactor(bankKey), backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337)) + registrarAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) // bankUser transfers some ether to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey) diff --git a/mobile/bind.go b/mobile/bind.go index afa97b5382..e32d864aa5 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -40,7 +40,7 @@ type MobileSigner struct { } func (s *MobileSigner) Sign(addr *Address, unsignedTx *Transaction) (signedTx *Transaction, _ error) { - sig, err := s.sign(types.EIP155Signer{}, addr.address, unsignedTx.tx) + sig, err := s.sign(addr.address, unsignedTx.tx) if err != nil { return nil, err } @@ -82,12 +82,16 @@ func NewTransactOpts() *TransactOpts { // NewKeyedTransactOpts is a utility method to easily create a transaction signer // from a single private key. -func NewKeyedTransactOpts(keyJson []byte, passphrase string) (*TransactOpts, error) { +func NewKeyedTransactOpts(keyJson []byte, passphrase string, chainID *big.Int) (*TransactOpts, error) { key, err := keystore.DecryptKey(keyJson, passphrase) if err != nil { return nil, err } - return &TransactOpts{*bind.NewKeyedTransactor(key.PrivateKey)}, nil + auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, chainID) + if err != nil { + return nil, err + } + return &TransactOpts{*auth}, nil } func (opts *TransactOpts) GetFrom() *Address { return &Address{opts.opts.From} } @@ -106,7 +110,7 @@ func (opts *TransactOpts) GetGasLimit() int64 { return int64(opts.opts.GasLimi func (opts *TransactOpts) SetFrom(from *Address) { opts.opts.From = from.address } func (opts *TransactOpts) SetNonce(nonce int64) { opts.opts.Nonce = big.NewInt(nonce) } func (opts *TransactOpts) SetSigner(s Signer) { - opts.opts.Signer = func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + opts.opts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { sig, err := s.Sign(&Address{addr}, &Transaction{tx}) if err != nil { return nil, err From bd848aad7c4e1f7d1eaecd9ea7ee23785090768a Mon Sep 17 00:00:00 2001 From: "Li, Cheng" Date: Tue, 8 Dec 2020 13:19:09 -0500 Subject: [PATCH 179/948] common: improve printing of Hash and Address (#21834) Both Hash and Address have a String method, which returns the value as hex with 0x prefix. They also had a Format method which tried to print the value using printf of []byte. The way Format worked was at odds with String though, leading to a situation where fmt.Sprintf("%v", hash) returned the decimal notation and hash.String() returned a hex string. This commit makes it consistent again. Both types now support the %v, %s, %q format verbs for 0x-prefixed hex output. %x, %X creates unprefixed hex output. %d is also supported and returns the decimal notation "[1 2 3...]". For Address, the case of hex characters in %v, %s, %q output is determined using the EIP-55 checksum. Using %x, %X with Address disables checksumming. Co-authored-by: Felix Lange --- common/types.go | 94 +++++++++++++++++++----- common/types_test.go | 166 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 18 deletions(-) diff --git a/common/types.go b/common/types.go index 94cf622e8c..d920e8b1f1 100644 --- a/common/types.go +++ b/common/types.go @@ -17,6 +17,7 @@ package common import ( + "bytes" "database/sql/driver" "encoding/hex" "encoding/json" @@ -84,10 +85,34 @@ func (h Hash) String() string { return h.Hex() } -// Format implements fmt.Formatter, forcing the byte slice to be formatted as is, -// without going through the stringer interface used for logging. +// Format implements fmt.Formatter. +// Hash supports the %v, %s, %v, %x, %X and %d format verbs. func (h Hash) Format(s fmt.State, c rune) { - fmt.Fprintf(s, "%"+string(c), h[:]) + hexb := make([]byte, 2+len(h)*2) + copy(hexb, "0x") + hex.Encode(hexb[2:], h[:]) + + switch c { + case 'x', 'X': + if !s.Flag('#') { + hexb = hexb[2:] + } + if c == 'X' { + hexb = bytes.ToUpper(hexb) + } + fallthrough + case 'v', 's': + s.Write(hexb) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(hexb) + s.Write(q) + case 'd': + fmt.Fprint(s, ([len(h)]byte)(h)) + default: + fmt.Fprintf(s, "%%!%c(hash=%x)", c, h) + } } // UnmarshalText parses a hash in hex syntax. @@ -208,35 +233,68 @@ func (a Address) Hash() Hash { return BytesToHash(a[:]) } // Hex returns an EIP55-compliant hex string representation of the address. func (a Address) Hex() string { - unchecksummed := hex.EncodeToString(a[:]) + return string(a.checksumHex()) +} + +// String implements fmt.Stringer. +func (a Address) String() string { + return a.Hex() +} + +func (a *Address) checksumHex() []byte { + buf := a.hex() + + // compute checksum sha := sha3.NewLegacyKeccak256() - sha.Write([]byte(unchecksummed)) + sha.Write(buf[2:]) hash := sha.Sum(nil) - - result := []byte(unchecksummed) - for i := 0; i < len(result); i++ { - hashByte := hash[i/2] + for i := 2; i < len(buf); i++ { + hashByte := hash[(i-2)/2] if i%2 == 0 { hashByte = hashByte >> 4 } else { hashByte &= 0xf } - if result[i] > '9' && hashByte > 7 { - result[i] -= 32 + if buf[i] > '9' && hashByte > 7 { + buf[i] -= 32 } } - return "0x" + string(result) + return buf[:] } -// String implements fmt.Stringer. -func (a Address) String() string { - return a.Hex() +func (a Address) hex() []byte { + var buf [len(a)*2 + 2]byte + copy(buf[:2], "0x") + hex.Encode(buf[2:], a[:]) + return buf[:] } -// Format implements fmt.Formatter, forcing the byte slice to be formatted as is, -// without going through the stringer interface used for logging. +// Format implements fmt.Formatter. +// Address supports the %v, %s, %v, %x, %X and %d format verbs. func (a Address) Format(s fmt.State, c rune) { - fmt.Fprintf(s, "%"+string(c), a[:]) + switch c { + case 'v', 's': + s.Write(a.checksumHex()) + case 'q': + q := []byte{'"'} + s.Write(q) + s.Write(a.checksumHex()) + s.Write(q) + case 'x', 'X': + // %x disables the checksum. + hex := a.hex() + if !s.Flag('#') { + hex = hex[2:] + } + if c == 'X' { + hex = bytes.ToUpper(hex) + } + s.Write(hex) + case 'd': + fmt.Fprint(s, ([len(a)]byte)(a)) + default: + fmt.Fprintf(s, "%%!%c(address=%x)", c, a) + } } // SetBytes sets the address to the value of b. diff --git a/common/types_test.go b/common/types_test.go index fffd673c6e..318e985f87 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -17,8 +17,10 @@ package common import ( + "bytes" "database/sql/driver" "encoding/json" + "fmt" "math/big" "reflect" "strings" @@ -371,3 +373,167 @@ func TestAddress_Value(t *testing.T) { }) } } + +func TestAddress_Format(t *testing.T) { + b := []byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + } + var addr Address + addr.SetBytes(b) + + tests := []struct { + name string + out string + want string + }{ + { + name: "println", + out: fmt.Sprintln(addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15\n", + }, + { + name: "print", + out: fmt.Sprint(addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + { + name: "printf-s", + out: func() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s", addr) + return buf.String() + }(), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + { + name: "printf-q", + out: fmt.Sprintf("%q", addr), + want: `"0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15"`, + }, + { + name: "printf-x", + out: fmt.Sprintf("%x", addr), + want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15", + }, + { + name: "printf-X", + out: fmt.Sprintf("%X", addr), + want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15", + }, + { + name: "printf-#x", + out: fmt.Sprintf("%#x", addr), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15", + }, + { + name: "printf-v", + out: fmt.Sprintf("%v", addr), + want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15", + }, + // The original default formatter for byte slice + { + name: "printf-d", + out: fmt.Sprintf("%d", addr), + want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21]", + }, + // Invalid format char. + { + name: "printf-t", + out: fmt.Sprintf("%t", addr), + want: "%!t(address=b26f2b342aab24bcf63ea218c6a9274d30ab9a15)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.out != tt.want { + t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want) + } + }) + } +} + +func TestHash_Format(t *testing.T) { + var hash Hash + hash.SetBytes([]byte{ + 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, + 0x10, 0x00, + }) + + tests := []struct { + name string + out string + want string + }{ + { + name: "println", + out: fmt.Sprintln(hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000\n", + }, + { + name: "print", + out: fmt.Sprint(hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-s", + out: func() string { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s", hash) + return buf.String() + }(), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-q", + out: fmt.Sprintf("%q", hash), + want: `"0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000"`, + }, + { + name: "printf-x", + out: fmt.Sprintf("%x", hash), + want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-X", + out: fmt.Sprintf("%X", hash), + want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000", + }, + { + name: "printf-#x", + out: fmt.Sprintf("%#x", hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + { + name: "printf-#X", + out: fmt.Sprintf("%#X", hash), + want: "0XB26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000", + }, + { + name: "printf-v", + out: fmt.Sprintf("%v", hash), + want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000", + }, + // The original default formatter for byte slice + { + name: "printf-d", + out: fmt.Sprintf("%d", hash), + want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21 162 24 198 169 39 77 48 171 154 21 16 0]", + }, + // Invalid format char. + { + name: "printf-t", + out: fmt.Sprintf("%t", hash), + want: "%!t(hash=b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.out != tt.want { + t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want) + } + }) + } +} From 40b6ccf383cba1471971767039a3071b8c57d28b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 9 Dec 2020 11:13:02 +0100 Subject: [PATCH 180/948] core,les: headerchain import in batches (#21471) * core: add test for headerchain inserts * core, light: write headerchains in batches * core: change to one callback per batch of inserted headers + review concerns * core: error-check on batch write * core: unexport writeHeaders * core: remove callback parameter in InsertHeaderChain The semantics of InsertHeaderChain are now much simpler: it is now an all-or-nothing operation. The new WriteStatus return value allows callers to check for the canonicality of the insertion. This change simplifies use of HeaderChain in package les, where the callback was previously used to post chain events. * core: skip some hashing when writing headers * core: less hashing in header validation * core: fix headerchain flaw regarding blacklisted hashes Co-authored-by: Felix Lange --- core/blockchain.go | 8 +- core/headerchain.go | 292 +++++++++++++++++++++++---------------- core/headerchain_test.go | 115 +++++++++++++++ light/lightchain.go | 34 ++--- 4 files changed, 309 insertions(+), 140 deletions(-) create mode 100644 core/headerchain_test.go diff --git a/core/blockchain.go b/core/blockchain.go index c52be68354..bc1db49f37 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2438,12 +2438,8 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i bc.wg.Add(1) defer bc.wg.Done() - - whFunc := func(header *types.Header) error { - _, err := bc.hc.WriteHeader(header) - return err - } - return bc.hc.InsertHeaderChain(chain, whFunc, start) + _, err := bc.hc.InsertHeaderChain(chain, start) + return 0, err } // CurrentHeader retrieves the current head header of the canonical chain. The diff --git a/core/headerchain.go b/core/headerchain.go index 7c1aff99d8..dc354549c0 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -129,118 +129,192 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 { return number } -// WriteHeader writes a header into the local chain, given that its parent is -// already known. If the total difficulty of the newly inserted header becomes -// greater than the current known TD, the canonical chain is re-routed. +type headerWriteResult struct { + status WriteStatus + ignored int + imported int + lastHash common.Hash + lastHeader *types.Header +} + +// WriteHeaders writes a chain of headers into the local chain, given that the parents +// are already known. If the total difficulty of the newly inserted chain becomes +// greater than the current known TD, the canonical chain is reorged. // // Note: This method is not concurrent-safe with inserting blocks simultaneously // into the chain, as side effects caused by reorganisations cannot be emulated // without the real blocks. Hence, writing headers directly should only be done // in two scenarios: pure-header mode of operation (light clients), or properly // separated header/block phases (non-archive clients). -func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, err error) { - // Cache some values to prevent constant recalculation +func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWriteResult, err error) { + if len(headers) == 0 { + return &headerWriteResult{}, nil + } + ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1) + if ptd == nil { + return &headerWriteResult{}, consensus.ErrUnknownAncestor + } var ( - hash = header.Hash() - number = header.Number.Uint64() + lastNumber = headers[0].Number.Uint64() - 1 // Last successfully imported number + lastHash = headers[0].ParentHash // Last imported header hash + newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain + + lastHeader *types.Header + inserted []numberHash // Ephemeral lookup of number/hash for the chain + firstInserted = -1 // Index of the first non-ignored header ) - // Calculate the total difficulty of the header - ptd := hc.GetTd(header.ParentHash, number-1) - if ptd == nil { - return NonStatTy, consensus.ErrUnknownAncestor - } - head := hc.CurrentHeader().Number.Uint64() - localTd := hc.GetTd(hc.currentHeaderHash, head) - externTd := new(big.Int).Add(header.Difficulty, ptd) - - // Irrelevant of the canonical status, write the td and header to the database - // - // Note all the components of header(td, hash->number index and header) should - // be written atomically. - headerBatch := hc.chainDb.NewBatch() - rawdb.WriteTd(headerBatch, hash, number, externTd) - rawdb.WriteHeader(headerBatch, header) - if err := headerBatch.Write(); err != nil { - log.Crit("Failed to write header into disk", "err", err) + + batch := hc.chainDb.NewBatch() + for i, header := range headers { + var hash common.Hash + // The headers have already been validated at this point, so we already + // know that it's a contiguous chain, where + // headers[i].Hash() == headers[i+1].ParentHash + if i < len(headers)-1 { + hash = headers[i+1].ParentHash + } else { + hash = header.Hash() + } + number := header.Number.Uint64() + newTD.Add(newTD, header.Difficulty) + + // If the header is already known, skip it, otherwise store + if !hc.HasHeader(hash, number) { + // Irrelevant of the canonical status, write the TD and header to the database. + rawdb.WriteTd(batch, hash, number, newTD) + hc.tdCache.Add(hash, new(big.Int).Set(newTD)) + + rawdb.WriteHeader(batch, header) + inserted = append(inserted, numberHash{number, hash}) + hc.headerCache.Add(hash, header) + hc.numberCache.Add(hash, number) + if firstInserted < 0 { + firstInserted = i + } + } + lastHeader, lastHash, lastNumber = header, hash, number + } + + // Skip the slow disk write of all headers if interrupted. + if hc.procInterrupt() { + log.Debug("Premature abort during headers import") + return &headerWriteResult{}, errors.New("aborted") } + // Commit to disk! + if err := batch.Write(); err != nil { + log.Crit("Failed to write headers", "error", err) + } + batch.Reset() + + var ( + head = hc.CurrentHeader().Number.Uint64() + localTD = hc.GetTd(hc.currentHeaderHash, head) + status = SideStatTy + ) // If the total difficulty is higher than our known, add it to the canonical chain // Second clause in the if statement reduces the vulnerability to selfish mining. // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf - reorg := externTd.Cmp(localTd) > 0 - if !reorg && externTd.Cmp(localTd) == 0 { - if header.Number.Uint64() < head { + reorg := newTD.Cmp(localTD) > 0 + if !reorg && newTD.Cmp(localTD) == 0 { + if lastNumber < head { reorg = true - } else if header.Number.Uint64() == head { + } else if lastNumber == head { reorg = mrand.Float64() < 0.5 } } + // If the parent of the (first) block is already the canon header, + // we don't have to go backwards to delete canon blocks, but + // simply pile them onto the existing chain + chainAlreadyCanon := headers[0].ParentHash == hc.currentHeaderHash if reorg { // If the header can be added into canonical chain, adjust the // header chain markers(canonical indexes and head header flag). // // Note all markers should be written atomically. - - // Delete any canonical number assignments above the new head - markerBatch := hc.chainDb.NewBatch() - for i := number + 1; ; i++ { - hash := rawdb.ReadCanonicalHash(hc.chainDb, i) - if hash == (common.Hash{}) { - break + markerBatch := batch // we can reuse the batch to keep allocs down + if !chainAlreadyCanon { + // Delete any canonical number assignments above the new head + for i := lastNumber + 1; ; i++ { + hash := rawdb.ReadCanonicalHash(hc.chainDb, i) + if hash == (common.Hash{}) { + break + } + rawdb.DeleteCanonicalHash(markerBatch, i) + } + // Overwrite any stale canonical number assignments, going + // backwards from the first header in this import + var ( + headHash = headers[0].ParentHash // inserted[0].parent? + headNumber = headers[0].Number.Uint64() - 1 // inserted[0].num-1 ? + headHeader = hc.GetHeader(headHash, headNumber) + ) + for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { + rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber) + headHash = headHeader.ParentHash + headNumber = headHeader.Number.Uint64() - 1 + headHeader = hc.GetHeader(headHash, headNumber) + } + // If some of the older headers were already known, but obtained canon-status + // during this import batch, then we need to write that now + // Further down, we continue writing the staus for the ones that + // were not already known + for i := 0; i < firstInserted; i++ { + hash := headers[i].Hash() + num := headers[i].Number.Uint64() + rawdb.WriteCanonicalHash(markerBatch, hash, num) + rawdb.WriteHeadHeaderHash(markerBatch, hash) } - rawdb.DeleteCanonicalHash(markerBatch, i) } - - // Overwrite any stale canonical number assignments - var ( - headHash = header.ParentHash - headNumber = header.Number.Uint64() - 1 - headHeader = hc.GetHeader(headHash, headNumber) - ) - for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { - rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber) - - headHash = headHeader.ParentHash - headNumber = headHeader.Number.Uint64() - 1 - headHeader = hc.GetHeader(headHash, headNumber) + // Extend the canonical chain with the new headers + for _, hn := range inserted { + rawdb.WriteCanonicalHash(markerBatch, hn.hash, hn.number) + rawdb.WriteHeadHeaderHash(markerBatch, hn.hash) } - // Extend the canonical chain with the new header - rawdb.WriteCanonicalHash(markerBatch, hash, number) - rawdb.WriteHeadHeaderHash(markerBatch, hash) if err := markerBatch.Write(); err != nil { log.Crit("Failed to write header markers into disk", "err", err) } + markerBatch.Reset() // Last step update all in-memory head header markers - hc.currentHeaderHash = hash - hc.currentHeader.Store(types.CopyHeader(header)) - headHeaderGauge.Update(header.Number.Int64()) + hc.currentHeaderHash = lastHash + hc.currentHeader.Store(types.CopyHeader(lastHeader)) + headHeaderGauge.Update(lastHeader.Number.Int64()) + // Chain status is canonical since this insert was a reorg. + // Note that all inserts which have higher TD than existing are 'reorg'. status = CanonStatTy - } else { - status = SideStatTy } - hc.tdCache.Add(hash, externTd) - hc.headerCache.Add(hash, header) - hc.numberCache.Add(hash, number) - return -} -// WhCallback is a callback function for inserting individual headers. -// A callback is used for two reasons: first, in a LightChain, status should be -// processed and light chain events sent, while in a BlockChain this is not -// necessary since chain events are sent after inserting blocks. Second, the -// header writes should be protected by the parent chain mutex individually. -type WhCallback func(*types.Header) error + if len(inserted) == 0 { + status = NonStatTy + } + return &headerWriteResult{ + status: status, + ignored: len(headers) - len(inserted), + imported: len(inserted), + lastHash: lastHash, + lastHeader: lastHeader, + }, nil +} func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) { // Do a sanity check that the provided chain is actually ordered and linked for i := 1; i < len(chain); i++ { - if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() { + parentHash := chain[i-1].Hash() + if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != parentHash { // Chain broke ancestry, log a message (programming error) and skip insertion log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(), - "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash()) + "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash) return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number, - chain[i-1].Hash().Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) + parentHash.Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) + } + // If the header is a banned one, straight out abort + if BadHashes[parentHash] { + return i - 1, ErrBlacklistedHash + } + // If it's the last header in the cunk, we need to check it too + if i == len(chain)-1 && BadHashes[chain[i].Hash()] { + return i, ErrBlacklistedHash } } @@ -263,16 +337,12 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) defer close(abort) // Iterate over the headers and ensure they all check out - for i, header := range chain { + for i := range chain { // If the chain is terminating, stop processing blocks if hc.procInterrupt() { log.Debug("Premature abort during headers verification") return 0, errors.New("aborted") } - // If the header is a banned one, straight out abort - if BadHashes[header.Hash()] { - return i, ErrBlacklistedHash - } // Otherwise wait for headers checks and ensure they pass if err := <-results; err != nil { return i, err @@ -282,55 +352,41 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) return 0, nil } -// InsertHeaderChain attempts to insert the given header chain in to the local -// chain, possibly creating a reorg. If an error is returned, it will return the -// index number of the failing header as well an error describing what went wrong. +// InsertHeaderChain inserts the given headers. // -// The verify parameter can be used to fine tune whether nonce verification -// should be done or not. The reason behind the optional check is because some -// of the header retrieval mechanisms already need to verfy nonces, as well as -// because nonces can be verified sparsely, not needing to check each. -func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, writeHeader WhCallback, start time.Time) (int, error) { - // Collect some import statistics to report on - stats := struct{ processed, ignored int }{} - // All headers passed verification, import them into the database - for i, header := range chain { - // Short circuit insertion if shutting down - if hc.procInterrupt() { - log.Debug("Premature abort during headers import") - return i, errors.New("aborted") - } - // If the header's already known, skip it, otherwise store - hash := header.Hash() - if hc.HasHeader(hash, header.Number.Uint64()) { - externTd := hc.GetTd(hash, header.Number.Uint64()) - localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64()) - if externTd == nil || externTd.Cmp(localTd) <= 0 { - stats.ignored++ - continue - } - } - if err := writeHeader(header); err != nil { - return i, err - } - stats.processed++ +// The validity of the headers is NOT CHECKED by this method, i.e. they need to be +// validated by ValidateHeaderChain before calling InsertHeaderChain. +// +// This insert is all-or-nothing. If this returns an error, no headers were written, +// otherwise they were all processed successfully. +// +// The returned 'write status' says if the inserted headers are part of the canonical chain +// or a side chain. +func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time) (WriteStatus, error) { + if hc.procInterrupt() { + return 0, errors.New("aborted") } - // Report some public statistics so the user has a clue what's going on - last := chain[len(chain)-1] + res, err := hc.writeHeaders(chain) + // Report some public statistics so the user has a clue what's going on context := []interface{}{ - "count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)), - "number", last.Number, "hash", last.Hash(), + "count", res.imported, + "elapsed", common.PrettyDuration(time.Since(start)), + } + if err != nil { + context = append(context, "err", err) } - if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute { - context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + if last := res.lastHeader; last != nil { + context = append(context, "number", last.Number, "hash", res.lastHash) + if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute { + context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) + } } - if stats.ignored > 0 { - context = append(context, []interface{}{"ignored", stats.ignored}...) + if res.ignored > 0 { + context = append(context, []interface{}{"ignored", res.ignored}...) } log.Info("Imported new block headers", context...) - - return 0, nil + return res.status, err } // GetBlockHashesFromHash retrieves a number of block hashes starting at a given diff --git a/core/headerchain_test.go b/core/headerchain_test.go new file mode 100644 index 0000000000..0aa25efd1f --- /dev/null +++ b/core/headerchain_test.go @@ -0,0 +1,115 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "errors" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +func verifyUnbrokenCanonchain(hc *HeaderChain) error { + h := hc.CurrentHeader() + for { + canonHash := rawdb.ReadCanonicalHash(hc.chainDb, h.Number.Uint64()) + if exp := h.Hash(); canonHash != exp { + return fmt.Errorf("Canon hash chain broken, block %d got %x, expected %x", + h.Number, canonHash[:8], exp[:8]) + } + // Verify that we have the TD + if td := rawdb.ReadTd(hc.chainDb, canonHash, h.Number.Uint64()); td == nil { + return fmt.Errorf("Canon TD missing at block %d", h.Number) + } + if h.Number.Uint64() == 0 { + break + } + h = hc.GetHeader(h.ParentHash, h.Number.Uint64()-1) + } + return nil +} + +func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error) { + t.Helper() + + status, err := hc.InsertHeaderChain(chain, time.Now()) + if status != wantStatus { + t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus) + } + // Always verify that the header chain is unbroken + if err := verifyUnbrokenCanonchain(hc); err != nil { + t.Fatal(err) + } + if !errors.Is(err, wantErr) { + t.Fatalf("unexpected error from InsertHeaderChain: %v", err) + } +} + +// This test checks status reporting of InsertHeaderChain. +func TestHeaderInsertion(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + genesis = new(Genesis).MustCommit(db) + ) + + hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false }) + if err != nil { + t.Fatal(err) + } + // chain A: G->A1->A2...A128 + chainA := makeHeaderChain(genesis.Header(), 128, ethash.NewFaker(), db, 10) + // chain B: G->A1->B2...B128 + chainB := makeHeaderChain(chainA[0], 128, ethash.NewFaker(), db, 10) + log.Root().SetHandler(log.StdoutHandler) + + // Inserting 64 headers on an empty chain, expecting + // 1 callbacks, 1 canon-status, 0 sidestatus, + testInsert(t, hc, chainA[:64], CanonStatTy, nil) + + // Inserting 64 identical headers, expecting + // 0 callbacks, 0 canon-status, 0 sidestatus, + testInsert(t, hc, chainA[:64], NonStatTy, nil) + + // Inserting the same some old, some new headers + // 1 callbacks, 1 canon, 0 side + testInsert(t, hc, chainA[32:96], CanonStatTy, nil) + + // Inserting side blocks, but not overtaking the canon chain + testInsert(t, hc, chainB[0:32], SideStatTy, nil) + + // Inserting more side blocks, but we don't have the parent + testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor) + + // Inserting more sideblocks, overtaking the canon chain + testInsert(t, hc, chainB[32:97], CanonStatTy, nil) + + // Inserting more A-headers, taking back the canonicality + testInsert(t, hc, chainA[90:100], CanonStatTy, nil) + + // And B becomes canon again + testInsert(t, hc, chainB[97:107], CanonStatTy, nil) + + // And B becomes even longer + testInsert(t, hc, chainB[107:128], CanonStatTy, nil) +} diff --git a/light/lightchain.go b/light/lightchain.go index 6fc321ae0b..ca6fbfac49 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -396,24 +396,26 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i lc.wg.Add(1) defer lc.wg.Done() - var events []interface{} - whFunc := func(header *types.Header) error { - status, err := lc.hc.WriteHeader(header) - - switch status { - case core.CanonStatTy: - log.Debug("Inserted new header", "number", header.Number, "hash", header.Hash()) - events = append(events, core.ChainEvent{Block: types.NewBlockWithHeader(header), Hash: header.Hash()}) - - case core.SideStatTy: - log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash()) - events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)}) - } - return err + status, err := lc.hc.InsertHeaderChain(chain, start) + if err != nil || len(chain) == 0 { + return 0, err + } + + // Create chain event for the new head block of this insertion. + var ( + events = make([]interface{}, 0, 1) + lastHeader = chain[len(chain)-1] + block = types.NewBlockWithHeader(lastHeader) + ) + switch status { + case core.CanonStatTy: + events = append(events, core.ChainEvent{Block: block, Hash: block.Hash()}) + case core.SideStatTy: + events = append(events, core.ChainSideEvent{Block: block}) } - i, err := lc.hc.InsertHeaderChain(chain, whFunc, start) lc.postChainEvents(events) - return i, err + + return 0, err } // CurrentHeader retrieves the current head header of the canonical chain. The From 915643a3e5a487899d925250b922824fb3d65708 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 9 Dec 2020 13:59:24 +0100 Subject: [PATCH 181/948] cmd/geth: add test to verify regexps in version check (#21962) --- .../{data2.json => vulnerabilities.json} | 22 ++++--- cmd/geth/version_check_test.go | 63 +++++++++++++++++-- 2 files changed, 72 insertions(+), 13 deletions(-) rename cmd/geth/testdata/vcheck/{data2.json => vulnerabilities.json} (79%) diff --git a/cmd/geth/testdata/vcheck/data2.json b/cmd/geth/testdata/vcheck/vulnerabilities.json similarity index 79% rename from cmd/geth/testdata/vcheck/data2.json rename to cmd/geth/testdata/vcheck/vulnerabilities.json index 75a31677a1..36509f95a9 100644 --- a/cmd/geth/testdata/vcheck/data2.json +++ b/cmd/geth/testdata/vcheck/vulnerabilities.json @@ -7,28 +7,32 @@ "links": [ "https://github.com/ethereum/go-ethereum/pull/21793", "https://blog.ethereum.org/2020/11/12/geth_security_release/", - "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p" ], "introduced": "v1.6.0", "fixed": "v1.9.24", "published": "2020-11-12", "severity": "Medium", - "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*", - "CVE": "correct" + "CVE": "CVE-2020-26240", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*" }, { - "name": "GoCrash", + "name": "Denial of service due to Go CVE-2020-28362", "uid": "GETH-2020-02", "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", "links": [ "https://blog.ethereum.org/2020/11/12/geth_security_release/", "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", - "https://github.com/golang/go/issues/42552" + "https://github.com/golang/go/issues/42552", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52" ], + "introduced": "v0.0.0", "fixed": "v1.9.24", "published": "2020-11-12", "severity": "Critical", + "CVE": "CVE-2020-28362", "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" }, { @@ -37,12 +41,14 @@ "summary": "A consensus flaw in Geth, related to `datacopy` precompile", "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", "links": [ - "https://blog.ethereum.org/2020/11/12/geth_security_release/" + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf" ], "introduced": "v1.9.7", "fixed": "v1.9.17", "published": "2020-11-12", "severity": "Critical", + "CVE": "CVE-2020-26241", "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" }, { @@ -51,12 +57,14 @@ "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", "description": "Full details to be disclosed at a later date", "links": [ - "https://blog.ethereum.org/2020/11/12/geth_security_release/" + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m" ], "introduced": "v1.9.16", "fixed": "v1.9.18", "published": "2020-11-12", "severity": "Critical", + "CVE": "CVE-2020-26242", "check": "Geth\\/v1\\.9.(16|17).*$" } ] diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go index 4afe8ddc83..0f056d1967 100644 --- a/cmd/geth/version_check_test.go +++ b/cmd/geth/version_check_test.go @@ -18,8 +18,12 @@ package main import ( "encoding/json" + "fmt" "io/ioutil" "path/filepath" + "regexp" + "strconv" + "strings" "testing" ) @@ -64,16 +68,63 @@ func testVerification(t *testing.T, pubkey, sigdir string) { } } -func TestJson(t *testing.T) { - data, _ := ioutil.ReadFile("./testdata/vcheck/data2.json") +func versionUint(v string) int { + mustInt := func(s string) int { + a, err := strconv.Atoi(s) + if err != nil { + panic(v) + } + return a + } + components := strings.Split(strings.TrimPrefix(v, "v"), ".") + a := mustInt(components[0]) + b := mustInt(components[1]) + c := mustInt(components[2]) + return a*100*100 + b*100 + c +} + +// TestMatching can be used to check that the regexps are correct +func TestMatching(t *testing.T) { + data, _ := ioutil.ReadFile("./testdata/vcheck/vulnerabilities.json") var vulns []vulnJson if err := json.Unmarshal(data, &vulns); err != nil { t.Fatal(err) } - if len(vulns) == 0 { - t.Fatal("expected data, got none") + check := func(version string) { + vFull := fmt.Sprintf("Geth/%v-unstable-15339cf1-20201204/linux-amd64/go1.15.4", version) + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + vulnIntro := versionUint(vuln.Introduced) + vulnFixed := versionUint(vuln.Fixed) + current := versionUint(version) + if err != nil { + t.Fatal(err) + } + if vuln.Name == "Denial of service due to Go CVE-2020-28362" { + // this one is not tied to geth-versions + continue + } + if vulnIntro <= current && vulnFixed > current { + // Should be vulnerable + if !r.MatchString(vFull) { + t.Errorf("Should be vulnerable, version %v, intro: %v, fixed: %v %v %v", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vuln.Check) + } + } else { + if r.MatchString(vFull) { + t.Errorf("Should not be flagged vulnerable, version %v, intro: %v, fixed: %v %v %d %d %d", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vulnIntro, current, vulnFixed) + } + } + + } } - if have, want := vulns[0].CVE, "correct"; have != want { - t.Errorf("have %v, want %v", have, want) + for major := 1; major < 2; major++ { + for minor := 0; minor < 30; minor++ { + for patch := 0; patch < 30; patch++ { + vShort := fmt.Sprintf("v%d.%d.%d", major, minor, patch) + check(vShort) + } + } } } From f935b1d5426b7c4147914b9f120b07f1f983f0ef Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 9 Dec 2020 15:43:36 +0100 Subject: [PATCH 182/948] crypto/signify, build: fix archive signing with signify (#21977) This fixes some issues in crypto/signify and makes release signing work. The archive signing step in ci.go used getenvBase64, which decodes the key data. This is incorrect here because crypto/signify already base64-decodes the key. --- .travis.yml | 26 ++++----- build/ci.go | 14 +++-- crypto/signify/signify.go | 100 ++++++++++++++------------------- crypto/signify/signify_test.go | 8 +-- 4 files changed, 66 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index d37458792a..1268c6d657 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,22 +67,22 @@ jobs: script: # Build for the primary platforms that Trusty can manage - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch 386 - - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # Switch over GCC to cross compilation (breaks 386, hence why do it here only) - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross - sudo ln -s /usr/include/asm-generic /usr/include/asm - GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc - - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # This builder does the Linux Azure MIPS xgo uploads - stage: build @@ -100,19 +100,19 @@ jobs: script: - go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done - - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done - - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done - - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done - - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify LINUX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # This builder does the Android Maven and Azure uploads - stage: build @@ -151,7 +151,7 @@ jobs: - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify ANDROID_SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds + - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds # This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads - stage: build @@ -167,7 +167,7 @@ jobs: submodules: false # avoid cloning ethereum/tests script: - go run build/ci.go install -dlgo - - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify OSX_SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure - gem uninstall cocoapods -a -x @@ -182,7 +182,7 @@ jobs: # Workaround for https://github.com/golang/go/issues/23749 - export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc' - - go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify IOS_SIGNIFY_KEY -deploy trunk -upload gethstore/builds + - go run build/ci.go xcode -signer IOS_SIGNING_KEY -signify SIGNIFY_KEY -deploy trunk -upload gethstore/builds # These builders run the tests - stage: build diff --git a/build/ci.go b/build/ci.go index 951b21f910..15946c257e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -58,7 +58,7 @@ import ( "time" "github.com/cespare/cp" - signifyPkg "github.com/ethereum/go-ethereum/crypto/signify" + "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/params" ) @@ -449,7 +449,7 @@ func archiveBasename(arch string, archiveVersion string) string { return platform + "-" + archiveVersion } -func archiveUpload(archive string, blobstore string, signer string, signify string) error { +func archiveUpload(archive string, blobstore string, signer string, signifyVar string) error { // If signing was requested, generate the signature files if signer != "" { key := getenvBase64(signer) @@ -457,9 +457,11 @@ func archiveUpload(archive string, blobstore string, signer string, signify stri return err } } - if signify != "" { - key := getenvBase64(string(signify)) - if err := signifyPkg.SignifySignFile(archive, archive+".sig", string(key), "verify with geth.pub", fmt.Sprintf("%d", time.Now().UTC().Unix())); err != nil { + if signifyVar != "" { + key := os.Getenv(signifyVar) + untrustedComment := "verify with geth-release.pub" + trustedComment := fmt.Sprintf("%s (%s)", archive, time.Now().UTC().Format(time.RFC1123)) + if err := signify.SignFile(archive, archive+".sig", key, untrustedComment, trustedComment); err != nil { return err } } @@ -478,7 +480,7 @@ func archiveUpload(archive string, blobstore string, signer string, signify stri return err } } - if signify != "" { + if signifyVar != "" { if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { return err } diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go index e86c4f09b0..7ba9705491 100644 --- a/crypto/signify/signify.go +++ b/crypto/signify/signify.go @@ -20,99 +20,81 @@ package signify import ( + "bytes" + "crypto/ed25519" "encoding/base64" "errors" "fmt" "io/ioutil" - "os" "strings" "time" - - "crypto/ed25519" ) var ( - errInvalidKeyHeader = errors.New("Incorrect key header") + errInvalidKeyHeader = errors.New("incorrect key header") errInvalidKeyLength = errors.New("invalid, key length != 104") ) -func parsePrivateKey(key string) (ed25519.PrivateKey, []byte, []byte, error) { +func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum []byte, err error) { keydata, err := base64.StdEncoding.DecodeString(key) if err != nil { return nil, nil, nil, err } - if len(keydata) != 104 { return nil, nil, nil, errInvalidKeyLength } - if string(keydata[:2]) != "Ed" { return nil, nil, nil, errInvalidKeyHeader } - return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil } -func commentHasManyLines(comment string) bool { - firstLFIndex := strings.IndexByte(comment, 10) - return (firstLFIndex >= 0 && firstLFIndex < len(comment)-1) -} - -// SignifySignFile creates a signature of the input file. -func SignifySignFile(input string, output string, key string, unTrustedComment string, trustedComment string) error { - in, err := os.Open(input) - if err != nil { - return err +// SignFile creates a signature of the input file. +// +// This accepts base64 keys in the format created by the 'signify' tool. +// The signature is written to the 'output' file. +func SignFile(input string, output string, key string, untrustedComment string, trustedComment string) error { + // Pre-check comments and ensure they're set to something. + if strings.IndexByte(untrustedComment, '\n') >= 0 { + return errors.New("untrusted comment must not contain newline") } - defer in.Close() - - out, err := os.Create(output) - if err != nil { - return err + if strings.IndexByte(trustedComment, '\n') >= 0 { + return errors.New("trusted comment must not contain newline") + } + if untrustedComment == "" { + untrustedComment = "verify with " + input + ".pub" + } + if trustedComment == "" { + trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) } - defer out.Close() - skey, header, keyNum, err := parsePrivateKey(key) + filedata, err := ioutil.ReadFile(input) if err != nil { return err } - - filedata, err := ioutil.ReadAll(in) + skey, header, keyNum, err := parsePrivateKey(key) if err != nil { return err } + // Create the main data signature. rawSig := ed25519.Sign(skey, filedata) - - var sigdata []byte - sigdata = append(sigdata, header...) - sigdata = append(sigdata, keyNum...) - sigdata = append(sigdata, rawSig...) - - // Check that the trusted comment fits in one line - if commentHasManyLines(unTrustedComment) { - return errors.New("untrusted comment must fit on a single line") - } - - if unTrustedComment == "" { - unTrustedComment = "verify with " + input + ".pub" - } - out.WriteString(fmt.Sprintf("untrusted comment: %s\n%s\n", unTrustedComment, base64.StdEncoding.EncodeToString(sigdata))) - - // Add the trusted comment if unavailable - if trustedComment == "" { - trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix()) - } - - // Check that the trusted comment fits in one line - if commentHasManyLines(trustedComment) { - return errors.New("trusted comment must fit on a single line") - } - - var sigAndComment []byte - sigAndComment = append(sigAndComment, rawSig...) - sigAndComment = append(sigAndComment, []byte(trustedComment)...) - out.WriteString(fmt.Sprintf("trusted comment: %s\n%s\n", trustedComment, base64.StdEncoding.EncodeToString(ed25519.Sign(skey, sigAndComment)))) - - return nil + var dataSig []byte + dataSig = append(dataSig, header...) + dataSig = append(dataSig, keyNum...) + dataSig = append(dataSig, rawSig...) + + // Create the comment signature. + var commentSigInput []byte + commentSigInput = append(commentSigInput, rawSig...) + commentSigInput = append(commentSigInput, []byte(trustedComment)...) + commentSig := ed25519.Sign(skey, commentSigInput) + + // Create the output file. + var out = new(bytes.Buffer) + fmt.Fprintln(out, "untrusted comment:", untrustedComment) + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(dataSig)) + fmt.Fprintln(out, "trusted comment:", trustedComment) + fmt.Fprintln(out, base64.StdEncoding.EncodeToString(commentSig)) + return ioutil.WriteFile(output, out.Bytes(), 0644) } diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go index af77eaf227..615d4e6527 100644 --- a/crypto/signify/signify_test.go +++ b/crypto/signify/signify_test.go @@ -52,7 +52,7 @@ func TestSignify(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants") if err != nil { t.Fatal(err) } @@ -96,7 +96,7 @@ func TestSignifyTrustedCommentTooManyLines(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants") if err == nil || err.Error() == "" { t.Fatalf("should have errored on a multi-line trusted comment, got %v", err) } @@ -121,7 +121,7 @@ func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "") if err != nil { t.Fatal(err) } @@ -146,7 +146,7 @@ func TestSignifyTrustedCommentEmpty(t *testing.T) { t.Fatal(err) } - err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") + err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "") if err != nil { t.Fatal(err) } From 817a3fb5622c8704116e9847661c16f9f3d785c6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 9 Dec 2020 20:21:31 +0100 Subject: [PATCH 183/948] p2p/enode: avoid crashing for invalid IP (#21981) The database panicked for invalid IPs. This is usually no problem because all code paths leading to node DB access verify the IP, but it's dangerous because improper validation can turn this panic into a DoS vulnerability. The quick fix here is to just turn database accesses using invalid IP into a noop. This isn't great, but I'm planning to remove the node DB for discv5 long-term, so it should be fine to have this quick fix for half a year. Fixes #21849 --- p2p/enode/nodedb.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 643fbe5711..d62f383f0b 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -61,6 +61,10 @@ const ( dbVersion = 9 ) +var ( + errInvalidIP = errors.New("invalid IP") +) + var zeroIP = make(net.IP, 16) // DB is the node database, storing previously seen nodes and any collected metadata about @@ -359,16 +363,25 @@ func (db *DB) expireNodes() { // LastPingReceived retrieves the time of the last ping packet received from // a remote node. func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time { + if ip = ip.To16(); ip == nil { + return time.Time{} + } return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0) } // UpdateLastPingReceived updates the last time we tried contacting a remote node. func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix()) } // LastPongReceived retrieves the time of the last successful pong from remote node. func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { + if ip = ip.To16(); ip == nil { + return time.Time{} + } // Launch expirer db.ensureExpirer() return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0) @@ -376,26 +389,41 @@ func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time { // UpdateLastPongReceived updates the last pong time of a node. func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix()) } // FindFails retrieves the number of findnode failures since bonding. func (db *DB) FindFails(id ID, ip net.IP) int { + if ip = ip.To16(); ip == nil { + return 0 + } return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails))) } // UpdateFindFails updates the number of findnode failures since bonding. func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails)) } // FindFailsV5 retrieves the discv5 findnode failure counter. func (db *DB) FindFailsV5(id ID, ip net.IP) int { + if ip = ip.To16(); ip == nil { + return 0 + } return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails))) } // UpdateFindFailsV5 stores the discv5 findnode failure counter. func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error { + if ip = ip.To16(); ip == nil { + return errInvalidIP + } return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails)) } From 9f6bb492bbcd6def92d0bc195faeb751e1591535 Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 10 Dec 2020 21:33:52 +0800 Subject: [PATCH 184/948] les, light: remove untrusted header retrieval in ODR (#21907) * les, light: remove untrusted header retrieval in ODR * les: polish * light: check the hash equality in odr --- les/client_handler.go | 62 +++++++++++++++++++++++++++++++++++++------ les/odr.go | 22 ++++++++------- les/odr_requests.go | 52 +++++++++++++++--------------------- les/retrieve.go | 9 +++++++ les/sync.go | 6 ++--- les/sync_test.go | 2 +- light/odr.go | 11 +++----- light/odr_util.go | 39 +++++++++++---------------- light/trie.go | 4 +++ 9 files changed, 125 insertions(+), 82 deletions(-) diff --git a/les/client_handler.go b/les/client_handler.go index 77a0ea5c6f..d7ca1c54f5 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -17,6 +17,7 @@ package les import ( + "context" "math/big" "sync" "sync/atomic" @@ -200,14 +201,23 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) - // Filter out any explicitly requested headers, deliver the rest to the downloader - filter := len(headers) == 1 - if filter { - headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) - } - if len(headers) != 0 || !filter { - if err := h.downloader.DeliverHeaders(p.id, headers); err != nil { - log.Debug("Failed to deliver headers", "err", err) + // Filter out the explicitly requested header by the retriever + if h.backend.retriever.requested(resp.ReqID) { + deliverMsg = &Msg{ + MsgType: MsgBlockHeaders, + ReqID: resp.ReqID, + Obj: resp.Headers, + } + } else { + // Filter out any explicitly requested headers, deliver the rest to the downloader + filter := len(headers) == 1 + if filter { + headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) + } + if len(headers) != 0 || !filter { + if err := h.downloader.DeliverHeaders(p.id, headers); err != nil { + log.Debug("Failed to deliver headers", "err", err) + } } } case BlockBodiesMsg: @@ -394,6 +404,42 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip return nil } +// RetrieveSingleHeaderByNumber requests a single header by the specified block +// number. This function will wait the response until it's timeout or delivered. +func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, number uint64) (*types.Header, error) { + reqID := genReqID() + rq := &distReq{ + getCost: func(dp distPeer) uint64 { + peer := dp.(*serverPeer) + return peer.getRequestCost(GetBlockHeadersMsg, 1) + }, + canSend: func(dp distPeer) bool { + return dp.(*serverPeer) == pc.peer + }, + request: func(dp distPeer) func() { + peer := dp.(*serverPeer) + cost := peer.getRequestCost(GetBlockHeadersMsg, 1) + peer.fcServer.QueuedRequest(reqID, cost) + return func() { peer.requestHeadersByNumber(reqID, number, 1, 0, false) } + }, + } + var header *types.Header + if err := pc.handler.backend.retriever.retrieve(context, reqID, rq, func(peer distPeer, msg *Msg) error { + if msg.MsgType != MsgBlockHeaders { + return errInvalidMessageType + } + headers := msg.Obj.([]*types.Header) + if len(headers) != 1 { + return errInvalidEntryCount + } + header = headers[0] + return nil + }, nil); err != nil { + return nil, err + } + return header, nil +} + // downloaderPeerNotify implements peerSetNotify type downloaderPeerNotify clientHandler diff --git a/les/odr.go b/les/odr.go index f8469cc103..2c36f512de 100644 --- a/les/odr.go +++ b/les/odr.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/log" ) // LesOdr implements light.OdrBackend @@ -83,7 +82,8 @@ func (odr *LesOdr) IndexerConfig() *light.IndexerConfig { } const ( - MsgBlockBodies = iota + MsgBlockHeaders = iota + MsgBlockBodies MsgCode MsgReceipts MsgProofsV2 @@ -122,13 +122,17 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro return func() { lreq.Request(reqID, p) } }, } - sent := mclock.Now() - if err = odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err == nil { - // retrieved from network, store in db - req.StoreResult(odr.db) + + defer func(sent mclock.AbsTime) { + if err != nil { + return + } requestRTT.Update(time.Duration(mclock.Now() - sent)) - } else { - log.Debug("Failed to retrieve data from network", "err", err) + }(mclock.Now()) + + if err := odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err != nil { + return err } - return + req.StoreResult(odr.db) + return nil } diff --git a/les/odr_requests.go b/les/odr_requests.go index 3704436a03..eb1d3602e0 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -327,9 +327,6 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool { peer.lock.RLock() defer peer.lock.RUnlock() - if r.Untrusted { - return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId - } return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize } @@ -369,39 +366,34 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { if err := rlp.DecodeBytes(headerEnc, header); err != nil { return errHeaderUnavailable } - // Verify the CHT - // Note: For untrusted CHT request, there is no proof response but - // header data. - var node light.ChtNode - if !r.Untrusted { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - reads := &readTraceDB{db: nodeSet} - value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) - if err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } + var ( + node light.ChtNode + encNumber [8]byte + ) + binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != header.Hash() { - return errCHTHashMismatch - } - if r.BlockNum != header.Number.Uint64() { - return errCHTNumberMismatch - } + reads := &readTraceDB{db: nodeSet} + value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) + if err != nil { + return fmt.Errorf("merkle proof verification failed: %v", err) + } + if len(reads.reads) != nodeSet.KeyCount() { + return errUselessNodes + } + if err := rlp.DecodeBytes(value, &node); err != nil { + return err + } + if node.Hash != header.Hash() { + return errCHTHashMismatch + } + if r.BlockNum != header.Number.Uint64() { + return errCHTNumberMismatch } // Verifications passed, store and return r.Header = header r.Proof = nodeSet - r.Td = node.Td // For untrusted request, td here is nil, todo improve the les/2 protocol - + r.Td = node.Td return nil } diff --git a/les/retrieve.go b/les/retrieve.go index 4f77004f20..ca4f867ea8 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -155,6 +155,15 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc return r } +// requested reports whether the request with given reqid is sent by the retriever. +func (rm *retrieveManager) requested(reqId uint64) bool { + rm.lock.RLock() + defer rm.lock.RUnlock() + + _, ok := rm.sentReqs[reqId] + return ok +} + // deliver is called by the LES protocol manager to deliver reply messages to waiting requests func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error { rm.lock.RLock() diff --git a/les/sync.go b/les/sync.go index d2568d45bc..ad3a0e0f3c 100644 --- a/les/sync.go +++ b/les/sync.go @@ -56,8 +56,8 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error { defer cancel() // Fetch the block header corresponding to the checkpoint registration. - cp := peer.checkpoint - header, err := light.GetUntrustedHeaderByNumber(ctx, h.backend.odr, peer.checkpointNumber, peer.id) + wrapPeer := &peerConnection{handler: h, peer: peer} + header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber) if err != nil { return err } @@ -66,7 +66,7 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error { if err != nil { return err } - events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash()) + events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, peer.checkpoint.SectionIndex, peer.checkpoint.Hash()) if len(events) == 0 { return errInvalidCheckpoint } diff --git a/les/sync_test.go b/les/sync_test.go index 6924e7b43a..2eb0f88bf9 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -53,7 +53,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { time.Sleep(10 * time.Millisecond) } } - // Generate 512+4 blocks (totally 1 CHT sections) + // Generate 128+1 blocks (totally 1 CHT sections) server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) defer tearDown() diff --git a/light/odr.go b/light/odr.go index 7016ef8ef2..bb243f9152 100644 --- a/light/odr.go +++ b/light/odr.go @@ -135,8 +135,6 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { // ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie type ChtRequest struct { - Untrusted bool // Indicator whether the result retrieved is trusted or not - PeerId string // The specified peer id from which to retrieve data. Config *IndexerConfig ChtNum, BlockNum uint64 ChtRoot common.Hash @@ -148,12 +146,9 @@ type ChtRequest struct { // StoreResult stores the retrieved data in local database func (req *ChtRequest) StoreResult(db ethdb.Database) { hash, num := req.Header.Hash(), req.Header.Number.Uint64() - - if !req.Untrusted { - rawdb.WriteHeader(db, req.Header) - rawdb.WriteTd(db, hash, num, req.Td) - rawdb.WriteCanonicalHash(db, hash, num) - } + rawdb.WriteHeader(db, req.Header) + rawdb.WriteTd(db, hash, num, req.Td) + rawdb.WriteCanonicalHash(db, hash, num) } // BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure diff --git a/light/odr_util.go b/light/odr_util.go index aec0c7b69f..ec2d1e6491 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -19,20 +19,23 @@ package light import ( "bytes" "context" + "errors" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) -var sha3Nil = crypto.Keccak256Hash(nil) +// errNonCanonicalHash is returned if the requested chain data doesn't belong +// to the canonical chain. ODR can only retrieve the canonical chain data covered +// by the CHT or Bloom trie for verification. +var errNonCanonicalHash = errors.New("hash is not currently canonical") // GetHeaderByNumber retrieves the canonical block header corresponding to the -// given number. +// given number. The returned header is proven by local CHT. func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) { // Try to find it in the local database first. db := odr.Database() @@ -63,25 +66,6 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ return r.Header, nil } -// GetUntrustedHeaderByNumber retrieves specified block header without -// correctness checking. Note this function should only be used in light -// client checkpoint syncing. -func GetUntrustedHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64, peerId string) (*types.Header, error) { - // todo(rjl493456442) it's a hack to retrieve headers which is not covered - // by CHT. Fix it in LES4 - r := &ChtRequest{ - BlockNum: number, - ChtNum: number / odr.IndexerConfig().ChtSize, - Untrusted: true, - PeerId: peerId, - Config: odr.IndexerConfig(), - } - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - return r.Header, nil -} - // GetCanonicalHash retrieves the canonical block hash corresponding to the number. func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) { hash := rawdb.ReadCanonicalHash(odr.Database(), number) @@ -102,10 +86,13 @@ func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) if td != nil { return td, nil } - _, err := GetHeaderByNumber(ctx, odr, number) + header, err := GetHeaderByNumber(ctx, odr, number) if err != nil { return nil, err } + if header.Hash() != hash { + return nil, errNonCanonicalHash + } // -> td mapping already be stored in db, get it. return rawdb.ReadTd(odr.Database(), hash, number), nil } @@ -120,6 +107,9 @@ func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number ui if err != nil { return nil, errNoHeader } + if header.Hash() != hash { + return nil, errNonCanonicalHash + } r := &BlockRequest{Hash: hash, Number: number, Header: header} if err := odr.Retrieve(ctx, r); err != nil { return nil, err @@ -167,6 +157,9 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num if err != nil { return nil, errNoHeader } + if header.Hash() != hash { + return nil, errNonCanonicalHash + } r := &ReceiptsRequest{Hash: hash, Number: number, Header: header} if err := odr.Retrieve(ctx, r); err != nil { return nil, err diff --git a/light/trie.go b/light/trie.go index 3eb05f4a3f..0516b94486 100644 --- a/light/trie.go +++ b/light/trie.go @@ -30,6 +30,10 @@ import ( "github.com/ethereum/go-ethereum/trie" ) +var ( + sha3Nil = crypto.Keccak256Hash(nil) +) + func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB { state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil) return state From b44f24e3e6644a2034ce23037827b21aa59e441d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 10 Dec 2020 14:48:32 +0100 Subject: [PATCH 185/948] core, trie: speed up some tests with quadratic processing flaw (#21987) This commit fixes a flaw in two testcases, and brings down the exec-time from ~40s to ~8s for trie/TestIncompleteSync. The checkConsistency was performed over and over again on the complete set of nodes, not just the recently added, turning it into a quadratic runtime. --- core/state/sync_test.go | 30 ++++++++++++++---------------- trie/sync_test.go | 7 ++----- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/core/state/sync_test.go b/core/state/sync_test.go index deb4b52b4c..9c4867093d 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -62,7 +62,8 @@ func makeTestState() (Database, common.Hash, []*testAccount) { } if i%5 == 0 { for j := byte(0); j < 5; j++ { - obj.SetState(db, crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}), crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})) + hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}) + obj.SetState(db, hash, hash) } } state.updateStateObject(obj) @@ -401,15 +402,14 @@ func TestIncompleteStateSync(t *testing.T) { // Create a random state to copy srcDb, srcRoot, srcAccounts := makeTestState() - // isCode reports whether the hash is contract code hash. - isCode := func(hash common.Hash) bool { - for _, acc := range srcAccounts { - if hash == crypto.Keccak256Hash(acc.code) { - return true - } + // isCodeLookup to save some hashing + var isCode = make(map[common.Hash]struct{}) + for _, acc := range srcAccounts { + if len(acc.code) > 0 { + isCode[crypto.Keccak256Hash(acc.code)] = struct{}{} } - return false } + isCode[common.BytesToHash(emptyCodeHash)] = struct{}{} checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot) // Create a destination state and sync with the scheduler @@ -447,15 +447,13 @@ func TestIncompleteStateSync(t *testing.T) { batch.Write() for _, result := range results { added = append(added, result.Hash) - } - // Check that all known sub-tries added so far are complete or missing entirely. - for _, hash := range added { - if isCode(hash) { + // Check that all known sub-tries added so far are complete or missing entirely. + if _, ok := isCode[result.Hash]; ok { continue } // Can't use checkStateConsistency here because subtrie keys may have odd // length and crash in LeafKey. - if err := checkTrieConsistency(dstDb, hash); err != nil { + if err := checkTrieConsistency(dstDb, result.Hash); err != nil { t.Fatalf("state inconsistent: %v", err) } } @@ -466,9 +464,9 @@ func TestIncompleteStateSync(t *testing.T) { // Sanity check that removing any node from the database is detected for _, node := range added[1:] { var ( - key = node.Bytes() - code = isCode(node) - val []byte + key = node.Bytes() + _, code = isCode[node] + val []byte ) if code { val = rawdb.ReadCode(dstDb, node) diff --git a/trie/sync_test.go b/trie/sync_test.go index 39e0f9575e..cb3283875d 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -377,7 +377,6 @@ func TestIncompleteSync(t *testing.T) { nodes, _, codes := sched.Missing(1) queue := append(append([]common.Hash{}, nodes...), codes...) - for len(queue) > 0 { // Fetch a batch of trie nodes results := make([]SyncResult, len(queue)) @@ -401,10 +400,8 @@ func TestIncompleteSync(t *testing.T) { batch.Write() for _, result := range results { added = append(added, result.Hash) - } - // Check that all known sub-tries in the synced trie are complete - for _, root := range added { - if err := checkTrieConsistency(triedb, root); err != nil { + // Check that all known sub-tries in the synced trie are complete + if err := checkTrieConsistency(triedb, result.Hash); err != nil { t.Fatalf("trie inconsistent: %v", err) } } From 004541098d28ed2d76ac4aa524b0e3fa80140f97 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 11 Dec 2020 00:20:55 +0800 Subject: [PATCH 186/948] les: introduce forkID (#21974) * les: introduce forkID * les: address comment --- les/client_handler.go | 28 ++++++++++++++++------------ les/peer.go | 27 ++++++++++++++++++++++----- les/peer_test.go | 23 +++++++++++++++++++++-- les/protocol.go | 5 ++++- les/server_handler.go | 6 +++++- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/les/client_handler.go b/les/client_handler.go index d7ca1c54f5..6de5766961 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" @@ -37,6 +38,7 @@ import ( // responses. type clientHandler struct { ulc *ulc + forkFilter forkid.Filter checkpoint *params.TrustedCheckpoint fetcher *lightFetcher downloader *downloader.Downloader @@ -49,6 +51,7 @@ type clientHandler struct { func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { handler := &clientHandler{ + forkFilter: forkid.NewFilter(backend.blockchain), checkpoint: checkpoint, backend: backend, closeCh: make(chan struct{}), @@ -103,7 +106,8 @@ func (h *clientHandler) handle(p *serverPeer) error { p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) // Execute the LES handshake - if err := p.Handshake(h.backend.blockchain.Genesis().Hash()); err != nil { + forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64()) + if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } @@ -154,8 +158,8 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { var deliverMsg *Msg // Handle the message depending on its contents - switch msg.Code { - case AnnounceMsg: + switch { + case msg.Code == AnnounceMsg: p.Log().Trace("Received announce message") var req announceData if err := msg.Decode(&req); err != nil { @@ -188,7 +192,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { p.updateHead(req.Hash, req.Number, req.Td) h.fetcher.announce(p, &req) } - case BlockHeadersMsg: + case msg.Code == BlockHeadersMsg: p.Log().Trace("Received block header response message") var resp struct { ReqID, BV uint64 @@ -220,7 +224,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } } } - case BlockBodiesMsg: + case msg.Code == BlockBodiesMsg: p.Log().Trace("Received block bodies response") var resp struct { ReqID, BV uint64 @@ -236,7 +240,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case CodeMsg: + case msg.Code == CodeMsg: p.Log().Trace("Received code response") var resp struct { ReqID, BV uint64 @@ -252,7 +256,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case ReceiptsMsg: + case msg.Code == ReceiptsMsg: p.Log().Trace("Received receipts response") var resp struct { ReqID, BV uint64 @@ -268,7 +272,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Receipts, } - case ProofsV2Msg: + case msg.Code == ProofsV2Msg: p.Log().Trace("Received les/2 proofs response") var resp struct { ReqID, BV uint64 @@ -284,7 +288,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case HelperTrieProofsMsg: + case msg.Code == HelperTrieProofsMsg: p.Log().Trace("Received helper trie proof response") var resp struct { ReqID, BV uint64 @@ -300,7 +304,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Data, } - case TxStatusMsg: + case msg.Code == TxStatusMsg: p.Log().Trace("Received tx status response") var resp struct { ReqID, BV uint64 @@ -316,11 +320,11 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { ReqID: resp.ReqID, Obj: resp.Status, } - case StopMsg: + case msg.Code == StopMsg && p.version >= lpv3: p.freeze() h.backend.retriever.frozen(p) p.Log().Debug("Service stopped") - case ResumeMsg: + case msg.Code == ResumeMsg && p.version >= lpv3: var bv uint64 if err := msg.Decode(&bv); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) diff --git a/les/peer.go b/les/peer.go index 2b0117bedc..31ee8f7f04 100644 --- a/les/peer.go +++ b/les/peer.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" @@ -246,7 +247,7 @@ func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, // network IDs, difficulties, head and genesis blocks. Besides the basic handshake // fields, server and client can exchange and resolve some specified fields through // two callback functions. -func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error { +func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error { p.lock.Lock() defer p.lock.Unlock() @@ -262,6 +263,12 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g send = send.add("headNum", headNum) send = send.add("genesisHash", genesis) + // If the protocol version is beyond les4, then pass the forkID + // as well. Check http://eips.ethereum.org/EIPS/eip-2124 for more + // spec detail. + if p.version >= lpv4 { + send = send.add("forkID", forkID) + } // Add client-specified or server-specified fields if sendCallback != nil { sendCallback(&send) @@ -295,6 +302,16 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g if int(rVersion) != p.version { return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) } + // Check forkID if the protocol version is beyond the les4 + if p.version >= lpv4 { + var forkID forkid.ID + if err := recv.get("forkID", &forkID); err != nil { + return err + } + if err := forkFilter(forkID); err != nil { + return errResp(ErrForkIDRejected, "%v", err) + } + } if recvCallback != nil { return recvCallback(recv) } @@ -561,10 +578,10 @@ func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) { // Handshake executes the les protocol handshake, negotiating version number, // network IDs and genesis blocks. -func (p *serverPeer) Handshake(genesis common.Hash) error { +func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter forkid.Filter) error { // Note: there is no need to share local head with a server but older servers still // require these fields so we announce zero values. - return p.handshake(common.Big0, common.Hash{}, 0, genesis, func(lists *keyValueList) { + return p.handshake(common.Big0, common.Hash{}, 0, genesis, forkid, forkFilter, func(lists *keyValueList) { // Add some client-specific handshake fields // // Enable signed announcement randomly even the server is not trusted. @@ -944,11 +961,11 @@ func (p *clientPeer) freezeClient() { // Handshake executes the les protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error { +func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error { // Note: clientPeer.headInfo should contain the last head announced to the client by us. // The values announced in the handshake are dummy values for compatibility reasons and should be ignored. p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td} - return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) { + return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) { // Add some information which services server can offer. if !server.config.UltraLightOnlyAnnounce { *lists = (*lists).add("serveHeaders", nil) diff --git a/les/peer_test.go b/les/peer_test.go index 6d3c7f9755..d6551ce6b6 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -26,8 +26,13 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" ) type testServerPeerSub struct { @@ -91,6 +96,14 @@ func TestPeerSubscription(t *testing.T) { checkPeers(sub.unregCh) } +type fakeChain struct{} + +func (f *fakeChain) Config() *params.ChainConfig { return params.MainnetChainConfig } +func (f *fakeChain) Genesis() *types.Block { + return core.DefaultGenesisBlock().ToBlock(rawdb.NewMemoryDatabase()) +} +func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} } + func TestHandshake(t *testing.T) { // Create a message pipe to communicate through app, net := p2p.MsgPipe() @@ -110,15 +123,21 @@ func TestHandshake(t *testing.T) { head = common.HexToHash("deadbeef") headNum = uint64(10) genesis = common.HexToHash("cafebabe") + + chain1, chain2 = &fakeChain{}, &fakeChain{} + forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64()) + forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64()) + filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2) ) + go func() { - errCh1 <- peer1.handshake(td, head, headNum, genesis, func(list *keyValueList) { + errCh1 <- peer1.handshake(td, head, headNum, genesis, forkID1, filter1, func(list *keyValueList) { var announceType uint64 = announceTypeSigned *list = (*list).add("announceType", announceType) }, nil) }() go func() { - errCh2 <- peer2.handshake(td, head, headNum, genesis, nil, func(recv keyValueMap) error { + errCh2 <- peer2.handshake(td, head, headNum, genesis, forkID2, filter2, nil, func(recv keyValueMap) error { var reqType uint64 err := recv.get("announceType", &reqType) if err != nil { diff --git a/les/protocol.go b/les/protocol.go index 19a9561ce9..aebe0f2c04 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -34,6 +34,7 @@ import ( const ( lpv2 = 2 lpv3 = 3 + lpv4 = 4 ) // Supported versions of the les protocol (first is primary) @@ -44,7 +45,7 @@ var ( ) // Number of implemented message corresponding to different protocol versions. -var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24} +var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24} const ( NetworkId = 1 @@ -150,6 +151,7 @@ const ( ErrInvalidResponse ErrTooManyTimeouts ErrMissingKey + ErrForkIDRejected ) func (e errCode) String() string { @@ -172,6 +174,7 @@ var errorToString = map[int]string{ ErrInvalidResponse: "Invalid response", ErrTooManyTimeouts: "Too many request timeouts", ErrMissingKey: "Key missing from list", + ErrForkIDRejected: "ForkID rejected", } // announceData is the network packet for the block announcements. diff --git a/les/server_handler.go b/les/server_handler.go index c0600b3686..f965e3fc64 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -66,6 +67,7 @@ var ( // serverHandler is responsible for serving light client and process // all incoming light requests. type serverHandler struct { + forkFilter forkid.Filter blockchain *core.BlockChain chainDb ethdb.Database txpool *core.TxPool @@ -81,6 +83,7 @@ type serverHandler struct { func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *core.TxPool, synced func() bool) *serverHandler { handler := &serverHandler{ + forkFilter: forkid.NewFilter(blockchain), server: server, blockchain: blockchain, chainDb: chainDb, @@ -121,8 +124,9 @@ func (h *serverHandler) handle(p *clientPeer) error { hash = head.Hash() number = head.Number.Uint64() td = h.blockchain.GetTd(hash, number) + forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), h.blockchain.CurrentBlock().NumberU64()) ) - if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), h.server); err != nil { + if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } From 1d1f5fea4a3327dd59e39bf1324501f9b43f3354 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2020 09:02:55 +0100 Subject: [PATCH 187/948] build: upgrade to Go 1.15.6 (#21986) --- build/checksums.txt | 20 ++++++++++++-------- build/ci.go | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index 32b376519f..a7a6a657e9 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,13 +1,17 @@ # This file contains sha256 checksums of optional build dependencies. -c1076b90cf94b73ebed62a81d802cd84d43d02dea8c07abdc922c57a071c84f1 go1.15.5.src.tar.gz -359a4334b8c8f5e3067e5a76f16419791ac3fef4613d8e8e1eac0b9719915f6d go1.15.5.darwin-amd64.tar.gz -4c8179d406136979724c71732009c7e2e7c794dbeaaa2a043c00da34d4be0559 go1.15.5.linux-386.tar.gz -9a58494e8da722c3aef248c9227b0e9c528c7318309827780f16220998180a0d go1.15.5.linux-amd64.tar.gz -a72a0b036beb4193a0214bca3fca4c5d68a38a4ccf098c909f7ce8bf08567c48 go1.15.5.linux-arm64.tar.gz -5ea6456620d3efed5dda99238c7f23866eafdd915e5348736e631bc283c0238a go1.15.5.linux-armv6l.tar.gz -d812436c7e3482ba3c97172edf26afaf35aca60a5621ff4a5f6a08386505ab9c go1.15.5.windows-386.zip -1d24be3a200201a74be25e4134fbec467750e834e84e9c7789a9fc13248c5507 go1.15.5.windows-amd64.zip +890bba73c5e2b19ffb1180e385ea225059eb008eb91b694875dd86ea48675817 go1.15.6.src.tar.gz +940a73b45993a3bae5792cf324140dded34af97c548af4864d22fd6d49f3bd9f go1.15.6.darwin-amd64.tar.gz +ad187f02158b9a9013ef03f41d14aa69c402477f178825a3940280814bcbb755 go1.15.6.linux-386.tar.gz +3918e6cc85e7eaaa6f859f1bdbaac772e7a825b0eb423c63d3ae68b21f84b844 go1.15.6.linux-amd64.tar.gz +f87515b9744154ffe31182da9341d0a61eb0795551173d242c8cad209239e492 go1.15.6.linux-arm64.tar.gz +40ba9a57764e374195018ef37c38a5fbac9bbce908eab436370631a84bfc5788 go1.15.6.linux-armv6l.tar.gz +5872eff6746a0a5f304272b27cbe9ce186f468454e95749cce01e903fbfc0e17 go1.15.6.windows-386.zip +b7b3808bb072c2bab73175009187fd5a7f20ffe0a31739937003a14c5c4d9006 go1.15.6.windows-amd64.zip +9d9dd5c217c1392f1b2ed5e03e1c71bf4cf8553884e57a38e68fd37fdcfe31a8 go1.15.6.freebsd-386.tar.gz +609f065d855aed5a0b40ef0245aacbcc0b4b7882dc3b1e75ae50576cf25265ee go1.15.6.freebsd-amd64.tar.gz +d4174fc217e749ac049eacc8827df879689f2246ac230d04991ae7df336f7cb2 go1.15.6.linux-ppc64le.tar.gz +839cc6b67687d8bb7cb044e4a9a2eac0c090765cc8ec55ffe714dfb7cd51cf3a go1.15.6.linux-s390x.tar.gz d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index 15946c257e..73d8961629 100644 --- a/build/ci.go +++ b/build/ci.go @@ -152,7 +152,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.15.5" + dlgoVersion = "1.15.6" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) From e7872729012a4871397307b12cc3f4772ffcbec6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2020 08:59:12 +0100 Subject: [PATCH 188/948] params: go-ethereum v1.9.25 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index a2ea188170..3237b51f85 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 25 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 25 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From fc0662bb2372f9a94dea60c3dcf122afd6bda55f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 11 Dec 2020 08:59:46 +0100 Subject: [PATCH 189/948] params: begin v1.9.26 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 3237b51f85..b6d6bd3f1d 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 25 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 26 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 1a715d7db57997307d309a498e8f819dd08725ad Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 11 Dec 2020 09:28:01 +0000 Subject: [PATCH 190/948] les: rework float conversion on arm64 and other architectures (#21994) The previous fix #21960 converted the float to an intermediate signed int, before attempting the uint conversion. Although this works, this doesn't guarantee that other architectures will work the same. --- les/utils/expiredvalue.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 1a2b3d995e..3fd52616fa 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -86,11 +86,15 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { e.Exp = integer } if base >= 0 || uint64(-base) <= e.Base { - // This is a temporary fix to circumvent a golang - // uint conversion issue on arm64, which needs to - // be investigated further. More details at: + // The conversion from negative float64 to + // uint64 is undefined in golang, and doesn't + // work with ARMv8. More details at: // https://github.com/golang/go/issues/43047 - e.Base += uint64(int64(base)) + if base >= 0 { + e.Base += uint64(base) + } else { + e.Base -= uint64(-base) + } return amount } net := int64(-float64(e.Base) / factor) From 62dc59c2bd6c80b711e873300d7cb91afa91e830 Mon Sep 17 00:00:00 2001 From: lzhfromustc <43191155+lzhfromustc@users.noreply.github.com> Date: Fri, 11 Dec 2020 04:29:42 -0500 Subject: [PATCH 191/948] miner, test: fix potential goroutine leak (#21989) In miner/worker.go, there are two goroutine using channel w.newWorkCh: newWorkerLoop() sends to this channel, and mainLoop() receives from this channel. Only the receive operation is in a select. However, w.exitCh may be closed by another goroutine. This is fine for the receive since receive is in select, but if the send operation is blocking, then it will block forever. This commit puts the send in a select, so it won't block even if w.exitCh is closed. Similarly, there are two goroutines using channel errc: the parent that runs the test receives from it, and the child created at line 573 sends to it. If the parent goroutine exits too early by calling t.Fatalf() at line 614, then the child goroutine will be blocked at line 574 forever. This commit adds 1 buffer to errc. Now send will not block, and receive is not influenced because receive still needs to wait for the send. --- eth/downloader/downloader_test.go | 2 +- miner/worker.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 7645f04e4f..5e46042ae4 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -569,7 +569,7 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) { <-proceed } // Start a synchronisation concurrently - errc := make(chan error) + errc := make(chan error, 1) go func() { errc <- tester.sync("peer", nil, mode) }() diff --git a/miner/worker.go b/miner/worker.go index d5813c18a8..5f07affdc4 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -347,7 +347,11 @@ func (w *worker) newWorkLoop(recommit time.Duration) { atomic.StoreInt32(interrupt, s) } interrupt = new(int32) - w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp} + select { + case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}: + case <-w.exitCh: + return + } timer.Reset(recommit) atomic.StoreInt32(&w.newTxs, 0) } From b47f4ca5cf3adf7c29e9ee00a6056196f295763c Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Fri, 11 Dec 2020 15:05:39 +0530 Subject: [PATCH 192/948] cmd/faucet: use Twitter API instead of scraping webpage (#21850) This PR adds support for using Twitter API to query the tweet and author details. There are two reasons behind this change: - Twitter will be deprecating the legacy website on 15th December. The current method is expected to stop working then. - More importantly, the current system uses Twitter handle for spam protection but the Twitter handle can be changed via automated calls. This allows bots to use the same tweet to withdraw funds infinite times as long as they keep changing their handle between every request. The Rinkeby as well as the Goerli faucet are being actively drained via this method. This PR changes the spam protection to be based on Twitter IDs instead of usernames. A user can not change their Twitter ID. --- cmd/faucet/faucet.go | 93 +++++++++++++++++++++++++++++++----- cmd/puppeth/module_faucet.go | 7 +++ cmd/puppeth/wizard_faucet.go | 23 +++++++++ 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index eaf0dc30c1..d7927ac491 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -83,6 +83,8 @@ var ( noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") + + twitterBearerToken = flag.String("twitter.token", "", "Twitter bearer token to authenticate with the twitter API") ) var ( @@ -443,6 +445,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } // Retrieve the Ethereum address to fund, the requesting user and a profile picture var ( + id string username string avatar string address common.Address @@ -462,11 +465,13 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } continue case strings.HasPrefix(msg.URL, "https://twitter.com/"): - username, avatar, address, err = authTwitter(msg.URL) + id, username, avatar, address, err = authTwitter(msg.URL, *twitterBearerToken) case strings.HasPrefix(msg.URL, "https://www.facebook.com/"): username, avatar, address, err = authFacebook(msg.URL) + id = username case *noauthFlag: username, avatar, address, err = authNoAuth(msg.URL) + id = username default: //lint:ignore ST1005 This error is to be displayed in the browser err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues") @@ -486,7 +491,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { fund bool timeout time.Time ) - if timeout = f.timeouts[username]; time.Now().After(timeout) { + if timeout = f.timeouts[id]; time.Now().After(timeout) { // User wasn't funded recently, create the funding transaction amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether) amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil)) @@ -520,7 +525,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace - f.timeouts[username] = time.Now().Add(timeout - grace) + f.timeouts[id] = time.Now().Add(timeout - grace) fund = true } f.lock.Unlock() @@ -684,23 +689,32 @@ func sendSuccess(conn *websocket.Conn, msg string) error { } // authTwitter tries to authenticate a faucet request using Twitter posts, returning -// the username, avatar URL and Ethereum address to fund on success. -func authTwitter(url string) (string, string, common.Address, error) { +// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. +func authTwitter(url string, token string) (string, string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense parts := strings.Split(url, "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Twitter status URL") + return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") + } + + // Twitter's API isn't really friendly with direct links. + // It is restricted to 300 queries / 15 minute with an app api key. + // Anything more will require read only authorization from the users and that we want to avoid. + + // If twitter bearer token is provided, use the twitter api + if token != "" { + return authTwitterWithToken(parts[len(parts)-1], token) } - // Twitter's API isn't really friendly with direct links. Still, we don't - // want to do ask read permissions from users, so just load the public posts + + // Twiter API token isn't provided so we just load the public posts // and scrape it for the Ethereum address and profile URL. We need to load // the mobile page though since the main page loads tweet contents via JS. url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1) res, err := http.Get(url) if err != nil { - return "", "", common.Address{}, err + return "", "", "", common.Address{}, err } defer res.Body.Close() @@ -708,24 +722,77 @@ func authTwitter(url string) (string, string, common.Address, error) { parts = strings.Split(res.Request.URL.String(), "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Twitter status URL") + return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") } username := parts[len(parts)-3] body, err := ioutil.ReadAll(res.Body) if err != nil { - return "", "", common.Address{}, err + return "", "", "", common.Address{}, err } address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) if address == (common.Address{}) { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("No Ethereum address found to fund") + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") } var avatar string if parts = regexp.MustCompile("src=\"([^\"]+twimg.com/profile_images[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 { avatar = parts[1] } - return username + "@twitter", avatar, address, nil + return username + "@twitter", username, avatar, address, nil +} + +// authTwitterWithToken tries to authenticate a faucet request using Twitter's API, returning +// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. +func authTwitterWithToken(tweetID string, token string) (string, string, string, common.Address, error) { + // Strip any query parameters from the tweet id + sanitizedTweetID := strings.Split(tweetID, "?")[0] + + // Ensure numeric tweetID + if !regexp.MustCompile("^[0-9]+$").MatchString(sanitizedTweetID) { + return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + } + + // Query the tweet details from Twitter + url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", sanitizedTweetID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "", "", common.Address{}, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", common.Address{}, err + } + defer res.Body.Close() + + var result struct { + Data struct { + AuthorID string `json:"author_id"` + ID string `json:"id"` + Text string `json:"text"` + } `json:"data"` + Includes struct { + Users []struct { + ProfileImageURL string `json:"profile_image_url"` + Username string `json:"username"` + ID string `json:"id"` + Name string `json:"name"` + } `json:"users"` + } `json:"includes"` + } + + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return "", "", "", common.Address{}, err + } + + address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Data.Text)) + if address == (common.Address{}) { + //lint:ignore ST1005 This error is to be displayed in the browser + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") + } + return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].ProfileImageURL, address, nil } // authFacebook tries to authenticate a faucet request using Facebook posts, diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go index 987bed14aa..2527e137f2 100644 --- a/cmd/puppeth/module_faucet.go +++ b/cmd/puppeth/module_faucet.go @@ -46,6 +46,7 @@ ENTRYPOINT [ \ "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ "--account.json", "/account.json", "--account.pass", "/account.pass" \ {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ + {{if .TwitterToken}}, "--twitter.token", "{{.TwitterToken}}", ]` // faucetComposefile is the docker-compose.yml file required to deploy and maintain @@ -71,6 +72,7 @@ services: - FAUCET_TIERS={{.FaucetTiers}} - CAPTCHA_TOKEN={{.CaptchaToken}} - CAPTCHA_SECRET={{.CaptchaSecret}} + - TWITTER_TOKEN={{.TwitterToken}} - NO_AUTH={{.NoAuth}}{{if .VHost}} - VIRTUAL_HOST={{.VHost}} - VIRTUAL_PORT=8080{{end}} @@ -103,6 +105,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config "FaucetMinutes": config.minutes, "FaucetTiers": config.tiers, "NoAuth": config.noauth, + "TwitterToken": config.twitterToken, }) files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes() @@ -120,6 +123,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config "FaucetMinutes": config.minutes, "FaucetTiers": config.tiers, "NoAuth": config.noauth, + "TwitterToken": config.twitterToken, }) files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes() @@ -152,6 +156,7 @@ type faucetInfos struct { noauth bool captchaToken string captchaSecret string + twitterToken string } // Report converts the typed struct into a plain string->string map, containing @@ -165,6 +170,7 @@ func (info *faucetInfos) Report() map[string]string { "Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes), "Funding tiers": strconv.Itoa(info.tiers), "Captha protection": fmt.Sprintf("%v", info.captchaToken != ""), + "Using Twitter API": fmt.Sprintf("%v", info.twitterToken != ""), "Ethstats username": info.node.ethstats, } if info.noauth { @@ -243,5 +249,6 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { captchaToken: infos.envvars["CAPTCHA_TOKEN"], captchaSecret: infos.envvars["CAPTCHA_SECRET"], noauth: infos.envvars["NO_AUTH"] == "true", + twitterToken: infos.envvars["TWITTER_TOKEN"], }, nil } diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go index 9f753ad68b..47e05cd9c1 100644 --- a/cmd/puppeth/wizard_faucet.go +++ b/cmd/puppeth/wizard_faucet.go @@ -102,6 +102,29 @@ func (w *wizard) deployFaucet() { infos.captchaSecret = w.readPassword() } } + + // Accessing the twitter api requires a bearer token, request it + if infos.twitterToken != "" { + fmt.Println() + fmt.Println("Reuse previous twitter API Bearer token (y/n)? (default = yes)") + if !w.readDefaultYesNo(true) { + infos.twitterToken = "" + } + } + if infos.twitterToken == "" { + // No previous twitter token (or old one discarded) + fmt.Println() + fmt.Println("Enable twitter API (y/n)? (default = no)") + if !w.readDefaultYesNo(false) { + log.Warn("The faucet will fallback to using direct calls") + } else { + // Twitter api explicitly requested, read the bearer token + fmt.Println() + fmt.Printf("What is the twitter API Bearer token?\n") + infos.twitterToken = w.readString() + } + } + // Figure out where the user wants to store the persistent data fmt.Println() if infos.node.datadir == "" { From 88c696240dc7dfd99588d9e2ef0b04f03a06d1a5 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 11 Dec 2020 17:44:57 +0800 Subject: [PATCH 193/948] core/txpool: remove "local" notion from the txpool price heap (#21478) * core: separate the local notion from the pricedHeap * core: add benchmarks * core: improve tests * core: address comments * core: degrade the panic to error message * core: fix typo * core: address comments * core: address comment * core: use PEAK instead of POP * core: address comments --- core/tx_list.go | 152 +++++++++++++++------------------ core/tx_pool.go | 195 +++++++++++++++++++++++++++++++++---------- core/tx_pool_test.go | 69 ++++++++++++--- 3 files changed, 271 insertions(+), 145 deletions(-) diff --git a/core/tx_list.go b/core/tx_list.go index cdd3df14c5..894640d570 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" ) // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for @@ -439,24 +438,29 @@ func (h *priceHeap) Pop() interface{} { } // txPricedList is a price-sorted heap to allow operating on transactions pool -// contents in a price-incrementing way. +// contents in a price-incrementing way. It's built opon the all transactions +// in txpool but only interested in the remote part. It means only remote transactions +// will be considered for tracking, sorting, eviction, etc. type txPricedList struct { - all *txLookup // Pointer to the map of all transactions - items *priceHeap // Heap of prices of all the stored transactions - stales int // Number of stale price points to (re-heap trigger) + all *txLookup // Pointer to the map of all transactions + remotes *priceHeap // Heap of prices of all the stored **remote** transactions + stales int // Number of stale price points to (re-heap trigger) } // newTxPricedList creates a new price-sorted transaction heap. func newTxPricedList(all *txLookup) *txPricedList { return &txPricedList{ - all: all, - items: new(priceHeap), + all: all, + remotes: new(priceHeap), } } // Put inserts a new transaction into the heap. -func (l *txPricedList) Put(tx *types.Transaction) { - heap.Push(l.items, tx) +func (l *txPricedList) Put(tx *types.Transaction, local bool) { + if local { + return + } + heap.Push(l.remotes, tx) } // Removed notifies the prices transaction list that an old transaction dropped @@ -465,121 +469,95 @@ func (l *txPricedList) Put(tx *types.Transaction) { func (l *txPricedList) Removed(count int) { // Bump the stale counter, but exit if still too low (< 25%) l.stales += count - if l.stales <= len(*l.items)/4 { + if l.stales <= len(*l.remotes)/4 { return } // Seems we've reached a critical number of stale transactions, reheap - reheap := make(priceHeap, 0, l.all.Count()) - - l.stales, l.items = 0, &reheap - l.all.Range(func(hash common.Hash, tx *types.Transaction) bool { - *l.items = append(*l.items, tx) - return true - }) - heap.Init(l.items) + l.Reheap() } // Cap finds all the transactions below the given price threshold, drops them // from the priced list and returns them for further removal from the entire pool. -func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions { +// +// Note: only remote transactions will be considered for eviction. +func (l *txPricedList) Cap(threshold *big.Int) types.Transactions { drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop - save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep - - for len(*l.items) > 0 { + for len(*l.remotes) > 0 { // Discard stale transactions if found during cleanup - tx := heap.Pop(l.items).(*types.Transaction) - if l.all.Get(tx.Hash()) == nil { + cheapest := (*l.remotes)[0] + if l.all.GetRemote(cheapest.Hash()) == nil { // Removed or migrated + heap.Pop(l.remotes) l.stales-- continue } // Stop the discards if we've reached the threshold - if tx.GasPriceIntCmp(threshold) >= 0 { - save = append(save, tx) + if cheapest.GasPriceIntCmp(threshold) >= 0 { break } - // Non stale transaction found, discard unless local - if local.containsTx(tx) { - save = append(save, tx) - } else { - drop = append(drop, tx) - } - } - for _, tx := range save { - heap.Push(l.items, tx) + heap.Pop(l.remotes) + drop = append(drop, cheapest) } return drop } // Underpriced checks whether a transaction is cheaper than (or as cheap as) the -// lowest priced transaction currently being tracked. -func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) bool { - // Local transactions cannot be underpriced - if local.containsTx(tx) { - return false - } +// lowest priced (remote) transaction currently being tracked. +func (l *txPricedList) Underpriced(tx *types.Transaction) bool { // Discard stale price points if found at the heap start - for len(*l.items) > 0 { - head := []*types.Transaction(*l.items)[0] - if l.all.Get(head.Hash()) == nil { + for len(*l.remotes) > 0 { + head := []*types.Transaction(*l.remotes)[0] + if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated l.stales-- - heap.Pop(l.items) + heap.Pop(l.remotes) continue } break } // Check if the transaction is underpriced or not - if len(*l.items) == 0 { - log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors - return false + if len(*l.remotes) == 0 { + return false // There is no remote transaction at all. } - cheapest := []*types.Transaction(*l.items)[0] + // If the remote transaction is even cheaper than the + // cheapest one tracked locally, reject it. + cheapest := []*types.Transaction(*l.remotes)[0] return cheapest.GasPriceCmp(tx) >= 0 } // Discard finds a number of most underpriced transactions, removes them from the // priced list and returns them for further removal from the entire pool. -func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions { - // If we have some local accountset, those will not be discarded - if !local.empty() { - // In case the list is filled to the brim with 'local' txs, we do this - // little check to avoid unpacking / repacking the heap later on, which - // is very expensive - discardable := 0 - for _, tx := range *l.items { - if !local.containsTx(tx) { - discardable++ - } - if discardable >= slots { - break - } - } - if slots > discardable { - slots = discardable - } - } - if slots == 0 { - return nil - } - drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop - save := make(types.Transactions, 0, len(*l.items)-slots) // Local underpriced transactions to keep - - for len(*l.items) > 0 && slots > 0 { +// +// Note local transaction won't be considered for eviction. +func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) { + drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop + for len(*l.remotes) > 0 && slots > 0 { // Discard stale transactions if found during cleanup - tx := heap.Pop(l.items).(*types.Transaction) - if l.all.Get(tx.Hash()) == nil { + tx := heap.Pop(l.remotes).(*types.Transaction) + if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated l.stales-- continue } - // Non stale transaction found, discard unless local - if local.containsTx(tx) { - save = append(save, tx) - } else { - drop = append(drop, tx) - slots -= numSlots(tx) + // Non stale transaction found, discard it + drop = append(drop, tx) + slots -= numSlots(tx) + } + // If we still can't make enough room for the new transaction + if slots > 0 && !force { + for _, tx := range drop { + heap.Push(l.remotes, tx) } + return nil, false } - for _, tx := range save { - heap.Push(l.items, tx) - } - return drop + return drop, true +} + +// Reheap forcibly rebuilds the heap based on the current remote transaction set. +func (l *txPricedList) Reheap() { + reheap := make(priceHeap, 0, l.all.RemoteCount()) + + l.stales, l.remotes = 0, &reheap + l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { + *l.remotes = append(*l.remotes, tx) + return true + }, false, true) // Only iterate remotes + heap.Init(l.remotes) } diff --git a/core/tx_pool.go b/core/tx_pool.go index e3ffe103cf..4a17c31ca8 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -63,6 +63,10 @@ var ( // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") + // ErrTxPoolOverflow is returned if the transaction pool is full and can't accpet + // another remote transaction. + ErrTxPoolOverflow = errors.New("txpool is full") + // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced // with a different one without the required price bump. ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") @@ -105,6 +109,7 @@ var ( validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) + overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) @@ -421,7 +426,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { defer pool.mu.Unlock() pool.gasPrice = price - for _, tx := range pool.priced.Cap(price, pool.locals) { + for _, tx := range pool.priced.Cap(price) { pool.removeTx(tx.Hash(), false) } log.Info("Transaction pool price threshold updated", "price", price) @@ -536,7 +541,6 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { return ErrInvalidSender } // Drop non-local transactions under our own minimal accepted gas price - local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { return ErrUnderpriced } @@ -575,22 +579,36 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e knownTxMeter.Mark(1) return false, ErrAlreadyKnown } + // Make the local flag. If it's from local source or it's from the network but + // the sender is marked as local previously, treat it as the local transaction. + isLocal := local || pool.locals.containsTx(tx) + // If the transaction fails basic validation, discard it - if err := pool.validateTx(tx, local); err != nil { + if err := pool.validateTx(tx, isLocal); err != nil { log.Trace("Discarding invalid transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) return false, err } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue { + if uint64(pool.all.Count()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it - if !local && pool.priced.Underpriced(tx, pool.locals) { + if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) underpricedTxMeter.Mark(1) return false, ErrUnderpriced } - // New transaction is better than our worse ones, make room for it - drop := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), pool.locals) + // New transaction is better than our worse ones, make room for it. + // If it's a local transaction, forcibly discard all available transactions. + // Otherwise if we can't make enough room for new one, abort the operation. + drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) + + // Special case, we still can't make the room for the new remote one. + if !isLocal && !success { + log.Trace("Discarding overflown transaction", "hash", hash) + overflowedTxMeter.Mark(1) + return false, ErrTxPoolOverflow + } + // Kick out the underpriced remote transactions. for _, tx := range drop { log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) underpricedTxMeter.Mark(1) @@ -612,8 +630,8 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e pool.priced.Removed(1) pendingReplaceMeter.Mark(1) } - pool.all.Add(tx) - pool.priced.Put(tx) + pool.all.Add(tx, isLocal) + pool.priced.Put(tx, isLocal) pool.journalTx(from, tx) pool.queueTxEvent(tx) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) @@ -623,18 +641,17 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e return old != nil, nil } // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx) + replaced, err = pool.enqueueTx(hash, tx, isLocal, true) if err != nil { return false, err } // Mark local addresses and journal local transactions - if local { - if !pool.locals.contains(from) { - log.Info("Setting new local account", "address", from) - pool.locals.add(from) - } + if local && !pool.locals.contains(from) { + log.Info("Setting new local account", "address", from) + pool.locals.add(from) + pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time. } - if local || pool.locals.contains(from) { + if isLocal { localGauge.Inc(1) } pool.journalTx(from, tx) @@ -646,7 +663,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // enqueueTx inserts a new transaction into the non-executable transaction queue. // // Note, this method assumes the pool lock is held! -func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) { +func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { // Try to insert the transaction into the future queue from, _ := types.Sender(pool.signer, tx) // already validated if pool.queue[from] == nil { @@ -667,9 +684,14 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er // Nothing was replaced, bump the queued counter queuedGauge.Inc(1) } - if pool.all.Get(hash) == nil { - pool.all.Add(tx) - pool.priced.Put(tx) + // If the transaction isn't in lookup set but it's expected to be there, + // show the error log. + if pool.all.Get(hash) == nil && !addAll { + log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) + } + if addAll { + pool.all.Add(tx, local) + pool.priced.Put(tx, local) } // If we never record the heartbeat, do it right now. if _, exist := pool.beats[from]; !exist { @@ -718,11 +740,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T // Nothing was replaced, bump the pending counter pendingGauge.Inc(1) } - // Failsafe to work around direct pending inserts (tests) - if pool.all.Get(hash) == nil { - pool.all.Add(tx) - pool.priced.Put(tx) - } // Set the potentially new pending nonce and notify any subsystems of the new tx pool.pendingNonces.set(addr, tx.Nonce()+1) @@ -904,7 +921,8 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { } // Postpone any invalidated transactions for _, tx := range invalids { - pool.enqueueTx(tx.Hash(), tx) + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(tx.Hash(), tx, false, false) } // Update the account nonce if needed pool.pendingNonces.setIfLower(addr, tx.Nonce()) @@ -1408,7 +1426,9 @@ func (pool *TxPool) demoteUnexecutables() { for _, tx := range invalids { hash := tx.Hash() log.Trace("Demoting pending transaction", "hash", hash) - pool.enqueueTx(hash, tx) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) } pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) if pool.locals.contains(addr) { @@ -1420,7 +1440,9 @@ func (pool *TxPool) demoteUnexecutables() { for _, tx := range gapped { hash := tx.Hash() log.Error("Demoting invalidated transaction", "hash", hash) - pool.enqueueTx(hash, tx) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) } pendingGauge.Dec(int64(len(gapped))) // This might happen in a reorg, so log it to the metering @@ -1519,8 +1541,8 @@ func (as *accountSet) merge(other *accountSet) { as.cache = nil } -// txLookup is used internally by TxPool to track transactions while allowing lookup without -// mutex contention. +// txLookup is used internally by TxPool to track transactions while allowing +// lookup without mutex contention. // // Note, although this type is properly protected against concurrent access, it // is **not** a type that should ever be mutated or even exposed outside of the @@ -1528,27 +1550,43 @@ func (as *accountSet) merge(other *accountSet) { // internal mechanisms. The sole purpose of the type is to permit out-of-bound // peeking into the pool in TxPool.Get without having to acquire the widely scoped // TxPool.mu mutex. +// +// This lookup set combines the notion of "local transactions", which is useful +// to build upper-level structure. type txLookup struct { - all map[common.Hash]*types.Transaction - slots int - lock sync.RWMutex + slots int + lock sync.RWMutex + locals map[common.Hash]*types.Transaction + remotes map[common.Hash]*types.Transaction } // newTxLookup returns a new txLookup structure. func newTxLookup() *txLookup { return &txLookup{ - all: make(map[common.Hash]*types.Transaction), + locals: make(map[common.Hash]*types.Transaction), + remotes: make(map[common.Hash]*types.Transaction), } } -// Range calls f on each key and value present in the map. -func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) { +// Range calls f on each key and value present in the map. The callback passed +// should return the indicator whether the iteration needs to be continued. +// Callers need to specify which set (or both) to be iterated. +func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { t.lock.RLock() defer t.lock.RUnlock() - for key, value := range t.all { - if !f(key, value) { - break + if local { + for key, value := range t.locals { + if !f(key, value, true) { + return + } + } + } + if remote { + for key, value := range t.remotes { + if !f(key, value, false) { + return + } } } } @@ -1558,15 +1596,50 @@ func (t *txLookup) Get(hash common.Hash) *types.Transaction { t.lock.RLock() defer t.lock.RUnlock() - return t.all[hash] + if tx := t.locals[hash]; tx != nil { + return tx + } + return t.remotes[hash] } -// Count returns the current number of items in the lookup. +// GetLocal returns a transaction if it exists in the lookup, or nil if not found. +func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.locals[hash] +} + +// GetRemote returns a transaction if it exists in the lookup, or nil if not found. +func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.remotes[hash] +} + +// Count returns the current number of transactions in the lookup. func (t *txLookup) Count() int { t.lock.RLock() defer t.lock.RUnlock() - return len(t.all) + return len(t.locals) + len(t.remotes) +} + +// LocalCount returns the current number of local transactions in the lookup. +func (t *txLookup) LocalCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.locals) +} + +// RemoteCount returns the current number of remote transactions in the lookup. +func (t *txLookup) RemoteCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.remotes) } // Slots returns the current number of slots used in the lookup. @@ -1578,14 +1651,18 @@ func (t *txLookup) Slots() int { } // Add adds a transaction to the lookup. -func (t *txLookup) Add(tx *types.Transaction) { +func (t *txLookup) Add(tx *types.Transaction, local bool) { t.lock.Lock() defer t.lock.Unlock() t.slots += numSlots(tx) slotsGauge.Update(int64(t.slots)) - t.all[tx.Hash()] = tx + if local { + t.locals[tx.Hash()] = tx + } else { + t.remotes[tx.Hash()] = tx + } } // Remove removes a transaction from the lookup. @@ -1593,10 +1670,36 @@ func (t *txLookup) Remove(hash common.Hash) { t.lock.Lock() defer t.lock.Unlock() - t.slots -= numSlots(t.all[hash]) + tx, ok := t.locals[hash] + if !ok { + tx, ok = t.remotes[hash] + } + if !ok { + log.Error("No transaction found to be deleted", "hash", hash) + return + } + t.slots -= numSlots(tx) slotsGauge.Update(int64(t.slots)) - delete(t.all, hash) + delete(t.locals, hash) + delete(t.remotes, hash) +} + +// RemoteToLocals migrates the transactions belongs to the given locals to locals +// set. The assumption is held the locals set is thread-safe to be used. +func (t *txLookup) RemoteToLocals(locals *accountSet) int { + t.lock.Lock() + defer t.lock.Unlock() + + var migrated int + for hash, tx := range t.remotes { + if locals.containsTx(tx) { + t.locals[hash] = tx + delete(t.remotes, hash) + migrated += 1 + } + } + return migrated } // numSlots calculates the number of slots needed for a single transaction. diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 246b3977d3..47d3830b06 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -107,10 +107,11 @@ func validateTxPoolInternals(pool *TxPool) error { if total := pool.all.Count(); total != pending+queued { return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued) } - if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued { - return fmt.Errorf("total priced transaction count %d != %d pending + %d queued", priced, pending, queued) + pool.priced.Reheap() + priced, remote := pool.priced.remotes.Len(), pool.all.RemoteCount() + if priced != remote { + return fmt.Errorf("total priced transaction count %d != %d", priced, remote) } - // Ensure the next nonce to assign is the correct one for addr, txs := range pool.pending { // Find the last transaction @@ -280,7 +281,7 @@ func TestTransactionQueue(t *testing.T) { pool.currentState.AddBalance(from, big.NewInt(1000)) <-pool.requestReset(nil, nil) - pool.enqueueTx(tx.Hash(), tx) + pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if len(pool.pending) != 1 { t.Error("expected valid txs to be 1 is", len(pool.pending)) @@ -289,7 +290,7 @@ func TestTransactionQueue(t *testing.T) { tx = transaction(1, 100, key) from, _ = deriveSender(tx) pool.currentState.SetNonce(from, 2) - pool.enqueueTx(tx.Hash(), tx) + pool.enqueueTx(tx.Hash(), tx, false, true) <-pool.requestPromoteExecutables(newAccountSet(pool.signer, from)) if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { @@ -313,9 +314,9 @@ func TestTransactionQueue2(t *testing.T) { pool.currentState.AddBalance(from, big.NewInt(1000)) pool.reset(nil, nil) - pool.enqueueTx(tx1.Hash(), tx1) - pool.enqueueTx(tx2.Hash(), tx2) - pool.enqueueTx(tx3.Hash(), tx3) + pool.enqueueTx(tx1.Hash(), tx1, false, true) + pool.enqueueTx(tx2.Hash(), tx2, false, true) + pool.enqueueTx(tx3.Hash(), tx3, false, true) pool.promoteExecutables([]common.Address{from}) if len(pool.pending) != 1 { @@ -488,12 +489,21 @@ func TestTransactionDropping(t *testing.T) { tx11 = transaction(11, 200, key) tx12 = transaction(12, 300, key) ) + pool.all.Add(tx0, false) + pool.priced.Put(tx0, false) pool.promoteTx(account, tx0.Hash(), tx0) + + pool.all.Add(tx1, false) + pool.priced.Put(tx1, false) pool.promoteTx(account, tx1.Hash(), tx1) + + pool.all.Add(tx2, false) + pool.priced.Put(tx2, false) pool.promoteTx(account, tx2.Hash(), tx2) - pool.enqueueTx(tx10.Hash(), tx10) - pool.enqueueTx(tx11.Hash(), tx11) - pool.enqueueTx(tx12.Hash(), tx12) + + pool.enqueueTx(tx10.Hash(), tx10, false, true) + pool.enqueueTx(tx11.Hash(), tx11, false, true) + pool.enqueueTx(tx12.Hash(), tx12, false, true) // Check that pre and post validations leave the pool as is if pool.pending[account].Len() != 3 { @@ -1964,7 +1974,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) { for i := 0; i < size; i++ { tx := transaction(uint64(1+i), 100000, key) - pool.enqueueTx(tx.Hash(), tx) + pool.enqueueTx(tx.Hash(), tx, false, true) } // Benchmark the speed of pool validation b.ResetTimer() @@ -2007,3 +2017,38 @@ func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { } } } + +func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { + // Allocate keys for testing + key, _ := crypto.GenerateKey() + account := crypto.PubkeyToAddress(key.PublicKey) + + remoteKey, _ := crypto.GenerateKey() + remoteAddr := crypto.PubkeyToAddress(remoteKey.PublicKey) + + locals := make([]*types.Transaction, 4096+1024) // Occupy all slots + for i := 0; i < len(locals); i++ { + locals[i] = transaction(uint64(i), 100000, key) + } + remotes := make([]*types.Transaction, 1000) + for i := 0; i < len(remotes); i++ { + remotes[i] = pricedTransaction(uint64(i), 100000, big.NewInt(2), remoteKey) // Higher gasprice + } + // Benchmark importing the transactions into the queue + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + pool, _ := setupTxPool() + pool.currentState.AddBalance(account, big.NewInt(100000000)) + for _, local := range locals { + pool.AddLocal(local) + } + b.StartTimer() + // Assign a high enough balance for testing + pool.currentState.AddBalance(remoteAddr, big.NewInt(100000000)) + for i := 0; i < len(remotes); i++ { + pool.AddRemotes([]*types.Transaction{remotes[i]}) + } + pool.Stop() + } +} From efe6dd29042b36d543420a422fc21d123f1e67e3 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 11 Dec 2020 11:06:44 +0100 Subject: [PATCH 194/948] consensus/ethash: implement faster difficulty calculators (#21976) This PR adds re-written difficulty calculators, which are based on uint256. It also adds a fuzzer + oss-fuzz integration for the new fuzzer. It does differential fuzzing between the new and old calculators. Note: this PR does not actually enable the new calculators. --- consensus/ethash/consensus.go | 5 + consensus/ethash/consensus_test.go | 102 +++++++++++ consensus/ethash/difficulty.go | 193 ++++++++++++++++++++ oss-fuzz.sh | 1 + tests/fuzzers/difficulty/debug/main.go | 23 +++ tests/fuzzers/difficulty/difficulty-fuzz.go | 145 +++++++++++++++ 6 files changed, 469 insertions(+) create mode 100644 consensus/ethash/difficulty.go create mode 100644 tests/fuzzers/difficulty/debug/main.go create mode 100644 tests/fuzzers/difficulty/difficulty-fuzz.go diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index bdc02098af..8e401af7ca 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -485,6 +485,11 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { return diff } +// Exported for fuzzing +var FrontierDifficultyCalulator = calcDifficultyFrontier +var HomesteadDifficultyCalulator = calcDifficultyHomestead +var DynamicDifficultyCalculator = makeDifficultyCalculator + // VerifySeal implements consensus.Engine, checking whether the given block satisfies // the PoW difficulty requirements. func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go index 675737d9e1..6f6dc79fd8 100644 --- a/consensus/ethash/consensus_test.go +++ b/consensus/ethash/consensus_test.go @@ -17,12 +17,15 @@ package ethash import ( + "encoding/binary" "encoding/json" "math/big" + "math/rand" "os" "path/filepath" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -84,3 +87,102 @@ func TestCalcDifficulty(t *testing.T) { } } } + +func randSlice(min, max uint32) []byte { + var b = make([]byte, 4) + rand.Read(b) + a := binary.LittleEndian.Uint32(b) + size := min + a%(max-min) + out := make([]byte, size) + rand.Read(out) + return out +} + +func TestDifficultyCalculators(t *testing.T) { + rand.Seed(2) + for i := 0; i < 5000; i++ { + // 1 to 300 seconds diff + var timeDelta = uint64(1 + rand.Uint32()%3000) + diffBig := big.NewInt(0).SetBytes(randSlice(2, 10)) + if diffBig.Cmp(params.MinimumDifficulty) < 0 { + diffBig.Set(params.MinimumDifficulty) + } + //rand.Read(difficulty) + header := &types.Header{ + Difficulty: diffBig, + Number: new(big.Int).SetUint64(rand.Uint64() % 50_000_000), + Time: rand.Uint64() - timeDelta, + } + if rand.Uint32()&1 == 0 { + header.UncleHash = types.EmptyUncleHash + } + bombDelay := new(big.Int).SetUint64(rand.Uint64() % 50_000_000) + for i, pair := range []struct { + bigFn func(time uint64, parent *types.Header) *big.Int + u256Fn func(time uint64, parent *types.Header) *big.Int + }{ + {FrontierDifficultyCalulator, CalcDifficultyFrontierU256}, + {HomesteadDifficultyCalulator, CalcDifficultyHomesteadU256}, + {DynamicDifficultyCalculator(bombDelay), MakeDifficultyCalculatorU256(bombDelay)}, + } { + time := header.Time + timeDelta + want := pair.bigFn(time, header) + have := pair.u256Fn(time, header) + if want.BitLen() > 256 { + continue + } + if want.Cmp(have) != 0 { + t.Fatalf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have, + header.Number, header.Time, time, bombDelay) + } + } + } +} + +func BenchmarkDifficultyCalculator(b *testing.B) { + x1 := makeDifficultyCalculator(big.NewInt(1000000)) + x2 := MakeDifficultyCalculatorU256(big.NewInt(1000000)) + h := &types.Header{ + ParentHash: common.Hash{}, + UncleHash: types.EmptyUncleHash, + Difficulty: big.NewInt(0xffffff), + Number: big.NewInt(500000), + Time: 1000000, + } + b.Run("big-frontier", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + calcDifficultyFrontier(1000014, h) + } + }) + b.Run("u256-frontier", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + CalcDifficultyFrontierU256(1000014, h) + } + }) + b.Run("big-homestead", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + calcDifficultyHomestead(1000014, h) + } + }) + b.Run("u256-homestead", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + CalcDifficultyHomesteadU256(1000014, h) + } + }) + b.Run("big-generic", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + x1(1000014, h) + } + }) + b.Run("u256-generic", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + x2(1000014, h) + } + }) +} diff --git a/consensus/ethash/difficulty.go b/consensus/ethash/difficulty.go new file mode 100644 index 0000000000..59c4ac7419 --- /dev/null +++ b/consensus/ethash/difficulty.go @@ -0,0 +1,193 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethash + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +const ( + // frontierDurationLimit is for Frontier: + // The decision boundary on the blocktime duration used to determine + // whether difficulty should go up or down. + frontierDurationLimit = 13 + // minimumDifficulty The minimum that the difficulty may ever be. + minimumDifficulty = 131072 + // expDiffPeriod is the exponential difficulty period + expDiffPeriodUint = 100000 + // difficultyBoundDivisorBitShift is the bound divisor of the difficulty (2048), + // This constant is the right-shifts to use for the division. + difficultyBoundDivisor = 11 +) + +// CalcDifficultyFrontierU256 is the difficulty adjustment algorithm. It returns the +// difficulty that a new block should have when created at time given the parent +// block's time and difficulty. The calculation uses the Frontier rules. +func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { + /* + Algorithm + block_diff = pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + int(2^((num // 100000) - 2)) + + Where: + - pdiff = parent.difficulty + - ptime = parent.time + - time = block.timestamp + - num = block.number + */ + + pDiff := uint256.NewInt() + pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff + adjust := pDiff.Clone() + adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 + + if time-parent.Time < frontierDurationLimit { + pDiff.Add(pDiff, adjust) + } else { + pDiff.Sub(pDiff, adjust) + } + if pDiff.LtUint64(minimumDifficulty) { + pDiff.SetUint64(minimumDifficulty) + } + // 'pdiff' now contains: + // pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + + if periodCount := (parent.Number.Uint64() + 1) / expDiffPeriodUint; periodCount > 1 { + // diff = diff + 2^(periodCount - 2) + expDiff := adjust.SetOne() + expDiff.Lsh(expDiff, uint(periodCount-2)) // expdiff: 2 ^ (periodCount -2) + pDiff.Add(pDiff, expDiff) + } + return pDiff.ToBig() +} + +// CalcDifficultyHomesteadU256 is the difficulty adjustment algorithm. It returns +// the difficulty that a new block should have when created at time given the +// parent block's time and difficulty. The calculation uses the Homestead rules. +func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { + /* + https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md + Algorithm: + block_diff = pdiff + pdiff / 2048 * max(1 - (time - ptime) / 10, -99) + 2 ^ int((num / 100000) - 2)) + + Our modification, to use unsigned ints: + block_diff = pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + 2 ^ int((num / 100000) - 2)) + + Where: + - pdiff = parent.difficulty + - ptime = parent.time + - time = block.timestamp + - num = block.number + */ + + pDiff := uint256.NewInt() + pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff + adjust := pDiff.Clone() + adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 + + x := (time - parent.Time) / 10 // (time - ptime) / 10) + var neg = true + if x == 0 { + x = 1 + neg = false + } else if x >= 100 { + x = 99 + } else { + x = x - 1 + } + z := new(uint256.Int).SetUint64(x) + adjust.Mul(adjust, z) // adjust: (pdiff / 2048) * max((time - ptime) / 10 - 1, 99) + if neg { + pDiff.Sub(pDiff, adjust) // pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + } else { + pDiff.Add(pDiff, adjust) // pdiff + pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + } + if pDiff.LtUint64(minimumDifficulty) { + pDiff.SetUint64(minimumDifficulty) + } + // for the exponential factor, a.k.a "the bomb" + // diff = diff + 2^(periodCount - 2) + if periodCount := (1 + parent.Number.Uint64()) / expDiffPeriodUint; periodCount > 1 { + expFactor := adjust.Lsh(adjust.SetOne(), uint(periodCount-2)) + pDiff.Add(pDiff, expFactor) + } + return pDiff.ToBig() +} + +// MakeDifficultyCalculatorU256 creates a difficultyCalculator with the given bomb-delay. +// the difficulty is calculated with Byzantium rules, which differs from Homestead in +// how uncles affect the calculation +func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int { + // Note, the calculations below looks at the parent number, which is 1 below + // the block number. Thus we remove one from the delay given + bombDelayFromParent := bombDelay.Uint64() - 1 + return func(time uint64, parent *types.Header) *big.Int { + /* + https://github.com/ethereum/EIPs/issues/100 + pDiff = parent.difficulty + BLOCK_DIFF_FACTOR = 9 + a = pDiff + (pDiff // BLOCK_DIFF_FACTOR) * adj_factor + b = min(parent.difficulty, MIN_DIFF) + child_diff = max(a,b ) + */ + x := (time - parent.Time) / 9 // (block_timestamp - parent_timestamp) // 9 + c := uint64(1) // if parent.unclehash == emptyUncleHashHash + if parent.UncleHash != types.EmptyUncleHash { + c = 2 + } + xNeg := x >= c + if xNeg { + // x is now _negative_ adjustment factor + x = x - c // - ( (t-p)/p -( 2 or 1) ) + } else { + x = c - x // (2 or 1) - (t-p)/9 + } + if x > 99 { + x = 99 // max(x, 99) + } + // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) + y := new(uint256.Int) + y.SetFromBig(parent.Difficulty) // y: p_diff + pDiff := y.Clone() // pdiff: p_diff + z := new(uint256.Int).SetUint64(x) //z : +-adj_factor (either pos or negative) + y.Rsh(y, difficultyBoundDivisor) // y: p__diff / 2048 + z.Mul(y, z) // z: (p_diff / 2048 ) * (+- adj_factor) + + if xNeg { + y.Sub(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor + } else { + y.Add(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor + } + // minimum difficulty can ever be (before exponential factor) + if y.LtUint64(minimumDifficulty) { + y.SetUint64(minimumDifficulty) + } + // calculate a fake block number for the ice-age delay + // Specification: https://eips.ethereum.org/EIPS/eip-1234 + var pNum = parent.Number.Uint64() + if pNum >= bombDelayFromParent { + if fakeBlockNumber := pNum - bombDelayFromParent; fakeBlockNumber >= 2*expDiffPeriodUint { + z.SetOne() + z.Lsh(z, uint(fakeBlockNumber/expDiffPeriodUint-2)) + y.Add(z, y) + } + } + return y.ToBig() + } +} diff --git a/oss-fuzz.sh b/oss-fuzz.sh index e0a293a6d6..e060ea88e1 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -57,6 +57,7 @@ compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie +compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul diff --git a/tests/fuzzers/difficulty/debug/main.go b/tests/fuzzers/difficulty/debug/main.go new file mode 100644 index 0000000000..23516b3a0d --- /dev/null +++ b/tests/fuzzers/difficulty/debug/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/difficulty" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug ") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + difficulty.Fuzz(data) +} diff --git a/tests/fuzzers/difficulty/difficulty-fuzz.go b/tests/fuzzers/difficulty/difficulty-fuzz.go new file mode 100644 index 0000000000..e4c5dcf57c --- /dev/null +++ b/tests/fuzzers/difficulty/difficulty-fuzz.go @@ -0,0 +1,145 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package difficulty + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/types" +) + +type fuzzer struct { + input io.Reader + exhausted bool + debugging bool +} + +func (f *fuzzer) read(size int) []byte { + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) readSlice(min, max int) []byte { + var a uint16 + binary.Read(f.input, binary.LittleEndian, &a) + size := min + int(a)%(max-min) + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) readUint64(min, max uint64) uint64 { + if min == max { + return min + } + var a uint64 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + a = min + a%(max-min) + return a +} +func (f *fuzzer) readBool() bool { + return f.read(1)[0]&0x1 == 0 +} + +// The function must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise +// other values are reserved for future use. +func Fuzz(data []byte) int { + f := fuzzer{ + input: bytes.NewReader(data), + exhausted: false, + } + return f.fuzz() +} + +var minDifficulty = big.NewInt(0x2000) + +type calculator func(time uint64, parent *types.Header) *big.Int + +func (f *fuzzer) fuzz() int { + // A parent header + header := &types.Header{} + if f.readBool() { + header.UncleHash = types.EmptyUncleHash + } + // Difficulty can range between 0x2000 (2 bytes) and up to 32 bytes + { + diff := new(big.Int).SetBytes(f.readSlice(2, 32)) + if diff.Cmp(minDifficulty) < 0 { + diff.Set(minDifficulty) + } + header.Difficulty = diff + } + // Number can range between 0 and up to 32 bytes (but not so that the child exceeds it) + { + // However, if we use astronomic numbers, then the bomb exp karatsuba calculation + // in the legacy methods) + // times out, so we limit it to fit within reasonable bounds + number := new(big.Int).SetBytes(f.readSlice(0, 4)) // 4 bytes: 32 bits: block num max 4 billion + header.Number = number + } + // Both parent and child time must fit within uint64 + var time uint64 + { + childTime := f.readUint64(1, 0xFFFFFFFFFFFFFFFF) + //fmt.Printf("childTime: %x\n",childTime) + delta := f.readUint64(1, childTime) + //fmt.Printf("delta: %v\n", delta) + pTime := childTime - delta + header.Time = pTime + time = childTime + } + // Bomb delay will never exceed uint64 + bombDelay := new(big.Int).SetUint64(f.readUint64(1, 0xFFFFFFFFFFFFFFFe)) + + if f.exhausted { + return 0 + } + + for i, pair := range []struct { + bigFn calculator + u256Fn calculator + }{ + {ethash.FrontierDifficultyCalulator, ethash.CalcDifficultyFrontierU256}, + {ethash.HomesteadDifficultyCalulator, ethash.CalcDifficultyHomesteadU256}, + {ethash.DynamicDifficultyCalculator(bombDelay), ethash.MakeDifficultyCalculatorU256(bombDelay)}, + } { + want := pair.bigFn(time, header) + have := pair.u256Fn(time, header) + if want.Cmp(have) != 0 { + panic(fmt.Sprintf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have, + header.Number, header.Time, time, bombDelay)) + } + } + return 1 +} From c49aae987040b3c0b846c5acb006fdba1eae282b Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Fri, 11 Dec 2020 16:49:44 +0200 Subject: [PATCH 195/948] consensus: refactor FinalizeAndAssemble to use Finalize (#21993) --- consensus/clique/clique.go | 5 ++--- consensus/ethash/consensus.go | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c05f84cc2e..6c667804d8 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -561,9 +561,8 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // nor block rewards given, and returns the final block. func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - // No block rewards in PoA, so the state remains as is and uncles are dropped - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - header.UncleHash = types.CalcUncleHash(nil) + // Finalize block + c.Finalize(chain, header, state, txs, uncles) // Assemble and return the final block for sealing return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 8e401af7ca..ae0905ee3a 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -584,9 +584,8 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, accumulating the block and // uncle rewards, setting the final state and assembling the block. func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - // Accumulate any block and uncle rewards and commit the final state root - accumulateRewards(chain.Config(), state, header, uncles) - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + // Finalize block + ethash.Finalize(chain, header, state, txs, uncles) // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil From 4d48980e74e7925d40fb89683eac0b43f3540d77 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 11 Dec 2020 15:56:00 +0100 Subject: [PATCH 196/948] core, eth, les: implement unclean-shutdown marker (#21893) This PR implements unclean shutdown marker. Every time geth boots, it adds a timestamp to a list of timestamps in the database. This list is capped at 10. At a clean shutdown, the timestamp is removed again. Thus, when geth exits unclean, the marker remains, and at boot up we show the most recent unclean shutdowns to the user, which makes it easier to diagnose root-causes to certain problems. Co-authored-by: Nagy Salem --- core/rawdb/accessors_metadata.go | 63 +++++++++++++++++++++++++++++++- core/rawdb/database.go | 2 +- core/rawdb/schema.go | 6 ++- eth/backend.go | 15 ++++++++ les/client.go | 14 +++++++ 5 files changed, 95 insertions(+), 5 deletions(-) diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 14a302a127..079e335fa6 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -18,6 +18,7 @@ package rawdb import ( "encoding/json" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -30,7 +31,7 @@ import ( func ReadDatabaseVersion(db ethdb.KeyValueReader) *uint64 { var version uint64 - enc, _ := db.Get(databaseVerisionKey) + enc, _ := db.Get(databaseVersionKey) if len(enc) == 0 { return nil } @@ -47,7 +48,7 @@ func WriteDatabaseVersion(db ethdb.KeyValueWriter, version uint64) { if err != nil { log.Crit("Failed to encode database version", "err", err) } - if err = db.Put(databaseVerisionKey, enc); err != nil { + if err = db.Put(databaseVersionKey, enc); err != nil { log.Crit("Failed to store the database version", "err", err) } } @@ -79,3 +80,61 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha log.Crit("Failed to store chain config", "err", err) } } + +// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the +// database +type crashList struct { + Discarded uint64 // how many ucs have we deleted + Recent []uint64 // unix timestamps of 10 latest unclean shutdowns +} + +const crashesToKeep = 10 + +// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns +// the previous data +// - a list of timestamps +// - a count of how many old unclean-shutdowns have been discarded +func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) { + var uncleanShutdowns crashList + // Read old data + if data, err := db.Get(uncleanShutdownKey); err != nil { + log.Warn("Error reading unclean shutdown markers", "error", err) + } else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { + return nil, 0, err + } + var discarded = uncleanShutdowns.Discarded + var previous = make([]uint64, len(uncleanShutdowns.Recent)) + copy(previous, uncleanShutdowns.Recent) + // Add a new (but cap it) + uncleanShutdowns.Recent = append(uncleanShutdowns.Recent, uint64(time.Now().Unix())) + if count := len(uncleanShutdowns.Recent); count > crashesToKeep+1 { + numDel := count - (crashesToKeep + 1) + uncleanShutdowns.Recent = uncleanShutdowns.Recent[numDel:] + uncleanShutdowns.Discarded += uint64(numDel) + } + // And save it again + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to write unclean-shutdown marker", "err", err) + return nil, 0, err + } + return previous, discarded, nil +} + +// PopUncleanShutdownMarker removes the last unclean shutdown marker +func PopUncleanShutdownMarker(db ethdb.KeyValueStore) { + var uncleanShutdowns crashList + // Read old data + if data, err := db.Get(uncleanShutdownKey); err != nil { + log.Warn("Error reading unclean shutdown markers", "error", err) + } else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { + log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen + } + if l := len(uncleanShutdowns.Recent); l > 0 { + uncleanShutdowns.Recent = uncleanShutdowns.Recent[:l-1] + } + data, _ := rlp.EncodeToBytes(uncleanShutdowns) + if err := db.Put(uncleanShutdownKey, data); err != nil { + log.Warn("Failed to clear unclean-shutdown marker", "err", err) + } +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b1ac3e9587..b01a31ebcd 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVerisionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey} { + for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dbc5025d5d..cff27b4bb0 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -27,8 +27,8 @@ import ( // The fields below define the low level database schema prefixing. var ( - // databaseVerisionKey tracks the current database version. - databaseVerisionKey = []byte("DatabaseVersion") + // databaseVersionKey tracks the current database version. + databaseVersionKey = []byte("DatabaseVersion") // headHeaderKey tracks the latest known header's hash. headHeaderKey = []byte("LastHeader") @@ -81,6 +81,8 @@ var ( preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db + uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db + // Chain index prefixes (use `i` + single byte to avoid mixing data types). BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress diff --git a/eth/backend.go b/eth/backend.go index 03b0b319b7..bb4275b92c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -24,6 +24,7 @@ import ( "runtime" "sync" "sync/atomic" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -220,6 +221,19 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { stack.RegisterAPIs(eth.APIs()) stack.RegisterProtocols(eth.Protocols()) stack.RegisterLifecycle(eth) + // Check for unclean shutdown + if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { + log.Error("Could not update unclean-shutdown-marker list", "error", err) + } else { + if discards > 0 { + log.Warn("Old unclean shutdowns found", "count", discards) + } + for _, tstamp := range uncleanShutdowns { + t := time.Unix(int64(tstamp), 0) + log.Warn("Unclean shutdown detected", "booted", t, + "age", common.PrettyAge(t)) + } + } return eth, nil } @@ -543,6 +557,7 @@ func (s *Ethereum) Stop() error { s.miner.Stop() s.blockchain.Stop() s.engine.Close() + rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.eventMux.Stop() return nil diff --git a/les/client.go b/les/client.go index 37250d076f..47997a098b 100644 --- a/les/client.go +++ b/les/client.go @@ -178,6 +178,19 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { stack.RegisterProtocols(leth.Protocols()) stack.RegisterLifecycle(leth) + // Check for unclean shutdown + if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil { + log.Error("Could not update unclean-shutdown-marker list", "error", err) + } else { + if discards > 0 { + log.Warn("Old unclean shutdowns found", "count", discards) + } + for _, tstamp := range uncleanShutdowns { + t := time.Unix(int64(tstamp), 0) + log.Warn("Unclean shutdown detected", "booted", t, + "age", common.PrettyAge(t)) + } + } return leth, nil } @@ -313,6 +326,7 @@ func (s *LightEthereum) Stop() error { s.engine.Close() s.pruner.close() s.eventMux.Stop() + rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.wg.Wait() log.Info("Light ethereum stopped") From 38c1d592b7121f26dc661c1bc2bf0e32eba9d888 Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Sat, 12 Dec 2020 04:16:34 -0500 Subject: [PATCH 197/948] abi/bind: fix error-handling in generated wrappers for functions returning structs (#22005) Fixes the template used when generating code, which in some scenarios would lead to panic instead of returning an error. --- accounts/abi/bind/bind_test.go | 39 ++++++++++++++++++++++++++++++++++ accounts/abi/bind/template.go | 3 +++ 2 files changed, 42 insertions(+) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 1a8a17e45e..4a504516bb 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -569,6 +569,45 @@ var bindTests = []struct { nil, nil, }, + { + `NonExistentStruct`, + ` + contract NonExistentStruct { + function Struct() public view returns(uint256 a, uint256 b) { + return (10, 10); + } + } + `, + []string{`6080604052348015600f57600080fd5b5060888061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d5f6622514602d575b600080fd5b6033604c565b6040805192835260208301919091528051918290030190f35b600a809156fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033`}, + []string{`[{"inputs":[],"name":"Struct","outputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"stateMutability":"pure","type":"function"}]`}, + ` + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, + ` + // Create a simulator and wrap a non-deployed contract + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000)) + defer sim.Close() + + nonexistent, err := NewNonExistentStruct(common.Address{}, sim) + if err != nil { + t.Fatalf("Failed to access non-existent contract: %v", err) + } + // Ensure that contract calls fail with the appropriate error + if res, err := nonexistent.Struct(nil); err == nil { + t.Fatalf("Call succeeded on non-existent contract: %v", res) + } else if (err != bind.ErrNoCode) { + t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) + } + `, + nil, + nil, + nil, + nil, + }, // Tests that gas estimation works for contracts with weird gas mechanics too. { `FunkyGasPattern`, diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 8dac11f79f..351eabd258 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -304,6 +304,9 @@ var ( err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) {{if .Structured}} outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } {{range $i, $t := .Normalized.Outputs}} outstruct.{{.Name}} = out[{{$i}}].({{bindtype .Type $structs}}){{end}} From 00d10e610f9fef56b5ee9c27f7fe7c842eba2e9b Mon Sep 17 00:00:00 2001 From: Shiming Date: Sun, 13 Dec 2020 00:36:32 +0800 Subject: [PATCH 198/948] cmd/abigen: clarify abigen alias flag usage (#21875) * doc: clarify abigen alias flag usage update the `abigen --alias` flag help info, give an example to make it more clear related issue: https://github.com/ethereum/go-ethereum/issues/21846 * Update cmd/abigen/main.go Co-authored-by: ligi Co-authored-by: Martin Holst Swende Co-authored-by: ligi --- cmd/abigen/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index a74b0396d4..7b3b35e4e5 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -96,7 +96,7 @@ var ( } aliasFlag = cli.StringFlag{ Name: "alias", - Usage: "Comma separated aliases for function and event renaming, e.g. foo=bar", + Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", } ) From 017831dd5b33a68076aed7c9ff05e62b0dcb5f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 14 Dec 2020 11:27:15 +0200 Subject: [PATCH 199/948] core, eth: split eth package, implement snap protocol (#21482) This commit splits the eth package, separating the handling of eth and snap protocols. It also includes the capability to run snap sync (https://github.com/ethereum/devp2p/blob/master/caps/snap.md) , but does not enable it by default. Co-authored-by: Marius van der Wijden Co-authored-by: Martin Holst Swende --- cmd/geth/misccmd.go | 2 - cmd/utils/flags.go | 29 +- core/blockchain.go | 8 +- core/blockchain_snapshot_test.go | 2 +- core/forkid/forkid.go | 9 + core/rawdb/accessors_snapshot.go | 21 + core/rawdb/schema.go | 3 + core/state/snapshot/generate.go | 2 +- core/state/statedb.go | 24 +- eth/api_backend.go | 6 +- eth/api_test.go | 6 + eth/backend.go | 60 +- eth/config.go | 3 +- eth/discovery.go | 9 +- eth/downloader/downloader.go | 108 +- eth/downloader/downloader_test.go | 114 +- eth/downloader/modes.go | 9 +- eth/downloader/peer.go | 14 +- eth/downloader/queue.go | 49 +- eth/downloader/statesync.go | 31 +- eth/gen_config.go | 10 +- eth/handler.go | 867 ++---- eth/handler_eth.go | 218 ++ eth/handler_eth_test.go | 740 +++++ eth/handler_snap.go | 48 + eth/handler_test.go | 736 +---- eth/helper_test.go | 231 -- eth/peer.go | 804 +----- eth/peerset.go | 301 ++ eth/protocol.go | 221 -- eth/protocol_test.go | 459 --- eth/protocols/eth/broadcast.go | 195 ++ eth/protocols/eth/discovery.go | 65 + eth/protocols/eth/handler.go | 512 ++++ eth/protocols/eth/handler_test.go | 519 ++++ eth/protocols/eth/handshake.go | 107 + eth/protocols/eth/handshake_test.go | 91 + eth/protocols/eth/peer.go | 429 +++ eth/protocols/eth/peer_test.go | 61 + eth/protocols/eth/protocol.go | 279 ++ eth/protocols/eth/protocol_test.go | 68 + eth/protocols/snap/discovery.go | 32 + eth/protocols/snap/handler.go | 490 ++++ eth/protocols/snap/peer.go | 111 + eth/protocols/snap/protocol.go | 218 ++ eth/protocols/snap/sync.go | 2481 +++++++++++++++++ eth/sync.go | 129 +- eth/sync_test.go | 50 +- ethstats/ethstats.go | 14 +- graphql/graphql.go | 4 - graphql/schema.go | 2 - internal/ethapi/api.go | 17 +- internal/ethapi/backend.go | 1 - les/client.go | 2 +- les/enr_entry.go | 4 +- les/handler_test.go | 2 +- les/peer.go | 13 +- les/server_handler.go | 2 +- tests/block_test_util.go | 2 +- ...1c14030f26872e57bf1481084f151d71eed8161c-1 | Bin 0 -> 16005 bytes ...27e54254422543060a13ea8a4bc913d768e4adb6-2 | Bin 0 -> 15965 bytes ...6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 | Bin 0 -> 15976 bytes ...a67e63bc0c0004bd009944a6061297cb7d4ac238-1 | Bin 0 -> 14980 bytes ...ae892bbae0a843950bc8316496e595b1a194c009-4 | Bin 0 -> 15977 bytes ...ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 | Bin 0 -> 16000 bytes ...f50a6d57a46d30184aa294af5b252ab9701af7c9-2 | Bin 0 -> 1748 bytes tests/fuzzers/rangeproof/corpus/random.dat | Bin 0 -> 16000 bytes tests/fuzzers/rangeproof/debug/main.go | 41 + tests/fuzzers/rangeproof/rangeproof-fuzzer.go | 218 ++ trie/notary.go | 57 + trie/proof.go | 104 +- trie/proof_test.go | 104 +- trie/sync_bloom.go | 6 +- trie/trie.go | 39 +- 74 files changed, 8224 insertions(+), 3389 deletions(-) create mode 100644 eth/handler_eth.go create mode 100644 eth/handler_eth_test.go create mode 100644 eth/handler_snap.go delete mode 100644 eth/helper_test.go create mode 100644 eth/peerset.go delete mode 100644 eth/protocol.go delete mode 100644 eth/protocol_test.go create mode 100644 eth/protocols/eth/broadcast.go create mode 100644 eth/protocols/eth/discovery.go create mode 100644 eth/protocols/eth/handler.go create mode 100644 eth/protocols/eth/handler_test.go create mode 100644 eth/protocols/eth/handshake.go create mode 100644 eth/protocols/eth/handshake_test.go create mode 100644 eth/protocols/eth/peer.go create mode 100644 eth/protocols/eth/peer_test.go create mode 100644 eth/protocols/eth/protocol.go create mode 100644 eth/protocols/eth/protocol_test.go create mode 100644 eth/protocols/snap/discovery.go create mode 100644 eth/protocols/snap/handler.go create mode 100644 eth/protocols/snap/peer.go create mode 100644 eth/protocols/snap/protocol.go create mode 100644 eth/protocols/snap/sync.go create mode 100644 tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 create mode 100644 tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 create mode 100644 tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 create mode 100644 tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 create mode 100644 tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 create mode 100644 tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 create mode 100644 tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 create mode 100644 tests/fuzzers/rangeproof/corpus/random.dat create mode 100644 tests/fuzzers/rangeproof/debug/main.go create mode 100644 tests/fuzzers/rangeproof/rangeproof-fuzzer.go create mode 100644 trie/notary.go diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 967df2ada0..b347d31d97 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -25,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/params" "gopkg.in/urfave/cli.v1" ) @@ -143,7 +142,6 @@ func version(ctx *cli.Context) error { fmt.Println("Git Commit Date:", gitDate) } fmt.Println("Architecture:", runtime.GOARCH) - fmt.Println("Protocol Versions:", eth.ProtocolVersions) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 051bdd6308..0b1695d0a5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -187,7 +187,7 @@ var ( defaultSyncMode = eth.DefaultConfig.SyncMode SyncModeFlag = TextMarshalerFlag{ Name: "syncmode", - Usage: `Blockchain sync mode ("fast", "full", or "light")`, + Usage: `Blockchain sync mode ("fast", "full", "snap" or "light")`, Value: &defaultSyncMode, } GCModeFlag = cli.StringFlag{ @@ -1555,8 +1555,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100 } if !ctx.GlobalIsSet(SnapshotFlag.Name) { - cfg.TrieCleanCache += cfg.SnapshotCache - cfg.SnapshotCache = 0 // Disabled + // If snap-sync is requested, this flag is also required + if cfg.SyncMode == downloader.SnapSync { + log.Info("Snap sync requested, enabling --snapshot") + ctx.Set(SnapshotFlag.Name, "true") + } else { + cfg.TrieCleanCache += cfg.SnapshotCache + cfg.SnapshotCache = 0 // Disabled + } } if ctx.GlobalIsSet(DocRootFlag.Name) { cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) @@ -1585,16 +1591,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name) } if ctx.GlobalIsSet(NoDiscoverFlag.Name) { - cfg.DiscoveryURLs = []string{} + cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{} } else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) if urls == "" { - cfg.DiscoveryURLs = []string{} + cfg.EthDiscoveryURLs = []string{} } else { - cfg.DiscoveryURLs = SplitAndTrim(urls) + cfg.EthDiscoveryURLs = SplitAndTrim(urls) } } - // Override any default configs for hard coded networks. switch { case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): @@ -1676,16 +1681,20 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // no URLs are set. func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { - if cfg.DiscoveryURLs != nil { + if cfg.EthDiscoveryURLs != nil { return // already set through flags/config } - protocol := "all" if cfg.SyncMode == downloader.LightSync { protocol = "les" } if url := params.KnownDNSNetwork(genesis, protocol); url != "" { - cfg.DiscoveryURLs = []string{url} + cfg.EthDiscoveryURLs = []string{url} + } + if cfg.SyncMode == downloader.SnapSync { + if url := params.KnownDNSNetwork(genesis, "snap"); url != "" { + cfg.SnapDiscoveryURLs = []string{url} + } } } diff --git a/core/blockchain.go b/core/blockchain.go index bc1db49f37..d9505dcf69 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -659,12 +659,8 @@ func (bc *BlockChain) CurrentBlock() *types.Block { return bc.currentBlock.Load().(*types.Block) } -// Snapshot returns the blockchain snapshot tree. This method is mainly used for -// testing, to make it possible to verify the snapshot after execution. -// -// Warning: There are no guarantees about the safety of using the returned 'snap' if the -// blockchain is simultaneously importing blocks, so take care. -func (bc *BlockChain) Snapshot() *snapshot.Tree { +// Snapshots returns the blockchain snapshot tree. +func (bc *BlockChain) Snapshots() *snapshot.Tree { return bc.snaps } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index e8d3b2470a..f35dae1678 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -751,7 +751,7 @@ func testSnapshot(t *testing.T, tt *snapshotTest) { t.Fatalf("Failed to recreate chain: %v", err) } chain.InsertChain(newBlocks) - chain.Snapshot().Cap(newBlocks[len(newBlocks)-1].Root(), 0) + chain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0) // Simulate the blockchain crash // Don't call chain.Stop here, so that no snapshot diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index c432858617..1bf3406828 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -84,6 +84,15 @@ func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { return ID{Hash: checksumToBytes(hash), Next: next} } +// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance. +func NewIDWithChain(chain Blockchain) ID { + return NewID( + chain.Config(), + chain.Genesis().Hash(), + chain.CurrentHeader().Number.Uint64(), + ) +} + // NewFilter creates a filter that returns if a fork ID should be rejected or not // based on the local chain's status. func NewFilter(chain Blockchain) Filter { diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 5bd48ad5fa..0a91d9353b 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -175,3 +175,24 @@ func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) { log.Crit("Failed to remove snapshot recovery number", "err", err) } } + +// ReadSanpshotSyncStatus retrieves the serialized sync status saved at shutdown. +func ReadSanpshotSyncStatus(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotSyncStatusKey) + return data +} + +// WriteSnapshotSyncStatus stores the serialized sync status to save at shutdown. +func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) { + if err := db.Put(snapshotSyncStatusKey, status); err != nil { + log.Crit("Failed to store snapshot sync status", "err", err) + } +} + +// DeleteSnapshotSyncStatus deletes the serialized sync status saved at the last +// shutdown +func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotSyncStatusKey); err != nil { + log.Crit("Failed to remove snapshot sync status", "err", err) + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index cff27b4bb0..2aabfd3baa 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -57,6 +57,9 @@ var ( // snapshotRecoveryKey tracks the snapshot recovery marker across restarts. snapshotRecoveryKey = []byte("SnapshotRecovery") + // snapshotSyncStatusKey tracks the snapshot sync status across restarts. + snapshotSyncStatusKey = []byte("SnapshotSyncStatus") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 92c7640c40..4a2fa78d3a 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -241,7 +241,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { if acc.Root != emptyRoot { storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) if err != nil { - log.Error("Generator failed to access storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) + log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err) abort := <-dl.genAbort abort <- stats return diff --git a/core/state/statedb.go b/core/state/statedb.go index ed9a82379f..a9d1de2e06 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -314,14 +314,19 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { return common.Hash{} } -// GetProof returns the MerkleProof for a given Account -func (s *StateDB) GetProof(a common.Address) ([][]byte, error) { +// GetProof returns the Merkle proof for a given account. +func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) { + return s.GetProofByHash(crypto.Keccak256Hash(addr.Bytes())) +} + +// GetProofByHash returns the Merkle proof for a given account. +func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) { var proof proofList - err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof) + err := s.trie.Prove(addrHash[:], 0, &proof) return proof, err } -// GetStorageProof returns the StorageProof for given key +// GetStorageProof returns the Merkle proof for given storage slot. func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) { var proof proofList trie := s.StorageTrie(a) @@ -332,6 +337,17 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, return proof, err } +// GetStorageProofByHash returns the Merkle proof for given storage slot. +func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][]byte, error) { + var proof proofList + trie := s.StorageTrie(a) + if trie == nil { + return proof, errors.New("storage trie for requested address does not exist") + } + err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) + return proof, err +} + // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) diff --git a/eth/api_backend.go b/eth/api_backend.go index e7f676f178..2f7020475f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -56,7 +56,7 @@ func (b *EthAPIBackend) CurrentBlock() *types.Block { } func (b *EthAPIBackend) SetHead(number uint64) { - b.eth.protocolManager.downloader.Cancel() + b.eth.handler.downloader.Cancel() b.eth.blockchain.SetHead(number) } @@ -272,10 +272,6 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader { return b.eth.Downloader() } -func (b *EthAPIBackend) ProtocolVersion() int { - return b.eth.EthVersion() -} - func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { return b.gpo.SuggestPrice(ctx) } diff --git a/eth/api_test.go b/eth/api_test.go index 2c9a2e54e8..b44eed40bc 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -57,6 +57,8 @@ func (h resultHash) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 } func TestAccountRange(t *testing.T) { + t.Parallel() + var ( statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil) state, _ = state.New(common.Hash{}, statedb, nil) @@ -126,6 +128,8 @@ func TestAccountRange(t *testing.T) { } func TestEmptyAccountRange(t *testing.T) { + t.Parallel() + var ( statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) state, _ = state.New(common.Hash{}, statedb, nil) @@ -142,6 +146,8 @@ func TestEmptyAccountRange(t *testing.T) { } func TestStorageRangeAt(t *testing.T) { + t.Parallel() + // Create a state where account 0x010000... has a few storage entries. var ( state, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) diff --git a/eth/backend.go b/eth/backend.go index bb4275b92c..987dee6d55 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -40,6 +40,8 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -48,7 +50,6 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -59,10 +60,11 @@ type Ethereum struct { config *Config // Handlers - txPool *core.TxPool - blockchain *core.BlockChain - protocolManager *ProtocolManager - dialCandidates enode.Iterator + txPool *core.TxPool + blockchain *core.BlockChain + handler *handler + ethDialCandidates enode.Iterator + snapDialCandidates enode.Iterator // DB interfaces chainDb ethdb.Database // Block chain database @@ -145,7 +147,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } - log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer) + log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) if !config.SkipBcVersionCheck { if bcVersion != nil && *bcVersion > core.BlockChainVersion { @@ -196,7 +198,17 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { if checkpoint == nil { checkpoint = params.TrustedCheckpoints[genesisHash] } - if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist); err != nil { + if eth.handler, err = newHandler(&handlerConfig{ + Database: chainDb, + Chain: eth.blockchain, + TxPool: eth.txPool, + Network: config.NetworkId, + Sync: config.SyncMode, + BloomCache: uint64(cacheLimit), + EventMux: eth.eventMux, + Checkpoint: checkpoint, + Whitelist: config.Whitelist, + }); err != nil { return nil, err } eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) @@ -209,13 +221,16 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.dialCandidates, err = eth.setupDiscovery() + eth.ethDialCandidates, err = setupDiscovery(eth.config.EthDiscoveryURLs) + if err != nil { + return nil, err + } + eth.snapDialCandidates, err = setupDiscovery(eth.config.SnapDiscoveryURLs) if err != nil { return nil, err } - // Start the RPC service - eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, eth.NetVersion()) + eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer) // Register the backend on the node stack.RegisterAPIs(eth.APIs()) @@ -310,7 +325,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux), + Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux), Public: true, }, { Namespace: "miner", @@ -473,7 +488,7 @@ func (s *Ethereum) StartMining(threads int) error { } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. - atomic.StoreUint32(&s.protocolManager.acceptTxs, 1) + atomic.StoreUint32(&s.handler.acceptTxs, 1) go s.miner.Start(eb) } @@ -504,21 +519,17 @@ func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) EthVersion() int { return int(ProtocolVersions[0]) } -func (s *Ethereum) NetVersion() uint64 { return s.networkID } -func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } -func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 } +func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader } +func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } // Protocols returns all the currently configured // network protocols to start. func (s *Ethereum) Protocols() []p2p.Protocol { - protos := make([]p2p.Protocol, len(ProtocolVersions)) - for i, vsn := range ProtocolVersions { - protos[i] = s.protocolManager.makeProtocol(vsn) - protos[i].Attributes = []enr.Entry{s.currentEthEntry()} - protos[i].DialCandidates = s.dialCandidates + protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates) + if s.config.SnapshotCache > 0 { + protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...) } return protos } @@ -526,7 +537,7 @@ func (s *Ethereum) Protocols() []p2p.Protocol { // Start implements node.Lifecycle, starting all internal goroutines needed by the // Ethereum protocol implementation. func (s *Ethereum) Start() error { - s.startEthEntryUpdate(s.p2pServer.LocalNode()) + eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode()) // Start the bloom bits servicing goroutines s.startBloomHandlers(params.BloomBitsBlocks) @@ -540,7 +551,7 @@ func (s *Ethereum) Start() error { maxPeers -= s.config.LightPeers } // Start the networking layer and the light server if requested - s.protocolManager.Start(maxPeers) + s.handler.Start(maxPeers) return nil } @@ -548,7 +559,7 @@ func (s *Ethereum) Start() error { // Ethereum protocol. func (s *Ethereum) Stop() error { // Stop all the peer-related stuff first. - s.protocolManager.Stop() + s.handler.Stop() // Then stop everything else. s.bloomIndexer.Close() @@ -560,5 +571,6 @@ func (s *Ethereum) Stop() error { rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() s.eventMux.Stop() + return nil } diff --git a/eth/config.go b/eth/config.go index 0d90376d94..77d03e9569 100644 --- a/eth/config.go +++ b/eth/config.go @@ -115,7 +115,8 @@ type Config struct { // This can be set to list of enrtree:// URLs which will be queried for // for nodes to connect to. - DiscoveryURLs []string + EthDiscoveryURLs []string + SnapDiscoveryURLs []string NoPruning bool // Whether to disable pruning and flush everything to disk NoPrefetch bool // Whether to disable prefetching and only load state on demand diff --git a/eth/discovery.go b/eth/discovery.go index e7a281d356..855ce3b0e1 100644 --- a/eth/discovery.go +++ b/eth/discovery.go @@ -63,11 +63,12 @@ func (eth *Ethereum) currentEthEntry() *ethEntry { eth.blockchain.CurrentHeader().Number.Uint64())} } -// setupDiscovery creates the node discovery source for the eth protocol. -func (eth *Ethereum) setupDiscovery() (enode.Iterator, error) { - if len(eth.config.DiscoveryURLs) == 0 { +// setupDiscovery creates the node discovery source for the `eth` and `snap` +// protocols. +func setupDiscovery(urls []string) (enode.Iterator, error) { + if len(urls) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(eth.config.DiscoveryURLs...) + return client.NewIterator(urls...) } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 686c1ace14..3123598437 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -38,7 +39,6 @@ import ( ) var ( - MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly @@ -89,7 +89,7 @@ var ( errCancelContentProcessing = errors.New("content processing canceled (requested)") errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") - errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 63)") + errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 64)") ) type Downloader struct { @@ -131,20 +131,22 @@ type Downloader struct { ancientLimit uint64 // The maximum block number which can be regarded as ancient data. // Channels - headerCh chan dataPack // [eth/62] Channel receiving inbound block headers - bodyCh chan dataPack // [eth/62] Channel receiving inbound block bodies - receiptCh chan dataPack // [eth/63] Channel receiving inbound receipts - bodyWakeCh chan bool // [eth/62] Channel to signal the block body fetcher of new tasks - receiptWakeCh chan bool // [eth/63] Channel to signal the receipt fetcher of new tasks - headerProcCh chan []*types.Header // [eth/62] Channel to feed the header processor new tasks + headerCh chan dataPack // Channel receiving inbound block headers + bodyCh chan dataPack // Channel receiving inbound block bodies + receiptCh chan dataPack // Channel receiving inbound receipts + bodyWakeCh chan bool // Channel to signal the block body fetcher of new tasks + receiptWakeCh chan bool // Channel to signal the receipt fetcher of new tasks + headerProcCh chan []*types.Header // Channel to feed the header processor new tasks // State sync pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root pivotLock sync.RWMutex // Lock protecting pivot header reads from updates + snapSync bool // Whether to run state sync over the snap protocol + SnapSyncer *snap.Syncer // TODO(karalabe): make private! hack for now stateSyncStart chan *stateSync trackStateReq chan *stateReq - stateCh chan dataPack // [eth/63] Channel receiving inbound node state data + stateCh chan dataPack // Channel receiving inbound node state data // Cancellation and termination cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) @@ -237,6 +239,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, headerProcCh: make(chan []*types.Header, 1), quitCh: make(chan struct{}), stateCh: make(chan dataPack), + SnapSyncer: snap.NewSyncer(stateDb, stateBloom), stateSyncStart: make(chan *stateSync), syncStatsState: stateSyncStats{ processed: rawdb.ReadFastTrieProgress(stateDb), @@ -286,19 +289,16 @@ func (d *Downloader) Synchronising() bool { return atomic.LoadInt32(&d.synchronising) > 0 } -// SyncBloomContains tests if the syncbloom filter contains the given hash: -// - false: the bloom definitely does not contain hash -// - true: the bloom maybe contains hash -// -// While the bloom is being initialized (or is closed), all queries will return true. -func (d *Downloader) SyncBloomContains(hash []byte) bool { - return d.stateBloom == nil || d.stateBloom.Contains(hash) -} - // RegisterPeer injects a new download peer into the set of block source to be // used for fetching hashes and blocks from. -func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error { - logger := log.New("peer", id) +func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } logger.Trace("Registering sync peer") if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { logger.Error("Failed to register sync peer", "err", err) @@ -310,7 +310,7 @@ func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error { } // RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer. -func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) error { +func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error { return d.RegisterPeer(id, version, &lightPeerWrapper{peer}) } @@ -319,7 +319,13 @@ func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) e // the queue. func (d *Downloader) UnregisterPeer(id string) error { // Unregister the peer from the active peer set and revoke any fetch tasks - logger := log.New("peer", id) + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } logger.Trace("Unregistering sync peer") if err := d.peers.Unregister(id); err != nil { logger.Error("Failed to unregister sync peer", "err", err) @@ -381,6 +387,16 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode if mode == FullSync && d.stateBloom != nil { d.stateBloom.Close() } + // If snap sync was requested, create the snap scheduler and switch to fast + // sync mode. Long term we could drop fast sync or merge the two together, + // but until snap becomes prevalent, we should support both. TODO(karalabe). + if mode == SnapSync { + if !d.snapSync { + log.Warn("Enabling snapshot sync prototype") + d.snapSync = true + } + mode = FastSync + } // Reset the queue, peer set and wake channels to clean any internal leftover state d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems) d.peers.Reset() @@ -443,8 +459,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.mux.Post(DoneEvent{latest}) } }() - if p.version < 63 { - return errTooOld + if p.version < 64 { + return fmt.Errorf("%w, peer version: %d", errTooOld, p.version) } mode := d.getMode() @@ -1910,27 +1926,53 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error { // DeliverHeaders injects a new batch of block headers received from a remote // node into the download schedule. -func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) { - return d.deliver(id, d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter) +func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) error { + return d.deliver(d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter) } // DeliverBodies injects a new batch of block bodies received from a remote node. -func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) (err error) { - return d.deliver(id, d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter) +func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) error { + return d.deliver(d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter) } // DeliverReceipts injects a new batch of receipts received from a remote node. -func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) (err error) { - return d.deliver(id, d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter) +func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) error { + return d.deliver(d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter) } // DeliverNodeData injects a new batch of node state data received from a remote node. -func (d *Downloader) DeliverNodeData(id string, data [][]byte) (err error) { - return d.deliver(id, d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter) +func (d *Downloader) DeliverNodeData(id string, data [][]byte) error { + return d.deliver(d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter) +} + +// DeliverSnapPacket is invoked from a peer's message handler when it transmits a +// data packet for the local node to consume. +func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error { + switch packet := packet.(type) { + case *snap.AccountRangePacket: + hashes, accounts, err := packet.Unpack() + if err != nil { + return err + } + return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) + + case *snap.StorageRangesPacket: + hashset, slotset := packet.Unpack() + return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof) + + case *snap.ByteCodesPacket: + return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes) + + case *snap.TrieNodesPacket: + return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes) + + default: + return fmt.Errorf("unexpected snap packet type: %T", packet) + } } // deliver injects a new batch of data received from a remote node. -func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) { +func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) { // Update the delivery metrics for both good and failed deliveries inMeter.Mark(int64(packet.Items())) defer func() { diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 5e46042ae4..6578275d0c 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -390,7 +390,7 @@ func (dl *downloadTester) Rollback(hashes []common.Hash) { } // newPeer registers a new block download source into the downloader. -func (dl *downloadTester) newPeer(id string, version int, chain *testChain) error { +func (dl *downloadTester) newPeer(id string, version uint, chain *testChain) error { dl.lock.Lock() defer dl.lock.Unlock() @@ -518,8 +518,6 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng // Tests that simple synchronization against a canonical chain works correctly. // In this test common ancestor lookup should be short circuited and not require // binary searching. -func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) } -func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) } func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonicalSynchronisation(t, 65, FullSync) } @@ -528,7 +526,7 @@ func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonicalSynchronisation(t, 65, LightSync) } -func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { +func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -547,14 +545,12 @@ func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { // Tests that if a large batch of blocks are being downloaded, it is throttled // until the cached blocks are retrieved. -func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) } -func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) } func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } func TestThrottling65Full(t *testing.T) { testThrottling(t, 65, FullSync) } func TestThrottling65Fast(t *testing.T) { testThrottling(t, 65, FastSync) } -func testThrottling(t *testing.T, protocol int, mode SyncMode) { +func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -632,15 +628,13 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) { // Tests that simple synchronization against a forked chain works correctly. In // this test common ancestor lookup should *not* be short circuited, and a full // binary search should be executed. -func TestForkedSync63Full(t *testing.T) { testForkedSync(t, 63, FullSync) } -func TestForkedSync63Fast(t *testing.T) { testForkedSync(t, 63, FastSync) } func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } func TestForkedSync65Full(t *testing.T) { testForkedSync(t, 65, FullSync) } func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, 65, FastSync) } func TestForkedSync65Light(t *testing.T) { testForkedSync(t, 65, LightSync) } -func testForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -665,15 +659,13 @@ func testForkedSync(t *testing.T, protocol int, mode SyncMode) { // Tests that synchronising against a much shorter but much heavyer fork works // corrently and is not dropped. -func TestHeavyForkedSync63Full(t *testing.T) { testHeavyForkedSync(t, 63, FullSync) } -func TestHeavyForkedSync63Fast(t *testing.T) { testHeavyForkedSync(t, 63, FastSync) } func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, 65, FullSync) } func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, 65, FastSync) } func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, 65, LightSync) } -func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -700,15 +692,13 @@ func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head, ensuring that malicious peers cannot waste resources by feeding // long dead chains. -func TestBoundedForkedSync63Full(t *testing.T) { testBoundedForkedSync(t, 63, FullSync) } -func TestBoundedForkedSync63Fast(t *testing.T) { testBoundedForkedSync(t, 63, FastSync) } func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, 65, FullSync) } func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, 65, FastSync) } func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, 65, LightSync) } -func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -734,15 +724,13 @@ func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head for short but heavy forks too. These are a bit special because they // take different ancestor lookup paths. -func TestBoundedHeavyForkedSync63Full(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FullSync) } -func TestBoundedHeavyForkedSync63Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 63, FastSync) } func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } func TestBoundedHeavyForkedSync65Full(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FullSync) } func TestBoundedHeavyForkedSync65Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FastSync) } func TestBoundedHeavyForkedSync65Light(t *testing.T) { testBoundedHeavyForkedSync(t, 65, LightSync) } -func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -786,15 +774,13 @@ func TestInactiveDownloader63(t *testing.T) { } // Tests that a canceled download wipes all previously accumulated state. -func TestCancel63Full(t *testing.T) { testCancel(t, 63, FullSync) } -func TestCancel63Fast(t *testing.T) { testCancel(t, 63, FastSync) } func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } func TestCancel65Full(t *testing.T) { testCancel(t, 65, FullSync) } func TestCancel65Fast(t *testing.T) { testCancel(t, 65, FastSync) } func TestCancel65Light(t *testing.T) { testCancel(t, 65, LightSync) } -func testCancel(t *testing.T, protocol int, mode SyncMode) { +func testCancel(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -819,15 +805,13 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) { } // Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation63Full(t *testing.T) { testMultiSynchronisation(t, 63, FullSync) } -func TestMultiSynchronisation63Fast(t *testing.T) { testMultiSynchronisation(t, 63, FastSync) } func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, 65, FullSync) } func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, 65, FastSync) } func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, 65, LightSync) } -func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) { +func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -849,15 +833,13 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) { // Tests that synchronisations behave well in multi-version protocol environments // and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation63Full(t *testing.T) { testMultiProtoSync(t, 63, FullSync) } -func TestMultiProtoSynchronisation63Fast(t *testing.T) { testMultiProtoSync(t, 63, FastSync) } func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, 65, FullSync) } func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, 65, FastSync) } func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, 65, LightSync) } -func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) { +func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -888,15 +870,13 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) { // Tests that if a block is empty (e.g. header only), no body request should be // made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit63Full(t *testing.T) { testEmptyShortCircuit(t, 63, FullSync) } -func TestEmptyShortCircuit63Fast(t *testing.T) { testEmptyShortCircuit(t, 63, FastSync) } func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, 65, FullSync) } func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, 65, FastSync) } func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, 65, LightSync) } -func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { +func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -942,15 +922,13 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { // Tests that headers are enqueued continuously, preventing malicious nodes from // stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack63Full(t *testing.T) { testMissingHeaderAttack(t, 63, FullSync) } -func TestMissingHeaderAttack63Fast(t *testing.T) { testMissingHeaderAttack(t, 63, FastSync) } func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, 65, FullSync) } func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, 65, FastSync) } func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, 65, LightSync) } -func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) { +func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -974,15 +952,13 @@ func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) { // Tests that if requested headers are shifted (i.e. first is missing), the queue // detects the invalid numbering. -func TestShiftedHeaderAttack63Full(t *testing.T) { testShiftedHeaderAttack(t, 63, FullSync) } -func TestShiftedHeaderAttack63Fast(t *testing.T) { testShiftedHeaderAttack(t, 63, FastSync) } func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, 65, FullSync) } func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, 65, FastSync) } func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, 65, LightSync) } -func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { +func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1011,11 +987,10 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { // Tests that upon detecting an invalid header, the recent ones are rolled back // for various failure scenarios. Afterwards a full sync is attempted to make // sure no state was corrupted. -func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback(t, 63, FastSync) } func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } -func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { +func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1103,15 +1078,13 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { // Tests that a peer advertising a high TD doesn't get to stall the downloader // afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack63Full(t *testing.T) { testHighTDStarvationAttack(t, 63, FullSync) } -func TestHighTDStarvationAttack63Fast(t *testing.T) { testHighTDStarvationAttack(t, 63, FastSync) } func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } func TestHighTDStarvationAttack65Full(t *testing.T) { testHighTDStarvationAttack(t, 65, FullSync) } func TestHighTDStarvationAttack65Fast(t *testing.T) { testHighTDStarvationAttack(t, 65, FastSync) } func TestHighTDStarvationAttack65Light(t *testing.T) { testHighTDStarvationAttack(t, 65, LightSync) } -func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) { +func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1125,11 +1098,10 @@ func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) { } // Tests that misbehaving peers are disconnected, whilst behaving ones are not. -func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) } func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, 65) } -func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { +func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { t.Parallel() // Define the disconnection requirement for individual hash fetch errors @@ -1179,15 +1151,13 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { // Tests that synchronisation progress (origin block number, current block number // and highest block number) is tracked and updated correctly. -func TestSyncProgress63Full(t *testing.T) { testSyncProgress(t, 63, FullSync) } -func TestSyncProgress63Fast(t *testing.T) { testSyncProgress(t, 63, FastSync) } func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, 65, FullSync) } func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, 65, FastSync) } func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, 65, LightSync) } -func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1263,21 +1233,19 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync // Tests that synchronisation progress (origin block number and highest block // number) is tracked and updated correctly in case of a fork (or manual head // revertal). -func TestForkedSyncProgress63Full(t *testing.T) { testForkedSyncProgress(t, 63, FullSync) } -func TestForkedSyncProgress63Fast(t *testing.T) { testForkedSyncProgress(t, 63, FastSync) } func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, 65, FullSync) } func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, 65, FastSync) } func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, 65, LightSync) } -func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() defer tester.terminate() - chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHashFetch) - chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHashFetch) + chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHeaderFetch) + chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHeaderFetch) // Set a sync init hook to catch progress changes starting := make(chan struct{}) @@ -1339,15 +1307,13 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // Tests that if synchronisation is aborted due to some failure, then the progress // origin is not updated in the next sync cycle, as it should be considered the // continuation of the previous sync and not a new instance. -func TestFailedSyncProgress63Full(t *testing.T) { testFailedSyncProgress(t, 63, FullSync) } -func TestFailedSyncProgress63Fast(t *testing.T) { testFailedSyncProgress(t, 63, FastSync) } func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, 65, FullSync) } func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, 65, FastSync) } func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, 65, LightSync) } -func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1412,15 +1378,13 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // Tests that if an attacker fakes a chain height, after the attack is detected, // the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress63Full(t *testing.T) { testFakedSyncProgress(t, 63, FullSync) } -func TestFakedSyncProgress63Fast(t *testing.T) { testFakedSyncProgress(t, 63, FastSync) } func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, 65, FullSync) } func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, 65, FastSync) } func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, 65, LightSync) } -func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { +func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -1489,31 +1453,15 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // This test reproduces an issue where unexpected deliveries would // block indefinitely if they arrived at the right time. -func TestDeliverHeadersHang(t *testing.T) { - t.Parallel() +func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } +func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } +func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, 65, FullSync) } +func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, 65, FastSync) } +func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, 65, LightSync) } - testCases := []struct { - protocol int - syncMode SyncMode - }{ - {63, FullSync}, - {63, FastSync}, - {64, FullSync}, - {64, FastSync}, - {64, LightSync}, - {65, FullSync}, - {65, FastSync}, - {65, LightSync}, - } - for _, tc := range testCases { - t.Run(fmt.Sprintf("protocol %d mode %v", tc.protocol, tc.syncMode), func(t *testing.T) { - t.Parallel() - testDeliverHeadersHang(t, tc.protocol, tc.syncMode) - }) - } -} +func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { + t.Parallel() -func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) { master := newTester() defer master.terminate() chain := testChainBase.shorten(15) @@ -1664,15 +1612,13 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { // Tests that peers below a pre-configured checkpoint block are prevented from // being fast-synced from, avoiding potential cheap eclipse attacks. -func TestCheckpointEnforcement63Full(t *testing.T) { testCheckpointEnforcement(t, 63, FullSync) } -func TestCheckpointEnforcement63Fast(t *testing.T) { testCheckpointEnforcement(t, 63, FastSync) } func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, 65, FullSync) } func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, 65, FastSync) } func TestCheckpointEnforcement65Light(t *testing.T) { testCheckpointEnforcement(t, 65, LightSync) } -func testCheckpointEnforcement(t *testing.T, protocol int, mode SyncMode) { +func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() // Create a new tester with a particular hard coded checkpoint block diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index d866ceabce..8ea7876a1f 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -24,7 +24,8 @@ type SyncMode uint32 const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks - FastSync // Quickly download the headers, full sync only at the chain head + FastSync // Quickly download the headers, full sync only at the chain + SnapSync // Download the chain and the state via compact snashots LightSync // Download only the headers and terminate afterwards ) @@ -39,6 +40,8 @@ func (mode SyncMode) String() string { return "full" case FastSync: return "fast" + case SnapSync: + return "snap" case LightSync: return "light" default: @@ -52,6 +55,8 @@ func (mode SyncMode) MarshalText() ([]byte, error) { return []byte("full"), nil case FastSync: return []byte("fast"), nil + case SnapSync: + return []byte("snap"), nil case LightSync: return []byte("light"), nil default: @@ -65,6 +70,8 @@ func (mode *SyncMode) UnmarshalText(text []byte) error { *mode = FullSync case "fast": *mode = FastSync + case "snap": + *mode = SnapSync case "light": *mode = LightSync default: diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index c6671436f9..ba90bf31cb 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -69,7 +69,7 @@ type peerConnection struct { peer Peer - version int // Eth protocol version number to switch strategies + version uint // Eth protocol version number to switch strategies log log.Logger // Contextual logger to add extra infos to peer logs lock sync.RWMutex } @@ -112,7 +112,7 @@ func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error { } // newPeerConnection creates a new downloader peer. -func newPeerConnection(id string, version int, peer Peer, logger log.Logger) *peerConnection { +func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection { return &peerConnection{ id: id, lacking: make(map[common.Hash]struct{}), @@ -457,7 +457,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.headerThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within @@ -471,7 +471,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.blockThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers @@ -485,7 +485,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.receiptThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle @@ -499,13 +499,13 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.stateThroughput } - return ps.idlePeers(63, 65, idle, throughput) + return ps.idlePeers(64, 65, idle, throughput) } // idlePeers retrieves a flat list of all currently idle peers satisfying the // protocol version constraints, using the provided function to check idleness. // The resulting set of peers are sorted by their measure throughput. -func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) { +func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) { ps.lock.RLock() defer ps.lock.RUnlock() diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index d2ec8ba694..2150842f8e 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -113,24 +113,24 @@ type queue struct { mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching // Headers are "special", they download in batches, supported by a skeleton chain - headerHead common.Hash // [eth/62] Hash of the last queued header to verify order - headerTaskPool map[uint64]*types.Header // [eth/62] Pending header retrieval tasks, mapping starting indexes to skeleton headers - headerTaskQueue *prque.Prque // [eth/62] Priority queue of the skeleton indexes to fetch the filling headers for - headerPeerMiss map[string]map[uint64]struct{} // [eth/62] Set of per-peer header batches known to be unavailable - headerPendPool map[string]*fetchRequest // [eth/62] Currently pending header retrieval operations - headerResults []*types.Header // [eth/62] Result cache accumulating the completed headers - headerProced int // [eth/62] Number of headers already processed from the results - headerOffset uint64 // [eth/62] Number of the first header in the result cache - headerContCh chan bool // [eth/62] Channel to notify when header download finishes + headerHead common.Hash // Hash of the last queued header to verify order + headerTaskPool map[uint64]*types.Header // Pending header retrieval tasks, mapping starting indexes to skeleton headers + headerTaskQueue *prque.Prque // Priority queue of the skeleton indexes to fetch the filling headers for + headerPeerMiss map[string]map[uint64]struct{} // Set of per-peer header batches known to be unavailable + headerPendPool map[string]*fetchRequest // Currently pending header retrieval operations + headerResults []*types.Header // Result cache accumulating the completed headers + headerProced int // Number of headers already processed from the results + headerOffset uint64 // Number of the first header in the result cache + headerContCh chan bool // Channel to notify when header download finishes // All data retrievals below are based on an already assembles header chain - blockTaskPool map[common.Hash]*types.Header // [eth/62] Pending block (body) retrieval tasks, mapping hashes to headers - blockTaskQueue *prque.Prque // [eth/62] Priority queue of the headers to fetch the blocks (bodies) for - blockPendPool map[string]*fetchRequest // [eth/62] Currently pending block (body) retrieval operations + blockTaskPool map[common.Hash]*types.Header // Pending block (body) retrieval tasks, mapping hashes to headers + blockTaskQueue *prque.Prque // Priority queue of the headers to fetch the blocks (bodies) for + blockPendPool map[string]*fetchRequest // Currently pending block (body) retrieval operations - receiptTaskPool map[common.Hash]*types.Header // [eth/63] Pending receipt retrieval tasks, mapping hashes to headers - receiptTaskQueue *prque.Prque // [eth/63] Priority queue of the headers to fetch the receipts for - receiptPendPool map[string]*fetchRequest // [eth/63] Currently pending receipt retrieval operations + receiptTaskPool map[common.Hash]*types.Header // Pending receipt retrieval tasks, mapping hashes to headers + receiptTaskQueue *prque.Prque // Priority queue of the headers to fetch the receipts for + receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations resultCache *resultStore // Downloaded but not yet delivered fetch results resultSize common.StorageSize // Approximate size of a block (exponential moving average) @@ -690,6 +690,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh q.lock.Lock() defer q.lock.Unlock() + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:16]) + } // Short circuit if the data was never requested request := q.headerPendPool[id] if request == nil { @@ -704,10 +711,10 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh accepted := len(headers) == MaxHeaderFetch if accepted { if headers[0].Number.Uint64() != request.From { - log.Trace("First header broke chain ordering", "peer", id, "number", headers[0].Number, "hash", headers[0].Hash(), request.From) + logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", headers[0].Hash(), "expected", request.From) accepted = false } else if headers[len(headers)-1].Hash() != target { - log.Trace("Last header broke skeleton structure ", "peer", id, "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target) + logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target) accepted = false } } @@ -716,12 +723,12 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh for i, header := range headers[1:] { hash := header.Hash() if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { - log.Warn("Header broke chain ordering", "peer", id, "number", header.Number, "hash", hash, "expected", want) + logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want) accepted = false break } if parentHash != header.ParentHash { - log.Warn("Header broke chain ancestry", "peer", id, "number", header.Number, "hash", hash) + logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) accepted = false break } @@ -731,7 +738,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh } // If the batch of headers wasn't accepted, mark as unavailable if !accepted { - log.Trace("Skeleton filling not accepted", "peer", id, "from", request.From) + logger.Trace("Skeleton filling not accepted", "from", request.From) miss := q.headerPeerMiss[id] if miss == nil { @@ -758,7 +765,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh select { case headerProcCh <- process: - log.Trace("Pre-scheduled new headers", "peer", id, "count", len(process), "from", process[0].Number) + logger.Trace("Pre-scheduled new headers", "count", len(process), "from", process[0].Number) q.headerProced += len(process) default: } diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 6745aa54ac..69bd13c2f7 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -101,8 +101,16 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { finished []*stateReq // Completed or failed requests timeout = make(chan *stateReq) // Timed out active requests ) - // Run the state sync. log.Trace("State sync starting", "root", s.root) + + defer func() { + // Cancel active request timers on exit. Also set peers to idle so they're + // available for the next sync. + for _, req := range active { + req.timer.Stop() + req.peer.SetNodeDataIdle(int(req.nItems), time.Now()) + } + }() go s.run() defer s.Cancel() @@ -252,8 +260,9 @@ func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []* type stateSync struct { d *Downloader // Downloader instance to access and manage current peerset - sched *trie.Sync // State trie sync scheduler defining the tasks - keccak hash.Hash // Keccak256 hasher to verify deliveries with + root common.Hash // State root currently being synced + sched *trie.Sync // State trie sync scheduler defining the tasks + keccak hash.Hash // Keccak256 hasher to verify deliveries with trieTasks map[common.Hash]*trieTask // Set of trie node tasks currently queued for retrieval codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval @@ -268,8 +277,6 @@ type stateSync struct { cancelOnce sync.Once // Ensures cancel only ever gets called once done chan struct{} // Channel to signal termination completion err error // Any error hit during sync (set before completion) - - root common.Hash } // trieTask represents a single trie node download task, containing a set of @@ -290,6 +297,7 @@ type codeTask struct { func newStateSync(d *Downloader, root common.Hash) *stateSync { return &stateSync{ d: d, + root: root, sched: state.NewStateSync(root, d.stateDB, d.stateBloom), keccak: sha3.NewLegacyKeccak256(), trieTasks: make(map[common.Hash]*trieTask), @@ -298,7 +306,6 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { cancel: make(chan struct{}), done: make(chan struct{}), started: make(chan struct{}), - root: root, } } @@ -306,7 +313,12 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { // it finishes, and finally notifying any goroutines waiting for the loop to // finish. func (s *stateSync) run() { - s.err = s.loop() + close(s.started) + if s.d.snapSync { + s.err = s.d.SnapSyncer.Sync(s.root, s.cancel) + } else { + s.err = s.loop() + } close(s.done) } @@ -318,7 +330,9 @@ func (s *stateSync) Wait() error { // Cancel cancels the sync and waits until it has shut down. func (s *stateSync) Cancel() error { - s.cancelOnce.Do(func() { close(s.cancel) }) + s.cancelOnce.Do(func() { + close(s.cancel) + }) return s.Wait() } @@ -329,7 +343,6 @@ func (s *stateSync) Cancel() error { // pushed here async. The reason is to decouple processing from data receipt // and timeouts. func (s *stateSync) loop() (err error) { - close(s.started) // Listen for new peer events to assign tasks to them newPeer := make(chan *peerConnection, 1024) peerSub := s.d.peers.SubscribeNewPeers(newPeer) diff --git a/eth/gen_config.go b/eth/gen_config.go index b0674c7d77..dd04635eee 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -20,7 +20,7 @@ func (c Config) MarshalTOML() (interface{}, error) { Genesis *core.Genesis `toml:",omitempty"` NetworkId uint64 SyncMode downloader.SyncMode - DiscoveryURLs []string + EthDiscoveryURLs []string NoPruning bool NoPrefetch bool TxLookupLimit uint64 `toml:",omitempty"` @@ -61,7 +61,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.Genesis = c.Genesis enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode - enc.DiscoveryURLs = c.DiscoveryURLs + enc.EthDiscoveryURLs = c.EthDiscoveryURLs enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.TxLookupLimit = c.TxLookupLimit @@ -106,7 +106,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { Genesis *core.Genesis `toml:",omitempty"` NetworkId *uint64 SyncMode *downloader.SyncMode - DiscoveryURLs []string + EthDiscoveryURLs []string NoPruning *bool NoPrefetch *bool TxLookupLimit *uint64 `toml:",omitempty"` @@ -156,8 +156,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SyncMode != nil { c.SyncMode = *dec.SyncMode } - if dec.DiscoveryURLs != nil { - c.DiscoveryURLs = dec.DiscoveryURLs + if dec.EthDiscoveryURLs != nil { + c.EthDiscoveryURLs = dec.EthDiscoveryURLs } if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning diff --git a/eth/handler.go b/eth/handler.go index 5b89986539..76a429f6d3 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -17,9 +17,7 @@ package eth import ( - "encoding/json" "errors" - "fmt" "math" "math/big" "sync" @@ -27,26 +25,22 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) const ( - softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. - estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - // txChanSize is the size of channel listening to NewTxsEvent. // The number is referenced from the size of tx pool. txChanSize = 4096 @@ -56,26 +50,61 @@ var ( syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge ) -func errResp(code errCode, format string, v ...interface{}) error { - return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) +// txPool defines the methods needed from a transaction pool implementation to +// support all the operations needed by the Ethereum chain protocols. +type txPool interface { + // Has returns an indicator whether txpool has a transaction + // cached with the given hash. + Has(hash common.Hash) bool + + // Get retrieves the transaction from local txpool with given + // tx hash. + Get(hash common.Hash) *types.Transaction + + // AddRemotes should add the given transactions to the pool. + AddRemotes([]*types.Transaction) []error + + // Pending should return pending transactions. + // The slice should be modifiable by the caller. + Pending() (map[common.Address]types.Transactions, error) + + // SubscribeNewTxsEvent should return an event subscription of + // NewTxsEvent and send events to the given channel. + SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription } -type ProtocolManager struct { +// handlerConfig is the collection of initialization parameters to create a full +// node network handler. +type handlerConfig struct { + Database ethdb.Database // Database for direct sync insertions + Chain *core.BlockChain // Blockchain to serve data from + TxPool txPool // Transaction pool to propagate from + Network uint64 // Network identifier to adfvertise + Sync downloader.SyncMode // Whether to fast or full sync + BloomCache uint64 // Megabytes to alloc for fast sync bloom + EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` + Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges + Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged +} + +type handler struct { networkID uint64 forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) + snapSync uint32 // Flag whether fast sync should operate on top of the snap protocol acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) checkpointNumber uint64 // Block number for the sync progress validator to cross reference checkpointHash common.Hash // Block hash for the sync progress validator to cross reference - txpool txPool - blockchain *core.BlockChain - chaindb ethdb.Database - maxPeers int + database ethdb.Database + txpool txPool + chain *core.BlockChain + maxPeers int downloader *downloader.Downloader + stateBloom *trie.SyncBloom blockFetcher *fetcher.BlockFetcher txFetcher *fetcher.TxFetcher peers *peerSet @@ -94,29 +123,27 @@ type ProtocolManager struct { chainSync *chainSyncer wg sync.WaitGroup peerWG sync.WaitGroup - - // Test fields or hooks - broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation } -// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable -// with the Ethereum network. -func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) { +// newHandler returns a handler for all Ethereum chain management protocol. +func newHandler(config *handlerConfig) (*handler, error) { // Create the protocol manager with the base fields - manager := &ProtocolManager{ - networkID: networkID, - forkFilter: forkid.NewFilter(blockchain), - eventMux: mux, - txpool: txpool, - blockchain: blockchain, - chaindb: chaindb, + if config.EventMux == nil { + config.EventMux = new(event.TypeMux) // Nicety initialization for tests + } + h := &handler{ + networkID: config.Network, + forkFilter: forkid.NewFilter(config.Chain), + eventMux: config.EventMux, + database: config.Database, + txpool: config.TxPool, + chain: config.Chain, peers: newPeerSet(), - whitelist: whitelist, + whitelist: config.Whitelist, txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), } - - if mode == downloader.FullSync { + if config.Sync == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the fast // block is ahead, so fast sync was enabled for this node at a certain point. // The scenarios where this can happen is @@ -125,42 +152,42 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh // * the last fast sync is not finished while user specifies a full sync this // time. But we don't have any recent state for full sync. // In these cases however it's safe to reenable fast sync. - fullBlock, fastBlock := blockchain.CurrentBlock(), blockchain.CurrentFastBlock() + fullBlock, fastBlock := h.chain.CurrentBlock(), h.chain.CurrentFastBlock() if fullBlock.NumberU64() == 0 && fastBlock.NumberU64() > 0 { - manager.fastSync = uint32(1) + h.fastSync = uint32(1) log.Warn("Switch sync mode from full sync to fast sync") } } else { - if blockchain.CurrentBlock().NumberU64() > 0 { + if h.chain.CurrentBlock().NumberU64() > 0 { // Print warning log if database is not empty to run fast sync. log.Warn("Switch sync mode from fast sync to full sync") } else { // If fast sync was requested and our database is empty, grant it - manager.fastSync = uint32(1) + h.fastSync = uint32(1) + if config.Sync == downloader.SnapSync { + h.snapSync = uint32(1) + } } } - // If we have trusted checkpoints, enforce them on the chain - if checkpoint != nil { - manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1 - manager.checkpointHash = checkpoint.SectionHead + if config.Checkpoint != nil { + h.checkpointNumber = (config.Checkpoint.SectionIndex+1)*params.CHTFrequency - 1 + h.checkpointHash = config.Checkpoint.SectionHead } - // Construct the downloader (long sync) and its backing state bloom if fast // sync is requested. The downloader is responsible for deallocating the state // bloom when it's done. - var stateBloom *trie.SyncBloom - if atomic.LoadUint32(&manager.fastSync) == 1 { - stateBloom = trie.NewSyncBloom(uint64(cacheLimit), chaindb) + if atomic.LoadUint32(&h.fastSync) == 1 { + h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database) } - manager.downloader = downloader.New(manager.checkpointNumber, chaindb, stateBloom, manager.eventMux, blockchain, nil, manager.removePeer) + h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer) // Construct the fetcher (short sync) validator := func(header *types.Header) error { - return engine.VerifyHeader(blockchain, header, true) + return h.chain.Engine().VerifyHeader(h.chain, header, true) } heighter := func() uint64 { - return blockchain.CurrentBlock().NumberU64() + return h.chain.CurrentBlock().NumberU64() } inserter := func(blocks types.Blocks) (int, error) { // If sync hasn't reached the checkpoint yet, deny importing weird blocks. @@ -169,7 +196,7 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh // the propagated block if the head is too old. Unfortunately there is a corner // case when starting new networks, where the genesis might be ancient (0 unix) // which would prevent full nodes from accepting it. - if manager.blockchain.CurrentBlock().NumberU64() < manager.checkpointNumber { + if h.chain.CurrentBlock().NumberU64() < h.checkpointNumber { log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) return 0, nil } @@ -178,180 +205,88 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh // accept each others' blocks until a restart. Unfortunately we haven't figured // out a way yet where nodes can decide unilaterally whether the network is new // or not. This should be fixed if we figure out a solution. - if atomic.LoadUint32(&manager.fastSync) == 1 { + if atomic.LoadUint32(&h.fastSync) == 1 { log.Warn("Fast syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) return 0, nil } - n, err := manager.blockchain.InsertChain(blocks) + n, err := h.chain.InsertChain(blocks) if err == nil { - atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import + atomic.StoreUint32(&h.acceptTxs, 1) // Mark initial sync done on any fetcher import } return n, err } - manager.blockFetcher = fetcher.NewBlockFetcher(false, nil, blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, nil, inserter, manager.removePeer) + h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) fetchTx := func(peer string, hashes []common.Hash) error { - p := manager.peers.Peer(peer) + p := h.peers.ethPeer(peer) if p == nil { return errors.New("unknown peer") } return p.RequestTxs(hashes) } - manager.txFetcher = fetcher.NewTxFetcher(txpool.Has, txpool.AddRemotes, fetchTx) - - manager.chainSync = newChainSyncer(manager) - - return manager, nil -} - -func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol { - length, ok := protocolLengths[version] - if !ok { - panic("makeProtocol for unknown version") - } - - return p2p.Protocol{ - Name: protocolName, - Version: version, - Length: length, - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return pm.runPeer(pm.newPeer(int(version), p, rw, pm.txpool.Get)) - }, - NodeInfo: func() interface{} { - return pm.NodeInfo() - }, - PeerInfo: func(id enode.ID) interface{} { - if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil { - return p.Info() - } - return nil - }, - } + h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx) + h.chainSync = newChainSyncer(h) + return h, nil } -func (pm *ProtocolManager) removePeer(id string) { - // Short circuit if the peer was already removed - peer := pm.peers.Peer(id) - if peer == nil { - return - } - log.Debug("Removing Ethereum peer", "peer", id) - - // Unregister the peer from the downloader and Ethereum peer set - pm.downloader.UnregisterPeer(id) - pm.txFetcher.Drop(id) - - if err := pm.peers.Unregister(id); err != nil { - log.Error("Peer removal failed", "peer", id, "err", err) - } - // Hard disconnect at the networking layer - if peer != nil { - peer.Peer.Disconnect(p2p.DiscUselessPeer) - } -} - -func (pm *ProtocolManager) Start(maxPeers int) { - pm.maxPeers = maxPeers - - // broadcast transactions - pm.wg.Add(1) - pm.txsCh = make(chan core.NewTxsEvent, txChanSize) - pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh) - go pm.txBroadcastLoop() - - // broadcast mined blocks - pm.wg.Add(1) - pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{}) - go pm.minedBroadcastLoop() - - // start sync handlers - pm.wg.Add(2) - go pm.chainSync.loop() - go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. -} - -func (pm *ProtocolManager) Stop() { - pm.txsSub.Unsubscribe() // quits txBroadcastLoop - pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop - - // Quit chainSync and txsync64. - // After this is done, no new peers will be accepted. - close(pm.quitSync) - pm.wg.Wait() - - // Disconnect existing sessions. - // This also closes the gate for any new registrations on the peer set. - // sessions which are already established but not added to pm.peers yet - // will exit when they try to register. - pm.peers.Close() - pm.peerWG.Wait() - - log.Info("Ethereum protocol stopped") -} - -func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { - return newPeer(pv, p, rw, getPooledTx) -} - -func (pm *ProtocolManager) runPeer(p *peer) error { - if !pm.chainSync.handlePeerEvent(p) { +// runEthPeer +func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + if !h.chainSync.handlePeerEvent(peer) { return p2p.DiscQuitting } - pm.peerWG.Add(1) - defer pm.peerWG.Done() - return pm.handle(p) -} - -// handle is the callback invoked to manage the life cycle of an eth peer. When -// this function terminates, the peer is disconnected. -func (pm *ProtocolManager) handle(p *peer) error { - // Ignore maxPeers if this is a trusted peer - if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted { - return p2p.DiscTooManyPeers - } - p.Log().Debug("Ethereum peer connected", "name", p.Name()) + h.peerWG.Add(1) + defer h.peerWG.Done() // Execute the Ethereum handshake var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() + genesis = h.chain.Genesis() + head = h.chain.CurrentHeader() hash = head.Hash() number = head.Number.Uint64() - td = pm.blockchain.GetTd(hash, number) + td = h.chain.GetTd(hash, number) ) - forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) - if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkID, pm.forkFilter); err != nil { - p.Log().Debug("Ethereum handshake failed", "err", err) + forkID := forkid.NewID(h.chain.Config(), h.chain.Genesis().Hash(), h.chain.CurrentHeader().Number.Uint64()) + if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { + peer.Log().Debug("Ethereum handshake failed", "err", err) return err } + // Ignore maxPeers if this is a trusted peer + if h.peers.Len() >= h.maxPeers && !peer.Peer.Info().Network.Trusted { + return p2p.DiscTooManyPeers + } + peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) // Register the peer locally - if err := pm.peers.Register(p, pm.removePeer); err != nil { - p.Log().Error("Ethereum peer registration failed", "err", err) + if err := h.peers.registerEthPeer(peer); err != nil { + peer.Log().Error("Ethereum peer registration failed", "err", err) return err } - defer pm.removePeer(p.id) + defer h.removePeer(peer.ID()) + p := h.peers.ethPeer(peer.ID()) + if p == nil { + return errors.New("peer dropped during handling") + } // Register the peer in the downloader. If the downloader considers it banned, we disconnect - if err := pm.downloader.RegisterPeer(p.id, p.version, p); err != nil { + if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil { return err } - pm.chainSync.handlePeerEvent(p) + h.chainSync.handlePeerEvent(peer) // Propagate existing transactions. new transactions appearing // after this will be sent via broadcasts. - pm.syncTransactions(p) + h.syncTransactions(peer) // If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse) - if pm.checkpointHash != (common.Hash{}) { + if h.checkpointHash != (common.Hash{}) { // Request the peer's checkpoint header for chain height/weight validation - if err := p.RequestHeadersByNumber(pm.checkpointNumber, 1, 0, false); err != nil { + if err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false); err != nil { return err } // Start a timer to disconnect if the peer doesn't reply in time p.syncDrop = time.AfterFunc(syncChallengeTimeout, func() { - p.Log().Warn("Checkpoint challenge timed out, dropping", "addr", p.RemoteAddr(), "type", p.Name()) - pm.removePeer(p.id) + peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) + h.removePeer(peer.ID()) }) // Make sure it's cleaned up if the peer dies off defer func() { @@ -362,474 +297,115 @@ func (pm *ProtocolManager) handle(p *peer) error { }() } // If we have any explicit whitelist block hashes, request them - for number := range pm.whitelist { - if err := p.RequestHeadersByNumber(number, 1, 0, false); err != nil { + for number := range h.whitelist { + if err := peer.RequestHeadersByNumber(number, 1, 0, false); err != nil { return err } } // Handle incoming messages until the connection is torn down - for { - if err := pm.handleMsg(p); err != nil { - p.Log().Debug("Ethereum message handling failed", "err", err) - return err - } - } + return handler(peer) } -// handleMsg is invoked whenever an inbound message is received from a remote -// peer. The remote connection is torn down upon returning any error. -func (pm *ProtocolManager) handleMsg(p *peer) error { - // Read the next message from the remote peer, and ensure it's fully consumed - msg, err := p.rw.ReadMsg() - if err != nil { +// runSnapPeer +func (h *handler) runSnapPeer(peer *snap.Peer, handler snap.Handler) error { + h.peerWG.Add(1) + defer h.peerWG.Done() + + // Register the peer locally + if err := h.peers.registerSnapPeer(peer); err != nil { + peer.Log().Error("Snapshot peer registration failed", "err", err) return err } - if msg.Size > protocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) - } - defer msg.Discard() - - // Handle the message depending on its contents - switch { - case msg.Code == StatusMsg: - // Status messages should never arrive after the handshake - return errResp(ErrExtraStatusMsg, "uncontrolled status message") - - // Block header query, collect the requested headers and reply - case msg.Code == GetBlockHeadersMsg: - // Decode the complex header query - var query getBlockHeadersData - if err := msg.Decode(&query); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch { - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - first = false - origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = pm.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderRlpSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = (query.Origin.Hash == common.Hash{}) - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := pm.blockchain.GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := pm.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - } - return p.SendBlockHeaders(headers) + defer h.removePeer(peer.ID()) - case msg.Code == BlockHeadersMsg: - // A batch of headers arrived to one of our previous requests - var headers []*types.Header - if err := msg.Decode(&headers); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // If no headers were received, but we're expencting a checkpoint header, consider it that - if len(headers) == 0 && p.syncDrop != nil { - // Stop the timer either way, decide later to drop or not - p.syncDrop.Stop() - p.syncDrop = nil - - // If we're doing a fast sync, we must enforce the checkpoint block to avoid - // eclipse attacks. Unsynced nodes are welcome to connect after we're done - // joining the network - if atomic.LoadUint32(&pm.fastSync) == 1 { - p.Log().Warn("Dropping unsynced node during fast sync", "addr", p.RemoteAddr(), "type", p.Name()) - return errors.New("unsynced node cannot serve fast sync") - } - } - // Filter out any explicitly requested headers, deliver the rest to the downloader - filter := len(headers) == 1 - if filter { - // If it's a potential sync progress check, validate the content and advertised chain weight - if p.syncDrop != nil && headers[0].Number.Uint64() == pm.checkpointNumber { - // Disable the sync drop timer - p.syncDrop.Stop() - p.syncDrop = nil - - // Validate the header and either drop the peer or continue - if headers[0].Hash() != pm.checkpointHash { - return errors.New("checkpoint hash mismatch") - } - return nil - } - // Otherwise if it's a whitelisted block, validate against the set - if want, ok := pm.whitelist[headers[0].Number.Uint64()]; ok { - if hash := headers[0].Hash(); want != hash { - p.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want) - return errors.New("whitelist block mismatch") - } - p.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want) - } - // Irrelevant of the fork checks, send the header to the fetcher just in case - headers = pm.blockFetcher.FilterHeaders(p.id, headers, time.Now()) - } - if len(headers) > 0 || !filter { - err := pm.downloader.DeliverHeaders(p.id, headers) - if err != nil { - log.Debug("Failed to deliver headers", "err", err) - } - } - - case msg.Code == GetBlockBodiesMsg: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather blocks until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - bodies []rlp.RawValue - ) - for bytes < softResponseLimit && len(bodies) < downloader.MaxBlockFetch { - // Retrieve the hash of the next block - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested block body, stopping if enough was found - if data := pm.blockchain.GetBodyRLP(hash); len(data) != 0 { - bodies = append(bodies, data) - bytes += len(data) - } - } - return p.SendBlockBodiesRLP(bodies) - - case msg.Code == BlockBodiesMsg: - // A batch of block bodies arrived to one of our previous requests - var request blockBodiesData - if err := msg.Decode(&request); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Deliver them all to the downloader for queuing - transactions := make([][]*types.Transaction, len(request)) - uncles := make([][]*types.Header, len(request)) - - for i, body := range request { - transactions[i] = body.Transactions - uncles[i] = body.Uncles - } - // Filter out any explicitly requested bodies, deliver the rest to the downloader - filter := len(transactions) > 0 || len(uncles) > 0 - if filter { - transactions, uncles = pm.blockFetcher.FilterBodies(p.id, transactions, uncles, time.Now()) - } - if len(transactions) > 0 || len(uncles) > 0 || !filter { - err := pm.downloader.DeliverBodies(p.id, transactions, uncles) - if err != nil { - log.Debug("Failed to deliver bodies", "err", err) - } - } + if err := h.downloader.SnapSyncer.Register(peer); err != nil { + return err + } + // Handle incoming messages until the connection is torn down + return handler(peer) +} - case p.version >= eth63 && msg.Code == GetNodeDataMsg: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather state data until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - data [][]byte - ) - for bytes < softResponseLimit && len(data) < downloader.MaxStateFetch { - // Retrieve the hash of the next state entry - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested state entry, stopping if enough was found - // todo now the code and trienode is mixed in the protocol level, - // separate these two types. - if !pm.downloader.SyncBloomContains(hash[:]) { - // Only lookup the trie node if there's chance that we actually have it - continue - } - entry, err := pm.blockchain.TrieNode(hash) - if len(entry) == 0 || err != nil { - // Read the contract code with prefix only to save unnecessary lookups. - entry, err = pm.blockchain.ContractCodeWithPrefix(hash) - } - if err == nil && len(entry) > 0 { - data = append(data, entry) - bytes += len(entry) - } - } - return p.SendNodeData(data) +func (h *handler) removePeer(id string) { + // Remove the eth peer if it exists + eth := h.peers.ethPeer(id) + if eth != nil { + log.Debug("Removing Ethereum peer", "peer", id) + h.downloader.UnregisterPeer(id) + h.txFetcher.Drop(id) - case p.version >= eth63 && msg.Code == NodeDataMsg: - // A batch of node state data arrived to one of our previous requests - var data [][]byte - if err := msg.Decode(&data); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) + if err := h.peers.unregisterEthPeer(id); err != nil { + log.Error("Peer removal failed", "peer", id, "err", err) } - // Deliver all to the downloader - if err := pm.downloader.DeliverNodeData(p.id, data); err != nil { - log.Debug("Failed to deliver node state data", "err", err) + } + // Remove the snap peer if it exists + snap := h.peers.snapPeer(id) + if snap != nil { + log.Debug("Removing Snapshot peer", "peer", id) + h.downloader.SnapSyncer.Unregister(id) + if err := h.peers.unregisterSnapPeer(id); err != nil { + log.Error("Peer removal failed", "peer", id, "err", err) } + } + // Hard disconnect at the networking layer + if eth != nil { + eth.Peer.Disconnect(p2p.DiscUselessPeer) + } + if snap != nil { + snap.Peer.Disconnect(p2p.DiscUselessPeer) + } +} - case p.version >= eth63 && msg.Code == GetReceiptsMsg: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather state data until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - receipts []rlp.RawValue - ) - for bytes < softResponseLimit && len(receipts) < downloader.MaxReceiptFetch { - // Retrieve the hash of the next block - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested block's receipts, skipping if unknown to us - results := pm.blockchain.GetReceiptsByHash(hash) - if results == nil { - if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - return p.SendReceiptsRLP(receipts) +func (h *handler) Start(maxPeers int) { + h.maxPeers = maxPeers - case p.version >= eth63 && msg.Code == ReceiptsMsg: - // A batch of receipts arrived to one of our previous requests - var receipts [][]*types.Receipt - if err := msg.Decode(&receipts); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Deliver all to the downloader - if err := pm.downloader.DeliverReceipts(p.id, receipts); err != nil { - log.Debug("Failed to deliver receipts", "err", err) - } + // broadcast transactions + h.wg.Add(1) + h.txsCh = make(chan core.NewTxsEvent, txChanSize) + h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh) + go h.txBroadcastLoop() - case msg.Code == NewBlockHashesMsg: - var announces newBlockHashesData - if err := msg.Decode(&announces); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - // Mark the hashes as present at the remote node - for _, block := range announces { - p.MarkBlock(block.Hash) - } - // Schedule all the unknown hashes for retrieval - unknown := make(newBlockHashesData, 0, len(announces)) - for _, block := range announces { - if !pm.blockchain.HasBlock(block.Hash, block.Number) { - unknown = append(unknown, block) - } - } - for _, block := range unknown { - pm.blockFetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies) - } + // broadcast mined blocks + h.wg.Add(1) + h.minedBlockSub = h.eventMux.Subscribe(core.NewMinedBlockEvent{}) + go h.minedBroadcastLoop() - case msg.Code == NewBlockMsg: - // Retrieve and decode the propagated block - var request newBlockData - if err := msg.Decode(&request); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - if hash := types.CalcUncleHash(request.Block.Uncles()); hash != request.Block.UncleHash() { - log.Warn("Propagated block has invalid uncles", "have", hash, "exp", request.Block.UncleHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if hash := types.DeriveSha(request.Block.Transactions(), trie.NewStackTrie(nil)); hash != request.Block.TxHash() { - log.Warn("Propagated block has invalid body", "have", hash, "exp", request.Block.TxHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if err := request.sanityCheck(); err != nil { - return err - } - request.Block.ReceivedAt = msg.ReceivedAt - request.Block.ReceivedFrom = p - - // Mark the peer as owning the block and schedule it for import - p.MarkBlock(request.Block.Hash()) - pm.blockFetcher.Enqueue(p.id, request.Block) - - // Assuming the block is importable by the peer, but possibly not yet done so, - // calculate the head hash and TD that the peer truly must have. - var ( - trueHead = request.Block.ParentHash() - trueTD = new(big.Int).Sub(request.TD, request.Block.Difficulty()) - ) - // Update the peer's total difficulty if better than the previous - if _, td := p.Head(); trueTD.Cmp(td) > 0 { - p.SetHead(trueHead, trueTD) - pm.chainSync.handlePeerEvent(p) - } + // start sync handlers + h.wg.Add(2) + go h.chainSync.loop() + go h.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. +} - case msg.Code == NewPooledTransactionHashesMsg && p.version >= eth65: - // New transaction announcement arrived, make sure we have - // a valid and fresh chain to handle them - if atomic.LoadUint32(&pm.acceptTxs) == 0 { - break - } - var hashes []common.Hash - if err := msg.Decode(&hashes); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Schedule all the unknown hashes for retrieval - for _, hash := range hashes { - p.MarkTransaction(hash) - } - pm.txFetcher.Notify(p.id, hashes) +func (h *handler) Stop() { + h.txsSub.Unsubscribe() // quits txBroadcastLoop + h.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop - case msg.Code == GetPooledTransactionsMsg && p.version >= eth65: - // Decode the retrieval message - msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size)) - if _, err := msgStream.List(); err != nil { - return err - } - // Gather transactions until the fetch or network limits is reached - var ( - hash common.Hash - bytes int - hashes []common.Hash - txs []rlp.RawValue - ) - for bytes < softResponseLimit { - // Retrieve the hash of the next block - if err := msgStream.Decode(&hash); err == rlp.EOL { - break - } else if err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Retrieve the requested transaction, skipping if unknown to us - tx := pm.txpool.Get(hash) - if tx == nil { - continue - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(tx); err != nil { - log.Error("Failed to encode transaction", "err", err) - } else { - hashes = append(hashes, hash) - txs = append(txs, encoded) - bytes += len(encoded) - } - } - return p.SendPooledTransactionsRLP(hashes, txs) + // Quit chainSync and txsync64. + // After this is done, no new peers will be accepted. + close(h.quitSync) + h.wg.Wait() - case msg.Code == TransactionMsg || (msg.Code == PooledTransactionsMsg && p.version >= eth65): - // Transactions arrived, make sure we have a valid and fresh chain to handle them - if atomic.LoadUint32(&pm.acceptTxs) == 0 { - break - } - // Transactions can be processed, parse all of them and deliver to the pool - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - for i, tx := range txs { - // Validate and mark the remote transaction - if tx == nil { - return errResp(ErrDecode, "transaction %d is nil", i) - } - p.MarkTransaction(tx.Hash()) - } - pm.txFetcher.Enqueue(p.id, txs, msg.Code == PooledTransactionsMsg) + // Disconnect existing sessions. + // This also closes the gate for any new registrations on the peer set. + // sessions which are already established but not added to h.peers yet + // will exit when they try to register. + h.peers.close() + h.peerWG.Wait() - default: - return errResp(ErrInvalidMsgCode, "%v", msg.Code) - } - return nil + log.Info("Ethereum protocol stopped") } // BroadcastBlock will either propagate a block to a subset of its peers, or // will only announce its availability (depending what's requested). -func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { +func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { hash := block.Hash() - peers := pm.peers.PeersWithoutBlock(hash) + peers := h.peers.ethPeersWithoutBlock(hash) // If propagation is requested, send to a subset of the peer if propagate { // Calculate the TD of the block (it's not imported yet, so block.Td is not valid) var td *big.Int - if parent := pm.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil { - td = new(big.Int).Add(block.Difficulty(), pm.blockchain.GetTd(block.ParentHash(), block.NumberU64()-1)) + if parent := h.chain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil { + td = new(big.Int).Add(block.Difficulty(), h.chain.GetTd(block.ParentHash(), block.NumberU64()-1)) } else { log.Error("Propagating dangling block", "number", block.Number(), "hash", hash) return @@ -843,7 +419,7 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { return } // Otherwise if the block is indeed in out own chain, announce it - if pm.blockchain.HasBlock(hash, block.NumberU64()) { + if h.chain.HasBlock(hash, block.NumberU64()) { for _, peer := range peers { peer.AsyncSendNewBlockHash(block) } @@ -853,15 +429,15 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) { // BroadcastTransactions will propagate a batch of transactions to all peers which are not known to // already have the given transaction. -func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propagate bool) { +func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) { var ( - txset = make(map[*peer][]common.Hash) - annos = make(map[*peer][]common.Hash) + txset = make(map[*ethPeer][]common.Hash) + annos = make(map[*ethPeer][]common.Hash) ) // Broadcast transactions to a batch of peers not knowing about it if propagate { for _, tx := range txs { - peers := pm.peers.PeersWithoutTx(tx.Hash()) + peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] @@ -877,13 +453,13 @@ func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propaga } // Otherwise only broadcast the announcement to peers for _, tx := range txs { - peers := pm.peers.PeersWithoutTx(tx.Hash()) + peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) for _, peer := range peers { annos[peer] = append(annos[peer], tx.Hash()) } } for peer, hashes := range annos { - if peer.version >= eth65 { + if peer.Version() >= eth.ETH65 { peer.AsyncSendPooledTransactionHashes(hashes) } else { peer.AsyncSendTransactions(hashes) @@ -892,56 +468,29 @@ func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propaga } // minedBroadcastLoop sends mined blocks to connected peers. -func (pm *ProtocolManager) minedBroadcastLoop() { - defer pm.wg.Done() +func (h *handler) minedBroadcastLoop() { + defer h.wg.Done() - for obj := range pm.minedBlockSub.Chan() { + for obj := range h.minedBlockSub.Chan() { if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok { - pm.BroadcastBlock(ev.Block, true) // First propagate block to peers - pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest + h.BroadcastBlock(ev.Block, true) // First propagate block to peers + h.BroadcastBlock(ev.Block, false) // Only then announce to the rest } } } // txBroadcastLoop announces new transactions to connected peers. -func (pm *ProtocolManager) txBroadcastLoop() { - defer pm.wg.Done() +func (h *handler) txBroadcastLoop() { + defer h.wg.Done() for { select { - case event := <-pm.txsCh: - // For testing purpose only, disable propagation - if pm.broadcastTxAnnouncesOnly { - pm.BroadcastTransactions(event.Txs, false) - continue - } - pm.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers - pm.BroadcastTransactions(event.Txs, false) // Only then announce to the rest + case event := <-h.txsCh: + h.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers + h.BroadcastTransactions(event.Txs, false) // Only then announce to the rest - case <-pm.txsSub.Err(): + case <-h.txsSub.Err(): return } } } - -// NodeInfo represents a short summary of the Ethereum sub-protocol metadata -// known about the host peer. -type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) - Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain - Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block - Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules - Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block -} - -// NodeInfo retrieves some protocol metadata about the running host node. -func (pm *ProtocolManager) NodeInfo() *NodeInfo { - currentBlock := pm.blockchain.CurrentBlock() - return &NodeInfo{ - Network: pm.networkID, - Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), - Genesis: pm.blockchain.Genesis().Hash(), - Config: pm.blockchain.Config(), - Head: currentBlock.Hash(), - } -} diff --git a/eth/handler_eth.go b/eth/handler_eth.go new file mode 100644 index 0000000000..84bdac659a --- /dev/null +++ b/eth/handler_eth.go @@ -0,0 +1,218 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/trie" +) + +// ethHandler implements the eth.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type ethHandler handler + +func (h *ethHandler) Chain() *core.BlockChain { return h.chain } +func (h *ethHandler) StateBloom() *trie.SyncBloom { return h.stateBloom } +func (h *ethHandler) TxPool() eth.TxPool { return h.txpool } + +// RunPeer is invoked when a peer joins on the `eth` protocol. +func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { + return (*handler)(h).runEthPeer(peer, hand) +} + +// PeerInfo retrieves all known `eth` information about a peer. +func (h *ethHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.ethPeer(id.String()); p != nil { + return p.info() + } + return nil +} + +// AcceptTxs retrieves whether transaction processing is enabled on the node +// or if inbound transactions should simply be dropped. +func (h *ethHandler) AcceptTxs() bool { + return atomic.LoadUint32(&h.acceptTxs) == 1 +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + // Consume any broadcasts and announces, forwarding the rest to the downloader + switch packet := packet.(type) { + case *eth.BlockHeadersPacket: + return h.handleHeaders(peer, *packet) + + case *eth.BlockBodiesPacket: + txset, uncleset := packet.Unpack() + return h.handleBodies(peer, txset, uncleset) + + case *eth.NodeDataPacket: + if err := h.downloader.DeliverNodeData(peer.ID(), *packet); err != nil { + log.Debug("Failed to deliver node state data", "err", err) + } + return nil + + case *eth.ReceiptsPacket: + if err := h.downloader.DeliverReceipts(peer.ID(), *packet); err != nil { + log.Debug("Failed to deliver receipts", "err", err) + } + return nil + + case *eth.NewBlockHashesPacket: + hashes, numbers := packet.Unpack() + return h.handleBlockAnnounces(peer, hashes, numbers) + + case *eth.NewBlockPacket: + return h.handleBlockBroadcast(peer, packet.Block, packet.TD) + + case *eth.NewPooledTransactionHashesPacket: + return h.txFetcher.Notify(peer.ID(), *packet) + + case *eth.TransactionsPacket: + return h.txFetcher.Enqueue(peer.ID(), *packet, false) + + case *eth.PooledTransactionsPacket: + return h.txFetcher.Enqueue(peer.ID(), *packet, true) + + default: + return fmt.Errorf("unexpected eth packet type: %T", packet) + } +} + +// handleHeaders is invoked from a peer's message handler when it transmits a batch +// of headers for the local node to process. +func (h *ethHandler) handleHeaders(peer *eth.Peer, headers []*types.Header) error { + p := h.peers.ethPeer(peer.ID()) + if p == nil { + return errors.New("unregistered during callback") + } + // If no headers were received, but we're expencting a checkpoint header, consider it that + if len(headers) == 0 && p.syncDrop != nil { + // Stop the timer either way, decide later to drop or not + p.syncDrop.Stop() + p.syncDrop = nil + + // If we're doing a fast (or snap) sync, we must enforce the checkpoint block to avoid + // eclipse attacks. Unsynced nodes are welcome to connect after we're done + // joining the network + if atomic.LoadUint32(&h.fastSync) == 1 { + peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name()) + return errors.New("unsynced node cannot serve sync") + } + } + // Filter out any explicitly requested headers, deliver the rest to the downloader + filter := len(headers) == 1 + if filter { + // If it's a potential sync progress check, validate the content and advertised chain weight + if p.syncDrop != nil && headers[0].Number.Uint64() == h.checkpointNumber { + // Disable the sync drop timer + p.syncDrop.Stop() + p.syncDrop = nil + + // Validate the header and either drop the peer or continue + if headers[0].Hash() != h.checkpointHash { + return errors.New("checkpoint hash mismatch") + } + return nil + } + // Otherwise if it's a whitelisted block, validate against the set + if want, ok := h.whitelist[headers[0].Number.Uint64()]; ok { + if hash := headers[0].Hash(); want != hash { + peer.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want) + return errors.New("whitelist block mismatch") + } + peer.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want) + } + // Irrelevant of the fork checks, send the header to the fetcher just in case + headers = h.blockFetcher.FilterHeaders(peer.ID(), headers, time.Now()) + } + if len(headers) > 0 || !filter { + err := h.downloader.DeliverHeaders(peer.ID(), headers) + if err != nil { + log.Debug("Failed to deliver headers", "err", err) + } + } + return nil +} + +// handleBodies is invoked from a peer's message handler when it transmits a batch +// of block bodies for the local node to process. +func (h *ethHandler) handleBodies(peer *eth.Peer, txs [][]*types.Transaction, uncles [][]*types.Header) error { + // Filter out any explicitly requested bodies, deliver the rest to the downloader + filter := len(txs) > 0 || len(uncles) > 0 + if filter { + txs, uncles = h.blockFetcher.FilterBodies(peer.ID(), txs, uncles, time.Now()) + } + if len(txs) > 0 || len(uncles) > 0 || !filter { + err := h.downloader.DeliverBodies(peer.ID(), txs, uncles) + if err != nil { + log.Debug("Failed to deliver bodies", "err", err) + } + } + return nil +} + +// handleBlockAnnounces is invoked from a peer's message handler when it transmits a +// batch of block announcements for the local node to process. +func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, numbers []uint64) error { + // Schedule all the unknown hashes for retrieval + var ( + unknownHashes = make([]common.Hash, 0, len(hashes)) + unknownNumbers = make([]uint64, 0, len(numbers)) + ) + for i := 0; i < len(hashes); i++ { + if !h.chain.HasBlock(hashes[i], numbers[i]) { + unknownHashes = append(unknownHashes, hashes[i]) + unknownNumbers = append(unknownNumbers, numbers[i]) + } + } + for i := 0; i < len(unknownHashes); i++ { + h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies) + } + return nil +} + +// handleBlockBroadcast is invoked from a peer's message handler when it transmits a +// block broadcast for the local node to process. +func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td *big.Int) error { + // Schedule the block for import + h.blockFetcher.Enqueue(peer.ID(), block) + + // Assuming the block is importable by the peer, but possibly not yet done so, + // calculate the head hash and TD that the peer truly must have. + var ( + trueHead = block.ParentHash() + trueTD = new(big.Int).Sub(td, block.Difficulty()) + ) + // Update the peer's total difficulty if better than the previous + if _, td := peer.Head(); trueTD.Cmp(td) > 0 { + peer.SetHead(trueHead, trueTD) + h.chainSync.handlePeerEvent(peer) + } + return nil +} diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go new file mode 100644 index 0000000000..0e5c0c90ee --- /dev/null +++ b/eth/handler_eth_test.go @@ -0,0 +1,740 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "fmt" + "math/big" + "math/rand" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +// testEthHandler is a mock event handler to listen for inbound network requests +// on the `eth` protocol and convert them into a more easily testable form. +type testEthHandler struct { + blockBroadcasts event.Feed + txAnnounces event.Feed + txBroadcasts event.Feed +} + +func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") } +func (h *testEthHandler) StateBloom() *trie.SyncBloom { panic("no backing state bloom") } +func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") } +func (h *testEthHandler) AcceptTxs() bool { return true } +func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") } +func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") } + +func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { + switch packet := packet.(type) { + case *eth.NewBlockPacket: + h.blockBroadcasts.Send(packet.Block) + return nil + + case *eth.NewPooledTransactionHashesPacket: + h.txAnnounces.Send(([]common.Hash)(*packet)) + return nil + + case *eth.TransactionsPacket: + h.txBroadcasts.Send(([]*types.Transaction)(*packet)) + return nil + + case *eth.PooledTransactionsPacket: + h.txBroadcasts.Send(([]*types.Transaction)(*packet)) + return nil + + default: + panic(fmt.Sprintf("unexpected eth packet type in tests: %T", packet)) + } +} + +// Tests that peers are correctly accepted (or rejected) based on the advertised +// fork IDs in the protocol handshake. +func TestForkIDSplit64(t *testing.T) { testForkIDSplit(t, 64) } +func TestForkIDSplit65(t *testing.T) { testForkIDSplit(t, 65) } + +func testForkIDSplit(t *testing.T, protocol uint) { + t.Parallel() + + var ( + engine = ethash.NewFaker() + + configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)} + configProFork = ¶ms.ChainConfig{ + HomesteadBlock: big.NewInt(1), + EIP150Block: big.NewInt(2), + EIP155Block: big.NewInt(2), + EIP158Block: big.NewInt(2), + ByzantiumBlock: big.NewInt(3), + } + dbNoFork = rawdb.NewMemoryDatabase() + dbProFork = rawdb.NewMemoryDatabase() + + gspecNoFork = &core.Genesis{Config: configNoFork} + gspecProFork = &core.Genesis{Config: configProFork} + + genesisNoFork = gspecNoFork.MustCommit(dbNoFork) + genesisProFork = gspecProFork.MustCommit(dbProFork) + + chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil) + chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil) + + blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil) + blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil) + + ethNoFork, _ = newHandler(&handlerConfig{ + Database: dbNoFork, + Chain: chainNoFork, + TxPool: newTestTxPool(), + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + ethProFork, _ = newHandler(&handlerConfig{ + Database: dbProFork, + Chain: chainProFork, + TxPool: newTestTxPool(), + Network: 1, + Sync: downloader.FullSync, + BloomCache: 1, + }) + ) + ethNoFork.Start(1000) + ethProFork.Start(1000) + + // Clean up everything after ourselves + defer chainNoFork.Stop() + defer chainProFork.Stop() + + defer ethNoFork.Stop() + defer ethProFork.Stop() + + // Both nodes should allow the other to connect (same genesis, next fork is the same) + p2pNoFork, p2pProFork := p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc := make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + t.Fatalf("frontier nofork <-> profork failed: %v", err) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("frontier nofork <-> profork handler timeout") + } + } + // Progress into Homestead. Fork's match, so we don't care what the future holds + chainNoFork.InsertChain(blocksNoFork[:1]) + chainProFork.InsertChain(blocksProFork[:1]) + + p2pNoFork, p2pProFork = p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc = make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + t.Fatalf("homestead nofork <-> profork failed: %v", err) + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("homestead nofork <-> profork handler timeout") + } + } + // Progress into Spurious. Forks mismatch, signalling differing chains, reject + chainNoFork.InsertChain(blocksNoFork[1:2]) + chainProFork.InsertChain(blocksProFork[1:2]) + + p2pNoFork, p2pProFork = p2p.MsgPipe() + defer p2pNoFork.Close() + defer p2pProFork.Close() + + peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + defer peerNoFork.Close() + defer peerProFork.Close() + + errc = make(chan error, 2) + go func(errc chan error) { + errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil }) + }(errc) + go func(errc chan error) { + errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil }) + }(errc) + + var successes int + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err == nil { + successes++ + if successes == 2 { // Only one side disconnects + t.Fatalf("fork ID rejection didn't happen") + } + } + case <-time.After(250 * time.Millisecond): + t.Fatalf("split peers not rejected") + } + } +} + +// Tests that received transactions are added to the local pool. +func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) } +func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) } + +func testRecvTransactions(t *testing.T, protocol uint) { + t.Parallel() + + // Create a message handler, configure it to accept transactions and watch them + handler := newTestHandler() + defer handler.close() + + handler.handler.acceptTxs = 1 // mark synced to accept transactions + + txs := make(chan core.NewTxsEvent) + sub := handler.txpool.SubscribeNewTxsEvent(txs) + defer sub.Unsubscribe() + + // Create a source peer to send messages through and a sink handler to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool) + defer src.Close() + defer sink.Close() + + go handler.handler.runEthPeer(sink, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a source handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.NumberU64()) + ) + if err := src.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // Send the transaction to the sink and verify that it's added to the tx pool + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + if err := src.SendTransactions([]*types.Transaction{tx}); err != nil { + t.Fatalf("failed to send transaction: %v", err) + } + select { + case event := <-txs: + if len(event.Txs) != 1 { + t.Errorf("wrong number of added transactions: got %d, want 1", len(event.Txs)) + } else if event.Txs[0].Hash() != tx.Hash() { + t.Errorf("added wrong tx hash: got %v, want %v", event.Txs[0].Hash(), tx.Hash()) + } + case <-time.After(2 * time.Second): + t.Errorf("no NewTxsEvent received within 2 seconds") + } +} + +// This test checks that pending transactions are sent. +func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) } +func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) } + +func testSendTransactions(t *testing.T, protocol uint) { + t.Parallel() + + // Create a message handler and fill the pool with big transactions + handler := newTestHandler() + defer handler.close() + + insert := make([]*types.Transaction, 100) + for nonce := range insert { + tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, txsyncPackSize/10)) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + insert[nonce] = tx + } + go handler.txpool.AddRemotes(insert) // Need goroutine to not block on feed + time.Sleep(250 * time.Millisecond) // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join) + + // Create a source handler to send messages through and a sink peer to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool) + defer src.Close() + defer sink.Close() + + go handler.handler.runEthPeer(src, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a source handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.NumberU64()) + ) + if err := sink.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // After the handshake completes, the source handler should stream the sink + // the transactions, subscribe to all inbound network events + backend := new(testEthHandler) + + anns := make(chan []common.Hash) + annSub := backend.txAnnounces.Subscribe(anns) + defer annSub.Unsubscribe() + + bcasts := make(chan []*types.Transaction) + bcastSub := backend.txBroadcasts.Subscribe(bcasts) + defer bcastSub.Unsubscribe() + + go eth.Handle(backend, sink) + + // Make sure we get all the transactions on the correct channels + seen := make(map[common.Hash]struct{}) + for len(seen) < len(insert) { + switch protocol { + case 63, 64: + select { + case <-anns: + t.Errorf("tx announce received on pre eth/65") + case txs := <-bcasts: + for _, tx := range txs { + if _, ok := seen[tx.Hash()]; ok { + t.Errorf("duplicate transaction announced: %x", tx.Hash()) + } + seen[tx.Hash()] = struct{}{} + } + } + case 65: + select { + case hashes := <-anns: + for _, hash := range hashes { + if _, ok := seen[hash]; ok { + t.Errorf("duplicate transaction announced: %x", hash) + } + seen[hash] = struct{}{} + } + case <-bcasts: + t.Errorf("initial tx broadcast received on post eth/65") + } + + default: + panic("unsupported protocol, please extend test") + } + } + for _, tx := range insert { + if _, ok := seen[tx.Hash()]; !ok { + t.Errorf("missing transaction: %x", tx.Hash()) + } + } +} + +// Tests that transactions get propagated to all attached peers, either via direct +// broadcasts or via announcements/retrievals. +func TestTransactionPropagation64(t *testing.T) { testTransactionPropagation(t, 64) } +func TestTransactionPropagation65(t *testing.T) { testTransactionPropagation(t, 65) } + +func testTransactionPropagation(t *testing.T, protocol uint) { + t.Parallel() + + // Create a source handler to send transactions from and a number of sinks + // to receive them. We need multiple sinks since a one-to-one peering would + // broadcast all transactions without announcement. + source := newTestHandler() + defer source.close() + + sinks := make([]*testHandler, 10) + for i := 0; i < len(sinks); i++ { + sinks[i] = newTestHandler() + defer sinks[i].close() + + sinks[i].handler.acceptTxs = 1 // mark synced to accept transactions + } + // Interconnect all the sink handlers with the source handler + for i, sink := range sinks { + sink := sink // Closure for gorotuine below + + sourcePipe, sinkPipe := p2p.MsgPipe() + defer sourcePipe.Close() + defer sinkPipe.Close() + + sourcePeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, source.txpool) + sinkPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, sink.txpool) + defer sourcePeer.Close() + defer sinkPeer.Close() + + go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + go sink.handler.runEthPeer(sinkPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(sink.handler), peer) + }) + } + // Subscribe to all the transaction pools + txChs := make([]chan core.NewTxsEvent, len(sinks)) + for i := 0; i < len(sinks); i++ { + txChs[i] = make(chan core.NewTxsEvent, 1024) + + sub := sinks[i].txpool.SubscribeNewTxsEvent(txChs[i]) + defer sub.Unsubscribe() + } + // Fill the source pool with transactions and wait for them at the sinks + txs := make([]*types.Transaction, 1024) + for nonce := range txs { + tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + txs[nonce] = tx + } + source.txpool.AddRemotes(txs) + + // Iterate through all the sinks and ensure they all got the transactions + for i := range sinks { + for arrived := 0; arrived < len(txs); { + select { + case event := <-txChs[i]: + arrived += len(event.Txs) + case <-time.NewTimer(time.Second).C: + t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs)) + } + } + } +} + +// Tests that post eth protocol handshake, clients perform a mutual checkpoint +// challenge to validate each other's chains. Hash mismatches, or missing ones +// during a fast sync should lead to the peer getting dropped. +func TestCheckpointChallenge(t *testing.T) { + tests := []struct { + syncmode downloader.SyncMode + checkpoint bool + timeout bool + empty bool + match bool + drop bool + }{ + // If checkpointing is not enabled locally, don't challenge and don't drop + {downloader.FullSync, false, false, false, false, false}, + {downloader.FastSync, false, false, false, false, false}, + + // If checkpointing is enabled locally and remote response is empty, only drop during fast sync + {downloader.FullSync, true, false, true, false, false}, + {downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer + + // If checkpointing is enabled locally and remote response mismatches, always drop + {downloader.FullSync, true, false, false, false, true}, + {downloader.FastSync, true, false, false, false, true}, + + // If checkpointing is enabled locally and remote response matches, never drop + {downloader.FullSync, true, false, false, true, false}, + {downloader.FastSync, true, false, false, true, false}, + + // If checkpointing is enabled locally and remote times out, always drop + {downloader.FullSync, true, true, false, true, true}, + {downloader.FastSync, true, true, false, true, true}, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) { + testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop) + }) + } +} + +func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) { + // Reduce the checkpoint handshake challenge timeout + defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout) + syncChallengeTimeout = 250 * time.Millisecond + + // Create a test handler and inject a CHT into it. The injection is a bit + // ugly, but it beats creating everything manually just to avoid reaching + // into the internals a bit. + handler := newTestHandler() + defer handler.close() + + if syncmode == downloader.FastSync { + atomic.StoreUint32(&handler.handler.fastSync, 1) + } else { + atomic.StoreUint32(&handler.handler.fastSync, 0) + } + var response *types.Header + if checkpoint { + number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1 + response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")} + + handler.handler.checkpointNumber = number + handler.handler.checkpointHash = response.Hash() + } + // Create a challenger peer and a challenged one + p2pLocal, p2pRemote := p2p.MsgPipe() + defer p2pLocal.Close() + defer p2pRemote.Close() + + local := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool) + remote := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool) + defer local.Close() + defer remote.Close() + + go handler.handler.runEthPeer(local, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + // Run the handshake locally to avoid spinning up a remote handler + var ( + genesis = handler.chain.Genesis() + head = handler.chain.CurrentBlock() + td = handler.chain.GetTd(head.Hash(), head.NumberU64()) + ) + if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // Connect a new peer and check that we receive the checkpoint challenge + if checkpoint { + if err := remote.ExpectRequestHeadersByNumber(response.Number.Uint64(), 1, 0, false); err != nil { + t.Fatalf("challenge mismatch: %v", err) + } + // Create a block to reply to the challenge if no timeout is simulated + if !timeout { + if empty { + if err := remote.SendBlockHeaders([]*types.Header{}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + } else if match { + if err := remote.SendBlockHeaders([]*types.Header{response}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + } else { + if err := remote.SendBlockHeaders([]*types.Header{{Number: response.Number}}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + } + } + } + // Wait until the test timeout passes to ensure proper cleanup + time.Sleep(syncChallengeTimeout + 300*time.Millisecond) + + // Verify that the remote peer is maintained or dropped + if drop { + if peers := handler.handler.peers.Len(); peers != 0 { + t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) + } + } else { + if peers := handler.handler.peers.Len(); peers != 1 { + t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) + } + } +} + +// Tests that blocks are broadcast to a sqrt number of peers only. +func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) } +func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) } +func TestBroadcastBlock3Peers(t *testing.T) { testBroadcastBlock(t, 3, 1) } +func TestBroadcastBlock4Peers(t *testing.T) { testBroadcastBlock(t, 4, 2) } +func TestBroadcastBlock5Peers(t *testing.T) { testBroadcastBlock(t, 5, 2) } +func TestBroadcastBlock8Peers(t *testing.T) { testBroadcastBlock(t, 9, 3) } +func TestBroadcastBlock12Peers(t *testing.T) { testBroadcastBlock(t, 12, 3) } +func TestBroadcastBlock16Peers(t *testing.T) { testBroadcastBlock(t, 16, 4) } +func TestBroadcastBloc26Peers(t *testing.T) { testBroadcastBlock(t, 26, 5) } +func TestBroadcastBlock100Peers(t *testing.T) { testBroadcastBlock(t, 100, 10) } + +func testBroadcastBlock(t *testing.T, peers, bcasts int) { + t.Parallel() + + // Create a source handler to broadcast blocks from and a number of sinks + // to receive them. + source := newTestHandlerWithBlocks(1) + defer source.close() + + sinks := make([]*testEthHandler, peers) + for i := 0; i < len(sinks); i++ { + sinks[i] = new(testEthHandler) + } + // Interconnect all the sink handlers with the source handler + var ( + genesis = source.chain.Genesis() + td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64()) + ) + for i, sink := range sinks { + sink := sink // Closure for gorotuine below + + sourcePipe, sinkPipe := p2p.MsgPipe() + defer sourcePipe.Close() + defer sinkPipe.Close() + + sourcePeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil) + sinkPeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil) + defer sourcePeer.Close() + defer sinkPeer.Close() + + go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + if err := sinkPeer.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + go eth.Handle(sink, sinkPeer) + } + // Subscribe to all the transaction pools + blockChs := make([]chan *types.Block, len(sinks)) + for i := 0; i < len(sinks); i++ { + blockChs[i] = make(chan *types.Block, 1) + defer close(blockChs[i]) + + sub := sinks[i].blockBroadcasts.Subscribe(blockChs[i]) + defer sub.Unsubscribe() + } + // Initiate a block propagation across the peers + time.Sleep(100 * time.Millisecond) + source.handler.BroadcastBlock(source.chain.CurrentBlock(), true) + + // Iterate through all the sinks and ensure the correct number got the block + done := make(chan struct{}, peers) + for _, ch := range blockChs { + ch := ch + go func() { + <-ch + done <- struct{}{} + }() + } + var received int + for { + select { + case <-done: + received++ + + case <-time.After(100 * time.Millisecond): + if received != bcasts { + t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts) + } + return + } + } +} + +// Tests that a propagated malformed block (uncles or transactions don't match +// with the hashes in the header) gets discarded and not broadcast forward. +func TestBroadcastMalformedBlock64(t *testing.T) { testBroadcastMalformedBlock(t, 64) } +func TestBroadcastMalformedBlock65(t *testing.T) { testBroadcastMalformedBlock(t, 65) } + +func testBroadcastMalformedBlock(t *testing.T, protocol uint) { + t.Parallel() + + // Create a source handler to broadcast blocks from and a number of sinks + // to receive them. + source := newTestHandlerWithBlocks(1) + defer source.close() + + // Create a source handler to send messages through and a sink peer to receive them + p2pSrc, p2pSink := p2p.MsgPipe() + defer p2pSrc.Close() + defer p2pSink.Close() + + src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, source.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, source.txpool) + defer src.Close() + defer sink.Close() + + go source.handler.runEthPeer(src, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(source.handler), peer) + }) + // Run the handshake locally to avoid spinning up a sink handler + var ( + genesis = source.chain.Genesis() + td = source.chain.GetTd(genesis.Hash(), genesis.NumberU64()) + ) + if err := sink.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil { + t.Fatalf("failed to run protocol handshake") + } + // After the handshake completes, the source handler should stream the sink + // the blocks, subscribe to inbound network events + backend := new(testEthHandler) + + blocks := make(chan *types.Block, 1) + sub := backend.blockBroadcasts.Subscribe(blocks) + defer sub.Unsubscribe() + + go eth.Handle(backend, sink) + + // Create various combinations of malformed blocks + head := source.chain.CurrentBlock() + + malformedUncles := head.Header() + malformedUncles.UncleHash[0]++ + malformedTransactions := head.Header() + malformedTransactions.TxHash[0]++ + malformedEverything := head.Header() + malformedEverything.UncleHash[0]++ + malformedEverything.TxHash[0]++ + + // Try to broadcast all malformations and ensure they all get discarded + for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} { + block := types.NewBlockWithHeader(header).WithBody(head.Transactions(), head.Uncles()) + if err := src.SendNewBlock(block, big.NewInt(131136)); err != nil { + t.Fatalf("failed to broadcast block: %v", err) + } + select { + case <-blocks: + t.Fatalf("malformed block forwarded") + case <-time.After(100 * time.Millisecond): + } + } +} diff --git a/eth/handler_snap.go b/eth/handler_snap.go new file mode 100644 index 0000000000..25975bf60b --- /dev/null +++ b/eth/handler_snap.go @@ -0,0 +1,48 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// snapHandler implements the snap.Backend interface to handle the various network +// packets that are sent as replies or broadcasts. +type snapHandler handler + +func (h *snapHandler) Chain() *core.BlockChain { return h.chain } + +// RunPeer is invoked when a peer joins on the `snap` protocol. +func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error { + return (*handler)(h).runSnapPeer(peer, hand) +} + +// PeerInfo retrieves all known `snap` information about a peer. +func (h *snapHandler) PeerInfo(id enode.ID) interface{} { + if p := h.peers.snapPeer(id.String()); p != nil { + return p.info() + } + return nil +} + +// Handle is invoked from a peer's message handler when it receives a new remote +// message that the handler couldn't consume and serve itself. +func (h *snapHandler) Handle(peer *snap.Peer, packet snap.Packet) error { + return h.downloader.DeliverSnapPacket(peer, packet) +} diff --git a/eth/handler_test.go b/eth/handler_test.go index fc6c6f2745..a90ef5c348 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -17,678 +17,154 @@ package eth import ( - "fmt" - "math" "math/big" - "math/rand" - "testing" - "time" + "sort" + "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" ) -// Tests that block headers can be retrieved from a remote chain based on user queries. -func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) } -func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") -func testGetBlockHeaders(t *testing.T, protocol int) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxHashFetch+15, nil, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) - // Create a "random" unknown hash for testing - var unknown common.Hash - for i := range unknown { - unknown[i] = byte(i) - } - // Create a batch of tests for various scenarios - limit := uint64(downloader.MaxHeaderFetch) - tests := []struct { - query *getBlockHeadersData // The query to execute for header retrieval - expect []common.Hash // The hashes of the block whose headers are expected - }{ - // A single random block should be retrievable by hash and number too - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, - []common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, - []common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, - }, - // Multiple headers should be retrievable in both directions - { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 1).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 2).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 1).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 2).Hash(), - }, - }, - // Multiple headers with skip lists should be retrievable - { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 4).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 + 8).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(limit / 2).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 4).Hash(), - pm.blockchain.GetBlockByNumber(limit/2 - 8).Hash(), - }, - }, - // The chain endpoints should be retrievable - { - &getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, - []common.Hash{pm.blockchain.GetBlockByNumber(0).Hash()}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64()}, Amount: 1}, - []common.Hash{pm.blockchain.CurrentBlock().Hash()}, - }, - // Ensure protocol limits are honored - { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, - pm.blockchain.GetBlockHashesFromHash(pm.blockchain.CurrentBlock().Hash(), limit), - }, - // Check that requesting more than available is handled gracefully - { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(), - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(4).Hash(), - pm.blockchain.GetBlockByNumber(0).Hash(), - }, - }, - // Check that requesting more than available is handled gracefully, even if mid skip - { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(), - pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 1).Hash(), - }, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(4).Hash(), - pm.blockchain.GetBlockByNumber(1).Hash(), - }, - }, - // Check a corner case where requesting more can iterate past the endpoints - { - &getBlockHeadersData{Origin: hashOrNumber{Number: 2}, Amount: 5, Reverse: true}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(2).Hash(), - pm.blockchain.GetBlockByNumber(1).Hash(), - pm.blockchain.GetBlockByNumber(0).Hash(), - }, - }, - // Check a corner case where skipping overflow loops back into the chain start - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(3).Hash(), - }, - }, - // Check a corner case where skipping overflow loops back to the same header - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64}, - []common.Hash{ - pm.blockchain.GetBlockByNumber(1).Hash(), - }, - }, - // Check that non existing headers aren't returned - { - &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, - []common.Hash{}, - }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() + 1}, Amount: 1}, - []common.Hash{}, - }, - } - // Run each of the tests and verify the results against the chain - for i, tt := range tests { - // Collect the headers to expect in the response - headers := []*types.Header{} - for _, hash := range tt.expect { - headers = append(headers, pm.blockchain.GetBlockByHash(hash).Header()) - } - // Send the hash request and verify the response - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) - } - // If the test used number origins, repeat with hashes as the too - if tt.query.Origin.Hash == (common.Hash{}) { - if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { - tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 +// testTxPool is a mock transaction pool that blindly accepts all transactions. +// Its goal is to get around setting up a valid statedb for the balance and nonce +// checks. +type testTxPool struct { + pool map[common.Hash]*types.Transaction // Hash map of collected transactions - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) - } - } - } - } + txFeed event.Feed // Notification feed to allow waiting for inclusion + lock sync.RWMutex // Protects the transaction pool } -// Tests that block contents can be retrieved from a remote chain based on their hashes. -func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) } -func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) } - -func testGetBlockBodies(t *testing.T, protocol int) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxBlockFetch+15, nil, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() - - // Create a batch of tests for various scenarios - limit := downloader.MaxBlockFetch - tests := []struct { - random int // Number of blocks to fetch randomly from the chain - explicit []common.Hash // Explicitly requested blocks - available []bool // Availability of explicitly requested blocks - expected int // Total number of existing blocks to expect - }{ - {1, nil, nil, 1}, // A single random block should be retrievable - {10, nil, nil, 10}, // Multiple random blocks should be retrievable - {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable - {limit + 1, nil, nil, limit}, // No more than the possible block count should be returned - {0, []common.Hash{pm.blockchain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable - {0, []common.Hash{pm.blockchain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable - {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned - - // Existing and non-existing blocks interleaved should not cause problems - {0, []common.Hash{ - {}, - pm.blockchain.GetBlockByNumber(1).Hash(), - {}, - pm.blockchain.GetBlockByNumber(10).Hash(), - {}, - pm.blockchain.GetBlockByNumber(100).Hash(), - {}, - }, []bool{false, true, false, true, false, true, false}, 3}, - } - // Run each of the tests and verify the results against the chain - for i, tt := range tests { - // Collect the hashes to request, and the response to expect - hashes, seen := []common.Hash{}, make(map[int64]bool) - bodies := []*blockBody{} - - for j := 0; j < tt.random; j++ { - for { - num := rand.Int63n(int64(pm.blockchain.CurrentBlock().NumberU64())) - if !seen[num] { - seen[num] = true - - block := pm.blockchain.GetBlockByNumber(uint64(num)) - hashes = append(hashes, block.Hash()) - if len(bodies) < tt.expected { - bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) - } - break - } - } - } - for j, hash := range tt.explicit { - hashes = append(hashes, hash) - if tt.available[j] && len(bodies) < tt.expected { - block := pm.blockchain.GetBlockByHash(hash) - bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) - } - } - // Send the hash request and verify the response - p2p.Send(peer.app, 0x05, hashes) - if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil { - t.Errorf("test %d: bodies mismatch: %v", i, err) - } +// newTestTxPool creates a mock transaction pool. +func newTestTxPool() *testTxPool { + return &testTxPool{ + pool: make(map[common.Hash]*types.Transaction), } } -// Tests that the node state database can be retrieved based on hashes. -func TestGetNodeData63(t *testing.T) { testGetNodeData(t, 63) } -func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) } +// Has returns an indicator whether txpool has a transaction +// cached with the given hash. +func (p *testTxPool) Has(hash common.Hash) bool { + p.lock.Lock() + defer p.lock.Unlock() -func testGetNodeData(t *testing.T, protocol int) { - // Define three accounts to simulate transactions with - acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) - - signer := types.HomesteadSigner{} - // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) - generator := func(i int, block *core.BlockGen) { - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) - block.AddTx(tx1) - block.AddTx(tx2) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra([]byte("yeehaw")) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = []byte("foo") - block.AddUncle(b2) - b3 := block.PrevBlock(2).Header() - b3.Extra = []byte("foo") - block.AddUncle(b3) - } - } - // Assemble the test environment - pm, db := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() - - // Fetch for now the entire chain db - hashes := []common.Hash{} - - it := db.NewIterator(nil, nil) - for it.Next() { - if key := it.Key(); len(key) == common.HashLength { - hashes = append(hashes, common.BytesToHash(key)) - } - } - it.Release() - - p2p.Send(peer.app, 0x0d, hashes) - msg, err := peer.app.ReadMsg() - if err != nil { - t.Fatalf("failed to read node data response: %v", err) - } - if msg.Code != 0x0e { - t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) - } - var data [][]byte - if err := msg.Decode(&data); err != nil { - t.Fatalf("failed to decode response node data: %v", err) - } - // Verify that all hashes correspond to the requested data, and reconstruct a state tree - for i, want := range hashes { - if hash := crypto.Keccak256Hash(data[i]); hash != want { - t.Errorf("data hash mismatch: have %x, want %x", hash, want) - } - } - statedb := rawdb.NewMemoryDatabase() - for i := 0; i < len(data); i++ { - statedb.Put(hashes[i].Bytes(), data[i]) - } - accounts := []common.Address{testBank, acc1Addr, acc2Addr} - for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { - trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil) - - for j, acc := range accounts { - state, _ := pm.blockchain.State() - bw := state.GetBalance(acc) - bh := trie.GetBalance(acc) - - if (bw != nil && bh == nil) || (bw == nil && bh != nil) { - t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) - } - if bw != nil && bh != nil && bw.Cmp(bw) != 0 { - t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) - } - } - } + return p.pool[hash] != nil } -// Tests that the transaction receipts can be retrieved based on hashes. -func TestGetReceipt63(t *testing.T) { testGetReceipt(t, 63) } -func TestGetReceipt64(t *testing.T) { testGetReceipt(t, 64) } +// Get retrieves the transaction from local txpool with given +// tx hash. +func (p *testTxPool) Get(hash common.Hash) *types.Transaction { + p.lock.Lock() + defer p.lock.Unlock() -func testGetReceipt(t *testing.T, protocol int) { - // Define three accounts to simulate transactions with - acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) - - signer := types.HomesteadSigner{} - // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) - generator := func(i int, block *core.BlockGen) { - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) - block.AddTx(tx1) - block.AddTx(tx2) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra([]byte("yeehaw")) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = []byte("foo") - block.AddUncle(b2) - b3 := block.PrevBlock(2).Header() - b3.Extra = []byte("foo") - block.AddUncle(b3) - } - } - // Assemble the test environment - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil) - peer, _ := newTestPeer("peer", protocol, pm, true) - defer peer.close() - - // Collect the hashes to request, and the response to expect - hashes, receipts := []common.Hash{}, []types.Receipts{} - for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { - block := pm.blockchain.GetBlockByNumber(i) - - hashes = append(hashes, block.Hash()) - receipts = append(receipts, pm.blockchain.GetReceiptsByHash(block.Hash())) - } - // Send the hash request and verify the response - p2p.Send(peer.app, 0x0f, hashes) - if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil { - t.Errorf("receipts mismatch: %v", err) - } + return p.pool[hash] } -// Tests that post eth protocol handshake, clients perform a mutual checkpoint -// challenge to validate each other's chains. Hash mismatches, or missing ones -// during a fast sync should lead to the peer getting dropped. -func TestCheckpointChallenge(t *testing.T) { - tests := []struct { - syncmode downloader.SyncMode - checkpoint bool - timeout bool - empty bool - match bool - drop bool - }{ - // If checkpointing is not enabled locally, don't challenge and don't drop - {downloader.FullSync, false, false, false, false, false}, - {downloader.FastSync, false, false, false, false, false}, - - // If checkpointing is enabled locally and remote response is empty, only drop during fast sync - {downloader.FullSync, true, false, true, false, false}, - {downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer - - // If checkpointing is enabled locally and remote response mismatches, always drop - {downloader.FullSync, true, false, false, false, true}, - {downloader.FastSync, true, false, false, false, true}, - - // If checkpointing is enabled locally and remote response matches, never drop - {downloader.FullSync, true, false, false, true, false}, - {downloader.FastSync, true, false, false, true, false}, +// AddRemotes appends a batch of transactions to the pool, and notifies any +// listeners if the addition channel is non nil +func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { + p.lock.Lock() + defer p.lock.Unlock() - // If checkpointing is enabled locally and remote times out, always drop - {downloader.FullSync, true, true, false, true, true}, - {downloader.FastSync, true, true, false, true, true}, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) { - testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop) - }) + for _, tx := range txs { + p.pool[tx.Hash()] = tx } + p.txFeed.Send(core.NewTxsEvent{Txs: txs}) + return make([]error, len(txs)) } -func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) { - // Reduce the checkpoint handshake challenge timeout - defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout) - syncChallengeTimeout = 250 * time.Millisecond - - // Initialize a chain and generate a fake CHT if checkpointing is enabled - var ( - db = rawdb.NewMemoryDatabase() - config = new(params.ChainConfig) - ) - (&core.Genesis{Config: config}).MustCommit(db) // Commit genesis block - // If checkpointing is enabled, create and inject a fake CHT and the corresponding - // chllenge response. - var response *types.Header - var cht *params.TrustedCheckpoint - if checkpoint { - index := uint64(rand.Intn(500)) - number := (index+1)*params.CHTFrequency - 1 - response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")} +// Pending returns all the transactions known to the pool +func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) { + p.lock.RLock() + defer p.lock.RUnlock() - cht = ¶ms.TrustedCheckpoint{ - SectionIndex: index, - SectionHead: response.Hash(), - } + batches := make(map[common.Address]types.Transactions) + for _, tx := range p.pool { + from, _ := types.Sender(types.HomesteadSigner{}, tx) + batches[from] = append(batches[from], tx) } - // Create a checkpoint aware protocol manager - blockchain, err := core.NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("failed to create new blockchain: %v", err) - } - pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil) - if err != nil { - t.Fatalf("failed to start test protocol manager: %v", err) - } - pm.Start(1000) - defer pm.Stop() - - // Connect a new peer and check that we receive the checkpoint challenge - peer, _ := newTestPeer("peer", eth63, pm, true) - defer peer.close() - - if checkpoint { - challenge := &getBlockHeadersData{ - Origin: hashOrNumber{Number: response.Number.Uint64()}, - Amount: 1, - Skip: 0, - Reverse: false, - } - if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil { - t.Fatalf("challenge mismatch: %v", err) - } - // Create a block to reply to the challenge if no timeout is simulated - if !timeout { - if empty { - if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{}); err != nil { - t.Fatalf("failed to answer challenge: %v", err) - } - } else if match { - if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{response}); err != nil { - t.Fatalf("failed to answer challenge: %v", err) - } - } else { - if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{{Number: response.Number}}); err != nil { - t.Fatalf("failed to answer challenge: %v", err) - } - } - } - } - // Wait until the test timeout passes to ensure proper cleanup - time.Sleep(syncChallengeTimeout + 300*time.Millisecond) - - // Verify that the remote peer is maintained or dropped - if drop { - if peers := pm.peers.Len(); peers != 0 { - t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) - } - } else { - if peers := pm.peers.Len(); peers != 1 { - t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) - } + for _, batch := range batches { + sort.Sort(types.TxByNonce(batch)) } + return batches, nil } -func TestBroadcastBlock(t *testing.T) { - var tests = []struct { - totalPeers int - broadcastExpected int - }{ - {1, 1}, - {2, 1}, - {3, 1}, - {4, 2}, - {5, 2}, - {9, 3}, - {12, 3}, - {16, 4}, - {26, 5}, - {100, 10}, - } - for _, test := range tests { - testBroadcastBlock(t, test.totalPeers, test.broadcastExpected) - } +// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and +// send events to the given channel. +func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { + return p.txFeed.Subscribe(ch) } -func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) { - var ( - evmux = new(event.TypeMux) - pow = ethash.NewFaker() - db = rawdb.NewMemoryDatabase() - config = ¶ms.ChainConfig{} - gspec = &core.Genesis{Config: config} - genesis = gspec.MustCommit(db) - ) - blockchain, err := core.NewBlockChain(db, nil, config, pow, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("failed to create new blockchain: %v", err) - } - pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil) - if err != nil { - t.Fatalf("failed to start test protocol manager: %v", err) - } - pm.Start(1000) - defer pm.Stop() - var peers []*testPeer - for i := 0; i < totalPeers; i++ { - peer, _ := newTestPeer(fmt.Sprintf("peer %d", i), eth63, pm, true) - defer peer.close() - - peers = append(peers, peer) - } - chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {}) - pm.BroadcastBlock(chain[0], true /*propagate*/) - - errCh := make(chan error, totalPeers) - doneCh := make(chan struct{}, totalPeers) - for _, peer := range peers { - go func(p *testPeer) { - if err := p2p.ExpectMsg(p.app, NewBlockMsg, &newBlockData{Block: chain[0], TD: big.NewInt(131136)}); err != nil { - errCh <- err - } else { - doneCh <- struct{}{} - } - }(peer) - } - var received int - for { - select { - case <-doneCh: - received++ - if received > broadcastExpected { - // We can bail early here - t.Errorf("broadcast count mismatch: have %d > want %d", received, broadcastExpected) - return - } - case <-time.After(2 * time.Second): - if received != broadcastExpected { - t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected) - } - return - case err = <-errCh: - t.Fatalf("broadcast failed: %v", err) - } - } +// testHandler is a live implementation of the Ethereum protocol handler, just +// preinitialized with some sane testing defaults and the transaction pool mocked +// out. +type testHandler struct { + db ethdb.Database + chain *core.BlockChain + txpool *testTxPool + handler *handler +} +// newTestHandler creates a new handler for testing purposes with no blocks. +func newTestHandler() *testHandler { + return newTestHandlerWithBlocks(0) } -// Tests that a propagated malformed block (uncles or transactions don't match -// with the hashes in the header) gets discarded and not broadcast forward. -func TestBroadcastMalformedBlock(t *testing.T) { - // Create a live node to test propagation with - var ( - engine = ethash.NewFaker() - db = rawdb.NewMemoryDatabase() - config = ¶ms.ChainConfig{} - gspec = &core.Genesis{Config: config} - genesis = gspec.MustCommit(db) - ) - blockchain, err := core.NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("failed to create new blockchain: %v", err) +// newTestHandlerWithBlocks creates a new handler for testing purposes, with a +// given number of initial blocks. +func newTestHandlerWithBlocks(blocks int) *testHandler { + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + (&core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + }).MustCommit(db) + + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + + bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, nil) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txpool := newTestTxPool() + + handler, _ := newHandler(&handlerConfig{ + Database: db, + Chain: chain, + TxPool: txpool, + Network: 1, + Sync: downloader.FastSync, + BloomCache: 1, + }) + handler.Start(1000) + + return &testHandler{ + db: db, + chain: chain, + txpool: txpool, + handler: handler, } - pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil) - if err != nil { - t.Fatalf("failed to start test protocol manager: %v", err) - } - pm.Start(2) - defer pm.Stop() - - // Create two peers, one to send the malformed block with and one to check - // propagation - source, _ := newTestPeer("source", eth63, pm, true) - defer source.close() - - sink, _ := newTestPeer("sink", eth63, pm, true) - defer sink.close() - - // Create various combinations of malformed blocks - chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {}) - - malformedUncles := chain[0].Header() - malformedUncles.UncleHash[0]++ - malformedTransactions := chain[0].Header() - malformedTransactions.TxHash[0]++ - malformedEverything := chain[0].Header() - malformedEverything.UncleHash[0]++ - malformedEverything.TxHash[0]++ +} - // Keep listening to broadcasts and notify if any arrives - notify := make(chan struct{}, 1) - go func() { - if _, err := sink.app.ReadMsg(); err == nil { - notify <- struct{}{} - } - }() - // Try to broadcast all malformations and ensure they all get discarded - for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} { - block := types.NewBlockWithHeader(header).WithBody(chain[0].Transactions(), chain[0].Uncles()) - if err := p2p.Send(source.app, NewBlockMsg, []interface{}{block, big.NewInt(131136)}); err != nil { - t.Fatalf("failed to broadcast block: %v", err) - } - select { - case <-notify: - t.Fatalf("malformed block forwarded") - case <-time.After(100 * time.Millisecond): - } - } +// close tears down the handler and all its internal constructs. +func (b *testHandler) close() { + b.handler.Stop() + b.chain.Stop() } diff --git a/eth/helper_test.go b/eth/helper_test.go deleted file mode 100644 index c0bda181ea..0000000000 --- a/eth/helper_test.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// This file contains some shares testing functionality, common to multiple -// different files and modules being tested. - -package eth - -import ( - "crypto/ecdsa" - "crypto/rand" - "fmt" - "math/big" - "sort" - "sync" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" -) - -var ( - testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testBank = crypto.PubkeyToAddress(testBankKey.PublicKey) -) - -// newTestProtocolManager creates a new protocol manager for testing purposes, -// with the given number of blocks already known, and potential notification -// channels for different events. -func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database, error) { - var ( - evmux = new(event.TypeMux) - engine = ethash.NewFaker() - db = rawdb.NewMemoryDatabase() - gspec = &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}}, - } - genesis = gspec.MustCommit(db) - blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) - ) - chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator) - if _, err := blockchain.InsertChain(chain); err != nil { - panic(err) - } - pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil) - if err != nil { - return nil, nil, err - } - pm.Start(1000) - return pm, db, nil -} - -// newTestProtocolManagerMust creates a new protocol manager for testing purposes, -// with the given number of blocks already known, and potential notification -// channels for different events. In case of an error, the constructor force- -// fails the test. -func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database) { - pm, db, err := newTestProtocolManager(mode, blocks, generator, newtx) - if err != nil { - t.Fatalf("Failed to create protocol manager: %v", err) - } - return pm, db -} - -// testTxPool is a fake, helper transaction pool for testing purposes -type testTxPool struct { - txFeed event.Feed - pool map[common.Hash]*types.Transaction // Hash map of collected transactions - added chan<- []*types.Transaction // Notification channel for new transactions - - lock sync.RWMutex // Protects the transaction pool -} - -// Has returns an indicator whether txpool has a transaction -// cached with the given hash. -func (p *testTxPool) Has(hash common.Hash) bool { - p.lock.Lock() - defer p.lock.Unlock() - - return p.pool[hash] != nil -} - -// Get retrieves the transaction from local txpool with given -// tx hash. -func (p *testTxPool) Get(hash common.Hash) *types.Transaction { - p.lock.Lock() - defer p.lock.Unlock() - - return p.pool[hash] -} - -// AddRemotes appends a batch of transactions to the pool, and notifies any -// listeners if the addition channel is non nil -func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { - p.lock.Lock() - defer p.lock.Unlock() - - for _, tx := range txs { - p.pool[tx.Hash()] = tx - } - if p.added != nil { - p.added <- txs - } - p.txFeed.Send(core.NewTxsEvent{Txs: txs}) - return make([]error, len(txs)) -} - -// Pending returns all the transactions known to the pool -func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) { - p.lock.RLock() - defer p.lock.RUnlock() - - batches := make(map[common.Address]types.Transactions) - for _, tx := range p.pool { - from, _ := types.Sender(types.HomesteadSigner{}, tx) - batches[from] = append(batches[from], tx) - } - for _, batch := range batches { - sort.Sort(types.TxByNonce(batch)) - } - return batches, nil -} - -func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return p.txFeed.Subscribe(ch) -} - -// newTestTransaction create a new dummy transaction. -func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction { - tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, datasize)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, from) - return tx -} - -// testPeer is a simulated peer to allow testing direct network calls. -type testPeer struct { - net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging - app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side - *peer -} - -// newTestPeer creates a new peer registered at the given protocol manager. -func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Start the peer on a new thread - var id enode.ID - rand.Read(id[:]) - peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get) - errc := make(chan error, 1) - go func() { errc <- pm.runPeer(peer) }() - tp := &testPeer{app: app, net: net, peer: peer} - - // Execute any implicitly requested handshakes and return - if shake { - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - forkID := forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) - tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(pm.blockchain)) - } - return tp, errc -} - -// handshake simulates a trivial handshake that expects the same state from the -// remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) { - var msg interface{} - switch { - case p.version == eth63: - msg = &statusData63{ - ProtocolVersion: uint32(p.version), - NetworkId: DefaultConfig.NetworkId, - TD: td, - CurrentBlock: head, - GenesisBlock: genesis, - } - case p.version >= eth64: - msg = &statusData{ - ProtocolVersion: uint32(p.version), - NetworkID: DefaultConfig.NetworkId, - TD: td, - Head: head, - Genesis: genesis, - ForkID: forkID, - } - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil { - t.Fatalf("status recv: %v", err) - } - if err := p2p.Send(p.app, StatusMsg, msg); err != nil { - t.Fatalf("status send: %v", err) - } -} - -// close terminates the local side of the peer, notifying the remote protocol -// manager of termination. -func (p *testPeer) close() { - p.app.Close() -} diff --git a/eth/peer.go b/eth/peer.go index 21b82a19c5..6970c8afd3 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -17,806 +17,58 @@ package eth import ( - "errors" - "fmt" "math/big" "sync" "time" - mapset "github.com/deckarep/golang-set" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" ) -var ( - errClosed = errors.New("peer set is closed") - errAlreadyRegistered = errors.New("peer is already registered") - errNotRegistered = errors.New("peer is not registered") -) - -const ( - maxKnownTxs = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS) - maxKnownBlocks = 1024 // Maximum block hashes to keep in the known list (prevent DOS) - - // maxQueuedTxs is the maximum number of transactions to queue up before dropping - // older broadcasts. - maxQueuedTxs = 4096 - - // maxQueuedTxAnns is the maximum number of transaction announcements to queue up - // before dropping older announcements. - maxQueuedTxAnns = 4096 - - // maxQueuedBlocks is the maximum number of block propagations to queue up before - // dropping broadcasts. There's not much point in queueing stale blocks, so a few - // that might cover uncles should be enough. - maxQueuedBlocks = 4 - - // maxQueuedBlockAnns is the maximum number of block announcements to queue up before - // dropping broadcasts. Similarly to block propagations, there's no point to queue - // above some healthy uncle limit, so use that. - maxQueuedBlockAnns = 4 - - handshakeTimeout = 5 * time.Second -) - -// max is a helper function which returns the larger of the two given integers. -func max(a, b int) int { - if a > b { - return a - } - return b -} - -// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known +// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known // about a connected peer. -type PeerInfo struct { - Version int `json:"version"` // Ethereum protocol version negotiated +type ethPeerInfo struct { + Version uint `json:"version"` // Ethereum protocol version negotiated Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain - Head string `json:"head"` // SHA3 hash of the peer's best owned block -} - -// propEvent is a block propagation, waiting for its turn in the broadcast queue. -type propEvent struct { - block *types.Block - td *big.Int -} - -type peer struct { - id string - - *p2p.Peer - rw p2p.MsgReadWriter - - version int // Protocol version negotiated - syncDrop *time.Timer // Timed connection dropper if sync progress isn't validated in time - - head common.Hash - td *big.Int - lock sync.RWMutex - - knownBlocks mapset.Set // Set of block hashes known to be known by this peer - queuedBlocks chan *propEvent // Queue of blocks to broadcast to the peer - queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer - - knownTxs mapset.Set // Set of transaction hashes known to be known by this peer - txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests - txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests - getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool - - term chan struct{} // Termination channel to stop the broadcaster + Head string `json:"head"` // Hex hash of the peer's best owned block } -func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer { - return &peer{ - Peer: p, - rw: rw, - version: version, - id: fmt.Sprintf("%x", p.ID().Bytes()[:8]), - knownTxs: mapset.NewSet(), - knownBlocks: mapset.NewSet(), - queuedBlocks: make(chan *propEvent, maxQueuedBlocks), - queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns), - txBroadcast: make(chan []common.Hash), - txAnnounce: make(chan []common.Hash), - getPooledTx: getPooledTx, - term: make(chan struct{}), - } -} - -// broadcastBlocks is a write loop that multiplexes blocks and block accouncements -// to the remote peer. The goal is to have an async writer that does not lock up -// node internals and at the same time rate limits queued data. -func (p *peer) broadcastBlocks(removePeer func(string)) { - for { - select { - case prop := <-p.queuedBlocks: - if err := p.SendNewBlock(prop.block, prop.td); err != nil { - removePeer(p.id) - return - } - p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td) - - case block := <-p.queuedBlockAnns: - if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil { - removePeer(p.id) - return - } - p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash()) - - case <-p.term: - return - } - } -} - -// broadcastTransactions is a write loop that schedules transaction broadcasts -// to the remote peer. The goal is to have an async writer that does not lock up -// node internals and at the same time rate limits queued data. -func (p *peer) broadcastTransactions(removePeer func(string)) { - var ( - queue []common.Hash // Queue of hashes to broadcast as full transactions - done chan struct{} // Non-nil if background broadcaster is running - fail = make(chan error, 1) // Channel used to receive network error - ) - for { - // If there's no in-flight broadcast running, check if a new one is needed - if done == nil && len(queue) > 0 { - // Pile transaction until we reach our allowed network limit - var ( - hashes []common.Hash - txs []*types.Transaction - size common.StorageSize - ) - for i := 0; i < len(queue) && size < txsyncPackSize; i++ { - if tx := p.getPooledTx(queue[i]); tx != nil { - txs = append(txs, tx) - size += tx.Size() - } - hashes = append(hashes, queue[i]) - } - queue = queue[:copy(queue, queue[len(hashes):])] - - // If there's anything available to transfer, fire up an async writer - if len(txs) > 0 { - done = make(chan struct{}) - go func() { - if err := p.sendTransactions(txs); err != nil { - fail <- err - return - } - close(done) - p.Log().Trace("Sent transactions", "count", len(txs)) - }() - } - } - // Transfer goroutine may or may not have been started, listen for events - select { - case hashes := <-p.txBroadcast: - // New batch of transactions to be broadcast, queue them (with cap) - queue = append(queue, hashes...) - if len(queue) > maxQueuedTxs { - // Fancy copy and resize to ensure buffer doesn't grow indefinitely - queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] - } - - case <-done: - done = nil - - case <-fail: - removePeer(p.id) - return +// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata. +type ethPeer struct { + *eth.Peer - case <-p.term: - return - } - } + syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time + lock sync.RWMutex // Mutex protecting the internal fields } -// announceTransactions is a write loop that schedules transaction broadcasts -// to the remote peer. The goal is to have an async writer that does not lock up -// node internals and at the same time rate limits queued data. -func (p *peer) announceTransactions(removePeer func(string)) { - var ( - queue []common.Hash // Queue of hashes to announce as transaction stubs - done chan struct{} // Non-nil if background announcer is running - fail = make(chan error, 1) // Channel used to receive network error - ) - for { - // If there's no in-flight announce running, check if a new one is needed - if done == nil && len(queue) > 0 { - // Pile transaction hashes until we reach our allowed network limit - var ( - hashes []common.Hash - pending []common.Hash - size common.StorageSize - ) - for i := 0; i < len(queue) && size < txsyncPackSize; i++ { - if p.getPooledTx(queue[i]) != nil { - pending = append(pending, queue[i]) - size += common.HashLength - } - hashes = append(hashes, queue[i]) - } - queue = queue[:copy(queue, queue[len(hashes):])] - - // If there's anything available to transfer, fire up an async writer - if len(pending) > 0 { - done = make(chan struct{}) - go func() { - if err := p.sendPooledTransactionHashes(pending); err != nil { - fail <- err - return - } - close(done) - p.Log().Trace("Sent transaction announcements", "count", len(pending)) - }() - } - } - // Transfer goroutine may or may not have been started, listen for events - select { - case hashes := <-p.txAnnounce: - // New batch of transactions to be broadcast, queue them (with cap) - queue = append(queue, hashes...) - if len(queue) > maxQueuedTxAnns { - // Fancy copy and resize to ensure buffer doesn't grow indefinitely - queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])] - } - - case <-done: - done = nil - - case <-fail: - removePeer(p.id) - return - - case <-p.term: - return - } - } -} - -// close signals the broadcast goroutine to terminate. -func (p *peer) close() { - close(p.term) -} - -// Info gathers and returns a collection of metadata known about a peer. -func (p *peer) Info() *PeerInfo { +// info gathers and returns some `eth` protocol metadata known about a peer. +func (p *ethPeer) info() *ethPeerInfo { hash, td := p.Head() - return &PeerInfo{ - Version: p.version, + return ðPeerInfo{ + Version: p.Version(), Difficulty: td, Head: hash.Hex(), } } -// Head retrieves a copy of the current head hash and total difficulty of the -// peer. -func (p *peer) Head() (hash common.Hash, td *big.Int) { - p.lock.RLock() - defer p.lock.RUnlock() - - copy(hash[:], p.head[:]) - return hash, new(big.Int).Set(p.td) -} - -// SetHead updates the head hash and total difficulty of the peer. -func (p *peer) SetHead(hash common.Hash, td *big.Int) { - p.lock.Lock() - defer p.lock.Unlock() - - copy(p.head[:], hash[:]) - p.td.Set(td) -} - -// MarkBlock marks a block as known for the peer, ensuring that the block will -// never be propagated to this particular peer. -func (p *peer) MarkBlock(hash common.Hash) { - // If we reached the memory allowance, drop a previously known block hash - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(hash) -} - -// MarkTransaction marks a transaction as known for the peer, ensuring that it -// will never be propagated to this particular peer. -func (p *peer) MarkTransaction(hash common.Hash) { - // If we reached the memory allowance, drop a previously known transaction hash - for p.knownTxs.Cardinality() >= maxKnownTxs { - p.knownTxs.Pop() - } - p.knownTxs.Add(hash) -} - -// SendTransactions64 sends transactions to the peer and includes the hashes -// in its transaction hash set for future reference. -// -// This method is legacy support for initial transaction exchange in eth/64 and -// prior. For eth/65 and higher use SendPooledTransactionHashes. -func (p *peer) SendTransactions64(txs types.Transactions) error { - return p.sendTransactions(txs) -} - -// sendTransactions sends transactions to the peer and includes the hashes -// in its transaction hash set for future reference. -// -// This method is a helper used by the async transaction sender. Don't call it -// directly as the queueing (memory) and transmission (bandwidth) costs should -// not be managed directly. -func (p *peer) sendTransactions(txs types.Transactions) error { - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) { - p.knownTxs.Pop() - } - for _, tx := range txs { - p.knownTxs.Add(tx.Hash()) - } - return p2p.Send(p.rw, TransactionMsg, txs) -} - -// AsyncSendTransactions queues a list of transactions (by hash) to eventually -// propagate to a remote peer. The number of pending sends are capped (new ones -// will force old sends to be dropped) -func (p *peer) AsyncSendTransactions(hashes []common.Hash) { - select { - case p.txBroadcast <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - case <-p.term: - p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) - } -} - -// sendPooledTransactionHashes sends transaction hashes to the peer and includes -// them in its transaction hash set for future reference. -// -// This method is a helper used by the async transaction announcer. Don't call it -// directly as the queueing (memory) and transmission (bandwidth) costs should -// not be managed directly. -func (p *peer) sendPooledTransactionHashes(hashes []common.Hash) error { - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - return p2p.Send(p.rw, NewPooledTransactionHashesMsg, hashes) -} - -// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually -// announce to a remote peer. The number of pending sends are capped (new ones -// will force old sends to be dropped) -func (p *peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { - select { - case p.txAnnounce <- hashes: - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - case <-p.term: - p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) - } -} - -// SendPooledTransactionsRLP sends requested transactions to the peer and adds the -// hashes in its transaction hash set for future reference. -// -// Note, the method assumes the hashes are correct and correspond to the list of -// transactions being sent. -func (p *peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error { - // Mark all the transactions as known, but ensure we don't overflow our limits - for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { - p.knownTxs.Pop() - } - for _, hash := range hashes { - p.knownTxs.Add(hash) - } - return p2p.Send(p.rw, PooledTransactionsMsg, txs) -} - -// SendNewBlockHashes announces the availability of a number of blocks through -// a hash notification. -func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { - // Mark all the block hashes as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) { - p.knownBlocks.Pop() - } - for _, hash := range hashes { - p.knownBlocks.Add(hash) - } - request := make(newBlockHashesData, len(hashes)) - for i := 0; i < len(hashes); i++ { - request[i].Hash = hashes[i] - request[i].Number = numbers[i] - } - return p2p.Send(p.rw, NewBlockHashesMsg, request) -} - -// AsyncSendNewBlockHash queues the availability of a block for propagation to a -// remote peer. If the peer's broadcast queue is full, the event is silently -// dropped. -func (p *peer) AsyncSendNewBlockHash(block *types.Block) { - select { - case p.queuedBlockAnns <- block: - // Mark all the block hash as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(block.Hash()) - default: - p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash()) - } -} - -// SendNewBlock propagates an entire block to a remote peer. -func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error { - // Mark all the block hash as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(block.Hash()) - return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td}) -} - -// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If -// the peer's broadcast queue is full, the event is silently dropped. -func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { - select { - case p.queuedBlocks <- &propEvent{block: block, td: td}: - // Mark all the block hash as known, but ensure we don't overflow our limits - for p.knownBlocks.Cardinality() >= maxKnownBlocks { - p.knownBlocks.Pop() - } - p.knownBlocks.Add(block.Hash()) - default: - p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash()) - } -} - -// SendBlockHeaders sends a batch of block headers to the remote peer. -func (p *peer) SendBlockHeaders(headers []*types.Header) error { - return p2p.Send(p.rw, BlockHeadersMsg, headers) -} - -// SendBlockBodies sends a batch of block contents to the remote peer. -func (p *peer) SendBlockBodies(bodies []*blockBody) error { - return p2p.Send(p.rw, BlockBodiesMsg, blockBodiesData(bodies)) -} - -// SendBlockBodiesRLP sends a batch of block contents to the remote peer from -// an already RLP encoded format. -func (p *peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error { - return p2p.Send(p.rw, BlockBodiesMsg, bodies) -} - -// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the -// hashes requested. -func (p *peer) SendNodeData(data [][]byte) error { - return p2p.Send(p.rw, NodeDataMsg, data) -} - -// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the -// ones requested from an already RLP encoded format. -func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error { - return p2p.Send(p.rw, ReceiptsMsg, receipts) -} - -// RequestOneHeader is a wrapper around the header query functions to fetch a -// single header. It is used solely by the fetcher. -func (p *peer) RequestOneHeader(hash common.Hash) error { - p.Log().Debug("Fetching single header", "hash", hash) - return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false}) -} - -// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the -// specified header query, based on the hash of an origin block. -func (p *peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) -} - -// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the -// specified header query, based on the number of an origin block. -func (p *peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) -} - -// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes -// specified. -func (p *peer) RequestBodies(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) - return p2p.Send(p.rw, GetBlockBodiesMsg, hashes) -} - -// RequestNodeData fetches a batch of arbitrary data from a node's known state -// data, corresponding to the specified hashes. -func (p *peer) RequestNodeData(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of state data", "count", len(hashes)) - return p2p.Send(p.rw, GetNodeDataMsg, hashes) -} - -// RequestReceipts fetches a batch of transaction receipts from a remote node. -func (p *peer) RequestReceipts(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) - return p2p.Send(p.rw, GetReceiptsMsg, hashes) -} - -// RequestTxs fetches a batch of transactions from a remote node. -func (p *peer) RequestTxs(hashes []common.Hash) error { - p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) - return p2p.Send(p.rw, GetPooledTransactionsMsg, hashes) -} - -// Handshake executes the eth protocol handshake, negotiating version number, -// network IDs, difficulties, head and genesis blocks. -func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { - // Send out own handshake in a new thread - errc := make(chan error, 2) - - var ( - status63 statusData63 // safe to read after two values have been received from errc - status statusData // safe to read after two values have been received from errc - ) - go func() { - switch { - case p.version == eth63: - errc <- p2p.Send(p.rw, StatusMsg, &statusData63{ - ProtocolVersion: uint32(p.version), - NetworkId: network, - TD: td, - CurrentBlock: head, - GenesisBlock: genesis, - }) - case p.version >= eth64: - errc <- p2p.Send(p.rw, StatusMsg, &statusData{ - ProtocolVersion: uint32(p.version), - NetworkID: network, - TD: td, - Head: head, - Genesis: genesis, - ForkID: forkID, - }) - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - }() - go func() { - switch { - case p.version == eth63: - errc <- p.readStatusLegacy(network, &status63, genesis) - case p.version >= eth64: - errc <- p.readStatus(network, &status, genesis, forkFilter) - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - }() - timeout := time.NewTimer(handshakeTimeout) - defer timeout.Stop() - for i := 0; i < 2; i++ { - select { - case err := <-errc: - if err != nil { - return err - } - case <-timeout.C: - return p2p.DiscReadTimeout - } - } - switch { - case p.version == eth63: - p.td, p.head = status63.TD, status63.CurrentBlock - case p.version >= eth64: - p.td, p.head = status.TD, status.Head - default: - panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version)) - } - return nil -} - -func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis common.Hash) error { - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - if msg.Code != StatusMsg { - return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) - } - if msg.Size > protocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) - } - // Decode the handshake and make sure everything matches - if err := msg.Decode(&status); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - if status.GenesisBlock != genesis { - return errResp(ErrGenesisMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8]) - } - if status.NetworkId != network { - return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkId, network) - } - if int(status.ProtocolVersion) != p.version { - return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version) - } - return nil -} - -func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash, forkFilter forkid.Filter) error { - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - if msg.Code != StatusMsg { - return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) - } - if msg.Size > protocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize) - } - // Decode the handshake and make sure everything matches - if err := msg.Decode(&status); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - if status.NetworkID != network { - return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkID, network) - } - if int(status.ProtocolVersion) != p.version { - return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version) - } - if status.Genesis != genesis { - return errResp(ErrGenesisMismatch, "%x (!= %x)", status.Genesis, genesis) - } - if err := forkFilter(status.ForkID); err != nil { - return errResp(ErrForkIDRejected, "%v", err) - } - return nil -} - -// String implements fmt.Stringer. -func (p *peer) String() string { - return fmt.Sprintf("Peer %s [%s]", p.id, - fmt.Sprintf("eth/%2d", p.version), - ) -} - -// peerSet represents the collection of active peers currently participating in -// the Ethereum sub-protocol. -type peerSet struct { - peers map[string]*peer - lock sync.RWMutex - closed bool -} - -// newPeerSet creates a new peer set to track the active participants. -func newPeerSet() *peerSet { - return &peerSet{ - peers: make(map[string]*peer), - } -} - -// Register injects a new peer into the working set, or returns an error if the -// peer is already known. If a new peer it registered, its broadcast loop is also -// started. -func (ps *peerSet) Register(p *peer, removePeer func(string)) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - if ps.closed { - return errClosed - } - if _, ok := ps.peers[p.id]; ok { - return errAlreadyRegistered - } - ps.peers[p.id] = p - - go p.broadcastBlocks(removePeer) - go p.broadcastTransactions(removePeer) - if p.version >= eth65 { - go p.announceTransactions(removePeer) - } - return nil -} - -// Unregister removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. -func (ps *peerSet) Unregister(id string) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - p, ok := ps.peers[id] - if !ok { - return errNotRegistered - } - delete(ps.peers, id) - p.close() - - return nil -} - -// Peer retrieves the registered peer with the given id. -func (ps *peerSet) Peer(id string) *peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// Len returns if the current number of peers in the set. -func (ps *peerSet) Len() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return len(ps.peers) -} - -// PeersWithoutBlock retrieves a list of peers that do not have a given block in -// their set of known hashes. -func (ps *peerSet) PeersWithoutBlock(hash common.Hash) []*peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*peer, 0, len(ps.peers)) - for _, p := range ps.peers { - if !p.knownBlocks.Contains(hash) { - list = append(list, p) - } - } - return list -} - -// PeersWithoutTx retrieves a list of peers that do not have a given transaction -// in their set of known hashes. -func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*peer, 0, len(ps.peers)) - for _, p := range ps.peers { - if !p.knownTxs.Contains(hash) { - list = append(list, p) - } - } - return list +// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known +// about a connected peer. +type snapPeerInfo struct { + Version uint `json:"version"` // Snapshot protocol version negotiated } -// BestPeer retrieves the known peer with the currently highest total difficulty. -func (ps *peerSet) BestPeer() *peer { - ps.lock.RLock() - defer ps.lock.RUnlock() +// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. +type snapPeer struct { + *snap.Peer - var ( - bestPeer *peer - bestTd *big.Int - ) - for _, p := range ps.peers { - if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { - bestPeer, bestTd = p, td - } - } - return bestPeer + ethDrop *time.Timer // Connection dropper if `eth` doesn't connect in time + lock sync.RWMutex // Mutex protecting the internal fields } -// Close disconnects all peers. -// No new peers can be registered after Close has returned. -func (ps *peerSet) Close() { - ps.lock.Lock() - defer ps.lock.Unlock() - - for _, p := range ps.peers { - p.Disconnect(p2p.DiscQuitting) +// info gathers and returns some `snap` protocol metadata known about a peer. +func (p *snapPeer) info() *snapPeerInfo { + return &snapPeerInfo{ + Version: p.Version(), } - ps.closed = true } diff --git a/eth/peerset.go b/eth/peerset.go new file mode 100644 index 0000000000..9b584ec320 --- /dev/null +++ b/eth/peerset.go @@ -0,0 +1,301 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + // errPeerSetClosed is returned if a peer is attempted to be added or removed + // from the peer set after it has been terminated. + errPeerSetClosed = errors.New("peerset closed") + + // errPeerAlreadyRegistered is returned if a peer is attempted to be added + // to the peer set, but one with the same id already exists. + errPeerAlreadyRegistered = errors.New("peer already registered") + + // errPeerNotRegistered is returned if a peer is attempted to be removed from + // a peer set, but no peer with the given id exists. + errPeerNotRegistered = errors.New("peer not registered") + + // ethConnectTimeout is the `snap` timeout for `eth` to connect too. + ethConnectTimeout = 3 * time.Second +) + +// peerSet represents the collection of active peers currently participating in +// the `eth` or `snap` protocols. +type peerSet struct { + ethPeers map[string]*ethPeer // Peers connected on the `eth` protocol + snapPeers map[string]*snapPeer // Peers connected on the `snap` protocol + + ethJoinFeed event.Feed // Events when an `eth` peer successfully joins + ethDropFeed event.Feed // Events when an `eth` peer gets dropped + snapJoinFeed event.Feed // Events when a `snap` peer joins on both `eth` and `snap` + snapDropFeed event.Feed // Events when a `snap` peer gets dropped (only if fully joined) + + scope event.SubscriptionScope // Subscription group to unsubscribe everyone at once + + lock sync.RWMutex + closed bool +} + +// newPeerSet creates a new peer set to track the active participants. +func newPeerSet() *peerSet { + return &peerSet{ + ethPeers: make(map[string]*ethPeer), + snapPeers: make(map[string]*snapPeer), + } +} + +// subscribeEthJoin registers a subscription for peers joining (and completing +// the handshake) on the `eth` protocol. +func (ps *peerSet) subscribeEthJoin(ch chan<- *eth.Peer) event.Subscription { + return ps.scope.Track(ps.ethJoinFeed.Subscribe(ch)) +} + +// subscribeEthDrop registers a subscription for peers being dropped from the +// `eth` protocol. +func (ps *peerSet) subscribeEthDrop(ch chan<- *eth.Peer) event.Subscription { + return ps.scope.Track(ps.ethDropFeed.Subscribe(ch)) +} + +// subscribeSnapJoin registers a subscription for peers joining (and completing +// the `eth` join) on the `snap` protocol. +func (ps *peerSet) subscribeSnapJoin(ch chan<- *snap.Peer) event.Subscription { + return ps.scope.Track(ps.snapJoinFeed.Subscribe(ch)) +} + +// subscribeSnapDrop registers a subscription for peers being dropped from the +// `snap` protocol. +func (ps *peerSet) subscribeSnapDrop(ch chan<- *snap.Peer) event.Subscription { + return ps.scope.Track(ps.snapDropFeed.Subscribe(ch)) +} + +// registerEthPeer injects a new `eth` peer into the working set, or returns an +// error if the peer is already known. The peer is announced on the `eth` join +// feed and if it completes a pending `snap` peer, also on that feed. +func (ps *peerSet) registerEthPeer(peer *eth.Peer) error { + ps.lock.Lock() + if ps.closed { + ps.lock.Unlock() + return errPeerSetClosed + } + id := peer.ID() + if _, ok := ps.ethPeers[id]; ok { + ps.lock.Unlock() + return errPeerAlreadyRegistered + } + ps.ethPeers[id] = ðPeer{Peer: peer} + + snap, ok := ps.snapPeers[id] + ps.lock.Unlock() + + if ok { + // Previously dangling `snap` peer, stop it's timer since `eth` connected + snap.lock.Lock() + if snap.ethDrop != nil { + snap.ethDrop.Stop() + snap.ethDrop = nil + } + snap.lock.Unlock() + } + ps.ethJoinFeed.Send(peer) + if ok { + ps.snapJoinFeed.Send(snap.Peer) + } + return nil +} + +// unregisterEthPeer removes a remote peer from the active set, disabling any further +// actions to/from that particular entity. The drop is announced on the `eth` drop +// feed and also on the `snap` feed if the eth/snap duality was broken just now. +func (ps *peerSet) unregisterEthPeer(id string) error { + ps.lock.Lock() + eth, ok := ps.ethPeers[id] + if !ok { + ps.lock.Unlock() + return errPeerNotRegistered + } + delete(ps.ethPeers, id) + + snap, ok := ps.snapPeers[id] + ps.lock.Unlock() + + ps.ethDropFeed.Send(eth) + if ok { + ps.snapDropFeed.Send(snap) + } + return nil +} + +// registerSnapPeer injects a new `snap` peer into the working set, or returns +// an error if the peer is already known. The peer is announced on the `snap` +// join feed if it completes an existing `eth` peer. +// +// If the peer isn't yet connected on `eth` and fails to do so within a given +// amount of time, it is dropped. This enforces that `snap` is an extension to +// `eth`, not a standalone leeching protocol. +func (ps *peerSet) registerSnapPeer(peer *snap.Peer) error { + ps.lock.Lock() + if ps.closed { + ps.lock.Unlock() + return errPeerSetClosed + } + id := peer.ID() + if _, ok := ps.snapPeers[id]; ok { + ps.lock.Unlock() + return errPeerAlreadyRegistered + } + ps.snapPeers[id] = &snapPeer{Peer: peer} + + _, ok := ps.ethPeers[id] + if !ok { + // Dangling `snap` peer, start a timer to drop if `eth` doesn't connect + ps.snapPeers[id].ethDrop = time.AfterFunc(ethConnectTimeout, func() { + peer.Log().Warn("Snapshot peer missing eth, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) + peer.Disconnect(p2p.DiscUselessPeer) + }) + } + ps.lock.Unlock() + + if ok { + ps.snapJoinFeed.Send(peer) + } + return nil +} + +// unregisterSnapPeer removes a remote peer from the active set, disabling any +// further actions to/from that particular entity. The drop is announced on the +// `snap` drop feed. +func (ps *peerSet) unregisterSnapPeer(id string) error { + ps.lock.Lock() + peer, ok := ps.snapPeers[id] + if !ok { + ps.lock.Unlock() + return errPeerNotRegistered + } + delete(ps.snapPeers, id) + ps.lock.Unlock() + + peer.lock.Lock() + if peer.ethDrop != nil { + peer.ethDrop.Stop() + peer.ethDrop = nil + } + peer.lock.Unlock() + + ps.snapDropFeed.Send(peer) + return nil +} + +// ethPeer retrieves the registered `eth` peer with the given id. +func (ps *peerSet) ethPeer(id string) *ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.ethPeers[id] +} + +// snapPeer retrieves the registered `snap` peer with the given id. +func (ps *peerSet) snapPeer(id string) *snapPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.snapPeers[id] +} + +// ethPeersWithoutBlock retrieves a list of `eth` peers that do not have a given +// block in their set of known hashes so it might be propagated to them. +func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*ethPeer, 0, len(ps.ethPeers)) + for _, p := range ps.ethPeers { + if !p.KnownBlock(hash) { + list = append(list, p) + } + } + return list +} + +// ethPeersWithoutTransacion retrieves a list of `eth` peers that do not have a +// given transaction in their set of known hashes. +func (ps *peerSet) ethPeersWithoutTransacion(hash common.Hash) []*ethPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + list := make([]*ethPeer, 0, len(ps.ethPeers)) + for _, p := range ps.ethPeers { + if !p.KnownTransaction(hash) { + list = append(list, p) + } + } + return list +} + +// Len returns if the current number of `eth` peers in the set. Since the `snap` +// peers are tied to the existnce of an `eth` connection, that will always be a +// subset of `eth`. +func (ps *peerSet) Len() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return len(ps.ethPeers) +} + +// ethPeerWithHighestTD retrieves the known peer with the currently highest total +// difficulty. +func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + var ( + bestPeer *eth.Peer + bestTd *big.Int + ) + for _, p := range ps.ethPeers { + if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { + bestPeer, bestTd = p.Peer, td + } + } + return bestPeer +} + +// close disconnects all peers. +func (ps *peerSet) close() { + ps.lock.Lock() + defer ps.lock.Unlock() + + for _, p := range ps.ethPeers { + p.Disconnect(p2p.DiscQuitting) + } + for _, p := range ps.snapPeers { + p.Disconnect(p2p.DiscQuitting) + } + ps.closed = true +} diff --git a/eth/protocol.go b/eth/protocol.go deleted file mode 100644 index dc75d6b31a..0000000000 --- a/eth/protocol.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package eth - -import ( - "fmt" - "io" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/rlp" -) - -// Constants to match up protocol versions and messages -const ( - eth63 = 63 - eth64 = 64 - eth65 = 65 -) - -// protocolName is the official short name of the protocol used during capability negotiation. -const protocolName = "eth" - -// ProtocolVersions are the supported versions of the eth protocol (first is primary). -var ProtocolVersions = []uint{eth65, eth64, eth63} - -// protocolLengths are the number of implemented message corresponding to different protocol versions. -var protocolLengths = map[uint]uint64{eth65: 17, eth64: 17, eth63: 17} - -const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message - -// eth protocol message codes -const ( - StatusMsg = 0x00 - NewBlockHashesMsg = 0x01 - TransactionMsg = 0x02 - GetBlockHeadersMsg = 0x03 - BlockHeadersMsg = 0x04 - GetBlockBodiesMsg = 0x05 - BlockBodiesMsg = 0x06 - NewBlockMsg = 0x07 - GetNodeDataMsg = 0x0d - NodeDataMsg = 0x0e - GetReceiptsMsg = 0x0f - ReceiptsMsg = 0x10 - - // New protocol message codes introduced in eth65 - // - // Previously these message ids were used by some legacy and unsupported - // eth protocols, reown them here. - NewPooledTransactionHashesMsg = 0x08 - GetPooledTransactionsMsg = 0x09 - PooledTransactionsMsg = 0x0a -) - -type errCode int - -const ( - ErrMsgTooLarge = iota - ErrDecode - ErrInvalidMsgCode - ErrProtocolVersionMismatch - ErrNetworkIDMismatch - ErrGenesisMismatch - ErrForkIDRejected - ErrNoStatusMsg - ErrExtraStatusMsg -) - -func (e errCode) String() string { - return errorToString[int(e)] -} - -// XXX change once legacy code is out -var errorToString = map[int]string{ - ErrMsgTooLarge: "Message too long", - ErrDecode: "Invalid message", - ErrInvalidMsgCode: "Invalid message code", - ErrProtocolVersionMismatch: "Protocol version mismatch", - ErrNetworkIDMismatch: "Network ID mismatch", - ErrGenesisMismatch: "Genesis mismatch", - ErrForkIDRejected: "Fork ID rejected", - ErrNoStatusMsg: "No status message", - ErrExtraStatusMsg: "Extra status message", -} - -type txPool interface { - // Has returns an indicator whether txpool has a transaction - // cached with the given hash. - Has(hash common.Hash) bool - - // Get retrieves the transaction from local txpool with given - // tx hash. - Get(hash common.Hash) *types.Transaction - - // AddRemotes should add the given transactions to the pool. - AddRemotes([]*types.Transaction) []error - - // Pending should return pending transactions. - // The slice should be modifiable by the caller. - Pending() (map[common.Address]types.Transactions, error) - - // SubscribeNewTxsEvent should return an event subscription of - // NewTxsEvent and send events to the given channel. - SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription -} - -// statusData63 is the network packet for the status message for eth/63. -type statusData63 struct { - ProtocolVersion uint32 - NetworkId uint64 - TD *big.Int - CurrentBlock common.Hash - GenesisBlock common.Hash -} - -// statusData is the network packet for the status message for eth/64 and later. -type statusData struct { - ProtocolVersion uint32 - NetworkID uint64 - TD *big.Int - Head common.Hash - Genesis common.Hash - ForkID forkid.ID -} - -// newBlockHashesData is the network packet for the block announcements. -type newBlockHashesData []struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced -} - -// getBlockHeadersData represents a block header query. -type getBlockHeadersData struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} - -// hashOrNumber is a combined field for specifying an origin block. -type hashOrNumber struct { - Hash common.Hash // Block hash from which to retrieve headers (excludes Number) - Number uint64 // Block hash from which to retrieve headers (excludes Hash) -} - -// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the -// two contained union fields. -func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { - if hn.Hash == (common.Hash{}) { - return rlp.Encode(w, hn.Number) - } - if hn.Number != 0 { - return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) - } - return rlp.Encode(w, hn.Hash) -} - -// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents -// into either a block hash or a block number. -func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } - } - return err -} - -// newBlockData is the network packet for the block propagation message. -type newBlockData struct { - Block *types.Block - TD *big.Int -} - -// sanityCheck verifies that the values are reasonable, as a DoS protection -func (request *newBlockData) sanityCheck() error { - if err := request.Block.SanityCheck(); err != nil { - return err - } - //TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times - // larger, it will still fit within 100 bits - if tdlen := request.TD.BitLen(); tdlen > 100 { - return fmt.Errorf("too large block TD: bitlen %d", tdlen) - } - return nil -} - -// blockBody represents the data content of a single block. -type blockBody struct { - Transactions []*types.Transaction // Transactions contained within a block - Uncles []*types.Header // Uncles contained within a block -} - -// blockBodiesData is the network packet for block content distribution. -type blockBodiesData []*blockBody diff --git a/eth/protocol_test.go b/eth/protocol_test.go deleted file mode 100644 index 331dd05ce1..0000000000 --- a/eth/protocol_test.go +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package eth - -import ( - "fmt" - "math/big" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -func init() { - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) -} - -var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - -// Tests that handshake failures are detected and reported correctly. -func TestStatusMsgErrors63(t *testing.T) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - defer pm.Stop() - - tests := []struct { - code uint64 - data interface{} - wantError error - }{ - { - code: TransactionMsg, data: []interface{}{}, - wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), - }, - { - code: StatusMsg, data: statusData63{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash()}, - wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 63), - }, - { - code: StatusMsg, data: statusData63{63, 999, td, head.Hash(), genesis.Hash()}, - wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId), - }, - { - code: StatusMsg, data: statusData63{63, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}}, - wantError: errResp(ErrGenesisMismatch, "0300000000000000 (!= %x)", genesis.Hash().Bytes()[:8]), - }, - } - for i, test := range tests { - p, errc := newTestPeer("peer", 63, pm, false) - // The send call might hang until reset because - // the protocol might not read the payload. - go p2p.Send(p.app, test.code, test.data) - - select { - case err := <-errc: - if err == nil { - t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError) - } else if err.Error() != test.wantError.Error() { - t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError) - } - case <-time.After(2 * time.Second): - t.Errorf("protocol did not shut down within 2 seconds") - } - p.close() - } -} - -func TestStatusMsgErrors64(t *testing.T) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) - var ( - genesis = pm.blockchain.Genesis() - head = pm.blockchain.CurrentHeader() - td = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - forkID = forkid.NewID(pm.blockchain.Config(), pm.blockchain.Genesis().Hash(), pm.blockchain.CurrentHeader().Number.Uint64()) - ) - defer pm.Stop() - - tests := []struct { - code uint64 - data interface{} - wantError error - }{ - { - code: TransactionMsg, data: []interface{}{}, - wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), - }, - { - code: StatusMsg, data: statusData{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkID}, - wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 64), - }, - { - code: StatusMsg, data: statusData{64, 999, td, head.Hash(), genesis.Hash(), forkID}, - wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId), - }, - { - code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}, forkID}, - wantError: errResp(ErrGenesisMismatch, "0300000000000000000000000000000000000000000000000000000000000000 (!= %x)", genesis.Hash()), - }, - { - code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, - wantError: errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()), - }, - } - for i, test := range tests { - p, errc := newTestPeer("peer", 64, pm, false) - // The send call might hang until reset because - // the protocol might not read the payload. - go p2p.Send(p.app, test.code, test.data) - - select { - case err := <-errc: - if err == nil { - t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError) - } else if err.Error() != test.wantError.Error() { - t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError) - } - case <-time.After(2 * time.Second): - t.Errorf("protocol did not shut down within 2 seconds") - } - p.close() - } -} - -func TestForkIDSplit(t *testing.T) { - var ( - engine = ethash.NewFaker() - - configNoFork = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)} - configProFork = ¶ms.ChainConfig{ - HomesteadBlock: big.NewInt(1), - EIP150Block: big.NewInt(2), - EIP155Block: big.NewInt(2), - EIP158Block: big.NewInt(2), - ByzantiumBlock: big.NewInt(3), - } - dbNoFork = rawdb.NewMemoryDatabase() - dbProFork = rawdb.NewMemoryDatabase() - - gspecNoFork = &core.Genesis{Config: configNoFork} - gspecProFork = &core.Genesis{Config: configProFork} - - genesisNoFork = gspecNoFork.MustCommit(dbNoFork) - genesisProFork = gspecProFork.MustCommit(dbProFork) - - chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil) - chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil) - - blocksNoFork, _ = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil) - blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil) - - ethNoFork, _ = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil) - ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil) - ) - ethNoFork.Start(1000) - ethProFork.Start(1000) - - // Both nodes should allow the other to connect (same genesis, next fork is the same) - p2pNoFork, p2pProFork := p2p.MsgPipe() - peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) - - errc := make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() - - select { - case err := <-errc: - t.Fatalf("frontier nofork <-> profork failed: %v", err) - case <-time.After(250 * time.Millisecond): - p2pNoFork.Close() - p2pProFork.Close() - } - // Progress into Homestead. Fork's match, so we don't care what the future holds - chainNoFork.InsertChain(blocksNoFork[:1]) - chainProFork.InsertChain(blocksProFork[:1]) - - p2pNoFork, p2pProFork = p2p.MsgPipe() - peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) - - errc = make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() - - select { - case err := <-errc: - t.Fatalf("homestead nofork <-> profork failed: %v", err) - case <-time.After(250 * time.Millisecond): - p2pNoFork.Close() - p2pProFork.Close() - } - // Progress into Spurious. Forks mismatch, signalling differing chains, reject - chainNoFork.InsertChain(blocksNoFork[1:2]) - chainProFork.InsertChain(blocksProFork[1:2]) - - p2pNoFork, p2pProFork = p2p.MsgPipe() - peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) - - errc = make(chan error, 2) - go func() { errc <- ethNoFork.handle(peerProFork) }() - go func() { errc <- ethProFork.handle(peerNoFork) }() - - select { - case err := <-errc: - if want := errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()); err.Error() != want.Error() { - t.Fatalf("fork ID rejection error mismatch: have %v, want %v", err, want) - } - case <-time.After(250 * time.Millisecond): - t.Fatalf("split peers not rejected") - } -} - -// This test checks that received transactions are added to the local pool. -func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) } -func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) } -func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) } - -func testRecvTransactions(t *testing.T, protocol int) { - txAdded := make(chan []*types.Transaction) - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, txAdded) - pm.acceptTxs = 1 // mark synced to accept transactions - p, _ := newTestPeer("peer", protocol, pm, true) - defer pm.Stop() - defer p.close() - - tx := newTestTransaction(testAccount, 0, 0) - if err := p2p.Send(p.app, TransactionMsg, []interface{}{tx}); err != nil { - t.Fatalf("send error: %v", err) - } - select { - case added := <-txAdded: - if len(added) != 1 { - t.Errorf("wrong number of added transactions: got %d, want 1", len(added)) - } else if added[0].Hash() != tx.Hash() { - t.Errorf("added wrong tx hash: got %v, want %v", added[0].Hash(), tx.Hash()) - } - case <-time.After(2 * time.Second): - t.Errorf("no NewTxsEvent received within 2 seconds") - } -} - -// This test checks that pending transactions are sent. -func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) } -func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) } -func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) } - -func testSendTransactions(t *testing.T, protocol int) { - pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil) - defer pm.Stop() - - // Fill the pool with big transactions (use a subscription to wait until all - // the transactions are announced to avoid spurious events causing extra - // broadcasts). - const txsize = txsyncPackSize / 10 - alltxs := make([]*types.Transaction, 100) - for nonce := range alltxs { - alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize) - } - pm.txpool.AddRemotes(alltxs) - time.Sleep(100 * time.Millisecond) // Wait until new tx even gets out of the system (lame) - - // Connect several peers. They should all receive the pending transactions. - var wg sync.WaitGroup - checktxs := func(p *testPeer) { - defer wg.Done() - defer p.close() - seen := make(map[common.Hash]bool) - for _, tx := range alltxs { - seen[tx.Hash()] = false - } - for n := 0; n < len(alltxs) && !t.Failed(); { - var forAllHashes func(callback func(hash common.Hash)) - switch protocol { - case 63: - fallthrough - case 64: - msg, err := p.app.ReadMsg() - if err != nil { - t.Errorf("%v: read error: %v", p.Peer, err) - continue - } else if msg.Code != TransactionMsg { - t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code) - continue - } - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - t.Errorf("%v: %v", p.Peer, err) - continue - } - forAllHashes = func(callback func(hash common.Hash)) { - for _, tx := range txs { - callback(tx.Hash()) - } - } - case 65: - msg, err := p.app.ReadMsg() - if err != nil { - t.Errorf("%v: read error: %v", p.Peer, err) - continue - } else if msg.Code != NewPooledTransactionHashesMsg { - t.Errorf("%v: got code %d, want NewPooledTransactionHashesMsg", p.Peer, msg.Code) - continue - } - var hashes []common.Hash - if err := msg.Decode(&hashes); err != nil { - t.Errorf("%v: %v", p.Peer, err) - continue - } - forAllHashes = func(callback func(hash common.Hash)) { - for _, h := range hashes { - callback(h) - } - } - } - forAllHashes(func(hash common.Hash) { - seentx, want := seen[hash] - if seentx { - t.Errorf("%v: got tx more than once: %x", p.Peer, hash) - } - if !want { - t.Errorf("%v: got unexpected tx: %x", p.Peer, hash) - } - seen[hash] = true - n++ - }) - } - } - for i := 0; i < 3; i++ { - p, _ := newTestPeer(fmt.Sprintf("peer #%d", i), protocol, pm, true) - wg.Add(1) - go checktxs(p) - } - wg.Wait() -} - -func TestTransactionPropagation(t *testing.T) { testSyncTransaction(t, true) } -func TestTransactionAnnouncement(t *testing.T) { testSyncTransaction(t, false) } - -func testSyncTransaction(t *testing.T, propagtion bool) { - // Create a protocol manager for transaction fetcher and sender - pmFetcher, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) - defer pmFetcher.Stop() - pmSender, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil) - pmSender.broadcastTxAnnouncesOnly = !propagtion - defer pmSender.Stop() - - // Sync up the two peers - io1, io2 := p2p.MsgPipe() - - go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get)) - go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get)) - - time.Sleep(250 * time.Millisecond) - pmFetcher.doSync(peerToSyncOp(downloader.FullSync, pmFetcher.peers.BestPeer())) - atomic.StoreUint32(&pmFetcher.acceptTxs, 1) - - newTxs := make(chan core.NewTxsEvent, 1024) - sub := pmFetcher.txpool.SubscribeNewTxsEvent(newTxs) - defer sub.Unsubscribe() - - // Fill the pool with new transactions - alltxs := make([]*types.Transaction, 1024) - for nonce := range alltxs { - alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), 0) - } - pmSender.txpool.AddRemotes(alltxs) - - var got int -loop: - for { - select { - case ev := <-newTxs: - got += len(ev.Txs) - if got == 1024 { - break loop - } - case <-time.NewTimer(time.Second).C: - t.Fatal("Failed to retrieve all transaction") - } - } -} - -// Tests that the custom union field encoder and decoder works correctly. -func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { - // Create a "random" hash for testing - var hash common.Hash - for i := range hash { - hash[i] = byte(i) - } - // Assemble some table driven tests - tests := []struct { - packet *getBlockHeadersData - fail bool - }{ - // Providing the origin as either a hash or a number should both work - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}}}, - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}}}, - - // Providing arbitrary query field should also work - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}}, - {fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}}, - - // Providing both the origin hash and origin number must fail - {fail: true, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash, Number: 314}}}, - } - // Iterate over each of the tests and try to encode and then decode - for i, tt := range tests { - bytes, err := rlp.EncodeToBytes(tt.packet) - if err != nil && !tt.fail { - t.Fatalf("test %d: failed to encode packet: %v", i, err) - } else if err == nil && tt.fail { - t.Fatalf("test %d: encode should have failed", i) - } - if !tt.fail { - packet := new(getBlockHeadersData) - if err := rlp.DecodeBytes(bytes, packet); err != nil { - t.Fatalf("test %d: failed to decode packet: %v", i, err) - } - if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount || - packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse { - t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet) - } - } - } -} diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go new file mode 100644 index 0000000000..2349398fae --- /dev/null +++ b/eth/protocols/eth/broadcast.go @@ -0,0 +1,195 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +const ( + // This is the target size for the packs of transactions or announcements. A + // pack can get larger than this if a single transactions exceeds this size. + maxTxPacketSize = 100 * 1024 +) + +// blockPropagation is a block propagation event, waiting for its turn in the +// broadcast queue. +type blockPropagation struct { + block *types.Block + td *big.Int +} + +// broadcastBlocks is a write loop that multiplexes blocks and block accouncements +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) broadcastBlocks() { + for { + select { + case prop := <-p.queuedBlocks: + if err := p.SendNewBlock(prop.block, prop.td); err != nil { + return + } + p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td) + + case block := <-p.queuedBlockAnns: + if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil { + return + } + p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash()) + + case <-p.term: + return + } + } +} + +// broadcastTransactions is a write loop that schedules transaction broadcasts +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) broadcastTransactions() { + var ( + queue []common.Hash // Queue of hashes to broadcast as full transactions + done chan struct{} // Non-nil if background broadcaster is running + fail = make(chan error, 1) // Channel used to receive network error + failed bool // Flag whether a send failed, discard everything onward + ) + for { + // If there's no in-flight broadcast running, check if a new one is needed + if done == nil && len(queue) > 0 { + // Pile transaction until we reach our allowed network limit + var ( + hashes []common.Hash + txs []*types.Transaction + size common.StorageSize + ) + for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { + if tx := p.txpool.Get(queue[i]); tx != nil { + txs = append(txs, tx) + size += tx.Size() + } + hashes = append(hashes, queue[i]) + } + queue = queue[:copy(queue, queue[len(hashes):])] + + // If there's anything available to transfer, fire up an async writer + if len(txs) > 0 { + done = make(chan struct{}) + go func() { + if err := p.SendTransactions(txs); err != nil { + fail <- err + return + } + close(done) + p.Log().Trace("Sent transactions", "count", len(txs)) + }() + } + } + // Transfer goroutine may or may not have been started, listen for events + select { + case hashes := <-p.txBroadcast: + // If the connection failed, discard all transaction events + if failed { + continue + } + // New batch of transactions to be broadcast, queue them (with cap) + queue = append(queue, hashes...) + if len(queue) > maxQueuedTxs { + // Fancy copy and resize to ensure buffer doesn't grow indefinitely + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + } + + case <-done: + done = nil + + case <-fail: + failed = true + + case <-p.term: + return + } + } +} + +// announceTransactions is a write loop that schedules transaction broadcasts +// to the remote peer. The goal is to have an async writer that does not lock up +// node internals and at the same time rate limits queued data. +func (p *Peer) announceTransactions() { + var ( + queue []common.Hash // Queue of hashes to announce as transaction stubs + done chan struct{} // Non-nil if background announcer is running + fail = make(chan error, 1) // Channel used to receive network error + failed bool // Flag whether a send failed, discard everything onward + ) + for { + // If there's no in-flight announce running, check if a new one is needed + if done == nil && len(queue) > 0 { + // Pile transaction hashes until we reach our allowed network limit + var ( + hashes []common.Hash + pending []common.Hash + size common.StorageSize + ) + for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { + if p.txpool.Get(queue[i]) != nil { + pending = append(pending, queue[i]) + size += common.HashLength + } + hashes = append(hashes, queue[i]) + } + queue = queue[:copy(queue, queue[len(hashes):])] + + // If there's anything available to transfer, fire up an async writer + if len(pending) > 0 { + done = make(chan struct{}) + go func() { + if err := p.sendPooledTransactionHashes(pending); err != nil { + fail <- err + return + } + close(done) + p.Log().Trace("Sent transaction announcements", "count", len(pending)) + }() + } + } + // Transfer goroutine may or may not have been started, listen for events + select { + case hashes := <-p.txAnnounce: + // If the connection failed, discard all transaction events + if failed { + continue + } + // New batch of transactions to be broadcast, queue them (with cap) + queue = append(queue, hashes...) + if len(queue) > maxQueuedTxAnns { + // Fancy copy and resize to ensure buffer doesn't grow indefinitely + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + } + + case <-done: + done = nil + + case <-fail: + failed = true + + case <-p.term: + return + } + } +} diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go new file mode 100644 index 0000000000..025479b423 --- /dev/null +++ b/eth/protocols/eth/discovery.go @@ -0,0 +1,65 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `eth` protocol on the discovery. +type enrEntry struct { + ForkID forkid.ID // Fork identifier per EIP-2124 + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "eth" +} + +// StartENRUpdater starts the `eth` ENR updater loop, which listens for chain +// head events and updates the requested node record whenever a fork is passed. +func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { + var newHead = make(chan core.ChainHeadEvent, 10) + sub := chain.SubscribeChainHeadEvent(newHead) + + go func() { + defer sub.Unsubscribe() + for { + select { + case <-newHead: + ln.Set(currentENREntry(chain)) + case <-sub.Err(): + // Would be nice to sync with Stop, but there is no + // good way to do that. + return + } + } + }() +} + +// currentENREntry constructs an `eth` ENR entry based on the current state of the chain. +func currentENREntry(chain *core.BlockChain) *enrEntry { + return &enrEntry{ + ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64()), + } +} diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go new file mode 100644 index 0000000000..25ddcd93ec --- /dev/null +++ b/eth/protocols/eth/handler.go @@ -0,0 +1,512 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "encoding/json" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // estHeaderSize is the approximate size of an RLP encoded block header. + estHeaderSize = 500 + + // maxHeadersServe is the maximum number of block headers to serve. This number + // is there to limit the number of disk lookups. + maxHeadersServe = 1024 + + // maxBodiesServe is the maximum number of block bodies to serve. This number + // is mostly there to limit the number of disk lookups. With 24KB block sizes + // nowadays, the practical limit will always be softResponseLimit. + maxBodiesServe = 1024 + + // maxNodeDataServe is the maximum number of state trie nodes to serve. This + // number is there to limit the number of disk lookups. + maxNodeDataServe = 1024 + + // maxReceiptsServe is the maximum number of block receipts to serve. This + // number is mostly there to limit the number of disk lookups. With block + // containing 200+ transactions nowadays, the practical limit will always + // be softResponseLimit. + maxReceiptsServe = 1024 +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +// Backend defines the data retrieval methods to serve remote requests and the +// callback methods to invoke on remote deliveries. +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // StateBloom retrieves the bloom filter - if any - for state trie nodes. + StateBloom() *trie.SyncBloom + + // TxPool retrieves the transaction pool object to serve data. + TxPool() TxPool + + // AcceptTxs retrieves whether transaction processing is enabled on the node + // or if inbound transactions should simply be dropped. + AcceptTxs() bool + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + // PeerInfo retrieves all known `eth` information about a peer. + PeerInfo(id enode.ID) interface{} + + // Handle is a callback to be invoked when a data packet is received from + // the remote peer. Only packets not consumed by the protocol handler will + // be forwarded to the backend. + Handle(peer *Peer, packet Packet) error +} + +// TxPool defines the methods needed by the protocol handler to serve transactions. +type TxPool interface { + // Get retrieves the the transaction from the local txpool with the given hash. + Get(hash common.Hash) *types.Transaction +} + +// MakeProtocols constructs the P2P protocol definitions for `eth`. +func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { + protocols := make([]p2p.Protocol, len(protocolVersions)) + for i, version := range protocolVersions { + version := version // Closure + + protocols[i] = p2p.Protocol{ + Name: protocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + peer := NewPeer(version, p, rw, backend.TxPool()) + defer peer.Close() + + return backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain(), network) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{currentENREntry(backend.Chain())}, + DialCandidates: dnsdisc, + } + } + return protocols +} + +// NodeInfo represents a short summary of the `eth` sub-protocol metadata +// known about the host peer. +type NodeInfo struct { + Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4) + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain + Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block + Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules + Head common.Hash `json:"head"` // Hex hash of the host's best owned block +} + +// nodeInfo retrieves some `eth` protocol metadata about the running host node. +func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo { + head := chain.CurrentBlock() + return &NodeInfo{ + Network: network, + Difficulty: chain.GetTd(head.Hash(), head.NumberU64()), + Genesis: chain.Genesis().Hash(), + Config: chain.Config(), + Head: head.Hash(), + } +} + +// Handle is invoked whenever an `eth` connection is made that successfully passes +// the protocol handshake. This method will keep processing messages until the +// connection is torn down. +func Handle(backend Backend, peer *Peer) error { + for { + if err := handleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `eth`", "err", err) + return err + } + } +} + +// handleMessage is invoked whenever an inbound message is received from a remote +// peer. The remote connection is torn down upon returning any error. +func handleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + + // Handle the message depending on its contents + switch { + case msg.Code == StatusMsg: + // Status messages should never arrive after the handshake + return fmt.Errorf("%w: uncontrolled status message", errExtraStatusMsg) + + // Block header query, collect the requested headers and reply + case msg.Code == GetBlockHeadersMsg: + // Decode the complex header query + var query GetBlockHeadersPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + hashMode := query.Origin.Hash != (common.Hash{}) + first := true + maxNonCanonical := uint64(100) + + // Gather headers until the fetch or network limits is reached + var ( + bytes common.StorageSize + headers []*types.Header + unknown bool + lookups int + ) + for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && + len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { + lookups++ + // Retrieve the next header satisfying the query + var origin *types.Header + if hashMode { + if first { + first = false + origin = backend.Chain().GetHeaderByHash(query.Origin.Hash) + if origin != nil { + query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number) + } + } else { + origin = backend.Chain().GetHeaderByNumber(query.Origin.Number) + } + if origin == nil { + break + } + headers = append(headers, origin) + bytes += estHeaderSize + + // Advance to the next header of the query + switch { + case hashMode && query.Reverse: + // Hash based traversal towards the genesis block + ancestor := query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) + unknown = (query.Origin.Hash == common.Hash{}) + } + case hashMode && !query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") + peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := backend.Chain().GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) + if expOldHash == query.Origin.Hash { + query.Origin.Hash, query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case query.Reverse: + // Number based traversal towards the genesis block + if query.Origin.Number >= query.Skip+1 { + query.Origin.Number -= query.Skip + 1 + } else { + unknown = true + } + + case !query.Reverse: + // Number based traversal towards the leaf block + query.Origin.Number += query.Skip + 1 + } + } + return peer.SendBlockHeaders(headers) + + case msg.Code == BlockHeadersMsg: + // A batch of headers arrived to one of our previous requests + res := new(BlockHeadersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetBlockBodiesMsg: + // Decode the block body retrieval message + var query GetBlockBodiesPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather blocks until the fetch or network limits is reached + var ( + bytes int + bodies []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || + lookups >= 2*maxBodiesServe { + break + } + if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 { + bodies = append(bodies, data) + bytes += len(data) + } + } + return peer.SendBlockBodiesRLP(bodies) + + case msg.Code == BlockBodiesMsg: + // A batch of block bodies arrived to one of our previous requests + res := new(BlockBodiesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetNodeDataMsg: + // Decode the trie node data retrieval message + var query GetNodeDataPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather state data until the fetch or network limits is reached + var ( + bytes int + nodes [][]byte + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || + lookups >= 2*maxNodeDataServe { + break + } + // Retrieve the requested state entry + if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) { + // Only lookup the trie node if there's chance that we actually have it + continue + } + entry, err := backend.Chain().TrieNode(hash) + if len(entry) == 0 || err != nil { + // Read the contract code with prefix only to save unnecessary lookups. + entry, err = backend.Chain().ContractCodeWithPrefix(hash) + } + if err == nil && len(entry) > 0 { + nodes = append(nodes, entry) + bytes += len(entry) + } + } + return peer.SendNodeData(nodes) + + case msg.Code == NodeDataMsg: + // A batch of node state data arrived to one of our previous requests + res := new(NodeDataPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetReceiptsMsg: + // Decode the block receipts retrieval message + var query GetReceiptsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather state data until the fetch or network limits is reached + var ( + bytes int + receipts []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || + lookups >= 2*maxReceiptsServe { + break + } + // Retrieve the requested block's receipts + results := backend.Chain().GetReceiptsByHash(hash) + if results == nil { + if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + continue + } + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(results); err != nil { + log.Error("Failed to encode receipt", "err", err) + } else { + receipts = append(receipts, encoded) + bytes += len(encoded) + } + } + return peer.SendReceiptsRLP(receipts) + + case msg.Code == ReceiptsMsg: + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == NewBlockHashesMsg: + // A batch of new block announcements just arrived + ann := new(NewBlockHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Mark the hashes as present at the remote node + for _, block := range *ann { + peer.markBlock(block.Hash) + } + // Deliver them all to the backend for queuing + return backend.Handle(peer, ann) + + case msg.Code == NewBlockMsg: + // Retrieve and decode the propagated block + ann := new(NewBlockPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { + log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) + break // TODO(karalabe): return error eventually, but wait a few releases + } + if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() { + log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) + break // TODO(karalabe): return error eventually, but wait a few releases + } + if err := ann.sanityCheck(); err != nil { + return err + } + ann.Block.ReceivedAt = msg.ReceivedAt + ann.Block.ReceivedFrom = peer + + // Mark the peer as owning the block + peer.markBlock(ann.Block.Hash()) + + return backend.Handle(peer, ann) + + case msg.Code == NewPooledTransactionHashesMsg && peer.version >= ETH65: + // New transaction announcement arrived, make sure we have + // a valid and fresh chain to handle them + if !backend.AcceptTxs() { + break + } + ann := new(NewPooledTransactionHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Schedule all the unknown hashes for retrieval + for _, hash := range *ann { + peer.markTransaction(hash) + } + return backend.Handle(peer, ann) + + case msg.Code == GetPooledTransactionsMsg && peer.version >= ETH65: + // Decode the pooled transactions retrieval message + var query GetPooledTransactionsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Gather transactions until the fetch or network limits is reached + var ( + bytes int + hashes []common.Hash + txs []rlp.RawValue + ) + for _, hash := range query { + if bytes >= softResponseLimit { + break + } + // Retrieve the requested transaction, skipping if unknown to us + tx := backend.TxPool().Get(hash) + if tx == nil { + continue + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(tx); err != nil { + log.Error("Failed to encode transaction", "err", err) + } else { + hashes = append(hashes, hash) + txs = append(txs, encoded) + bytes += len(encoded) + } + } + return peer.SendPooledTransactionsRLP(hashes, txs) + + case msg.Code == TransactionsMsg || (msg.Code == PooledTransactionsMsg && peer.version >= ETH65): + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + break + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs []*types.Transaction + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + if msg.Code == PooledTransactionsMsg { + return backend.Handle(peer, (*PooledTransactionsPacket)(&txs)) + } + return backend.Handle(peer, (*TransactionsPacket)(&txs)) + + default: + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + } + return nil +} diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go new file mode 100644 index 0000000000..65c4a10b0a --- /dev/null +++ b/eth/protocols/eth/handler_test.go @@ -0,0 +1,519 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math" + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +// testBackend is a mock implementation of the live Ethereum message handler. Its +// purpose is to allow testing the request/reply workflows and wire serialization +// in the `eth` protocol without actually doing any data processing. +type testBackend struct { + db ethdb.Database + chain *core.BlockChain + txpool *core.TxPool +} + +// newTestBackend creates an empty chain and wraps it into a mock backend. +func newTestBackend(blocks int) *testBackend { + return newTestBackendWithGenerator(blocks, nil) +} + +// newTestBackend creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend { + // Create a database pre-initialize with a genesis block + db := rawdb.NewMemoryDatabase() + (&core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + }).MustCommit(db) + + chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) + + bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator) + if _, err := chain.InsertChain(bs); err != nil { + panic(err) + } + txconfig := core.DefaultTxPoolConfig + txconfig.Journal = "" // Don't litter the disk with test journals + + return &testBackend{ + db: db, + chain: chain, + txpool: core.NewTxPool(txconfig, params.TestChainConfig, chain), + } +} + +// close tears down the transaction pool and chain behind the mock backend. +func (b *testBackend) close() { + b.txpool.Stop() + b.chain.Stop() +} + +func (b *testBackend) Chain() *core.BlockChain { return b.chain } +func (b *testBackend) StateBloom() *trie.SyncBloom { return nil } +func (b *testBackend) TxPool() TxPool { return b.txpool } + +func (b *testBackend) RunPeer(peer *Peer, handler Handler) error { + // Normally the backend would do peer mainentance and handshakes. All that + // is omitted and we will just give control back to the handler. + return handler(peer) +} +func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") } + +func (b *testBackend) AcceptTxs() bool { + panic("data processing tests should be done in the handler package") +} +func (b *testBackend) Handle(*Peer, Packet) error { + panic("data processing tests should be done in the handler package") +} + +// Tests that block headers can be retrieved from a remote chain based on user queries. +func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } +func TestGetBlockHeaders65(t *testing.T) { testGetBlockHeaders(t, 65) } + +func testGetBlockHeaders(t *testing.T, protocol uint) { + t.Parallel() + + backend := newTestBackend(maxHeadersServe + 15) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Create a "random" unknown hash for testing + var unknown common.Hash + for i := range unknown { + unknown[i] = byte(i) + } + // Create a batch of tests for various scenarios + limit := uint64(maxHeadersServe) + tests := []struct { + query *GetBlockHeadersPacket // The query to execute for header retrieval + expect []common.Hash // The hashes of the block whose headers are expected + }{ + // A single random block should be retrievable by hash and number too + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, + }, + // Multiple headers should be retrievable in both directions + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 1).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 2).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 1).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 2).Hash(), + }, + }, + // Multiple headers with skip lists should be retrievable + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 4).Hash(), + backend.chain.GetBlockByNumber(limit/2 + 8).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(limit / 2).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 4).Hash(), + backend.chain.GetBlockByNumber(limit/2 - 8).Hash(), + }, + }, + // The chain endpoints should be retrievable + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 0}, Amount: 1}, + []common.Hash{backend.chain.GetBlockByNumber(0).Hash()}, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64()}, Amount: 1}, + []common.Hash{backend.chain.CurrentBlock().Hash()}, + }, + // Ensure protocol limits are honored + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, + backend.chain.GetBlockHashesFromHash(backend.chain.CurrentBlock().Hash(), limit), + }, + // Check that requesting more than available is handled gracefully + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(), + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64()).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(4).Hash(), + backend.chain.GetBlockByNumber(0).Hash(), + }, + }, + // Check that requesting more than available is handled gracefully, even if mid skip + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, + []common.Hash{ + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(), + backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 1).Hash(), + }, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(4).Hash(), + backend.chain.GetBlockByNumber(1).Hash(), + }, + }, + // Check a corner case where requesting more can iterate past the endpoints + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true}, + []common.Hash{ + backend.chain.GetBlockByNumber(2).Hash(), + backend.chain.GetBlockByNumber(1).Hash(), + backend.chain.GetBlockByNumber(0).Hash(), + }, + }, + // Check a corner case where skipping overflow loops back into the chain start + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1}, + []common.Hash{ + backend.chain.GetBlockByNumber(3).Hash(), + }, + }, + // Check a corner case where skipping overflow loops back to the same header + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64}, + []common.Hash{ + backend.chain.GetBlockByNumber(1).Hash(), + }, + }, + // Check that non existing headers aren't returned + { + &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: unknown}, Amount: 1}, + []common.Hash{}, + }, { + &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() + 1}, Amount: 1}, + []common.Hash{}, + }, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the headers to expect in the response + var headers []*types.Header + for _, hash := range tt.expect { + headers = append(headers, backend.chain.GetBlockByHash(hash).Header()) + } + // Send the hash request and verify the response + p2p.Send(peer.app, 0x03, tt.query) + if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + // If the test used number origins, repeat with hashes as the too + if tt.query.Origin.Hash == (common.Hash{}) { + if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { + tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 + + p2p.Send(peer.app, 0x03, tt.query) + if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + } + } + } +} + +// Tests that block contents can be retrieved from a remote chain based on their hashes. +func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) } +func TestGetBlockBodies65(t *testing.T) { testGetBlockBodies(t, 65) } + +func testGetBlockBodies(t *testing.T, protocol uint) { + t.Parallel() + + backend := newTestBackend(maxBodiesServe + 15) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Create a batch of tests for various scenarios + limit := maxBodiesServe + tests := []struct { + random int // Number of blocks to fetch randomly from the chain + explicit []common.Hash // Explicitly requested blocks + available []bool // Availability of explicitly requested blocks + expected int // Total number of existing blocks to expect + }{ + {1, nil, nil, 1}, // A single random block should be retrievable + {10, nil, nil, 10}, // Multiple random blocks should be retrievable + {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable + {limit + 1, nil, nil, limit}, // No more than the possible block count should be returned + {0, []common.Hash{backend.chain.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable + {0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable + {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned + + // Existing and non-existing blocks interleaved should not cause problems + {0, []common.Hash{ + {}, + backend.chain.GetBlockByNumber(1).Hash(), + {}, + backend.chain.GetBlockByNumber(10).Hash(), + {}, + backend.chain.GetBlockByNumber(100).Hash(), + {}, + }, []bool{false, true, false, true, false, true, false}, 3}, + } + // Run each of the tests and verify the results against the chain + for i, tt := range tests { + // Collect the hashes to request, and the response to expectva + var ( + hashes []common.Hash + bodies []*BlockBody + seen = make(map[int64]bool) + ) + for j := 0; j < tt.random; j++ { + for { + num := rand.Int63n(int64(backend.chain.CurrentBlock().NumberU64())) + if !seen[num] { + seen[num] = true + + block := backend.chain.GetBlockByNumber(uint64(num)) + hashes = append(hashes, block.Hash()) + if len(bodies) < tt.expected { + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) + } + break + } + } + } + for j, hash := range tt.explicit { + hashes = append(hashes, hash) + if tt.available[j] && len(bodies) < tt.expected { + block := backend.chain.GetBlockByHash(hash) + bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()}) + } + } + // Send the hash request and verify the response + p2p.Send(peer.app, 0x05, hashes) + if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil { + t.Errorf("test %d: bodies mismatch: %v", i, err) + } + } +} + +// Tests that the state trie nodes can be retrieved based on hashes. +func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) } +func TestGetNodeData65(t *testing.T) { testGetNodeData(t, 65) } + +func testGetNodeData(t *testing.T, protocol uint) { + t.Parallel() + + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + signer := types.HomesteadSigner{} + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + backend := newTestBackendWithGenerator(4, generator) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Fetch for now the entire chain db + var hashes []common.Hash + + it := backend.db.NewIterator(nil, nil) + for it.Next() { + if key := it.Key(); len(key) == common.HashLength { + hashes = append(hashes, common.BytesToHash(key)) + } + } + it.Release() + + p2p.Send(peer.app, 0x0d, hashes) + msg, err := peer.app.ReadMsg() + if err != nil { + t.Fatalf("failed to read node data response: %v", err) + } + if msg.Code != 0x0e { + t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) + } + var data [][]byte + if err := msg.Decode(&data); err != nil { + t.Fatalf("failed to decode response node data: %v", err) + } + // Verify that all hashes correspond to the requested data, and reconstruct a state tree + for i, want := range hashes { + if hash := crypto.Keccak256Hash(data[i]); hash != want { + t.Errorf("data hash mismatch: have %x, want %x", hash, want) + } + } + statedb := rawdb.NewMemoryDatabase() + for i := 0; i < len(data); i++ { + statedb.Put(hashes[i].Bytes(), data[i]) + } + accounts := []common.Address{testAddr, acc1Addr, acc2Addr} + for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ { + trie, _ := state.New(backend.chain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil) + + for j, acc := range accounts { + state, _ := backend.chain.State() + bw := state.GetBalance(acc) + bh := trie.GetBalance(acc) + + if (bw != nil && bh == nil) || (bw == nil && bh != nil) { + t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) + } + if bw != nil && bh != nil && bw.Cmp(bw) != 0 { + t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) + } + } + } +} + +// Tests that the transaction receipts can be retrieved based on hashes. +func TestGetBlockReceipts64(t *testing.T) { testGetBlockReceipts(t, 64) } +func TestGetBlockReceipts65(t *testing.T) { testGetBlockReceipts(t, 65) } + +func testGetBlockReceipts(t *testing.T, protocol uint) { + t.Parallel() + + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + signer := types.HomesteadSigner{} + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + backend := newTestBackendWithGenerator(4, generator) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + // Collect the hashes to request, and the response to expect + var ( + hashes []common.Hash + receipts []types.Receipts + ) + for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ { + block := backend.chain.GetBlockByNumber(i) + + hashes = append(hashes, block.Hash()) + receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) + } + // Send the hash request and verify the response + p2p.Send(peer.app, 0x0f, hashes) + if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil { + t.Errorf("receipts mismatch: %v", err) + } +} diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go new file mode 100644 index 0000000000..57a4e0bc34 --- /dev/null +++ b/eth/protocols/eth/handshake.go @@ -0,0 +1,107 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" +) + +const ( + // handshakeTimeout is the maximum allowed time for the `eth` handshake to + // complete before dropping the connection.= as malicious. + handshakeTimeout = 5 * time.Second +) + +// Handshake executes the eth protocol handshake, negotiating version number, +// network IDs, difficulties, head and genesis blocks. +func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error { + // Send out own handshake in a new thread + errc := make(chan error, 2) + + var status StatusPacket // safe to read after two values have been received from errc + + go func() { + errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{ + ProtocolVersion: uint32(p.version), + NetworkID: network, + TD: td, + Head: head, + Genesis: genesis, + ForkID: forkID, + }) + }() + go func() { + errc <- p.readStatus(network, &status, genesis, forkFilter) + }() + timeout := time.NewTimer(handshakeTimeout) + defer timeout.Stop() + for i := 0; i < 2; i++ { + select { + case err := <-errc: + if err != nil { + return err + } + case <-timeout.C: + return p2p.DiscReadTimeout + } + } + p.td, p.head = status.TD, status.Head + + // TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times + // larger, it will still fit within 100 bits + if tdlen := p.td.BitLen(); tdlen > 100 { + return fmt.Errorf("too large total difficulty: bitlen %d", tdlen) + } + return nil +} + +// readStatus reads the remote handshake message. +func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error { + msg, err := p.rw.ReadMsg() + if err != nil { + return err + } + if msg.Code != StatusMsg { + return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoStatusMsg, msg.Code, StatusMsg) + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + // Decode the handshake and make sure everything matches + if err := msg.Decode(&status); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if status.NetworkID != network { + return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network) + } + if uint(status.ProtocolVersion) != p.version { + return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version) + } + if status.Genesis != genesis { + return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis) + } + if err := forkFilter(status.ForkID); err != nil { + return fmt.Errorf("%w: %v", errForkIDRejected, err) + } + return nil +} diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go new file mode 100644 index 0000000000..65f9a00064 --- /dev/null +++ b/eth/protocols/eth/handshake_test.go @@ -0,0 +1,91 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// Tests that handshake failures are detected and reported correctly. +func TestHandshake64(t *testing.T) { testHandshake(t, 64) } +func TestHandshake65(t *testing.T) { testHandshake(t, 65) } + +func testHandshake(t *testing.T, protocol uint) { + t.Parallel() + + // Create a test backend only to have some valid genesis chain + backend := newTestBackend(3) + defer backend.close() + + var ( + genesis = backend.chain.Genesis() + head = backend.chain.CurrentBlock() + td = backend.chain.GetTd(head.Hash(), head.NumberU64()) + forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64()) + ) + tests := []struct { + code uint64 + data interface{} + want error + }{ + { + code: TransactionsMsg, data: []interface{}{}, + want: errNoStatusMsg, + }, + { + code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID}, + want: errProtocolVersionMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID}, + want: errNetworkIDMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID}, + want: errGenesisMismatch, + }, + { + code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}}, + want: errForkIDRejected, + }, + } + for i, test := range tests { + // Create the two peers to shake with each other + app, net := p2p.MsgPipe() + defer app.Close() + defer net.Close() + + peer := NewPeer(protocol, p2p.NewPeer(enode.ID{}, "peer", nil), net, nil) + defer peer.Close() + + // Send the junk test with one peer, check the handshake failure + go p2p.Send(app, test.code, test.data) + + err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain)) + if err == nil { + t.Errorf("test %d: protocol returned nil error, want %q", i, test.want) + } else if !errors.Is(err, test.want) { + t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.want) + } + } +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go new file mode 100644 index 0000000000..735ef78ce7 --- /dev/null +++ b/eth/protocols/eth/peer.go @@ -0,0 +1,429 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "math/big" + "sync" + + mapset "github.com/deckarep/golang-set" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + // maxKnownTxs is the maximum transactions hashes to keep in the known list + // before starting to randomly evict them. + maxKnownTxs = 32768 + + // maxKnownBlocks is the maximum block hashes to keep in the known list + // before starting to randomly evict them. + maxKnownBlocks = 1024 + + // maxQueuedTxs is the maximum number of transactions to queue up before dropping + // older broadcasts. + maxQueuedTxs = 4096 + + // maxQueuedTxAnns is the maximum number of transaction announcements to queue up + // before dropping older announcements. + maxQueuedTxAnns = 4096 + + // maxQueuedBlocks is the maximum number of block propagations to queue up before + // dropping broadcasts. There's not much point in queueing stale blocks, so a few + // that might cover uncles should be enough. + maxQueuedBlocks = 4 + + // maxQueuedBlockAnns is the maximum number of block announcements to queue up before + // dropping broadcasts. Similarly to block propagations, there's no point to queue + // above some healthy uncle limit, so use that. + maxQueuedBlockAnns = 4 +) + +// max is a helper function which returns the larger of the two given integers. +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// Peer is a collection of relevant information we have about a `eth` peer. +type Peer struct { + id string // Unique ID for the peer, cached + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for snap + version uint // Protocol version negotiated + + head common.Hash // Latest advertised head block hash + td *big.Int // Latest advertised head block total difficulty + + knownBlocks mapset.Set // Set of block hashes known to be known by this peer + queuedBlocks chan *blockPropagation // Queue of blocks to broadcast to the peer + queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer + + txpool TxPool // Transaction pool used by the broadcasters for liveness checks + knownTxs mapset.Set // Set of transaction hashes known to be known by this peer + txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests + txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests + + term chan struct{} // Termination channel to stop the broadcasters + lock sync.RWMutex // Mutex protecting the internal fields +} + +// NewPeer create a wrapper for a network connection and negotiated protocol +// version. +func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer { + peer := &Peer{ + id: p.ID().String(), + Peer: p, + rw: rw, + version: version, + knownTxs: mapset.NewSet(), + knownBlocks: mapset.NewSet(), + queuedBlocks: make(chan *blockPropagation, maxQueuedBlocks), + queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns), + txBroadcast: make(chan []common.Hash), + txAnnounce: make(chan []common.Hash), + txpool: txpool, + term: make(chan struct{}), + } + // Start up all the broadcasters + go peer.broadcastBlocks() + go peer.broadcastTransactions() + if version >= ETH65 { + go peer.announceTransactions() + } + return peer +} + +// Close signals the broadcast goroutine to terminate. Only ever call this if +// you created the peer yourself via NewPeer. Otherwise let whoever created it +// clean it up! +func (p *Peer) Close() { + close(p.term) +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negoatiated `eth` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +// Head retrieves the current head hash and total difficulty of the peer. +func (p *Peer) Head() (hash common.Hash, td *big.Int) { + p.lock.RLock() + defer p.lock.RUnlock() + + copy(hash[:], p.head[:]) + return hash, new(big.Int).Set(p.td) +} + +// SetHead updates the head hash and total difficulty of the peer. +func (p *Peer) SetHead(hash common.Hash, td *big.Int) { + p.lock.Lock() + defer p.lock.Unlock() + + copy(p.head[:], hash[:]) + p.td.Set(td) +} + +// KnownBlock returns whether peer is known to already have a block. +func (p *Peer) KnownBlock(hash common.Hash) bool { + return p.knownBlocks.Contains(hash) +} + +// KnownTransaction returns whether peer is known to already have a transaction. +func (p *Peer) KnownTransaction(hash common.Hash) bool { + return p.knownTxs.Contains(hash) +} + +// markBlock marks a block as known for the peer, ensuring that the block will +// never be propagated to this particular peer. +func (p *Peer) markBlock(hash common.Hash) { + // If we reached the memory allowance, drop a previously known block hash + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(hash) +} + +// markTransaction marks a transaction as known for the peer, ensuring that it +// will never be propagated to this particular peer. +func (p *Peer) markTransaction(hash common.Hash) { + // If we reached the memory allowance, drop a previously known transaction hash + for p.knownTxs.Cardinality() >= maxKnownTxs { + p.knownTxs.Pop() + } + p.knownTxs.Add(hash) +} + +// SendTransactions sends transactions to the peer and includes the hashes +// in its transaction hash set for future reference. +// +// This method is a helper used by the async transaction sender. Don't call it +// directly as the queueing (memory) and transmission (bandwidth) costs should +// not be managed directly. +// +// The reasons this is public is to allow packages using this protocol to write +// tests that directly send messages without having to do the asyn queueing. +func (p *Peer) SendTransactions(txs types.Transactions) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) { + p.knownTxs.Pop() + } + for _, tx := range txs { + p.knownTxs.Add(tx.Hash()) + } + return p2p.Send(p.rw, TransactionsMsg, txs) +} + +// AsyncSendTransactions queues a list of transactions (by hash) to eventually +// propagate to a remote peer. The number of pending sends are capped (new ones +// will force old sends to be dropped) +func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { + select { + case p.txBroadcast <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + case <-p.term: + p.Log().Debug("Dropping transaction propagation", "count", len(hashes)) + } +} + +// sendPooledTransactionHashes sends transaction hashes to the peer and includes +// them in its transaction hash set for future reference. +// +// This method is a helper used by the async transaction announcer. Don't call it +// directly as the queueing (memory) and transmission (bandwidth) costs should +// not be managed directly. +func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket(hashes)) +} + +// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually +// announce to a remote peer. The number of pending sends are capped (new ones +// will force old sends to be dropped) +func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { + select { + case p.txAnnounce <- hashes: + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + case <-p.term: + p.Log().Debug("Dropping transaction announcement", "count", len(hashes)) + } +} + +// SendPooledTransactionsRLP sends requested transactions to the peer and adds the +// hashes in its transaction hash set for future reference. +// +// Note, the method assumes the hashes are correct and correspond to the list of +// transactions being sent. +func (p *Peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + return p2p.Send(p.rw, PooledTransactionsMsg, txs) // Not packed into PooledTransactionsPacket to avoid RLP decoding +} + +// SendNewBlockHashes announces the availability of a number of blocks through +// a hash notification. +func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { + // Mark all the block hashes as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) { + p.knownBlocks.Pop() + } + for _, hash := range hashes { + p.knownBlocks.Add(hash) + } + request := make(NewBlockHashesPacket, len(hashes)) + for i := 0; i < len(hashes); i++ { + request[i].Hash = hashes[i] + request[i].Number = numbers[i] + } + return p2p.Send(p.rw, NewBlockHashesMsg, request) +} + +// AsyncSendNewBlockHash queues the availability of a block for propagation to a +// remote peer. If the peer's broadcast queue is full, the event is silently +// dropped. +func (p *Peer) AsyncSendNewBlockHash(block *types.Block) { + select { + case p.queuedBlockAnns <- block: + // Mark all the block hash as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(block.Hash()) + default: + p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash()) + } +} + +// SendNewBlock propagates an entire block to a remote peer. +func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { + // Mark all the block hash as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(block.Hash()) + return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{block, td}) +} + +// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If +// the peer's broadcast queue is full, the event is silently dropped. +func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { + select { + case p.queuedBlocks <- &blockPropagation{block: block, td: td}: + // Mark all the block hash as known, but ensure we don't overflow our limits + for p.knownBlocks.Cardinality() >= maxKnownBlocks { + p.knownBlocks.Pop() + } + p.knownBlocks.Add(block.Hash()) + default: + p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash()) + } +} + +// SendBlockHeaders sends a batch of block headers to the remote peer. +func (p *Peer) SendBlockHeaders(headers []*types.Header) error { + return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket(headers)) +} + +// SendBlockBodies sends a batch of block contents to the remote peer. +func (p *Peer) SendBlockBodies(bodies []*BlockBody) error { + return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesPacket(bodies)) +} + +// SendBlockBodiesRLP sends a batch of block contents to the remote peer from +// an already RLP encoded format. +func (p *Peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error { + return p2p.Send(p.rw, BlockBodiesMsg, bodies) // Not packed into BlockBodiesPacket to avoid RLP decoding +} + +// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the +// hashes requested. +func (p *Peer) SendNodeData(data [][]byte) error { + return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket(data)) +} + +// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the +// ones requested from an already RLP encoded format. +func (p *Peer) SendReceiptsRLP(receipts []rlp.RawValue) error { + return p2p.Send(p.rw, ReceiptsMsg, receipts) // Not packed into ReceiptsPacket to avoid RLP decoding +} + +// RequestOneHeader is a wrapper around the header query functions to fetch a +// single header. It is used solely by the fetcher. +func (p *Peer) RequestOneHeader(hash common.Hash) error { + p.Log().Debug("Fetching single header", "hash", hash) + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + Origin: HashOrNumber{Hash: hash}, + Amount: uint64(1), + Skip: uint64(0), + Reverse: false, + }) +} + +// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the +// specified header query, based on the hash of an origin block. +func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { + p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + Origin: HashOrNumber{Hash: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }) +} + +// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the +// specified header query, based on the number of an origin block. +func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { + p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + Origin: HashOrNumber{Number: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + }) +} + +// ExpectRequestHeadersByNumber is a testing method to mirror the recipient side +// of the RequestHeadersByNumber operation. +func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { + req := &GetBlockHeadersPacket{ + Origin: HashOrNumber{Number: origin}, + Amount: uint64(amount), + Skip: uint64(skip), + Reverse: reverse, + } + return p2p.ExpectMsg(p.rw, GetBlockHeadersMsg, req) +} + +// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes +// specified. +func (p *Peer) RequestBodies(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) + return p2p.Send(p.rw, GetBlockBodiesMsg, GetBlockBodiesPacket(hashes)) +} + +// RequestNodeData fetches a batch of arbitrary data from a node's known state +// data, corresponding to the specified hashes. +func (p *Peer) RequestNodeData(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of state data", "count", len(hashes)) + return p2p.Send(p.rw, GetNodeDataMsg, GetNodeDataPacket(hashes)) +} + +// RequestReceipts fetches a batch of transaction receipts from a remote node. +func (p *Peer) RequestReceipts(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) + return p2p.Send(p.rw, GetReceiptsMsg, GetReceiptsPacket(hashes)) +} + +// RequestTxs fetches a batch of transactions from a remote node. +func (p *Peer) RequestTxs(hashes []common.Hash) error { + p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) + return p2p.Send(p.rw, GetPooledTransactionsMsg, GetPooledTransactionsPacket(hashes)) +} diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go new file mode 100644 index 0000000000..70e9959f82 --- /dev/null +++ b/eth/protocols/eth/peer_test.go @@ -0,0 +1,61 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. + +package eth + +import ( + "crypto/rand" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// testPeer is a simulated peer to allow testing direct network calls. +type testPeer struct { + *Peer + + net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging + app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side +} + +// newTestPeer creates a new peer registered at the given data backend. +func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Start the peer on a new thread + var id enode.ID + rand.Read(id[:]) + + peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool()) + errc := make(chan error, 1) + go func() { + errc <- backend.RunPeer(peer, func(peer *Peer) error { + return Handle(backend, peer) + }) + }() + return &testPeer{app: app, net: net, Peer: peer}, errc +} + +// close terminates the local side of the peer, notifying the remote protocol +// manager of termination. +func (p *testPeer) close() { + p.Peer.Close() + p.app.Close() +} diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go new file mode 100644 index 0000000000..63d3494ec4 --- /dev/null +++ b/eth/protocols/eth/protocol.go @@ -0,0 +1,279 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + ETH64 = 64 + ETH65 = 65 +) + +// protocolName is the official short name of the `eth` protocol used during +// devp2p capability negotiation. +const protocolName = "eth" + +// protocolVersions are the supported versions of the `eth` protocol (first +// is primary). +var protocolVersions = []uint{ETH65, ETH64} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{ETH65: 17, ETH64: 17} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + // Protocol messages in eth/64 + StatusMsg = 0x00 + NewBlockHashesMsg = 0x01 + TransactionsMsg = 0x02 + GetBlockHeadersMsg = 0x03 + BlockHeadersMsg = 0x04 + GetBlockBodiesMsg = 0x05 + BlockBodiesMsg = 0x06 + NewBlockMsg = 0x07 + GetNodeDataMsg = 0x0d + NodeDataMsg = 0x0e + GetReceiptsMsg = 0x0f + ReceiptsMsg = 0x10 + + // Protocol messages overloaded in eth/65 + NewPooledTransactionHashesMsg = 0x08 + GetPooledTransactionsMsg = 0x09 + PooledTransactionsMsg = 0x0a +) + +var ( + errNoStatusMsg = errors.New("no status message") + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errProtocolVersionMismatch = errors.New("protocol version mismatch") + errNetworkIDMismatch = errors.New("network ID mismatch") + errGenesisMismatch = errors.New("genesis mismatch") + errForkIDRejected = errors.New("fork ID rejected") + errExtraStatusMsg = errors.New("extra status message") +) + +// Packet represents a p2p message in the `eth` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +// StatusPacket is the network packet for the status message for eth/64 and later. +type StatusPacket struct { + ProtocolVersion uint32 + NetworkID uint64 + TD *big.Int + Head common.Hash + Genesis common.Hash + ForkID forkid.ID +} + +// NewBlockHashesPacket is the network packet for the block announcements. +type NewBlockHashesPacket []struct { + Hash common.Hash // Hash of one particular block being announced + Number uint64 // Number of one particular block being announced +} + +// Unpack retrieves the block hashes and numbers from the announcement packet +// and returns them in a split flat format that's more consistent with the +// internal data structures. +func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) { + var ( + hashes = make([]common.Hash, len(*p)) + numbers = make([]uint64, len(*p)) + ) + for i, body := range *p { + hashes[i], numbers[i] = body.Hash, body.Number + } + return hashes, numbers +} + +// TransactionsPacket is the network packet for broadcasting new transactions. +type TransactionsPacket []*types.Transaction + +// GetBlockHeadersPacket represents a block header query. +type GetBlockHeadersPacket struct { + Origin HashOrNumber // Block from which to retrieve headers + Amount uint64 // Maximum number of headers to retrieve + Skip uint64 // Blocks to skip between consecutive headers + Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) +} + +// HashOrNumber is a combined field for specifying an origin block. +type HashOrNumber struct { + Hash common.Hash // Block hash from which to retrieve headers (excludes Number) + Number uint64 // Block hash from which to retrieve headers (excludes Hash) +} + +// EncodeRLP is a specialized encoder for HashOrNumber to encode only one of the +// two contained union fields. +func (hn *HashOrNumber) EncodeRLP(w io.Writer) error { + if hn.Hash == (common.Hash{}) { + return rlp.Encode(w, hn.Number) + } + if hn.Number != 0 { + return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) + } + return rlp.Encode(w, hn.Hash) +} + +// DecodeRLP is a specialized decoder for HashOrNumber to decode the contents +// into either a block hash or a block number. +func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { + _, size, _ := s.Kind() + origin, err := s.Raw() + if err == nil { + switch { + case size == 32: + err = rlp.DecodeBytes(origin, &hn.Hash) + case size <= 8: + err = rlp.DecodeBytes(origin, &hn.Number) + default: + err = fmt.Errorf("invalid input size %d for origin", size) + } + } + return err +} + +// BlockHeadersPacket represents a block header response. +type BlockHeadersPacket []*types.Header + +// NewBlockPacket is the network packet for the block propagation message. +type NewBlockPacket struct { + Block *types.Block + TD *big.Int +} + +// sanityCheck verifies that the values are reasonable, as a DoS protection +func (request *NewBlockPacket) sanityCheck() error { + if err := request.Block.SanityCheck(); err != nil { + return err + } + //TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times + // larger, it will still fit within 100 bits + if tdlen := request.TD.BitLen(); tdlen > 100 { + return fmt.Errorf("too large block TD: bitlen %d", tdlen) + } + return nil +} + +// GetBlockBodiesPacket represents a block body query. +type GetBlockBodiesPacket []common.Hash + +// BlockBodiesPacket is the network packet for block content distribution. +type BlockBodiesPacket []*BlockBody + +// BlockBody represents the data content of a single block. +type BlockBody struct { + Transactions []*types.Transaction // Transactions contained within a block + Uncles []*types.Header // Uncles contained within a block +} + +// Unpack retrieves the transactions and uncles from the range packet and returns +// them in a split flat format that's more consistent with the internal data structures. +func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) { + var ( + txset = make([][]*types.Transaction, len(*p)) + uncleset = make([][]*types.Header, len(*p)) + ) + for i, body := range *p { + txset[i], uncleset[i] = body.Transactions, body.Uncles + } + return txset, uncleset +} + +// GetNodeDataPacket represents a trie node data query. +type GetNodeDataPacket []common.Hash + +// NodeDataPacket is the network packet for trie node data distribution. +type NodeDataPacket [][]byte + +// GetReceiptsPacket represents a block receipts query. +type GetReceiptsPacket []common.Hash + +// ReceiptsPacket is the network packet for block receipts distribution. +type ReceiptsPacket [][]*types.Receipt + +// NewPooledTransactionHashesPacket represents a transaction announcement packet. +type NewPooledTransactionHashesPacket []common.Hash + +// GetPooledTransactionsPacket represents a transaction query. +type GetPooledTransactionsPacket []common.Hash + +// PooledTransactionsPacket is the network packet for transaction distribution. +type PooledTransactionsPacket []*types.Transaction + +func (*StatusPacket) Name() string { return "Status" } +func (*StatusPacket) Kind() byte { return StatusMsg } + +func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" } +func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg } + +func (*TransactionsPacket) Name() string { return "Transactions" } +func (*TransactionsPacket) Kind() byte { return TransactionsMsg } + +func (*GetBlockHeadersPacket) Name() string { return "GetBlockHeaders" } +func (*GetBlockHeadersPacket) Kind() byte { return GetBlockHeadersMsg } + +func (*BlockHeadersPacket) Name() string { return "BlockHeaders" } +func (*BlockHeadersPacket) Kind() byte { return BlockHeadersMsg } + +func (*GetBlockBodiesPacket) Name() string { return "GetBlockBodies" } +func (*GetBlockBodiesPacket) Kind() byte { return GetBlockBodiesMsg } + +func (*BlockBodiesPacket) Name() string { return "BlockBodies" } +func (*BlockBodiesPacket) Kind() byte { return BlockBodiesMsg } + +func (*NewBlockPacket) Name() string { return "NewBlock" } +func (*NewBlockPacket) Kind() byte { return NewBlockMsg } + +func (*GetNodeDataPacket) Name() string { return "GetNodeData" } +func (*GetNodeDataPacket) Kind() byte { return GetNodeDataMsg } + +func (*NodeDataPacket) Name() string { return "NodeData" } +func (*NodeDataPacket) Kind() byte { return NodeDataMsg } + +func (*GetReceiptsPacket) Name() string { return "GetReceipts" } +func (*GetReceiptsPacket) Kind() byte { return GetReceiptsMsg } + +func (*ReceiptsPacket) Name() string { return "Receipts" } +func (*ReceiptsPacket) Kind() byte { return ReceiptsMsg } + +func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" } +func (*NewPooledTransactionHashesPacket) Kind() byte { return NewPooledTransactionHashesMsg } + +func (*GetPooledTransactionsPacket) Name() string { return "GetPooledTransactions" } +func (*GetPooledTransactionsPacket) Kind() byte { return GetPooledTransactionsMsg } + +func (*PooledTransactionsPacket) Name() string { return "PooledTransactions" } +func (*PooledTransactionsPacket) Kind() byte { return PooledTransactionsMsg } diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go new file mode 100644 index 0000000000..056ea56480 --- /dev/null +++ b/eth/protocols/eth/protocol_test.go @@ -0,0 +1,68 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// Tests that the custom union field encoder and decoder works correctly. +func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { + // Create a "random" hash for testing + var hash common.Hash + for i := range hash { + hash[i] = byte(i) + } + // Assemble some table driven tests + tests := []struct { + packet *GetBlockHeadersPacket + fail bool + }{ + // Providing the origin as either a hash or a number should both work + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}}}, + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}}}, + + // Providing arbitrary query field should also work + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}}, + {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}}, + + // Providing both the origin hash and origin number must fail + {fail: true, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash, Number: 314}}}, + } + // Iterate over each of the tests and try to encode and then decode + for i, tt := range tests { + bytes, err := rlp.EncodeToBytes(tt.packet) + if err != nil && !tt.fail { + t.Fatalf("test %d: failed to encode packet: %v", i, err) + } else if err == nil && tt.fail { + t.Fatalf("test %d: encode should have failed", i) + } + if !tt.fail { + packet := new(GetBlockHeadersPacket) + if err := rlp.DecodeBytes(bytes, packet); err != nil { + t.Fatalf("test %d: failed to decode packet: %v", i, err) + } + if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount || + packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse { + t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet) + } + } + } +} diff --git a/eth/protocols/snap/discovery.go b/eth/protocols/snap/discovery.go new file mode 100644 index 0000000000..684ec7e632 --- /dev/null +++ b/eth/protocols/snap/discovery.go @@ -0,0 +1,32 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "github.com/ethereum/go-ethereum/rlp" +) + +// enrEntry is the ENR entry which advertises `snap` protocol on the discovery. +type enrEntry struct { + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +// ENRKey implements enr.Entry. +func (e enrEntry) ENRKey() string { + return "snap" +} diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go new file mode 100644 index 0000000000..36322e648b --- /dev/null +++ b/eth/protocols/snap/handler.go @@ -0,0 +1,490 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + // softResponseLimit is the target maximum size of replies to data retrievals. + softResponseLimit = 2 * 1024 * 1024 + + // maxCodeLookups is the maximum number of bytecodes to serve. This number is + // there to limit the number of disk lookups. + maxCodeLookups = 1024 + + // stateLookupSlack defines the ratio by how much a state response can exceed + // the requested limit in order to try and avoid breaking up contracts into + // multiple packages and proving them. + stateLookupSlack = 0.1 + + // maxTrieNodeLookups is the maximum number of state trie nodes to serve. This + // number is there to limit the number of disk lookups. + maxTrieNodeLookups = 1024 +) + +// Handler is a callback to invoke from an outside runner after the boilerplate +// exchanges have passed. +type Handler func(peer *Peer) error + +// Backend defines the data retrieval methods to serve remote requests and the +// callback methods to invoke on remote deliveries. +type Backend interface { + // Chain retrieves the blockchain object to serve data. + Chain() *core.BlockChain + + // RunPeer is invoked when a peer joins on the `eth` protocol. The handler + // should do any peer maintenance work, handshakes and validations. If all + // is passed, control should be given back to the `handler` to process the + // inbound messages going forward. + RunPeer(peer *Peer, handler Handler) error + + // PeerInfo retrieves all known `snap` information about a peer. + PeerInfo(id enode.ID) interface{} + + // Handle is a callback to be invoked when a data packet is received from + // the remote peer. Only packets not consumed by the protocol handler will + // be forwarded to the backend. + Handle(peer *Peer, packet Packet) error +} + +// MakeProtocols constructs the P2P protocol definitions for `snap`. +func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { + protocols := make([]p2p.Protocol, len(protocolVersions)) + for i, version := range protocolVersions { + version := version // Closure + + protocols[i] = p2p.Protocol{ + Name: protocolName, + Version: version, + Length: protocolLengths[version], + Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { + return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error { + return handle(backend, peer) + }) + }, + NodeInfo: func() interface{} { + return nodeInfo(backend.Chain()) + }, + PeerInfo: func(id enode.ID) interface{} { + return backend.PeerInfo(id) + }, + Attributes: []enr.Entry{&enrEntry{}}, + DialCandidates: dnsdisc, + } + } + return protocols +} + +// handle is the callback invoked to manage the life cycle of a `snap` peer. +// When this function terminates, the peer is disconnected. +func handle(backend Backend, peer *Peer) error { + for { + if err := handleMessage(backend, peer); err != nil { + peer.Log().Debug("Message handling failed in `snap`", "err", err) + return err + } + } +} + +// handleMessage is invoked whenever an inbound message is received from a +// remote peer on the `spap` protocol. The remote connection is torn down upon +// returning any error. +func handleMessage(backend Backend, peer *Peer) error { + // Read the next message from the remote peer, and ensure it's fully consumed + msg, err := peer.rw.ReadMsg() + if err != nil { + return err + } + if msg.Size > maxMessageSize { + return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) + } + defer msg.Discard() + + // Handle the message depending on its contents + switch { + case msg.Code == GetAccountRangeMsg: + // Decode the account retrieval request + var req GetAccountRangePacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // Retrieve the requested state and bail out if non existent + tr, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB()) + if err != nil { + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + it, err := backend.Chain().Snapshots().AccountIterator(req.Root, req.Origin) + if err != nil { + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + // Iterate over the requested range and pile accounts up + var ( + accounts []*AccountData + size uint64 + last common.Hash + ) + for it.Next() && size < req.Bytes { + hash, account := it.Hash(), common.CopyBytes(it.Account()) + + // Track the returned interval for the Merkle proofs + last = hash + + // Assemble the reply item + size += uint64(common.HashLength + len(account)) + accounts = append(accounts, &AccountData{ + Hash: hash, + Body: account, + }) + // If we've exceeded the request threshold, abort + if bytes.Compare(hash[:], req.Limit[:]) >= 0 { + break + } + } + it.Release() + + // Generate the Merkle proofs for the first and last account + proof := light.NewNodeSet() + if err := tr.Prove(req.Origin[:], 0, proof); err != nil { + log.Warn("Failed to prove account range", "origin", req.Origin, "err", err) + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + if last != (common.Hash{}) { + if err := tr.Prove(last[:], 0, proof); err != nil { + log.Warn("Failed to prove account range", "last", last, "err", err) + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID}) + } + } + var proofs [][]byte + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + // Send back anything accumulated + return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ + ID: req.ID, + Accounts: accounts, + Proof: proofs, + }) + + case msg.Code == AccountRangeMsg: + // A range of accounts arrived to one of our previous requests + res := new(AccountRangePacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Ensure the range is monotonically increasing + for i := 1; i < len(res.Accounts); i++ { + if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 { + return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) + } + } + return backend.Handle(peer, res) + + case msg.Code == GetStorageRangesMsg: + // Decode the storage retrieval request + var req GetStorageRangesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // TODO(karalabe): Do we want to enforce > 0 accounts and 1 account if origin is set? + // TODO(karalabe): - Logging locally is not ideal as remote faulst annoy the local user + // TODO(karalabe): - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional) + + // Calculate the hard limit at which to abort, even if mid storage trie + hardLimit := uint64(float64(req.Bytes) * (1 + stateLookupSlack)) + + // Retrieve storage ranges until the packet limit is reached + var ( + slots [][]*StorageData + proofs [][]byte + size uint64 + ) + for _, account := range req.Accounts { + // If we've exceeded the requested data limit, abort without opening + // a new storage range (that we'd need to prove due to exceeded size) + if size >= req.Bytes { + break + } + // The first account might start from a different origin and end sooner + var origin common.Hash + if len(req.Origin) > 0 { + origin, req.Origin = common.BytesToHash(req.Origin), nil + } + var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + if len(req.Limit) > 0 { + limit, req.Limit = common.BytesToHash(req.Limit), nil + } + // Retrieve the requested state and bail out if non existent + it, err := backend.Chain().Snapshots().StorageIterator(req.Root, account, origin) + if err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + // Iterate over the requested range and pile slots up + var ( + storage []*StorageData + last common.Hash + ) + for it.Next() && size < hardLimit { + hash, slot := it.Hash(), common.CopyBytes(it.Slot()) + + // Track the returned interval for the Merkle proofs + last = hash + + // Assemble the reply item + size += uint64(common.HashLength + len(slot)) + storage = append(storage, &StorageData{ + Hash: hash, + Body: slot, + }) + // If we've exceeded the request threshold, abort + if bytes.Compare(hash[:], limit[:]) >= 0 { + break + } + } + slots = append(slots, storage) + it.Release() + + // Generate the Merkle proofs for the first and last storage slot, but + // only if the response was capped. If the entire storage trie included + // in the response, no need for any proofs. + if origin != (common.Hash{}) || size >= hardLimit { + // Request started at a non-zero hash or was capped prematurely, add + // the endpoint Merkle proofs + accTrie, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB()) + if err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + var acc state.Account + if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + stTrie, err := trie.New(acc.Root, backend.Chain().StateCache().TrieDB()) + if err != nil { + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + proof := light.NewNodeSet() + if err := stTrie.Prove(origin[:], 0, proof); err != nil { + log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err) + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + if last != (common.Hash{}) { + if err := stTrie.Prove(last[:], 0, proof); err != nil { + log.Warn("Failed to prove storage range", "last", last, "err", err) + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID}) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + // Proof terminates the reply as proofs are only added if a node + // refuses to serve more data (exception when a contract fetch is + // finishing, but that's that). + break + } + } + // Send back anything accumulated + return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ + ID: req.ID, + Slots: slots, + Proof: proofs, + }) + + case msg.Code == StorageRangesMsg: + // A range of storage slots arrived to one of our previous requests + res := new(StorageRangesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Ensure the ranges ae monotonically increasing + for i, slots := range res.Slots { + for j := 1; j < len(slots); j++ { + if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 { + return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:]) + } + } + } + return backend.Handle(peer, res) + + case msg.Code == GetByteCodesMsg: + // Decode bytecode retrieval request + var req GetByteCodesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + if len(req.Hashes) > maxCodeLookups { + req.Hashes = req.Hashes[:maxCodeLookups] + } + // Retrieve bytecodes until the packet size limit is reached + var ( + codes [][]byte + bytes uint64 + ) + for _, hash := range req.Hashes { + if hash == emptyCode { + // Peers should not request the empty code, but if they do, at + // least sent them back a correct response without db lookups + codes = append(codes, []byte{}) + } else if blob, err := backend.Chain().ContractCode(hash); err == nil { + codes = append(codes, blob) + bytes += uint64(len(blob)) + } + if bytes > req.Bytes { + break + } + } + // Send back anything accumulated + return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{ + ID: req.ID, + Codes: codes, + }) + + case msg.Code == ByteCodesMsg: + // A batch of byte codes arrived to one of our previous requests + res := new(ByteCodesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + case msg.Code == GetTrieNodesMsg: + // Decode trie node retrieval request + var req GetTrieNodesPacket + if err := msg.Decode(&req); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if req.Bytes > softResponseLimit { + req.Bytes = softResponseLimit + } + // Make sure we have the state associated with the request + triedb := backend.Chain().StateCache().TrieDB() + + accTrie, err := trie.NewSecure(req.Root, triedb) + if err != nil { + // We don't have the requested state available, bail out + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID}) + } + snap := backend.Chain().Snapshots().Snapshot(req.Root) + if snap == nil { + // We don't have the requested state snapshotted yet, bail out. + // In reality we could still serve using the account and storage + // tries only, but let's protect the node a bit while it's doing + // snapshot generation. + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID}) + } + // Retrieve trie nodes until the packet size limit is reached + var ( + nodes [][]byte + bytes uint64 + loads int // Trie hash expansions to cound database reads + ) + for _, pathset := range req.Paths { + switch len(pathset) { + case 0: + // Ensure we penalize invalid requests + return fmt.Errorf("%w: zero-item pathset requested", errBadRequest) + + case 1: + // If we're only retrieving an account trie node, fetch it directly + blob, resolved, err := accTrie.TryGetNode(pathset[0]) + loads += resolved // always account database reads, even for failures + if err != nil { + break + } + nodes = append(nodes, blob) + bytes += uint64(len(blob)) + + default: + // Storage slots requested, open the storage trie and retrieve from there + account, err := snap.Account(common.BytesToHash(pathset[0])) + loads++ // always account database reads, even for failures + if err != nil { + break + } + stTrie, err := trie.NewSecure(common.BytesToHash(account.Root), triedb) + loads++ // always account database reads, even for failures + if err != nil { + break + } + for _, path := range pathset[1:] { + blob, resolved, err := stTrie.TryGetNode(path) + loads += resolved // always account database reads, even for failures + if err != nil { + break + } + nodes = append(nodes, blob) + bytes += uint64(len(blob)) + + // Sanity check limits to avoid DoS on the store trie loads + if bytes > req.Bytes || loads > maxTrieNodeLookups { + break + } + } + } + // Abort request processing if we've exceeded our limits + if bytes > req.Bytes || loads > maxTrieNodeLookups { + break + } + } + // Send back anything accumulated + return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ + ID: req.ID, + Nodes: nodes, + }) + + case msg.Code == TrieNodesMsg: + // A batch of trie nodes arrived to one of our previous requests + res := new(TrieNodesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) + + default: + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + } +} + +// NodeInfo represents a short summary of the `snap` sub-protocol metadata +// known about the host peer. +type NodeInfo struct{} + +// nodeInfo retrieves some `snap` protocol metadata about the running host node. +func nodeInfo(chain *core.BlockChain) *NodeInfo { + return &NodeInfo{} +} diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go new file mode 100644 index 0000000000..73eaaadd09 --- /dev/null +++ b/eth/protocols/snap/peer.go @@ -0,0 +1,111 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p" +) + +// Peer is a collection of relevant information we have about a `snap` peer. +type Peer struct { + id string // Unique ID for the peer, cached + + *p2p.Peer // The embedded P2P package peer + rw p2p.MsgReadWriter // Input/output streams for snap + version uint // Protocol version negotiated + + logger log.Logger // Contextual logger with the peer id injected +} + +// newPeer create a wrapper for a network connection and negotiated protocol +// version. +func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer { + id := p.ID().String() + return &Peer{ + id: id, + Peer: p, + rw: rw, + version: version, + logger: log.New("peer", id[:8]), + } +} + +// ID retrieves the peer's unique identifier. +func (p *Peer) ID() string { + return p.id +} + +// Version retrieves the peer's negoatiated `snap` protocol version. +func (p *Peer) Version() uint { + return p.version +} + +// RequestAccountRange fetches a batch of accounts rooted in a specific account +// trie, starting with the origin. +func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error { + p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{ + ID: id, + Root: root, + Origin: origin, + Limit: limit, + Bytes: bytes, + }) +} + +// RequestStorageRange fetches a batch of storage slots belonging to one or more +// accounts. If slots from only one accout is requested, an origin marker may also +// be used to retrieve from there. +func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + if len(accounts) == 1 && origin != nil { + p.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) + } else { + p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) + } + return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{ + ID: id, + Root: root, + Accounts: accounts, + Origin: origin, + Limit: limit, + Bytes: bytes, + }) +} + +// RequestByteCodes fetches a batch of bytecodes by hash. +func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{ + ID: id, + Hashes: hashes, + Bytes: bytes, + }) +} + +// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in +// a specificstate trie. +func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { + p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{ + ID: id, + Root: root, + Paths: paths, + Bytes: bytes, + }) +} diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go new file mode 100644 index 0000000000..a1e4349691 --- /dev/null +++ b/eth/protocols/snap/protocol.go @@ -0,0 +1,218 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/rlp" +) + +// Constants to match up protocol versions and messages +const ( + snap1 = 1 +) + +// protocolName is the official short name of the `snap` protocol used during +// devp2p capability negotiation. +const protocolName = "snap" + +// protocolVersions are the supported versions of the `snap` protocol (first +// is primary). +var protocolVersions = []uint{snap1} + +// protocolLengths are the number of implemented message corresponding to +// different protocol versions. +var protocolLengths = map[uint]uint64{snap1: 8} + +// maxMessageSize is the maximum cap on the size of a protocol message. +const maxMessageSize = 10 * 1024 * 1024 + +const ( + GetAccountRangeMsg = 0x00 + AccountRangeMsg = 0x01 + GetStorageRangesMsg = 0x02 + StorageRangesMsg = 0x03 + GetByteCodesMsg = 0x04 + ByteCodesMsg = 0x05 + GetTrieNodesMsg = 0x06 + TrieNodesMsg = 0x07 +) + +var ( + errMsgTooLarge = errors.New("message too long") + errDecode = errors.New("invalid message") + errInvalidMsgCode = errors.New("invalid message code") + errBadRequest = errors.New("bad request") +) + +// Packet represents a p2p message in the `snap` protocol. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. +} + +// GetAccountRangePacket represents an account query. +type GetAccountRangePacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Origin common.Hash // Hash of the first account to retrieve + Limit common.Hash // Hash of the last account to retrieve + Bytes uint64 // Soft limit at which to stop returning data +} + +// AccountRangePacket represents an account query response. +type AccountRangePacket struct { + ID uint64 // ID of the request this is a response for + Accounts []*AccountData // List of consecutive accounts from the trie + Proof [][]byte // List of trie nodes proving the account range +} + +// AccountData represents a single account in a query response. +type AccountData struct { + Hash common.Hash // Hash of the account + Body rlp.RawValue // Account body in slim format +} + +// Unpack retrieves the accounts from the range packet and converts from slim +// wire representation to consensus format. The returned data is RLP encoded +// since it's expected to be serialized to disk without further interpretation. +// +// Note, this method does a round of RLP decoding and reencoding, so only use it +// once and cache the results if need be. Ideally discard the packet afterwards +// to not double the memory use. +func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) { + var ( + hashes = make([]common.Hash, len(p.Accounts)) + accounts = make([][]byte, len(p.Accounts)) + ) + for i, acc := range p.Accounts { + val, err := snapshot.FullAccountRLP(acc.Body) + if err != nil { + return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err) + } + hashes[i], accounts[i] = acc.Hash, val + } + return hashes, accounts, nil +} + +// GetStorageRangesPacket represents an storage slot query. +type GetStorageRangesPacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Accounts []common.Hash // Account hashes of the storage tries to serve + Origin []byte // Hash of the first storage slot to retrieve (large contract mode) + Limit []byte // Hash of the last storage slot to retrieve (large contract mode) + Bytes uint64 // Soft limit at which to stop returning data +} + +// StorageRangesPacket represents a storage slot query response. +type StorageRangesPacket struct { + ID uint64 // ID of the request this is a response for + Slots [][]*StorageData // Lists of consecutive storage slots for the requested accounts + Proof [][]byte // Merkle proofs for the *last* slot range, if it's incomplete +} + +// StorageData represents a single storage slot in a query response. +type StorageData struct { + Hash common.Hash // Hash of the storage slot + Body []byte // Data content of the slot +} + +// Unpack retrieves the storage slots from the range packet and returns them in +// a split flat format that's more consistent with the internal data structures. +func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) { + var ( + hashset = make([][]common.Hash, len(p.Slots)) + slotset = make([][][]byte, len(p.Slots)) + ) + for i, slots := range p.Slots { + hashset[i] = make([]common.Hash, len(slots)) + slotset[i] = make([][]byte, len(slots)) + for j, slot := range slots { + hashset[i][j] = slot.Hash + slotset[i][j] = slot.Body + } + } + return hashset, slotset +} + +// GetByteCodesPacket represents a contract bytecode query. +type GetByteCodesPacket struct { + ID uint64 // Request ID to match up responses with + Hashes []common.Hash // Code hashes to retrieve the code for + Bytes uint64 // Soft limit at which to stop returning data +} + +// ByteCodesPacket represents a contract bytecode query response. +type ByteCodesPacket struct { + ID uint64 // ID of the request this is a response for + Codes [][]byte // Requested contract bytecodes +} + +// GetTrieNodesPacket represents a state trie node query. +type GetTrieNodesPacket struct { + ID uint64 // Request ID to match up responses with + Root common.Hash // Root hash of the account trie to serve + Paths []TrieNodePathSet // Trie node hashes to retrieve the nodes for + Bytes uint64 // Soft limit at which to stop returning data +} + +// TrieNodePathSet is a list of trie node paths to retrieve. A naive way to +// represent trie nodes would be a simple list of `account || storage` path +// segments concatenated, but that would be very wasteful on the network. +// +// Instead, this array special cases the first element as the path in the +// account trie and the remaining elements as paths in the storage trie. To +// address an account node, the slice should have a length of 1 consisting +// of only the account path. There's no need to be able to address both an +// account node and a storage node in the same request as it cannot happen +// that a slot is accessed before the account path is fully expanded. +type TrieNodePathSet [][]byte + +// TrieNodesPacket represents a state trie node query response. +type TrieNodesPacket struct { + ID uint64 // ID of the request this is a response for + Nodes [][]byte // Requested state trie nodes +} + +func (*GetAccountRangePacket) Name() string { return "GetAccountRange" } +func (*GetAccountRangePacket) Kind() byte { return GetAccountRangeMsg } + +func (*AccountRangePacket) Name() string { return "AccountRange" } +func (*AccountRangePacket) Kind() byte { return AccountRangeMsg } + +func (*GetStorageRangesPacket) Name() string { return "GetStorageRanges" } +func (*GetStorageRangesPacket) Kind() byte { return GetStorageRangesMsg } + +func (*StorageRangesPacket) Name() string { return "StorageRanges" } +func (*StorageRangesPacket) Kind() byte { return StorageRangesMsg } + +func (*GetByteCodesPacket) Name() string { return "GetByteCodes" } +func (*GetByteCodesPacket) Kind() byte { return GetByteCodesMsg } + +func (*ByteCodesPacket) Name() string { return "ByteCodes" } +func (*ByteCodesPacket) Kind() byte { return ByteCodesMsg } + +func (*GetTrieNodesPacket) Name() string { return "GetTrieNodes" } +func (*GetTrieNodesPacket) Kind() byte { return GetTrieNodesMsg } + +func (*TrieNodesPacket) Name() string { return "TrieNodes" } +func (*TrieNodesPacket) Kind() byte { return TrieNodesMsg } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go new file mode 100644 index 0000000000..679b328283 --- /dev/null +++ b/eth/protocols/snap/sync.go @@ -0,0 +1,2481 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math/big" + "math/rand" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256Hash(nil) +) + +const ( + // maxRequestSize is the maximum number of bytes to request from a remote peer. + maxRequestSize = 512 * 1024 + + // maxStorageSetRequestCountis th maximum number of contracts to request the + // storage of in a single query. If this number is too low, we're not filling + // responses fully and waste round trip times. If it's too high, we're capping + // responses and waste bandwidth. + maxStorageSetRequestCount = maxRequestSize / 1024 + + // maxCodeRequestCount is the maximum number of bytecode blobs to request in a + // single query. If this number is too low, we're not filling responses fully + // and waste round trip times. If it's too high, we're capping responses and + // waste bandwidth. + // + // Depoyed bytecodes are currently capped at 24KB, so the minimum request + // size should be maxRequestSize / 24K. Assuming that most contracts do not + // come close to that, requesting 4x should be a good approximation. + maxCodeRequestCount = maxRequestSize / (24 * 1024) * 4 + + // maxTrieRequestCount is the maximum number of trie node blobs to request in + // a single query. If this number is too low, we're not filling responses fully + // and waste round trip times. If it's too high, we're capping responses and + // waste bandwidth. + maxTrieRequestCount = 512 + + // requestTimeout is the maximum time a peer is allowed to spend on serving + // a single network request. + requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? + + // accountConcurrency is the number of chunks to split the account trie into + // to allow concurrent retrievals. + accountConcurrency = 16 + + // storageConcurrency is the number of chunks to split the a large contract + // storage trie into to allow concurrent retrievals. + storageConcurrency = 16 +) + +// accountRequest tracks a pending account range request to ensure responses are +// to actual requests and to validate any security constraints. +// +// Concurrency note: account requests and responses are handled concurrently from +// the main runloop to allow Merkle proof verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type accountRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + origin common.Hash // First account requested to allow continuation checks + limit common.Hash // Last account requested to allow non-overlapping chunking + + task *accountTask // Task which this request is filling (only access fields through the runloop!!) +} + +// accountResponse is an already Merkle-verified remote response to an account +// range request. It contains the subtrie for the requested account range and +// the database that's going to be filled with the internal nodes on commit. +type accountResponse struct { + task *accountTask // Task which this request is filling + + hashes []common.Hash // Account hashes in the returned range + accounts []*state.Account // Expanded accounts in the returned range + + nodes ethdb.KeyValueStore // Database containing the reconstructed trie nodes + trie *trie.Trie // Reconstructed trie to reject incomplete account paths + + bounds map[common.Hash]struct{} // Boundary nodes to avoid persisting incomplete accounts + overflow *light.NodeSet // Overflow nodes to avoid persisting across chunk boundaries + + cont bool // Whether the account range has a continuation +} + +// bytecodeRequest tracks a pending bytecode request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: bytecode requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type bytecodeRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Bytecode hashes to validate responses + task *accountTask // Task which this request is filling (only access fields through the runloop!!) +} + +// bytecodeResponse is an already verified remote response to a bytecode request. +type bytecodeResponse struct { + task *accountTask // Task which this request is filling + + hashes []common.Hash // Hashes of the bytecode to avoid double hashing + codes [][]byte // Actual bytecodes to store into the database (nil = missing) +} + +// storageRequest tracks a pending storage ranges request to ensure responses are +// to actual requests and to validate any security constraints. +// +// Concurrency note: storage requests and responses are handled concurrently from +// the main runloop to allow Merkel proof verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. tasks). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type storageRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + accounts []common.Hash // Account hashes to validate responses + roots []common.Hash // Storage roots to validate responses + + origin common.Hash // First storage slot requested to allow continuation checks + limit common.Hash // Last storage slot requested to allow non-overlapping chunking + + mainTask *accountTask // Task which this response belongs to (only access fields through the runloop!!) + subTask *storageTask // Task which this response is filling (only access fields through the runloop!!) +} + +// storageResponse is an already Merkle-verified remote response to a storage +// range request. It contains the subtries for the requested storage ranges and +// the databases that's going to be filled with the internal nodes on commit. +type storageResponse struct { + mainTask *accountTask // Task which this response belongs to + subTask *storageTask // Task which this response is filling + + accounts []common.Hash // Account hashes requested, may be only partially filled + roots []common.Hash // Storage roots requested, may be only partially filled + + hashes [][]common.Hash // Storage slot hashes in the returned range + slots [][][]byte // Storage slot values in the returned range + nodes []ethdb.KeyValueStore // Database containing the reconstructed trie nodes + tries []*trie.Trie // Reconstructed tries to reject overflown slots + + // Fields relevant for the last account only + bounds map[common.Hash]struct{} // Boundary nodes to avoid persisting (incomplete) + overflow *light.NodeSet // Overflow nodes to avoid persisting across chunk boundaries + cont bool // Whether the last storage range has a continuation +} + +// trienodeHealRequest tracks a pending state trie request to ensure responses +// are to actual requests and to validate any security constraints. +// +// Concurrency note: trie node requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type trienodeHealRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Trie node hashes to validate responses + paths []trie.SyncPath // Trie node paths requested for rescheduling + + task *healTask // Task which this request is filling (only access fields through the runloop!!) +} + +// trienodeHealResponse is an already verified remote response to a trie node request. +type trienodeHealResponse struct { + task *healTask // Task which this request is filling + + hashes []common.Hash // Hashes of the trie nodes to avoid double hashing + paths []trie.SyncPath // Trie node paths requested for rescheduling missing ones + nodes [][]byte // Actual trie nodes to store into the database (nil = missing) +} + +// bytecodeHealRequest tracks a pending bytecode request to ensure responses are to +// actual requests and to validate any security constraints. +// +// Concurrency note: bytecode requests and responses are handled concurrently from +// the main runloop to allow Keccak256 hash verifications on the peer's thread and +// to drop on invalid response. The request struct must contain all the data to +// construct the response without accessing runloop internals (i.e. task). That +// is only included to allow the runloop to match a response to the task being +// synced without having yet another set of maps. +type bytecodeHealRequest struct { + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped + + hashes []common.Hash // Bytecode hashes to validate responses + task *healTask // Task which this request is filling (only access fields through the runloop!!) +} + +// bytecodeHealResponse is an already verified remote response to a bytecode request. +type bytecodeHealResponse struct { + task *healTask // Task which this request is filling + + hashes []common.Hash // Hashes of the bytecode to avoid double hashing + codes [][]byte // Actual bytecodes to store into the database (nil = missing) +} + +// accountTask represents the sync task for a chunk of the account snapshot. +type accountTask struct { + // These fields get serialized to leveldb on shutdown + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts + + // These fields are internals used during runtime + req *accountRequest // Pending request to fill this task + res *accountResponse // Validate response filling this task + pend int // Number of pending subtasks for this round + + needCode []bool // Flags whether the filling accounts need code retrieval + needState []bool // Flags whether the filling accounts need storage retrieval + needHeal []bool // Flags whether the filling accounts's state was chunked and need healing + + codeTasks map[common.Hash]struct{} // Code hashes that need retrieval + stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval + + done bool // Flag whether the task can be removed +} + +// storageTask represents the sync task for a chunk of the storage snapshot. +type storageTask struct { + Next common.Hash // Next account to sync in this interval + Last common.Hash // Last account to sync in this interval + + // These fields are internals used during runtime + root common.Hash // Storage root hash for this instance + req *storageRequest // Pending request to fill this task + done bool // Flag whether the task can be removed +} + +// healTask represents the sync task for healing the snap-synced chunk boundaries. +type healTask struct { + scheduler *trie.Sync // State trie sync scheduler defining the tasks + + trieTasks map[common.Hash]trie.SyncPath // Set of trie node tasks currently queued for retrieval + codeTasks map[common.Hash]struct{} // Set of byte code tasks currently queued for retrieval +} + +// syncProgress is a database entry to allow suspending and resuming a snapshot state +// sync. Opposed to full and fast sync, there is no way to restart a suspended +// snap sync without prior knowledge of the suspension point. +type syncProgress struct { + Tasks []*accountTask // The suspended account tasks (contract tasks within) + + // Status report during syncing phase + AccountSynced uint64 // Number of accounts downloaded + AccountBytes common.StorageSize // Number of account trie bytes persisted to disk + BytecodeSynced uint64 // Number of bytecodes downloaded + BytecodeBytes common.StorageSize // Number of bytecode bytes downloaded + StorageSynced uint64 // Number of storage slots downloaded + StorageBytes common.StorageSize // Number of storage trie bytes persisted to disk + + // Status report during healing phase + TrienodeHealSynced uint64 // Number of state trie nodes downloaded + TrienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk + TrienodeHealDups uint64 // Number of state trie nodes already processed + TrienodeHealNops uint64 // Number of state trie nodes not requested + BytecodeHealSynced uint64 // Number of bytecodes downloaded + BytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk + BytecodeHealDups uint64 // Number of bytecodes already processed + BytecodeHealNops uint64 // Number of bytecodes not requested +} + +// Syncer is an Ethereum account and storage trie syncer based on snapshots and +// the snap protocol. It's purpose is to download all the accounts and storage +// slots from remote peers and reassemble chunks of the state trie, on top of +// which a state sync can be run to fix any gaps / overlaps. +// +// Every network request has a variety of failure events: +// - The peer disconnects after task assignment, failing to send the request +// - The peer disconnects after sending the request, before delivering on it +// - The peer remains connected, but does not deliver a response in time +// - The peer delivers a stale response after a previous timeout +// - The peer delivers a refusal to serve the requested state +type Syncer struct { + db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) + bloom *trie.SyncBloom // Bloom filter to deduplicate nodes for state fixup + + root common.Hash // Current state trie root being synced + tasks []*accountTask // Current account task set being synced + healer *healTask // Current state healing task being executed + update chan struct{} // Notification channel for possible sync progression + + peers map[string]*Peer // Currently active peers to download from + peerJoin *event.Feed // Event feed to react to peers joining + peerDrop *event.Feed // Event feed to react to peers dropping + + // Request tracking during syncing phase + statelessPeers map[string]struct{} // Peers that failed to deliver state data + accountIdlers map[string]struct{} // Peers that aren't serving account requests + bytecodeIdlers map[string]struct{} // Peers that aren't serving bytecode requests + storageIdlers map[string]struct{} // Peers that aren't serving storage requests + + accountReqs map[uint64]*accountRequest // Account requests currently running + bytecodeReqs map[uint64]*bytecodeRequest // Bytecode requests currently running + storageReqs map[uint64]*storageRequest // Storage requests currently running + + accountReqFails chan *accountRequest // Failed account range requests to revert + bytecodeReqFails chan *bytecodeRequest // Failed bytecode requests to revert + storageReqFails chan *storageRequest // Failed storage requests to revert + + accountResps chan *accountResponse // Account sub-tries to integrate into the database + bytecodeResps chan *bytecodeResponse // Bytecodes to integrate into the database + storageResps chan *storageResponse // Storage sub-tries to integrate into the database + + accountSynced uint64 // Number of accounts downloaded + accountBytes common.StorageSize // Number of account trie bytes persisted to disk + bytecodeSynced uint64 // Number of bytecodes downloaded + bytecodeBytes common.StorageSize // Number of bytecode bytes downloaded + storageSynced uint64 // Number of storage slots downloaded + storageBytes common.StorageSize // Number of storage trie bytes persisted to disk + + // Request tracking during healing phase + trienodeHealIdlers map[string]struct{} // Peers that aren't serving trie node requests + bytecodeHealIdlers map[string]struct{} // Peers that aren't serving bytecode requests + + trienodeHealReqs map[uint64]*trienodeHealRequest // Trie node requests currently running + bytecodeHealReqs map[uint64]*bytecodeHealRequest // Bytecode requests currently running + + trienodeHealReqFails chan *trienodeHealRequest // Failed trienode requests to revert + bytecodeHealReqFails chan *bytecodeHealRequest // Failed bytecode requests to revert + + trienodeHealResps chan *trienodeHealResponse // Trie nodes to integrate into the database + bytecodeHealResps chan *bytecodeHealResponse // Bytecodes to integrate into the database + + trienodeHealSynced uint64 // Number of state trie nodes downloaded + trienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk + trienodeHealDups uint64 // Number of state trie nodes already processed + trienodeHealNops uint64 // Number of state trie nodes not requested + bytecodeHealSynced uint64 // Number of bytecodes downloaded + bytecodeHealBytes common.StorageSize // Number of bytecodes persisted to disk + bytecodeHealDups uint64 // Number of bytecodes already processed + bytecodeHealNops uint64 // Number of bytecodes not requested + + startTime time.Time // Time instance when snapshot sync started + startAcc common.Hash // Account hash where sync started from + logTime time.Time // Time instance when status was last reported + + pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown + lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) +} + +func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { + return &Syncer{ + db: db, + bloom: bloom, + + peers: make(map[string]*Peer), + peerJoin: new(event.Feed), + peerDrop: new(event.Feed), + update: make(chan struct{}, 1), + + accountIdlers: make(map[string]struct{}), + storageIdlers: make(map[string]struct{}), + bytecodeIdlers: make(map[string]struct{}), + + accountReqs: make(map[uint64]*accountRequest), + storageReqs: make(map[uint64]*storageRequest), + bytecodeReqs: make(map[uint64]*bytecodeRequest), + accountReqFails: make(chan *accountRequest), + storageReqFails: make(chan *storageRequest), + bytecodeReqFails: make(chan *bytecodeRequest), + accountResps: make(chan *accountResponse), + storageResps: make(chan *storageResponse), + bytecodeResps: make(chan *bytecodeResponse), + + trienodeHealIdlers: make(map[string]struct{}), + bytecodeHealIdlers: make(map[string]struct{}), + + trienodeHealReqs: make(map[uint64]*trienodeHealRequest), + bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest), + trienodeHealReqFails: make(chan *trienodeHealRequest), + bytecodeHealReqFails: make(chan *bytecodeHealRequest), + trienodeHealResps: make(chan *trienodeHealResponse), + bytecodeHealResps: make(chan *bytecodeHealResponse), + } +} + +// Register injects a new data source into the syncer's peerset. +func (s *Syncer) Register(peer *Peer) error { + // Make sure the peer is not registered yet + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + log.Error("Snap peer already registered", "id", peer.id) + + s.lock.Unlock() + return errors.New("already registered") + } + s.peers[peer.id] = peer + + // Mark the peer as idle, even if no sync is running + s.accountIdlers[peer.id] = struct{}{} + s.storageIdlers[peer.id] = struct{}{} + s.bytecodeIdlers[peer.id] = struct{}{} + s.trienodeHealIdlers[peer.id] = struct{}{} + s.bytecodeHealIdlers[peer.id] = struct{}{} + s.lock.Unlock() + + // Notify any active syncs that a new peer can be assigned data + s.peerJoin.Send(peer.id) + return nil +} + +// Unregister injects a new data source into the syncer's peerset. +func (s *Syncer) Unregister(id string) error { + // Remove all traces of the peer from the registry + s.lock.Lock() + if _, ok := s.peers[id]; !ok { + log.Error("Snap peer not registered", "id", id) + + s.lock.Unlock() + return errors.New("not registered") + } + delete(s.peers, id) + + // Remove status markers, even if no sync is running + delete(s.statelessPeers, id) + + delete(s.accountIdlers, id) + delete(s.storageIdlers, id) + delete(s.bytecodeIdlers, id) + delete(s.trienodeHealIdlers, id) + delete(s.bytecodeHealIdlers, id) + s.lock.Unlock() + + // Notify any active syncs that pending requests need to be reverted + s.peerDrop.Send(id) + return nil +} + +// Sync starts (or resumes a previous) sync cycle to iterate over an state trie +// with the given root and reconstruct the nodes based on the snapshot leaves. +// Previously downloaded segments will not be redownloaded of fixed, rather any +// errors will be healed after the leaves are fully accumulated. +func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { + // Move the trie root from any previous value, revert stateless markers for + // any peers and initialize the syncer if it was not yet run + s.lock.Lock() + s.root = root + s.healer = &healTask{ + scheduler: state.NewStateSync(root, s.db, s.bloom), + trieTasks: make(map[common.Hash]trie.SyncPath), + codeTasks: make(map[common.Hash]struct{}), + } + s.statelessPeers = make(map[string]struct{}) + s.lock.Unlock() + + if s.startTime == (time.Time{}) { + s.startTime = time.Now() + } + // Retrieve the previous sync status from LevelDB and abort if already synced + s.loadSyncStatus() + if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + log.Debug("Snapshot sync already completed") + return nil + } + defer func() { // Persist any progress, independent of failure + for _, task := range s.tasks { + s.forwardAccountTask(task) + } + s.cleanAccountTasks() + s.saveSyncStatus() + }() + + log.Debug("Starting snapshot sync cycle", "root", root) + defer s.report(true) + + // Whether sync completed or not, disregard any future packets + defer func() { + log.Debug("Terminating snapshot sync cycle", "root", root) + s.lock.Lock() + s.accountReqs = make(map[uint64]*accountRequest) + s.storageReqs = make(map[uint64]*storageRequest) + s.bytecodeReqs = make(map[uint64]*bytecodeRequest) + s.trienodeHealReqs = make(map[uint64]*trienodeHealRequest) + s.bytecodeHealReqs = make(map[uint64]*bytecodeHealRequest) + s.lock.Unlock() + }() + // Keep scheduling sync tasks + peerJoin := make(chan string, 16) + peerJoinSub := s.peerJoin.Subscribe(peerJoin) + defer peerJoinSub.Unsubscribe() + + peerDrop := make(chan string, 16) + peerDropSub := s.peerDrop.Subscribe(peerDrop) + defer peerDropSub.Unsubscribe() + + for { + // Remove all completed tasks and terminate sync if everything's done + s.cleanStorageTasks() + s.cleanAccountTasks() + if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 { + return nil + } + // Assign all the data retrieval tasks to any free peers + s.assignAccountTasks(cancel) + s.assignBytecodeTasks(cancel) + s.assignStorageTasks(cancel) + if len(s.tasks) == 0 { + // Sync phase done, run heal phase + s.assignTrienodeHealTasks(cancel) + s.assignBytecodeHealTasks(cancel) + } + // Wait for something to happen + select { + case <-s.update: + // Something happened (new peer, delivery, timeout), recheck tasks + case <-peerJoin: + // A new peer joined, try to schedule it new tasks + case id := <-peerDrop: + s.revertRequests(id) + case <-cancel: + return nil + + case req := <-s.accountReqFails: + s.revertAccountRequest(req) + case req := <-s.bytecodeReqFails: + s.revertBytecodeRequest(req) + case req := <-s.storageReqFails: + s.revertStorageRequest(req) + case req := <-s.trienodeHealReqFails: + s.revertTrienodeHealRequest(req) + case req := <-s.bytecodeHealReqFails: + s.revertBytecodeHealRequest(req) + + case res := <-s.accountResps: + s.processAccountResponse(res) + case res := <-s.bytecodeResps: + s.processBytecodeResponse(res) + case res := <-s.storageResps: + s.processStorageResponse(res) + case res := <-s.trienodeHealResps: + s.processTrienodeHealResponse(res) + case res := <-s.bytecodeHealResps: + s.processBytecodeHealResponse(res) + } + // Report stats if something meaningful happened + s.report(false) + } +} + +// loadSyncStatus retrieves a previously aborted sync status from the database, +// or generates a fresh one if none is available. +func (s *Syncer) loadSyncStatus() { + var progress syncProgress + + if status := rawdb.ReadSanpshotSyncStatus(s.db); status != nil { + if err := json.Unmarshal(status, &progress); err != nil { + log.Error("Failed to decode snap sync status", "err", err) + } else { + for _, task := range progress.Tasks { + log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) + } + s.tasks = progress.Tasks + + s.accountSynced = progress.AccountSynced + s.accountBytes = progress.AccountBytes + s.bytecodeSynced = progress.BytecodeSynced + s.bytecodeBytes = progress.BytecodeBytes + s.storageSynced = progress.StorageSynced + s.storageBytes = progress.StorageBytes + + s.trienodeHealSynced = progress.TrienodeHealSynced + s.trienodeHealBytes = progress.TrienodeHealBytes + s.bytecodeHealSynced = progress.BytecodeHealSynced + s.bytecodeHealBytes = progress.BytecodeHealBytes + return + } + } + // Either we've failed to decode the previus state, or there was none. + // Start a fresh sync by chunking up the account range and scheduling + // them for retrieval. + s.tasks = nil + s.accountSynced, s.accountBytes = 0, 0 + s.bytecodeSynced, s.bytecodeBytes = 0, 0 + s.storageSynced, s.storageBytes = 0, 0 + s.trienodeHealSynced, s.trienodeHealBytes = 0, 0 + s.bytecodeHealSynced, s.bytecodeHealBytes = 0, 0 + + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(accountConcurrency), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if i == accountConcurrency-1 { + // Make sure we don't overflow if the step is not a proper divisor + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + s.tasks = append(s.tasks, &accountTask{ + Next: next, + Last: last, + SubTasks: make(map[common.Hash][]*storageTask), + }) + log.Debug("Created account sync task", "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } +} + +// saveSyncStatus marshals the remaining sync tasks into leveldb. +func (s *Syncer) saveSyncStatus() { + progress := &syncProgress{ + Tasks: s.tasks, + AccountSynced: s.accountSynced, + AccountBytes: s.accountBytes, + BytecodeSynced: s.bytecodeSynced, + BytecodeBytes: s.bytecodeBytes, + StorageSynced: s.storageSynced, + StorageBytes: s.storageBytes, + TrienodeHealSynced: s.trienodeHealSynced, + TrienodeHealBytes: s.trienodeHealBytes, + BytecodeHealSynced: s.bytecodeHealSynced, + BytecodeHealBytes: s.bytecodeHealBytes, + } + status, err := json.Marshal(progress) + if err != nil { + panic(err) // This can only fail during implementation + } + rawdb.WriteSnapshotSyncStatus(s.db, status) +} + +// cleanAccountTasks removes account range retrieval tasks that have already been +// completed. +func (s *Syncer) cleanAccountTasks() { + for i := 0; i < len(s.tasks); i++ { + if s.tasks[i].done { + s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) + i-- + } + } +} + +// cleanStorageTasks iterates over all the account tasks and storage sub-tasks +// within, cleaning any that have been completed. +func (s *Syncer) cleanStorageTasks() { + for _, task := range s.tasks { + for account, subtasks := range task.SubTasks { + // Remove storage range retrieval tasks that completed + for j := 0; j < len(subtasks); j++ { + if subtasks[j].done { + subtasks = append(subtasks[:j], subtasks[j+1:]...) + j-- + } + } + if len(subtasks) > 0 { + task.SubTasks[account] = subtasks + continue + } + // If all storage chunks are done, mark the account as done too + for j, hash := range task.res.hashes { + if hash == account { + task.needState[j] = false + } + } + delete(task.SubTasks, account) + task.pend-- + + // If this was the last pending task, forward the account task + if task.pend == 0 { + s.forwardAccountTask(task) + } + } + } +} + +// assignAccountTasks attempts to match idle peers to pending account range +// retrievals. +func (s *Syncer) assignAccountTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.accountIdlers) == 0 { + return + } + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks already filling + if task.req != nil || task.res != nil { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.accountIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.accountReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + req := &accountRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + origin: task.Next, + limit: task.Last, + task: task, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Account range request timed out") + select { + case s.accountReqFails <- req: + default: + } + }) + s.accountReqs[reqid] = req + delete(s.accountIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer, root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, maxRequestSize); err != nil { + peer.Log().Debug("Failed to request account range", "err", err) + select { + case s.accountReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + + // Inject the request into the task to block further assignments + task.req = req + } +} + +// assignBytecodeTasks attempts to match idle peers to pending code retrievals. +func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.bytecodeIdlers) == 0 { + return + } + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks not in the bytecode retrieval phase + if task.res == nil { + continue + } + // Skip tasks that are already retrieving (or done with) all codes + if len(task.codeTasks) == 0 { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.bytecodeIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.bytecodeReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + hashes := make([]common.Hash, 0, maxCodeRequestCount) + for hash := range task.codeTasks { + delete(task.codeTasks, hash) + hashes = append(hashes, hash) + if len(hashes) >= maxCodeRequestCount { + break + } + } + req := &bytecodeRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: task, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Bytecode request timed out") + select { + case s.bytecodeReqFails <- req: + default: + } + }) + s.bytecodeReqs[reqid] = req + delete(s.bytecodeIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { + log.Debug("Failed to request bytecodes", "err", err) + select { + case s.bytecodeReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle]) // We're in the lock, peers[id] surely exists + } +} + +// assignStorageTasks attempts to match idle peers to pending storage range +// retrievals. +func (s *Syncer) assignStorageTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.storageIdlers) == 0 { + return + } + // Iterate over all the tasks and try to find a pending one + for _, task := range s.tasks { + // Skip any tasks not in the storage retrieval phase + if task.res == nil { + continue + } + // Skip tasks that are already retrieving (or done with) all small states + if len(task.SubTasks) == 0 && len(task.stateTasks) == 0 { + continue + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.storageIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.storageReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer. If there are + // large contract tasks pending, complete those before diving into + // even more new contracts. + var ( + accounts = make([]common.Hash, 0, maxStorageSetRequestCount) + roots = make([]common.Hash, 0, maxStorageSetRequestCount) + subtask *storageTask + ) + for account, subtasks := range task.SubTasks { + for _, st := range subtasks { + // Skip any subtasks already filling + if st.req != nil { + continue + } + // Found an incomplete storage chunk, schedule it + accounts = append(accounts, account) + roots = append(roots, st.root) + + subtask = st + break // Large contract chunks are downloaded individually + } + if subtask != nil { + break // Large contract chunks are downloaded individually + } + } + if subtask == nil { + // No large contract required retrieval, but small ones available + for acccount, root := range task.stateTasks { + delete(task.stateTasks, acccount) + + accounts = append(accounts, acccount) + roots = append(roots, root) + + if len(accounts) >= maxStorageSetRequestCount { + break + } + } + } + // If nothing was found, it means this task is actually already fully + // retrieving, but large contracts are hard to detect. Skip to the next. + if len(accounts) == 0 { + continue + } + req := &storageRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + accounts: accounts, + roots: roots, + mainTask: task, + subTask: subtask, + } + if subtask != nil { + req.origin = subtask.Next + req.limit = subtask.Last + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Storage request timed out") + select { + case s.storageReqFails <- req: + default: + } + }) + s.storageReqs[reqid] = req + delete(s.storageIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer, root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + var origin, limit []byte + if subtask != nil { + origin, limit = req.origin[:], req.limit[:] + } + if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, maxRequestSize); err != nil { + log.Debug("Failed to request storage", "err", err) + select { + case s.storageReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + + // Inject the request into the subtask to block further assignments + if subtask != nil { + subtask.req = req + } + } +} + +// assignTrienodeHealTasks attempts to match idle peers to trie node requests to +// heal any trie errors caused by the snap sync's chunked retrieval model. +func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.trienodeHealIdlers) == 0 { + return + } + // Iterate over pending tasks and try to find a peer to retrieve with + for len(s.healer.trieTasks) > 0 || s.healer.scheduler.Pending() > 0 { + // If there are not enough trie tasks queued to fully assign, fill the + // queue from the state sync scheduler. The trie synced schedules these + // together with bytecodes, so we need to queue them combined. + var ( + have = len(s.healer.trieTasks) + len(s.healer.codeTasks) + want = maxTrieRequestCount + maxCodeRequestCount + ) + if have < want { + nodes, paths, codes := s.healer.scheduler.Missing(want - have) + for i, hash := range nodes { + s.healer.trieTasks[hash] = paths[i] + } + for _, hash := range codes { + s.healer.codeTasks[hash] = struct{}{} + } + } + // If all the heal tasks are bytecodes or already downloading, bail + if len(s.healer.trieTasks) == 0 { + return + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.trienodeHealIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.trienodeHealReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + var ( + hashes = make([]common.Hash, 0, maxTrieRequestCount) + paths = make([]trie.SyncPath, 0, maxTrieRequestCount) + pathsets = make([]TrieNodePathSet, 0, maxTrieRequestCount) + ) + for hash, pathset := range s.healer.trieTasks { + delete(s.healer.trieTasks, hash) + + hashes = append(hashes, hash) + paths = append(paths, pathset) + pathsets = append(pathsets, [][]byte(pathset)) // TODO(karalabe): group requests by account hash + + if len(hashes) >= maxTrieRequestCount { + break + } + } + req := &trienodeHealRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + paths: paths, + task: s.healer, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Trienode heal request timed out") + select { + case s.trienodeHealReqFails <- req: + default: + } + }) + s.trienodeHealReqs[reqid] = req + delete(s.trienodeHealIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer, root common.Hash) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil { + log.Debug("Failed to request trienode healers", "err", err) + select { + case s.trienodeHealReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + } +} + +// assignBytecodeHealTasks attempts to match idle peers to bytecode requests to +// heal any trie errors caused by the snap sync's chunked retrieval model. +func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { + s.lock.Lock() + defer s.lock.Unlock() + + // If there are no idle peers, short circuit assignment + if len(s.bytecodeHealIdlers) == 0 { + return + } + // Iterate over pending tasks and try to find a peer to retrieve with + for len(s.healer.codeTasks) > 0 || s.healer.scheduler.Pending() > 0 { + // If there are not enough trie tasks queued to fully assign, fill the + // queue from the state sync scheduler. The trie synced schedules these + // together with trie nodes, so we need to queue them combined. + var ( + have = len(s.healer.trieTasks) + len(s.healer.codeTasks) + want = maxTrieRequestCount + maxCodeRequestCount + ) + if have < want { + nodes, paths, codes := s.healer.scheduler.Missing(want - have) + for i, hash := range nodes { + s.healer.trieTasks[hash] = paths[i] + } + for _, hash := range codes { + s.healer.codeTasks[hash] = struct{}{} + } + } + // If all the heal tasks are trienodes or already downloading, bail + if len(s.healer.codeTasks) == 0 { + return + } + // Task pending retrieval, try to find an idle peer. If no such peer + // exists, we probably assigned tasks for all (or they are stateless). + // Abort the entire assignment mechanism. + var idle string + for id := range s.bytecodeHealIdlers { + // If the peer rejected a query in this sync cycle, don't bother asking + // again for anything, it's either out of sync or already pruned + if _, ok := s.statelessPeers[id]; ok { + continue + } + idle = id + break + } + if idle == "" { + return + } + // Matched a pending task to an idle peer, allocate a unique request id + var reqid uint64 + for { + reqid = uint64(rand.Int63()) + if reqid == 0 { + continue + } + if _, ok := s.bytecodeHealReqs[reqid]; ok { + continue + } + break + } + // Generate the network query and send it to the peer + hashes := make([]common.Hash, 0, maxCodeRequestCount) + for hash := range s.healer.codeTasks { + delete(s.healer.codeTasks, hash) + + hashes = append(hashes, hash) + if len(hashes) >= maxCodeRequestCount { + break + } + } + req := &bytecodeHealRequest{ + peer: idle, + id: reqid, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: s.healer, + } + req.timeout = time.AfterFunc(requestTimeout, func() { + log.Debug("Bytecode heal request timed out") + select { + case s.bytecodeHealReqFails <- req: + default: + } + }) + s.bytecodeHealReqs[reqid] = req + delete(s.bytecodeHealIdlers, idle) + + s.pend.Add(1) + go func(peer *Peer) { + defer s.pend.Done() + + // Attempt to send the remote request and revert if it fails + if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { + log.Debug("Failed to request bytecode healers", "err", err) + select { + case s.bytecodeHealReqFails <- req: + default: + } + } + // Request successfully sent, start a + }(s.peers[idle]) // We're in the lock, peers[id] surely exists + } +} + +// revertRequests locates all the currently pending reuqests from a particular +// peer and reverts them, rescheduling for others to fulfill. +func (s *Syncer) revertRequests(peer string) { + // Gather the requests first, revertals need the lock too + s.lock.Lock() + var accountReqs []*accountRequest + for _, req := range s.accountReqs { + if req.peer == peer { + accountReqs = append(accountReqs, req) + } + } + var bytecodeReqs []*bytecodeRequest + for _, req := range s.bytecodeReqs { + if req.peer == peer { + bytecodeReqs = append(bytecodeReqs, req) + } + } + var storageReqs []*storageRequest + for _, req := range s.storageReqs { + if req.peer == peer { + storageReqs = append(storageReqs, req) + } + } + var trienodeHealReqs []*trienodeHealRequest + for _, req := range s.trienodeHealReqs { + if req.peer == peer { + trienodeHealReqs = append(trienodeHealReqs, req) + } + } + var bytecodeHealReqs []*bytecodeHealRequest + for _, req := range s.bytecodeHealReqs { + if req.peer == peer { + bytecodeHealReqs = append(bytecodeHealReqs, req) + } + } + s.lock.Unlock() + + // Revert all the requests matching the peer + for _, req := range accountReqs { + s.revertAccountRequest(req) + } + for _, req := range bytecodeReqs { + s.revertBytecodeRequest(req) + } + for _, req := range storageReqs { + s.revertStorageRequest(req) + } + for _, req := range trienodeHealReqs { + s.revertTrienodeHealRequest(req) + } + for _, req := range bytecodeHealReqs { + s.revertBytecodeHealRequest(req) + } +} + +// revertAccountRequest cleans up an account range request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertAccountRequest(req *accountRequest) { + log.Trace("Reverting account request", "peer", req.peer, "reqid", req.id) + select { + case <-req.stale: + log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.accountReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the account + // task as not-pending, ready for resheduling + req.timeout.Stop() + if req.task.req == req { + req.task.req = nil + } +} + +// revertBytecodeRequest cleans up an bytecode request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { + log.Trace("Reverting bytecode request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.bytecodeReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the code + // retrievals as not-pending, ready for resheduling + req.timeout.Stop() + for _, hash := range req.hashes { + req.task.codeTasks[hash] = struct{}{} + } +} + +// revertStorageRequest cleans up a storage range request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertStorageRequest(req *storageRequest) { + log.Trace("Reverting storage request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.storageReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the storage + // task as not-pending, ready for resheduling + req.timeout.Stop() + if req.subTask != nil { + req.subTask.req = nil + } else { + for i, account := range req.accounts { + req.mainTask.stateTasks[account] = req.roots[i] + } + } +} + +// revertTrienodeHealRequest cleans up an trienode heal request and returns all +// failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { + log.Trace("Reverting trienode heal request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.trienodeHealReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the trie node + // retrievals as not-pending, ready for resheduling + req.timeout.Stop() + for i, hash := range req.hashes { + req.task.trieTasks[hash] = [][]byte(req.paths[i]) + } +} + +// revertBytecodeHealRequest cleans up an bytecode request and returns all failed +// retrieval tasks to the scheduler for reassignment. +func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) { + log.Trace("Reverting bytecode heal request", "peer", req.peer) + select { + case <-req.stale: + log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id) + return + default: + } + close(req.stale) + + // Remove the request from the tracked set + s.lock.Lock() + delete(s.bytecodeHealReqs, req.id) + s.lock.Unlock() + + // If there's a timeout timer still running, abort it and mark the code + // retrievals as not-pending, ready for resheduling + req.timeout.Stop() + for _, hash := range req.hashes { + req.task.codeTasks[hash] = struct{}{} + } +} + +// processAccountResponse integrates an already validated account range response +// into the account tasks. +func (s *Syncer) processAccountResponse(res *accountResponse) { + // Switch the task from pending to filling + res.task.req = nil + res.task.res = res + + // Ensure that the response doesn't overflow into the subsequent task + last := res.task.Last.Big() + for i, hash := range res.hashes { + if hash.Big().Cmp(last) > 0 { + // Chunk overflown, cut off excess, but also update the boundary nodes + for j := i; j < len(res.hashes); j++ { + if err := res.trie.Prove(res.hashes[j][:], 0, res.overflow); err != nil { + panic(err) // Account range was already proven, what happened + } + } + res.hashes = res.hashes[:i] + res.accounts = res.accounts[:i] + res.cont = false // Mark range completed + break + } + } + // Itereate over all the accounts and assemble which ones need further sub- + // filling before the entire account range can be persisted. + res.task.needCode = make([]bool, len(res.accounts)) + res.task.needState = make([]bool, len(res.accounts)) + res.task.needHeal = make([]bool, len(res.accounts)) + + res.task.codeTasks = make(map[common.Hash]struct{}) + res.task.stateTasks = make(map[common.Hash]common.Hash) + + resumed := make(map[common.Hash]struct{}) + + res.task.pend = 0 + for i, account := range res.accounts { + // Check if the account is a contract with an unknown code + if !bytes.Equal(account.CodeHash, emptyCode[:]) { + if code := rawdb.ReadCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)); code == nil { + res.task.codeTasks[common.BytesToHash(account.CodeHash)] = struct{}{} + res.task.needCode[i] = true + res.task.pend++ + } + } + // Check if the account is a contract with an unknown storage trie + if account.Root != emptyRoot { + if node, err := s.db.Get(account.Root[:]); err != nil || node == nil { + // If there was a previous large state retrieval in progress, + // don't restart it from scratch. This happens if a sync cycle + // is interrupted and resumed later. However, *do* update the + // previous root hash. + if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok { + log.Error("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) + for _, subtask := range subtasks { + subtask.root = account.Root + } + res.task.needHeal[i] = true + resumed[res.hashes[i]] = struct{}{} + } else { + res.task.stateTasks[res.hashes[i]] = account.Root + } + res.task.needState[i] = true + res.task.pend++ + } + } + } + // Delete any subtasks that have been aborted but not resumed. This may undo + // some progress if a newpeer gives us less accounts than an old one, but for + // now we have to live with that. + for hash := range res.task.SubTasks { + if _, ok := resumed[hash]; !ok { + log.Error("Aborting suspended storage retrieval", "account", hash) + delete(res.task.SubTasks, hash) + } + } + // If the account range contained no contracts, or all have been fully filled + // beforehand, short circuit storage filling and forward to the next task + if res.task.pend == 0 { + s.forwardAccountTask(res.task) + return + } + // Some accounts are incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processBytecodeResponse integrates an already validated bytecode response +// into the account tasks. +func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { + batch := s.db.NewBatch() + + var ( + codes uint64 + bytes common.StorageSize + ) + for i, hash := range res.hashes { + code := res.codes[i] + + // If the bytecode was not delivered, reschedule it + if code == nil { + res.task.codeTasks[hash] = struct{}{} + continue + } + // Code was delivered, mark it not needed any more + for j, account := range res.task.res.accounts { + if res.task.needCode[j] && hash == common.BytesToHash(account.CodeHash) { + res.task.needCode[j] = false + res.task.pend-- + } + } + // Push the bytecode into a database batch + s.bytecodeSynced++ + s.bytecodeBytes += common.StorageSize(len(code)) + + codes++ + bytes += common.StorageSize(len(code)) + + rawdb.WriteCode(batch, hash, code) + s.bloom.Add(hash[:]) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist bytecodes", "err", err) + } + log.Debug("Persisted set of bytecodes", "count", codes, "bytes", bytes) + + // If this delivery completed the last pending task, forward the account task + // to the next chunk + if res.task.pend == 0 { + s.forwardAccountTask(res.task) + return + } + // Some accounts are still incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processStorageResponse integrates an already validated storage response +// into the account tasks. +func (s *Syncer) processStorageResponse(res *storageResponse) { + // Switch the suntask from pending to idle + if res.subTask != nil { + res.subTask.req = nil + } + batch := s.db.NewBatch() + + var ( + slots int + nodes int + skipped int + bytes common.StorageSize + ) + // Iterate over all the accounts and reconstruct their storage tries from the + // delivered slots + delivered := make(map[common.Hash]bool) + for i := 0; i < len(res.hashes); i++ { + delivered[res.roots[i]] = true + } + for i, account := range res.accounts { + // If the account was not delivered, reschedule it + if i >= len(res.hashes) { + if !delivered[res.roots[i]] { + res.mainTask.stateTasks[account] = res.roots[i] + } + continue + } + // State was delivered, if complete mark as not needed any more, otherwise + // mark the account as needing healing + for j, acc := range res.mainTask.res.accounts { + if res.roots[i] == acc.Root { + // If the packet contains multiple contract storage slots, all + // but the last are surely complete. The last contract may be + // chunked, so check it's continuation flag. + if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { + res.mainTask.needState[j] = false + res.mainTask.pend-- + } + // If the last contract was chunked, mark it as needing healing + // to avoid writing it out to disk prematurely. + if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { + res.mainTask.needHeal[j] = true + } + // If the last contract was chunked, we need to switch to large + // contract handling mode + if res.subTask == nil && i == len(res.hashes)-1 && res.cont { + // If we haven't yet started a large-contract retrieval, create + // the subtasks for it within the main account task + if tasks, ok := res.mainTask.SubTasks[account]; !ok { + var ( + next common.Hash + ) + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(storageConcurrency), + ), common.Big1, + ) + for k := 0; k < storageConcurrency; k++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if k == storageConcurrency-1 { + // Make sure we don't overflow if the step is not a proper divisor + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + tasks = append(tasks, &storageTask{ + Next: next, + Last: last, + root: acc.Root, + }) + log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + res.mainTask.SubTasks[account] = tasks + + // Since we've just created the sub-tasks, this response + // is surely for the first one (zero origin) + res.subTask = tasks[0] + } + } + // If we're in large contract delivery mode, forward the subtask + if res.subTask != nil { + // Ensure the response doesn't overflow into the subsequent task + last := res.subTask.Last.Big() + for k, hash := range res.hashes[i] { + if hash.Big().Cmp(last) > 0 { + // Chunk overflown, cut off excess, but also update the boundary + for l := k; l < len(res.hashes[i]); l++ { + if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { + panic(err) // Account range was already proven, what happened + } + } + res.hashes[i] = res.hashes[i][:k] + res.slots[i] = res.slots[i][:k] + res.cont = false // Mark range completed + break + } + } + // Forward the relevant storage chunk (even if created just now) + if res.cont { + res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) + } else { + res.subTask.done = true + } + } + } + } + // Iterate over all the reconstructed trie nodes and push them to disk + slots += len(res.hashes[i]) + + it := res.nodes[i].NewIterator(nil, nil) + for it.Next() { + // Boundary nodes are not written for the last result, since they are incomplete + if i == len(res.hashes)-1 { + if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { + skipped++ + continue + } + } + // Node is not a boundary, persist to disk + batch.Put(it.Key(), it.Value()) + s.bloom.Add(it.Key()) + + bytes += common.StorageSize(common.HashLength + len(it.Value())) + nodes++ + } + it.Release() + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist storage slots", "err", err) + } + s.storageSynced += uint64(slots) + s.storageBytes += bytes + + log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "nodes", nodes, "skipped", skipped, "bytes", bytes) + + // If this delivery completed the last pending task, forward the account task + // to the next chunk + if res.mainTask.pend == 0 { + s.forwardAccountTask(res.mainTask) + return + } + // Some accounts are still incomplete, leave as is for the storage and contract + // task assigners to pick up and fill. +} + +// processTrienodeHealResponse integrates an already validated trienode response +// into the healer tasks. +func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { + for i, hash := range res.hashes { + node := res.nodes[i] + + // If the trie node was not delivered, reschedule it + if node == nil { + res.task.trieTasks[hash] = res.paths[i] + continue + } + // Push the trie node into the state syncer + s.trienodeHealSynced++ + s.trienodeHealBytes += common.StorageSize(len(node)) + + err := s.healer.scheduler.Process(trie.SyncResult{Hash: hash, Data: node}) + switch err { + case nil: + case trie.ErrAlreadyProcessed: + s.trienodeHealDups++ + case trie.ErrNotRequested: + s.trienodeHealNops++ + default: + log.Error("Invalid trienode processed", "hash", hash, "err", err) + } + } + batch := s.db.NewBatch() + if err := s.healer.scheduler.Commit(batch); err != nil { + log.Error("Failed to commit healing data", "err", err) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist healing data", "err", err) + } + log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) +} + +// processBytecodeHealResponse integrates an already validated bytecode response +// into the healer tasks. +func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) { + for i, hash := range res.hashes { + node := res.codes[i] + + // If the trie node was not delivered, reschedule it + if node == nil { + res.task.codeTasks[hash] = struct{}{} + continue + } + // Push the trie node into the state syncer + s.bytecodeHealSynced++ + s.bytecodeHealBytes += common.StorageSize(len(node)) + + err := s.healer.scheduler.Process(trie.SyncResult{Hash: hash, Data: node}) + switch err { + case nil: + case trie.ErrAlreadyProcessed: + s.bytecodeHealDups++ + case trie.ErrNotRequested: + s.bytecodeHealNops++ + default: + log.Error("Invalid bytecode processed", "hash", hash, "err", err) + } + } + batch := s.db.NewBatch() + if err := s.healer.scheduler.Commit(batch); err != nil { + log.Error("Failed to commit healing data", "err", err) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to persist healing data", "err", err) + } + log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) +} + +// forwardAccountTask takes a filled account task and persists anything available +// into the database, after which it forwards the next account marker so that the +// task's next chunk may be filled. +func (s *Syncer) forwardAccountTask(task *accountTask) { + // Remove any pending delivery + res := task.res + if res == nil { + return // nothing to forward + } + task.res = nil + + // Iterate over all the accounts and gather all the incomplete trie nodes. A + // node is incomplete if we haven't yet filled it (sync was interrupted), or + // if we filled it in multiple chunks (storage trie), in which case the few + // nodes on the chunk boundaries are missing. + incompletes := light.NewNodeSet() + for i := range res.accounts { + // If the filling was interrupted, mark everything after as incomplete + if task.needCode[i] || task.needState[i] { + for j := i; j < len(res.accounts); j++ { + if err := res.trie.Prove(res.hashes[j][:], 0, incompletes); err != nil { + panic(err) // Account range was already proven, what happened + } + } + break + } + // Filling not interrupted until this point, mark incomplete if needs healing + if task.needHeal[i] { + if err := res.trie.Prove(res.hashes[i][:], 0, incompletes); err != nil { + panic(err) // Account range was already proven, what happened + } + } + } + // Persist every finalized trie node that's not on the boundary + batch := s.db.NewBatch() + + var ( + nodes int + skipped int + bytes common.StorageSize + ) + it := res.nodes.NewIterator(nil, nil) + for it.Next() { + // Boundary nodes are not written, since they are incomplete + if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { + skipped++ + continue + } + // Overflow nodes are not written, since they mess with another task + if _, err := res.overflow.Get(it.Key()); err == nil { + skipped++ + continue + } + // Accounts with split storage requests are incomplete + if _, err := incompletes.Get(it.Key()); err == nil { + skipped++ + continue + } + // Node is neither a boundary, not an incomplete account, persist to disk + batch.Put(it.Key(), it.Value()) + s.bloom.Add(it.Key()) + + bytes += common.StorageSize(common.HashLength + len(it.Value())) + nodes++ + } + it.Release() + + if err := batch.Write(); err != nil { + log.Crit("Failed to persist accounts", "err", err) + } + s.accountBytes += bytes + s.accountSynced += uint64(len(res.accounts)) + + log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "nodes", nodes, "skipped", skipped, "bytes", bytes) + + // Task filling persisted, push it the chunk marker forward to the first + // account still missing data. + for i, hash := range res.hashes { + if task.needCode[i] || task.needState[i] { + return + } + task.Next = common.BigToHash(new(big.Int).Add(hash.Big(), big.NewInt(1))) + } + // All accounts marked as complete, track if the entire task is done + task.done = !res.cont +} + +// OnAccounts is a callback method to invoke when a range of accounts are +// received from a remote peer. +func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { + size := common.StorageSize(len(hashes) * common.HashLength) + for _, account := range accounts { + size += common.StorageSize(len(account)) + } + for _, node := range proof { + size += common.StorageSize(len(node)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.accountIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.accountReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected account range packet") + s.lock.Unlock() + return nil + } + delete(s.accountReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For account range queries that means the state being + // retrieved was either already pruned remotely, or the peer is not yet + // synced to our head. + if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 { + logger.Debug("Peer rejected account range request", "root", s.root) + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + root := s.root + s.lock.Unlock() + + // Reconstruct a partial trie from the response and verify it + keys := make([][]byte, len(hashes)) + for i, key := range hashes { + keys[i] = common.CopyBytes(key[:]) + } + nodes := make(light.NodeList, len(proof)) + for i, node := range proof { + nodes[i] = node + } + proofdb := nodes.NodeSet() + + var end []byte + if len(keys) > 0 { + end = keys[len(keys)-1] + } + db, tr, notary, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) + if err != nil { + logger.Warn("Account range failed proof", "err", err) + return err + } + // Partial trie reconstructed, send it to the scheduler for storage filling + bounds := make(map[common.Hash]struct{}) + + it := notary.Accessed().NewIterator(nil, nil) + for it.Next() { + bounds[common.BytesToHash(it.Key())] = struct{}{} + } + it.Release() + + accs := make([]*state.Account, len(accounts)) + for i, account := range accounts { + acc := new(state.Account) + if err := rlp.DecodeBytes(account, acc); err != nil { + panic(err) // We created these blobs, we must be able to decode them + } + accs[i] = acc + } + response := &accountResponse{ + task: req.task, + hashes: hashes, + accounts: accs, + nodes: db, + trie: tr, + bounds: bounds, + overflow: light.NewNodeSet(), + cont: cont, + } + select { + case s.accountResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer. +func (s *Syncer) OnByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { + s.lock.RLock() + syncing := len(s.tasks) > 0 + s.lock.RUnlock() + + if syncing { + return s.onByteCodes(peer, id, bytecodes) + } + return s.onHealByteCodes(peer, id, bytecodes) +} + +// onByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer in the syncing phase. +func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { + var size common.StorageSize + for _, code := range bytecodes { + size += common.StorageSize(len(code)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.bytecodeIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.bytecodeReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected bytecode packet") + s.lock.Unlock() + return nil + } + delete(s.bytecodeReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(bytecodes) == 0 { + logger.Debug("Peer rejected bytecode request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + hasher := sha3.NewLegacyKeccak256() + + codes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i) + return errors.New("unexpected bytecode") + } + // Response validated, send it to the scheduler for filling + response := &bytecodeResponse{ + task: req.task, + hashes: req.hashes, + codes: codes, + } + select { + case s.bytecodeResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnStorage is a callback method to invoke when ranges of storage slots +// are received from a remote peer. +func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { + // Gather some trace stats to aid in debugging issues + var ( + hashCount int + slotCount int + size common.StorageSize + ) + for _, hashset := range hashes { + size += common.StorageSize(common.HashLength * len(hashset)) + hashCount += len(hashset) + } + for _, slotset := range slots { + for _, slot := range slotset { + size += common.StorageSize(len(slot)) + } + slotCount += len(slotset) + } + for _, node := range proof { + size += common.StorageSize(len(node)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.storageIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.storageReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected storage ranges packet") + s.lock.Unlock() + return nil + } + delete(s.storageReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Reject the response if the hash sets and slot sets don't match, or if the + // peer sent more data than requested. + if len(hashes) != len(slots) { + s.lock.Unlock() + logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots)) + return errors.New("hash and slot set size mismatch") + } + if len(hashes) > len(req.accounts) { + s.lock.Unlock() + logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts)) + return errors.New("hash set larger than requested") + } + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For storage range queries that means the state being + // retrieved was either already pruned remotely, or the peer is not yet + // synced to our head. + if len(hashes) == 0 { + logger.Debug("Peer rejected storage request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Reconstruct the partial tries from the response and verify them + var ( + dbs = make([]ethdb.KeyValueStore, len(hashes)) + tries = make([]*trie.Trie, len(hashes)) + notary *trie.KeyValueNotary + cont bool + ) + for i := 0; i < len(hashes); i++ { + // Convert the keys and proofs into an internal format + keys := make([][]byte, len(hashes[i])) + for j, key := range hashes[i] { + keys[j] = common.CopyBytes(key[:]) + } + nodes := make(light.NodeList, 0, len(proof)) + if i == len(hashes)-1 { + for _, node := range proof { + nodes = append(nodes, node) + } + } + var err error + if len(nodes) == 0 { + // No proof has been attached, the response must cover the entire key + // space and hash to the origin root. + dbs[i], tries[i], _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) + if err != nil { + logger.Warn("Storage slots failed proof", "err", err) + return err + } + } else { + // A proof was attached, the response is only partial, check that the + // returned data is indeed part of the storage trie + proofdb := nodes.NodeSet() + + var end []byte + if len(keys) > 0 { + end = keys[len(keys)-1] + } + dbs[i], tries[i], notary, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) + if err != nil { + logger.Warn("Storage range failed proof", "err", err) + return err + } + } + } + // Partial tries reconstructed, send them to the scheduler for storage filling + bounds := make(map[common.Hash]struct{}) + + if notary != nil { // if all contract storages are delivered in full, no notary will be created + it := notary.Accessed().NewIterator(nil, nil) + for it.Next() { + bounds[common.BytesToHash(it.Key())] = struct{}{} + } + it.Release() + } + response := &storageResponse{ + mainTask: req.mainTask, + subTask: req.subTask, + accounts: req.accounts, + roots: req.roots, + hashes: hashes, + slots: slots, + nodes: dbs, + tries: tries, + bounds: bounds, + overflow: light.NewNodeSet(), + cont: cont, + } + select { + case s.storageResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// OnTrieNodes is a callback method to invoke when a batch of trie nodes +// are received from a remote peer. +func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { + var size common.StorageSize + for _, node := range trienodes { + size += common.StorageSize(len(node)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.trienodeHealIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.trienodeHealReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected trienode heal packet") + s.lock.Unlock() + return nil + } + delete(s.trienodeHealReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(trienodes) == 0 { + logger.Debug("Peer rejected trienode heal request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Cross reference the requested trienodes with the response to find gaps + // that the serving node is missing + hasher := sha3.NewLegacyKeccak256() + + nodes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(trienodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(trienodes[i]) + hash := hasher.Sum(nil) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + nodes[j] = trienodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i) + return errors.New("unexpected healing trienode") + } + // Response validated, send it to the scheduler for filling + response := &trienodeHealResponse{ + task: req.task, + hashes: req.hashes, + paths: req.paths, + nodes: nodes, + } + select { + case s.trienodeHealResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// onHealByteCodes is a callback method to invoke when a batch of contract +// bytes codes are received from a remote peer in the healing phase. +func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { + var size common.StorageSize + for _, code := range bytecodes { + size += common.StorageSize(len(code)) + } + logger := peer.logger.New("reqid", id) + logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size) + + // Whether or not the response is valid, we can mark the peer as idle and + // notify the scheduler to assign a new task. If the response is invalid, + // we'll drop the peer in a bit. + s.lock.Lock() + if _, ok := s.peers[peer.id]; ok { + s.bytecodeHealIdlers[peer.id] = struct{}{} + } + select { + case s.update <- struct{}{}: + default: + } + // Ensure the response is for a valid request + req, ok := s.bytecodeHealReqs[id] + if !ok { + // Request stale, perhaps the peer timed out but came through in the end + logger.Warn("Unexpected bytecode heal packet") + s.lock.Unlock() + return nil + } + delete(s.bytecodeHealReqs, id) + + // Clean up the request timeout timer, we'll see how to proceed further based + // on the actual delivered content + req.timeout.Stop() + + // Response is valid, but check if peer is signalling that it does not have + // the requested data. For bytecode range queries that means the peer is not + // yet synced. + if len(bytecodes) == 0 { + logger.Debug("Peer rejected bytecode heal request") + s.statelessPeers[peer.id] = struct{}{} + s.lock.Unlock() + return nil + } + s.lock.Unlock() + + // Cross reference the requested bytecodes with the response to find gaps + // that the serving node is missing + hasher := sha3.NewLegacyKeccak256() + + codes := make([][]byte, len(req.hashes)) + for i, j := 0, 0; i < len(bytecodes); i++ { + // Find the next hash that we've been served, leaving misses with nils + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + + for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { + j++ + } + if j < len(req.hashes) { + codes[j] = bytecodes[i] + j++ + continue + } + // We've either ran out of hashes, or got unrequested data + logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i) + return errors.New("unexpected healing bytecode") + } + // Response validated, send it to the scheduler for filling + response := &bytecodeHealResponse{ + task: req.task, + hashes: req.hashes, + codes: codes, + } + select { + case s.bytecodeHealResps <- response: + case <-req.cancel: + case <-req.stale: + } + return nil +} + +// hashSpace is the total size of the 256 bit hash space for accounts. +var hashSpace = new(big.Int).Exp(common.Big2, common.Big256, nil) + +// report calculates various status reports and provides it to the user. +func (s *Syncer) report(force bool) { + if len(s.tasks) > 0 { + s.reportSyncProgress(force) + return + } + s.reportHealProgress(force) +} + +// reportSyncProgress calculates various status reports and provides it to the user. +func (s *Syncer) reportSyncProgress(force bool) { + // Don't report all the events, just occasionally + if !force && time.Since(s.logTime) < 3*time.Second { + return + } + // Don't report anything until we have a meaningful progress + synced := s.accountBytes + s.bytecodeBytes + s.storageBytes + if synced == 0 { + return + } + accountGaps := new(big.Int) + for _, task := range s.tasks { + accountGaps.Add(accountGaps, new(big.Int).Sub(task.Last.Big(), task.Next.Big())) + } + accountFills := new(big.Int).Sub(hashSpace, accountGaps) + if accountFills.BitLen() == 0 { + return + } + s.logTime = time.Now() + estBytes := float64(new(big.Int).Div( + new(big.Int).Mul(new(big.Int).SetUint64(uint64(synced)), hashSpace), + accountFills, + ).Uint64()) + + elapsed := time.Since(s.startTime) + estTime := elapsed / time.Duration(synced) * time.Duration(estBytes) + + // Create a mega progress report + var ( + progress = fmt.Sprintf("%.2f%%", float64(synced)*100/estBytes) + accounts = fmt.Sprintf("%d@%v", s.accountSynced, s.accountBytes.TerminalString()) + storage = fmt.Sprintf("%d@%v", s.storageSynced, s.storageBytes.TerminalString()) + bytecode = fmt.Sprintf("%d@%v", s.bytecodeSynced, s.bytecodeBytes.TerminalString()) + ) + log.Info("State sync in progress", "synced", progress, "state", synced, + "accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed)) +} + +// reportHealProgress calculates various status reports and provides it to the user. +func (s *Syncer) reportHealProgress(force bool) { + // Don't report all the events, just occasionally + if !force && time.Since(s.logTime) < 3*time.Second { + return + } + s.logTime = time.Now() + + // Create a mega progress report + var ( + trienode = fmt.Sprintf("%d@%v", s.trienodeHealSynced, s.trienodeHealBytes.TerminalString()) + bytecode = fmt.Sprintf("%d@%v", s.bytecodeHealSynced, s.bytecodeHealBytes.TerminalString()) + ) + log.Info("State heal in progress", "nodes", trienode, "codes", bytecode, + "pending", s.healer.scheduler.Pending()) +} diff --git a/eth/sync.go b/eth/sync.go index 26badd1e21..03a5165245 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -40,12 +41,12 @@ const ( ) type txsync struct { - p *peer + p *eth.Peer txs []*types.Transaction } // syncTransactions starts sending all currently pending transactions to the given peer. -func (pm *ProtocolManager) syncTransactions(p *peer) { +func (h *handler) syncTransactions(p *eth.Peer) { // Assemble the set of transaction to broadcast or announce to the remote // peer. Fun fact, this is quite an expensive operation as it needs to sort // the transactions if the sorting is not cached yet. However, with a random @@ -53,7 +54,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // // TODO(karalabe): Figure out if we could get away with random order somehow var txs types.Transactions - pending, _ := pm.txpool.Pending() + pending, _ := h.txpool.Pending() for _, batch := range pending { txs = append(txs, batch...) } @@ -63,7 +64,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // The eth/65 protocol introduces proper transaction announcements, so instead // of dripping transactions across multiple peers, just send the entire list as // an announcement and let the remote side decide what they need (likely nothing). - if p.version >= eth65 { + if p.Version() >= eth.ETH65 { hashes := make([]common.Hash, len(txs)) for i, tx := range txs { hashes[i] = tx.Hash() @@ -73,8 +74,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { } // Out of luck, peer is running legacy protocols, drop the txs over select { - case pm.txsyncCh <- &txsync{p: p, txs: txs}: - case <-pm.quitSync: + case h.txsyncCh <- &txsync{p: p, txs: txs}: + case <-h.quitSync: } } @@ -82,8 +83,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // connection. When a new peer appears, we relay all currently pending // transactions. In order to minimise egress bandwidth usage, we send // the transactions in small packs to one peer at a time. -func (pm *ProtocolManager) txsyncLoop64() { - defer pm.wg.Done() +func (h *handler) txsyncLoop64() { + defer h.wg.Done() var ( pending = make(map[enode.ID]*txsync) @@ -94,7 +95,7 @@ func (pm *ProtocolManager) txsyncLoop64() { // send starts a sending a pack of transactions from the sync. send := func(s *txsync) { - if s.p.version >= eth65 { + if s.p.Version() >= eth.ETH65 { panic("initial transaction syncer running on eth/65+") } // Fill pack with transactions up to the target size. @@ -108,14 +109,13 @@ func (pm *ProtocolManager) txsyncLoop64() { // Remove the transactions that will be sent. s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])] if len(s.txs) == 0 { - delete(pending, s.p.ID()) + delete(pending, s.p.Peer.ID()) } // Send the pack in the background. s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size) sending = true - go func() { done <- pack.p.SendTransactions64(pack.txs) }() + go func() { done <- pack.p.SendTransactions(pack.txs) }() } - // pick chooses the next pending sync. pick := func() *txsync { if len(pending) == 0 { @@ -132,8 +132,8 @@ func (pm *ProtocolManager) txsyncLoop64() { for { select { - case s := <-pm.txsyncCh: - pending[s.p.ID()] = s + case s := <-h.txsyncCh: + pending[s.p.Peer.ID()] = s if !sending { send(s) } @@ -142,13 +142,13 @@ func (pm *ProtocolManager) txsyncLoop64() { // Stop tracking peers that cause send failures. if err != nil { pack.p.Log().Debug("Transaction send failed", "err", err) - delete(pending, pack.p.ID()) + delete(pending, pack.p.Peer.ID()) } // Schedule the next send. if s := pick(); s != nil { send(s) } - case <-pm.quitSync: + case <-h.quitSync: return } } @@ -156,7 +156,7 @@ func (pm *ProtocolManager) txsyncLoop64() { // chainSyncer coordinates blockchain sync components. type chainSyncer struct { - pm *ProtocolManager + handler *handler force *time.Timer forced bool // true when force timer fired peerEventCh chan struct{} @@ -166,15 +166,15 @@ type chainSyncer struct { // chainSyncOp is a scheduled sync operation. type chainSyncOp struct { mode downloader.SyncMode - peer *peer + peer *eth.Peer td *big.Int head common.Hash } // newChainSyncer creates a chainSyncer. -func newChainSyncer(pm *ProtocolManager) *chainSyncer { +func newChainSyncer(handler *handler) *chainSyncer { return &chainSyncer{ - pm: pm, + handler: handler, peerEventCh: make(chan struct{}), } } @@ -182,23 +182,24 @@ func newChainSyncer(pm *ProtocolManager) *chainSyncer { // handlePeerEvent notifies the syncer about a change in the peer set. // This is called for new peers and every time a peer announces a new // chain head. -func (cs *chainSyncer) handlePeerEvent(p *peer) bool { +func (cs *chainSyncer) handlePeerEvent(peer *eth.Peer) bool { select { case cs.peerEventCh <- struct{}{}: return true - case <-cs.pm.quitSync: + case <-cs.handler.quitSync: return false } } // loop runs in its own goroutine and launches the sync when necessary. func (cs *chainSyncer) loop() { - defer cs.pm.wg.Done() + defer cs.handler.wg.Done() - cs.pm.blockFetcher.Start() - cs.pm.txFetcher.Start() - defer cs.pm.blockFetcher.Stop() - defer cs.pm.txFetcher.Stop() + cs.handler.blockFetcher.Start() + cs.handler.txFetcher.Start() + defer cs.handler.blockFetcher.Stop() + defer cs.handler.txFetcher.Stop() + defer cs.handler.downloader.Terminate() // The force timer lowers the peer count threshold down to one when it fires. // This ensures we'll always start sync even if there aren't enough peers. @@ -209,7 +210,6 @@ func (cs *chainSyncer) loop() { if op := cs.nextSyncOp(); op != nil { cs.startSync(op) } - select { case <-cs.peerEventCh: // Peer information changed, recheck. @@ -220,14 +220,13 @@ func (cs *chainSyncer) loop() { case <-cs.force.C: cs.forced = true - case <-cs.pm.quitSync: + case <-cs.handler.quitSync: // Disable all insertion on the blockchain. This needs to happen before // terminating the downloader because the downloader waits for blockchain // inserts, and these can take a long time to finish. - cs.pm.blockchain.StopInsert() - cs.pm.downloader.Terminate() + cs.handler.chain.StopInsert() + cs.handler.downloader.Terminate() if cs.doneCh != nil { - // Wait for the current sync to end. <-cs.doneCh } return @@ -245,19 +244,22 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { minPeers := defaultMinSyncPeers if cs.forced { minPeers = 1 - } else if minPeers > cs.pm.maxPeers { - minPeers = cs.pm.maxPeers + } else if minPeers > cs.handler.maxPeers { + minPeers = cs.handler.maxPeers } - if cs.pm.peers.Len() < minPeers { + if cs.handler.peers.Len() < minPeers { return nil } - - // We have enough peers, check TD. - peer := cs.pm.peers.BestPeer() + // We have enough peers, check TD + peer := cs.handler.peers.ethPeerWithHighestTD() if peer == nil { return nil } mode, ourTD := cs.modeAndLocalHead() + if mode == downloader.FastSync && atomic.LoadUint32(&cs.handler.snapSync) == 1 { + // Fast sync via the snap protocol + mode = downloader.SnapSync + } op := peerToSyncOp(mode, peer) if op.td.Cmp(ourTD) <= 0 { return nil // We're in sync. @@ -265,42 +267,42 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { return op } -func peerToSyncOp(mode downloader.SyncMode, p *peer) *chainSyncOp { +func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp { peerHead, peerTD := p.Head() return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead} } func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) { // If we're in fast sync mode, return that directly - if atomic.LoadUint32(&cs.pm.fastSync) == 1 { - block := cs.pm.blockchain.CurrentFastBlock() - td := cs.pm.blockchain.GetTdByHash(block.Hash()) + if atomic.LoadUint32(&cs.handler.fastSync) == 1 { + block := cs.handler.chain.CurrentFastBlock() + td := cs.handler.chain.GetTdByHash(block.Hash()) return downloader.FastSync, td } // We are probably in full sync, but we might have rewound to before the // fast sync pivot, check if we should reenable - if pivot := rawdb.ReadLastPivotNumber(cs.pm.chaindb); pivot != nil { - if head := cs.pm.blockchain.CurrentBlock(); head.NumberU64() < *pivot { - block := cs.pm.blockchain.CurrentFastBlock() - td := cs.pm.blockchain.GetTdByHash(block.Hash()) + if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil { + if head := cs.handler.chain.CurrentBlock(); head.NumberU64() < *pivot { + block := cs.handler.chain.CurrentFastBlock() + td := cs.handler.chain.GetTdByHash(block.Hash()) return downloader.FastSync, td } } // Nope, we're really full syncing - head := cs.pm.blockchain.CurrentHeader() - td := cs.pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + head := cs.handler.chain.CurrentHeader() + td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64()) return downloader.FullSync, td } // startSync launches doSync in a new goroutine. func (cs *chainSyncer) startSync(op *chainSyncOp) { cs.doneCh = make(chan error, 1) - go func() { cs.doneCh <- cs.pm.doSync(op) }() + go func() { cs.doneCh <- cs.handler.doSync(op) }() } // doSync synchronizes the local blockchain with a remote peer. -func (pm *ProtocolManager) doSync(op *chainSyncOp) error { - if op.mode == downloader.FastSync { +func (h *handler) doSync(op *chainSyncOp) error { + if op.mode == downloader.FastSync || op.mode == downloader.SnapSync { // Before launch the fast sync, we have to ensure user uses the same // txlookup limit. // The main concern here is: during the fast sync Geth won't index the @@ -310,35 +312,33 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error { // has been indexed. So here for the user-experience wise, it's non-optimal // that user can't change limit during the fast sync. If changed, Geth // will just blindly use the original one. - limit := pm.blockchain.TxLookupLimit() - if stored := rawdb.ReadFastTxLookupLimit(pm.chaindb); stored == nil { - rawdb.WriteFastTxLookupLimit(pm.chaindb, limit) + limit := h.chain.TxLookupLimit() + if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil { + rawdb.WriteFastTxLookupLimit(h.database, limit) } else if *stored != limit { - pm.blockchain.SetTxLookupLimit(*stored) + h.chain.SetTxLookupLimit(*stored) log.Warn("Update txLookup limit", "provided", limit, "updated", *stored) } } // Run the sync cycle, and disable fast sync if we're past the pivot block - err := pm.downloader.Synchronise(op.peer.id, op.head, op.td, op.mode) + err := h.downloader.Synchronise(op.peer.ID(), op.head, op.td, op.mode) if err != nil { return err } - if atomic.LoadUint32(&pm.fastSync) == 1 { + if atomic.LoadUint32(&h.fastSync) == 1 { log.Info("Fast sync complete, auto disabling") - atomic.StoreUint32(&pm.fastSync, 0) + atomic.StoreUint32(&h.fastSync, 0) } - // If we've successfully finished a sync cycle and passed any required checkpoint, // enable accepting transactions from the network. - head := pm.blockchain.CurrentBlock() - if head.NumberU64() >= pm.checkpointNumber { + head := h.chain.CurrentBlock() + if head.NumberU64() >= h.checkpointNumber { // Checkpoint passed, sanity check the timestamp to have a fallback mechanism // for non-checkpointed (number = 0) private networks. if head.Time() >= uint64(time.Now().AddDate(0, -1, 0).Unix()) { - atomic.StoreUint32(&pm.acceptTxs, 1) + atomic.StoreUint32(&h.acceptTxs, 1) } } - if head.NumberU64() > 0 { // We've completed a sync cycle, notify all peers of new state. This path is // essential in star-topology networks where a gateway node needs to notify @@ -346,8 +346,7 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error { // scenario will most often crop up in private and hackathon networks with // degenerate connectivity, but it should be healthy for the mainnet too to // more reliably update peers or the local TD state. - pm.BroadcastBlock(head, false) + h.BroadcastBlock(head, false) } - return nil } diff --git a/eth/sync_test.go b/eth/sync_test.go index ac1e5fad1b..473e19518f 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -22,43 +22,59 @@ import ( "time" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" ) -func TestFastSyncDisabling63(t *testing.T) { testFastSyncDisabling(t, 63) } +// Tests that fast sync is disabled after a successful sync cycle. func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) } func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) } // Tests that fast sync gets disabled as soon as a real block is successfully // imported into the blockchain. -func testFastSyncDisabling(t *testing.T, protocol int) { +func testFastSyncDisabling(t *testing.T, protocol uint) { t.Parallel() - // Create a pristine protocol manager, check that fast sync is left enabled - pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) - if atomic.LoadUint32(&pmEmpty.fastSync) == 0 { + // Create an empty handler and ensure it's in fast sync mode + empty := newTestHandler() + if atomic.LoadUint32(&empty.handler.fastSync) == 0 { t.Fatalf("fast sync disabled on pristine blockchain") } - // Create a full protocol manager, check that fast sync gets disabled - pmFull, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil) - if atomic.LoadUint32(&pmFull.fastSync) == 1 { + defer empty.close() + + // Create a full handler and ensure fast sync ends up disabled + full := newTestHandlerWithBlocks(1024) + if atomic.LoadUint32(&full.handler.fastSync) == 1 { t.Fatalf("fast sync not disabled on non-empty blockchain") } + defer full.close() + + // Sync up the two handlers + emptyPipe, fullPipe := p2p.MsgPipe() + defer emptyPipe.Close() + defer fullPipe.Close() - // Sync up the two peers - io1, io2 := p2p.MsgPipe() - go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get)) - go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get)) + emptyPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), emptyPipe, empty.txpool) + fullPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), fullPipe, full.txpool) + defer emptyPeer.Close() + defer fullPeer.Close() + go empty.handler.runEthPeer(emptyPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(empty.handler), peer) + }) + go full.handler.runEthPeer(fullPeer, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(full.handler), peer) + }) + // Wait a bit for the above handlers to start time.Sleep(250 * time.Millisecond) - op := peerToSyncOp(downloader.FastSync, pmEmpty.peers.BestPeer()) - if err := pmEmpty.doSync(op); err != nil { - t.Fatal("sync failed:", err) - } // Check that fast sync was disabled - if atomic.LoadUint32(&pmEmpty.fastSync) == 1 { + op := peerToSyncOp(downloader.FastSync, empty.handler.peers.ethPeerWithHighestTD()) + if err := empty.handler.doSync(op); err != nil { + t.Fatal("sync failed:", err) + } + if atomic.LoadUint32(&empty.handler.fastSync) == 1 { t.Fatalf("fast sync not disabled after successful synchronisation") } } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 1828ad70fb..e0f4f95ff3 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -36,8 +36,8 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" @@ -444,13 +444,15 @@ func (s *Service) login(conn *connWrapper) error { // Construct and send the login authentication infos := s.server.NodeInfo() - var network, protocol string + var protocols []string + for _, proto := range s.server.Protocols { + protocols = append(protocols, fmt.Sprintf("%s/%d", proto.Name, proto.Version)) + } + var network string if info := infos.Protocols["eth"]; info != nil { - network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network) - protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) + network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network) } else { network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) - protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0]) } auth := &authMsg{ ID: s.node, @@ -459,7 +461,7 @@ func (s *Service) login(conn *connWrapper) error { Node: infos.Name, Port: infos.Ports.Listener, Network: network, - Protocol: protocol, + Protocol: strings.Join(protocols, ", "), API: "No", Os: runtime.GOOS, OsVer: runtime.GOARCH, diff --git a/graphql/graphql.go b/graphql/graphql.go index 16a74b4037..22cfcf6637 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -1040,10 +1040,6 @@ func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { return hexutil.Big(*price), err } -func (r *Resolver) ProtocolVersion(ctx context.Context) (int32, error) { - return int32(r.backend.ProtocolVersion()), nil -} - func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) { return hexutil.Big(*r.backend.ChainConfig().ChainID), nil } diff --git a/graphql/schema.go b/graphql/schema.go index d7b253f227..1fdc370040 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -310,8 +310,6 @@ const schema string = ` # GasPrice returns the node's estimate of a gas price sufficient to # ensure a transaction is mined in a timely fashion. gasPrice: BigInt! - # ProtocolVersion returns the current wire protocol version number. - protocolVersion: Int! # Syncing returns information on the current synchronisation state. syncing: SyncState # ChainID returns the current chain ID for transaction replay protection. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 030bdb37a4..9ff1781e4a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -64,11 +64,6 @@ func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error) return (*hexutil.Big)(price), err } -// ProtocolVersion returns the current Ethereum protocol version this node supports -func (s *PublicEthereumAPI) ProtocolVersion() hexutil.Uint { - return hexutil.Uint(s.b.ProtocolVersion()) -} - // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not // yet received the latest block headers from its pears. In case it is synchronizing: // - startingBlock: block number this node started to synchronise from @@ -1905,13 +1900,12 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) { // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { - net *p2p.Server - networkVersion uint64 + net *p2p.Server } // NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI { - return &PublicNetAPI{net, networkVersion} +func NewPublicNetAPI(net *p2p.Server) *PublicNetAPI { + return &PublicNetAPI{net} } // Listening returns an indication if the node is listening for network connections. @@ -1924,11 +1918,6 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(s.net.PeerCount()) } -// Version returns the current ethereum protocol version. -func (s *PublicNetAPI) Version() string { - return fmt.Sprintf("%d", s.networkVersion) -} - // checkTxFee is an internal function used to check whether the fee of // the given transaction is _reasonable_(under the cap). func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 10e716bf20..f0a4c0493c 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -41,7 +41,6 @@ import ( type Backend interface { // General Ethereum API Downloader() *downloader.Downloader - ProtocolVersion() int SuggestPrice(ctx context.Context) (*big.Int, error) ChainDb() ethdb.Database AccountManager() *accounts.Manager diff --git a/les/client.go b/les/client.go index 47997a098b..198255dc54 100644 --- a/les/client.go +++ b/les/client.go @@ -171,7 +171,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { leth.blockchain.DisableCheckFreq() } - leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId) + leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer) // Register the backend on the node stack.RegisterAPIs(leth.APIs()) diff --git a/les/enr_entry.go b/les/enr_entry.go index 8f0169bee1..a357f689df 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -35,9 +35,9 @@ func (e lesEntry) ENRKey() string { // setupDiscovery creates the node discovery source for the eth protocol. func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { - if len(eth.config.DiscoveryURLs) == 0 { + if len(eth.config.EthDiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(eth.config.DiscoveryURLs...) + return client.NewIterator(eth.config.EthDiscoveryURLs...) } diff --git a/les/handler_test.go b/les/handler_test.go index 1612caf427..04277f661b 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -51,7 +51,7 @@ func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } func testGetBlockHeaders(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0) + server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0) defer tearDown() bc := server.handler.blockchain diff --git a/les/peer.go b/les/peer.go index 31ee8f7f04..6004af03f5 100644 --- a/les/peer.go +++ b/les/peer.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" lpc "github.com/ethereum/go-ethereum/les/lespay/client" lps "github.com/ethereum/go-ethereum/les/lespay/server" @@ -162,9 +161,17 @@ func (p *peerCommons) String() string { return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version)) } +// PeerInfo represents a short summary of the `eth` sub-protocol metadata known +// about a connected peer. +type PeerInfo struct { + Version int `json:"version"` // Ethereum protocol version negotiated + Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain + Head string `json:"head"` // SHA3 hash of the peer's best owned block +} + // Info gathers and returns a collection of metadata known about a peer. -func (p *peerCommons) Info() *eth.PeerInfo { - return ð.PeerInfo{ +func (p *peerCommons) Info() *PeerInfo { + return &PeerInfo{ Version: p.version, Difficulty: p.Td(), Head: fmt.Sprintf("%x", p.Head()), diff --git a/les/server_handler.go b/les/server_handler.go index f965e3fc64..2316c9c5a4 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -47,7 +47,7 @@ import ( const ( softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - ethVersion = 63 // equivalent eth version for the downloader + ethVersion = 64 // equivalent eth version for the downloader MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request diff --git a/tests/block_test_util.go b/tests/block_test_util.go index be9cdb70cd..c043f0b3eb 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -147,7 +147,7 @@ func (t *BlockTest) Run(snapshotter bool) error { } // Cross-check the snapshot-to-hash against the trie hash if snapshotter { - if err := snapshot.VerifyState(chain.Snapshot(), chain.CurrentBlock().Root()); err != nil { + if err := snapshot.VerifyState(chain.Snapshots(), chain.CurrentBlock().Root()); err != nil { return err } } diff --git a/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 b/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 new file mode 100644 index 0000000000000000000000000000000000000000..31c08bafaff6f7268e7babe1eacae4239d397cf8 GIT binary patch literal 16005 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 b/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 new file mode 100644 index 0000000000000000000000000000000000000000..7bce13ef80e5a67258a89e3a0e4da1c7610e20bb GIT binary patch literal 15965 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 LNurw1vw$0>u6V$y literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 b/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 new file mode 100644 index 0000000000000000000000000000000000000000..613e76a020f5870f2e45687b8bdb97a1b570381a GIT binary patch literal 15976 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

    Pa>BrM`Ck<4mCkN=Y`#_E!Cw7L5Ojm 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 W%DL(vR^B^@dJtVnqMFaMfE%W*cEP3q literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 b/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 new file mode 100644 index 0000000000000000000000000000000000000000..805ad8df77be7ed7802ef8666f0cb5e180b2caaf GIT binary patch literal 14980 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

    Pa>BrM`Ck<4mCkN=Y`#_E!Cw7L5Ojm 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 OdJtVnqMFaMfE%U+^5S^_ literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 b/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 new file mode 100644 index 0000000000000000000000000000000000000000..605acf81c1a9814ac7c3950c4cc47081b4d29f4e GIT binary patch literal 15977 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 X_R6{HA6DKwhk6iQNurw1vw$0>5y--x literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 b/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 new file mode 100644 index 0000000000000000000000000000000000000000..8f32dd775ada5351365dbe3035668944b112046e GIT binary patch literal 16000 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

    tqh-JF4rtl^ z#Vf-l_B@W!l!o}pHSb(%QvZ8f*rEyo1lTq}wmVL6>-RC6?e1WdfXS|uqbJ@7ej@gzPEB9^1t;VP_1T|!dc6w+R zLRxb|i95|KBHO(HoV3Q6;%)6_M9?cHuP^>hx7hIbmJ6_d;;!{kR#>y#V0?Cf>yrq}*(`wnQAks76(1 zdL1kjJ5``P#hy3Fer0V@HL!GI0y_n9|0ObLr!FuH!khzPQMNtP|H8wZ?b!8Uv>1ro^ocr-r{ zl~mj%Ys4ua*1vTX2{>hQbRv)e$O=Wtm^pD?G)rjgam6-#ak%hT-B) z(XoN$rLx|*jaODvxsyf<+LVzjDX4_g%ZfuK?l{526xfj?<-aRuT*U`)|ZB?$bXlK@@b< zD(8)iEiJB|{fzlh5`dz#*NF!i1)^dAOfx!k51LhlV>b)TkGlelepY2M6ih#gTA`Ja z5qF%sJ~M_;AEXh>@_u#GHPxJGP}DbL!GwMz8w=n!0AgvQT=~BeO^~;b5e{QeeHUH2 z`7@Ldqm66YkUoFr_av~9}YbZU8bd9+5fy%GNn__=+K z-N`6R(>zY;B%NHZfjAGCeFPA0I8devL`kqORBoMol?Ky}u1-1nAITO3Tw_%&TBYC% zTho;?`H_80;L#g!z=We}T(OFt&KVNdSccs!p*BQT-KPCf5daG=@LK;y8TaKNntLFU z*-sJFN72;X=s^)cO7BZl&?+Y%0bI1Eu?;WyYT{4!ge$$fl#hEYhJf;rFQ#%P-ZISqwn%Zq$>FTF;kJup&G=>V;-JjY^<&B%J(@^C zZz*hbG7;FJBC;!Job|Wx3!GUlNLjLnhAzta3qyy4Kzo!(T`|jT)mp{W88*VZU?M(* z{TZxbF|(m=#3Qw&ouw`-q5f|p0p+XJn}6`GKmLUqqHd}hnHZ1bS20Dd1r72OPkfBQ>bB!VR=iaBAzNfn^HlPA+D60oEUM$$yz|-RkQAo zonIhpL5hBy`^*3K4_}N~s2HIIuaGuB1g6ijSe5Er7dM)aNge7_f~soF0>{E}PeCez z&Tz*jG<1fC8o4n|L#sWnn>4ITaSQ5Eq3B_m=Q?I-5fGFIQXeAu8<17X0_WzX!bCX~Ofe)P z9fOmVw(x`gs|!GGRl^pz~;9Apg7<|jmzd|t&SRd=PJb{7{u+{1){ zAF~{<6;xN3LPTk>Ic1pi)RILWb5MpRE=$^&rPK_fM(4vpmH)P(?Ldnz$Ys4pns*Gz zJ>&IX_Yy>HlMPK;a(&+8#j|zuJbm1td&zz5GmDIcV==WY5t@mqR7>l>iYuoFuH-65 zB|e=s1Clu{vr+Xo8JS{rnIlJ~rrI{4VWlb==PN)geR*+nzboro!8-0gFe zC*Cd!fz5!VVpCQqc#KqWEnvWMHw?MSXM5l@!<;%7it}7citPjs$`98VYptzfoLt%T zxBI3gZ@Dm~4YZ@7xbqYAZ6p_w(^Ec;#U9|5n|1aQMGXn*gsPml@WNB(nzS`K{m7m( zj>7bDiFou+B!6#2h3HAKlUQEpf*+=0$k##g`oX5Bi{)v^OsRiMr@|H%s6K~qAtlLq z(!NjYqMk13z@GU?$lr#0K9gN^hpnEnGH^`!t<0!!`rNg3IU#KB3$S zq80;`g}JKMO56F~@oTzqYGz#W&UfGo;5B=uTSgZ-fp51Ie?ggd?fW+w`%(m2IQ|F{ zKyYGwfwu>bdpdxT-?qaF~&1)IJ;!wHQC^OM*lVA z`+8gYR@7hqgENFrE_e^jU2;`DYbb_)h&Ku&q8s~KOnM`3>gi)*X9jzd&?!0NQ*w>34LB z*kCW+mBa{uARHJJCCy&NK)Y7FmG`tV{YP ziUeMo)%k@J*MsK*%lRn6F|WiViu;$$I}e>ySgZv6X0?>6g$sqQeav@DROMhci%j^HJ;%DF2Qci0KO~!3@w9 zy4&xL)Y!swLJ)$5$!psWfoP|79d_}+%3=?>Trg0}tvvah|tqj|2DGA$y z(UR7^4lV3&AXRV;F%ie(N2xHLl0f~RZL6dUZis>%Le$^|J#jG%Wpq8oaUr>P-}NF+ zTIhFXPH2j$RZIQGL&3i{%9os!x~o1eOQJyd8yAt%OzAFgOW7ZwKQCJUYf2YM*{r5b znL0QS793pUYp98PCL_T^?%fz`|DT+v5$R38d4l4((oBx7V zuN`+%nmp|Eyjbx<#c@)f}&c2K{ZyivM+*(8xFA27{qcfqx@DdZ=WhXI82yTPO zOZ1bCMRD#dICw<+7=LQ(d##K^(}sgZ9_E#r*X{G;H+gwzUFv^5=Y>}U8ovU*)-}Sx zqN7$s%649$Q%B4{4%+>r3d40VPVid(>w8*(Eg|cewS>3~+wIr1o(D#%sos&Ax7!f3 zdUIzwSq3C(WQDGp#YAVN$^5d&c2|Xj75FAP)ZWZ};x>YbjJt1%X}v`lqAd{= zdH2}zfOx_Lw+eo_t!KA%TkkYU`?RVCr*pJYWU9T9k{(u4;E+F`N`0x8GK=j|=V7D6 zbGn)rJ)manmWHccNu0# zT1kA@4BI}yXRe2dS4A+}SkgQO4hthBydAU;8&bCB>}`Lmib*Xd5FU0qon~LoR8kPo zY1=d34}ViSCK_tm=etL~RW3{JR_j-vlLe9Yn}lb!QLop)E_BcnR-3G6>}p{MT@6qA zEP(zdzP+Z~>n*qwo*=4rdJ)MP(}n32VhakfYF{^$qz#iL z*|wg6e2_gZt(us%#X~6C`?HL9l)|uZA{Dr!Ev1M|@jn-8S`>nCFUL?st$8%UV|noe z=pwmNIvSd#RLSH5N^|@2wCjgk+q0fX%T;vw_tQm1zRG&czEk@hW<@i2To8X1;F7)f zg!?>~By%fAkdYsk@X&p4|v<~*UANOw$E2I-zXj*ve(fm+lEzC8x7c{Gxx zTp?QG$1-lRcPpHJ*P$tb_WK{pnQKR8P>kTnD!H=b)e&rqP3(X_&pbLMHCLZTsh!vH zFVXkLG`kdBhKgu}R}nostsb2y*1<9emh$FVb9dC@xvQV(X+ca(R_@4 zbX#oYLkOeVfhp%XP_@t2lB+0@H~1Bb^?TbeaE__uMCqF&wffBO+LMa7H^l!#A3S%A z-0KkIK0&dpcn4@WlUnr>)x#&Pj|C1z%FSStqE!WVqjXM8ytzn;$nAQzz9B#&xP7Q} zIO;v`YFtlPT=O+3iFsvwl59xBU@{V?5qoOHt`X*LgNqv<%8h1$e+=gc5NfSa9Ui}k zi4uDz+c6K7H;H={lvb(-iJijF3ie26G z8Y?(9R8Zb*fw4=$mn-e-eC3$tUo_tPMaiIGpUr&_QBf%vr>04bqmHJ3+>3zc|JjQc zd`yB3#(v+xDr-q4g7|i0nL6v!)JZNuN$hDHCc`1@!w`#Qw&nb{#B@Nt@g-=is-H+)n!(fh-mDs%Hlo!7>Upw=6AXB=PvxRPGv3VVX(Itl z=8R#E82S|jcdD{DoK9-tP0Y`dSjlJAhs77yssGV8^A$_z)FPa1fu;5!Tb*;t?wK-a zlLk$Qe`d&y)$tgewJk*(Lg1$nSGl^r$CN*iXTzg?y7^+ap z$U%km4X|gZjORQztyHSMb4DaILwpV(?7}l`mojk-Is|VT{J2UR#d}z?!UD^w))zL6 zqO>{PgVJ|m8}vmxhq&SV(+I#a@GKAbIg<0z0VW^X65P`Y{;Pr*ITO6k^e+e7xM z#i_ByGb1N!>nVJC-wER=Kv73GA!nCg)P0^#tbqsLgB9Se_*Z+S6PU}D?_7}u;#()Z z7p0S&KkOV^R=z3a-YDR;Jyp%|)3dP5ikCwga0fZ^(^~yGGsC~W zVKyY$bWjkWo!Ds>%h76^X)#CGp_g&zsX6t$O0X9lh~`KC#&2sTE$b5d#*c!OoYdEZ z&^n##J_g1_<~6#g_B}v;H9aJZle5U(`QaaP zR#{pg3t&pmwW!%L1axG-?m81Q8T#G!f3x$!GdqhItaWOn8YC|GFSU;W#G&e}2gvUN zz;{~N%06cKgF+;Y;%g!tgeT+yRL~>^&xoQ}T7Di;AF*L5P4-WpfL*E&ByBpt9LDxa zqGfEiWGsj_GdqKX@0npXJavwk%5S5?BnGpZWm%PN1W?6Lsb^a>U3IHNnaKINxp>9CRGZaxk3x z=1>YbVZhGAo*@w_SFVBjy~dnE*Q%>8^oJ1LY%$iMK&nZ4cnwo8tH2Qq zw65e%w@u@gxnb*x|ARvk-|{NL($@GXY$l;6OupfpXXxbXpgL;euunTIO{~6oaPH_; zN5=Gl6~|j9VRPpg!Ik!ZGqh0 z2YgCLtWVqgz)^Kaa{prJ80)pfH`kj2@`R4R0| zIcCR18)`fKI>L%$+Uo>hl)=^;USslT0Aka`w{WcEnSf8gjDR9c|iq1XE0(mT4 z)fdu_`<5Qsil%!H1j_e_8W;13&{=4U9H!rCmOI+AHsP9fiv^bW8e`&2)9Ia)T=b4| z3D6ZL4eEZW$Hg^iImbMZK-0Z7205h{ogplrOl@R!n@@qs^GHF${rhW?LxVYCYp)*Z zso~h`ifb>$@BYR2+g_{;CUUC^jaOPv?It^P6VJqA->sR6N@icXlqBs=TgwKJiR;W* z^IM)36M<9Mai+Z_SuhJM%7f0>Z5ux`DNz6)Dsc%qH=vdHz?5f1IN{(d-ZpqDbe)tV zd(gq`L;H;@jo-vN82a+ooy58P$HAk6K$(-=IXj{eDN&$0;2roXFVack9@+B1jklwh zb4QfyK)TvVsI3O+UuoyJQ~DX$2wAKAtT|C9ReBx<*+-RiFMcYPa}9&-L80oN_6dxa z(nkUhGdh$j%wJu1V7O;Buu}qLpcVMiqv8S#i|nE#x z2f}=BSg$bbWhPn^VGG_K&3aC*j>Qyba%1?gUa)&GdcM_%2|>+2oP-%DL(vR^B^@dJtVnqMFaMfE%V?V$37} literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 b/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 new file mode 100644 index 0000000000000000000000000000000000000000..af96210f204e19513ebde336e6db31430d771a7c GIT binary patch literal 1748 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

    tqh-JF4rtl^ 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 literal 0 HcmV?d00001 diff --git a/tests/fuzzers/rangeproof/debug/main.go b/tests/fuzzers/rangeproof/debug/main.go new file mode 100644 index 0000000000..a81c69fea5 --- /dev/null +++ b/tests/fuzzers/rangeproof/debug/main.go @@ -0,0 +1,41 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/rangeproof" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug \n") + fmt.Fprintf(os.Stderr, "Example\n") + fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + rangeproof.Fuzz(data) +} diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go new file mode 100644 index 0000000000..b82a380723 --- /dev/null +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -0,0 +1,218 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rangeproof + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/trie" +) + +type kv struct { + k, v []byte + t bool +} + +type entrySlice []*kv + +func (p entrySlice) Len() int { return len(p) } +func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } +func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +type fuzzer struct { + input io.Reader + exhausted bool +} + +func (f *fuzzer) randBytes(n int) []byte { + r := make([]byte, n) + if _, err := f.input.Read(r); err != nil { + f.exhausted = true + } + return r +} + +func (f *fuzzer) readInt() uint64 { + var x uint64 + if err := binary.Read(f.input, binary.LittleEndian, &x); err != nil { + f.exhausted = true + } + return x +} + +func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) { + + trie := new(trie.Trie) + vals := make(map[string]*kv) + size := f.readInt() + // Fill it with some fluff + for i := byte(0); i < byte(size); i++ { + value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false} + trie.Update(value.k, value.v) + trie.Update(value2.k, value2.v) + vals[string(value.k)] = value + vals[string(value2.k)] = value2 + } + if f.exhausted { + return nil, nil + } + // And now fill with some random + for i := 0; i < n; i++ { + k := f.randBytes(32) + v := f.randBytes(20) + value := &kv{k, v, false} + trie.Update(k, v) + vals[string(k)] = value + if f.exhausted { + return nil, nil + } + } + return trie, vals +} + +func (f *fuzzer) fuzz() int { + maxSize := 200 + tr, vals := f.randomTrie(1 + int(f.readInt())%maxSize) + if f.exhausted { + return 0 // input too short + } + var entries entrySlice + for _, kv := range vals { + entries = append(entries, kv) + } + if len(entries) <= 1 { + return 0 + } + sort.Sort(entries) + + var ok = 0 + for { + start := int(f.readInt() % uint64(len(entries))) + end := 1 + int(f.readInt()%uint64(len(entries)-1)) + testcase := int(f.readInt() % uint64(6)) + index := int(f.readInt() & 0xFFFFFFFF) + index2 := int(f.readInt() & 0xFFFFFFFF) + if f.exhausted { + break + } + proof := memorydb.New() + if err := tr.Prove(entries[start].k, 0, proof); err != nil { + panic(fmt.Sprintf("Failed to prove the first node %v", err)) + } + if err := tr.Prove(entries[end-1].k, 0, proof); err != nil { + panic(fmt.Sprintf("Failed to prove the last node %v", err)) + } + var keys [][]byte + var vals [][]byte + for i := start; i < end; i++ { + keys = append(keys, entries[i].k) + vals = append(vals, entries[i].v) + } + if len(keys) == 0 { + return 0 + } + var first, last = keys[0], keys[len(keys)-1] + testcase %= 6 + switch testcase { + case 0: + // Modified key + keys[index%len(keys)] = f.randBytes(32) // In theory it can't be same + case 1: + // Modified val + vals[index%len(vals)] = f.randBytes(20) // In theory it can't be same + case 2: + // Gapped entry slice + index = index % len(keys) + keys = append(keys[:index], keys[index+1:]...) + vals = append(vals[:index], vals[index+1:]...) + case 3: + // Out of order + index1 := index % len(keys) + index2 := index2 % len(keys) + keys[index1], keys[index2] = keys[index2], keys[index1] + vals[index1], vals[index2] = vals[index2], vals[index1] + case 4: + // Set random key to nil, do nothing + keys[index%len(keys)] = nil + case 5: + // Set random value to nil, deletion + vals[index%len(vals)] = nil + + // Other cases: + // Modify something in the proof db + // add stuff to proof db + // drop stuff from proof db + + } + if f.exhausted { + break + } + ok = 1 + //nodes, subtrie + nodes, subtrie, notary, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) + if err != nil { + if nodes != nil { + panic("err != nil && nodes != nil") + } + if subtrie != nil { + panic("err != nil && subtrie != nil") + } + if notary != nil { + panic("err != nil && notary != nil") + } + if hasMore { + panic("err != nil && hasMore == true") + } + } else { + if nodes == nil { + panic("err == nil && nodes == nil") + } + if subtrie == nil { + panic("err == nil && subtrie == nil") + } + if notary == nil { + panic("err == nil && subtrie == nil") + } + } + } + return ok +} + +// The function must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise; other values are reserved for future use. +func Fuzz(input []byte) int { + if len(input) < 100 { + return 0 + } + r := bytes.NewReader(input) + f := fuzzer{ + input: r, + exhausted: false, + } + return f.fuzz() +} diff --git a/trie/notary.go b/trie/notary.go new file mode 100644 index 0000000000..5a64727aa7 --- /dev/null +++ b/trie/notary.go @@ -0,0 +1,57 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package trie + +import ( + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +// KeyValueNotary tracks which keys have been accessed through a key-value reader +// with te scope of verifying if certain proof datasets are maliciously bloated. +type KeyValueNotary struct { + ethdb.KeyValueReader + reads map[string]struct{} +} + +// NewKeyValueNotary wraps a key-value database with an access notary to track +// which items have bene accessed. +func NewKeyValueNotary(db ethdb.KeyValueReader) *KeyValueNotary { + return &KeyValueNotary{ + KeyValueReader: db, + reads: make(map[string]struct{}), + } +} + +// Get retrieves an item from the underlying database, but also tracks it as an +// accessed slot for bloat checks. +func (k *KeyValueNotary) Get(key []byte) ([]byte, error) { + k.reads[string(key)] = struct{}{} + return k.KeyValueReader.Get(key) +} + +// Accessed returns s snapshot of the original key-value store containing only the +// data accessed through the notary. +func (k *KeyValueNotary) Accessed() ethdb.KeyValueStore { + db := memorydb.New() + for keystr := range k.reads { + key := []byte(keystr) + val, _ := k.KeyValueReader.Get(key) + db.Put(key, val) + } + return db +} diff --git a/trie/proof.go b/trie/proof.go index 2f52438f98..e7102f12b7 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -426,7 +426,7 @@ func hasRightElement(node node, key []byte) bool { // VerifyRangeProof checks whether the given leaf nodes and edge proof // can prove the given trie leaves range is matched with the specific root. -// Besides, the range should be consecutive(no gap inside) and monotonic +// Besides, the range should be consecutive (no gap inside) and monotonic // increasing. // // Note the given proof actually contains two edge proofs. Both of them can @@ -454,96 +454,136 @@ func hasRightElement(node node, key []byte) bool { // // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. -func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (error, bool) { +func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, *Trie, *KeyValueNotary, bool, error) { if len(keys) != len(values) { - return fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)), false + return nil, nil, nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) } // Ensure the received batch is monotonic increasing. for i := 0; i < len(keys)-1; i++ { if bytes.Compare(keys[i], keys[i+1]) >= 0 { - return errors.New("range is not monotonically increasing"), false + return nil, nil, nil, false, errors.New("range is not monotonically increasing") } } + // Create a key-value notary to track which items from the given proof the + // range prover actually needed to verify the data + notary := NewKeyValueNotary(proof) + // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if proof == nil { - emptytrie, err := New(common.Hash{}, NewDatabase(memorydb.New())) + var ( + diskdb = memorydb.New() + triedb = NewDatabase(diskdb) + ) + tr, err := New(common.Hash{}, triedb) if err != nil { - return err, false + return nil, nil, nil, false, err } for index, key := range keys { - emptytrie.TryUpdate(key, values[index]) + tr.TryUpdate(key, values[index]) + } + if tr.Hash() != rootHash { + return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) + } + // Proof seems valid, serialize all the nodes into the database + if _, err := tr.Commit(nil); err != nil { + return nil, nil, nil, false, err } - if emptytrie.Hash() != rootHash { - return fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, emptytrie.Hash()), false + if err := triedb.Commit(rootHash, false, nil); err != nil { + return nil, nil, nil, false, err } - return nil, false // no more element. + return diskdb, tr, notary, false, nil // No more elements } // Special case, there is a provided edge proof but zero key/value // pairs, ensure there are no more accounts / slots in the trie. if len(keys) == 0 { - root, val, err := proofToPath(rootHash, nil, firstKey, proof, true) + root, val, err := proofToPath(rootHash, nil, firstKey, notary, true) if err != nil { - return err, false + return nil, nil, nil, false, err } if val != nil || hasRightElement(root, firstKey) { - return errors.New("more entries available"), false + return nil, nil, nil, false, errors.New("more entries available") } - return nil, false + // Since the entire proof is a single path, we can construct a trie and a + // node database directly out of the inputs, no need to generate them + diskdb := notary.Accessed() + tr := &Trie{ + db: NewDatabase(diskdb), + root: root, + } + return diskdb, tr, notary, hasRightElement(root, firstKey), nil } // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. if len(keys) == 1 && bytes.Equal(firstKey, lastKey) { - root, val, err := proofToPath(rootHash, nil, firstKey, proof, false) + root, val, err := proofToPath(rootHash, nil, firstKey, notary, false) if err != nil { - return err, false + return nil, nil, nil, false, err } if !bytes.Equal(firstKey, keys[0]) { - return errors.New("correct proof but invalid key"), false + return nil, nil, nil, false, errors.New("correct proof but invalid key") } if !bytes.Equal(val, values[0]) { - return errors.New("correct proof but invalid data"), false + return nil, nil, nil, false, errors.New("correct proof but invalid data") + } + // Since the entire proof is a single path, we can construct a trie and a + // node database directly out of the inputs, no need to generate them + diskdb := notary.Accessed() + tr := &Trie{ + db: NewDatabase(diskdb), + root: root, } - return nil, hasRightElement(root, firstKey) + return diskdb, tr, notary, hasRightElement(root, firstKey), nil } // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. if bytes.Compare(firstKey, lastKey) >= 0 { - return errors.New("invalid edge keys"), false + return nil, nil, nil, false, errors.New("invalid edge keys") } // todo(rjl493456442) different length edge keys should be supported if len(firstKey) != len(lastKey) { - return errors.New("inconsistent edge keys"), false + return nil, nil, nil, false, errors.New("inconsistent edge keys") } // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. - root, _, err := proofToPath(rootHash, nil, firstKey, proof, true) + root, _, err := proofToPath(rootHash, nil, firstKey, notary, true) if err != nil { - return err, false + return nil, nil, nil, false, err } // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent // proof is also allowed. - root, _, err = proofToPath(rootHash, root, lastKey, proof, true) + root, _, err = proofToPath(rootHash, root, lastKey, notary, true) if err != nil { - return err, false + return nil, nil, nil, false, err } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. if err := unsetInternal(root, firstKey, lastKey); err != nil { - return err, false + return nil, nil, nil, false, err } - // Rebuild the trie with the leave stream, the shape of trie + // Rebuild the trie with the leaf stream, the shape of trie // should be same with the original one. - newtrie := &Trie{root: root, db: NewDatabase(memorydb.New())} + var ( + diskdb = memorydb.New() + triedb = NewDatabase(diskdb) + ) + tr := &Trie{root: root, db: triedb} for index, key := range keys { - newtrie.TryUpdate(key, values[index]) + tr.TryUpdate(key, values[index]) + } + if tr.Hash() != rootHash { + return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) + } + // Proof seems valid, serialize all the nodes into the database + if _, err := tr.Commit(nil); err != nil { + return nil, nil, nil, false, err } - if newtrie.Hash() != rootHash { - return fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, newtrie.Hash()), false + if err := triedb.Commit(rootHash, false, nil); err != nil { + return nil, nil, nil, false, err } - return nil, hasRightElement(root, keys[len(keys)-1]) + return diskdb, tr, notary, hasRightElement(root, keys[len(keys)-1]), nil } // get returns the child of the given node. Return nil if the diff --git a/trie/proof_test.go b/trie/proof_test.go index 6cdc242d9a..3ecd318886 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -19,6 +19,7 @@ package trie import ( "bytes" crand "crypto/rand" + "encoding/binary" mrand "math/rand" "sort" "testing" @@ -181,7 +182,7 @@ func TestRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -232,7 +233,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -253,7 +254,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatal("Failed to verify whole rang with non-existent edges") } @@ -288,7 +289,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -310,7 +311,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -334,7 +335,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -349,7 +350,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -364,7 +365,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -379,7 +380,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -401,7 +402,7 @@ func TestAllElementsProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -414,7 +415,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -429,7 +430,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -462,7 +463,7 @@ func TestSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -498,7 +499,7 @@ func TestReverseSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -570,7 +571,7 @@ func TestBadRangeProof(t *testing.T) { index = mrand.Intn(end - start) vals[index] = nil } - err, _ := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err == nil { t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1) } @@ -604,7 +605,7 @@ func TestGappedRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err == nil { t.Fatal("expect error, got nil") } @@ -631,7 +632,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -647,7 +648,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - err, _ = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -715,7 +716,7 @@ func TestHasRightElement(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - err, hasMore := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) + _, _, _, hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -748,13 +749,57 @@ func TestEmptyRangeProof(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - err, _ := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) + db, tr, not, _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) if c.err && err == nil { t.Fatalf("Expected error, got nil") } if !c.err && err != nil { t.Fatalf("Expected no error, got %v", err) } + // If no error was returned, ensure the returned trie and database contains + // the entire proof, since there's no value + if !c.err { + if err := tr.Prove(first, 0, memorydb.New()); err != nil { + t.Errorf("returned trie doesn't contain original proof: %v", err) + } + if memdb := db.(*memorydb.Database); memdb.Len() != proof.Len() { + t.Errorf("database entry count mismatch: have %d, want %d", memdb.Len(), proof.Len()) + } + if not == nil { + t.Errorf("missing notary") + } + } + } +} + +// TestBloatedProof tests a malicious proof, where the proof is more or less the +// whole trie. +func TestBloatedProof(t *testing.T) { + // Use a small trie + trie, kvs := nonRandomTrie(100) + var entries entrySlice + for _, kv := range kvs { + entries = append(entries, kv) + } + sort.Sort(entries) + var keys [][]byte + var vals [][]byte + + proof := memorydb.New() + for i, entry := range entries { + trie.Prove(entry.k, 0, proof) + if i == 50 { + keys = append(keys, entry.k) + vals = append(vals, entry.v) + } + } + want := memorydb.New() + trie.Prove(keys[0], 0, want) + trie.Prove(keys[len(keys)-1], 0, want) + + _, _, notary, _, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + if used := notary.Accessed().(*memorydb.Database); used.Len() != want.Len() { + t.Fatalf("notary proof size mismatch: have %d, want %d", used.Len(), want.Len()) } } @@ -858,7 +903,7 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { b.ResetTimer() for i := 0; i < b.N; i++ { - err, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) + _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) if err != nil { b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -889,3 +934,20 @@ func randBytes(n int) []byte { crand.Read(r) return r } + +func nonRandomTrie(n int) (*Trie, map[string]*kv) { + trie := new(Trie) + vals := make(map[string]*kv) + max := uint64(0xffffffffffffffff) + for i := uint64(0); i < uint64(n); i++ { + value := make([]byte, 32) + key := make([]byte, 32) + binary.LittleEndian.PutUint64(key, i) + binary.LittleEndian.PutUint64(value, i-max) + //value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} + elem := &kv{key, value, false} + trie.Update(elem.k, elem.v) + vals[string(elem.k)] = elem + } + return trie, vals +} diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go index 89f61d66d9..979f4748f3 100644 --- a/trie/sync_bloom.go +++ b/trie/sync_bloom.go @@ -125,14 +125,14 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { it.Release() it = database.NewIterator(nil, key) - log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) swap = time.Now() } } it.Release() // Mark the bloom filter inited and return - log.Info("Initialized fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) atomic.StoreUint32(&b.inited, 1) } @@ -162,7 +162,7 @@ func (b *SyncBloom) Close() error { b.pend.Wait() // Wipe the bloom, but mark it "uninited" just in case someone attempts an access - log.Info("Deallocated fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate()) + log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.errorRate()) atomic.StoreUint32(&b.inited, 0) b.bloom = nil diff --git a/trie/trie.go b/trie/trie.go index 6ddbbd78d3..87b72ecf17 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -19,13 +19,13 @@ package trie import ( "bytes" + "errors" "fmt" "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) var ( @@ -159,29 +159,26 @@ func (t *Trie) TryGetNode(path []byte) ([]byte, int, error) { if item == nil { return nil, resolved, nil } - enc, err := rlp.EncodeToBytes(item) - if err != nil { - log.Error("Encoding existing trie node failed", "err", err) - return nil, resolved, err - } - return enc, resolved, err + return item, resolved, err } -func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item node, newnode node, resolved int, err error) { +func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) { // If we reached the requested path, return the current node if pos >= len(path) { - // Don't return collapsed hash nodes though - if _, ok := origNode.(hashNode); !ok { - // Short nodes have expanded keys, compact them before returning - item := origNode - if sn, ok := item.(*shortNode); ok { - item = &shortNode{ - Key: hexToCompact(sn.Key), - Val: sn.Val, - } - } - return item, origNode, 0, nil + // Although we most probably have the original node expanded, encoding + // that into consensus form can be nasty (needs to cascade down) and + // time consuming. Instead, just pull the hash up from disk directly. + var hash hashNode + if node, ok := origNode.(hashNode); ok { + hash = node + } else { + hash, _ = origNode.cache() + } + if hash == nil { + return nil, origNode, 0, errors.New("non-consensus node") } + blob, err := t.db.Node(common.BytesToHash(hash)) + return blob, origNode, 1, err } // Path still needs to be traversed, descend into children switch n := (origNode).(type) { @@ -491,7 +488,7 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { // Hash returns the root hash of the trie. It does not write to the // database and can be used even if the trie doesn't have one. func (t *Trie) Hash() common.Hash { - hash, cached, _ := t.hashRoot(nil) + hash, cached, _ := t.hashRoot() t.root = cached return common.BytesToHash(hash.(hashNode)) } @@ -545,7 +542,7 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { } // hashRoot calculates the root hash of the given trie -func (t *Trie) hashRoot(db *Database) (node, node, error) { +func (t *Trie) hashRoot() (node, node, error) { if t.root == nil { return hashNode(emptyRoot.Bytes()), nil, nil } From 485992979827596d92e622fec25ce68fe1bfd35b Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 14 Dec 2020 14:08:53 +0100 Subject: [PATCH 200/948] cmd/geth: fixed parallelization flaw in account import test (#22002) --- cmd/geth/accountcmd_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index e27adb6916..04f55e9e7a 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -102,6 +102,7 @@ func TestAccountImport(t *testing.T) { }, } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { t.Parallel() importAccountWithExpect(t, test.key, test.output) From 0fe66f8ae41d2ca773f6b01080ddda10bec24377 Mon Sep 17 00:00:00 2001 From: Mr-Leshiy Date: Mon, 14 Dec 2020 15:31:23 +0200 Subject: [PATCH 201/948] eth/protocols/eth: remove magic numbers in test (#21999) --- eth/protocols/eth/handler_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 65c4a10b0a..30beae931b 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -254,8 +254,8 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { headers = append(headers, backend.chain.GetBlockByHash(hash).Header()) } // Send the hash request and verify the response - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } // If the test used number origins, repeat with hashes as the too @@ -263,8 +263,8 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 - p2p.Send(peer.app, 0x03, tt.query) - if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil { + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } } @@ -343,8 +343,8 @@ func testGetBlockBodies(t *testing.T, protocol uint) { } } // Send the hash request and verify the response - p2p.Send(peer.app, 0x05, hashes) - if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil { + p2p.Send(peer.app, GetBlockBodiesMsg, hashes) + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, bodies); err != nil { t.Errorf("test %d: bodies mismatch: %v", i, err) } } @@ -410,13 +410,13 @@ func testGetNodeData(t *testing.T, protocol uint) { } it.Release() - p2p.Send(peer.app, 0x0d, hashes) + p2p.Send(peer.app, GetNodeDataMsg, hashes) msg, err := peer.app.ReadMsg() if err != nil { t.Fatalf("failed to read node data response: %v", err) } - if msg.Code != 0x0e { - t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c) + if msg.Code != NodeDataMsg { + t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg) } var data [][]byte if err := msg.Decode(&data); err != nil { @@ -512,8 +512,8 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) } // Send the hash request and verify the response - p2p.Send(peer.app, 0x0f, hashes) - if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil { + p2p.Send(peer.app, GetReceiptsMsg, hashes) + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, receipts); err != nil { t.Errorf("receipts mismatch: %v", err) } } From 8cde2966af916d85805a47a4350f3567d9e51dbe Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 15 Dec 2020 18:52:51 +0100 Subject: [PATCH 202/948] eth, core: speed up some tests (#22000) --- core/bloombits/matcher_test.go | 6 ++++++ core/bloombits/scheduler_test.go | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go index 91143e525e..923579221f 100644 --- a/core/bloombits/matcher_test.go +++ b/core/bloombits/matcher_test.go @@ -30,6 +30,7 @@ const testSectionSize = 4096 // Tests that wildcard filter rules (nil) can be specified and are handled well. func TestMatcherWildcards(t *testing.T) { + t.Parallel() matcher := NewMatcher(testSectionSize, [][][]byte{ {common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard {common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()}, // Default hash is not a wildcard @@ -56,6 +57,7 @@ func TestMatcherWildcards(t *testing.T) { // Tests the matcher pipeline on a single continuous workflow without interrupts. func TestMatcherContinuous(t *testing.T) { + t.Parallel() testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, false, 75) testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, false, 81) testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, false, 36) @@ -64,6 +66,7 @@ func TestMatcherContinuous(t *testing.T) { // Tests the matcher pipeline on a constantly interrupted and resumed work pattern // with the aim of ensuring data items are requested only once. func TestMatcherIntermittent(t *testing.T) { + t.Parallel() testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, true, 75) testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, true, 81) testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, true, 36) @@ -71,6 +74,7 @@ func TestMatcherIntermittent(t *testing.T) { // Tests the matcher pipeline on random input to hopefully catch anomalies. func TestMatcherRandom(t *testing.T) { + t.Parallel() for i := 0; i < 10; i++ { testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 0, 10000, 0) testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 0, 10000, 0) @@ -84,6 +88,7 @@ func TestMatcherRandom(t *testing.T) { // shifter from a multiple of 8. This is needed to cover an optimisation with // bitset matching https://github.com/ethereum/go-ethereum/issues/15309. func TestMatcherShifted(t *testing.T) { + t.Parallel() // Block 0 always matches in the tests, skip ahead of first 8 blocks with the // start to get a potential zero byte in the matcher bitset. @@ -97,6 +102,7 @@ func TestMatcherShifted(t *testing.T) { // Tests that matching on everything doesn't crash (special case internally). func TestWildcardMatcher(t *testing.T) { + t.Parallel() testMatcherBothModes(t, nil, 0, 10000, 0) } diff --git a/core/bloombits/scheduler_test.go b/core/bloombits/scheduler_test.go index 70772e4ab9..707e8ea11d 100644 --- a/core/bloombits/scheduler_test.go +++ b/core/bloombits/scheduler_test.go @@ -35,6 +35,7 @@ func TestSchedulerMultiClientSingleFetcher(t *testing.T) { testScheduler(t, 10, func TestSchedulerMultiClientMultiFetcher(t *testing.T) { testScheduler(t, 10, 10, 5000) } func testScheduler(t *testing.T, clients int, fetchers int, requests int) { + t.Parallel() f := newScheduler(0) // Create a batch of handler goroutines that respond to bloom bit requests and @@ -88,10 +89,10 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) { } close(in) }() - + b := new(big.Int) for j := 0; j < requests; j++ { bits := <-out - if want := new(big.Int).SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { + if want := b.SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) { t.Errorf("vector %d: delivered content mismatch: have %x, want %x", j, bits, want) } } From c7f2536735a1a47ae63edb488e15ae597dbaf1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 15 Dec 2020 20:12:14 +0100 Subject: [PATCH 203/948] les: les/4 minimalistic version (#21909) * les: allow tx unindexing in les/4 light server mode * les: minor fixes * les: more small fixes * les: add meaningful constants for recentTxIndex handshake field --- cmd/utils/flags.go | 7 +++---- les/odr_requests.go | 2 +- les/peer.go | 32 +++++++++++++++++++++++++++++++- les/protocol.go | 5 +++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0b1695d0a5..c51d7916ca 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1491,10 +1491,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) - // todo(rjl493456442) make it available for les server - // Ancient tx indices pruning is not available for les server now - // since light client relies on the server for transaction status query. - CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, TxLookupLimitFlag) + if (ctx.GlobalIsSet(LegacyLightServFlag.Name) || ctx.GlobalIsSet(LightServeFlag.Name)) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { + log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") + } var ks *keystore.KeyStore if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 { ks = keystores[0].(*keystore.KeyStore) diff --git a/les/odr_requests.go b/les/odr_requests.go index eb1d3602e0..a8cf8f50a9 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -488,7 +488,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 { // CanSend tells if a certain peer is suitable for serving the given request func (r *TxStatusRequest) CanSend(peer *serverPeer) bool { - return peer.version >= lpv2 + return peer.serveTxLookup } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) diff --git a/les/peer.go b/les/peer.go index 6004af03f5..0e2ed52c12 100644 --- a/les/peer.go +++ b/les/peer.go @@ -341,6 +341,7 @@ type serverPeer struct { onlyAnnounce bool // The flag whether the server sends announcement only. chainSince, chainRecent uint64 // The range of chain server peer can serve. stateSince, stateRecent uint64 // The range of state server peer can serve. + serveTxLookup bool // The server peer can serve tx lookups. // Advertised checkpoint fields checkpointNumber uint64 // The block height which the checkpoint is registered. @@ -628,6 +629,18 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter if recv.get("txRelay", nil) != nil { p.onlyAnnounce = true } + if p.version >= lpv4 { + var recentTx uint + if err := recv.get("recentTxLookup", &recentTx); err != nil { + return err + } + // Note: in the current version we only consider the tx index service useful + // if it is unlimited. This can be made configurable in the future. + p.serveTxLookup = recentTx == txIndexUnlimited + } else { + p.serveTxLookup = true + } + if p.onlyAnnounce && !p.trusted { return errResp(ErrUselessPeer, "peer cannot serve requests") } @@ -969,6 +982,20 @@ func (p *clientPeer) freezeClient() { // Handshake executes the les protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error { + recentTx := server.handler.blockchain.TxLookupLimit() + if recentTx != txIndexUnlimited { + if recentTx < blockSafetyMargin { + recentTx = txIndexDisabled + } else { + recentTx -= blockSafetyMargin - txIndexRecentOffset + } + } + if server.config.UltraLightOnlyAnnounce { + recentTx = txIndexDisabled + } + if recentTx != txIndexUnlimited && p.version < lpv4 { + return errors.New("Cannot serve old clients without a complete tx index") + } // Note: clientPeer.headInfo should contain the last head announced to the client by us. // The values announced in the handshake are dummy values for compatibility reasons and should be ignored. p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td} @@ -981,13 +1008,16 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge // If local ethereum node is running in archive mode, advertise ourselves we have // all version state data. Otherwise only recent state is available. - stateRecent := uint64(core.TriesInMemory - 4) + stateRecent := uint64(core.TriesInMemory - blockSafetyMargin) if server.archiveMode { stateRecent = 0 } *lists = (*lists).add("serveRecentState", stateRecent) *lists = (*lists).add("txRelay", nil) } + if p.version >= lpv4 { + *lists = (*lists).add("recentTxLookup", recentTx) + } *lists = (*lists).add("flowControl/BL", server.defParams.BufLimit) *lists = (*lists).add("flowControl/MRR", server.defParams.MinRecharge) diff --git a/les/protocol.go b/les/protocol.go index aebe0f2c04..39d9f5152f 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -50,6 +50,11 @@ var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24} const ( NetworkId = 1 ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message + blockSafetyMargin = 4 // safety margin applied to block ranges specified relative to head block + + txIndexUnlimited = 0 // this value in the "recentTxLookup" handshake field means the entire tx index history is served + txIndexDisabled = 1 // this value means tx index is not served at all + txIndexRecentOffset = 1 // txIndexRecentOffset + N in the handshake field means then tx index of the last N blocks is supported ) // les protocol message codes From 3c46f5570bd674cf49c0113352ff79a4e026a5b8 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 17 Dec 2020 01:20:20 +0100 Subject: [PATCH 204/948] cmd/faucet: sort requests by newest first (#22018) --- cmd/faucet/faucet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index d7927ac491..008cb14296 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -516,12 +516,12 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } continue } - f.reqs = append(f.reqs, &request{ + f.reqs = append([]*request{{ Avatar: avatar, Account: address, Time: time.Now(), Tx: signed, - }) + }}, f.reqs...) timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace From c5a3ffa3638c8fbcb694881efa7e89f69717cdd3 Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 21 Dec 2020 18:54:39 +0800 Subject: [PATCH 205/948] eth/download/statesync : optimize to avoid a copy in state sync hashing (#22035) * eth/download/statesync : state hash sum optimized * go fmt with blank in imports * keccak read arg fix --- eth/downloader/statesync.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 69bd13c2f7..6231588ad2 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -18,13 +18,13 @@ package downloader import ( "fmt" - "hash" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie" @@ -260,9 +260,9 @@ func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []* type stateSync struct { d *Downloader // Downloader instance to access and manage current peerset - root common.Hash // State root currently being synced - sched *trie.Sync // State trie sync scheduler defining the tasks - keccak hash.Hash // Keccak256 hasher to verify deliveries with + root common.Hash // State root currently being synced + sched *trie.Sync // State trie sync scheduler defining the tasks + keccak crypto.KeccakState // Keccak256 hasher to verify deliveries with trieTasks map[common.Hash]*trieTask // Set of trie node tasks currently queued for retrieval codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval @@ -299,7 +299,7 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { d: d, root: root, sched: state.NewStateSync(root, d.stateDB, d.stateBloom), - keccak: sha3.NewLegacyKeccak256(), + keccak: sha3.NewLegacyKeccak256().(crypto.KeccakState), trieTasks: make(map[common.Hash]*trieTask), codeTasks: make(map[common.Hash]*codeTask), deliver: make(chan *stateReq), @@ -590,7 +590,7 @@ func (s *stateSync) processNodeData(blob []byte) (common.Hash, error) { res := trie.SyncResult{Data: blob} s.keccak.Reset() s.keccak.Write(blob) - s.keccak.Sum(res.Hash[:0]) + s.keccak.Read(res.Hash[:]) err := s.sched.Process(res) return res.Hash, err } From 61469cfeaf2a6d0b1598afbaf35dd2d1872604ce Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 21 Dec 2020 22:39:58 +0800 Subject: [PATCH 206/948] eth/downloader: fix typo in comment (#22019) --- eth/downloader/modes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index 8ea7876a1f..3ea14d22d7 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -25,7 +25,7 @@ type SyncMode uint32 const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks FastSync // Quickly download the headers, full sync only at the chain - SnapSync // Download the chain and the state via compact snashots + SnapSync // Download the chain and the state via compact snapshots LightSync // Download only the headers and terminate afterwards ) From 158f72cc0c889739f49dde42210328902073353d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 23 Dec 2020 13:43:22 +0100 Subject: [PATCH 207/948] internal/ethapi: restore net_version RPC method (#22061) During the snap and eth refactor, the net_version rpc call was falsely deprecated. This restores the net_version RPC handler as most eth2 nodes and other software depend on it. --- eth/backend.go | 2 +- internal/ethapi/api.go | 12 +++++++++--- les/client.go | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 987dee6d55..c1732d3ceb 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -230,7 +230,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, err } // Start the RPC service - eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer) + eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId) // Register the backend on the node stack.RegisterAPIs(eth.APIs()) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9ff1781e4a..b424435b50 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1900,12 +1900,13 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) { // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { - net *p2p.Server + net *p2p.Server + networkVersion uint64 } // NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net *p2p.Server) *PublicNetAPI { - return &PublicNetAPI{net} +func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI { + return &PublicNetAPI{net, networkVersion} } // Listening returns an indication if the node is listening for network connections. @@ -1918,6 +1919,11 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(s.net.PeerCount()) } +// Version returns the current ethereum protocol version. +func (s *PublicNetAPI) Version() string { + return fmt.Sprintf("%d", s.networkVersion) +} + // checkTxFee is an internal function used to check whether the fee of // the given transaction is _reasonable_(under the cap). func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { diff --git a/les/client.go b/les/client.go index 198255dc54..47997a098b 100644 --- a/les/client.go +++ b/les/client.go @@ -171,7 +171,7 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { leth.blockchain.DisableCheckFreq() } - leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer) + leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId) // Register the backend on the node stack.RegisterAPIs(leth.APIs()) From b9012a039b8aaf3e68ccc3826bb17d27eaf0fa1c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 23 Dec 2020 17:44:45 +0100 Subject: [PATCH 208/948] common,crypto: move fuzzers out of core (#22029) * common,crypto: move fuzzers out of core * fuzzers: move vm fuzzer out from core * fuzzing: rework cover package logic * fuzzers: lint --- oss-fuzz.sh | 88 ++++++++++++++----- .../fuzzers}/bitutil/compress_fuzz.go | 14 +-- {crypto => tests/fuzzers}/bn256/bn256_fuzz.go | 6 +- .../fuzzers/runtime/runtime_fuzz.go | 14 +-- 4 files changed, 84 insertions(+), 38 deletions(-) rename {common => tests/fuzzers}/bitutil/compress_fuzz.go (84%) rename {crypto => tests/fuzzers}/bn256/bn256_fuzz.go (94%) rename core/vm/runtime/fuzz.go => tests/fuzzers/runtime/runtime_fuzz.go (82%) diff --git a/oss-fuzz.sh b/oss-fuzz.sh index e060ea88e1..5919b2077f 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -26,38 +26,80 @@ # $CFLAGS, $CXXFLAGS C and C++ compiler flags. # $LIB_FUZZING_ENGINE C++ compiler argument to link fuzz target against the prebuilt engine library (e.g. libFuzzer). +# This sets the -coverpgk for the coverage report when the corpus is executed through go test +coverpkg="github.com/ethereum/go-ethereum/..." + +function coverbuild { + path=$1 + function=$2 + fuzzer=$3 + tags="" + + if [[ $# -eq 4 ]]; then + tags="-tags $4" + fi + cd $path + fuzzed_package=`pwd | rev | cut -d'/' -f 1 | rev` + cp $GOPATH/ossfuzz_coverage_runner.go ./"${function,,}"_test.go + sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go + sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go + sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go + +cat << DOG > $OUT/$fuzzer +#/bin/sh + + cd $OUT/$path + go test -run Test${function}Corpus -v $tags -coverprofile \$1 -coverpkg $coverpkg + +DOG + + chmod +x $OUT/$fuzzer + #echo "Built script $OUT/$fuzzer" + #cat $OUT/$fuzzer + cd - +} + function compile_fuzzer { - path=$SRC/go-ethereum/$1 + # Inputs: + # $1: The package to fuzz, within go-ethereum + # $2: The name of the fuzzing function + # $3: The name to give to the final fuzzing-binary + + path=$GOPATH/src/github.com/ethereum/go-ethereum/$1 func=$2 fuzzer=$3 - corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" - echo "Building $fuzzer (expecting corpus at $corpusfile)" - (cd $path && \ + + echo "Building $fuzzer" + + # Do a coverage-build or a regular build + if [[ $SANITIZER = *coverage* ]]; then + coverbuild $path $func $fuzzer $coverpkg + else + (cd $path && \ go-fuzz -func $func -o $WORK/$fuzzer.a . && \ - echo "First stage built OK" && \ - $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer && \ - echo "Second stage built ok" ) - - ## Check if there exists a seed corpus file - if [ -f $corpusfile ] - then - cp $corpusfile $OUT/ - echo "Found seed corpus: $corpusfile" - fi + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer) + fi + + ## Check if there exists a seed corpus file + corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" + if [ -f $corpusfile ] + then + cp $corpusfile $OUT/ + echo "Found seed corpus: $corpusfile" + fi } -compile_fuzzer common/bitutil Fuzz fuzzBitutilCompress -compile_fuzzer crypto/bn256 FuzzAdd fuzzBn256Add -compile_fuzzer crypto/bn256 FuzzMul fuzzBn256Mul -compile_fuzzer crypto/bn256 FuzzPair fuzzBn256Pair -compile_fuzzer core/vm/runtime Fuzz fuzzVmRuntime -compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b +compile_fuzzer tests/fuzzers/bitutil Fuzz fuzzBitutilCompress +compile_fuzzer tests/fuzzers/bn256 FuzzAdd fuzzBn256Add +compile_fuzzer tests/fuzzers/bn256 FuzzMul fuzzBn256Mul +compile_fuzzer tests/fuzzers/bn256 FuzzPair fuzzBn256Pair +compile_fuzzer tests/fuzzers/runtime Fuzz fuzzVmRuntime compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie -compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty +compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul @@ -69,6 +111,10 @@ compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1 compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 +#TODO: move this to tests/fuzzers, if possible +compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b + + # This doesn't work very well @TODO #compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi diff --git a/common/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go similarity index 84% rename from common/bitutil/compress_fuzz.go rename to tests/fuzzers/bitutil/compress_fuzz.go index 714bbcd131..0b77b2dc9e 100644 --- a/common/bitutil/compress_fuzz.go +++ b/tests/fuzzers/bitutil/compress_fuzz.go @@ -14,11 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build gofuzz - package bitutil -import "bytes" +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common/bitutil" +) // Fuzz implements a go-fuzz fuzzer method to test various encoding method // invocations. @@ -35,7 +37,7 @@ func Fuzz(data []byte) int { // fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and // decoding algorithm. func fuzzEncode(data []byte) int { - proc, _ := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) + proc, _ := bitutil.DecompressBytes(bitutil.CompressBytes(data), len(data)) if !bytes.Equal(data, proc) { panic("content mismatch") } @@ -45,11 +47,11 @@ func fuzzEncode(data []byte) int { // fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and // reencoding algorithm. func fuzzDecode(data []byte) int { - blob, err := bitsetDecodeBytes(data, 1024) + blob, err := bitutil.DecompressBytes(data, 1024) if err != nil { return 0 } - if comp := bitsetEncodeBytes(blob); !bytes.Equal(comp, data) { + if comp := bitutil.CompressBytes(blob); !bytes.Equal(comp, data) { panic("content mismatch") } return 1 diff --git a/crypto/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go similarity index 94% rename from crypto/bn256/bn256_fuzz.go rename to tests/fuzzers/bn256/bn256_fuzz.go index b34043487f..477fe0a160 100644 --- a/crypto/bn256/bn256_fuzz.go +++ b/tests/fuzzers/bn256/bn256_fuzz.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. -// +build gofuzz - package bn256 import ( @@ -24,7 +22,7 @@ func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1) { } xg := new(google.G1) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) } return xc, xg } @@ -37,7 +35,7 @@ func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2) { } xg := new(google.G2) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) } return xc, xg } diff --git a/core/vm/runtime/fuzz.go b/tests/fuzzers/runtime/runtime_fuzz.go similarity index 82% rename from core/vm/runtime/fuzz.go rename to tests/fuzzers/runtime/runtime_fuzz.go index cb9ff08b5b..9b96045752 100644 --- a/core/vm/runtime/fuzz.go +++ b/tests/fuzzers/runtime/runtime_fuzz.go @@ -14,23 +14,23 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build gofuzz - package runtime +import ( + "github.com/ethereum/go-ethereum/core/vm/runtime" +) + // Fuzz is the basic entry point for the go-fuzz tool // // This returns 1 for valid parsable/runable code, 0 // for invalid opcode. func Fuzz(input []byte) int { - _, _, err := Execute(input, input, &Config{ - GasLimit: 3000000, + _, _, err := runtime.Execute(input, input, &runtime.Config{ + GasLimit: 12000000, }) - // invalid opcode - if err != nil && len(err.Error()) > 6 && string(err.Error()[:7]) == "invalid" { + if err != nil && len(err.Error()) > 6 && err.Error()[:7] == "invalid" { return 0 } - return 1 } From 25c0bd9b43da2c5b64e17b3847a8490d6abbe5c1 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sun, 27 Dec 2020 17:56:50 +0000 Subject: [PATCH 209/948] README.md: update Travis badge (#22079) The legacy dot-org URL was displaying a message about the repository having migrated to the dot-com service, which now covers open-source projects as well. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ddb885dfdc..19357355a8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Official Golang implementation of the Ethereum protocol. https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667 )](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) [![Go Report Card](https://goreportcard.com/badge/github.com/ethereum/go-ethereum)](https://goreportcard.com/report/github.com/ethereum/go-ethereum) -[![Travis](https://travis-ci.org/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.org/ethereum/go-ethereum) +[![Travis](https://travis-ci.com/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.com/ethereum/go-ethereum) [![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/nthXNEv) Automated builds are available for stable releases and the unstable master branch. Binary From 9c6b5b904a0ea050a0ffda7cf7b60678b457783d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Sun, 27 Dec 2020 21:57:19 +0100 Subject: [PATCH 210/948] eth, eth/tracers: include intrinsic gas in calltracer, expose for all tracers (#22038) * eth/tracers: share tx gas price with js tracer * eth/tracers: use `go generate` * eth/tracers: try with another version of go-bindata * eth/tracers: export txGas * eth, eth/tracers: pass intrinsic gas to js tracers eth/tracers: include tx gas in tracers usedGas eth/tracers: fix prestate tracer's sender balance eth/tracers: rm unnecessary import eth/tracers: pass intrinsicGas separately to tracer eth/tracers: fix tests broken by lack of txdata eth, eth/tracers: minor fix * eth/tracers: regenerate assets + unexport test-struct + add testcase * eth/tracers: simplify tests + make table-driven Co-authored-by: Guillaume Ballet Co-authored-by: Martin Holst Swende --- eth/api_tracer.go | 2 +- eth/tracers/internal/tracers/assets.go | 6 +- .../internal/tracers/prestate_tracer.js | 2 +- eth/tracers/tracer.go | 21 ++- eth/tracers/tracer_test.go | 151 ++++++++---------- eth/tracers/tracers_test.go | 4 +- 6 files changed, 92 insertions(+), 94 deletions(-) diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 2497c8d95e..c68b762255 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -794,7 +794,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } } // Constuct the JavaScript tracer to execute with - if tracer, err = tracers.New(*config.Tracer); err != nil { + if tracer, err = tracers.New(*config.Tracer, txContext); err != nil { return nil, err } // Handle timeouts and RPC cancellations diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go index 432398ebb5..8b89ad4182 100644 --- a/eth/tracers/internal/tracers/assets.go +++ b/eth/tracers/internal/tracers/assets.go @@ -6,7 +6,7 @@ // evmdis_tracer.js (4.195kB) // noop_tracer.js (1.271kB) // opcount_tracer.js (1.372kB) -// prestate_tracer.js (4.234kB) +// prestate_tracer.js (4.287kB) // trigram_tracer.js (1.788kB) // unigram_tracer.js (1.51kB) @@ -197,7 +197,7 @@ func opcount_tracerJs() (*asset, error) { return a, nil } -var _prestate_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xdd\x6f\xdb\x38\x12\x7f\x96\xfe\x8a\x41\x5f\x6c\xa3\xae\xdc\x64\x81\x3d\xc0\xb9\x1c\xa0\xba\x6e\x1b\x20\x9b\x04\xb6\x7b\xb9\xdc\x62\x1f\x28\x72\x24\x73\x4d\x93\x02\x49\xd9\xf1\x15\xf9\xdf\x0f\x43\x7d\xf8\xa3\x49\xd3\xdd\x37\x9b\x1c\xfe\xe6\xfb\x37\xa3\xd1\x08\x26\xa6\xdc\x59\x59\x2c\x3d\x9c\xbf\x3f\xfb\x07\x2c\x96\x08\x85\x79\x87\x7e\x89\x16\xab\x35\xa4\x95\x5f\x1a\xeb\xe2\xd1\x08\x16\x4b\xe9\x20\x97\x0a\x41\x3a\x28\x99\xf5\x60\x72\xf0\x27\xf2\x4a\x66\x96\xd9\x5d\x12\x8f\x46\xf5\x9b\x67\xaf\x09\x21\xb7\x88\xe0\x4c\xee\xb7\xcc\xe2\x18\x76\xa6\x02\xce\x34\x58\x14\xd2\x79\x2b\xb3\xca\x23\x48\x0f\x4c\x8b\x91\xb1\xb0\x36\x42\xe6\x3b\x82\x94\x1e\x2a\x2d\xd0\x06\xd5\x1e\xed\xda\xb5\x76\x7c\xbe\xf9\x0a\xd7\xe8\x1c\x5a\xf8\x8c\x1a\x2d\x53\x70\x57\x65\x4a\x72\xb8\x96\x1c\xb5\x43\x60\x0e\x4a\x3a\x71\x4b\x14\x90\x05\x38\x7a\xf8\x89\x4c\x99\x37\xa6\xc0\x27\x53\x69\xc1\xbc\x34\x7a\x08\x28\xc9\x72\xd8\xa0\x75\xd2\x68\xf8\xa5\x55\xd5\x00\x0e\xc1\x58\x02\xe9\x33\x4f\x0e\x58\x30\x25\xbd\x1b\x00\xd3\x3b\x50\xcc\xef\x9f\xfe\x44\x40\xf6\x7e\x0b\x90\x3a\xa8\x59\x9a\x12\xc1\x2f\x99\x27\xaf\xb7\x52\x29\xc8\x10\x2a\x87\x79\xa5\x86\x84\x96\x55\x1e\xee\xaf\x16\x5f\x6e\xbf\x2e\x20\xbd\x79\x80\xfb\x74\x36\x4b\x6f\x16\x0f\x17\xb0\x95\x7e\x69\x2a\x0f\xb8\xc1\x1a\x4a\xae\x4b\x25\x51\xc0\x96\x59\xcb\xb4\xdf\x81\xc9\x09\xe1\xb7\xe9\x6c\xf2\x25\xbd\x59\xa4\x1f\xae\xae\xaf\x16\x0f\x60\x2c\x7c\xba\x5a\xdc\x4c\xe7\x73\xf8\x74\x3b\x83\x14\xee\xd2\xd9\xe2\x6a\xf2\xf5\x3a\x9d\xc1\xdd\xd7\xd9\xdd\xed\x7c\x9a\xc0\x1c\xc9\x2a\xa4\xf7\xaf\xc7\x3c\x0f\xd9\xb3\x08\x02\x3d\x93\xca\xb5\x91\x78\x30\x15\xb8\xa5\xa9\x94\x80\x25\xdb\x20\x58\xe4\x28\x37\x28\x80\x01\x37\xe5\xee\xa7\x93\x4a\x58\x4c\x19\x5d\x04\x9f\x5f\x2c\x48\xb8\xca\x41\x1b\x3f\x04\x87\x08\xff\x5c\x7a\x5f\x8e\x47\xa3\xed\x76\x9b\x14\xba\x4a\x8c\x2d\x46\xaa\x86\x73\xa3\x7f\x25\x31\x61\x96\x16\x9d\x67\x1e\x17\x96\x71\xb4\x60\x2a\x5f\x56\xde\x81\xab\xf2\x5c\x72\x89\xda\x83\xd4\xb9\xb1\xeb\x50\x29\xe0\x0d\x70\x8b\xcc\x23\x30\x50\x86\x33\x05\xf8\x88\xbc\x0a\x77\x75\xa4\x43\xb9\x5a\xa6\x1d\xe3\xe1\x34\xb7\x66\x4d\xbe\x56\xce\xd3\x0f\xe7\x70\x9d\x29\x14\x50\xa0\x46\x27\x1d\x64\xca\xf0\x55\x12\x7f\x8b\xa3\x03\x63\xa8\x4e\x82\x87\x8d\x50\xa8\x8d\x2d\xf6\x2c\x42\x56\x49\x25\xa4\x2e\x92\x38\x6a\xa5\xc7\xa0\x2b\xa5\x86\x71\x80\x50\xc6\xac\xaa\x32\xe5\xdc\x54\xc1\xf6\x3f\x91\xfb\x1a\xcc\x95\xc8\x65\x4e\xc5\xc1\xba\x5b\x6f\xc2\x55\xa7\xd7\x64\x24\x9f\xc4\xd1\x11\xcc\x18\xf2\x4a\x07\x77\xfa\x4c\x08\x3b\x04\x91\x0d\xbe\xc5\x51\xb4\x61\x96\xb0\xe0\x12\xbc\xf9\x82\x8f\xe1\x72\x70\x11\x47\x91\xcc\xa1\xef\x97\xd2\x25\x2d\xf0\xef\x8c\xf3\x3f\xe0\xf2\xf2\x32\x34\x75\x2e\x35\x8a\x01\x10\x44\xf4\x9c\x58\x7d\x13\x65\x4c\x31\xcd\x71\x0c\xbd\xf7\x8f\x3d\x78\x0b\x22\x4b\x0a\xf4\x1f\xea\xd3\x5a\x59\xe2\xcd\xdc\x5b\xa9\x8b\xfe\xd9\xaf\x83\x61\x78\xa5\x4d\x78\x03\x8d\xf8\x8d\xe9\x84\xeb\x7b\x6e\x44\xb8\x6e\x6c\xae\xa5\x26\x46\x34\x42\x8d\x94\xf3\xc6\xb2\x02\xc7\xf0\xed\x89\xfe\x3f\x91\x57\x4f\x71\xf4\x74\x14\xe5\x79\x2d\xf4\x42\x94\x1b\x08\x40\xed\x6d\x57\xe7\x85\xa4\x4e\x3d\x4c\x40\xc0\xfb\x51\x12\xe6\xad\x29\x27\x49\x58\xe1\xee\xf5\x4c\xd0\x85\x14\x8f\xdd\xc5\x0a\x77\x83\x8b\xf8\xc5\x14\x25\x8d\xd1\xbf\x4b\xf1\xf8\xb3\xf9\x3a\x79\x73\x14\xd7\x39\x49\xed\xed\x1d\x0c\x4e\xe2\x68\xd1\x55\xca\x53\xb9\x4b\xbd\x31\x2b\x22\xae\x25\xc5\x47\xa9\x10\x12\x53\x52\xb6\x5c\xcd\x1c\x19\xa2\x06\xe9\xd1\x32\xa2\x4e\xb3\x41\x4b\x53\x03\x2c\xfa\xca\x6a\xd7\x85\x31\x97\x9a\xa9\x16\xb8\x89\xba\xb7\x8c\xd7\x3d\x53\x9f\x1f\xc4\x92\xfb\xc7\x10\xc5\xe0\xdd\x68\x04\xa9\x07\x72\x11\x4a\x23\xb5\x1f\xc2\x16\x41\x23\x0a\x6a\x7c\x81\xa2\xe2\x3e\xe0\xf5\x36\x4c\x55\xd8\xab\x9b\x9b\x28\x32\x3c\x35\x15\x4d\x82\x83\xe6\x1f\x06\x03\xd7\x66\x13\x46\x5c\xc6\xf8\x0a\x9a\x86\x33\x56\x16\x52\xc7\x4d\x38\x8f\x9a\x8d\x2c\x4a\x08\x38\x98\x15\x72\x45\x49\xa4\x93\x0f\x4c\xc1\x25\x64\xb2\xb8\xd2\xfe\x24\x79\x75\xd0\xdb\xa7\x83\x3f\x92\xa6\x79\x12\x47\x84\xd7\x3f\x1f\x0c\xe1\xec\xd7\xae\x22\xbc\x21\x28\x78\x1d\xcc\x9b\x97\xa1\xe2\xd3\x62\x78\xfe\x59\x50\x43\x1d\xfc\x36\x68\x4d\x5c\x95\x51\x3a\x6a\x3f\x43\x1c\x8f\xbb\xf8\xe2\x07\xb8\xc7\xbe\xb5\xb8\x4d\x68\x12\x26\xc4\xcb\xa0\x75\x8a\x3e\x22\xb7\xb8\x26\x56\xa7\x2c\x70\xa6\x14\xda\x9e\x83\xc0\x19\xc3\xa6\x9c\x42\xbe\x70\x5d\xfa\x5d\xcb\xf5\x9e\xd9\x02\xbd\x7b\xdd\xb0\x80\xf3\xee\x5d\x4b\x81\x21\x14\xbb\x12\xe1\xf2\x12\x7a\x93\xd9\x34\x5d\x4c\x7b\x4d\x1b\x8d\x46\x70\x8f\x61\x13\xca\x94\xcc\x84\xda\x81\x40\x85\x1e\x6b\xbb\x8c\x0e\x21\xea\x28\x61\x48\x2b\x0d\x2d\x1b\xf8\x28\x9d\x97\xba\x80\x9a\x29\xb6\x34\x57\x1b\xb8\xd0\x23\x9c\x55\x8e\xaa\xf5\x64\x08\x79\x43\x1b\x85\x45\xe2\x15\xe2\xff\xd0\x6e\x4c\xc9\x6e\x03\xc9\xa5\x75\x1e\x4a\xc5\x38\x26\x84\xd7\x19\xf3\x72\x7e\x9b\x4e\x26\xd5\xb3\xd0\x82\x01\x68\x3f\xe0\x98\xa2\x01\x49\xea\x1d\xf4\x5b\x8c\x41\x1c\x45\xb6\x95\x3e\xc0\xbe\xd8\x53\x82\xf3\x58\x1e\x12\x02\x2d\x16\xb8\x41\xa2\xd0\xc0\x06\xf5\x30\x24\x5d\xff\xfe\xad\x99\xbe\xe8\x92\x38\xa2\x77\x07\x7d\xad\x4c\x71\xdc\xd7\xa2\x0e\x0b\xaf\xac\xa5\xfc\x77\x14\x9c\x53\x8f\xff\x59\x39\x4f\x31\xb5\x14\x9e\x86\x2d\x9e\x23\xc9\x40\x89\x34\x6d\x07\xdf\x93\x21\xcd\xad\x30\x27\x48\x5d\x33\xa5\xea\x6d\xae\x34\x1e\xb5\x97\x4c\xa9\x1d\xe5\x61\x6b\x69\x8d\xa1\xc5\x65\x08\x4e\x92\x54\x60\x9c\x20\x2a\x35\x57\x95\xa8\xcb\x20\xd4\x71\x83\xe7\x82\xcd\xc7\xfb\xcf\x1a\x9d\x63\x05\x26\x54\x49\xb9\x7c\x6c\x36\x48\x0d\xbd\x9a\xe4\xfa\x83\x5e\xd2\x19\x79\x4c\x31\xca\x14\x49\x5b\x64\x44\xd3\xa9\x10\x16\x9d\xeb\x0f\x1a\xce\xe9\x32\x7b\xbf\x44\x4d\xc1\x07\x8d\x5b\xe8\x56\x13\xc6\x39\xad\x6a\x62\x08\x4c\x08\xa2\xb6\x93\x35\x22\x8e\x22\xb7\x95\x9e\x2f\x21\x68\x32\xe5\xbe\x17\x07\x4d\xfd\x73\xe6\x10\xde\x4c\xff\xb3\x98\xdc\x7e\x9c\x4e\x6e\xef\x1e\xde\x8c\xe1\xe8\x6c\x7e\xf5\xdf\x69\x77\xf6\x21\xbd\x4e\x6f\x26\xd3\x37\xe3\x30\x9b\x9f\x71\xc8\x9b\xd6\x05\x52\xe8\x3c\xe3\xab\xa4\x44\x5c\xf5\xdf\x1f\xf3\xc0\xde\xc1\x28\xca\x2c\xb2\xd5\xc5\xde\x98\xba\x41\x1b\x1d\x2d\xe5\xc2\x25\xbc\x18\xac\x8b\x97\xad\x99\x34\xf2\xfd\x96\xc8\xf7\xab\x48\xa0\x8a\xd7\xed\x38\xff\xcb\x86\x84\xde\x61\x7c\x35\x06\xc7\x14\x6d\xc0\xf2\x7f\xf4\xe5\x92\xe7\x0e\xfd\x10\x50\x0b\xb3\x25\xe6\xeb\x50\xeb\x9b\x06\xf7\x20\x64\x67\x83\x9a\x41\x6f\xf3\xfe\xa0\x13\x26\xb0\xef\x45\xcf\x9f\x13\x45\x2d\xe0\xb2\x45\x7f\x1b\x5e\xbe\x1e\xa8\xf3\x26\x52\x27\x0a\x7e\x39\xd9\xf0\xc2\xfd\x1a\xd7\xc6\xee\x9a\x71\x74\xe0\xdf\x8f\xa3\x9a\x5e\x5f\x77\xf5\x44\x7f\xa8\xc8\xba\x83\x8f\xd3\xeb\xe9\xe7\x74\x31\x3d\x92\x9a\x2f\xd2\xc5\xd5\xa4\x3e\xfa\xcb\x85\x77\xf6\xd3\x85\xd7\x9b\xcf\x17\xb7\xb3\x69\x6f\xdc\xfc\xbb\xbe\x4d\x3f\xf6\xbe\x53\xd8\x6c\x81\x3f\x6a\x5d\x6f\xee\x8d\x15\x7f\xa7\x03\x0e\x36\xb2\x9c\x3d\xb7\x90\x05\x6a\xe7\xbe\x3a\xf9\xe0\x01\xa6\x5b\x56\xce\xeb\x8f\xbe\x28\xbc\x7f\x96\x87\x9f\xe2\xa7\xf8\xff\x01\x00\x00\xff\xff\xb1\x28\x85\x2a\x8a\x10\x00\x00") +var _prestate_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xdd\x6f\xdb\x38\x12\x7f\xb6\xfe\x8a\x41\x5f\x6c\x5d\x5d\xb9\xcd\x02\x7b\x80\x73\x39\x40\x75\xdd\x36\x40\x36\x09\x6c\xe7\x72\xb9\xc5\x3e\x50\xe4\x48\xe6\x9a\x26\x05\x92\xb2\xe3\x2b\xf2\xbf\x1f\x86\xfa\xf0\x47\x93\xa6\x7b\x6f\x16\x39\xfc\xcd\xf7\x6f\xc6\xa3\x11\x4c\x4c\xb9\xb3\xb2\x58\x7a\x38\x7b\xff\xe1\xef\xb0\x58\x22\x14\xe6\x1d\xfa\x25\x5a\xac\xd6\x90\x56\x7e\x69\xac\x8b\x46\x23\x58\x2c\xa5\x83\x5c\x2a\x04\xe9\xa0\x64\xd6\x83\xc9\xc1\x9f\xc8\x2b\x99\x59\x66\x77\x49\x34\x1a\xd5\x6f\x9e\xbd\x26\x84\xdc\x22\x82\x33\xb9\xdf\x32\x8b\x63\xd8\x99\x0a\x38\xd3\x60\x51\x48\xe7\xad\xcc\x2a\x8f\x20\x3d\x30\x2d\x46\xc6\xc2\xda\x08\x99\xef\x08\x52\x7a\xa8\xb4\x40\x1b\x54\x7b\xb4\x6b\xd7\xda\xf1\xe5\xfa\x0e\xae\xd0\x39\xb4\xf0\x05\x35\x5a\xa6\xe0\xb6\xca\x94\xe4\x70\x25\x39\x6a\x87\xc0\x1c\x94\x74\xe2\x96\x28\x20\x0b\x70\xf4\xf0\x33\x99\x32\x6f\x4c\x81\xcf\xa6\xd2\x82\x79\x69\xf4\x10\x50\x92\xe5\xb0\x41\xeb\xa4\xd1\xf0\x4b\xab\xaa\x01\x1c\x82\xb1\x04\x32\x60\x9e\x1c\xb0\x60\x4a\x7a\x17\x03\xd3\x3b\x50\xcc\xef\x9f\xfe\x44\x40\xf6\x7e\x0b\x90\x3a\xa8\x59\x9a\x12\xc1\x2f\x99\x27\xaf\xb7\x52\x29\xc8\x10\x2a\x87\x79\xa5\x86\x84\x96\x55\x1e\xee\x2f\x17\x5f\x6f\xee\x16\x90\x5e\x3f\xc0\x7d\x3a\x9b\xa5\xd7\x8b\x87\x73\xd8\x4a\xbf\x34\x95\x07\xdc\x60\x0d\x25\xd7\xa5\x92\x28\x60\xcb\xac\x65\xda\xef\xc0\xe4\x84\xf0\xdb\x74\x36\xf9\x9a\x5e\x2f\xd2\x8f\x97\x57\x97\x8b\x07\x30\x16\x3e\x5f\x2e\xae\xa7\xf3\x39\x7c\xbe\x99\x41\x0a\xb7\xe9\x6c\x71\x39\xb9\xbb\x4a\x67\x70\x7b\x37\xbb\xbd\x99\x4f\x13\x98\x23\x59\x85\xf4\xfe\xf5\x98\xe7\x21\x7b\x16\x41\xa0\x67\x52\xb9\x36\x12\x0f\xa6\x02\xb7\x34\x95\x12\xb0\x64\x1b\x04\x8b\x1c\xe5\x06\x05\x30\xe0\xa6\xdc\xfd\x74\x52\x09\x8b\x29\xa3\x8b\xe0\xf3\x8b\x05\x09\x97\x39\x68\xe3\x87\xe0\x10\xe1\x1f\x4b\xef\xcb\xf1\x68\xb4\xdd\x6e\x93\x42\x57\x89\xb1\xc5\x48\xd5\x70\x6e\xf4\xcf\x24\x22\xcc\xd2\xa2\xf3\xcc\xe3\xc2\x32\x8e\x16\x4c\xe5\xcb\xca\x3b\x70\x55\x9e\x4b\x2e\x51\x7b\x90\x3a\x37\x76\x1d\x2a\x05\xbc\x01\x6e\x91\x79\x04\x06\xca\x70\xa6\x00\x1f\x91\x57\xe1\xae\x8e\x74\x28\x57\xcb\xb4\x63\x3c\x9c\xe6\xd6\xac\xc9\xd7\xca\x79\xfa\xe1\x1c\xae\x33\x85\x02\x0a\xd4\xe8\xa4\x83\x4c\x19\xbe\x4a\xa2\x6f\x51\xef\xc0\x18\xaa\x93\xe0\x61\x23\x14\x6a\x63\x8b\x7d\x8b\x90\x55\x52\x09\xa9\x8b\x24\xea\xb5\xd2\x63\xd0\x95\x52\xc3\x28\x40\x28\x63\x56\x55\x99\x72\x6e\xaa\x60\xfb\x9f\xc8\x7d\x0d\xe6\x4a\xe4\x32\xa7\xe2\x60\xdd\xad\x37\xe1\xaa\xd3\x6b\x32\x92\x4f\xa2\xde\x11\xcc\x18\xf2\x4a\x07\x77\x06\x4c\x08\x3b\x04\x91\xc5\xdf\xa2\x5e\x6f\xc3\x2c\x61\xc1\x05\x78\xf3\x15\x1f\xc3\x65\x7c\x1e\xf5\x7a\x32\x87\x81\x5f\x4a\x97\xb4\xc0\xbf\x33\xce\xff\x80\x8b\x8b\x8b\xd0\xd4\xb9\xd4\x28\x62\x20\x88\xde\x73\x62\xf5\x4d\x2f\x63\x8a\x69\x8e\x63\xe8\xbf\x7f\xec\xc3\x5b\x10\x59\x52\xa0\xff\x58\x9f\xd6\xca\x12\x6f\xe6\xde\x4a\x5d\x0c\x3e\xfc\x1a\x0f\xc3\x2b\x6d\xc2\x1b\x68\xc4\xaf\x4d\x27\x5c\xdf\x73\x23\xc2\x75\x63\x73\x2d\x35\x31\xa2\x11\x6a\xa4\x9c\x37\x96\x15\x38\x86\x6f\x4f\xf4\xfd\x44\x5e\x3d\x45\xbd\xa7\xa3\x28\xcf\x6b\xa1\x17\xa2\xdc\x40\x00\x6a\x6f\xbb\x3a\x2f\x24\x75\xea\x61\x02\x02\xde\x8f\x92\x30\x6f\x4d\x39\x49\xc2\x0a\x77\xaf\x67\x82\x2e\xa4\x78\xec\x2e\x56\xb8\x8b\xcf\xa3\x17\x53\x94\x34\x46\xff\x2e\xc5\xe3\xcf\xe6\xeb\xe4\xcd\x51\x5c\xe7\x24\xb5\xb7\x37\x8e\x4f\xe2\x68\xd1\x55\xca\x53\xb9\x4b\xbd\x31\x2b\x22\xae\x25\xc5\x47\xa9\x10\x12\x53\x52\xb6\x5c\xcd\x1c\x19\xa2\x06\xe9\xd1\x32\xa2\x4e\xb3\x41\x4b\x53\x03\x2c\xfa\xca\x6a\xd7\x85\x31\x97\x9a\xa9\x16\xb8\x89\xba\xb7\x8c\xd7\x3d\x53\x9f\x1f\xc4\x92\xfb\xc7\x10\xc5\xe0\xdd\x68\x04\xa9\x07\x72\x11\x4a\x23\xb5\x1f\xc2\x16\x41\x23\x0a\x6a\x7c\x81\xa2\xe2\x3e\xe0\xf5\x37\x4c\x55\xd8\xaf\x9b\x9b\x28\x32\x3c\x35\x15\x4d\x82\x83\xe6\x1f\x06\x03\xd7\x66\x13\x46\x5c\xc6\xf8\x0a\x9a\x86\x33\x56\x16\x52\x47\x4d\x38\x8f\x9a\x8d\x2c\x4a\x08\x38\x98\x15\x72\x45\x49\xa4\x93\x8f\x4c\xc1\x05\x64\xb2\xb8\xd4\xfe\x24\x79\x75\xd0\xdb\xa7\xf1\x1f\x49\xd3\x3c\x89\x23\xc2\x1b\x9c\xc5\x43\xf8\xf0\x6b\x57\x11\xde\x10\x14\xbc\x0e\xe6\xcd\xcb\x50\xd1\x69\x31\x3c\xff\x2c\xa8\xa1\x0e\x7e\x1b\xb4\x26\xae\xca\x28\x1d\xb5\x9f\x21\x8e\xc7\x5d\x7c\xfe\x03\xdc\x63\xdf\x5a\xdc\x26\x34\x09\x13\xe2\x10\x94\x3e\xc3\x77\xc1\xdc\x9d\x43\x01\x6f\x81\xbe\xa4\x26\x55\x4e\xf2\x2f\xcc\xc5\xf0\x37\x68\x24\x6e\xad\xe4\xdf\x59\x52\xe7\xf5\x13\x72\x8b\x6b\x1a\x05\x94\x3a\xce\x94\x42\xdb\x77\x10\x88\x66\xd8\xd4\x60\x48\x32\xae\x4b\xbf\x6b\x07\x84\x67\xb6\x40\xef\x5e\xf7\x26\xe0\xbc\x7b\xd7\xf2\x66\x88\xdf\xae\x44\xb8\xb8\x80\xfe\x64\x36\x4d\x17\xd3\x7e\xd3\x7b\xa3\x11\xdc\x63\x58\x9f\x32\x25\x33\xa1\x76\x20\x50\xa1\xc7\xda\x2e\xa3\x43\x5c\x3b\x1e\x19\xd2\x1e\x44\x1b\x0a\x3e\x4a\xe7\xa5\x2e\xa0\xa6\x97\x2d\x0d\xe3\x06\x2e\x34\x16\x67\x15\x85\xe7\x74\x72\x79\x43\x6b\x88\x45\x22\x23\x1a\x1a\xa1\x47\x99\x92\xdd\xda\x92\x4b\xeb\x3c\x94\x8a\x71\x4c\x08\xaf\x33\xe6\xe5\xa2\x68\xda\x9f\x54\xcf\x42\xdf\x06\xa0\xfd\x54\x64\x8a\xa6\x2a\xa9\x77\x30\x68\x31\xe2\xa8\xd7\xb3\xad\xf4\x01\xf6\xf9\x9e\x47\x9c\xc7\xf2\x90\x45\x68\x1b\xc1\x0d\x12\xef\x06\x0a\xa9\x27\x28\xe9\xfa\xd7\x6f\xcd\xc8\x46\x97\x44\x3d\x7a\x77\x40\x06\xca\x14\xc7\x64\x20\xea\xb0\xf0\xca\x5a\xca\x7f\xc7\xdb\x39\x11\xc3\x9f\x95\xf3\x14\x53\x4b\xe1\x69\x28\xe6\x39\x66\x0d\x3c\x4a\x23\x3a\xfe\x9e\x41\x69\xd8\x85\xe1\x42\xea\x9a\xd1\x56\xaf\x80\xa5\xf1\xa8\xbd\x64\x4a\xed\x28\x0f\x5b\x4b\xbb\x0f\x6d\x3b\x43\x70\x92\xa4\x02\x4d\x05\x51\xa9\xb9\xaa\x44\x5d\x06\xa1\xf8\x1b\x3c\x17\x6c\x3e\x5e\x9a\xd6\xe8\x1c\x2b\x30\xa1\x4a\xca\xe5\x63\xb3\x76\x6a\xe8\xd7\xcc\x38\x88\xfb\x49\x67\xe4\x31\x2f\x29\x53\x24\x6d\x91\x11\xb7\xa7\x42\x58\x74\x6e\x10\x37\x44\xd5\x65\xf6\x7e\x89\x9a\x82\x0f\x1a\xb7\xd0\xed\x33\x8c\x73\xda\xef\xc4\x10\x98\x10\xc4\x87\x27\xbb\x47\xd4\xeb\xb9\xad\xf4\x7c\x09\x41\x93\x29\xf7\xbd\x18\x37\xf5\xcf\x99\x43\x78\x33\xfd\xf7\x62\x72\xf3\x69\x3a\xb9\xb9\x7d\x78\x33\x86\xa3\xb3\xf9\xe5\x7f\xa6\xdd\xd9\xc7\xf4\x2a\xbd\x9e\x4c\xdf\x8c\xc3\x40\x7f\xc6\x21\x6f\x5a\x17\x48\xa1\xf3\x8c\xaf\x92\x12\x71\x35\x78\x7f\xcc\x03\x7b\x07\x7b\xbd\xcc\x22\x5b\x9d\xef\x8d\xa9\x1b\xb4\xd1\xd1\xf2\x34\x5c\xc0\x8b\xc1\x3a\x7f\xd9\x9a\x49\x23\x3f\x68\xd9\x7f\xbf\xbf\x04\xaa\x78\xdd\x8e\xb3\xbf\x6c\x48\xe8\x1d\xc6\x57\x63\x70\x4c\xd1\xda\x2c\xff\x4b\x7f\x77\xf2\xdc\xa1\x1f\x02\x6a\x61\xb6\xc4\x7c\x1d\x6a\x7d\xd3\xe0\x1e\x84\xec\x43\x5c\xd3\xee\x4d\x3e\x88\x3b\x61\x02\xfb\x5e\xf4\xec\x39\x51\xd4\x02\x2e\x5a\xf4\xb7\xe1\xe5\xeb\x81\x3a\x6b\x22\x75\xa2\xe0\x97\x93\xb5\x30\xdc\xaf\x71\x6d\xec\xae\x99\x61\x07\xfe\xfd\x38\xaa\xe9\xd5\x55\x57\x4f\xf4\x41\x45\xd6\x1d\x7c\x9a\x5e\x4d\xbf\xa4\x8b\xe9\x91\xd4\x7c\x91\x2e\x2e\x27\xf5\xd1\x5f\x2e\xbc\x0f\x3f\x5d\x78\xfd\xf9\x7c\x71\x33\x9b\xf6\xc7\xcd\xd7\xd5\x4d\xfa\xa9\xff\x9d\xc2\x66\x75\xfc\x51\xeb\x7a\x73\x6f\xac\xf8\x7f\x3a\xe0\x60\x8d\xcb\xd9\x73\x5b\x5c\xa0\x76\xee\xab\x93\x7f\x49\xc0\x74\xcb\xca\x79\xfd\x4f\xb1\x17\xde\x3f\xcb\xc3\x4f\xd1\x53\xf4\xbf\x00\x00\x00\xff\xff\x3a\xb7\x37\x41\xbf\x10\x00\x00") func prestate_tracerJsBytes() ([]byte, error) { return bindataRead( @@ -213,7 +213,7 @@ func prestate_tracerJs() (*asset, error) { } info := bindataFileInfo{name: "prestate_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe9, 0x79, 0x70, 0x4f, 0xc5, 0x78, 0x57, 0x63, 0x6f, 0x5, 0x31, 0xce, 0x3e, 0x5d, 0xbd, 0x71, 0x4, 0x46, 0x78, 0xcd, 0x1d, 0xcd, 0xb9, 0xd8, 0x10, 0xff, 0xe6, 0xc5, 0x59, 0xb9, 0x25, 0x6e}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0x9, 0xf9, 0x44, 0x13, 0x31, 0x89, 0xf7, 0x35, 0x9a, 0xc6, 0xf0, 0x86, 0x9d, 0xb2, 0xe3, 0x57, 0xe2, 0xc0, 0xde, 0xc9, 0x3a, 0x4c, 0x4a, 0x94, 0x90, 0xa5, 0x92, 0x2f, 0xbf, 0xc0, 0xb8}} return a, nil } diff --git a/eth/tracers/internal/tracers/prestate_tracer.js b/eth/tracers/internal/tracers/prestate_tracer.js index e0a22bf157..084c04ec46 100644 --- a/eth/tracers/internal/tracers/prestate_tracer.js +++ b/eth/tracers/internal/tracers/prestate_tracer.js @@ -55,7 +55,7 @@ var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16); this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16); - this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).toString(16); + this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).add((ctx.gasUsed + ctx.intrinsicGas) * ctx.gasPrice).toString(16); // Decrement the caller's nonce, and remove empty create targets this.prestate[toHex(ctx.from)].nonce--; diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 5d806d9026..c9f00d7371 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -27,10 +27,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "gopkg.in/olebedev/go-duktape.v3" + duktape "gopkg.in/olebedev/go-duktape.v3" ) // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. @@ -316,7 +317,7 @@ type Tracer struct { // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func New(code string) (*Tracer, error) { +func New(code string, txCtx vm.TxContext) (*Tracer, error) { // Resolve any tracers by name and assemble the tracer object if tracer, ok := tracer(code); ok { code = tracer @@ -335,6 +336,8 @@ func New(code string) (*Tracer, error) { depthValue: new(uint), refundValue: new(uint), } + tracer.ctx["gasPrice"] = txCtx.GasPrice + // Set up builtins for this environment tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { ctx.PushString(hexutil.Encode(popSlice(ctx))) @@ -546,6 +549,18 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost // Initialize the context if it wasn't done yet if !jst.inited { jst.ctx["block"] = env.Context.BlockNumber.Uint64() + // Compute intrinsic gas + isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) + isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) + var input []byte + if data, ok := jst.ctx["input"].([]byte); ok { + input = data + } + intrinsicGas, err := core.IntrinsicGas(input, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) + if err != nil { + return err + } + jst.ctx["intrinsicGas"] = intrinsicGas jst.inited = true } // If tracing was interrupted, set the error and stop @@ -597,8 +612,8 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost // CaptureEnd is called after the call finishes to finalize the tracing. func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { jst.ctx["output"] = output - jst.ctx["gasUsed"] = gasUsed jst.ctx["time"] = t.String() + jst.ctx["gasUsed"] = gasUsed if err != nil { jst.ctx["error"] = err.Error() diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 554b2282f1..f28e14864b 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -17,7 +17,6 @@ package tracers import ( - "bytes" "encoding/json" "errors" "math/big" @@ -50,94 +49,77 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } -func runTrace(tracer *Tracer) (json.RawMessage, error) { - env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) - - contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) - contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - - _, err := env.Interpreter().Run(contract, []byte{}, false) - if err != nil { - return nil, err - } - return tracer.GetResult() +type vmContext struct { + blockCtx vm.BlockContext + txCtx vm.TxContext } -// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access -func TestRegressionPanicSlice(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } - if _, err = runTrace(tracer); err != nil { - t.Fatal(err) - } -} - -// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks -func TestRegressionPanicPeek(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } - if _, err = runTrace(tracer); err != nil { - t.Fatal(err) - } -} - -// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint -func TestRegressionPanicGetUint(t *testing.T) { - tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } - if _, err = runTrace(tracer); err != nil { - t.Fatal(err) - } +func testCtx() *vmContext { + return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} } -func TestTracing(t *testing.T) { - tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}") - if err != nil { - t.Fatal(err) - } - - ret, err := runTrace(tracer) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ret, []byte("3")) { - t.Errorf("Expected return value to be 3, got %s", string(ret)) - } -} - -func TestStack(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}") - if err != nil { - t.Fatal(err) - } +func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { + env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + var ( + startGas uint64 = 10000 + value = big.NewInt(0) + ) + contract := vm.NewContract(account{}, account{}, value, startGas) + contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - ret, err := runTrace(tracer) + tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) + ret, err := env.Interpreter().Run(contract, []byte{}, false) + tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ret, []byte("[0,1,2]")) { - t.Errorf("Expected return value to be [0,1,2], got %s", string(ret)) + return nil, err } + return tracer.GetResult() } -func TestOpcodes(t *testing.T) { - tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}") - if err != nil { - t.Fatal(err) - } - - ret, err := runTrace(tracer) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) { - t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret)) +func TestTracer(t *testing.T) { + execTracer := func(code string) []byte { + t.Helper() + ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} + tracer, err := New(code, ctx.txCtx) + if err != nil { + t.Fatal(err) + } + ret, err := runTrace(tracer, ctx) + if err != nil { + t.Fatal(err) + } + return ret + } + for i, tt := range []struct { + code string + want string + }{ + { // tests that we don't panic on bad arguments to memory access + code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", + want: `[{},{},{}]`, + }, { // tests that we don't panic on bad arguments to stack peeks + code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", + want: `["0","0","0"]`, + }, { // tests that we don't panic on bad arguments to memory getUint + code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", + want: `["0","0","0"]`, + }, { // tests some general counting + code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", + want: `3`, + }, { // tests that depth is reported correctly + code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", + want: `[0,1,2]`, + }, { // tests to-string of opcodes + code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", + want: `["PUSH1","PUSH1","STOP"]`, + }, { // tests intrinsic gas + code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}", + want: `"100000.6.21000"`, + }, + } { + if have := execTracer(tt.code); tt.want != string(have) { + t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) + } } } @@ -145,7 +127,8 @@ func TestHalt(t *testing.T) { t.Skip("duktape doesn't support abortion") timeout := errors.New("stahp") - tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}") + vmctx := testCtx() + tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx) if err != nil { t.Fatal(err) } @@ -155,17 +138,17 @@ func TestHalt(t *testing.T) { tracer.Stop(timeout) }() - if _, err = runTrace(tracer); err.Error() != "stahp in server-side tracer function 'step'" { + if _, err = runTrace(tracer, vmctx); err.Error() != "stahp in server-side tracer function 'step'" { t.Errorf("Expected timeout error, got %v", err) } } func TestHaltBetweenSteps(t *testing.T) { - tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}") + vmctx := testCtx() + tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx) if err != nil { t.Fatal(err) } - env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index b749d832b4..9dc4c69631 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -173,7 +173,7 @@ func TestPrestateTracerCreate2(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("prestateTracer") + tracer, err := New("prestateTracer", txContext) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -248,7 +248,7 @@ func TestCallTracer(t *testing.T) { _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it - tracer, err := New("callTracer") + tracer, err := New("callTracer", txContext) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } From b13e9c4e3d603955f92c1542a5e86c740f43f33e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 27 Dec 2020 21:58:39 +0100 Subject: [PATCH 211/948] tests/fuzzers: fix false positive in bitutil fuzzer (#22076) --- tests/fuzzers/bitutil/compress_fuzz.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/fuzzers/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go index 0b77b2dc9e..5903cf2f93 100644 --- a/tests/fuzzers/bitutil/compress_fuzz.go +++ b/tests/fuzzers/bitutil/compress_fuzz.go @@ -51,7 +51,19 @@ func fuzzDecode(data []byte) int { if err != nil { return 0 } - if comp := bitutil.CompressBytes(blob); !bytes.Equal(comp, data) { + // re-compress it (it's OK if the re-compressed differs from the + // original - the first input may not have been compressed at all) + comp := bitutil.CompressBytes(blob) + if len(comp) > len(blob) { + // After compression, it must be smaller or equal + panic("bad compression") + } + // But decompressing it once again should work + decomp, err := bitutil.DecompressBytes(data, 1024) + if err != nil { + panic(err) + } + if !bytes.Equal(decomp, blob) { panic("content mismatch") } return 1 From 2f8100615ad1d8335e38024221143163f09d54fe Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:01:28 +0100 Subject: [PATCH 212/948] cmd/geth: replace wiki links with new doc pages (#22071) --- cmd/geth/consolecmd.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index cbecbe0a5f..7822c73b31 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -43,7 +43,7 @@ var ( Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, +See https://geth.ethereum.org/docs/interface/javascript-console.`, } attachCommand = cli.Command{ @@ -56,7 +56,7 @@ See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console. +See https://geth.ethereum.org/docs/interface/javascript-console. This command allows to open a console on a running geth node.`, } @@ -69,7 +69,7 @@ This command allows to open a console on a running geth node.`, Category: "CONSOLE COMMANDS", Description: ` The JavaScript VM exposes a node admin interface as well as the Ðapp -JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console`, +JavaScript API. See https://geth.ethereum.org/docs/interface/javascript-console`, } ) From 0a09a39325814a2acbf4486c74b9aa9e6cff04d6 Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:09:05 +0100 Subject: [PATCH 213/948] eth/filters: replace wiki links with new doc pages (#22070) --- eth/filters/api.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 664f229a04..b6f974c3ba 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -101,7 +101,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // It is part of the filter package because this filter can be used through the // `eth_getFilterChanges` polling method that is also used for log filters. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter +// https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { var ( pendingTxs = make(chan []common.Hash) @@ -171,7 +171,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. // It is part of the filter package since polling goes with eth_getFilterChanges. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter +// https://eth.wiki/json-rpc/API#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { var ( headers = make(chan *types.Header) @@ -287,7 +287,7 @@ type FilterCriteria ethereum.FilterQuery // // In case "fromBlock" > "toBlock" an error is returned. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter +// https://eth.wiki/json-rpc/API#eth_newfilter func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { logs := make(chan []*types.Log) logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs) @@ -322,7 +322,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { // GetLogs returns logs matching the given argument that are stored within the state. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs +// https://eth.wiki/json-rpc/API#eth_getlogs func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { var filter *Filter if crit.BlockHash != nil { @@ -351,7 +351,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([ // UninstallFilter removes the filter with the given filter id. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter +// https://eth.wiki/json-rpc/API#eth_uninstallfilter func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { api.filtersMu.Lock() f, found := api.filters[id] @@ -369,7 +369,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { // GetFilterLogs returns the logs for the filter with the given id. // If the filter could not be found an empty array of logs is returned. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs +// https://eth.wiki/json-rpc/API#eth_getfilterlogs func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) { api.filtersMu.Lock() f, found := api.filters[id] @@ -410,7 +410,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty // For pending transaction and block filters the result is []common.Hash. // (pending)Log filters return []Log. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges +// https://eth.wiki/json-rpc/API#eth_getfilterchanges func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { api.filtersMu.Lock() defer api.filtersMu.Unlock() From ab0979f9306e87025d821b2629985e8bffe130ef Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:18:57 +0100 Subject: [PATCH 214/948] signer: docs - replace wiki links with new doc pages (#22069) --- signer/fourbyte/abi_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/signer/fourbyte/abi_test.go b/signer/fourbyte/abi_test.go index 314c12735b..68c027ecea 100644 --- a/signer/fourbyte/abi_test.go +++ b/signer/fourbyte/abi_test.go @@ -68,7 +68,7 @@ func TestNewUnpacker(t *testing.T) { [10]byte{49, 50, 51, 52, 53, 54, 55, 56, 57, 48}, common.Hex2Bytes("48656c6c6f2c20776f726c6421"), }, - }, { // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples + }, { // https://docs.soliditylang.org/en/develop/abi-spec.html#examples `[{"type":"function","name":"sam","inputs":[{"type":"bytes"},{"type":"bool"},{"type":"uint256[]"}]}]`, // "dave", true and [1,2,3] "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", @@ -124,7 +124,7 @@ func TestCalldataDecoding(t *testing.T) { "42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", // Too short compareAndApprove "a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", - // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // From https://docs.soliditylang.org/en/develop/abi-spec.html // contains a bool with illegal values "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", } { @@ -135,7 +135,7 @@ func TestCalldataDecoding(t *testing.T) { } // Expected success for i, hexdata := range []string{ - // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // From https://docs.soliditylang.org/en/develop/abi-spec.html "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", "a52c101e0000000000000000000000000000000000000000000000000000000000000012", "a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", From 653e8b9dd9b7a1565b0e26c71ad70a83803ad529 Mon Sep 17 00:00:00 2001 From: jk-jeongkyun <45347815+jeongkyun-oh@users.noreply.github.com> Date: Mon, 28 Dec 2020 06:26:42 +0900 Subject: [PATCH 215/948] eth/downloader: remove unnecessary condition (#22052) --- eth/downloader/queue.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 2150842f8e..ac7edc2c68 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -893,9 +893,6 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, return accepted, nil } // If none of the data was good, it's a stale delivery - if errors.Is(failure, errInvalidChain) { - return accepted, failure - } if accepted > 0 { return accepted, fmt.Errorf("partial failure: %v", failure) } From c17a7733df3aa7f68d4e0ff5ce9d5c2919284faa Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Sun, 27 Dec 2020 22:28:08 +0100 Subject: [PATCH 216/948] docs: replace wiki links with new doc pages in readme.md (#22065) (#22066) --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 19357355a8..57d431db1e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ archives are published at https://geth.ethereum.org/downloads/. ## Building the source -For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. +For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth). Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install them using your favourite package manager. Once the dependencies are installed, run @@ -36,18 +36,18 @@ directory. | Command | Description | | :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. | -| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. | +| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. | +| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. | | `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | | `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | -| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | -| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | +| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://eth.wiki/json-rpc/API) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | +| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://eth.wiki/en/fundamentals/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | | `puppeth` | a CLI wizard that aids in creating a new Ethereum network. | ## Running `geth` Going through all the possible command line flags is out of scope here (please consult our -[CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)), +[CLI Wiki page](https://geth.ethereum.org/docs/interface/command-line-options)), but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own `geth` instance. @@ -66,9 +66,9 @@ This command will: * Start `geth` in fast sync mode (default, can be changed with the `--syncmode` flag), causing it to download more data in exchange for avoiding processing the entire history of the Ethereum network, which is very CPU intensive. - * Start up `geth`'s built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console), - (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API) - as well as `geth`'s own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs). + * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console), + (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://web3js.readthedocs.io/en/) + as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server). This tool is optional and if you leave it out you can always attach to an already running `geth` instance with `geth attach`. @@ -170,8 +170,8 @@ accessible from the outside. As a developer, sooner rather than later you'll want to start interacting with `geth` and the Ethereum network via your own programs and not manually through the console. To aid -this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) -and [`geth` specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). +this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://eth.wiki/json-rpc/API) +and [`geth` specific APIs](https://geth.ethereum.org/docs/rpc/server)). These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based platforms, and named pipes on Windows). @@ -277,7 +277,7 @@ $ bootnode --genkey=boot.key $ bootnode --nodekey=boot.key ``` -With the bootnode online, it will display an [`enode` URL](https://github.com/ethereum/wiki/wiki/enode-url-format) +With the bootnode online, it will display an [`enode` URL](https://eth.wiki/en/fundamentals/enode-url-format) that other nodes can use to connect to it and exchange peer information. Make sure to replace the displayed IP address information (most probably `[::]`) with your externally accessible IP to get the actual `enode` URL. @@ -344,7 +344,7 @@ Please make sure your contributions adhere to our coding guidelines: * Commit messages should be prefixed with the package(s) they modify. * E.g. "eth, rpc: make trace configs optional" -Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide) for more details on configuring your environment, managing project dependencies, and testing procedures. From a425a47ddcb6078e2ae6ab062bb73f2c0939fd1d Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 28 Dec 2020 05:38:16 +0800 Subject: [PATCH 217/948] core/rawdb, eth/protocols : Method name typo fix (#22026) --- core/rawdb/accessors_snapshot.go | 4 ++-- eth/protocols/snap/sync.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 0a91d9353b..c3616ba3aa 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -176,8 +176,8 @@ func DeleteSnapshotRecoveryNumber(db ethdb.KeyValueWriter) { } } -// ReadSanpshotSyncStatus retrieves the serialized sync status saved at shutdown. -func ReadSanpshotSyncStatus(db ethdb.KeyValueReader) []byte { +// ReadSnapshotSyncStatus retrieves the serialized sync status saved at shutdown. +func ReadSnapshotSyncStatus(db ethdb.KeyValueReader) []byte { data, _ := db.Get(snapshotSyncStatusKey) return data } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 679b328283..437b0caab4 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -614,7 +614,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { func (s *Syncer) loadSyncStatus() { var progress syncProgress - if status := rawdb.ReadSanpshotSyncStatus(s.db); status != nil { + if status := rawdb.ReadSnapshotSyncStatus(s.db); status != nil { if err := json.Unmarshal(status, &progress); err != nil { log.Error("Failed to decode snap sync status", "err", err) } else { From 0a3993c558616868e35f9730e92c704ac16ee437 Mon Sep 17 00:00:00 2001 From: yumiel yoomee1313 Date: Wed, 30 Dec 2020 21:10:11 +0900 Subject: [PATCH 218/948] accounts/abi/bind: fix erroneous test (#22053) closes #22049 --- accounts/abi/bind/bind_test.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 4a504516bb..db87703d03 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1640,11 +1640,7 @@ var bindTests = []struct { contract NewFallbacks { event Fallback(bytes data); fallback() external { - bytes memory data; - assembly { - calldatacopy(data, 0, calldatasize()) - } - emit Fallback(data); + emit Fallback(msg.data); } event Received(address addr, uint value); @@ -1653,7 +1649,7 @@ var bindTests = []struct { } } `, - []string{"60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040526004361061000d575b36610081575b7f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a15b005b34801561008e5760006000fd5b505b606036600082377f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f986816040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fa5780820151818401525b6020810190506100de565b50505050905090810190601f1680156101275780820380516001836020036101000a031916815260200191505b509250505060405180910390a1505b00fea26469706673582212205643ca37f40c2b352dc541f42e9e6720de065de756324b7fcc9fb1d67eda4a7d64736f6c63430006040033"}, + []string{"6080604052348015600f57600080fd5b506101078061001f6000396000f3fe608060405236605f577f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1005b348015606a57600080fd5b507f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f98660003660405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a100fea26469706673582212201f994dcfbc53bf610b19176f9a361eafa77b447fd9c796fa2c615dfd0aaf3b8b64736f6c634300060c0033"}, []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Fallback","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Received","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]`}, ` "bytes" @@ -1701,6 +1697,7 @@ var bindTests = []struct { } // Test fallback function + gotEvent = false opts.Value = nil calldata := []byte{0x01, 0x02, 0x03} c.Fallback(opts, calldata) From 167ff563d16a405a89ce449fdb34eb6d99631053 Mon Sep 17 00:00:00 2001 From: Melvin Junhee Woo Date: Mon, 4 Jan 2021 17:07:43 +0900 Subject: [PATCH 219/948] core/state/snapshot: gethring -> gathering typo (#22104) --- core/state/snapshot/generate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 4a2fa78d3a..17f1ca6078 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -151,7 +151,7 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta // generate is a background thread that iterates over the state and storage tries, // constructing the state snapshot. All the arguments are purely for statistics -// gethering and logging, since the method surfs the blocks as they arrive, often +// gathering and logging, since the method surfs the blocks as they arrive, often // being restarted. func (dl *diskLayer) generate(stats *generatorStats) { // If a database wipe is in operation, wait until it's done From f83fc302a504919f6668060110cbb8b64c26dd07 Mon Sep 17 00:00:00 2001 From: Vie Date: Mon, 4 Jan 2021 18:52:23 +0800 Subject: [PATCH 220/948] cmd/geth: update copyright year (#22099) --- cmd/geth/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e587a5f86d..0d1b569b8b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -218,7 +218,7 @@ func init() { // Initialize the CLI app and start Geth app.Action = geth app.HideVersion = true // we have a command to print the version - app.Copyright = "Copyright 2013-2020 The go-ethereum Authors" + app.Copyright = "Copyright 2013-2021 The go-ethereum Authors" app.Commands = []cli.Command{ // See chaincmd.go: initCommand, From 47820ef726a7b08ea2e22baff8fff64231c3046b Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Mon, 4 Jan 2021 11:58:51 +0100 Subject: [PATCH 221/948] .github: Replace wiki links with new doc pages (#22065) (#22068) --- .github/CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f87996cdcb..a08542df25 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,11 +30,11 @@ Please make sure your contributions adhere to our coding guidelines: Before you submit a feature request, please check and make sure that it isn't possible through some other means. The JavaScript-enabled console is a powerful feature in the right hands. Please check our -[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info +[Geth documentation page](https://geth.ethereum.org/docs/) for more info and help. ## Configuration, dependencies, and tests -Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide) for more details on configuring your environment, managing project dependencies and testing procedures. From 5c2a7ce2ccace9f453bcd320b4ac52e1f5ce3ab2 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 4 Jan 2021 12:39:25 +0100 Subject: [PATCH 222/948] node: rename startNetworking to openEndpoints (#22105) --- node/node.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/node/node.go b/node/node.go index c66ebb89d0..b58594ef1d 100644 --- a/node/node.go +++ b/node/node.go @@ -159,12 +159,13 @@ func (n *Node) Start() error { return ErrNodeStopped } n.state = runningState - err := n.startNetworking() + // open networking and RPC endpoints + err := n.openEndpoints() lifecycles := make([]Lifecycle, len(n.lifecycles)) copy(lifecycles, n.lifecycles) n.lock.Unlock() - // Check if networking startup failed. + // Check if endpoint startup failed. if err != nil { n.doClose(nil) return err @@ -247,12 +248,14 @@ func (n *Node) doClose(errs []error) error { } } -// startNetworking starts all network endpoints. -func (n *Node) startNetworking() error { +// openEndpoints starts all network and RPC endpoints. +func (n *Node) openEndpoints() error { + // start networking endpoints n.log.Info("Starting peer-to-peer node", "instance", n.server.Name) if err := n.server.Start(); err != nil { return convertFileLockError(err) } + // start RPC endpoints err := n.startRPC() if err != nil { n.stopRPC() From 1951e20d1040627faf3b6722c88ddf0e86ecf50e Mon Sep 17 00:00:00 2001 From: Suriyaa Sundararuban Date: Mon, 4 Jan 2021 12:42:47 +0100 Subject: [PATCH 223/948] SECURITY.md: link to release page (#22067) Add links to go-ethereum's GitHub release page. Co-authored-by: Felix Lange --- SECURITY.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index bc54ede42f..bdce7b8d2a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,31 +2,29 @@ ## Supported Versions -Please see Releases. We recommend to use the most recent released version. +Please see [Releases](https://github.com/ethereum/go-ethereum/releases). We recommend using the [most recently released version](https://github.com/ethereum/go-ethereum/releases/latest). ## Audit reports Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits - | Scope | Date | Report Link | | ------- | ------- | ----------- | | `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) | | `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) | - - ## Reporting a Vulnerability **Please do not file a public ticket** mentioning the vulnerability. -To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. +To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publically disclosed security vulnerabilities. + +Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number. The following key may be used to communicate sensitive information to developers. Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A` - ``` -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 From e4571d8c12be5aaa380ee58ea35a5617823e5324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 4 Jan 2021 13:58:46 +0200 Subject: [PATCH 224/948] cmd: support v1.1 Twitter API in faucet, fix puppeth --- cmd/faucet/README.md | 50 ++++++++++++++++++ cmd/faucet/faucet.go | 100 ++++++++++++++++++++++------------- cmd/puppeth/module_faucet.go | 2 +- cmd/puppeth/wizard_faucet.go | 18 ++----- 4 files changed, 118 insertions(+), 52 deletions(-) create mode 100644 cmd/faucet/README.md diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md new file mode 100644 index 0000000000..364689a782 --- /dev/null +++ b/cmd/faucet/README.md @@ -0,0 +1,50 @@ +# Faucet + +The `faucet` is a simplistic web application with the goal of distributing small amounts of Ether in private and test networks. + +Users need to post their Ethereum addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the Ether. After a funding round, the faucet prevents the same user requesting again for a pre-configured amount of time, proportional to the amount of Ether requested. + +## Operation + +The `faucet` is a single binary app (everything included) with all configurations set via command line flags and a few files. + +First thing's first, the `faucet` needs to connect to an Ethereum network, for which it needs the necessary genesis and network infos. Each of the following flags must be set: + +- `--genesis` is a path to a file containin the network `genesis.json` +- `--network` is the devp2p network id used during connection +- `--bootnodes` is a list of `enode://` ids to join the network through + +The `faucet` will use the `les` protocol to join the configured Ethereum network and will store its data in `$HOME/.faucet` (currently not configurable). + +## Funding + +To be able to distribute funds, the `faucet` needs access to an already funded Ethereum account. This can be configured via: + +- `--account.json` is a path to the Ethereum account's JSON key file +- `--account.pass` is a path to a text file with the decryption passphrase + +The faucet is able to distribute various amounts of Ether in exchange for various timeouts. These can be configured via: + +- `--faucet.amount` is the number of Ethers to send by default +- `--faucet.minutes` is the time to wait before allowing a rerequest +- `--faucet.tiers` is the funding tiers to support (x3 time, x2.5 funds) + +## Sybil protection + +To prevent the same user from exhausting funds in a loop, the `faucet` ties requests to social networks and captcha resolvers. + +Captcha protection uses Google's invisible ReCaptcha, thus the `faucet` needs to run on a live domain. The domain needs to be registered in Google's systems to retrieve the captcha API token and secrets. After doing so, captcha protection may be enabled via: + +- `--captcha.token` is the API token for ReCaptcha +- `--captcha.secret` is the API secret for ReCaptcha + +Sybil protection via Twitter requires an API key as of 15th December, 2020. To obtain it, a Twitter user must be upgraded to developer status and a new Twitter App deployed with it. The app's `Bearer` token is required by the faucet to retrieve tweet data: + +- `--twitter.token` is the Bearer token for `v2` API access +- `--twitter.token.v1` is the Bearer token for `v1` API access + +Sybil protection via Facebook uses the website to directly download post data thus does not currently require an API configuration. + +## Miscellaneous + +Beside the above - mostly essential - CLI flags, there are a number that can be used to fine tune the `faucet`'s operation. Please see `faucet --help` for a full list. \ No newline at end of file diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 008cb14296..79a84fd24e 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -84,7 +84,8 @@ var ( noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") - twitterBearerToken = flag.String("twitter.token", "", "Twitter bearer token to authenticate with the twitter API") + twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API") + twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API") ) var ( @@ -245,6 +246,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u cfg.NetworkId = network cfg.Genesis = genesis utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash()) + lesBackend, err := les.New(stack, &cfg) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) @@ -388,8 +390,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if err = conn.ReadJSON(&msg); err != nil { return } - if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") && - !strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { + if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { log.Warn("Failed to send URL error to client", "err", err) return @@ -451,21 +452,8 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { address common.Address ) switch { - case strings.HasPrefix(msg.URL, "https://gist.github.com/"): - if err = sendError(conn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil { - log.Warn("Failed to send GitHub deprecation to client", "err", err) - return - } - continue - case strings.HasPrefix(msg.URL, "https://plus.google.com/"): - //lint:ignore ST1005 Google is a company name and should be capitalized. - if err = sendError(conn, errors.New("Google+ authentication discontinued as the service was sunset")); err != nil { - log.Warn("Failed to send Google+ deprecation to client", "err", err) - return - } - continue case strings.HasPrefix(msg.URL, "https://twitter.com/"): - id, username, avatar, address, err = authTwitter(msg.URL, *twitterBearerToken) + id, username, avatar, address, err = authTwitter(msg.URL, *twitterTokenV1Flag, *twitterTokenFlag) case strings.HasPrefix(msg.URL, "https://www.facebook.com/"): username, avatar, address, err = authFacebook(msg.URL) id = username @@ -690,23 +678,31 @@ func sendSuccess(conn *websocket.Conn, msg string) error { // authTwitter tries to authenticate a faucet request using Twitter posts, returning // the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. -func authTwitter(url string, token string) (string, string, string, common.Address, error) { +func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense parts := strings.Split(url, "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") } - + // Strip any query parameters from the tweet id and ensure it's numeric + tweetID := strings.Split(parts[len(parts)-1], "?")[0] + if !regexp.MustCompile("^[0-9]+$").MatchString(tweetID) { + return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + } // Twitter's API isn't really friendly with direct links. // It is restricted to 300 queries / 15 minute with an app api key. // Anything more will require read only authorization from the users and that we want to avoid. - // If twitter bearer token is provided, use the twitter api - if token != "" { - return authTwitterWithToken(parts[len(parts)-1], token) + // If Twitter bearer token is provided, use the API, selecting the version + // the user would prefer (currently there's a limit of 1 v2 app / developer + // but unlimited v1.1 apps). + switch { + case tokenV1 != "": + return authTwitterWithTokenV1(tweetID, tokenV1) + case tokenV2 != "": + return authTwitterWithTokenV2(tweetID, tokenV2) } - // Twiter API token isn't provided so we just load the public posts // and scrape it for the Ethereum address and profile URL. We need to load // the mobile page though since the main page loads tweet contents via JS. @@ -742,19 +738,49 @@ func authTwitter(url string, token string) (string, string, string, common.Addre return username + "@twitter", username, avatar, address, nil } -// authTwitterWithToken tries to authenticate a faucet request using Twitter's API, returning -// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. -func authTwitterWithToken(tweetID string, token string) (string, string, string, common.Address, error) { - // Strip any query parameters from the tweet id - sanitizedTweetID := strings.Split(tweetID, "?")[0] +// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1 +// API, returning the user id, username, avatar URL and Ethereum address to fund on +// success. +func authTwitterWithTokenV1(tweetID string, token string) (string, string, string, common.Address, error) { + // Query the tweet details from Twitter + url := fmt.Sprintf("https://api.twitter.com/1.1/statuses/show.json?id=%s", tweetID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "", "", common.Address{}, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", common.Address{}, err + } + defer res.Body.Close() - // Ensure numeric tweetID - if !regexp.MustCompile("^[0-9]+$").MatchString(sanitizedTweetID) { - return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + var result struct { + Text string `json:"text"` + User struct { + ID string `json:"id_str"` + Username string `json:"screen_name"` + Avatar string `json:"profile_image_url"` + } `json:"user"` } + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return "", "", "", common.Address{}, err + } + address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Text)) + if address == (common.Address{}) { + //lint:ignore ST1005 This error is to be displayed in the browser + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") + } + return result.User.ID + "@twitter", result.User.Username, result.User.Avatar, address, nil +} +// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2 +// API, returning the user id, username, avatar URL and Ethereum address to fund on +// success. +func authTwitterWithTokenV2(tweetID string, token string) (string, string, string, common.Address, error) { // Query the tweet details from Twitter - url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", sanitizedTweetID) + url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", tweetID) req, err := http.NewRequest("GET", url, nil) if err != nil { return "", "", "", common.Address{}, err @@ -769,15 +795,13 @@ func authTwitterWithToken(tweetID string, token string) (string, string, string, var result struct { Data struct { AuthorID string `json:"author_id"` - ID string `json:"id"` Text string `json:"text"` } `json:"data"` Includes struct { Users []struct { - ProfileImageURL string `json:"profile_image_url"` - Username string `json:"username"` - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id"` + Username string `json:"username"` + Avatar string `json:"profile_image_url"` } `json:"users"` } `json:"includes"` } @@ -792,7 +816,7 @@ func authTwitterWithToken(tweetID string, token string) (string, string, string, //lint:ignore ST1005 This error is to be displayed in the browser return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") } - return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].ProfileImageURL, address, nil + return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].Avatar, address, nil } // authFacebook tries to authenticate a faucet request using Facebook posts, diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go index 2527e137f2..88cb80ae4c 100644 --- a/cmd/puppeth/module_faucet.go +++ b/cmd/puppeth/module_faucet.go @@ -46,7 +46,7 @@ ENTRYPOINT [ \ "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \ "--account.json", "/account.json", "--account.pass", "/account.pass" \ {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \ - {{if .TwitterToken}}, "--twitter.token", "{{.TwitterToken}}", + {{if .TwitterToken}}, "--twitter.token.v1", "{{.TwitterToken}}"{{end}} \ ]` // faucetComposefile is the docker-compose.yml file required to deploy and maintain diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go index 47e05cd9c1..65d4e8b8ed 100644 --- a/cmd/puppeth/wizard_faucet.go +++ b/cmd/puppeth/wizard_faucet.go @@ -102,11 +102,10 @@ func (w *wizard) deployFaucet() { infos.captchaSecret = w.readPassword() } } - - // Accessing the twitter api requires a bearer token, request it + // Accessing the Twitter API requires a bearer token, request it if infos.twitterToken != "" { fmt.Println() - fmt.Println("Reuse previous twitter API Bearer token (y/n)? (default = yes)") + fmt.Println("Reuse previous Twitter API token (y/n)? (default = yes)") if !w.readDefaultYesNo(true) { infos.twitterToken = "" } @@ -114,17 +113,10 @@ func (w *wizard) deployFaucet() { if infos.twitterToken == "" { // No previous twitter token (or old one discarded) fmt.Println() - fmt.Println("Enable twitter API (y/n)? (default = no)") - if !w.readDefaultYesNo(false) { - log.Warn("The faucet will fallback to using direct calls") - } else { - // Twitter api explicitly requested, read the bearer token - fmt.Println() - fmt.Printf("What is the twitter API Bearer token?\n") - infos.twitterToken = w.readString() - } + fmt.Println() + fmt.Printf("What is the Twitter API app Bearer token?\n") + infos.twitterToken = w.readString() } - // Figure out where the user wants to store the persistent data fmt.Println() if infos.node.datadir == "" { From 9584f56b9d2fe950b8fa70f5c7398de404f6b71c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Jan 2021 10:44:33 +0100 Subject: [PATCH 225/948] miner: avoid sleeping in miner (#22108) This PR removes a logic in the miner, which was originally intended to help temporary testnets based on ethash from "running off into the future". If the difficulty was low, and a few computers started mining several blocks per second, the ethash rules (which demand 1s delay between blocks) would push the blocktimes further and further away. The solution was to make the miner sleep while this happened. Nowadays, this problem is solved instead by PoA chains, and it's recommended to let testnets and devnets be based on clique instead. The existing logic is problematic, since it can cause stalls within the miner making it difficult for remote workers to submit work if the channel is blocked on a sleep. Credits to Saar Tochner for reporting this via the bug bounty --- miner/worker.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 5f07affdc4..2c5032c656 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -861,13 +861,6 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) if parent.Time() >= uint64(timestamp) { timestamp = int64(parent.Time() + 1) } - // this will ensure we're not going off too far in the future - if now := time.Now().Unix(); timestamp > now+1 { - wait := time.Duration(timestamp-now) * time.Second - log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) - time.Sleep(wait) - } - num := parent.Number() header := &types.Header{ ParentHash: parent.Hash(), From 664903dc889ec295b8eea292964547dc7910a26d Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 5 Jan 2021 10:18:22 +0000 Subject: [PATCH 226/948] cmd/geth: usb is off by default (#21984) --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 3 ++- cmd/utils/flags.go | 11 +++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0d1b569b8b..a2d5c36e76 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -68,6 +68,7 @@ var ( utils.KeyStoreDirFlag, utils.ExternalSignerFlag, utils.NoUSBFlag, + utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 0e70451ed3..73fdcacac5 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -37,7 +37,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.DataDirFlag, utils.AncientFlag, utils.KeyStoreDirFlag, - utils.NoUSBFlag, + utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, utils.GoerliFlag, @@ -219,6 +219,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ { Name: "ALIASED (deprecated)", Flags: append([]cli.Flag{ + utils.NoUSBFlag, utils.LegacyRPCEnabledFlag, utils.LegacyRPCListenAddrFlag, utils.LegacyRPCPortFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c51d7916ca..20b1744fdd 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -119,7 +119,11 @@ var ( } NoUSBFlag = cli.BoolFlag{ Name: "nousb", - Usage: "Disables monitoring for and managing USB hardware wallets", + Usage: "Disables monitoring for and managing USB hardware wallets (deprecated)", + } + USBFlag = cli.BoolFlag{ + Name: "usb", + Usage: "Enable monitoring and management of USB hardware wallets", } SmartCardDaemonPathFlag = cli.StringFlag{ Name: "pcscdpath", @@ -1225,8 +1229,11 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LightKDFFlag.Name) { cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name) } + if ctx.GlobalIsSet(USBFlag.Name) { + cfg.NoUSB = !ctx.GlobalBool(USBFlag.Name) + } if ctx.GlobalIsSet(NoUSBFlag.Name) { - cfg.NoUSB = ctx.GlobalBool(NoUSBFlag.Name) + log.Warn("Option nousb is deprecated and USB is deactivated by default. Use --usb to enable") } if ctx.GlobalIsSet(InsecureUnlockAllowedFlag.Name) { cfg.InsecureUnlockAllowed = ctx.GlobalBool(InsecureUnlockAllowedFlag.Name) From eb2a1dfdd21eeb89fcd6f9c06d42770e5078a6ed Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 5 Jan 2021 02:22:32 -0800 Subject: [PATCH 227/948] graphql: use a decimal representation for gas limit and gas used (#21883) This changes the JSON encoding of blocks returned by the API to have decimal instead of hexadecimal numbers. The spec wants it this way. Co-authored-by: Martin Holst Swende --- graphql/graphql.go | 56 ++++++++++--- graphql/graphql_test.go | 175 ++++++++++++++++++++++++---------------- 2 files changed, 150 insertions(+), 81 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 22cfcf6637..66c581628b 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -20,6 +20,8 @@ package graphql import ( "context" "errors" + "fmt" + "strconv" "time" "github.com/ethereum/go-ethereum" @@ -39,6 +41,37 @@ var ( errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash") ) +type Long int64 + +// ImplementsGraphQLType returns true if Long implements the provided GraphQL type. +func (b Long) ImplementsGraphQLType(name string) bool { return name == "Long" } + +// UnmarshalGraphQL unmarshals the provided GraphQL query data. +func (b *Long) UnmarshalGraphQL(input interface{}) error { + var err error + switch input := input.(type) { + case string: + // uncomment to support hex values + //if strings.HasPrefix(input, "0x") { + // // apply leniency and support hex representations of longs. + // value, err := hexutil.DecodeUint64(input) + // *b = Long(value) + // return err + //} else { + value, err := strconv.ParseInt(input, 10, 64) + *b = Long(value) + return err + //} + case int32: + *b = Long(input) + case int64: + *b = Long(input) + default: + err = fmt.Errorf("unexpected type %T for Long", input) + } + return err +} + // Account represents an Ethereum account at a particular block. type Account struct { backend ethapi.Backend @@ -415,13 +448,13 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { return b.receipts, nil } -func (b *Block) Number(ctx context.Context) (hexutil.Uint64, error) { +func (b *Block) Number(ctx context.Context) (Long, error) { header, err := b.resolveHeader(ctx) if err != nil { return 0, err } - return hexutil.Uint64(header.Number.Uint64()), nil + return Long(header.Number.Uint64()), nil } func (b *Block) Hash(ctx context.Context) (common.Hash, error) { @@ -435,20 +468,20 @@ func (b *Block) Hash(ctx context.Context) (common.Hash, error) { return b.hash, nil } -func (b *Block) GasLimit(ctx context.Context) (hexutil.Uint64, error) { +func (b *Block) GasLimit(ctx context.Context) (Long, error) { header, err := b.resolveHeader(ctx) if err != nil { return 0, err } - return hexutil.Uint64(header.GasLimit), nil + return Long(header.GasLimit), nil } -func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) { +func (b *Block) GasUsed(ctx context.Context) (Long, error) { header, err := b.resolveHeader(ctx) if err != nil { return 0, err } - return hexutil.Uint64(header.GasUsed), nil + return Long(header.GasUsed), nil } func (b *Block) Parent(ctx context.Context) (*Block, error) { @@ -902,11 +935,14 @@ type Resolver struct { } func (r *Resolver) Block(ctx context.Context, args struct { - Number *hexutil.Uint64 + Number *Long Hash *common.Hash }) (*Block, error) { var block *Block if args.Number != nil { + if *args.Number < 0 { + return nil, nil + } number := rpc.BlockNumber(*args.Number) numberOrHash := rpc.BlockNumberOrHashWithNumber(number) block = &Block{ @@ -939,10 +975,10 @@ func (r *Resolver) Block(ctx context.Context, args struct { } func (r *Resolver) Blocks(ctx context.Context, args struct { - From hexutil.Uint64 - To *hexutil.Uint64 + From *Long + To *Long }) ([]*Block, error) { - from := rpc.BlockNumber(args.From) + from := rpc.BlockNumber(*args.From) var to rpc.BlockNumber if args.To != nil { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 98c8622ee6..77129673c0 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -19,18 +19,17 @@ package graphql import ( "fmt" "io/ioutil" + "math/big" "net/http" "strings" "testing" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" - "github.com/stretchr/testify/assert" + "github.com/ethereum/go-ethereum/params" ) func TestBuildSchema(t *testing.T) { @@ -45,29 +44,95 @@ func TestBuildSchema(t *testing.T) { } // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint -func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { +func TestGraphQLBlockSerialization(t *testing.T) { stack := createNode(t, true) defer stack.Close() // start node if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) } - // create http request - body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) - if err != nil { - t.Error("could not issue new http request ", err) - } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) + + for i, tt := range []struct { + body string + want string + code int + }{ + { // Should return latest block + body: `{"query": "{block{number}}","variables": null}`, + want: `{"data":{"block":{"number":10}}}`, + code: 200, + }, + { // Should return info about latest block + body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":10,"gasUsed":0,"gasLimit":11500000}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:-1){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:-500){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"-33\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"1337\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"data":{"block":null}}`, + code: 200, + }, + { + body: `{"query": "{block(number:\"0xbad\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0xbad\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { // hex strings are currently not supported. If that's added to the spec, this test will need to change + body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { + body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`, + want: `{"errors":[{"message":"strconv.ParseInt: parsing \"a\": invalid syntax"}],"data":{}}`, + code: 400, + }, + { + body: `{"query": "{bleh{number}}","variables": null}"`, + want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`, + code: 400, + }, + } { + resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", strings.NewReader(tt.body)) + if err != nil { + t.Fatalf("could not post: %v", err) + } + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + if have := string(bodyBytes); have != tt.want { + t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want) + } + if tt.code != resp.StatusCode { + t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code) + } } - expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" - assert.Equal(t, 200, resp.StatusCode) - assert.Equal(t, expected, string(bodyBytes)) } // Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint @@ -77,49 +142,22 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) } - - // create http request - body := strings.NewReader("{\"query\": \"{block{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) + body := strings.NewReader(`{"query": "{block{number}}","variables": null}`) + resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", body) if err != nil { - t.Error("could not issue new http request ", err) + t.Fatalf("could not post: %v", err) } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatalf("could not read from response body: %v", err) } // make sure the request is not handled successfully - assert.Equal(t, 404, resp.StatusCode) - assert.Equal(t, "404 page not found\n", string(bodyBytes)) -} - -// Tests that 400 is returned when an invalid RPC request is made. -func TestGraphQL_BadRequest(t *testing.T) { - stack := createNode(t, true) - defer stack.Close() - // start node - if err := stack.Start(); err != nil { - t.Fatalf("could not start node: %v", err) + if want, have := "404 page not found\n", string(bodyBytes); have != want { + t.Errorf("have:\n%v\nwant:\n%v", have, want) } - // create http request - body := strings.NewReader("{\"query\": \"{bleh{number}}\",\"variables\": null}") - gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) - if err != nil { - t.Error("could not issue new http request ", err) - } - gqlReq.Header.Set("Content-Type", "application/json") - // read from response - resp := doHTTPRequest(t, gqlReq) - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) + if want, have := 404, resp.StatusCode; want != have { + t.Errorf("wrong statuscode, have:\n%v\nwant:%v", have, want) } - expected := "{\"errors\":[{\"message\":\"Cannot query field \\\"bleh\\\" on type \\\"Query\\\".\",\"locations\":[{\"line\":1,\"column\":2}]}]}" - assert.Equal(t, expected, string(bodyBytes)) - assert.Equal(t, 400, resp.StatusCode) } func createNode(t *testing.T, gqlEnabled bool) *node.Node { @@ -135,21 +173,20 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { if !gqlEnabled { return stack } - createGQLService(t, stack, "127.0.0.1:9393") - return stack } func createGQLService(t *testing.T, stack *node.Node, endpoint string) { - // create backend (use a config which is light on mem consumption) + // create backend ethConf := ð.Config{ - Genesis: core.DeveloperGenesisBlock(15, common.Address{}), - Miner: miner.Config{ - Etherbase: common.HexToAddress("0xaabb"), + Genesis: &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), }, Ethash: ethash.Config{ - PowMode: ethash.ModeTest, + PowMode: ethash.ModeFake, }, NetworkId: 1337, TrieCleanCache: 5, @@ -163,20 +200,16 @@ func createGQLService(t *testing.T, stack *node.Node, endpoint string) { if err != nil { t.Fatalf("could not create eth backend: %v", err) } - + // Create some blocks and import them + chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(), + ethash.NewFaker(), ethBackend.ChainDb(), 10, func(i int, gen *core.BlockGen) {}) + _, err = ethBackend.BlockChain().InsertChain(chain) + if err != nil { + t.Fatalf("could not create import blocks: %v", err) + } // create gql service err = New(stack, ethBackend.APIBackend, []string{}, []string{}) if err != nil { t.Fatalf("could not create graphql service: %v", err) } } - -func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - t.Fatal("could not issue a GET request to the given endpoint", err) - - } - return resp -} From 4714ce9430d2519e869a993d9f973a062dfc52d6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 5 Jan 2021 14:31:23 +0100 Subject: [PATCH 228/948] cmd/geth: added --mainnet flag (#21932) * cmd/geth: added --mainnet flag * cmd/utils: set default genesis if --mainnet is specified * cmd/utils: addressed comments --- cmd/geth/chaincmd.go | 2 ++ cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 12 +++++++++++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 6418f90957..f539322654 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -159,6 +159,7 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.CacheFlag, utils.SyncModeFlag, utils.FakePoWFlag, + utils.MainnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.TxLookupLimitFlag, @@ -210,6 +211,7 @@ Use "ethereum dump 0" to dump the genesis block.`, utils.DataDirFlag, utils.AncientFlag, utils.CacheFlag, + utils.MainnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a2d5c36e76..7af24e6523 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -140,6 +140,7 @@ var ( utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, utils.DNSDiscoveryFlag, + utils.MainnetFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, utils.LegacyTestnetFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 73fdcacac5..78ebb807e1 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -40,6 +40,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, + utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, utils.YoloV2Flag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 20b1744fdd..764d7ad73e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -135,6 +135,10 @@ var ( Usage: "Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead)", Value: eth.DefaultConfig.NetworkId, } + MainnetFlag = cli.BoolFlag{ + Name: "mainnet", + Usage: "Ethereum mainnet", + } GoerliFlag = cli.BoolFlag{ Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", @@ -1494,7 +1498,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) @@ -1608,6 +1612,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } // Override any default configs for hard coded networks. switch { + case ctx.GlobalBool(MainnetFlag.Name): + if !ctx.GlobalIsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 1 + } + cfg.Genesis = core.DefaultGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 3 From 9ba306d47ef3211de83bb858643abab77faf0528 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 5 Jan 2021 14:48:22 +0100 Subject: [PATCH 229/948] common/compiler: fix parsing of solc output with solidity v.0.8.0 (#22092) Solidity 0.8.0 changes the way that output is marshalled. This patch allows to parse both the legacy format used previously and the new format. See also https://docs.soliditylang.org/en/breaking/080-breaking-changes.html#interface-changes --- common/compiler/solidity.go | 50 +++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index b689f258a3..01de3d4c65 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -44,6 +44,20 @@ type solcOutput struct { Version string } +// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized +type solcOutputV8 struct { + Contracts map[string]struct { + BinRuntime string `json:"bin-runtime"` + SrcMapRuntime string `json:"srcmap-runtime"` + Bin, SrcMap, Metadata string + Abi interface{} + Devdoc interface{} + Userdoc interface{} + Hashes map[string]string + } + Version string +} + func (s *Solidity) makeArgs() []string { p := []string{ "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc", @@ -125,7 +139,6 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro if err := cmd.Run(); err != nil { return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes()) } - return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " ")) } @@ -141,7 +154,8 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { var output solcOutput if err := json.Unmarshal(combinedJSON, &output); err != nil { - return nil, err + // Try to parse the output with the new solidity v.0.8.0 rules + return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions) } // Compilation succeeded, assemble and return the contracts. contracts := make(map[string]*Contract) @@ -176,3 +190,35 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin } return contracts, nil } + +// parseCombinedJSONV8 parses the direct output of solc --combined-output +// and parses it using the rules from solidity v.0.8.0 and later. +func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) { + var output solcOutputV8 + if err := json.Unmarshal(combinedJSON, &output); err != nil { + return nil, err + } + // Compilation succeeded, assemble and return the contracts. + contracts := make(map[string]*Contract) + for name, info := range output.Contracts { + contracts[name] = &Contract{ + Code: "0x" + info.Bin, + RuntimeCode: "0x" + info.BinRuntime, + Hashes: info.Hashes, + Info: ContractInfo{ + Source: source, + Language: "Solidity", + LanguageVersion: languageVersion, + CompilerVersion: compilerVersion, + CompilerOptions: compilerOptions, + SrcMap: info.SrcMap, + SrcMapRuntime: info.SrcMapRuntime, + AbiDefinition: info.Abi, + UserDoc: info.Userdoc, + DeveloperDoc: info.Devdoc, + Metadata: info.Metadata, + }, + } + } + return contracts, nil +} From 618454214b70124646dc1c6333a59138b97d6b0a Mon Sep 17 00:00:00 2001 From: jk-jeongkyun <45347815+jeongkyun-oh@users.noreply.github.com> Date: Tue, 5 Jan 2021 22:56:01 +0900 Subject: [PATCH 230/948] eth/downloader: enhanced test cases for downloader queue (#22114) --- eth/downloader/queue_test.go | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index aedfba4565..f43ad67a41 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -97,6 +97,9 @@ func dummyPeer(id string) *peerConnection { } func TestBasics(t *testing.T) { + numOfBlocks := len(emptyChain.blocks) + numOfReceipts := len(emptyChain.blocks) / 2 + q := newQueue(10, 10) if !q.Idle() { t.Errorf("new queue should be idle") @@ -135,6 +138,12 @@ func TestBasics(t *testing.T) { t.Fatalf("expected header %d, got %d", exp, got) } } + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } { peer := dummyPeer("peer-2") fetchReq, _, throttle := q.ReserveBodies(peer, 50) @@ -148,8 +157,12 @@ func TestBasics(t *testing.T) { t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers)) } } - //fmt.Printf("blockTaskQueue len: %d\n", q.blockTaskQueue.Size()) - //fmt.Printf("receiptTaskQueue len: %d\n", q.receiptTaskQueue.Size()) + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } { // The receipt delivering peer should not be affected // by the throttling of body deliveries @@ -168,12 +181,20 @@ func TestBasics(t *testing.T) { } } - //fmt.Printf("blockTaskQueue len: %d\n", q.blockTaskQueue.Size()) - //fmt.Printf("receiptTaskQueue len: %d\n", q.receiptTaskQueue.Size()) - //fmt.Printf("processable: %d\n", q.resultCache.countCompleted()) + if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { + t.Errorf("expected block task queue to be %d, got %d", exp, got) + } + if exp, got := q.receiptTaskQueue.Size(), numOfReceipts-5; exp != got { + t.Errorf("expected receipt task queue to be %d, got %d", exp, got) + } + if got, exp := q.resultCache.countCompleted(), 0; got != exp { + t.Errorf("wrong processable count, got %d, exp %d", got, exp) + } } func TestEmptyBlocks(t *testing.T) { + numOfBlocks := len(emptyChain.blocks) + q := newQueue(10, 10) q.Prepare(1, FastSync) @@ -208,13 +229,12 @@ func TestEmptyBlocks(t *testing.T) { } } - if q.blockTaskQueue.Size() != len(emptyChain.blocks)-10 { - t.Errorf("expected block task queue to be 0, got %d", q.blockTaskQueue.Size()) + if q.blockTaskQueue.Size() != numOfBlocks-10 { + t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) } if q.receiptTaskQueue.Size() != 0 { - t.Errorf("expected receipt task queue to be 0, got %d", q.receiptTaskQueue.Size()) + t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) } - //fmt.Printf("receiptTaskQueue len: %d\n", q.receiptTaskQueue.Size()) { peer := dummyPeer("peer-3") fetchReq, _, _ := q.ReserveReceipts(peer, 50) @@ -224,6 +244,12 @@ func TestEmptyBlocks(t *testing.T) { t.Fatal("there should be no body fetch tasks remaining") } } + if q.blockTaskQueue.Size() != numOfBlocks-10 { + t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) + } + if q.receiptTaskQueue.Size() != 0 { + t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) + } if got, exp := q.resultCache.countCompleted(), 10; got != exp { t.Errorf("wrong processable count, got %d, exp %d", got, exp) } From 83d317cff937940395fcb7ece29effc9c7779c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 6 Jan 2021 08:37:45 +0200 Subject: [PATCH 231/948] cmd/utils, eth/downloader: minor snap nitpicks --- cmd/utils/flags.go | 1 - eth/downloader/downloader.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 764d7ad73e..684e3428ba 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1568,7 +1568,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // If snap-sync is requested, this flag is also required if cfg.SyncMode == downloader.SnapSync { log.Info("Snap sync requested, enabling --snapshot") - ctx.Set(SnapshotFlag.Name, "true") } else { cfg.TrieCleanCache += cfg.SnapshotCache cfg.SnapshotCache = 0 // Disabled diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 3123598437..315354ea99 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -89,7 +89,7 @@ var ( errCancelContentProcessing = errors.New("content processing canceled (requested)") errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") - errTooOld = errors.New("peer doesn't speak recent enough protocol version (need version >= 64)") + errTooOld = errors.New("peer's protocol version too old") ) type Downloader struct { @@ -460,7 +460,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I } }() if p.version < 64 { - return fmt.Errorf("%w, peer version: %d", errTooOld, p.version) + return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, 64) } mode := d.getMode() From d667ee2d1063faad0b4347db498bda00f34a1ba6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 6 Jan 2021 12:06:44 +0100 Subject: [PATCH 232/948] crypto: fix ineffectual assignments (#22124) * crypto/bls12381: fixed ineffectual assignment * crypto/signify: fix ineffectual assignment --- crypto/bls12381/arithmetic_fallback.go | 2 +- crypto/signify/signify_fuzz.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crypto/bls12381/arithmetic_fallback.go b/crypto/bls12381/arithmetic_fallback.go index 19fb589104..91cabf4f3d 100644 --- a/crypto/bls12381/arithmetic_fallback.go +++ b/crypto/bls12381/arithmetic_fallback.go @@ -207,7 +207,7 @@ func lsubAssign(z, x *fe) { z[2], b = bits.Sub64(z[2], x[2], b) z[3], b = bits.Sub64(z[3], x[3], b) z[4], b = bits.Sub64(z[4], x[4], b) - z[5], b = bits.Sub64(z[5], x[5], b) + z[5], _ = bits.Sub64(z[5], x[5], b) } func neg(z *fe, x *fe) { diff --git a/crypto/signify/signify_fuzz.go b/crypto/signify/signify_fuzz.go index d1bcf356a4..f9167900ad 100644 --- a/crypto/signify/signify_fuzz.go +++ b/crypto/signify/signify_fuzz.go @@ -25,7 +25,6 @@ import ( "log" "os" "os/exec" - "runtime" fuzz "github.com/google/gofuzz" "github.com/jedisct1/go-minisign" @@ -129,6 +128,9 @@ func getKey(fileS string) (string, error) { func createKeyPair() (string, string) { // Create key and put it in correct format tmpKey, err := ioutil.TempFile("", "") + if err != nil { + panic(err) + } defer os.Remove(tmpKey.Name()) defer os.Remove(tmpKey.Name() + ".pub") defer os.Remove(tmpKey.Name() + ".sec") From 072fd9625472b585dc7c0bd5fab17e289c0d91a9 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 6 Jan 2021 17:19:16 +0100 Subject: [PATCH 233/948] graphql: return decimal for `estimateGas` and `cumulativeGas` queries (#22126) * estimateGas, cumulativeGas * linted * add test for estimateGas --- graphql/graphql.go | 27 ++++++++++++++------------- graphql/graphql_test.go | 6 ++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 66c581628b..ea587106b4 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -301,21 +301,21 @@ func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) { return &ret, nil } -func (t *Transaction) GasUsed(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) GasUsed(ctx context.Context) (*Long, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil { return nil, err } - ret := hexutil.Uint64(receipt.GasUsed) + ret := Long(receipt.GasUsed) return &ret, nil } -func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*Long, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil { return nil, err } - ret := hexutil.Uint64(receipt.CumulativeGasUsed) + ret := Long(receipt.CumulativeGasUsed) return &ret, nil } @@ -811,7 +811,7 @@ type CallData struct { // CallResult encapsulates the result of an invocation of the `call` accessor. type CallResult struct { data hexutil.Bytes // The return data from the call - gasUsed hexutil.Uint64 // The amount of gas used + gasUsed Long // The amount of gas used status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success. } @@ -819,7 +819,7 @@ func (c *CallResult) Data() hexutil.Bytes { return c.data } -func (c *CallResult) GasUsed() hexutil.Uint64 { +func (c *CallResult) GasUsed() Long { return c.gasUsed } @@ -847,22 +847,22 @@ func (b *Block) Call(ctx context.Context, args struct { return &CallResult{ data: result.ReturnData, - gasUsed: hexutil.Uint64(result.UsedGas), + gasUsed: Long(result.UsedGas), status: status, }, nil } func (b *Block) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs -}) (hexutil.Uint64, error) { +}) (Long, error) { if b.numberOrHash == nil { _, err := b.resolveHeader(ctx) if err != nil { - return hexutil.Uint64(0), err + return 0, err } } gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap()) - return gas, err + return Long(gas), err } type Pending struct { @@ -917,16 +917,17 @@ func (p *Pending) Call(ctx context.Context, args struct { return &CallResult{ data: result.ReturnData, - gasUsed: hexutil.Uint64(result.UsedGas), + gasUsed: Long(result.UsedGas), status: status, }, nil } func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs -}) (hexutil.Uint64, error) { +}) (Long, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) + gas, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) + return Long(gas), err } // Resolver is the top-level object in the GraphQL hierarchy. diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 77129673c0..fc62da1813 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -117,6 +117,12 @@ func TestGraphQLBlockSerialization(t *testing.T) { want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`, code: 400, }, + // should return `estimateGas` as decimal + { + body: `{"query": "{block{ estimateGas(data:{}) }}"}`, + want: `{"data":{"block":{"estimateGas":53000}}}`, + code: 200, + }, } { resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", strings.NewReader(tt.body)) if err != nil { From d2e1b17f1828424906c9033b1dcbd3f2756a0c2c Mon Sep 17 00:00:00 2001 From: Melvin Junhee Woo Date: Thu, 7 Jan 2021 15:36:21 +0900 Subject: [PATCH 234/948] snapshot, trie: fixed typos, mostly in snapshot pkg (#22133) --- core/state/snapshot/conversion.go | 2 +- core/state/snapshot/difflayer.go | 10 +++++----- core/state/snapshot/difflayer_test.go | 2 +- core/state/snapshot/disklayer.go | 2 +- core/state/snapshot/disklayer_test.go | 2 +- core/state/snapshot/iterator.go | 4 ++-- core/state/snapshot/iterator_binary.go | 8 ++++---- core/state/snapshot/iterator_fast.go | 6 +++--- core/state/snapshot/snapshot.go | 4 ++-- trie/database.go | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index dee9ff0bf2..9832225345 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -240,7 +240,7 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato } in <- leaf - // Accumulate the generaation statistic if it's required. + // Accumulate the generation statistic if it's required. processed++ if time.Since(logged) > 3*time.Second && stats != nil { if account == (common.Hash{}) { diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 0aef6cf570..3be78cae88 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -44,7 +44,7 @@ var ( // aggregatorItemLimit is an approximate number of items that will end up // in the agregator layer before it's flushed out to disk. A plain account // weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot - // 0B (+hash). Slots are mostly set/unset in lockstep, so thet average at + // 0B (+hash). Slots are mostly set/unset in lockstep, so that average at // 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a // smaller number to be on the safe side. aggregatorItemLimit = aggregatorMemoryLimit / 42 @@ -114,9 +114,9 @@ type diffLayer struct { // deleted, all data in other set belongs to the "new" A. destructSet map[common.Hash]struct{} // Keyed markers for deleted (and potentially) recreated accounts accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil - accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) + accountData map[common.Hash][]byte // Keyed accounts for direct retrieval (nil means deleted) storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil - storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) + storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrieval. one per account (nil means deleted) diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer @@ -482,7 +482,7 @@ func (dl *diffLayer) flatten() snapshot { } } -// AccountList returns a sorted list of all accounts in this difflayer, including +// AccountList returns a sorted list of all accounts in this diffLayer, including // the deleted ones. // // Note, the returned slice is not a copy, so do not modify it. @@ -513,7 +513,7 @@ func (dl *diffLayer) AccountList() []common.Hash { return dl.accountList } -// StorageList returns a sorted list of all storage slot hashes in this difflayer +// StorageList returns a sorted list of all storage slot hashes in this diffLayer // for the given account. If the whole storage is destructed in this layer, then // an additional flag *destructed = true* will be returned, otherwise the flag is // false. Besides, the returned list will include the hash of deleted storage slot. diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 31636ee133..919af5fa86 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -314,7 +314,7 @@ func BenchmarkSearchSlot(b *testing.B) { // With accountList and sorting // BenchmarkFlatten-6 50 29890856 ns/op // -// Without sorting and tracking accountlist +// Without sorting and tracking accountList // BenchmarkFlatten-6 300 5511511 ns/op func BenchmarkFlatten(b *testing.B) { fill := func(parent snapshot) *diffLayer { diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index e8f2bc853f..7cbf6e293d 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -31,7 +31,7 @@ import ( // diskLayer is a low level persistent snapshot built on top of a key-value store. type diskLayer struct { diskdb ethdb.KeyValueStore // Key-value store containing the base snapshot - triedb *trie.Database // Trie node cache for reconstuction purposes + triedb *trie.Database // Trie node cache for reconstruction purposes cache *fastcache.Cache // Cache to avoid hitting the disk for direct access root common.Hash // Root hash of the base snapshot diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index 40ff5ade4c..6beb944e07 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -482,7 +482,7 @@ func TestDiskGeneratorPersistence(t *testing.T) { if !bytes.Equal(generator.Marker, genMarker) { t.Fatalf("Generator marker is not matched") } - // Test senario 2, the disk layer is fully generated + // Test scenario 2, the disk layer is fully generated // Modify or delete some accounts, flatten everything onto disk if err := snaps.Update(diffTwoRoot, diffRoot, nil, map[common.Hash][]byte{ accThree: accThree.Bytes(), diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index 5f943fea9f..1d9340bbad 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -133,7 +133,7 @@ func (it *diffAccountIterator) Hash() common.Hash { // Account returns the RLP encoded slim account the iterator is currently at. // This method may _fail_, if the underlying layer has been flattened between -// the call to Next and Acccount. That type of error will set it.Err. +// the call to Next and Account. That type of error will set it.Err. // This method assumes that flattening does not delete elements from // the accountdata mapping (writing nil into it is fine though), and will panic // if elements have been deleted. @@ -243,7 +243,7 @@ type diffStorageIterator struct { } // StorageIterator creates a storage iterator over a single diff layer. -// Execept the storage iterator is returned, there is an additional flag +// Except the storage iterator is returned, there is an additional flag // "destructed" returned. If it's true then it means the whole storage is // destructed in this layer(maybe recreated too), don't bother deeper layer // for storage retrieval. diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go index f82f750029..22184b2545 100644 --- a/core/state/snapshot/iterator_binary.go +++ b/core/state/snapshot/iterator_binary.go @@ -37,7 +37,7 @@ type binaryIterator struct { } // initBinaryAccountIterator creates a simplistic iterator to step over all the -// accounts in a slow, but eaily verifiable way. Note this function is used for +// accounts in a slow, but easily verifiable way. Note this function is used for // initialization, use `newBinaryAccountIterator` as the API. func (dl *diffLayer) initBinaryAccountIterator() Iterator { parent, ok := dl.parent.(*diffLayer) @@ -62,7 +62,7 @@ func (dl *diffLayer) initBinaryAccountIterator() Iterator { } // initBinaryStorageIterator creates a simplistic iterator to step over all the -// storage slots in a slow, but eaily verifiable way. Note this function is used +// storage slots in a slow, but easily verifiable way. Note this function is used // for initialization, use `newBinaryStorageIterator` as the API. func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator { parent, ok := dl.parent.(*diffLayer) @@ -199,14 +199,14 @@ func (it *binaryIterator) Release() { } // newBinaryAccountIterator creates a simplistic account iterator to step over -// all the accounts in a slow, but eaily verifiable way. +// all the accounts in a slow, but easily verifiable way. func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { iter := dl.initBinaryAccountIterator() return iter.(AccountIterator) } // newBinaryStorageIterator creates a simplistic account iterator to step over -// all the storage slots in a slow, but eaily verifiable way. +// all the storage slots in a slow, but easily verifiable way. func (dl *diffLayer) newBinaryStorageIterator(account common.Hash) StorageIterator { iter := dl.initBinaryStorageIterator(account) return iter.(StorageIterator) diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 291d52900d..48069b8fcf 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -75,7 +75,7 @@ type fastIterator struct { fail error } -// newFastIterator creates a new hierarhical account or storage iterator with one +// newFastIterator creates a new hierarchical account or storage iterator with one // element per diff layer. The returned combo iterator can be used to walk over // the entire snapshot diff stack simultaneously. func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash, accountIterator bool) (*fastIterator, error) { @@ -335,14 +335,14 @@ func (fi *fastIterator) Debug() { fmt.Println() } -// newFastAccountIterator creates a new hierarhical account iterator with one +// newFastAccountIterator creates a new hierarchical account iterator with one // element per diff layer. The returned combo iterator can be used to walk over // the entire snapshot diff stack simultaneously. func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) { return newFastIterator(tree, root, common.Hash{}, seek, true) } -// newFastStorageIterator creates a new hierarhical storage iterator with one +// newFastStorageIterator creates a new hierarchical storage iterator with one // element per diff layer. The returned combo iterator can be used to walk over // the entire snapshot diff stack simultaneously. func newFastStorageIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) { diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 60b4158b56..443fc8e5c7 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -368,7 +368,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // crossed. All diffs beyond the permitted number are flattened downwards. If the // layer limit is reached, memory cap is also enforced (but not before). // -// The method returns the new disk layer if diffs were persistend into it. +// The method returns the new disk layer if diffs were persisted into it. func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { // Dive until we run out of layers or reach the persistent database for ; layers > 2; layers-- { @@ -647,7 +647,7 @@ func (t *Tree) Rebuild(root common.Hash) { panic(fmt.Sprintf("unknown layer type: %T", layer)) } } - // Start generating a new snapshot from scratch on a backgroung thread. The + // Start generating a new snapshot from scratch on a background thread. The // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot") t.layers = map[common.Hash]snapshot{ diff --git a/trie/database.go b/trie/database.go index 7c2f207097..b18665770e 100644 --- a/trie/database.go +++ b/trie/database.go @@ -736,7 +736,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H batch.Replay(uncacher) batch.Reset() - // Reset the storage counters and bumpd metrics + // Reset the storage counters and bumped metrics if db.preimages != nil { db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 } From 44208d925811dc309b9e26df6dc1752ba359a0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 7 Jan 2021 10:23:50 +0200 Subject: [PATCH 235/948] cmd/faucet: fix websocket race regression after switching to gorilla --- cmd/faucet/faucet.go | 59 ++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 79a84fd24e..a0fc28c0d4 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -213,7 +213,7 @@ type faucet struct { nonce uint64 // Current pending nonce of the faucet price *big.Int // Current gas price to issue funds with - conns []*websocket.Conn // Currently live websocket connections + conns []*wsConn // Currently live websocket connections timeouts map[string]time.Time // History of users and their funding timeouts reqs []*request // Currently pending funding requests update chan struct{} // Channel to signal request updates @@ -221,6 +221,13 @@ type faucet struct { lock sync.RWMutex // Lock protecting the faucet's internals } +// wsConn wraps a websocket connection with a write mutex as the underlying +// websocket library does not synchronize access to the stream. +type wsConn struct { + conn *websocket.Conn + wlock sync.Mutex +} + func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ @@ -321,13 +328,14 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { defer conn.Close() f.lock.Lock() - f.conns = append(f.conns, conn) + wsconn := &wsConn{conn: conn} + f.conns = append(f.conns, wsconn) f.lock.Unlock() defer func() { f.lock.Lock() for i, c := range f.conns { - if c == conn { + if c.conn == conn { f.conns = append(f.conns[:i], f.conns[i+1:]...) break } @@ -355,7 +363,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if head == nil || balance == nil { // Report the faucet offline until initial stats are ready //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(conn, errors.New("Faucet offline")); err != nil { + if err = sendError(wsconn, errors.New("Faucet offline")); err != nil { log.Warn("Failed to send faucet error to client", "err", err) return } @@ -366,7 +374,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { f.lock.RLock() reqs := f.reqs f.lock.RUnlock() - if err = send(conn, map[string]interface{}{ + if err = send(wsconn, map[string]interface{}{ "funds": new(big.Int).Div(balance, ether), "funded": nonce, "peers": f.stack.Server().PeerCount(), @@ -375,7 +383,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send initial stats to client", "err", err) return } - if err = send(conn, head, 3*time.Second); err != nil { + if err = send(wsconn, head, 3*time.Second); err != nil { log.Warn("Failed to send initial header to client", "err", err) return } @@ -391,7 +399,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { return } if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { - if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { + if err = sendError(wsconn, errors.New("URL doesn't link to supported services")); err != nil { log.Warn("Failed to send URL error to client", "err", err) return } @@ -399,7 +407,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } if msg.Tier >= uint(*tiersFlag) { //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil { + if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil { log.Warn("Failed to send tier error to client", "err", err) return } @@ -415,7 +423,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form) if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha post error to client", "err", err) return } @@ -428,7 +436,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(res.Body).Decode(&result) res.Body.Close() if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha decode error to client", "err", err) return } @@ -437,7 +445,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if !result.Success { log.Warn("Captcha verification failed", "err", string(result.Errors)) //lint:ignore ST1005 it's funny and the robot won't mind - if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil { + if err = sendError(wsconn, errors.New("Beep-bop, you're a robot!")); err != nil { log.Warn("Failed to send captcha failure to client", "err", err) return } @@ -465,7 +473,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues") } if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send prefix error to client", "err", err) return } @@ -489,7 +497,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) if err != nil { f.lock.Unlock() - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction creation error to client", "err", err) return } @@ -498,7 +506,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { // Submit the transaction and mark as funded if successful if err := f.client.SendTransaction(context.Background(), signed); err != nil { f.lock.Unlock() - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction transmission error to client", "err", err) return } @@ -520,13 +528,13 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { // Send an error if too frequent funding, othewise a success if !fund { - if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple + if err = sendError(wsconn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple log.Warn("Failed to send funding error to client", "err", err) return } continue } - if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { + if err = sendSuccess(wsconn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { log.Warn("Failed to send funding success to client", "err", err) return } @@ -619,12 +627,12 @@ func (f *faucet) loop() { "requests": f.reqs, }, time.Second); err != nil { log.Warn("Failed to send stats to client", "err", err) - conn.Close() + conn.conn.Close() continue } if err := send(conn, head, time.Second); err != nil { log.Warn("Failed to send header to client", "err", err) - conn.Close() + conn.conn.Close() } } f.lock.RUnlock() @@ -646,7 +654,7 @@ func (f *faucet) loop() { for _, conn := range f.conns { if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil { log.Warn("Failed to send requests to client", "err", err) - conn.Close() + conn.conn.Close() } } f.lock.RUnlock() @@ -656,23 +664,26 @@ func (f *faucet) loop() { // sends transmits a data packet to the remote end of the websocket, but also // setting a write deadline to prevent waiting forever on the node. -func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error { +func send(conn *wsConn, value interface{}, timeout time.Duration) error { if timeout == 0 { timeout = 60 * time.Second } - conn.SetWriteDeadline(time.Now().Add(timeout)) - return conn.WriteJSON(value) + conn.wlock.Lock() + defer conn.wlock.Unlock() + + conn.conn.SetWriteDeadline(time.Now().Add(timeout)) + return conn.conn.WriteJSON(value) } // sendError transmits an error to the remote end of the websocket, also setting // the write deadline to 1 second to prevent waiting forever. -func sendError(conn *websocket.Conn, err error) error { +func sendError(conn *wsConn, err error) error { return send(conn, map[string]string{"error": err.Error()}, time.Second) } // sendSuccess transmits a success message to the remote end of the websocket, also // setting the write deadline to 1 second to prevent waiting forever. -func sendSuccess(conn *websocket.Conn, msg string) error { +func sendSuccess(conn *wsConn, msg string) error { return send(conn, map[string]string{"success": msg}, time.Second) } From 58b9db5f7cf7a3db4d9d0afa3772fb8da32ebc3a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 7 Jan 2021 11:58:07 +0100 Subject: [PATCH 236/948] eth/protocols/snap: track reverts when peer rejects request (#22016) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: reschedule missed deliveries * eth/protocols/snap: clarify log message * eth/protocols/snap: revert failures async and update runloop Co-authored-by: Péter Szilágyi --- eth/protocols/snap/sync.go | 172 +++++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 56 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 437b0caab4..82b21c4701 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -792,10 +792,7 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Account range request timed out") - select { - case s.accountReqFails <- req: - default: - } + s.scheduleRevertAccountRequest(req) }) s.accountReqs[reqid] = req delete(s.accountIdlers, idle) @@ -807,12 +804,8 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, maxRequestSize); err != nil { peer.Log().Debug("Failed to request account range", "err", err) - select { - case s.accountReqFails <- req: - default: - } + s.scheduleRevertAccountRequest(req) } - // Request successfully sent, start a }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists // Inject the request into the task to block further assignments @@ -886,10 +879,7 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Bytecode request timed out") - select { - case s.bytecodeReqFails <- req: - default: - } + s.scheduleRevertBytecodeRequest(req) }) s.bytecodeReqs[reqid] = req delete(s.bytecodeIdlers, idle) @@ -901,12 +891,8 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { log.Debug("Failed to request bytecodes", "err", err) - select { - case s.bytecodeReqFails <- req: - default: - } + s.scheduleRevertBytecodeRequest(req) } - // Request successfully sent, start a }(s.peers[idle]) // We're in the lock, peers[id] surely exists } } @@ -1018,10 +1004,7 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Storage request timed out") - select { - case s.storageReqFails <- req: - default: - } + s.scheduleRevertStorageRequest(req) }) s.storageReqs[reqid] = req delete(s.storageIdlers, idle) @@ -1037,12 +1020,8 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { } if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, maxRequestSize); err != nil { log.Debug("Failed to request storage", "err", err) - select { - case s.storageReqFails <- req: - default: - } + s.scheduleRevertStorageRequest(req) } - // Request successfully sent, start a }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists // Inject the request into the subtask to block further assignments @@ -1140,10 +1119,7 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Trienode heal request timed out") - select { - case s.trienodeHealReqFails <- req: - default: - } + s.scheduleRevertTrienodeHealRequest(req) }) s.trienodeHealReqs[reqid] = req delete(s.trienodeHealIdlers, idle) @@ -1155,12 +1131,8 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil { log.Debug("Failed to request trienode healers", "err", err) - select { - case s.trienodeHealReqFails <- req: - default: - } + s.scheduleRevertTrienodeHealRequest(req) } - // Request successfully sent, start a }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists } } @@ -1245,10 +1217,7 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { } req.timeout = time.AfterFunc(requestTimeout, func() { log.Debug("Bytecode heal request timed out") - select { - case s.bytecodeHealReqFails <- req: - default: - } + s.scheduleRevertBytecodeHealRequest(req) }) s.bytecodeHealReqs[reqid] = req delete(s.bytecodeHealIdlers, idle) @@ -1260,12 +1229,8 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { // Attempt to send the remote request and revert if it fails if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil { log.Debug("Failed to request bytecode healers", "err", err) - select { - case s.bytecodeHealReqFails <- req: - default: - } + s.scheduleRevertBytecodeHealRequest(req) } - // Request successfully sent, start a }(s.peers[idle]) // We're in the lock, peers[id] surely exists } } @@ -1325,10 +1290,26 @@ func (s *Syncer) revertRequests(peer string) { } } +// scheduleRevertAccountRequest asks the event loop to clean up an account range +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertAccountRequest(req *accountRequest) { + select { + case s.accountReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + // revertAccountRequest cleans up an account range request and returns all failed // retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertAccountRequest. func (s *Syncer) revertAccountRequest(req *accountRequest) { - log.Trace("Reverting account request", "peer", req.peer, "reqid", req.id) + log.Debug("Reverting account request", "peer", req.peer, "reqid", req.id) select { case <-req.stale: log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id) @@ -1350,10 +1331,26 @@ func (s *Syncer) revertAccountRequest(req *accountRequest) { } } -// revertBytecodeRequest cleans up an bytecode request and returns all failed +// scheduleRevertBytecodeRequest asks the event loop to clean up a bytecode request +// and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertBytecodeRequest(req *bytecodeRequest) { + select { + case s.bytecodeReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertBytecodeRequest cleans up a bytecode request and returns all failed // retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertBytecodeRequest. func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { - log.Trace("Reverting bytecode request", "peer", req.peer) + log.Debug("Reverting bytecode request", "peer", req.peer) select { case <-req.stale: log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id) @@ -1375,10 +1372,26 @@ func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { } } +// scheduleRevertStorageRequest asks the event loop to clean up a storage range +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertStorageRequest(req *storageRequest) { + select { + case s.storageReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + // revertStorageRequest cleans up a storage range request and returns all failed // retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertStorageRequest. func (s *Syncer) revertStorageRequest(req *storageRequest) { - log.Trace("Reverting storage request", "peer", req.peer) + log.Debug("Reverting storage request", "peer", req.peer) select { case <-req.stale: log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id) @@ -1404,10 +1417,26 @@ func (s *Syncer) revertStorageRequest(req *storageRequest) { } } -// revertTrienodeHealRequest cleans up an trienode heal request and returns all +// scheduleRevertTrienodeHealRequest asks the event loop to clean up a trienode heal +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertTrienodeHealRequest(req *trienodeHealRequest) { + select { + case s.trienodeHealReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertTrienodeHealRequest cleans up a trienode heal request and returns all // failed retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertTrienodeHealRequest. func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { - log.Trace("Reverting trienode heal request", "peer", req.peer) + log.Debug("Reverting trienode heal request", "peer", req.peer) select { case <-req.stale: log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id) @@ -1429,10 +1458,26 @@ func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { } } -// revertBytecodeHealRequest cleans up an bytecode request and returns all failed -// retrieval tasks to the scheduler for reassignment. +// scheduleRevertBytecodeHealRequest asks the event loop to clean up a bytecode heal +// request and return all failed retrieval tasks to the scheduler for reassignment. +func (s *Syncer) scheduleRevertBytecodeHealRequest(req *bytecodeHealRequest) { + select { + case s.bytecodeHealReqFails <- req: + // Sync event loop notified + case <-req.cancel: + // Sync cycle got cancelled + case <-req.stale: + // Request already reverted + } +} + +// revertBytecodeHealRequest cleans up a bytecode heal request and returns all +// failed retrieval tasks to the scheduler for reassignment. +// +// Note, this needs to run on the event runloop thread to reschedule to idle peers. +// On peer threads, use scheduleRevertBytecodeHealRequest. func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) { - log.Trace("Reverting bytecode heal request", "peer", req.peer) + log.Debug("Reverting bytecode heal request", "peer", req.peer) select { case <-req.stale: log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id) @@ -1768,7 +1813,7 @@ func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) { if err := batch.Write(); err != nil { log.Crit("Failed to persist healing data", "err", err) } - log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) + log.Debug("Persisted set of healing data", "type", "trienodes", "bytes", common.StorageSize(batch.ValueSize())) } // processBytecodeHealResponse integrates an already validated bytecode response @@ -1804,7 +1849,7 @@ func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) { if err := batch.Write(); err != nil { log.Crit("Failed to persist healing data", "err", err) } - log.Debug("Persisted set of healing data", "bytes", common.StorageSize(batch.ValueSize())) + log.Debug("Persisted set of healing data", "type", "bytecode", "bytes", common.StorageSize(batch.ValueSize())) } // forwardAccountTask takes a filled account task and persists anything available @@ -1940,6 +1985,9 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account logger.Debug("Peer rejected account range request", "root", s.root) s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertAccountRequest(req) return nil } root := s.root @@ -2055,6 +2103,9 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { logger.Debug("Peer rejected bytecode request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeRequest(req) return nil } s.lock.Unlock() @@ -2166,6 +2217,9 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots logger.Debug("Peer rejected storage request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertStorageRequest(req) return nil } s.lock.Unlock() @@ -2287,6 +2341,9 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { logger.Debug("Peer rejected trienode heal request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertTrienodeHealRequest(req) return nil } s.lock.Unlock() @@ -2371,6 +2428,9 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro logger.Debug("Peer rejected bytecode heal request") s.statelessPeers[peer.id] = struct{}{} s.lock.Unlock() + + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeHealRequest(req) return nil } s.lock.Unlock() From 4bb5c6ca7a19f5ad0230879205380adaca12ef4f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 7 Jan 2021 17:12:41 +0100 Subject: [PATCH 237/948] eth/protocols/snap: speed up hash checks (#22023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: speed up hash checks * eth/protocols/snap: nit fix Co-authored-by: Péter Szilágyi --- eth/protocols/snap/sync.go | 15 +++-- eth/protocols/snap/sync_test.go | 98 +++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 eth/protocols/snap/sync_test.go diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 82b21c4701..d6f0eb5472 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -2112,14 +2112,15 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // Cross reference the requested bytecodes with the response to find gaps // that the serving node is missing - hasher := sha3.NewLegacyKeccak256() + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + hash := make([]byte, 32) codes := make([][]byte, len(req.hashes)) for i, j := 0, 0; i < len(bytecodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() hasher.Write(bytecodes[i]) - hash := hasher.Sum(nil) + hasher.Read(hash) for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ @@ -2350,14 +2351,15 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { // Cross reference the requested trienodes with the response to find gaps // that the serving node is missing - hasher := sha3.NewLegacyKeccak256() + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + hash := make([]byte, 32) nodes := make([][]byte, len(req.hashes)) for i, j := 0, 0; i < len(trienodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() hasher.Write(trienodes[i]) - hash := hasher.Sum(nil) + hasher.Read(hash) for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ @@ -2437,14 +2439,15 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro // Cross reference the requested bytecodes with the response to find gaps // that the serving node is missing - hasher := sha3.NewLegacyKeccak256() + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + hash := make([]byte, 32) codes := make([][]byte, len(req.hashes)) for i, j := 0, 0; i < len(bytecodes); i++ { // Find the next hash that we've been served, leaving misses with nils hasher.Reset() hasher.Write(bytecodes[i]) - hash := hasher.Sum(nil) + hasher.Read(hash) for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) { j++ diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go new file mode 100644 index 0000000000..4f28b99bfe --- /dev/null +++ b/eth/protocols/snap/sync_test.go @@ -0,0 +1,98 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "crypto/rand" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/crypto/sha3" +) + +func TestHashing(t *testing.T) { + var bytecodes = make([][]byte, 10) + for i := 0; i < len(bytecodes); i++ { + buf := make([]byte, 100) + rand.Read(buf) + bytecodes[i] = buf + } + var want, got string + var old = func() { + hasher := sha3.NewLegacyKeccak256() + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hash := hasher.Sum(nil) + got = fmt.Sprintf("%v\n%v", got, hash) + } + } + var new = func() { + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + var hash = make([]byte, 32) + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + want = fmt.Sprintf("%v\n%v", want, hash) + } + } + old() + new() + if want != got { + t.Errorf("want\n%v\ngot\n%v\n", want, got) + } +} + +func BenchmarkHashing(b *testing.B) { + var bytecodes = make([][]byte, 10000) + for i := 0; i < len(bytecodes); i++ { + buf := make([]byte, 100) + rand.Read(buf) + bytecodes[i] = buf + } + var old = func() { + hasher := sha3.NewLegacyKeccak256() + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Sum(nil) + } + } + var new = func() { + hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState) + var hash = make([]byte, 32) + for i := 0; i < len(bytecodes); i++ { + hasher.Reset() + hasher.Write(bytecodes[i]) + hasher.Read(hash) + } + } + b.Run("old", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + old() + } + }) + b.Run("new", func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + new() + } + }) +} From 3c6665e7d62ed166d9f1cf4519ad23ab77c5cae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 7 Jan 2021 13:04:20 +0200 Subject: [PATCH 238/948] cmd/faucet: switch Facebook auth over to mobile site --- cmd/faucet/faucet.go | 10 +++++++-- cmd/faucet/faucet_test.go | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 cmd/faucet/faucet_test.go diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index a0fc28c0d4..2c0881f5a2 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . -// faucet is a Ether faucet backed by a light client. +// faucet is an Ether faucet backed by a light client. package main //go:generate go-bindata -nometadata -o website.go faucet.html @@ -847,7 +847,13 @@ func authFacebook(url string) (string, string, common.Address, error) { // Facebook's Graph API isn't really friendly with direct links. Still, we don't // want to do ask read permissions from users, so just load the public posts and // scrape it for the Ethereum address and profile URL. - res, err := http.Get(url) + // + // Facebook recently changed their desktop webpage to use AJAX for loading post + // content, so switch over to the mobile site for now. Will probably end up having + // to use the API eventually. + crawl := strings.Replace(url, "www.facebook.com", "m.facebook.com", 1) + + res, err := http.Get(crawl) if err != nil { return "", "", common.Address{}, err } diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go new file mode 100644 index 0000000000..4f3e47084e --- /dev/null +++ b/cmd/faucet/faucet_test.go @@ -0,0 +1,43 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestFacebook(t *testing.T) { + for _, tt := range []struct { + url string + want common.Address + }{ + { + "https://www.facebook.com/fooz.gazonk/posts/2837228539847129", + common.HexToAddress("0xDeadDeaDDeaDbEefbEeFbEEfBeeFBeefBeeFbEEF"), + }, + } { + _, _, gotAddress, err := authFacebook(tt.url) + if err != nil { + t.Fatal(err) + } + if gotAddress != tt.want { + t.Fatalf("address wrong, have %v want %v", gotAddress, tt.want) + } + } +} From 165f53fc6e9e904054c67462000a19fc83dcc12f Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 8 Jan 2021 06:39:35 +0800 Subject: [PATCH 239/948] les: remove transaction propagation limits (#22125) --- les/txrelay.go | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/les/txrelay.go b/les/txrelay.go index 57f2412eba..9d29b2f234 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -25,13 +25,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -type ltrInfo struct { - tx *types.Transaction - sentTo map[*serverPeer]struct{} -} - type lesTxRelay struct { - txSent map[common.Hash]*ltrInfo + txSent map[common.Hash]*types.Transaction txPending map[common.Hash]struct{} peerList []*serverPeer peerStartPos int @@ -43,7 +38,7 @@ type lesTxRelay struct { func newLesTxRelay(ps *serverPeerSet, retriever *retrieveManager) *lesTxRelay { r := &lesTxRelay{ - txSent: make(map[common.Hash]*ltrInfo), + txSent: make(map[common.Hash]*types.Transaction), txPending: make(map[common.Hash]struct{}), retriever: retriever, stop: make(chan struct{}), @@ -80,8 +75,7 @@ func (ltrx *lesTxRelay) unregisterPeer(p *serverPeer) { } } -// send sends a list of transactions to at most a given number of peers at -// once, never resending any particular transaction to the same peer twice +// send sends a list of transactions to at most a given number of peers. func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { sendTo := make(map[*serverPeer]types.Transactions) @@ -92,26 +86,18 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { for _, tx := range txs { hash := tx.Hash() - ltr, ok := ltrx.txSent[hash] + _, ok := ltrx.txSent[hash] if !ok { - ltr = <rInfo{ - tx: tx, - sentTo: make(map[*serverPeer]struct{}), - } - ltrx.txSent[hash] = ltr + ltrx.txSent[hash] = tx ltrx.txPending[hash] = struct{}{} } - if len(ltrx.peerList) > 0 { cnt := count pos := ltrx.peerStartPos for { peer := ltrx.peerList[pos] - if _, ok := ltr.sentTo[peer]; !ok { - sendTo[peer] = append(sendTo[peer], tx) - ltr.sentTo[peer] = struct{}{} - cnt-- - } + sendTo[peer] = append(sendTo[peer], tx) + cnt-- if cnt == 0 { break // sent it to the desired number of peers } @@ -174,7 +160,7 @@ func (ltrx *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback txs := make(types.Transactions, len(ltrx.txPending)) i := 0 for hash := range ltrx.txPending { - txs[i] = ltrx.txSent[hash].tx + txs[i] = ltrx.txSent[hash] i++ } ltrx.send(txs, 1) From 6b88ab75bcbc5eaecaf5619ec730aa00f5e7c941 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 8 Jan 2021 11:17:15 +0100 Subject: [PATCH 240/948] cmd/faucet: fix nonce-gap problem (#22145) * cmd/faucet: avoid encoding for each client * cmd/faucet: fix flaw in clearing of txs, avoid sending more than necessary * cmd/faucet: fix flaw in tx cropping * cmd/faucet: revert change to not always send tx info * cmd/faucet: review fixes * cmd/faucet: revert #22018, fix order in UI * cmd/faucet: fix lock error * cmd/faucet: revert json changes * squashme --- cmd/faucet/faucet.go | 5 ++--- cmd/faucet/faucet.html | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 2c0881f5a2..b9c4e1819a 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -512,12 +512,12 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } continue } - f.reqs = append([]*request{{ + f.reqs = append(f.reqs, &request{ Avatar: avatar, Account: address, Time: time.Now(), Tx: signed, - }}, f.reqs...) + }) timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace @@ -670,7 +670,6 @@ func send(conn *wsConn, value interface{}, timeout time.Duration) error { } conn.wlock.Lock() defer conn.wlock.Unlock() - conn.conn.SetWriteDeadline(time.Now().Add(timeout)) return conn.conn.WriteJSON(value) } diff --git a/cmd/faucet/faucet.html b/cmd/faucet/faucet.html index ba14333186..dad5ad84f2 100644 --- a/cmd/faucet/faucet.html +++ b/cmd/faucet/faucet.html @@ -177,7 +177,7 @@ } // Iterate over our entire local collection and re-render the funding table var content = ""; - for (var i=0; i= 0; i--) { var done = requests[i].time == ""; var elapsed = moment().unix()-moment(requests[i].time).unix(); From 889f5645b57dde5b5d4cccf1561bdb449293d2d8 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 8 Jan 2021 21:29:25 +0100 Subject: [PATCH 241/948] ethclient: better test suite for ethclient package (#22127) This commit extends the ethclient test suite and increases code coverage of the ethclient package from ~15% to >55%. These tests act as early smoke tests to signal issues in the RPC-interface. E.g. if a functionality like eth_chainId or eth_call breaks, the test will break. --- ethclient/ethclient_test.go | 259 ++++++++++++++++++++++++++++++++---- 1 file changed, 231 insertions(+), 28 deletions(-) diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 0ca72c6ee7..d700022e8f 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -17,6 +17,7 @@ package ethclient import ( + "bytes" "context" "errors" "fmt" @@ -35,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" ) // Verify that Client implements the ethereum interfaces. @@ -229,12 +231,48 @@ func generateTestChain() (*core.Genesis, []*types.Block) { return genesis, blocks } -func TestHeader(t *testing.T) { +func TestEthClient(t *testing.T) { backend, chain := newTestBackend(t) client, _ := backend.Attach() defer backend.Close() defer client.Close() + tests := map[string]struct { + test func(t *testing.T) + }{ + "TestHeader": { + func(t *testing.T) { testHeader(t, chain, client) }, + }, + "TestBalanceAt": { + func(t *testing.T) { testBalanceAt(t, client) }, + }, + "TestTxInBlockInterrupted": { + func(t *testing.T) { testTransactionInBlockInterrupted(t, client) }, + }, + "TestChainID": { + func(t *testing.T) { testChainID(t, client) }, + }, + "TestGetBlock": { + func(t *testing.T) { testGetBlock(t, client) }, + }, + "TestStatusFunctions": { + func(t *testing.T) { testStatusFunctions(t, client) }, + }, + "TestCallContract": { + func(t *testing.T) { testCallContract(t, client) }, + }, + "TestAtFunctions": { + func(t *testing.T) { testAtFunctions(t, client) }, + }, + } + + t.Parallel() + for name, tt := range tests { + t.Run(name, tt.test) + } +} + +func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { tests := map[string]struct { block *big.Int want *types.Header @@ -273,12 +311,7 @@ func TestHeader(t *testing.T) { } } -func TestBalanceAt(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() - +func testBalanceAt(t *testing.T, client *rpc.Client) { tests := map[string]struct { account common.Address block *big.Int @@ -319,31 +352,32 @@ func TestBalanceAt(t *testing.T) { } } -func TestTransactionInBlockInterrupted(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() - +func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { ec := NewClient(client) + + // Get current block by number + block, err := ec.BlockByNumber(context.Background(), nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // Test tx in block interupted ctx, cancel := context.WithCancel(context.Background()) cancel() - tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1) + tx, err := ec.TransactionInBlock(ctx, block.Hash(), 1) if tx != nil { t.Fatal("transaction should be nil") } - if err == nil { - t.Fatal("error should not be nil") + if err == nil || err == ethereum.NotFound { + t.Fatal("error should not be nil/notfound") + } + // Test tx in block not found + if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != ethereum.NotFound { + t.Fatal("error should be ethereum.NotFound") } } -func TestChainID(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() +func testChainID(t *testing.T, client *rpc.Client) { ec := NewClient(client) - id, err := ec.ChainID(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -353,13 +387,9 @@ func TestChainID(t *testing.T) { } } -func TestBlockNumber(t *testing.T) { - backend, _ := newTestBackend(t) - client, _ := backend.Attach() - defer backend.Close() - defer client.Close() +func testGetBlock(t *testing.T, client *rpc.Client) { ec := NewClient(client) - + // Get current block number blockNumber, err := ec.BlockNumber(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -367,4 +397,177 @@ func TestBlockNumber(t *testing.T) { if blockNumber != 1 { t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) } + // Get current block by number + block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.NumberU64() != blockNumber { + t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64()) + } + // Get current block by hash + blockH, err := ec.BlockByHash(context.Background(), block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Hash() != blockH.Hash() { + t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex()) + } + // Get header by number + header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Header().Hash() != header.Hash() { + t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex()) + } + // Get header by hash + headerH, err := ec.HeaderByHash(context.Background(), block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if block.Header().Hash() != headerH.Hash() { + t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex()) + } +} + +func testStatusFunctions(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // Sync progress + progress, err := ec.SyncProgress(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if progress != nil { + t.Fatalf("unexpected progress: %v", progress) + } + // NetworkID + networkID, err := ec.NetworkID(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if networkID.Cmp(big.NewInt(0)) != 0 { + t.Fatalf("unexpected networkID: %v", networkID) + } + // SuggestGasPrice (should suggest 1 Gwei) + gasPrice, err := ec.SuggestGasPrice(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gasPrice.Cmp(big.NewInt(1000000000)) != 0 { + t.Fatalf("unexpected gas price: %v", gasPrice) + } +} + +func testCallContract(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(1), + Value: big.NewInt(1), + } + gas, err := ec.EstimateGas(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gas != 21000 { + t.Fatalf("unexpected gas price: %v", gas) + } + // CallContract + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // PendingCallCOntract + if _, err := ec.PendingCallContract(context.Background(), msg); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func testAtFunctions(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + // send a transaction for some interesting pending status + sendTransaction(ec) + time.Sleep(100 * time.Millisecond) + // Check pending transaction count + pending, err := ec.PendingTransactionCount(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if pending != 1 { + t.Fatalf("unexpected pending, wanted 1 got: %v", pending) + } + // Query balance + balance, err := ec.BalanceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if balance.Cmp(penBalance) == 0 { + t.Fatalf("unexpected balance: %v %v", balance, penBalance) + } + // NonceAt + nonce, err := ec.NonceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penNonce, err := ec.PendingNonceAt(context.Background(), testAddr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if penNonce != nonce+1 { + t.Fatalf("unexpected nonce: %v %v", nonce, penNonce) + } + // StorageAt + storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(storage, penStorage) { + t.Fatalf("unexpected storage: %v %v", storage, penStorage) + } + // CodeAt + code, err := ec.CodeAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + penCode, err := ec.PendingCodeAt(context.Background(), testAddr) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(code, penCode) { + t.Fatalf("unexpected code: %v %v", code, penCode) + } +} + +func sendTransaction(ec *Client) error { + // Retrieve chainID + chainID, err := ec.ChainID(context.Background()) + if err != nil { + return err + } + // Create transaction + tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) + signer := types.NewEIP155Signer(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + if err != nil { + return err + } + signedTx, err := tx.WithSignature(signer, signature) + if err != nil { + return err + } + // Send transaction + return ec.SendTransaction(context.Background(), signedTx) } From 89030ec0b442a08fb82d4452f3132ab1602b8182 Mon Sep 17 00:00:00 2001 From: Chris Ziogas Date: Sat, 9 Jan 2021 18:29:19 +0200 Subject: [PATCH 242/948] eth/downloader: fix race condition in tests (#22140) * downloader: fix race condition in tests * eth/downloader: fix race condition in tests * Revert "downloader: fix race condition in tests" This reverts commit 108033ebc6985de83791d375b6e6647a77d28d5a. --- eth/downloader/downloader_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 6578275d0c..5de1ef3f8e 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -584,14 +584,15 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { time.Sleep(25 * time.Millisecond) tester.lock.Lock() + tester.downloader.queue.lock.Lock() + tester.downloader.queue.resultCache.lock.Lock() { - tester.downloader.queue.resultCache.lock.Lock() cached = tester.downloader.queue.resultCache.countCompleted() - tester.downloader.queue.resultCache.lock.Unlock() frozen = int(atomic.LoadUint32(&blocked)) retrieved = len(tester.ownBlocks) - } + tester.downloader.queue.resultCache.lock.Unlock() + tester.downloader.queue.lock.Unlock() tester.lock.Unlock() if cached == blockCacheMaxItems || From 5a1b38435270336fd86fe742c9951abad870b84d Mon Sep 17 00:00:00 2001 From: gary rong Date: Sun, 10 Jan 2021 19:54:15 +0800 Subject: [PATCH 243/948] core: persist bad blocks (#21827) * core: persist bad blocks * core, eth, internal: address comments * core/rawdb: add badblocks to inspector * core, eth: update * internal: revert * core, eth: only save 10 bad blocks * core/rawdb: address comments * core/rawdb: fix * core: address comments --- core/blockchain.go | 23 +------ core/rawdb/accessors_chain.go | 97 ++++++++++++++++++++++++++++++ core/rawdb/accessors_chain_test.go | 70 +++++++++++++++++++++ core/rawdb/database.go | 2 +- core/rawdb/schema.go | 8 ++- eth/api.go | 31 ++++++---- eth/api_tracer.go | 6 +- 7 files changed, 196 insertions(+), 41 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d9505dcf69..b8f483b85e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -89,7 +89,6 @@ const ( txLookupCacheLimit = 1024 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 - badBlockLimit = 10 TriesInMemory = 128 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. @@ -208,7 +207,6 @@ type BlockChain struct { processor Processor // Block transaction processor interface vmConfig vm.Config - badBlocks *lru.Cache // Bad block cache shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format. @@ -227,7 +225,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par blockCache, _ := lru.New(blockCacheLimit) txLookupCache, _ := lru.New(txLookupCacheLimit) futureBlocks, _ := lru.New(maxFutureBlocks) - badBlocks, _ := lru.New(badBlockLimit) bc := &BlockChain{ chainConfig: chainConfig, @@ -249,7 +246,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par futureBlocks: futureBlocks, engine: engine, vmConfig: vmConfig, - badBlocks: badBlocks, } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) @@ -2374,26 +2370,9 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { } } -// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network -func (bc *BlockChain) BadBlocks() []*types.Block { - blocks := make([]*types.Block, 0, bc.badBlocks.Len()) - for _, hash := range bc.badBlocks.Keys() { - if blk, exist := bc.badBlocks.Peek(hash); exist { - block := blk.(*types.Block) - blocks = append(blocks, block) - } - } - return blocks -} - -// addBadBlock adds a bad block to the bad-block LRU cache -func (bc *BlockChain) addBadBlock(block *types.Block) { - bc.badBlocks.Add(block.Hash(), block) -} - // reportBlock logs a bad block error. func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) { - bc.addBadBlock(block) + rawdb.WriteBadBlock(bc.db, block) var receiptString string for i, receipt := range receipts { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index c948cdc7c6..461e1cbb17 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "math/big" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -702,6 +703,102 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number DeleteTd(db, hash, number) } +const badBlockToKeep = 10 + +type badBlock struct { + Header *types.Header + Body *types.Body +} + +// badBlockList implements the sort interface to allow sorting a list of +// bad blocks by their number in the reverse order. +type badBlockList []*badBlock + +func (s badBlockList) Len() int { return len(s) } +func (s badBlockList) Less(i, j int) bool { + return s[i].Header.Number.Uint64() < s[j].Header.Number.Uint64() +} +func (s badBlockList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// ReadBadBlock retrieves the bad block with the corresponding block hash. +func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { + blob, err := db.Get(badBlockKey) + if err != nil { + return nil + } + var badBlocks badBlockList + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + return nil + } + for _, bad := range badBlocks { + if bad.Header.Hash() == hash { + return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles) + } + } + return nil +} + +// ReadAllBadBlocks retrieves all the bad blocks in the database. +// All returned blocks are sorted in reverse order by number. +func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { + blob, err := db.Get(badBlockKey) + if err != nil { + return nil + } + var badBlocks badBlockList + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + return nil + } + var blocks []*types.Block + for _, bad := range badBlocks { + blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)) + } + return blocks +} + +// WriteBadBlock serializes the bad block into the database. If the cumulated +// bad blocks exceeds the limitation, the oldest will be dropped. +func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { + blob, err := db.Get(badBlockKey) + if err != nil { + log.Warn("Failed to load old bad blocks", "error", err) + } + var badBlocks badBlockList + if len(blob) > 0 { + if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { + log.Crit("Failed to decode old bad blocks", "error", err) + } + } + for _, b := range badBlocks { + if b.Header.Number.Uint64() == block.NumberU64() && b.Header.Hash() == block.Hash() { + log.Info("Skip duplicated bad block", "number", block.NumberU64(), "hash", block.Hash()) + return + } + } + badBlocks = append(badBlocks, &badBlock{ + Header: block.Header(), + Body: block.Body(), + }) + sort.Sort(sort.Reverse(badBlocks)) + if len(badBlocks) > badBlockToKeep { + badBlocks = badBlocks[:badBlockToKeep] + } + data, err := rlp.EncodeToBytes(badBlocks) + if err != nil { + log.Crit("Failed to encode bad blocks", "err", err) + } + if err := db.Put(badBlockKey, data); err != nil { + log.Crit("Failed to write bad blocks", "err", err) + } +} + +// DeleteBadBlocks deletes all the bad blocks from the database +func DeleteBadBlocks(db ethdb.KeyValueWriter) { + if err := db.Delete(badBlockKey); err != nil { + log.Crit("Failed to delete bad blocks", "err", err) + } +} + // FindCommonAncestor returns the last common ancestor of two block headers func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 074c24d8fe..a5804cd309 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "math/big" + "math/rand" "os" "reflect" "testing" @@ -188,6 +189,75 @@ func TestPartialBlockStorage(t *testing.T) { } } +// Tests block storage and retrieval operations. +func TestBadBlockStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test block to move around the database and make sure it's really new + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(1), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + if entry := ReadBadBlock(db, block.Hash()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + // Write and verify the block in the database + WriteBadBlock(db, block) + if entry := ReadBadBlock(db, block.Hash()); entry == nil { + t.Fatalf("Stored block not found") + } else if entry.Hash() != block.Hash() { + t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) + } + // Write one more bad block + blockTwo := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(2), + Extra: []byte("bad block two"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + WriteBadBlock(db, blockTwo) + + // Write the block one again, should be filtered out. + WriteBadBlock(db, block) + badBlocks := ReadAllBadBlocks(db) + if len(badBlocks) != 2 { + t.Fatalf("Failed to load all bad blocks") + } + + // Write a bunch of bad blocks, all the blocks are should sorted + // in reverse order. The extra blocks should be truncated. + for _, n := range rand.Perm(100) { + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(int64(n)), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + WriteBadBlock(db, block) + } + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != badBlockToKeep { + t.Fatalf("The number of persised bad blocks in incorrect %d", len(badBlocks)) + } + for i := 0; i < len(badBlocks)-1; i++ { + if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() { + t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64()) + } + } + + // Delete all bad blocks + DeleteBadBlocks(db) + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != 0 { + t.Fatalf("Failed to delete bad blocks") + } +} + // Tests block total difficulty storage and retrieval operations. func TestTdStorage(t *testing.T) { db := NewMemoryDatabase() diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b01a31ebcd..a2cc9c7be9 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} { + for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey} { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 2aabfd3baa..9749a30d8a 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -66,6 +66,12 @@ var ( // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") + // badBlockKey tracks the list of bad blocks seen by local + badBlockKey = []byte("InvalidBlock") + + // uncleanShutdownKey tracks the list of local crashes + uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td @@ -84,8 +90,6 @@ var ( preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db - uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db - // Chain index prefixes (use `i` + single byte to avoid mixing data types). BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress diff --git a/eth/api.go b/eth/api.go index fd35656476..6cd0fd7005 100644 --- a/eth/api.go +++ b/eth/api.go @@ -331,22 +331,29 @@ type BadBlockArgs struct { // GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network // and returns them as a JSON list of block-hashes func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { - blocks := api.eth.BlockChain().BadBlocks() - results := make([]*BadBlockArgs, len(blocks)) - - var err error - for i, block := range blocks { - results[i] = &BadBlockArgs{ - Hash: block.Hash(), - } + var ( + err error + blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb) + results = make([]*BadBlockArgs, 0, len(blocks)) + ) + for _, block := range blocks { + var ( + blockRlp string + blockJSON map[string]interface{} + ) if rlpBytes, err := rlp.EncodeToBytes(block); err != nil { - results[i].RLP = err.Error() // Hacky, but hey, it works + blockRlp = err.Error() // Hacky, but hey, it works } else { - results[i].RLP = fmt.Sprintf("0x%x", rlpBytes) + blockRlp = fmt.Sprintf("0x%x", rlpBytes) } - if results[i].Block, err = ethapi.RPCMarshalBlock(block, true, true); err != nil { - results[i].Block = map[string]interface{}{"error": err.Error()} + if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true); err != nil { + blockJSON = map[string]interface{}{"error": err.Error()} } + results = append(results, &BadBlockArgs{ + Hash: block.Hash(), + RLP: blockRlp, + Block: blockJSON, + }) } return results, nil } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index c68b762255..8e71945ee2 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -404,8 +404,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, // EVM against a block pulled from the pool of bad ones and returns them as a JSON // object. func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - blocks := api.eth.blockchain.BadBlocks() - for _, block := range blocks { + for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { if block.Hash() == hash { return api.traceBlock(ctx, block, config) } @@ -428,8 +427,7 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c // execution of EVM against a block pulled from the pool of bad ones to the // local file system and returns a list of files to the caller. func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { - blocks := api.eth.blockchain.BadBlocks() - for _, block := range blocks { + for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { if block.Hash() == hash { return api.standardTraceBlockToFile(ctx, block, config) } From ab5e3f400f9dd3832508208ecef8b75c681728ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 11 Jan 2021 10:31:03 +0200 Subject: [PATCH 244/948] common/prque: pull in tests and benchmarks from upstream --- common/prque/prque_test.go | 130 ++++++++++++++++++++++++++++++++++++ common/prque/sstack_test.go | 100 +++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 common/prque/prque_test.go create mode 100644 common/prque/sstack_test.go diff --git a/common/prque/prque_test.go b/common/prque/prque_test.go new file mode 100644 index 0000000000..1cffcebad4 --- /dev/null +++ b/common/prque/prque_test.go @@ -0,0 +1,130 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "testing" +) + +func TestPrque(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New(nil) + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], int64(prio[i])) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int64]int) + for i := 0; i < size; i++ { + dict[int64(prio[i])] = data[i] + } + // Pop out the elements in priority order and verify them + prevPrio := int64(size + 1) + for !queue.Empty() { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + } +} + +func TestReset(t *testing.T) { + // Generate a batch of random data and a specific priority order + size := 16 * blockSize + prio := rand.Perm(size) + data := make([]int, size) + for i := 0; i < size; i++ { + data[i] = rand.Int() + } + queue := New(nil) + for rep := 0; rep < 2; rep++ { + // Fill a priority queue with the above data + for i := 0; i < size; i++ { + queue.Push(data[i], int64(prio[i])) + if queue.Size() != i+1 { + t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1) + } + } + // Create a map the values to the priorities for easier verification + dict := make(map[int64]int) + for i := 0; i < size; i++ { + dict[int64(prio[i])] = data[i] + } + // Pop out half the elements in priority order and verify them + prevPrio := int64(size + 1) + for i := 0; i < size/2; i++ { + val, prio := queue.Pop() + if prio > prevPrio { + t.Errorf("invalid priority order: %v after %v.", prio, prevPrio) + } + prevPrio = prio + if val != dict[prio] { + t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio]) + } + delete(dict, prio) + } + // Reset and ensure it's empty + queue.Reset() + if !queue.Empty() { + t.Errorf("priority queue not empty after reset: %v", queue) + } + } +} + +func BenchmarkPush(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + // Execute the benchmark + b.ResetTimer() + queue := New(nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } +} + +func BenchmarkPop(b *testing.B) { + // Create some initial data + data := make([]int, b.N) + prio := make([]int64, b.N) + for i := 0; i < len(data); i++ { + data[i] = rand.Int() + prio[i] = rand.Int63() + } + queue := New(nil) + for i := 0; i < len(data); i++ { + queue.Push(data[i], prio[i]) + } + // Execute the benchmark + b.ResetTimer() + for !queue.Empty() { + queue.Pop() + } +} diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go new file mode 100644 index 0000000000..2ff093579d --- /dev/null +++ b/common/prque/sstack_test.go @@ -0,0 +1,100 @@ +// CookieJar - A contestant's algorithm toolbox +// Copyright (c) 2013 Peter Szilagyi. All rights reserved. +// +// CookieJar is dual licensed: use of this source code is governed by a BSD +// license that can be found in the LICENSE file. Alternatively, the CookieJar +// toolbox may be used in accordance with the terms and conditions contained +// in a signed written agreement between you and the author(s). + +package prque + +import ( + "math/rand" + "sort" + "testing" +) + +func TestSstack(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), rand.Int63()} + } + stack := newSstack(nil) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item)) + } + } + rest := []*item{} + for stack.Len() > 0 { + rest = append(rest, stack.Pop().(*item)) + } + // Make sure the contents of the resulting slices are ok + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + if i%2 == 1 && data[i] != rest[len(rest)-i/2-1] { + t.Errorf("push/pop mismatch: have %v, want %v.", rest[len(rest)-i/2-1], data[i]) + } + } + } +} + +func TestSstackSort(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), int64(i)} + } + // Push all the data into the stack + stack := newSstack(nil) + for _, val := range data { + stack.Push(val) + } + // Sort and pop the stack contents (should reverse the order) + sort.Sort(stack) + for _, val := range data { + out := stack.Pop() + if out != val { + t.Errorf("push/pop mismatch after sort: have %v, want %v.", out, val) + } + } +} + +func TestSstackReset(t *testing.T) { + // Create some initial data + size := 16 * blockSize + data := make([]*item, size) + for i := 0; i < size; i++ { + data[i] = &item{rand.Int(), rand.Int63()} + } + stack := newSstack(nil) + for rep := 0; rep < 2; rep++ { + // Push all the data into the stack, pop out every second + secs := []*item{} + for i := 0; i < size; i++ { + stack.Push(data[i]) + if i%2 == 0 { + secs = append(secs, stack.Pop().(*item)) + } + } + // Reset and verify both pulled and stack contents + stack.Reset() + if stack.Len() != 0 { + t.Errorf("stack not empty after reset: %v", stack) + } + for i := 0; i < size; i++ { + if i%2 == 0 && data[i] != secs[i/2] { + t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i]) + } + } + } +} From 49c2816d5464ed208389d52ed7cf47f35630b1c2 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 11 Jan 2021 12:53:13 +0100 Subject: [PATCH 245/948] eth: improve log message (#22146) * eth: fixed typos * eth: fixed log message --- eth/handler.go | 6 +++--- eth/peerset.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 76a429f6d3..a9506c4995 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -437,14 +437,14 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) // Broadcast transactions to a batch of peers not knowing about it if propagate { for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) + peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] for _, peer := range transfer { txset[peer] = append(txset[peer], tx.Hash()) } - log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers)) + log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(transfer)) } for peer, hashes := range txset { peer.AsyncSendTransactions(hashes) @@ -453,7 +453,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) } // Otherwise only broadcast the announcement to peers for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransacion(tx.Hash()) + peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) for _, peer := range peers { annos[peer] = append(annos[peer], tx.Hash()) } diff --git a/eth/peerset.go b/eth/peerset.go index 9b584ec320..bf5785ff3f 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -243,9 +243,9 @@ func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { return list } -// ethPeersWithoutTransacion retrieves a list of `eth` peers that do not have a +// ethPeersWithoutTransaction retrieves a list of `eth` peers that do not have a // given transaction in their set of known hashes. -func (ps *peerSet) ethPeersWithoutTransacion(hash common.Hash) []*ethPeer { +func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() From 39b3b8ffb44983a260031d5077226d952ddfb9bf Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 11 Jan 2021 14:55:42 +0100 Subject: [PATCH 246/948] graphql: fix issue with unmarshalling int32 into `Long` type #22153 --- graphql/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/schema.go b/graphql/schema.go index 1fdc370040..6ea63db636 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -300,7 +300,7 @@ const schema string = ` block(number: Long, hash: Bytes32): Block # Blocks returns all the blocks between two numbers, inclusive. If # to is not supplied, it defaults to the most recent known block. - blocks(from: Long!, to: Long): [Block!]! + blocks(from: Long, to: Long): [Block!]! # Pending returns the current pending state. pending: Pending! # Transaction returns a transaction specified by its hash. From 984e752ce52b09142ed936c337456df27128e3bc Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 12 Jan 2021 10:52:13 +0100 Subject: [PATCH 247/948] eth: return error from eth_chainID during sync before EIP-155 activates (#21686) This changes the chainID RPC method to return an error when EIP-155 is not yet active at the current block height. It used to simply return zero in this case, but that's confusing. --- eth/api.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/api.go b/eth/api.go index 6cd0fd7005..be1dcbb52a 100644 --- a/eth/api.go +++ b/eth/api.go @@ -67,12 +67,12 @@ func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { } // ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. -func (api *PublicEthereumAPI) ChainId() hexutil.Uint64 { - chainID := new(big.Int) +func (api *PublicEthereumAPI) ChainId() (hexutil.Uint64, error) { + // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config if config := api.e.blockchain.Config(); config.IsEIP155(api.e.blockchain.CurrentBlock().Number()) { - chainID = config.ChainID + return (hexutil.Uint64)(config.ChainID.Uint64()), nil } - return (hexutil.Uint64)(chainID.Uint64()) + return hexutil.Uint64(0), fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") } // PublicMinerAPI provides an API to control the miner. From 23f837c38827d7c1ea67b71eb6f79934562f0d98 Mon Sep 17 00:00:00 2001 From: meowsbits Date: Tue, 12 Jan 2021 08:50:11 -0600 Subject: [PATCH 248/948] cmd/utils: avoid making console preloads absolute (#22109) Resolves https://github.com/etclabscore/core-geth/issues/273 jsre.JSRE already handles establishing preload file paths relative to the 'assets' path (aka docroot), where it joins the assets dir and the file path if relative, or uses the file path only if absolute. The duplication of this logic by MakeConsolePreloads caused preloaded files to have paths which contained duplicate references to the assets dir path. Date: 2020-12-30 08:25:01-06:00 Signed-off-by: meows --- cmd/utils/flags.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 684e3428ba..fc64539a7c 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1908,9 +1908,8 @@ func MakeConsolePreloads(ctx *cli.Context) []string { // Otherwise resolve absolute paths and return them var preloads []string - assets := ctx.GlobalString(JSpathFlag.Name) for _, file := range strings.Split(ctx.GlobalString(PreloadJSFlag.Name), ",") { - preloads = append(preloads, common.AbsolutePath(assets, strings.TrimSpace(file))) + preloads = append(preloads, strings.TrimSpace(file)) } return preloads } From 93a89b26819c9b21ff32ab650b8916076b53b471 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 12 Jan 2021 17:39:31 +0100 Subject: [PATCH 249/948] go.mod: use github.com/holiman/bloomfilter/v2 (#22044) * deps: use improved bloom filter implementation * eth/handler, trie: use 4 keys for syncbloom + minor fixes * eth/protocols, trie: revert change on syncbloom method signature --- core/state/snapshot/difflayer.go | 2 +- go.mod | 7 ++--- go.sum | 34 ++------------------- trie/sync.go | 2 +- trie/sync_bloom.go | 51 ++++++++------------------------ 5 files changed, 18 insertions(+), 78 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 3be78cae88..cc82df9a54 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" - "github.com/steakknife/bloomfilter" + bloomfilter "github.com/holiman/bloomfilter/v2" ) var ( diff --git a/go.mod b/go.mod index 0f47809ef6..57c15a34f6 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 - github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 // indirect github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c github.com/fatih/color v1.3.0 github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc @@ -32,6 +31,7 @@ require ( github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 github.com/hashicorp/golang-lru v0.5.4 + github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v1.0.0 github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 @@ -54,14 +54,11 @@ require ( github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 - github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 - github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/mobile v0.0.0-20200801112145-973feb4309de // indirect golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 golang.org/x/text v0.3.3 @@ -69,5 +66,5 @@ require ( gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 - gotest.tools v2.2.0+incompatible + gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index d1bd608585..d063cd8f08 100644 --- a/go.sum +++ b/go.sum @@ -20,7 +20,6 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -60,8 +59,6 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU= -github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= @@ -95,8 +92,6 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 h1:lMm2hD9Fy0ynom5+85/pbdkiYcBqM1JWmhpAXLmy0fw= -github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -104,8 +99,6 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= @@ -114,6 +107,8 @@ github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKx github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -200,10 +195,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= -github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= -github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= -github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -217,26 +208,12 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de h1:OVJ6QQUBAesB8CZijKDSsXX7xYVtUhrkY0gwMfbi4p4= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= @@ -244,7 +221,6 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -266,12 +242,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/trie/sync.go b/trie/sync.go index bc93ddd3fb..05b9f55d33 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -410,7 +410,7 @@ func (s *Sync) children(req *request, object node) ([]*request, error) { // Bloom filter says this might be a duplicate, double check. // If database says yes, then at least the trie node is present // and we hold the assumption that it's NOT legacy contract code. - if blob := rawdb.ReadTrieNode(s.database, common.BytesToHash(node)); len(blob) > 0 { + if blob := rawdb.ReadTrieNode(s.database, hash); len(blob) > 0 { continue } // False positive, bump fault meter diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go index 979f4748f3..1afcce21da 100644 --- a/trie/sync_bloom.go +++ b/trie/sync_bloom.go @@ -19,7 +19,6 @@ package trie import ( "encoding/binary" "fmt" - "math" "sync" "sync/atomic" "time" @@ -29,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/steakknife/bloomfilter" + bloomfilter "github.com/holiman/bloomfilter/v2" ) var ( @@ -41,18 +40,6 @@ var ( bloomErrorGauge = metrics.NewRegisteredGauge("trie/bloom/error", nil) ) -// syncBloomHasher is a wrapper around a byte blob to satisfy the interface API -// requirements of the bloom library used. It's used to convert a trie hash or -// contract code hash into a 64 bit mini hash. -type syncBloomHasher []byte - -func (f syncBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } -func (f syncBloomHasher) Sum(b []byte) []byte { panic("not implemented") } -func (f syncBloomHasher) Reset() { panic("not implemented") } -func (f syncBloomHasher) BlockSize() int { panic("not implemented") } -func (f syncBloomHasher) Size() int { return 8 } -func (f syncBloomHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } - // SyncBloom is a bloom filter used during fast sync to quickly decide if a trie // node or contract code already exists on disk or not. It self populates from the // provided disk database on creation in a background thread and will only start @@ -69,7 +56,7 @@ type SyncBloom struct { // initializes it from the database. The bloom is hard coded to use 3 filters. func NewSyncBloom(memory uint64, database ethdb.Iteratee) *SyncBloom { // Create the bloom filter to track known trie nodes - bloom, err := bloomfilter.New(memory*1024*1024*8, 3) + bloom, err := bloomfilter.New(memory*1024*1024*8, 4) if err != nil { panic(fmt.Sprintf("failed to create bloom: %v", err)) } @@ -110,12 +97,11 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { // If the database entry is a trie node, add it to the bloom key := it.Key() if len(key) == common.HashLength { - b.bloom.Add(syncBloomHasher(key)) + b.bloom.AddHash(binary.BigEndian.Uint64(key)) bloomLoadMeter.Mark(1) - } - // If the database entry is a contract code, add it to the bloom - if ok, hash := rawdb.IsCodeKey(key); ok { - b.bloom.Add(syncBloomHasher(hash)) + } else if ok, hash := rawdb.IsCodeKey(key); ok { + // If the database entry is a contract code, add it to the bloom + b.bloom.AddHash(binary.BigEndian.Uint64(hash)) bloomLoadMeter.Mark(1) } // If enough time elapsed since the last iterator swap, restart @@ -125,14 +111,14 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { it.Release() it = database.NewIterator(nil, key) - log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability(), "elapsed", common.PrettyDuration(time.Since(start))) swap = time.Now() } } it.Release() // Mark the bloom filter inited and return - log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) + log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability(), "elapsed", common.PrettyDuration(time.Since(start))) atomic.StoreUint32(&b.inited, 1) } @@ -141,7 +127,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { func (b *SyncBloom) meter() { for { // Report the current error ration. No floats, lame, scale it up. - bloomErrorGauge.Update(int64(b.errorRate() * 100000)) + bloomErrorGauge.Update(int64(b.bloom.FalsePosititveProbability() * 100000)) // Wait one second, but check termination more frequently for i := 0; i < 10; i++ { @@ -162,7 +148,7 @@ func (b *SyncBloom) Close() error { b.pend.Wait() // Wipe the bloom, but mark it "uninited" just in case someone attempts an access - log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.errorRate()) + log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability()) atomic.StoreUint32(&b.inited, 0) b.bloom = nil @@ -175,7 +161,7 @@ func (b *SyncBloom) Add(hash []byte) { if atomic.LoadUint32(&b.closed) == 1 { return } - b.bloom.Add(syncBloomHasher(hash)) + b.bloom.AddHash(binary.BigEndian.Uint64(hash)) bloomAddMeter.Mark(1) } @@ -193,22 +179,9 @@ func (b *SyncBloom) Contains(hash []byte) bool { return true } // Bloom initialized, check the real one and report any successful misses - maybe := b.bloom.Contains(syncBloomHasher(hash)) + maybe := b.bloom.ContainsHash(binary.BigEndian.Uint64(hash)) if !maybe { bloomMissMeter.Mark(1) } return maybe } - -// errorRate calculates the probability of a random containment test returning a -// false positive. -// -// We're calculating it ourselves because the bloom library we used missed a -// parentheses in the formula and calculates it wrong. And it's discontinued... -func (b *SyncBloom) errorRate() float64 { - k := float64(b.bloom.K()) - n := float64(b.bloom.N()) - m := float64(b.bloom.M()) - - return math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k) -} From c7a6be163f07c967172dfcb3fe2ef25ff08dd4de Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 13 Jan 2021 10:14:36 +0000 Subject: [PATCH 250/948] cmd/utils: don't enumerate USB unless --usb is set (#22130) USB enumeration still occured. Make sure it will only occur if --usb is set. This also deprecates the 'NoUSB' config file option in favor of a new option 'USB'. --- cmd/geth/accountcmd_test.go | 10 +++++----- cmd/geth/consolecmd_test.go | 2 +- cmd/geth/dao_test.go | 4 ++-- cmd/geth/genesis_test.go | 4 ++-- cmd/geth/les_test.go | 6 +++--- cmd/utils/flags.go | 8 ++++---- miner/stress_clique.go | 1 - miner/stress_ethash.go | 1 - node/api_test.go | 1 - node/config.go | 5 ++++- p2p/simulations/adapters/exec.go | 1 - p2p/simulations/adapters/inproc.go | 1 - 12 files changed, 21 insertions(+), 23 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 04f55e9e7a..9455eeda36 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -43,13 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string { } func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "--nousb", "account", "list") + geth := runGeth(t, "account", "list") geth.ExpectExit() } func TestAccountList(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "--nousb", "account", "list", "--datadir", datadir) + geth := runGeth(t, "account", "list", "--datadir", datadir) defer geth.ExpectExit() if runtime.GOOS == "windows" { geth.Expect(` @@ -139,7 +139,7 @@ Fatal: Passwords do not match func TestAccountUpdate(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "--nousb", "account", "update", + geth := runGeth(t, "account", "update", "--datadir", datadir, "--lightkdf", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() @@ -154,7 +154,7 @@ Repeat password: {{.InputLine "foobar2"}} } func TestWalletImport(t *testing.T) { - geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. @@ -169,7 +169,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} } func TestWalletImportBadPassword(t *testing.T) { - geth := runGeth(t, "--nousb", "wallet", "import", "--lightkdf", "testdata/guswallet.json") + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` !! Unsupported terminal, password will be echoed. diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index b0555c45d7..93f9e3d0d9 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -42,7 +42,7 @@ func runMinimalGeth(t *testing.T, args ...string) *testgeth { // --ropsten to make the 'writing genesis to disk' faster (no accounts) // --networkid=1337 to avoid cache bump // --syncmode=full to avoid allocating fast sync bloom - allArgs := []string{"--ropsten", "--nousb", "--networkid", "1337", "--syncmode=full", "--port", "0", + allArgs := []string{"--ropsten", "--networkid", "1337", "--syncmode=full", "--port", "0", "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64"} return runGeth(t, append(allArgs, args...)...) } diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index df7f14fdb8..29b1a7f474 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -115,10 +115,10 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", test, err) } - runGeth(t, "--datadir", datadir, "--nousb", "--networkid", "1337", "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "--networkid", "1337", "init", json).WaitExit() } else { // Force chain initialization - args := []string{"--port", "0", "--nousb", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} + args := []string{"--port", "0", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit() } // Retrieve the DAO config flag from the database diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index 0651c32cad..cbc1b38374 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -81,10 +81,10 @@ func TestCustomGenesis(t *testing.T) { if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", i, err) } - runGeth(t, "--nousb", "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, "--nousb", "--networkid", "1337", "--syncmode=full", + geth := runGeth(t, "--networkid", "1337", "--syncmode=full", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index d2f63ac7bd..053ce96aa3 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -130,7 +130,7 @@ var nextIPC = uint32(0) func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { ipcName := fmt.Sprintf("geth-%d.ipc", atomic.AddUint32(&nextIPC, 1)) - args = append([]string{"--networkid=42", "--port=0", "--nousb", "--ipcpath", ipcName}, args...) + args = append([]string{"--networkid=42", "--port=0", "--ipcpath", ipcName}, args...) t.Logf("Starting %v with rpc: %v", name, args) g := &gethrpc{ @@ -148,7 +148,7 @@ func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { } func initGeth(t *testing.T) string { - args := []string{"--nousb", "--networkid=42", "init", "./testdata/clique.json"} + args := []string{"--networkid=42", "init", "./testdata/clique.json"} t.Logf("Initializing geth: %v ", args) g := runGeth(t, args...) datadir := g.Datadir @@ -159,7 +159,7 @@ func initGeth(t *testing.T) string { func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) t.Logf("Importing keys to geth") - runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() + runGeth(t, "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") return server diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fc64539a7c..6f4da58320 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1233,12 +1233,12 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LightKDFFlag.Name) { cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name) } - if ctx.GlobalIsSet(USBFlag.Name) { - cfg.NoUSB = !ctx.GlobalBool(USBFlag.Name) - } - if ctx.GlobalIsSet(NoUSBFlag.Name) { + if ctx.GlobalIsSet(NoUSBFlag.Name) || cfg.NoUSB { log.Warn("Option nousb is deprecated and USB is deactivated by default. Use --usb to enable") } + if ctx.GlobalIsSet(USBFlag.Name) { + cfg.USB = ctx.GlobalBool(USBFlag.Name) + } if ctx.GlobalIsSet(InsecureUnlockAllowedFlag.Name) { cfg.InsecureUnlockAllowed = ctx.GlobalBool(InsecureUnlockAllowedFlag.Name) } diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 21538aaaed..a0fd596098 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -178,7 +178,6 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { NoDiscovery: true, MaxPeers: 25, }, - NoUSB: true, } // Start the node and configure a full Ethereum node on it stack, err := node.New(config) diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 5a7e7685a6..1713af9d23 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -155,7 +155,6 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { NoDiscovery: true, MaxPeers: 25, }, - NoUSB: true, UseLightweightKDF: true, } // Create the node and configure a full Ethereum node on it diff --git a/node/api_test.go b/node/api_test.go index e4c08962c3..a07ce833c4 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -248,7 +248,6 @@ func TestStartRPC(t *testing.T) { // Apply some sane defaults. config := test.cfg // config.Logger = testlog.Logger(t, log.LvlDebug) - config.NoUSB = true config.P2P.NoDiscovery = true // Create Node. diff --git a/node/config.go b/node/config.go index 55532632cd..61e41cd7d0 100644 --- a/node/config.go +++ b/node/config.go @@ -95,6 +95,9 @@ type Config struct { // NoUSB disables hardware wallet monitoring and connectivity. NoUSB bool `toml:",omitempty"` + // USB enables hardware wallet monitoring and connectivity. + USB bool `toml:",omitempty"` + // SmartCardDaemonPath is the path to the smartcard daemon's socket SmartCardDaemonPath string `toml:",omitempty"` @@ -476,7 +479,7 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { // we can have both, but it's very confusing for the user to see the same // accounts in both externally and locally, plus very racey. backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP)) - if !conf.NoUSB { + if conf.USB { // Start a USB hub for Ledger hardware wallets if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 0ed3deab38..35ccdfb068 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -115,7 +115,6 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { conf.Stack.P2P.EnableMsgEvents = config.EnableMsgEvents conf.Stack.P2P.NoDiscovery = true conf.Stack.P2P.NAT = nil - conf.Stack.NoUSB = true // Listen on a localhost port, which we set when we // initialise NodeConfig (usually a random port) diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 4fc7abc06a..1cb26a8ea0 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -100,7 +100,6 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) { EnableMsgEvents: config.EnableMsgEvents, }, ExternalSigner: config.ExternalSigner, - NoUSB: true, Logger: log.New("node.id", id.String()), }) if err != nil { From c94081774f6c8c43e10deb5735c3f9a09a7bcd04 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 13 Jan 2021 11:29:28 +0100 Subject: [PATCH 251/948] tests: update the reference tests (#22009) --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index 7497b116a0..6c863f03be 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 7497b116a019beb26215cbea4028df068dea06be +Subproject commit 6c863f03bee8d7a66bb7a028a9f880a86a5f4975 From 6296211a3ee2502bdec73256eadaf9185fb4d946 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 13 Jan 2021 11:42:26 +0100 Subject: [PATCH 252/948] graphql: fix spurious error in test (#22164) This solves an issue in graphql tests: graphql_test.go:38: could not create new node: datadir already used by another process --- graphql/graphql_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index fc62da1813..4a1644a61b 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -33,7 +33,14 @@ import ( ) func TestBuildSchema(t *testing.T) { - stack, err := node.New(&node.DefaultConfig) + ddir, err := ioutil.TempDir("", "graphql-buildschema") + if err != nil { + t.Fatalf("failed to create temporary datadir: %v", err) + } + // Copy config + conf := node.DefaultConfig + conf.DataDir = ddir + stack, err := node.New(&conf) if err != nil { t.Fatalf("could not create new node: %v", err) } @@ -157,6 +164,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if err != nil { t.Fatalf("could not read from response body: %v", err) } + resp.Body.Close() // make sure the request is not handled successfully if want, have := "404 page not found\n", string(bodyBytes); have != want { t.Errorf("have:\n%v\nwant:\n%v", have, want) From 2aaff0ad76991be8851ae30454d2e2e967704102 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 13 Jan 2021 11:44:20 +0100 Subject: [PATCH 253/948] consensus/ethash: increase seal timeout for tests (#22162) It seems that the 2 second timeout is not enough for Travis CI: --- FAIL: TestTestMode (2.00s) ethash_test.go:53: sealing result timeout --- consensus/ethash/ethash_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index fdfd81320f..adbf1ccfeb 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -49,7 +49,7 @@ func TestTestMode(t *testing.T) { if err := ethash.VerifySeal(nil, header); err != nil { t.Fatalf("unexpected verification error: %v", err) } - case <-time.NewTimer(2 * time.Second).C: + case <-time.NewTimer(4 * time.Second).C: t.Error("sealing result timeout") } } From 96157a897be2032be5fdb87f947fbe5df8a53bd4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 13 Jan 2021 22:43:07 +0100 Subject: [PATCH 254/948] graphql: fix spurious travis failure (#22166) The tests sometimes failed with certain go versions because the behavior of http.Server.Shutdown changed over time. A bug that was fixed in Go 1.15 could cause active connections on unrelated servers to close unexpectedly. This is fixed by avoiding use of the same port number in all tests. --- graphql/graphql_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 4a1644a61b..e9c129c44c 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -131,7 +131,7 @@ func TestGraphQLBlockSerialization(t *testing.T) { code: 200, }, } { - resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", strings.NewReader(tt.body)) + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) if err != nil { t.Fatalf("could not post: %v", err) } @@ -156,7 +156,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { t.Fatalf("could not start node: %v", err) } body := strings.NewReader(`{"query": "{block{number}}","variables": null}`) - resp, err := http.Post(fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), "application/json", body) + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", body) if err != nil { t.Fatalf("could not post: %v", err) } @@ -177,9 +177,9 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", - HTTPPort: 9393, + HTTPPort: 0, WSHost: "127.0.0.1", - WSPort: 9393, + WSPort: 0, }) if err != nil { t.Fatalf("could not create node: %v", err) @@ -187,11 +187,11 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { if !gqlEnabled { return stack } - createGQLService(t, stack, "127.0.0.1:9393") + createGQLService(t, stack) return stack } -func createGQLService(t *testing.T, stack *node.Node, endpoint string) { +func createGQLService(t *testing.T, stack *node.Node) { // create backend ethConf := ð.Config{ Genesis: &core.Genesis{ From 12969084d17878ccb7978f24574bafacfe99f4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 14 Jan 2021 12:10:52 +0200 Subject: [PATCH 255/948] cmd/faucet: update the embedded website asset --- cmd/faucet/website.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/faucet/website.go b/cmd/faucet/website.go index a091d24919..aed067893a 100644 --- a/cmd/faucet/website.go +++ b/cmd/faucet/website.go @@ -1,6 +1,6 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: -// faucet.html (11.27kB) +// faucet.html (11.276kB) package main @@ -69,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _faucetHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x6d\x93\xdb\x36\x92\xfe\x3c\xfe\x15\x1d\x9e\xbd\x92\xce\x43\x52\x33\x63\x7b\x7d\x12\xa9\x94\xd7\x9b\xdd\xf3\xd5\x5d\x92\x4a\x9c\xba\xdb\xca\xa6\xae\x40\xb2\x25\xc2\x03\x02\x0c\x00\x4a\xa3\x4c\xe9\xbf\x5f\x35\x40\x52\xd4\xcb\x4c\xec\xb5\xaf\x6a\xfd\x61\x4c\x02\x8d\x46\xa3\xfb\x69\xf4\x0b\x95\x7c\xf5\xe7\xef\xde\xbe\xff\xdb\xf7\xdf\x40\x69\x2b\xb1\x78\x92\xd0\x7f\x20\x98\x5c\xa5\x01\xca\x60\xf1\xe4\x22\x29\x91\x15\x8b\x27\x17\x17\x49\x85\x96\x41\x5e\x32\x6d\xd0\xa6\x41\x63\x97\xe1\xeb\x60\x3f\x51\x5a\x5b\x87\xf8\x6b\xc3\xd7\x69\xf0\x3f\xe1\x4f\x6f\xc2\xb7\xaa\xaa\x99\xe5\x99\xc0\x00\x72\x25\x2d\x4a\x9b\x06\xef\xbe\x49\xb1\x58\xe1\x60\x9d\x64\x15\xa6\xc1\x9a\xe3\xa6\x56\xda\x0e\x48\x37\xbc\xb0\x65\x5a\xe0\x9a\xe7\x18\xba\x97\x4b\xe0\x92\x5b\xce\x44\x68\x72\x26\x30\xbd\x0a\x16\x4f\x88\x8f\xe5\x56\xe0\xe2\xfe\x3e\xfa\x16\xed\x46\xe9\xdb\xdd\x6e\x06\x6f\x1a\x5b\xa2\xb4\x3c\x67\x16\x0b\xf8\x0b\x6b\x72\xb4\x49\xec\x29\xdd\x22\xc1\xe5\x2d\x94\x1a\x97\x69\x40\xa2\x9b\x59\x1c\xe7\x85\xfc\x60\xa2\x5c\xa8\xa6\x58\x0a\xa6\x31\xca\x55\x15\xb3\x0f\xec\x2e\x16\x3c\x33\xb1\xdd\x70\x6b\x51\x87\x99\x52\xd6\x58\xcd\xea\xf8\x26\xba\x89\xfe\x18\xe7\xc6\xc4\xfd\x58\x54\x71\x19\xe5\xc6\x04\xa0\x51\xa4\x81\xb1\x5b\x81\xa6\x44\xb4\x01\xc4\x8b\x7f\x6c\xdf\xa5\x92\x36\x64\x1b\x34\xaa\xc2\xf8\x45\xf4\xc7\x68\xea\xb6\x1c\x0e\x3f\xbe\x2b\x6d\x6b\x72\xcd\x6b\x0b\x46\xe7\x1f\xbd\xef\x87\x5f\x1b\xd4\xdb\xf8\x26\xba\x8a\xae\xda\x17\xb7\xcf\x07\x13\x2c\x92\xd8\x33\x5c\x7c\x16\xef\x50\x2a\xbb\x8d\xaf\xa3\x17\xd1\x55\x5c\xb3\xfc\x96\xad\xb0\xe8\x76\xa2\xa9\xa8\x1b\xfc\x62\xfb\x3e\x64\xc3\x0f\xc7\x26\xfc\x12\x9b\x55\xaa\x42\x69\xa3\x0f\x26\xbe\x8e\xae\x5e\x47\xd3\x6e\xe0\x94\xbf\xdb\x80\x8c\x46\x5b\x5d\x44\x6b\xd4\x84\x5c\x11\xe6\x28\x2d\x6a\xb8\xa7\xd1\x8b\x8a\xcb\xb0\x44\xbe\x2a\xed\x0c\xae\xa6\xd3\x67\xf3\x73\xa3\xeb\xd2\x0f\x17\xdc\xd4\x82\x6d\x67\xb0\x14\x78\xe7\x87\x98\xe0\x2b\x19\x72\x8b\x95\x99\x81\xe7\xec\x26\x76\x6e\xcf\x5a\xab\x95\x46\x63\xda\xcd\x6a\x65\xb8\xe5\x4a\xce\x08\x51\xcc\xf2\x35\x9e\xa3\x35\x35\x93\x27\x0b\x58\x66\x94\x68\x2c\x1e\x09\x92\x09\x95\xdf\xfa\x31\xe7\xcd\xc3\x43\xe4\x4a\x28\x3d\x83\x4d\xc9\xdb\x65\xe0\x36\x82\x5a\x63\xcb\x1e\x6a\x56\x14\x5c\xae\x66\xf0\xaa\x6e\xcf\x03\x15\xd3\x2b\x2e\x67\x30\xdd\x2f\x49\xe2\x4e\x8d\x49\xec\x2f\xae\x27\x17\x49\xa6\x8a\xad\xb3\x61\xc1\xd7\x90\x0b\x66\x4c\x1a\x1c\xa9\xd8\x5d\x48\x07\x04\x74\x0f\x31\x2e\xbb\xa9\x83\x39\xad\x36\x01\xb8\x8d\xd2\xc0\x0b\x11\x66\xca\x5a\x55\xcd\xe0\x8a\xc4\x6b\x97\x1c\xf1\x13\xa1\x58\x85\x57\xd7\xdd\xe4\x45\x52\x5e\x75\x4c\x2c\xde\xd9\xd0\xd9\xa7\xb7\x4c\xb0\x48\x78\xb7\x76\xc9\x60\xc9\xc2\x8c\xd9\x32\x00\xa6\x39\x0b\x4b\x5e\x14\x28\xd3\xc0\xea\x06\x09\x47\x7c\x01\xc3\xeb\xef\x81\xdb\xaf\xbc\xea\xe4\x8a\x0b\xbe\x6e\x8f\x35\x78\x3c\x3a\xe1\xc3\x87\x78\x0d\xed\x83\x5a\x2e\x0d\xda\x70\x70\xa6\x01\x31\x97\x75\x63\xc3\x95\x56\x4d\xdd\xcf\x5f\x24\x6e\x14\x78\x91\x06\x8d\x16\x41\x7b\xfd\xbb\x47\xbb\xad\x5b\x55\x04\xfd\xc1\x95\xae\x42\xb2\x84\x56\x22\x80\x5a\xb0\x1c\x4b\x25\x0a\xd4\x69\xf0\xa3\xca\x39\x13\x20\xfd\x99\xe1\xa7\x1f\xfe\x13\x5a\x93\x71\xb9\x82\xad\x6a\x34\x7c\x63\x4b\xd4\xd8\x54\xc0\x8a\x82\xe0\x1a\x45\xd1\x40\x10\x87\xdd\x53\x51\xc3\xcc\xca\x3d\xd5\x45\x92\x35\xd6\xaa\x9e\x30\xb3\x12\x32\x2b\xc3\x02\x97\xac\x11\x16\x0a\xad\xea\x42\x6d\x64\x68\xd5\x6a\x45\x91\xce\x1f\xc2\x2f\x0a\xa0\x60\x96\xb5\x53\x69\xd0\xd1\x76\x36\x64\xa6\x56\x75\x53\xb7\x56\xf4\x83\x78\x57\x33\x59\x60\x41\x36\x17\x06\x83\xc5\x5f\xf9\x1a\xa1\x42\x7f\x96\x8b\x63\x48\xe4\x4c\xa3\x0d\x87\x4c\x4f\x80\x91\xc4\x5e\x18\x7f\x24\x68\xff\x25\x8d\xe8\x38\xf5\x47\xa8\x50\x36\x70\xf0\x16\x6a\xba\x57\x82\xc5\xfd\xbd\x66\x72\x85\xf0\x94\x17\x77\x97\xf0\x94\x55\xaa\x91\x16\x66\x29\x44\x6f\xdc\xa3\xd9\xed\x0e\xb8\x03\x24\x82\x2f\x12\xf6\x18\xbc\x41\xc9\x5c\xf0\xfc\x36\x0d\x2c\x47\x9d\xde\xdf\x13\xf3\xdd\x6e\x0e\xf7\xf7\x7c\x09\x4f\xa3\x1f\x30\x67\xb5\xcd\x4b\xb6\xdb\xad\x74\xf7\x1c\xe1\x1d\xe6\x8d\xc5\xf1\xe4\xfe\x1e\x85\xc1\xdd\xce\x34\x59\xc5\xed\xb8\x5b\x4e\xe3\xb2\xd8\xed\x48\xe6\x56\xce\xdd\x0e\x62\x62\x2a\x0b\xbc\x83\xa7\xd1\xf7\xa8\xb9\x2a\x0c\x78\xfa\x24\x66\x8b\x24\x16\x7c\xd1\xae\x3b\x54\x52\xdc\x88\x3d\x5e\x62\x02\x4c\x8f\x73\xe7\x36\x4e\xd4\xa1\xa4\x67\xbc\x60\x15\xf6\xd2\xb7\x78\x30\xdc\xe2\x2d\x6e\xd3\xe0\xfe\x7e\xb8\xb6\x9d\xcd\x99\x10\x19\x23\xbd\xf8\xa3\xf5\x8b\x7e\x43\xc2\xe9\x9a\x1b\x97\x52\x2d\x3a\x09\xf6\x62\x7f\xa4\x5b\x1f\x5d\x5c\x56\xd5\x33\xb8\xb9\x1e\xdc\x5a\xe7\x3c\xfe\xd5\x91\xc7\xdf\x9c\x25\xae\x99\x44\x01\xee\x6f\x68\x2a\x26\xba\xe7\xd6\x5b\x06\xce\x77\xbc\x28\xa4\x3b\xba\x17\xad\xbf\xeb\xa7\x73\x50\x6b\xd4\x4b\xa1\x36\x33\x60\x8d\x55\x73\xa8\xd8\x5d\x1f\xef\x6e\xa6\xd3\xa1\xdc\x94\x0a\xb2\x4c\xa0\xbb\x5d\x34\xfe\xda\xa0\xb1\xa6\xbf\x4b\xfc\x94\xfb\x4b\x57\x4a\x81\xd2\x60\x71\xa4\x0d\xda\x91\x54\xeb\xa8\x06\xa6\xef\x95\x79\x56\xf6\xa5\x52\x7d\x08\x19\x8a\xd1\xb2\x1e\x44\xbb\x60\x91\x58\xbd\xa7\xbb\x48\x6c\xf1\x49\x21\x40\x53\x8a\xf7\x50\x04\xf0\x37\x1a\x9d\xbd\x46\xd4\x3e\xbf\x20\xc8\x82\x7b\x4d\x62\x5b\x7c\xc6\xce\x04\xc2\x8c\x19\xfc\x98\xed\x5d\xa4\xdf\x6f\xef\x5e\x3f\x77\xff\x12\x99\xb6\x19\x32\xfb\x31\x02\x2c\x1b\x59\x0c\xce\xef\xee\xce\xcf\x15\xa0\x91\x7c\x8d\xda\x70\xbb\xfd\x58\x09\xb0\xd8\x8b\xe0\xdf\x0f\x45\x48\x62\xab\x1f\xc7\xda\xf0\xe5\x0b\x39\xf7\xef\xa5\x24\x37\x8b\x7f\x57\x1b\x28\x14\x1a\xb0\x25\x37\x40\xc1\xf5\xeb\x24\x2e\x6f\x7a\x92\x7a\xf1\x9e\x26\x9c\x52\x61\xe9\x52\x0b\xe0\x06\x74\x23\x5d\xe4\x55\x12\x6c\x89\x87\xe9\x48\x1b\xa4\x23\x78\xaf\x28\xa5\x5b\xa3\xb4\x50\x31\xc1\x73\xae\x1a\x03\x2c\xb7\x4a\x1b\x58\x6a\x55\x01\xde\x95\xac\x31\x96\x18\xd1\xf5\xc1\xd6\x8c\x0b\xe7\x4b\xce\xa4\xa0\x34\xb0\x3c\x6f\xaa\x86\x52\x52\xb9\x02\x94\xaa\x59\x95\xad\x2c\x56\x81\x0f\x4c\x42\xc9\x55\x2f\x8f\xa9\x59\x05\xcc\x5a\x96\xdf\x9a\x4b\xe8\x6e\x05\x60\x1a\xc1\x72\x2c\x68\x55\xae\xaa\x4a\x49\xb8\xd1\x05\xd4\x4c\xdb\x2d\x98\xc3\xdc\x82\xe5\xb9\x8b\x72\x11\xbc\x91\x5b\x25\x11\x4a\xb6\x76\x12\xc2\x7b\x5f\x4e\x90\x5c\x7f\x61\x39\x66\x4a\xf5\xd4\x50\xb1\x6d\xb7\x5d\x2b\xfd\x86\xdb\x92\x7b\xf5\xd4\xa8\x2b\x5a\x5a\x80\xe0\x15\xb7\x26\x4a\xe2\x7a\x7f\xa3\xee\x63\xb3\x08\x4b\xa5\xf9\x6f\x94\xd8\x88\xe1\xf5\x69\x8f\x2e\x97\xee\x6e\x74\x56\x17\xb8\xb4\x33\x78\xe1\xef\xc6\x63\x1c\xb7\x15\xd0\x39\x10\x77\x3c\x5d\x65\x49\x01\x67\x06\x37\x3e\x9d\xf5\x89\x44\x61\x07\x12\x14\x47\x50\xf3\x9b\xbe\x7e\x5d\xdf\xf5\x72\xf4\x39\xf1\xb4\x67\x42\x08\x38\x54\xca\x9a\xf7\x6a\xbc\x84\x8a\xdd\x22\x30\x48\xd8\x51\x85\xdc\x0a\xed\xea\x2b\xee\xfa\x03\xb1\xdd\x20\xda\xaf\xc9\x75\xd3\x1f\x3c\x43\x2e\x57\xcf\xae\xa7\x1e\x91\xf4\x40\xec\x9f\x5d\x4f\xb9\xb4\xea\xd9\xf5\x74\x7a\x37\xfd\xc8\x7f\xcf\xae\xa7\x4a\x3e\xbb\x9e\xda\x12\x9f\x5d\x4f\x9f\x5d\xdf\x0c\xb1\xec\x47\xba\xcc\x92\xa8\xd0\xd0\x6e\x1d\xc4\x03\xb0\x4c\xaf\xd0\xa6\xc1\xff\xb2\x4c\x35\x76\x96\x09\x26\x6f\x83\x85\x13\x97\xb2\x0d\x87\x82\xf3\xf9\x29\xd4\xcc\x10\x24\x48\x62\x87\x92\xb6\x17\x62\x60\x6c\x1a\xad\x55\x23\x29\x2a\x02\x9d\xd9\x79\xa8\x1c\x11\xca\x48\x31\x93\x28\xc9\x74\xbc\x78\xab\xea\x6d\xe8\x98\xb8\xe5\x27\x6a\x34\x4d\x5d\x2b\x6d\xa3\xa1\x3a\x19\xd5\x41\x02\x4d\xfc\x7a\xfa\xf2\xf5\xab\x47\xc5\x37\x94\x65\xbb\x33\xf4\x12\xb2\x4c\xad\x11\x7c\x4e\x9f\xa9\x3b\x60\xb2\x80\x25\xd7\x08\x6c\xc3\xb6\x5f\x25\x71\xe1\x2a\xb0\xcf\x47\xed\xb2\xf5\xae\x7f\x2a\xd8\x76\x2e\x7f\x09\x75\x93\x09\x6e\x4a\x60\x20\x71\x03\x89\xb1\x5a\xc9\xd5\xc2\x8d\xe6\x54\x92\xba\x57\xa8\x95\xb1\x8f\x99\x1f\xab\x0c\x8b\xe2\x0c\x00\xbe\x94\xfd\x37\x9b\x4d\xd4\x69\xd2\x19\xbf\x44\x51\xc7\x74\xfd\x35\x92\xdb\x6d\xec\xdd\x48\xc9\xf8\x6b\x5e\xa4\xd7\xaf\xaf\x5f\xbd\xba\x7e\xf1\x6f\xaf\x5f\xbe\xbc\x7e\xfd\xe2\xe5\x43\xc8\xa0\x43\x7d\x26\x30\x7c\x1a\xfd\xad\xa2\xaa\xb5\xcf\xa1\x3d\x5e\xba\xdc\x8d\x22\x74\x41\x35\x88\x0e\xfe\x61\x0c\x35\x92\x12\x91\x90\x89\xb3\x39\xc4\x27\xa0\xc8\xc1\xe8\x11\xc9\x3e\x13\x5a\x1d\x7c\x08\x29\xaa\xb1\x74\xc2\xae\x98\xe7\x4a\xf6\x70\xba\x04\xc3\xab\x5a\x6c\x21\xdf\x5b\xfd\x3c\xae\x1e\x34\xca\xef\xc2\xea\xd0\x6c\x1e\x64\x2e\xfa\x57\xaa\x40\x8a\xfa\xa6\x31\x39\xd6\xae\xcb\x4b\x91\xf4\x4f\xdb\xdf\x98\xb4\x5c\x62\x17\x71\x23\xf8\x4e\x8a\x2d\x34\x06\x61\xa9\x34\x14\x98\x35\xab\x95\x4b\x13\x34\xd4\x9a\xaf\x99\xc5\x2e\xcc\x9a\x16\x15\x3d\x28\x06\x95\x0d\xa5\x3c\x62\x90\x81\xfc\x4d\x35\x90\x33\x09\x56\xb3\xfc\xd6\x7b\x4a\xa3\x35\x79\x4a\x8d\xfe\x34\x7d\xa0\xcf\x50\xa8\x8d\x23\xf1\xe7\x5e\x72\x14\x2e\xea\x1b\x44\x28\xd5\x06\xaa\x26\x77\x0e\x49\x51\xdd\x1d\x62\xc3\xb8\x85\x46\x5a\x2e\xbc\x3e\x6d\xa3\x25\xe5\x08\x78\x10\xa5\x4f\x6a\xbf\x04\xab\xc5\xfb\x12\xcf\xa4\x44\x7d\xd5\x06\x1a\xdf\x7a\x72\xa8\xb5\xb2\x98\x93\x41\x81\xad\x18\x97\x86\x2c\xe2\xf2\x00\xac\x3e\xa2\xaa\xeb\x9f\xda\x87\x7d\x87\xd2\x4d\xc7\x31\xfc\x55\xa8\x8c\x09\x58\x13\xd2\x33\x41\xe9\x9c\x82\x52\xd1\xd1\x07\xda\x32\x96\xd9\xc6\x80\x5a\xba\x51\x2f\x39\xad\x5f\x33\x4d\x16\xc4\xaa\xb6\x90\xb6\xfd\x35\x1a\x33\xa8\xd7\x6d\xd7\x90\x5e\xa9\x72\x3f\x98\xef\xb5\x9e\xc2\xcf\xbf\xcc\x9f\xb4\xa2\xfc\x19\x97\x0e\x12\x84\x6f\x7f\x64\x5b\x32\x0b\xb9\x46\x66\xd1\x40\x2e\x94\x69\xb4\x97\xb0\xd0\xaa\x06\x92\xb2\xe3\xd4\x71\xa6\x89\xda\xed\xd6\x31\x19\x97\xcc\x94\x93\xb6\x3d\xa8\xd1\x59\xa9\x9f\xeb\xc6\x2f\x08\x75\x63\x62\xc0\xd3\xe9\x1c\x78\xd2\xf1\x8d\x04\xca\x95\x2d\xe7\xc0\x9f\x3f\xef\x89\x2f\xf8\x12\xc6\x1d\xc5\xcf\xfc\x97\xc8\xde\x45\xb4\x0b\xa4\x29\x0c\x77\x73\x1b\xb6\x7c\x4c\x2d\x78\x8e\x63\x7e\x09\x57\x93\x79\x37\x9b\x69\x64\xb7\xdd\x5b\x6b\x47\xff\x9f\xfb\xbb\x9b\x1f\x6a\xc6\x29\xff\x40\x37\xbe\xf6\x37\xc0\x60\xc5\x8d\x85\x46\x0b\x68\x7d\xd8\x9b\xa0\x37\x88\xa3\x1b\x6a\xe5\x04\x97\xed\x43\x8b\xa9\xee\x08\x9e\x4d\x64\x50\x16\xe3\xff\xf8\xf1\xbb\x6f\x23\x63\x35\x97\x2b\xbe\xdc\x8e\xef\x1b\x2d\x66\xf0\x74\x1c\xfc\x4b\xa3\x45\x30\xf9\x79\xfa\x4b\xb4\x66\xa2\xc1\x4b\x67\xef\x99\xfb\x7b\xb2\xcb\x25\xb4\x8f\x33\x38\xdc\x70\x37\x99\xcc\xcf\xf7\x49\x06\x6d\x1d\x8d\x06\xed\x98\x08\x7b\xe0\x1f\xeb\x88\x41\x85\xb6\x54\xce\x75\x35\xe6\x4a\x4a\xcc\x2d\x34\xb5\x92\xad\x4a\x40\x28\x63\xf6\x40\xec\x28\xd2\x53\x50\xb4\xf4\xa9\x0b\xd6\xff\x8d\xd9\x8f\x2a\xbf\x45\x3b\x1e\x8f\x37\x5c\x16\x6a\x13\x09\xe5\xaf\xda\x88\x9c\x54\xe5\x4a\x40\x9a\xa6\xd0\x46\xd1\x60\x02\x5f\x43\xb0\x31\x14\x4f\x03\x98\xd1\x23\x3d\x4d\xe0\x39\x1c\x2f\x2f\x29\xde\x3f\x87\x20\x66\x35\x0f\x26\xde\x1d\x3a\xc5\x2b\x59\xa1\x31\x6c\x85\x43\x01\x5d\x65\xd4\x83\x8c\xce\x51\x99\x15\xa4\xe0\x0c\x54\x33\x6d\xd0\x93\x44\x54\x8d\x77\x68\x23\xcc\x3a\xb2\x34\x05\xd9\x08\xb1\x07\xa9\x77\x8a\x79\x07\xbf\x03\xf2\xc8\xc7\x9a\xaf\xd2\x14\xa8\x34\x25\x15\x17\xfb\x95\x64\x7c\x5f\x44\x4f\x22\x8a\x0b\xfb\x15\x93\xf9\x10\xcd\x07\xdc\xb0\xf8\x3d\x76\x58\x1c\xf3\xc3\xe2\x01\x86\xae\x67\xf1\x18\x3f\xdf\xe3\x18\xb0\x73\x03\x0f\x70\x93\x4d\x95\xa1\x7e\x8c\x9d\xef\x59\xb4\xec\x9c\xaa\xdf\x49\x3b\x58\x7b\x09\x57\xaf\x26\x0f\x70\x47\xad\xd5\x83\xcc\xa5\xb2\xdb\xf1\xbd\x60\x5b\xca\x99\x60\x64\x55\xfd\xd6\xb5\x18\x46\x97\x2e\xe2\xce\xa0\xe7\x70\xe9\x9a\xc7\x33\x18\xb9\x37\x9a\xe7\x15\xba\x55\x2f\xa7\xd3\xe9\x25\x74\x5f\x5d\xfe\xc4\xc8\x09\x75\x83\xbb\x07\xe4\x31\x4d\x9e\x53\xdc\xff\x1c\x89\x5a\x1e\xbd\x4c\xed\xfb\x67\x48\xd5\xc7\x86\x03\xb1\xe0\x0f\x7f\x80\x93\xd9\x43\x18\xc7\x31\xfc\x17\xa3\x32\x5c\x08\xd7\x3d\x70\x4d\x83\x9e\xbe\xe2\xc6\xb8\x62\xdc\x40\xa1\x24\xb6\x6b\x3e\xed\xda\x3f\x91\xb1\x25\x83\x05\x4c\x8f\x05\xa4\xeb\x70\x10\x16\xce\x44\x8b\x01\xdf\xc3\x40\x70\xb1\x1b\xee\x77\xb0\x92\x57\x08\x5f\xa5\x10\x04\xc3\xc5\x27\x14\x44\xd0\x33\xbb\x30\x68\xdf\x7b\x5b\x8c\xdb\xe8\x78\x2e\x76\x4d\x2e\xe1\x66\x3a\x9d\x4e\x4e\x84\xd8\xed\xd5\xfb\xa6\xa6\xb4\x09\x98\xdc\xba\x2b\xb1\xd7\xad\x4b\x1c\x29\x05\xa2\x2b\x4d\x40\xae\x84\xf0\x39\x4b\xbb\x94\x14\xdc\x36\x4f\x52\x08\xaf\xe6\x67\xa2\xe8\x40\x93\x83\xa3\x1d\x9b\xe7\x8c\xee\x8f\x4d\x74\xa8\xb3\x23\xe2\xf0\xea\xc0\x28\x07\xf6\x3a\x6f\x98\x8b\x5e\x6e\xbe\xd7\xe8\x91\xb9\xf6\xf6\x3a\xd6\xd9\x40\x7e\xcf\xe7\xf9\xd5\x47\x1e\xa3\x9f\xae\x1b\x53\x8e\x8f\x04\x9d\xcc\x4f\x6d\xf3\xce\xa2\xa6\x2c\x59\x51\xc8\x22\x5b\x50\x29\xa0\xf1\xc4\x24\x2e\x55\xd7\x18\x6a\x94\x05\xea\x2e\xa5\xf0\x99\x3d\x25\x80\x07\x26\xf3\x55\xe5\x10\x4e\x9f\xe8\x30\x2e\x25\x53\x12\x01\x00\x8e\x9c\xc0\x01\xf5\x00\xa9\x44\x8c\x82\xd5\x06\x0b\x48\xc1\x7f\x04\x1f\x4f\xa2\x46\xf2\xbb\xf1\x24\x6c\xdf\x8f\x79\x74\xf3\xf3\xbe\x4c\xec\xc4\x7e\x9e\x42\x90\x58\x0d\xbc\x48\x47\x01\x3c\x3f\xe7\x82\x14\x75\x47\x8b\xbd\x04\xc3\xa5\x00\x89\x2d\x16\xae\x0f\xea\xeb\xb5\xbf\x07\x19\xcb\x6f\x57\xae\x10\x9a\x51\xaa\x35\x3e\x61\xcb\xd6\xcc\x32\xed\xb8\x4e\xe6\xb0\x27\x6f\x0b\xc5\x9c\x8c\x33\x07\x5f\x91\xba\x76\x2b\xf4\x9f\x28\xdc\x5b\xa6\x74\x81\x3a\xd4\xac\xe0\x8d\x99\xc1\x8b\xfa\x6e\xfe\xf7\xee\x13\x8e\x6b\x0a\x3f\x2a\x6a\xad\x71\x71\x22\x51\xdb\x65\x7c\x0e\x41\x12\x13\xc1\xef\xb1\xe9\x0f\x3b\xfc\xf8\x0e\x67\x5a\xdf\xd0\x7f\x1a\x6f\xc7\x2b\x5e\x14\x02\x49\xe0\x3d\x7b\x72\x46\xb2\xff\xd0\xa5\x0e\xb7\x84\xb6\xe7\xbd\x5f\xb3\x03\x14\x06\x1f\x59\xd0\xb7\xcf\x47\x04\x80\x90\x8e\xcc\x9d\xce\xdb\x62\xdb\x0d\xeb\x91\xd3\x45\xfb\x53\x8a\xa2\xd1\x2e\xd7\x1a\x87\x2d\xc0\x2e\x61\x64\x28\xf7\x2b\xcc\x68\x12\x95\x4d\xc5\x24\xff\x0d\xc7\x14\x97\x26\x5e\x57\xae\x1f\x1f\x9c\x5e\xc9\x27\xc2\xec\x1b\xe5\xa3\x2e\xc6\x8d\x5a\x25\x8e\x3a\xeb\xbe\xd8\xd7\xf6\x33\x98\xce\x47\x9f\xa8\xa1\xf3\xbb\x84\x19\xd3\x30\x7c\x09\xbb\xe0\x0b\x5a\xd1\xee\xdd\x5c\xc6\xf4\xc8\x77\x32\x5c\x7e\x2e\xd5\x26\x1d\xdd\x4c\x7b\x21\xbd\xa1\x9d\x9d\x47\x2d\xd6\x4e\x8c\x41\x52\x76\xae\xb9\x80\x9b\xe9\x97\x90\xd6\x77\x43\x8e\x4e\x60\x35\xaf\xb1\x00\x96\x5b\xbe\xc6\xff\x87\x83\x7c\x01\x25\x7f\xb2\x88\x84\xc3\x4e\x79\x0e\xa6\x07\xf2\xd2\x6c\xaf\xdb\x7f\x25\x7f\x83\xd8\x69\xf8\x39\x04\x67\x0f\xf2\x20\x12\x8f\x08\x8f\x5c\xfb\x61\xbf\x77\x1f\x98\x82\xe3\x98\x42\xd9\x6e\xff\x71\x74\x12\x95\xb6\x12\xe3\x20\xb1\xee\x47\x32\x24\x73\xcf\xc1\x31\xf0\xc3\x87\x29\xdd\xee\xb0\x90\xa1\xfa\x1d\x8f\xea\x2c\x18\x24\x27\x7d\x2d\xd6\x65\x22\xb0\xdb\xff\x96\x28\x8e\xe1\x47\xcb\xb4\x05\x06\x3f\xbd\x83\xa6\x2e\x98\xf5\x9f\x72\x28\x3e\xfa\x4f\x25\xdd\x8f\x8d\x32\xa6\x0d\x2c\x95\xde\x30\x5d\xb4\xfd\x19\x5b\xe2\xd6\x7d\xca\xe9\x52\x3f\x83\xf6\x1d\xdd\x62\x6b\x26\xc6\x27\x75\xdf\xd3\xf1\x28\x1a\x9a\x7c\x34\x89\x90\xe5\xe5\x29\xa1\x8b\x58\xfd\xbe\x29\x7c\xeb\x4a\x80\xf1\xd3\xb1\x2d\xb9\x99\x44\xcc\x5a\x3d\x1e\x1d\x80\x61\x34\x21\xbb\x5e\x0d\x4a\xb2\x7e\x79\x72\xe0\x56\x8f\xf1\xd8\x27\xd3\x7d\x22\xd0\x91\xe7\xc6\x8c\x3d\xae\x46\x97\x03\xde\x87\xb0\x1a\x3d\x1b\xf5\x86\xda\xbb\xf7\xfe\x1c\xe9\x59\x49\x0e\x58\x8f\xc8\xcb\x46\x27\xdb\xb3\xa2\x78\x4b\xfe\x33\x0e\xce\x78\xfa\x31\x3a\x26\xbd\xb2\xfd\x7d\xfd\xa8\x96\xfd\xcf\x32\x1e\x50\x31\x2f\x46\x93\xc8\x34\x99\xef\x4d\x8c\x5f\xf6\x05\x58\x47\xe6\xc0\x7b\x1c\x0a\x4e\x12\x0a\xda\xe2\x30\xa9\x08\x8f\x92\x90\x47\xa2\x46\xbb\xa5\x3f\xd5\xee\x92\x14\x3e\x9d\xf4\xad\xad\x6f\x0c\x25\x57\xbe\xf5\xbf\xc1\xcc\xb8\x4e\x02\xb4\x78\x77\xdd\x1c\xdf\xb5\x79\xf3\xfd\xbb\x41\xe7\xa6\xf7\x88\xb1\xe3\xde\xff\x0e\xf0\x5c\x9f\xe4\xec\x0f\x0f\x37\x9b\x4d\xb4\x52\x6a\x25\xfc\x4f\x0e\xfb\x46\x4a\xcc\x6a\x1e\x7d\x30\x01\x30\xb3\x95\x39\x14\xb8\x44\xbd\x18\xb0\x6f\xbb\x2b\x49\xec\x7f\x12\x97\xc4\xfe\x57\xbf\xff\x17\x00\x00\xff\xff\x31\x9f\x54\x5e\x06\x2c\x00\x00") +var _faucetHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5a\x7b\x93\xdb\x36\x92\xff\x7b\xfc\x29\x3a\x3c\x7b\x25\x9d\x87\xa4\x66\xc6\xf6\xfa\x24\x52\x29\xaf\x37\xbb\xe7\xab\xbb\x24\x95\x38\x75\xb7\x95\x4d\x5d\x81\x64\x4b\x84\x07\x04\x18\x00\x94\x46\x99\xd2\x77\xbf\x6a\x80\xa4\xa8\xc7\x4c\xec\xb5\xaf\x6a\xfd\xc7\x98\xc4\xa3\xd1\x8f\x5f\xa3\x1f\x54\xf2\xd5\x9f\xbf\x7b\xfb\xfe\x6f\xdf\x7f\x03\xa5\xad\xc4\xe2\x49\x42\xff\x81\x60\x72\x95\x06\x28\x83\xc5\x93\x8b\xa4\x44\x56\x2c\x9e\x5c\x5c\x24\x15\x5a\x06\x79\xc9\xb4\x41\x9b\x06\x8d\x5d\x86\xaf\x83\xfd\x44\x69\x6d\x1d\xe2\xaf\x0d\x5f\xa7\xc1\xff\x84\x3f\xbd\x09\xdf\xaa\xaa\x66\x96\x67\x02\x03\xc8\x95\xb4\x28\x6d\x1a\xbc\xfb\x26\xc5\x62\x85\x83\x7d\x92\x55\x98\x06\x6b\x8e\x9b\x5a\x69\x3b\x58\xba\xe1\x85\x2d\xd3\x02\xd7\x3c\xc7\xd0\xbd\x5c\x02\x97\xdc\x72\x26\x42\x93\x33\x81\xe9\x55\xb0\x78\x42\x74\x2c\xb7\x02\x17\xf7\xf7\xd1\xb7\x68\x37\x4a\xdf\xee\x76\x33\x78\xd3\xd8\x12\xa5\xe5\x39\xb3\x58\xc0\x5f\x58\x93\xa3\x4d\x62\xbf\xd2\x6d\x12\x5c\xde\x42\xa9\x71\x99\x06\xc4\xba\x99\xc5\x71\x5e\xc8\x0f\x26\xca\x85\x6a\x8a\xa5\x60\x1a\xa3\x5c\x55\x31\xfb\xc0\xee\x62\xc1\x33\x13\xdb\x0d\xb7\x16\x75\x98\x29\x65\x8d\xd5\xac\x8e\x6f\xa2\x9b\xe8\x8f\x71\x6e\x4c\xdc\x8f\x45\x15\x97\x51\x6e\x4c\x00\x1a\x45\x1a\x18\xbb\x15\x68\x4a\x44\x1b\x40\xbc\xf8\xc7\xce\x5d\x2a\x69\x43\xb6\x41\xa3\x2a\x8c\x5f\x44\x7f\x8c\xa6\xee\xc8\xe1\xf0\xe3\xa7\xd2\xb1\x26\xd7\xbc\xb6\x60\x74\xfe\xd1\xe7\x7e\xf8\xb5\x41\xbd\x8d\x6f\xa2\xab\xe8\xaa\x7d\x71\xe7\x7c\x30\xc1\x22\x89\x3d\xc1\xc5\x67\xd1\x0e\xa5\xb2\xdb\xf8\x3a\x7a\x11\x5d\xc5\x35\xcb\x6f\xd9\x0a\x8b\xee\x24\x9a\x8a\xba\xc1\x2f\x76\xee\x43\x36\xfc\x70\x6c\xc2\x2f\x71\x58\xa5\x2a\x94\x36\xfa\x60\xe2\xeb\xe8\xea\x75\x34\xed\x06\x4e\xe9\xbb\x03\xc8\x68\x74\xd4\x45\xb4\x46\x4d\xc8\x15\x61\x8e\xd2\xa2\x86\x7b\x1a\xbd\xa8\xb8\x0c\x4b\xe4\xab\xd2\xce\xe0\x6a\x3a\x7d\x36\x3f\x37\xba\x2e\xfd\x70\xc1\x4d\x2d\xd8\x76\x06\x4b\x81\x77\x7e\x88\x09\xbe\x92\x21\xb7\x58\x99\x19\x78\xca\x6e\x62\xe7\xce\xac\xb5\x5a\x69\x34\xa6\x3d\xac\x56\x86\x5b\xae\xe4\x8c\x10\xc5\x2c\x5f\xe3\xb9\xb5\xa6\x66\xf2\x64\x03\xcb\x8c\x12\x8d\xc5\x23\x46\x32\xa1\xf2\x5b\x3f\xe6\xbc\x79\x28\x44\xae\x84\xd2\x33\xd8\x94\xbc\xdd\x06\xee\x20\xa8\x35\xb6\xe4\xa1\x66\x45\xc1\xe5\x6a\x06\xaf\xea\x56\x1e\xa8\x98\x5e\x71\x39\x83\xe9\x7e\x4b\x12\x77\x6a\x4c\x62\x7f\x71\x3d\xb9\x48\x32\x55\x6c\x9d\x0d\x0b\xbe\x86\x5c\x30\x63\xd2\xe0\x48\xc5\xee\x42\x3a\x58\x40\xf7\x10\xe3\xb2\x9b\x3a\x98\xd3\x6a\x13\x80\x3b\x28\x0d\x3c\x13\x61\xa6\xac\x55\xd5\x0c\xae\x88\xbd\x76\xcb\x11\x3d\x11\x8a\x55\x78\x75\xdd\x4d\x5e\x24\xe5\x55\x47\xc4\xe2\x9d\x0d\x9d\x7d\x7a\xcb\x04\x8b\x84\x77\x7b\x97\x0c\x96\x2c\xcc\x98\x2d\x03\x60\x9a\xb3\xb0\xe4\x45\x81\x32\x0d\xac\x6e\x90\x70\xc4\x17\x30\xbc\xfe\x1e\xb8\xfd\xca\xab\x8e\xaf\xb8\xe0\xeb\x56\xac\xc1\xe3\x91\x84\x0f\x0b\xf1\x1a\xda\x07\xb5\x5c\x1a\xb4\xe1\x40\xa6\xc1\x62\x2e\xeb\xc6\x86\x2b\xad\x9a\xba\x9f\xbf\x48\xdc\x28\xf0\x22\x0d\x1a\x2d\x82\xf6\xfa\x77\x8f\x76\x5b\xb7\xaa\x08\x7a\xc1\x95\xae\x42\xb2\x84\x56\x22\x80\x5a\xb0\x1c\x4b\x25\x0a\xd4\x69\xf0\xa3\xca\x39\x13\x20\xbd\xcc\xf0\xd3\x0f\xff\x09\xad\xc9\xb8\x5c\xc1\x56\x35\x1a\xbe\xb1\x25\x6a\x6c\x2a\x60\x45\x41\x70\x8d\xa2\x28\x88\xf7\x9c\x38\xf0\x9e\xf2\x1a\x66\x56\xee\xf9\xbd\x48\xb2\xc6\x5a\xd5\x2f\xcc\xac\x84\xcc\xca\xb0\xc0\x25\x6b\x84\x85\x42\xab\xba\x50\x1b\x19\x5a\xb5\x5a\x51\xa8\xf3\x52\xf8\x4d\x01\x14\xcc\xb2\x76\x2a\x0d\xba\xb5\x9d\x11\x99\xa9\x55\xdd\xd4\xad\x19\xfd\x20\xde\xd5\x4c\x16\x58\x90\xd1\x85\xc1\x60\xf1\x57\xbe\x46\xa8\xd0\x0b\x73\x71\x8c\x89\x9c\x69\xb4\xe1\x90\xe8\x09\x32\x92\xd8\x33\xe3\x45\x82\xf6\x5f\xd2\x88\x8e\x52\x2f\x42\x85\xb2\x81\x83\xb7\x50\xd3\xc5\x12\x2c\xee\xef\x35\x93\x2b\x84\xa7\xbc\xb8\xbb\x84\xa7\xac\x52\x8d\xb4\x30\x4b\x21\x7a\xe3\x1e\xcd\x6e\x77\x40\x1d\x20\x11\x7c\x91\xb0\xc7\xf0\x0d\x4a\xe6\x82\xe7\xb7\x69\x60\x39\xea\xf4\xfe\x9e\x88\xef\x76\x73\xb8\xbf\xe7\x4b\x78\x1a\xfd\x80\x39\xab\x6d\x5e\xb2\xdd\x6e\xa5\xbb\xe7\x08\xef\x30\x6f\x2c\x8e\x27\xf7\xf7\x28\x0c\xee\x76\xa6\xc9\x2a\x6e\xc7\xdd\x76\x1a\x97\xc5\x6e\x47\x3c\xb7\x7c\xee\x76\x10\x13\x51\x59\xe0\x1d\x3c\x8d\xbe\x47\xcd\x55\x61\xc0\xaf\x4f\x62\xb6\x48\x62\xc1\x17\xed\xbe\x43\x25\xc5\x8d\xd8\xe3\x25\x26\xc0\xf4\x40\x77\x7e\xe3\x58\x1d\x72\x7a\xc6\x0d\x56\x61\xcf\x7d\x8b\x07\xc3\x2d\xde\xe2\x36\x0d\xee\xef\x87\x7b\xdb\xd9\x9c\x09\x91\x31\xd2\x8b\x17\xad\xdf\xf4\x1b\x12\x4e\xd7\xdc\xb8\x9c\x6a\xd1\x71\xb0\x67\xfb\x23\xfd\xfa\xe8\xe6\xb2\xaa\x9e\xc1\xcd\xf5\xe0\xda\x3a\xe7\xf2\xaf\x8e\x5c\xfe\xe6\xec\xe2\x9a\x49\x14\xe0\xfe\x86\xa6\x62\xa2\x7b\x6e\xbd\x65\x70\x0d\x1c\x6f\x0a\xe9\x92\xee\x59\xeb\x2f\xfb\xe9\x1c\xd4\x1a\xf5\x52\xa8\xcd\x0c\x58\x63\xd5\x1c\x2a\x76\xd7\x07\xbc\x9b\xe9\x74\xc8\x37\xe5\x82\x2c\x13\xe8\xae\x17\x8d\xbf\x36\x68\xac\xe9\x2f\x13\x3f\xe5\xfe\xd2\x9d\x52\xa0\x34\x58\x1c\x69\x83\x4e\x24\xd5\xba\x55\x03\xd3\xf7\xca\x3c\xcb\xfb\x52\xa9\x3e\x86\x0c\xd9\x68\x49\x0f\xc2\x5d\xb0\x48\xac\xde\xaf\xbb\x48\x6c\xf1\x49\x31\x40\x53\x8e\xf7\x50\x08\xf0\x37\x1a\xc9\x5e\x23\x6a\x9f\x60\x10\x64\xc1\xbd\x26\xb1\x2d\x3e\xe3\x64\x02\x61\xc6\x0c\x7e\xcc\xf1\x2e\xd4\xef\x8f\x77\xaf\x9f\x7b\x7e\x89\x4c\xdb\x0c\x99\xfd\x18\x06\x96\x8d\x2c\x06\xf2\xbb\xbb\xf3\x73\x19\x68\x24\x5f\xa3\x36\xdc\x6e\x3f\x96\x03\x2c\xf6\x2c\xf8\xf7\x43\x16\x92\xd8\xea\xc7\xb1\x36\x7c\xf9\x42\xce\xfd\x7b\x39\xc9\xcd\xe2\xdf\xd5\x06\x0a\x85\x06\x6c\xc9\x0d\x50\x74\xfd\x3a\x89\xcb\x9b\x7e\x49\xbd\x78\x4f\x13\x4e\xa9\xb0\x74\xb9\x05\x70\x03\xba\x91\x2e\xf4\x2a\x09\xb6\xc4\xc3\x7c\xa4\x8d\xd2\x11\xbc\x57\x94\xd3\xad\x51\x5a\xa8\x98\xe0\x39\x57\x8d\x01\x96\x5b\xa5\x0d\x2c\xb5\xaa\x00\xef\x4a\xd6\x18\x4b\x84\xe8\xfa\x60\x6b\xc6\x85\xf3\x25\x67\x52\x50\x1a\x58\x9e\x37\x55\x43\x39\xa9\x5c\x01\x4a\xd5\xac\xca\x96\x17\xab\xc0\x07\x26\xa1\xe4\xaa\xe7\xc7\xd4\xac\x02\x66\x2d\xcb\x6f\xcd\x25\x74\xb7\x02\x30\x8d\x60\x39\x16\xb4\x2b\x57\x55\xa5\x24\xdc\xe8\x02\x6a\xa6\xed\x16\xcc\x61\x72\xc1\xf2\xdc\x45\xb9\x08\xde\xc8\xad\x92\x08\x25\x5b\x3b\x0e\xe1\xbd\xaf\x27\x88\xaf\xbf\xb0\x1c\x33\xa5\xfa\xd5\x50\xb1\x6d\x77\x5c\xcb\xfd\x86\xdb\x92\x7b\xf5\xd4\xa8\x2b\xda\x5a\x80\xe0\x15\xb7\x26\x4a\xe2\x7a\x7f\xa3\xee\x63\xb3\x08\x4b\xa5\xf9\x6f\x94\xd9\x88\xe1\xf5\x69\x8f\x2e\x97\xee\x6e\x74\x56\x17\xb8\xb4\x33\x78\xe1\xef\xc6\x63\x1c\xb7\x25\xd0\x39\x10\x77\x34\x5d\x69\x49\x01\x67\x06\x37\x3e\x9f\xf5\x89\x44\x61\x07\x1c\x14\x47\x50\xf3\x87\xbe\x7e\x5d\xdf\xf5\x7c\xf4\x49\xf1\xb4\x27\x42\x08\x38\x54\xca\x9a\xf7\x6a\xbc\x84\x8a\xdd\x22\x30\x48\xd8\x51\x89\xdc\x32\xed\x0a\x2c\xee\x1a\x04\xb1\xdd\x20\xda\xaf\xc9\x75\xd3\x1f\x3c\x41\x2e\x57\xcf\xae\xa7\x1e\x91\xf4\x40\xe4\x9f\x5d\x4f\xb9\xb4\xea\xd9\xf5\x74\x7a\x37\xfd\xc8\x7f\xcf\xae\xa7\x4a\x3e\xbb\x9e\xda\x12\x9f\x5d\x4f\x9f\x5d\xdf\x0c\xb1\xec\x47\xba\xd4\x92\x56\xa1\xa1\xd3\x3a\x88\x07\x60\x99\x5e\xa1\x4d\x83\xff\x65\x99\x6a\xec\x2c\x13\x4c\xde\x06\x0b\xc7\x2e\x65\x1b\x0e\x05\xe7\x13\x54\xa8\x99\x21\x48\x10\xc7\x0e\x25\x6d\x33\xc4\xc0\xd8\x34\x5a\xab\x46\x52\x54\x04\x92\xd9\x79\xa8\x1c\x11\xca\x48\x31\x93\x28\xc9\x74\xbc\x78\xab\xea\x6d\xe8\x88\xb8\xed\x27\x6a\x34\x4d\x5d\x2b\x6d\xa3\xa1\x3a\x19\x15\x42\x02\x4d\xfc\x7a\xfa\xf2\xf5\xab\x47\xd9\x37\x94\x66\x3b\x19\x7a\x0e\x59\xa6\xd6\x08\x3e\xa9\xcf\xd4\x1d\x30\x59\xc0\x92\x6b\x04\xb6\x61\xdb\xaf\x92\xb8\x70\x25\xd8\xe7\xa3\x76\xd9\x7a\xd7\x3f\x15\x6c\x3b\x97\xbf\x84\xba\xc9\x04\x37\x25\x30\x90\xb8\x81\xc4\x58\xad\xe4\x6a\xe1\x46\x73\xaa\x49\xdd\x2b\xd4\xca\xd8\xc7\xcc\x8f\x55\x86\x45\x71\x06\x00\x5f\xca\xfe\x9b\xcd\x26\xea\x34\xe9\x8c\x5f\xa2\xa8\x63\xba\xfe\x1a\xc9\xed\x36\xf6\x6e\xa4\x64\xfc\x35\x2f\xd2\xeb\xd7\xd7\xaf\x5e\x5d\xbf\xf8\xb7\xd7\x2f\x5f\x5e\xbf\x7e\xf1\xf2\x21\x64\x90\x50\x9f\x09\x0c\x9f\x46\x7f\xab\xa8\x6c\xed\x73\x68\x8f\x97\x2e\x77\xa3\x08\x5d\x50\x0d\xa2\x83\x7f\x18\x43\x8d\xa4\x44\x24\x64\xe2\x6c\x0e\xf1\x09\x28\x72\x30\x7a\x84\xb3\xcf\x84\x56\x07\x1f\x42\x8a\x6a\x2c\x49\xd8\x55\xf3\x5c\xc9\x1e\x4e\x97\x60\x78\x55\x8b\x2d\xe4\x7b\xab\x9f\xc7\xd5\x83\x46\xf9\x5d\x58\x1d\x9a\xcd\x83\xcc\x45\xff\x4a\x15\x48\x51\xdf\x34\x26\xc7\xda\xb5\x79\x29\x92\xfe\x69\xfb\x1b\x93\x96\x4b\xec\x22\x6e\x04\xdf\x49\xb1\x85\xc6\x20\x2c\x95\x86\x02\xb3\x66\xb5\x72\x69\x82\x86\x5a\xf3\x35\xb3\xd8\x85\x59\xd3\xa2\xa2\x07\xc5\xa0\xb2\xa1\x94\x47\x0c\x32\x90\xbf\xa9\x06\x72\x26\xc1\x6a\x96\xdf\x7a\x4f\x69\xb4\x26\x4f\xa9\xd1\x4b\xd3\x07\xfa\x0c\x85\xda\xb8\x25\x5e\xee\x25\x47\xe1\xa2\xbe\x41\x84\x52\x6d\xa0\x6a\x72\xe7\x90\x14\xd5\x9d\x10\x1b\xc6\x2d\x34\xd2\x72\xe1\xf5\x69\x1b\x2d\x29\x47\xc0\x83\x28\x7d\x52\xfb\x25\x58\x2d\xde\x97\x78\x26\x25\xea\xab\x36\xd0\xf8\xd6\x2f\x87\x5a\x2b\x8b\x39\x19\x14\xd8\x8a\x71\x69\xc8\x22\x2e\x0f\xc0\xea\x23\xaa\xba\xfe\xa9\x7d\xd8\xb7\x28\xdd\x74\x1c\xc3\x5f\x85\xca\x98\x80\x35\x21\x3d\x13\x94\xce\x29\x28\x15\x89\x3e\xd0\x96\xb1\xcc\x36\x06\xd4\xd2\x8d\x7a\xce\x69\xff\x9a\x69\xb2\x20\x56\xb5\x85\xb4\x6d\xb0\xd1\x98\x41\xbd\x6e\xdb\x86\xf4\x4a\x95\xfb\xc1\x7c\xaf\xf5\x14\x7e\xfe\x65\xfe\xa4\x65\xe5\xcf\xb8\x74\x90\x20\x7c\x7b\x91\x6d\xc9\x2c\xe4\x1a\x99\x45\x03\xb9\x50\xa6\xd1\x9e\xc3\x42\xab\x1a\x88\xcb\x8e\x52\x47\x99\x26\x6a\x77\x5a\x47\x64\x5c\x32\x53\x4e\xda\xfe\xa0\x46\x67\xa5\x7e\xae\x1b\xbf\x20\xd4\x8d\x89\x00\x4f\xa7\x73\xe0\x49\x47\x37\x12\x28\x57\xb6\x9c\x03\x7f\xfe\xbc\x5f\x7c\xc1\x97\x30\xee\x56\xfc\xcc\x7f\x89\xec\x5d\x44\xa7\x40\x9a\xc2\xf0\x34\x77\x60\x4b\xc7\xd4\x82\xe7\x38\xe6\x97\x70\x35\x99\x77\xb3\x99\x46\x76\xdb\xbd\xb5\x76\xf4\xff\xb9\xbf\xbb\xf9\xa1\x66\x9c\xf2\x0f\x74\xe3\x6b\x7f\x03\x0c\x56\xdc\x58\x68\xb4\x80\xd6\x87\xbd\x09\x7a\x83\xb8\x75\x43\xad\x9c\xe0\xb2\x7d\x68\x31\xd5\x89\xe0\xc9\x44\x06\x65\x31\xfe\x8f\x1f\xbf\xfb\x36\x32\x56\x73\xb9\xe2\xcb\xed\xf8\xbe\xd1\x62\x06\x4f\xc7\xc1\xbf\x34\x5a\x04\x93\x9f\xa7\xbf\x44\x6b\x26\x1a\xbc\x74\xf6\x9e\xb9\xbf\x27\xa7\x5c\x42\xfb\x38\x83\xc3\x03\x77\x93\xc9\xfc\x7c\x9f\x64\xd0\xd6\xd1\x68\xd0\x8e\x69\x61\x0f\xfc\x63\x1d\x31\xa8\xd0\x96\xca\xb9\xae\xc6\x5c\x49\x89\xb9\x85\xa6\x56\xb2\x55\x09\x08\x65\xcc\x1e\x88\xdd\x8a\xf4\x14\x14\xed\xfa\xd4\x05\xeb\xff\xc6\xec\x47\x95\xdf\xa2\x1d\x8f\xc7\x1b\x2e\x0b\xb5\x89\x84\xf2\x57\x6d\x44\x4e\xaa\x72\x25\x20\x4d\x53\x68\xa3\x68\x30\x81\xaf\x21\xd8\x18\x8a\xa7\x01\xcc\xe8\x91\x9e\x26\xf0\x1c\x8e\xb7\x97\x14\xef\x9f\x43\x10\xb3\x9a\x07\x13\xef\x0e\x9d\xe2\x95\xac\xd0\x18\xb6\xc2\x21\x83\xae\x32\xea\x41\x46\x72\x54\x66\x05\x29\x38\x03\xd5\x4c\x1b\xf4\x4b\x22\xaa\xc6\x3b\xb4\x11\x66\xdd\xb2\x34\x05\xd9\x08\xb1\x07\xa9\x77\x8a\x79\x07\xbf\x83\xe5\x91\x8f\x35\x5f\xa5\x29\x50\x69\x4a\x2a\x2e\xf6\x3b\xc9\xf8\xbe\x88\x9e\x44\x14\x17\xf6\x3b\x26\xf3\x21\x9a\x0f\xa8\x61\xf1\x7b\xe4\xb0\x38\xa6\x87\xc5\x03\x04\x5d\xcf\xe2\x31\x7a\xbe\xc7\x31\x20\xe7\x06\x1e\xa0\x26\x9b\x2a\x43\xfd\x18\x39\xdf\xb3\x68\xc9\x39\x55\xbf\x93\x76\xb0\xf7\x12\xae\x5e\x4d\x1e\xa0\x8e\x5a\xab\x07\x89\x4b\x65\xb7\xe3\x7b\xc1\xb6\x94\x33\xc1\xc8\xaa\xfa\xad\x6b\x31\x8c\x2e\x5d\xc4\x9d\x41\x4f\xe1\xd2\x35\x8f\x67\x30\x72\x6f\x34\xcf\x2b\x74\xbb\x5e\x4e\xa7\xd3\x4b\xe8\x3e\xbb\xfc\x89\x91\x13\xea\x06\x77\x0f\xf0\x63\x9a\x3c\xa7\xb8\xff\x39\x1c\xb5\x34\x7a\x9e\xda\xf7\xcf\xe0\xaa\x8f\x0d\x07\x6c\xc1\x1f\xfe\x00\x27\xb3\x87\x30\x8e\x63\xf8\x2f\x46\x65\xb8\x10\xae\x7b\xe0\x9a\x06\xfd\xfa\x8a\x1b\xe3\x8a\x71\x03\x85\x92\xd8\xee\xf9\xb4\x6b\xff\x84\xc7\x76\x19\x2c\x60\x7a\xcc\x20\x5d\x87\x83\xb0\x70\x26\x5a\x0c\xe8\x1e\x06\x82\x8b\xdd\xf0\xbc\x83\x9d\xbc\x42\xf8\x2a\x85\x20\x18\x6e\x3e\x59\x41\x0b\x7a\x62\x17\x06\xed\x7b\x6f\x8b\x71\x1b\x1d\xcf\xc5\xae\xc9\x25\xdc\x4c\xa7\xd3\xc9\x09\x13\xbb\xbd\x7a\xdf\xd4\x94\x36\x01\x93\x5b\x77\x25\xf6\xba\x75\x89\x23\xa5\x40\x74\xa5\x09\xc8\x95\x10\x3e\x67\x69\xb7\x92\x82\xdb\xe6\x49\x0a\xe1\xd5\xfc\x4c\x14\x1d\x68\x72\x20\xda\xb1\x79\xce\xe8\xfe\xd8\x44\x87\x3a\x3b\x5a\x1c\x5e\x1d\x18\xe5\xc0\x5e\xe7\x0d\x73\xd1\xf3\xcd\xf7\x1a\x3d\x32\xd7\xde\x5e\xc7\x3a\x1b\xf0\xef\xe9\x3c\xbf\xfa\x48\x31\xfa\xe9\xba\x31\xe5\xf8\x88\xd1\xc9\xfc\xd4\x36\xef\x2c\x6a\xca\x92\x15\x85\x2c\xb2\x05\x95\x02\x1a\x4f\x4c\xe2\x52\x75\x8d\xa1\x46\x59\xa0\xee\x52\x0a\x9f\xd9\x53\x02\x78\x60\x32\x5f\x55\x0e\xe1\x34\x90\xe8\x44\xb7\x73\xe0\xb0\xa0\x34\x0f\x78\x18\x0e\x64\x71\x79\x99\x92\x08\x00\x70\xe4\x09\x0e\xad\x07\x70\xa5\xc5\x28\x58\x6d\xb0\x80\x14\xfc\xa7\xf0\xf1\x24\x6a\x24\xbf\x1b\x4f\xc2\xf6\xfd\x98\x46\x37\x3f\xef\x6b\xc5\x8e\xf7\xe7\x29\x04\x89\xd5\xc0\x8b\x74\x14\xc0\xf3\x73\x7e\x48\xa1\x77\xb4\xd8\x73\x30\xdc\x0a\x90\xd8\x62\xe1\x9a\xa1\xbe\x68\xfb\x7b\x90\xb1\xfc\x76\xe5\xaa\xa1\x19\xe5\x5b\xe3\x13\xb2\x6c\xcd\x2c\xd3\x8e\xea\x64\x0e\xfb\xe5\x6d\xb5\x98\x93\x85\xe6\xe0\xcb\x52\xd7\x73\x85\xfe\x3b\x85\x7b\xcb\x94\x2e\x50\x87\x9a\x15\xbc\x31\x33\x78\x51\xdf\xcd\xff\xde\x7d\xc7\x71\x9d\xe1\x47\x59\xad\x35\x2e\x4e\x38\x6a\x5b\x8d\xcf\x21\x48\x62\x5a\xf0\x7b\x64\x7a\x61\x87\x9f\xe0\xe1\x4c\xff\x1b\xfa\x0f\xe4\xed\x78\xc5\x8b\x42\x20\x31\xbc\x27\x4f\x1e\x49\xf6\x1f\xfa\xd5\xe1\x91\xd0\x36\xbe\xf7\x7b\x76\x80\xc2\xe0\x23\x1b\xfa\x1e\xfa\x88\x00\x10\x92\xc8\xdc\xe9\xbc\xad\xb8\xdd\xb0\x1e\x39\x5d\xb4\x3f\xa8\x28\x1a\xed\x12\xae\x71\xd8\x02\xec\x12\x46\x86\x12\xc0\xc2\x8c\x26\x51\xd9\x54\x4c\xf2\xdf\x70\x4c\xc1\x69\xe2\x75\xe5\x9a\xf2\xc1\xe9\xbd\x7c\xc2\xcc\xbe\x5b\x3e\xea\x02\xdd\xa8\x55\xe2\xa8\xb3\xee\x8b\x7d\x81\x3f\x83\xe9\x7c\xf4\x89\x1a\x3a\x7f\x4a\x98\x31\x0d\xc3\x97\xb0\x8b\xc0\xa0\x15\x9d\xde\xcd\x65\x4c\x8f\x7c\x3b\xc3\x25\xe9\x52\x6d\xd2\xd1\xcd\xb4\x67\xd2\x1b\xda\xd9\x79\xd4\x62\xed\xc4\x18\xc4\x65\xe7\x9a\x0b\xb8\x99\x7e\x09\x6e\x7d\x4b\xe4\x48\x02\xab\x79\x8d\x05\xb0\xdc\xf2\x35\xfe\x3f\x08\xf2\x05\x94\xfc\xc9\x2c\x12\x0e\x3b\xe5\x39\x98\x1e\xf0\x4b\xb3\xbd\x6e\xff\x95\xfc\x0d\x62\xa7\xe1\xe7\x10\x9c\x15\xe4\x41\x24\x1e\x2d\x3c\x72\xed\x87\xfd\xde\x7d\x65\x0a\x8e\x03\x0b\xa5\xbc\xfd\x17\xd2\x49\x54\xda\x4a\x8c\x83\xc4\xba\x9f\xca\x10\xcf\x3d\x05\x47\xc0\x0f\x1f\xe6\x75\xbb\xc3\x6a\x86\x8a\x78\x3c\x2a\xb6\x60\x90\xa1\xf4\x05\x59\x97\x8e\xc0\x6e\xff\x8b\xa2\x38\x86\x1f\x2d\xd3\x16\x18\xfc\xf4\x0e\x9a\xba\x60\xd6\x7f\xcf\xa1\x20\xe9\xbf\x97\x74\x3f\x39\xca\x98\x36\xb0\x54\x7a\xc3\x74\xd1\x36\x69\x6c\x89\x5b\xf7\x3d\xa7\xcb\xff\x0c\xda\x77\x74\x8b\xad\x99\x18\x9f\x14\x7f\x4f\xc7\xa3\x68\x68\xf2\xd1\x24\x42\x96\x97\xa7\x0b\x5d\xc4\xea\xcf\x4d\xe1\x5b\x57\x07\x8c\x9f\x8e\x6d\xc9\xcd\x24\x62\xd6\xea\xf1\xe8\x00\x0c\xa3\x09\xd9\xf5\x6a\x50\x97\xf5\xdb\x93\x03\xb7\x7a\x8c\xc6\x3e\xa3\xee\xb3\x81\x6e\x79\x6e\xcc\xd8\xe3\x6a\x74\x39\xa0\x7d\x08\xab\xd1\xb3\x51\x6f\xa8\xbd\x7b\xef\xe5\x48\xcf\x72\x72\x40\x7a\x44\x5e\x36\x3a\x39\x9e\x15\xc5\x5b\xf2\x9f\x71\x70\xc6\xd3\x8f\xd1\x31\xe9\x95\xed\xef\xeb\x47\xb5\xec\x7f\x9b\xf1\x80\x8a\x79\x31\x9a\x44\xa6\xc9\x7c\x83\x62\xfc\xb2\xaf\xc2\xba\x65\x0e\xbc\xc7\xa1\xe0\x24\xa1\xa0\x23\x0e\x93\x8a\xf0\x28\x09\x79\x24\x6a\xb4\x47\x7a\xa9\x76\x97\xa4\xf0\xe9\xa4\xef\x6f\x7d\x63\x28\xc3\xf2\xfd\xff\x0d\x66\xc6\xb5\x13\xa0\xc5\xbb\x6b\xe9\xf8\xd6\xcd\x9b\xef\xdf\x0d\xda\x37\xbd\x47\x8c\x1d\xf5\xfe\xd7\x80\xe7\x9a\x25\x67\x7f\x7e\xb8\xd9\x6c\xa2\x95\x52\x2b\xe1\x7f\x78\xd8\x77\x53\x62\x56\xf3\xe8\x83\x09\x80\x99\xad\xcc\xa1\xc0\x25\xea\xc5\x80\x7c\xdb\x62\x49\x62\xff\xc3\xb8\x24\xf6\xbf\xfd\xfd\xbf\x00\x00\x00\xff\xff\xb2\x1e\x6f\x68\x0c\x2c\x00\x00") func faucetHtmlBytes() ([]byte, error) { return bindataRead( @@ -85,7 +85,7 @@ func faucetHtml() (*asset, error) { } info := bindataFileInfo{name: "faucet.html", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xdb, 0xa2, 0x98, 0x44, 0x4b, 0x50, 0xf8, 0xa1, 0xac, 0x4a, 0x76, 0x2e, 0xcc, 0x3d, 0xcb, 0x81, 0x9e, 0x2a, 0xaa, 0x87, 0xf5, 0x9d, 0x53, 0x4, 0x8a, 0xdd, 0x5a, 0xfe, 0xd3, 0xc3, 0xf, 0x11}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc5, 0x8d, 0xb, 0x7a, 0xfd, 0x70, 0x68, 0x68, 0xd2, 0xd8, 0xf3, 0xf6, 0xac, 0x72, 0xed, 0xc2, 0x76, 0x18, 0x2d, 0x1, 0xe5, 0x3b, 0x55, 0xb, 0xce, 0xfc, 0xb6, 0xd5, 0x59, 0xc3, 0x94, 0x5b}} return a, nil } From c4deebbf1e186e3b7e96c4e0ab395d3207cec55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 15 Jan 2021 12:26:46 +0200 Subject: [PATCH 256/948] core/state/snapshot: add generation logs to storage too --- core/state/snapshot/generate.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 17f1ca6078..fcc6b44cb6 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -281,6 +281,10 @@ func (dl *diskLayer) generate(stats *generatorStats) { abort <- stats return } + if time.Since(logged) > 8*time.Second { + stats.Log("Generating state snapshot", dl.root, append(accountHash[:], storeIt.Key...)) + logged = time.Now() + } } } if err := storeIt.Err; err != nil { From 8d62ee65b2d3100da0292232f8169282237f5487 Mon Sep 17 00:00:00 2001 From: gary rong Date: Sat, 16 Jan 2021 06:04:38 +0800 Subject: [PATCH 257/948] les: don't drop sentTo for normal cases (#22048) --- les/retrieve.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/les/retrieve.go b/les/retrieve.go index ca4f867ea8..3174d49878 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -337,7 +337,6 @@ func (r *sentReq) tryRequest() { } defer func() { - // send feedback to server pool and remove peer if hard timeout happened pp, ok := p.(*serverPeer) if hrto && ok { pp.Log().Debug("Request timed out hard") @@ -345,10 +344,6 @@ func (r *sentReq) tryRequest() { r.rm.peers.unregister(pp.id) } } - - r.lock.Lock() - delete(r.sentTo, p) - r.lock.Unlock() }() select { From c76573a97b15e28c0d5c783cab3a62e9203db1c9 Mon Sep 17 00:00:00 2001 From: Dan DeGreef Date: Sat, 16 Jan 2021 11:15:18 -0600 Subject: [PATCH 258/948] eth/protocols/eth: fix slice resize flaw (#22181) --- eth/protocols/eth/broadcast.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index 2349398fae..74ec2f0654 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -179,7 +179,7 @@ func (p *Peer) announceTransactions() { queue = append(queue, hashes...) if len(queue) > maxQueuedTxAnns { // Fancy copy and resize to ensure buffer doesn't grow indefinitely - queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])] + queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])] } case <-done: From 034ecc3210623d1585f0ffd6a2c8a64d4b7ee0c1 Mon Sep 17 00:00:00 2001 From: gary rong Date: Sun, 17 Jan 2021 02:06:18 +0800 Subject: [PATCH 259/948] les: remove useless protocol defines (#22115) This PR has two changes in the les protocol: - the auxRoot is not supported. See ethereum/devp2p#171 for more information - the empty response will be returned in GetHelperTrieProofsMsg request if the merkle proving is failed. note, for backward compatibility, the empty merkle proof as well as the request auxiliary data will still be returned in les2/3 protocol no matter the proving is successful or not. the proving failure can happen e.g. request the proving for a non-included entry in helper trie (unstable header). --- les/benchmark.go | 2 +- les/handler_test.go | 2 +- les/odr_requests.go | 9 ++++----- les/server_handler.go | 32 +++++++++++++++++--------------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/les/benchmark.go b/les/benchmark.go index 312d4533df..6255c1049e 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -156,7 +156,7 @@ func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error { for i := range reqs { key := make([]byte, 8) binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum)))) - reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: auxHeader} + reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: htAuxHeader} } } diff --git a/les/handler_test.go b/les/handler_test.go index 04277f661b..f5cbeb8efc 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -443,7 +443,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { Type: htCanonical, TrieIdx: 0, Key: key, - AuxReq: auxHeader, + AuxReq: htAuxHeader, }} // Send the proof request and verify the response sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requestsV2) diff --git a/les/odr_requests.go b/les/odr_requests.go index a8cf8f50a9..962b88a322 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -295,10 +295,9 @@ const ( htCanonical = iota // Canonical hash trie htBloomBits // BloomBits trie - // applicable for all helper trie requests - auxRoot = 1 - // applicable for htCanonical - auxHeader = 2 + // helper trie auxiliary types + // htAuxNone = 1 ; deprecated number, used in les2/3 previously. + htAuxHeader = 2 // applicable for htCanonical, requests for relevant headers ) type HelperTrieReq struct { @@ -339,7 +338,7 @@ func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error { Type: htCanonical, TrieIdx: r.ChtNum, Key: encNum[:], - AuxReq: auxHeader, + AuxReq: htAuxHeader, } return peer.requestHelperTrieProofs(reqID, []HelperTrieReq{req}) } diff --git a/les/server_handler.go b/les/server_handler.go index 2316c9c5a4..bec4206e2b 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -741,22 +741,24 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) } } - if request.AuxReq == auxRoot { - var data []byte - if root != (common.Hash{}) { - data = root[:] - } + if auxTrie == nil { + sendResponse(req.ReqID, 0, nil, task.servingTime) + return + } + // TODO(rjl493456442) short circuit if the proving is failed. + // The original client side code has a dirty hack to retrieve + // the headers with no valid proof. Keep the compatibility for + // legacy les protocol and drop this hack when the les2/3 are + // not supported. + err := auxTrie.Prove(request.Key, request.FromLevel, nodes) + if p.version >= lpv4 && err != nil { + sendResponse(req.ReqID, 0, nil, task.servingTime) + return + } + if request.AuxReq == htAuxHeader { + data := h.getAuxiliaryHeaders(request) auxData = append(auxData, data) auxBytes += len(data) - } else { - if auxTrie != nil { - auxTrie.Prove(request.Key, request.FromLevel, nodes) - } - if request.AuxReq != 0 { - data := h.getAuxiliaryHeaders(request) - auxData = append(auxData, data) - auxBytes += len(data) - } } if nodes.DataSize()+auxBytes >= softResponseLimit { break @@ -904,7 +906,7 @@ func (h *serverHandler) getHelperTrie(typ uint, index uint64) (common.Hash, stri // getAuxiliaryHeaders returns requested auxiliary headers for the CHT request. func (h *serverHandler) getAuxiliaryHeaders(req HelperTrieReq) []byte { - if req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8 { + if req.Type == htCanonical && req.AuxReq == htAuxHeader && len(req.Key) == 8 { blockNum := binary.BigEndian.Uint64(req.Key) hash := rawdb.ReadCanonicalHash(h.chainDb, blockNum) return rawdb.ReadHeaderRLP(h.chainDb, hash, blockNum) From 398182284cb1635be833017e87d484795a5e5c56 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 18 Jan 2021 14:33:15 +0100 Subject: [PATCH 260/948] tests/fuzzers/abi: better test generation (#22158) * tests/fuzzers/abi: better test generation * tests/fuzzers/abi: fixed packing issue * oss-fuzz: enable abi fuzzer --- oss-fuzz.sh | 6 +- tests/fuzzers/abi/abifuzzer.go | 115 ++++++++++++---------------- tests/fuzzers/abi/abifuzzer_test.go | 9 +-- 3 files changed, 51 insertions(+), 79 deletions(-) diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 5919b2077f..7ae4d77b29 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -100,6 +100,7 @@ compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty +compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul @@ -113,8 +114,3 @@ compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 #TODO: move this to tests/fuzzers, if possible compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b - - -# This doesn't work very well @TODO -#compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi - diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index 76d3c800f7..8c083b371e 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -17,38 +17,53 @@ package abi import ( - "bytes" "fmt" - "math/rand" "reflect" "strings" "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/crypto" fuzz "github.com/google/gofuzz" ) -func unpackPack(abi abi.ABI, method string, inputType []interface{}, input []byte) bool { - outptr := reflect.New(reflect.TypeOf(inputType)) - if err := abi.UnpackIntoInterface(outptr.Interface(), method, input); err == nil { - output, err := abi.Pack(method, input) +var ( + names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} + stateMut = []string{"", "pure", "view", "payable"} + stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} + pays = []string{"", "true", "false"} + payables = []*string{&pays[0], &pays[1]} + vNames = []string{"a", "b", "c", "d", "e", "f", "g"} + varNames = append(vNames, names...) + varTypes = []string{"bool", "address", "bytes", "string", + "uint8", "int8", "uint8", "int8", "uint16", "int16", + "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", + "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", + "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", + "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", + "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", + "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", + "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", + "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", + "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", + "bytes32", "bytes"} +) + +func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) { + if out, err := abi.Unpack(method, input); err == nil { + _, err := abi.Pack(method, out...) if err != nil { // We have some false positives as we can unpack these type successfully, but not pack them if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" || err.Error() == "abi: cannot use uint8 as type int8 as argument" { - return false + return out, false } panic(err) } - if !bytes.Equal(input, output[4:]) { - panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, output[4:])) - } - return true + return out, true } - return false + return nil, false } -func packUnpack(abi abi.ABI, method string, input []interface{}) bool { +func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool { if packed, err := abi.Pack(method, input); err == nil { outptr := reflect.New(reflect.TypeOf(input)) err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) @@ -100,64 +115,23 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab return abi.JSON(strings.NewReader(sig)) } -func fillStruct(structs []interface{}, data []byte) { - if structs != nil && len(data) != 0 { - fuzz.NewFromGoFuzz(data).Fuzz(&structs) - } -} - -func createStructs(args []args) []interface{} { - structs := make([]interface{}, len(args)) - for i, arg := range args { - t, err := abi.NewType(arg.typ, "", nil) - if err != nil { - panic(err) - } - structs[i] = reflect.New(t.GetType()).Elem() - } - return structs -} - func runFuzzer(input []byte) int { good := false + fuzzer := fuzz.NewFromGoFuzz(input) - names := []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} - stateMut := []string{"", "pure", "view", "payable"} - stateMutabilites := []*string{nil, &stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} - pays := []string{"true", "false"} - payables := []*string{nil, &pays[0], &pays[1]} - varNames := []string{"a", "b", "c", "d", "e", "f", "g"} - varNames = append(varNames, names...) - varTypes := []string{"bool", "address", "bytes", "string", - "uint8", "int8", "uint8", "int8", "uint16", "int16", - "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", - "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", - "uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136", - "uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176", - "uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216", - "uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256", - "bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11", - "bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21", - "bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31", - "bytes32", "bytes"} - rnd := rand.New(rand.NewSource(123456)) - if len(input) > 0 { - kec := crypto.Keccak256(input) - rnd = rand.New(rand.NewSource(int64(kec[0]))) - } - name := names[rnd.Intn(len(names))] - stateM := stateMutabilites[rnd.Intn(len(stateMutabilites))] - payable := payables[rnd.Intn(len(payables))] + name := names[getUInt(fuzzer)%len(names)] + stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)] + payable := payables[getUInt(fuzzer)%len(payables)] maxLen := 5 for k := 1; k < maxLen; k++ { var arg []args for i := k; i > 0; i-- { argName := varNames[i] - argTyp := varTypes[rnd.Int31n(int32(len(varTypes)))] - if rnd.Int31n(10) == 0 { + argTyp := varTypes[getUInt(fuzzer)%len(varTypes)] + if getUInt(fuzzer)%10 == 0 { argTyp += "[]" - } else if rnd.Int31n(10) == 0 { - arrayArgs := rnd.Int31n(30) + 1 + } else if getUInt(fuzzer)%10 == 0 { + arrayArgs := getUInt(fuzzer)%30 + 1 argTyp += fmt.Sprintf("[%d]", arrayArgs) } arg = append(arg, args{ @@ -169,10 +143,8 @@ func runFuzzer(input []byte) int { if err != nil { continue } - structs := createStructs(arg) - b := unpackPack(abi, name, structs, input) - fillStruct(structs, input) - c := packUnpack(abi, name, structs) + structs, b := unpackPack(abi, name, input) + c := packUnpack(abi, name, &structs) good = good || b || c } if good { @@ -184,3 +156,12 @@ func runFuzzer(input []byte) int { func Fuzz(input []byte) int { return runFuzzer(input) } + +func getUInt(fuzzer *fuzz.Fuzzer) int { + var i int + fuzzer.Fuzz(&i) + if i < 0 { + i *= -1 + } + return i +} diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go index c72577e9ee..c59c45ab1a 100644 --- a/tests/fuzzers/abi/abifuzzer_test.go +++ b/tests/fuzzers/abi/abifuzzer_test.go @@ -23,13 +23,8 @@ import ( // TestReplicate can be used to replicate crashers from the fuzzing tests. // Just replace testString with the data in .quoted func TestReplicate(t *testing.T) { - testString := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00000000000" + - "00000000000000000000" + - "00000000000000000000" + - "00000001" + testString := "N\xef\xbf0\xef\xbf99000000000000" + + "000000000000" data := []byte(testString) runFuzzer(data) From 10555d46849fc805aa28921fed4d46e4bdaf0c4c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 18 Jan 2021 14:36:05 +0100 Subject: [PATCH 261/948] cmd/geth: dump config for metrics (#22083) * cmd/geth: dump config * cmd/geth: dump config * cmd/geth: properly read config again * cmd/geth: override metrics if flags are set * cmd/geth: write metrics regardless if enabled * cmd/geth: renamed to metricsfromcliargs * metrics: add default configuration --- cmd/geth/config.go | 43 ++++++++++++++++++++++++++++++++++++++++--- cmd/utils/flags.go | 14 +++++++------- metrics/config.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 metrics/config.go diff --git a/cmd/geth/config.go b/cmd/geth/config.go index bf1fc55b17..c5b330b2d8 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/naoina/toml" @@ -88,6 +89,7 @@ type gethConfig struct { Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig + Metrics metrics.Config } func loadConfig(file string, cfg *gethConfig) error { @@ -119,8 +121,9 @@ func defaultNodeConfig() node.Config { func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ - Eth: eth.DefaultConfig, - Node: defaultNodeConfig(), + Eth: eth.DefaultConfig, + Node: defaultNodeConfig(), + Metrics: metrics.DefaultConfig, } // Load config file. @@ -133,7 +136,6 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { log.Warn("Deprecated whisper config detected. Whisper has been moved to github.com/ethereum/whisper") } } - // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) @@ -146,6 +148,8 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { } utils.SetShhConfig(ctx, stack) + applyMetricConfig(ctx, &cfg) + return stack, cfg } @@ -204,3 +208,36 @@ func dumpConfig(ctx *cli.Context) error { return nil } + +func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { + if ctx.GlobalIsSet(utils.MetricsEnabledFlag.Name) { + cfg.Metrics.Enabled = ctx.GlobalBool(utils.MetricsEnabledFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsEnabledExpensiveFlag.Name) { + cfg.Metrics.EnabledExpensive = ctx.GlobalBool(utils.MetricsEnabledExpensiveFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsHTTPFlag.Name) { + cfg.Metrics.HTTP = ctx.GlobalString(utils.MetricsHTTPFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsPortFlag.Name) { + cfg.Metrics.Port = ctx.GlobalInt(utils.MetricsPortFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsEnableInfluxDBFlag.Name) { + cfg.Metrics.EnableInfluxDB = ctx.GlobalBool(utils.MetricsEnableInfluxDBFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBEndpointFlag.Name) { + cfg.Metrics.InfluxDBEndpoint = ctx.GlobalString(utils.MetricsInfluxDBEndpointFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBDatabaseFlag.Name) { + cfg.Metrics.InfluxDBDatabase = ctx.GlobalString(utils.MetricsInfluxDBDatabaseFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBUsernameFlag.Name) { + cfg.Metrics.InfluxDBUsername = ctx.GlobalString(utils.MetricsInfluxDBUsernameFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBPasswordFlag.Name) { + cfg.Metrics.InfluxDBPassword = ctx.GlobalString(utils.MetricsInfluxDBPasswordFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBTagsFlag.Name) { + cfg.Metrics.InfluxDBTags = ctx.GlobalString(utils.MetricsInfluxDBTagsFlag.Name) + } +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 6f4da58320..688e25618d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -684,12 +684,12 @@ var ( MetricsHTTPFlag = cli.StringFlag{ Name: "metrics.addr", Usage: "Enable stand-alone metrics HTTP server listening interface", - Value: "127.0.0.1", + Value: metrics.DefaultConfig.HTTP, } MetricsPortFlag = cli.IntFlag{ Name: "metrics.port", Usage: "Metrics HTTP server listening port", - Value: 6060, + Value: metrics.DefaultConfig.Port, } MetricsEnableInfluxDBFlag = cli.BoolFlag{ Name: "metrics.influxdb", @@ -698,22 +698,22 @@ var ( MetricsInfluxDBEndpointFlag = cli.StringFlag{ Name: "metrics.influxdb.endpoint", Usage: "InfluxDB API endpoint to report metrics to", - Value: "http://localhost:8086", + Value: metrics.DefaultConfig.InfluxDBEndpoint, } MetricsInfluxDBDatabaseFlag = cli.StringFlag{ Name: "metrics.influxdb.database", Usage: "InfluxDB database name to push reported metrics to", - Value: "geth", + Value: metrics.DefaultConfig.InfluxDBDatabase, } MetricsInfluxDBUsernameFlag = cli.StringFlag{ Name: "metrics.influxdb.username", Usage: "Username to authorize access to the database", - Value: "test", + Value: metrics.DefaultConfig.InfluxDBUsername, } MetricsInfluxDBPasswordFlag = cli.StringFlag{ Name: "metrics.influxdb.password", Usage: "Password to authorize access to the database", - Value: "test", + Value: metrics.DefaultConfig.InfluxDBPassword, } // Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB. // For example `host` tag could be used so that we can group all nodes and average a measurement @@ -722,7 +722,7 @@ var ( MetricsInfluxDBTagsFlag = cli.StringFlag{ Name: "metrics.influxdb.tags", Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", - Value: "host=localhost", + Value: metrics.DefaultConfig.InfluxDBTags, } EWASMInterpreterFlag = cli.StringFlag{ Name: "vm.ewasm", diff --git a/metrics/config.go b/metrics/config.go new file mode 100644 index 0000000000..d05d664265 --- /dev/null +++ b/metrics/config.go @@ -0,0 +1,45 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of go-ethereum. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package metrics + +// Config contains the configuration for the metric collection. +type Config struct { + Enabled bool `toml:",omitempty"` + EnabledExpensive bool `toml:",omitempty"` + HTTP string `toml:",omitempty"` + Port int `toml:",omitempty"` + EnableInfluxDB bool `toml:",omitempty"` + InfluxDBEndpoint string `toml:",omitempty"` + InfluxDBDatabase string `toml:",omitempty"` + InfluxDBUsername string `toml:",omitempty"` + InfluxDBPassword string `toml:",omitempty"` + InfluxDBTags string `toml:",omitempty"` +} + +// DefaultConfig is the default config for metrics used in go-ethereum. +var DefaultConfig = Config{ + Enabled: false, + EnabledExpensive: false, + HTTP: "127.0.0.1", + Port: 6060, + EnableInfluxDB: false, + InfluxDBEndpoint: "http://localhost:8086", + InfluxDBDatabase: "geth", + InfluxDBUsername: "test", + InfluxDBPassword: "test", + InfluxDBTags: "host=localhost", +} From 5e9f5ca5d302298b933668af539ad1e213bdfa6e Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 18 Jan 2021 21:39:43 +0800 Subject: [PATCH 262/948] core/state/snapshot: write snapshot generator in batch (#22163) * core/state/snapshot: write snapshot generator in batch * core: refactor the tests * core: update tests * core: update tests --- core/blockchain_snapshot_test.go | 1033 ++++++++++++++++++------------ core/state/snapshot/generate.go | 35 +- core/state/snapshot/journal.go | 2 +- 3 files changed, 653 insertions(+), 417 deletions(-) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index f35dae1678..cb634a451d 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -28,27 +28,19 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" ) -// snapshotTest is a test case for snapshot recovery. It can be used for -// simulating these scenarios: -// (i) Geth restarts normally with valid legacy snapshot -// (ii) Geth restarts normally with valid new-format snapshot -// (iii) Geth restarts after the crash, with broken legacy snapshot -// (iv) Geth restarts after the crash, with broken new-format snapshot -// (v) Geth restarts normally, but it's requested to be rewound to a lower point via SetHead -// (vi) Geth restarts normally with a stale snapshot -type snapshotTest struct { - legacy bool // Flag whether the loaded snapshot is in legacy format - crash bool // Flag whether the Geth restarts from the previous crash - restartCrash int // Number of blocks to insert after the normal stop, then the crash happens - gapped int // Number of blocks to insert without enabling snapshot - setHead uint64 // Block number to set head back to - +// snapshotTestBasic wraps the common testing fields in the snapshot tests. +type snapshotTestBasic struct { + legacy bool // Wether write the snapshot journal in legacy format chainBlocks int // Number of blocks to generate for the canonical chain snapshotBlock uint64 // Block number of the relevant snapshot disk layer commitBlock uint64 // Block number for which to commit the state to disk @@ -58,56 +50,418 @@ type snapshotTest struct { expHeadFastBlock uint64 // Block number of the expected head fast sync block expHeadBlock uint64 // Block number of the expected head full block expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer + + // share fields, set in runtime + datadir string + db ethdb.Database + gendb ethdb.Database + engine consensus.Engine +} + +func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { + // Create a temporary persistent database + datadir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatalf("Failed to create temporary datadir: %v", err) + } + os.RemoveAll(datadir) + + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + if err != nil { + t.Fatalf("Failed to create persistent database: %v", err) + } + // Initialize a fresh chain + var ( + genesis = new(Genesis).MustCommit(db) + engine = ethash.NewFullFaker() + gendb = rawdb.NewMemoryDatabase() + + // Snapshot is enabled, the first snapshot is created from the Genesis. + // The snapshot memory allowance is 256MB, it means no snapshot flush + // will happen during the block insertion. + cacheConfig = defaultCacheConfig + ) + chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to create chain: %v", err) + } + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, func(i int, b *BlockGen) {}) + + // Insert the blocks with configured settings. + var breakpoints []uint64 + if basic.commitBlock > basic.snapshotBlock { + breakpoints = append(breakpoints, basic.snapshotBlock, basic.commitBlock) + } else { + breakpoints = append(breakpoints, basic.commitBlock, basic.snapshotBlock) + } + var startPoint uint64 + for _, point := range breakpoints { + if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { + t.Fatalf("Failed to import canonical chain start: %v", err) + } + startPoint = point + + if basic.commitBlock > 0 && basic.commitBlock == point { + chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) + } + if basic.snapshotBlock > 0 && basic.snapshotBlock == point { + if basic.legacy { + // Here we commit the snapshot disk root to simulate + // committing the legacy snapshot. + rawdb.WriteSnapshotRoot(db, blocks[point-1].Root()) + } else { + // Flushing the entire snap tree into the disk, the + // relavant (a) snapshot root and (b) snapshot generator + // will be persisted atomically. + chain.snaps.Cap(blocks[point-1].Root(), 0) + diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() + if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { + t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) + } + } + } + } + if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { + t.Fatalf("Failed to import canonical chain tail: %v", err) + } + + // Set runtime fields + basic.datadir = datadir + basic.db = db + basic.gendb = gendb + basic.engine = engine + + // Ugly hack, notify the chain to flush the journal in legacy format + // if it's requested. + if basic.legacy { + chain.writeLegacyJournal = true + } + return chain, blocks } -func (tt *snapshotTest) dump() string { +func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) { + // Iterate over all the remaining blocks and ensure there are no gaps + verifyNoGaps(t, chain, true, blocks) + verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks) + + if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadHeader { + t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadHeader) + } + if head := chain.CurrentFastBlock(); head.NumberU64() != basic.expHeadFastBlock { + t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadFastBlock) + } + if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock { + t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock) + } + + // Check the disk layer, ensure they are matched + block := chain.GetBlockByNumber(basic.expSnapshotBottom) + if block == nil { + t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) + } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { + t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) + } + + // Check the snapshot, ensure it's integrated + if err := snapshot.VerifyState(chain.snaps, block.Root()); err != nil { + t.Errorf("The disk layer is not integrated %v", err) + } +} + +func (basic *snapshotTestBasic) dump() string { buffer := new(strings.Builder) fmt.Fprint(buffer, "Chain:\n G") - for i := 0; i < tt.chainBlocks; i++ { + for i := 0; i < basic.chainBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprint(buffer, " (HEAD)\n\n") fmt.Fprintf(buffer, "Commit: G") - if tt.commitBlock > 0 { - fmt.Fprintf(buffer, ", C%d", tt.commitBlock) + if basic.commitBlock > 0 { + fmt.Fprintf(buffer, ", C%d", basic.commitBlock) } fmt.Fprint(buffer, "\n") fmt.Fprintf(buffer, "Snapshot: G") - if tt.snapshotBlock > 0 { - fmt.Fprintf(buffer, ", C%d", tt.snapshotBlock) + if basic.snapshotBlock > 0 { + fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock) } fmt.Fprint(buffer, "\n") - if tt.crash { - fmt.Fprintf(buffer, "\nCRASH\n\n") - } else { - fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setHead) - } + //if crash { + // fmt.Fprintf(buffer, "\nCRASH\n\n") + //} else { + // fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", basic.setHead) + //} fmt.Fprintf(buffer, "------------------------------\n\n") fmt.Fprint(buffer, "Expected in leveldb:\n G") - for i := 0; i < tt.expCanonicalBlocks; i++ { + for i := 0; i < basic.expCanonicalBlocks; i++ { fmt.Fprintf(buffer, "->C%d", i+1) } fmt.Fprintf(buffer, "\n\n") - fmt.Fprintf(buffer, "Expected head header : C%d\n", tt.expHeadHeader) - fmt.Fprintf(buffer, "Expected head fast block: C%d\n", tt.expHeadFastBlock) - if tt.expHeadBlock == 0 { + fmt.Fprintf(buffer, "Expected head header : C%d\n", basic.expHeadHeader) + fmt.Fprintf(buffer, "Expected head fast block: C%d\n", basic.expHeadFastBlock) + if basic.expHeadBlock == 0 { fmt.Fprintf(buffer, "Expected head block : G\n") } else { - fmt.Fprintf(buffer, "Expected head block : C%d\n", tt.expHeadBlock) + fmt.Fprintf(buffer, "Expected head block : C%d\n", basic.expHeadBlock) } - if tt.expSnapshotBottom == 0 { + if basic.expSnapshotBottom == 0 { fmt.Fprintf(buffer, "Expected snapshot disk : G\n") } else { - fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", tt.expSnapshotBottom) + fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", basic.expSnapshotBottom) } return buffer.String() } +func (basic *snapshotTestBasic) teardown() { + basic.db.Close() + basic.gendb.Close() + os.RemoveAll(basic.datadir) +} + +// snapshotTest is a test case type for normal snapshot recovery. +// It can be used for testing that restart Geth normally. +type snapshotTest struct { + snapshotTestBasic +} + +func (snaptest *snapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Restart the chain normally + chain.Stop() + newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// crashSnapshotTest is a test case type for innormal snapshot recovery. +// It can be used for testing that restart Geth after the crash. +type crashSnapshotTest struct { + snapshotTestBasic +} + +func (snaptest *crashSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Pull the plug on the database, simulating a hard crash + db := chain.db + db.Close() + + // Start a new blockchain back up and see where the repair leads us + newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "") + if err != nil { + t.Fatalf("Failed to reopen persistent database: %v", err) + } + defer newdb.Close() + + // The interesting thing is: instead of starting the blockchain after + // the crash, we do restart twice here: one after the crash and one + // after the normal stop. It's used to ensure the broken snapshot + // can be detected all the time. + newchain, err := NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newchain.Stop() + + newchain, err = NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// gappedSnapshotTest is a test type used to test this scenario: +// - have a complete snapshot +// - restart without enabling the snapshot +// - insert a few blocks +// - restart with enabling the snapshot again +type gappedSnapshotTest struct { + snapshotTestBasic + gapped int // Number of blocks to insert without enabling snapshot +} + +func (snaptest *gappedSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Insert blocks without enabling snapshot if gapping is required. + chain.Stop() + gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, func(i int, b *BlockGen) {}) + + // Insert a few more blocks without enabling snapshot + var cacheConfig = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + } + newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newchain.InsertChain(gappedBlocks) + newchain.Stop() + + // Restart the chain with enabling the snapshot + newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// setHeadSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - set the head to a lower point +// - restart +type setHeadSnapshotTest struct { + snapshotTestBasic + setHead uint64 // Block number to set head back to +} + +func (snaptest *setHeadSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Rewind the chain if setHead operation is required. + chain.SetHead(snaptest.setHead) + chain.Stop() + + newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// restartCrashSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - restart chain +// - insert more blocks with enabling the snapshot +// - commit the snapshot +// - crash +// - restart again +type restartCrashSnapshotTest struct { + snapshotTestBasic + newBlocks int +} + +func (snaptest *restartCrashSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Firstly, stop the chain properly, with all snapshot journal + // and state committed. + chain.Stop() + + newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + newchain.InsertChain(newBlocks) + + // Commit the entire snapshot into the disk if requested. Note only + // (a) snapshot root and (b) snapshot generator will be committed, + // the diff journal is not. + newchain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0) + + // Simulate the blockchain crash + // Don't call chain.Stop here, so that no snapshot + // journal and latest state will be committed + + // Restart the chain after the crash + newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + defer newchain.Stop() + + snaptest.verify(t, newchain, blocks) +} + +// wipeCrashSnapshotTest is the test type used to test this scenario: +// - have a complete snapshot +// - restart, insert more blocks without enabling the snapshot +// - restart again with enabling the snapshot +// - crash +type wipeCrashSnapshotTest struct { + snapshotTestBasic + newBlocks int +} + +func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { + // It's hard to follow the test case, visualize the input + // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + // fmt.Println(tt.dump()) + chain, blocks := snaptest.prepare(t) + + // Firstly, stop the chain properly, with all snapshot journal + // and state committed. + chain.Stop() + + config := &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + } + newchain, err := NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + newchain.InsertChain(newBlocks) + newchain.Stop() + + // Restart the chain, the wiper should starts working + config = &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 256, + SnapshotWait: false, // Don't wait rebuild + } + newchain, err = NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + // Simulate the blockchain crash. + + newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("Failed to recreate chain: %v", err) + } + snaptest.verify(t, newchain, blocks) +} + // Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot // journal will be persisted correctly. In this case no snapshot recovery is // required. @@ -129,20 +483,21 @@ func TestRestartWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C8 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: false, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 8, - expSnapshotBottom: 0, // Initial disk layer built from genesis - }) + test := &snapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }, + } + test.test(t) + test.teardown() } // Tests a Geth restart with valid but "legacy" snapshot. Before the shutdown, @@ -166,20 +521,22 @@ func TestRestartWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C8 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 8, - expSnapshotBottom: 0, // Initial disk layer built from genesis - }) + t.Skip("Legacy format testing is not supported") + test := &snapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken snapshot. In this case the @@ -205,20 +562,21 @@ func TestNoCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : C4 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }) + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken snapshot. In this case the @@ -244,20 +602,21 @@ func TestLowCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C2 // Expected snapshot disk : C4 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 2, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 2, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }) + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken snapshot. In this case @@ -283,20 +642,21 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : C4 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 6, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }) + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken and "legacy format" @@ -321,20 +681,22 @@ func TestNoCommitCrashWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) - }) + t.Skip("Legacy format testing is not supported") + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken and "legacy format" @@ -359,20 +721,22 @@ func TestLowCommitCrashWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C2 // Expected snapshot disk : C2 - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 2, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 2, - expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD - }) + t.Skip("Legacy format testing is not supported") + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD + }, + } + test.test(t) + test.teardown() } // Tests a Geth was crashed and restarts with a broken and "legacy format" @@ -402,20 +766,22 @@ func TestHighCommitCrashWithLegacySnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: true, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 6, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) - }) + t.Skip("Legacy format testing is not supported") + test := &crashSnapshotTest{ + snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) + }, + } + test.test(t) + test.teardown() } // Tests a Geth was running with snapshot enabled. Then restarts without @@ -439,20 +805,22 @@ func TestGappedNewSnapshot(t *testing.T) { // Expected head fast block: C10 // Expected head block : C10 // Expected snapshot disk : C10 - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: false, - gapped: 2, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD - }) + test := &gappedSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }, + gapped: 2, + } + test.test(t) + test.teardown() } // Tests a Geth was running with leagcy snapshot enabled. Then restarts @@ -476,20 +844,23 @@ func TestGappedLegacySnapshot(t *testing.T) { // Expected head fast block: C10 // Expected head block : C10 // Expected snapshot disk : C10 - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - gapped: 2, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD - }) + t.Skip("Legacy format testing is not supported") + test := &gappedSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }, + gapped: 2, + } + test.test(t) + test.teardown() } // Tests the Geth was running with snapshot enabled and resetHead is applied. @@ -513,20 +884,22 @@ func TestSetHeadWithNewSnapshot(t *testing.T) { // Expected head fast block: C4 // Expected head block : C4 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: false, - crash: false, - gapped: 0, - setHead: 4, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 4, - expHeadHeader: 4, - expHeadFastBlock: 4, - expHeadBlock: 4, - expSnapshotBottom: 0, // The initial disk layer is built from the genesis - }) + test := &setHeadSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }, + setHead: 4, + } + test.test(t) + test.teardown() } // Tests the Geth was running with snapshot(legacy-format) enabled and resetHead @@ -550,20 +923,23 @@ func TestSetHeadWithLegacySnapshot(t *testing.T) { // Expected head fast block: C4 // Expected head block : C4 // Expected snapshot disk : G - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - gapped: 0, - setHead: 4, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 4, - expHeadHeader: 4, - expHeadFastBlock: 4, - expHeadBlock: 4, - expSnapshotBottom: 0, // The initial disk layer is built from the genesis - }) + t.Skip("Legacy format testing is not supported") + test := &setHeadSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }, + setHead: 4, + } + test.test(t) + test.teardown() } // Tests the Geth was running with snapshot(legacy-format) enabled and upgrades @@ -589,209 +965,60 @@ func TestRecoverSnapshotFromCrashWithLegacyDiffJournal(t *testing.T) { // Expected head fast block: C10 // Expected head block : C8 // Expected snapshot disk : C10 - testSnapshot(t, &snapshotTest{ - legacy: true, - crash: false, - restartCrash: 2, - gapped: 0, - setHead: 0, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 8, // The persisted state in the first running - expSnapshotBottom: 10, // The persisted disk layer in the second running - }) -} - -func testSnapshot(t *testing.T, tt *snapshotTest) { - // It's hard to follow the test case, visualize the input - // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - // fmt.Println(tt.dump()) - - // Create a temporary persistent database - datadir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Failed to create temporary datadir: %v", err) - } - os.RemoveAll(datadir) - - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") - if err != nil { - t.Fatalf("Failed to create persistent database: %v", err) - } - defer db.Close() // Might double close, should be fine - - // Initialize a fresh chain - var ( - genesis = new(Genesis).MustCommit(db) - engine = ethash.NewFullFaker() - gendb = rawdb.NewMemoryDatabase() - - // Snapshot is enabled, the first snapshot is created from the Genesis. - // The snapshot memory allowance is 256MB, it means no snapshot flush - // will happen during the block insertion. - cacheConfig = defaultCacheConfig - ) - chain, err := NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to create chain: %v", err) - } - blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, tt.chainBlocks, func(i int, b *BlockGen) {}) - - // Insert the blocks with configured settings. - var breakpoints []uint64 - if tt.commitBlock > tt.snapshotBlock { - breakpoints = append(breakpoints, tt.snapshotBlock, tt.commitBlock) - } else { - breakpoints = append(breakpoints, tt.commitBlock, tt.snapshotBlock) - } - var startPoint uint64 - for _, point := range breakpoints { - if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { - t.Fatalf("Failed to import canonical chain start: %v", err) - } - startPoint = point - - if tt.commitBlock > 0 && tt.commitBlock == point { - chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) - } - if tt.snapshotBlock > 0 && tt.snapshotBlock == point { - if tt.legacy { - // Here we commit the snapshot disk root to simulate - // committing the legacy snapshot. - rawdb.WriteSnapshotRoot(db, blocks[point-1].Root()) - } else { - chain.snaps.Cap(blocks[point-1].Root(), 0) - diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() - if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { - t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) - } - } - } - } - if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { - t.Fatalf("Failed to import canonical chain tail: %v", err) + t.Skip("Legacy format testing is not supported") + test := &restartCrashSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: true, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 8, // The persisted state in the first running + expSnapshotBottom: 10, // The persisted disk layer in the second running + }, + newBlocks: 2, } - // Set the flag for writing legacy journal if necessary - if tt.legacy { - chain.writeLegacyJournal = true - } - // Pull the plug on the database, simulating a hard crash - if tt.crash { - db.Close() - - // Start a new blockchain back up and see where the repair leads us - db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") - if err != nil { - t.Fatalf("Failed to reopen persistent database: %v", err) - } - defer db.Close() - - // The interesting thing is: instead of start the blockchain after - // the crash, we do restart twice here: one after the crash and one - // after the normal stop. It's used to ensure the broken snapshot - // can be detected all the time. - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - chain.Stop() - - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else if tt.gapped > 0 { - // Insert blocks without enabling snapshot if gapping is required. - chain.Stop() - gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.gapped, func(i int, b *BlockGen) {}) - - // Insert a few more blocks without enabling snapshot - var cacheConfig = &CacheConfig{ - TrieCleanLimit: 256, - TrieDirtyLimit: 256, - TrieTimeLimit: 5 * time.Minute, - SnapshotLimit: 0, - } - chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - chain.InsertChain(gappedBlocks) - chain.Stop() - - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else if tt.setHead != 0 { - // Rewind the chain if setHead operation is required. - chain.SetHead(tt.setHead) - chain.Stop() - - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else if tt.restartCrash != 0 { - // Firstly, stop the chain properly, with all snapshot journal - // and state committed. - chain.Stop() - - // Restart chain, forcibly flush the disk layer journal with new format - newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.restartCrash, func(i int, b *BlockGen) {}) - chain, err = NewBlockChain(db, cacheConfig, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - chain.InsertChain(newBlocks) - chain.Snapshots().Cap(newBlocks[len(newBlocks)-1].Root(), 0) - - // Simulate the blockchain crash - // Don't call chain.Stop here, so that no snapshot - // journal and latest state will be committed - - // Restart the chain after the crash - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } else { - chain.Stop() - - // Restart the chain normally - chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) - if err != nil { - t.Fatalf("Failed to recreate chain: %v", err) - } - defer chain.Stop() - } - - // Iterate over all the remaining blocks and ensure there are no gaps - verifyNoGaps(t, chain, true, blocks) - verifyCutoff(t, chain, true, blocks, tt.expCanonicalBlocks) + test.test(t) + test.teardown() +} - if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader { - t.Errorf("Head header mismatch: have %d, want %d", head.Number, tt.expHeadHeader) - } - if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock { - t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock) - } - if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock { - t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadBlock) - } - // Check the disk layer, ensure they are matched - block := chain.GetBlockByNumber(tt.expSnapshotBottom) - if block == nil { - t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", tt.expSnapshotBottom) - } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { - t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) +// Tests the Geth was running with a complete snapshot and then imports a few +// more new blocks on top without enabling the snapshot. After the restart, +// crash happens. Check everything is ok after the restart. +func TestRecoverSnapshotFromWipingCrash(t *testing.T) { + // Chain: + // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) + // + // Commit: G + // Snapshot: G + // + // SetHead(0) + // + // ------------------------------ + // + // Expected in leveldb: + // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 + // + // Expected head header : C10 + // Expected head fast block: C10 + // Expected head block : C8 + // Expected snapshot disk : C10 + test := &wipeCrashSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + legacy: false, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, + }, + newBlocks: 2, } + test.test(t) + test.teardown() } diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index fcc6b44cb6..2b41dd5513 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -101,18 +101,26 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i wiper = wipeSnapshot(diskdb, true) } // Create a new disk layer with an initialized state marker at zero - rawdb.WriteSnapshotRoot(diskdb, root) - + var ( + stats = &generatorStats{wiping: wiper, start: time.Now()} + batch = diskdb.NewBatch() + genMarker = []byte{} // Initialized but empty! + ) + rawdb.WriteSnapshotRoot(batch, root) + journalProgress(batch, genMarker, stats) + if err := batch.Write(); err != nil { + log.Crit("Failed to write initialized state marker", "error", err) + } base := &diskLayer{ diskdb: diskdb, triedb: triedb, root: root, cache: fastcache.New(cache * 1024 * 1024), - genMarker: []byte{}, // Initialized but empty! + genMarker: genMarker, genPending: make(chan struct{}), genAbort: make(chan chan *generatorStats), } - go base.generate(&generatorStats{wiping: wiper, start: time.Now()}) + go base.generate(stats) log.Debug("Start snapshot generation", "root", root) return base } @@ -137,10 +145,12 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta panic(err) // Cannot happen, here to catch dev errors } var logstr string - switch len(marker) { - case 0: + switch { + case marker == nil: logstr = "done" - case common.HashLength: + case bytes.Equal(marker, []byte{}): + logstr = "empty" + case len(marker) == common.HashLength: logstr = fmt.Sprintf("%#x", marker) default: logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) @@ -307,13 +317,12 @@ func (dl *diskLayer) generate(stats *generatorStats) { abort <- stats return } - // Snapshot fully generated, set the marker to nil - if batch.ValueSize() > 0 { - // Ensure the generator entry is in sync with the data - journalProgress(batch, nil, stats) + // Snapshot fully generated, set the marker to nil. + // Note even there is nothing to commit, persist the + // generator anyway to mark the snapshot is complete. + journalProgress(batch, nil, stats) + batch.Write() - batch.Write() - } log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, "storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 178ba08902..d7e454cceb 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -441,6 +441,6 @@ func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { if err := rlp.Encode(buffer, storage); err != nil { return common.Hash{}, err } - log.Debug("Legacy journalled disk layer", "root", dl.root, "parent", dl.parent.Root()) + log.Debug("Legacy journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) return base, nil } From 24c1e3053b6767add29bf257c7943dc6aa5fa91d Mon Sep 17 00:00:00 2001 From: Alex Mazalov Date: Tue, 19 Jan 2021 08:26:42 +0000 Subject: [PATCH 263/948] cmd/geth: graceful shutdown if disk is full (#22103) Adding warnings of free disk space left and graceful shutdown when there is not enough space left. This also adds a flag datadir.minfreedisk which can be used to set the trigger for low disk space, and setting it to zero disables the check. Co-authored-by: Martin Holst Swende Co-authored-by: Felix Lange --- cmd/geth/main.go | 3 ++- cmd/geth/usage.go | 1 + cmd/utils/cmd.go | 34 +++++++++++++++++++++++++++++- cmd/utils/diskusage.go | 35 +++++++++++++++++++++++++++++++ cmd/utils/diskusage_windows.go | 38 ++++++++++++++++++++++++++++++++++ cmd/utils/flags.go | 4 ++++ 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 cmd/utils/diskusage.go create mode 100644 cmd/utils/diskusage_windows.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 7af24e6523..e577ab370d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -65,6 +65,7 @@ var ( utils.LegacyBootnodesV5Flag, utils.DataDirFlag, utils.AncientFlag, + utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.ExternalSignerFlag, utils.NoUSBFlag, @@ -368,7 +369,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { debug.Memsize.Add("node", stack) // Start up the node itself - utils.StartNode(stack) + utils.StartNode(ctx, stack) // Unlock any account specifically requested unlockAccounts(ctx, stack) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 78ebb807e1..25accc9b79 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -36,6 +36,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ configFileFlag, utils.DataDirFlag, utils.AncientFlag, + utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 869cf90ea5..4306216892 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -26,17 +26,20 @@ import ( "runtime" "strings" "syscall" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rlp" + "gopkg.in/urfave/cli.v1" ) const ( @@ -63,7 +66,7 @@ func Fatalf(format string, args ...interface{}) { os.Exit(1) } -func StartNode(stack *node.Node) { +func StartNode(ctx *cli.Context, stack *node.Node) { if err := stack.Start(); err != nil { Fatalf("Error starting protocol stack: %v", err) } @@ -71,6 +74,17 @@ func StartNode(stack *node.Node) { sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigc) + + minFreeDiskSpace := eth.DefaultConfig.TrieDirtyCache + if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) { + minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name) + } else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { + minFreeDiskSpace = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 + } + if minFreeDiskSpace > 0 { + go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024) + } + <-sigc log.Info("Got interrupt, shutting down...") go stack.Close() @@ -85,6 +99,24 @@ func StartNode(stack *node.Node) { }() } +func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) { + for { + freeSpace, err := getFreeDiskSpace(path) + if err != nil { + log.Warn("Failed to get free disk space", "path", path, "err", err) + break + } + if freeSpace < freeDiskSpaceCritical { + log.Error("Low disk space. Gracefully shutting down Geth to prevent database corruption.", "available", common.StorageSize(freeSpace)) + sigc <- syscall.SIGTERM + break + } else if freeSpace < 2*freeDiskSpaceCritical { + log.Warn("Disk space is running low. Geth will shutdown if disk space runs below critical level.", "available", common.StorageSize(freeSpace), "critical_level", common.StorageSize(freeDiskSpaceCritical)) + } + time.Sleep(60 * time.Second) + } +} + func ImportChain(chain *core.BlockChain, fn string) error { // Watch for Ctrl-C while the import is running. // If a signal is received, the import will stop at the next batch. diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go new file mode 100644 index 0000000000..a822118a39 --- /dev/null +++ b/cmd/utils/diskusage.go @@ -0,0 +1,35 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build !windows + +package utils + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + return stat.Bavail * uint64(stat.Bsize), nil +} diff --git a/cmd/utils/diskusage_windows.go b/cmd/utils/diskusage_windows.go new file mode 100644 index 0000000000..9bf7740b99 --- /dev/null +++ b/cmd/utils/diskusage_windows.go @@ -0,0 +1,38 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "fmt" + + "golang.org/x/sys/windows" +) + +func getFreeDiskSpace(path string) (uint64, error) { + + cwd, err := windows.UTF16PtrFromString(path) + if err != nil { + return 0, fmt.Errorf("failed to call UTF16PtrFromString: %v", err) + } + + var freeBytesAvailableToCaller, totalNumberOfBytes, totalNumberOfFreeBytes uint64 + if err := windows.GetDiskFreeSpaceEx(cwd, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); err != nil { + return 0, fmt.Errorf("failed to call GetDiskFreeSpaceEx: %v", err) + } + + return freeBytesAvailableToCaller, nil +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 688e25618d..df036cbbf0 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -113,6 +113,10 @@ var ( Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", } + MinFreeDiskSpaceFlag = DirectoryFlag{ + Name: "datadir.minfreedisk", + Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)", + } KeyStoreDirFlag = DirectoryFlag{ Name: "keystore", Usage: "Directory for the keystore (default = inside the datadir)", From 45cb1a580abad0d4e8caa1c8b7dfacd5ef3d27bc Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 19 Jan 2021 17:52:45 +0800 Subject: [PATCH 264/948] eth, les: add new config field SyncFromCheckpoint (#22123) This PR introduces a new config field SyncFromCheckpoint for light client. In some special scenarios, it's required to start synchronization from some arbitrary checkpoint or even from the scratch. So this PR offers this flexibility to users so that the synchronization start point can be configured. There are two relevant configs: SyncFromCheckpoint and Checkpoint. - If the SyncFromCheckpoint is true, the light client will try to sync from the specified checkpoint. - If the Checkpoint is not configured, then the light client will sync from the scratch(from the latest header if the database is not empty) Additional notes: these two configs are not visible in the CLI flags but only accessable in the config file. Example Usage: [Eth] SyncFromCheckpoint = true [Eth.Checkpoint] SectionIndex = 100 SectionHead = "0xabc" CHTRoot = "0xabc" BloomRoot = "0xabc" PS. Historical checkpoint can be retrieved from the synced full node or light client via les_getCheckpoint API. --- eth/config.go | 11 +-- eth/gen_config.go | 12 ++++ les/client_handler.go | 9 ++- les/commons.go | 6 +- les/sync.go | 52 ++++++++++----- les/sync_test.go | 152 ++++++++++++++++++++++++++++++++++++++++-- les/test_helper.go | 2 +- 7 files changed, 209 insertions(+), 35 deletions(-) diff --git a/eth/config.go b/eth/config.go index 77d03e9569..446467d364 100644 --- a/eth/config.go +++ b/eth/config.go @@ -127,11 +127,12 @@ type Config struct { Whitelist map[uint64]common.Hash `toml:"-"` // Light client options - LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests - LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers - LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers - LightPeers int `toml:",omitempty"` // Maximum number of LES client peers - LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests + LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers + LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers + LightPeers int `toml:",omitempty"` // Maximum number of LES client peers + LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint // Ultra Light client options UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers diff --git a/eth/gen_config.go b/eth/gen_config.go index dd04635eee..e68b29ce5e 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -21,6 +21,7 @@ func (c Config) MarshalTOML() (interface{}, error) { NetworkId uint64 SyncMode downloader.SyncMode EthDiscoveryURLs []string + SnapDiscoveryURLs []string NoPruning bool NoPrefetch bool TxLookupLimit uint64 `toml:",omitempty"` @@ -30,6 +31,7 @@ func (c Config) MarshalTOML() (interface{}, error) { LightEgress int `toml:",omitempty"` LightPeers int `toml:",omitempty"` LightNoPrune bool `toml:",omitempty"` + SyncFromCheckpoint bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction int `toml:",omitempty"` UltraLightOnlyAnnounce bool `toml:",omitempty"` @@ -62,6 +64,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.NetworkId = c.NetworkId enc.SyncMode = c.SyncMode enc.EthDiscoveryURLs = c.EthDiscoveryURLs + enc.SnapDiscoveryURLs = c.SnapDiscoveryURLs enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.TxLookupLimit = c.TxLookupLimit @@ -71,6 +74,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.LightEgress = c.LightEgress enc.LightPeers = c.LightPeers enc.LightNoPrune = c.LightNoPrune + enc.SyncFromCheckpoint = c.SyncFromCheckpoint enc.UltraLightServers = c.UltraLightServers enc.UltraLightFraction = c.UltraLightFraction enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce @@ -107,6 +111,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { NetworkId *uint64 SyncMode *downloader.SyncMode EthDiscoveryURLs []string + SnapDiscoveryURLs []string NoPruning *bool NoPrefetch *bool TxLookupLimit *uint64 `toml:",omitempty"` @@ -116,6 +121,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { LightEgress *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` LightNoPrune *bool `toml:",omitempty"` + SyncFromCheckpoint *bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction *int `toml:",omitempty"` UltraLightOnlyAnnounce *bool `toml:",omitempty"` @@ -159,6 +165,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.EthDiscoveryURLs != nil { c.EthDiscoveryURLs = dec.EthDiscoveryURLs } + if dec.SnapDiscoveryURLs != nil { + c.SnapDiscoveryURLs = dec.SnapDiscoveryURLs + } if dec.NoPruning != nil { c.NoPruning = *dec.NoPruning } @@ -186,6 +195,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.LightNoPrune != nil { c.LightNoPrune = *dec.LightNoPrune } + if dec.SyncFromCheckpoint != nil { + c.SyncFromCheckpoint = *dec.SyncFromCheckpoint + } if dec.UltraLightServers != nil { c.UltraLightServers = dec.UltraLightServers } diff --git a/les/client_handler.go b/les/client_handler.go index 6de5766961..6cd786cda0 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -44,9 +44,12 @@ type clientHandler struct { downloader *downloader.Downloader backend *LightEthereum - closeCh chan struct{} - wg sync.WaitGroup // WaitGroup used to track all connected peers. - syncDone func() // Test hooks when syncing is done. + closeCh chan struct{} + wg sync.WaitGroup // WaitGroup used to track all connected peers. + + // Hooks used in the testing + syncStart func(header *types.Header) // Hook called when the syncing is started + syncEnd func(header *types.Header) // Hook called when the syncing is done } func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { diff --git a/les/commons.go b/les/commons.go index 003e196d2b..8de1057d26 100644 --- a/les/commons.go +++ b/les/commons.go @@ -157,17 +157,17 @@ func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig config = params.CheckpointOracles[genesis] } if config == nil { - log.Info("Checkpoint registrar is not enabled") + log.Info("Checkpoint oracle is not enabled") return nil } if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold { - log.Warn("Invalid checkpoint registrar config") + log.Warn("Invalid checkpoint oracle config") return nil } oracle := checkpointoracle.New(config, c.localCheckpoint) rpcClient, _ := node.Attach() client := ethclient.NewClient(rpcClient) oracle.Start(client) - log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) + log.Info("Configured checkpoint oracle", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) return oracle } diff --git a/les/sync.go b/les/sync.go index ad3a0e0f3c..fa5ef4ff82 100644 --- a/les/sync.go +++ b/les/sync.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" ) var errInvalidCheckpoint = errors.New("invalid advertised checkpoint") @@ -98,22 +99,33 @@ func (h *clientHandler) synchronise(peer *serverPeer) { if currentTd != nil && peer.Td().Cmp(currentTd) < 0 { return } - // Recap the checkpoint. - // - // The light client may be connected to several different versions of the server. - // (1) Old version server which can not provide stable checkpoint in the handshake packet. - // => Use hardcoded checkpoint or empty checkpoint - // (2) New version server but simple checkpoint syncing is not enabled(e.g. mainnet, new testnet or private network) - // => Use hardcoded checkpoint or empty checkpoint - // (3) New version server but the provided stable checkpoint is even lower than the hardcoded one. - // => Use hardcoded checkpoint + // Recap the checkpoint. The light client may be connected to several different + // versions of the server. + // (1) Old version server which can not provide stable checkpoint in the + // handshake packet. + // => Use local checkpoint or empty checkpoint + // (2) New version server but simple checkpoint syncing is not enabled + // (e.g. mainnet, new testnet or private network) + // => Use local checkpoint or empty checkpoint + // (3) New version server but the provided stable checkpoint is even lower + // than the local one. + // => Use local checkpoint // (4) New version server with valid and higher stable checkpoint // => Use provided checkpoint - var checkpoint = &peer.checkpoint - var hardcoded bool + var ( + local bool + checkpoint = &peer.checkpoint + ) if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex { - checkpoint = h.checkpoint // Use the hardcoded one. - hardcoded = true + local, checkpoint = true, h.checkpoint + } + // Replace the checkpoint with locally configured one If it's required by + // users. Nil checkpoint means synchronization from the scratch. + if h.backend.config.SyncFromCheckpoint { + local, checkpoint = true, h.backend.config.Checkpoint + if h.backend.config.Checkpoint == nil { + checkpoint = ¶ms.TrustedCheckpoint{} + } } // Determine whether we should run checkpoint syncing or normal light syncing. // @@ -121,7 +133,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { // // 1. The checkpoint is empty // 2. The latest head block of the local chain is above the checkpoint. - // 3. The checkpoint is hardcoded(recap with local hardcoded checkpoint) + // 3. The checkpoint is local(replaced with local checkpoint) // 4. For some networks the checkpoint syncing is not activated. mode := checkpointSync switch { @@ -131,7 +143,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) { case latest.Number.Uint64() >= (checkpoint.SectionIndex+1)*h.backend.iConfig.ChtSize-1: mode = lightSync log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint") - case hardcoded: + case local: mode = legacyCheckpointSync log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded") case h.backend.oracle == nil || !h.backend.oracle.IsRunning(): @@ -143,12 +155,14 @@ func (h *clientHandler) synchronise(peer *serverPeer) { } log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated") } + // Notify testing framework if syncing has completed(for testing purpose). defer func() { - if h.syncDone != nil { - h.syncDone() + if h.syncEnd != nil { + h.syncEnd(h.backend.blockchain.CurrentHeader()) } }() + start := time.Now() if mode == checkpointSync || mode == legacyCheckpointSync { // Validate the advertised checkpoint @@ -177,6 +191,10 @@ func (h *clientHandler) synchronise(peer *serverPeer) { return } } + + if h.syncStart != nil { + h.syncStart(h.backend.blockchain.CurrentHeader()) + } // Fetch the remaining block headers based on the current chain header. if err := h.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil { log.Debug("Synchronise failed", "reason", err) diff --git a/les/sync_test.go b/les/sync_test.go index 2eb0f88bf9..64e7283663 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/params" @@ -53,7 +54,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { time.Sleep(10 * time.Millisecond) } } - // Generate 128+1 blocks (totally 1 CHT sections) + // Generate 128+1 blocks (totally 1 CHT section) server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) defer tearDown() @@ -100,8 +101,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } done := make(chan error) - client.handler.syncDone = func() { - header := client.handler.backend.blockchain.CurrentHeader() + client.handler.syncEnd = func(header *types.Header) { if header.Number.Uint64() == expected { done <- nil } else { @@ -144,7 +144,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { time.Sleep(10 * time.Millisecond) } } - // Generate 512+4 blocks (totally 1 CHT sections) + // Generate 128+1 blocks (totally 1 CHT section) server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) defer tearDown() @@ -198,8 +198,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } done := make(chan error) - client.handler.syncDone = func() { - header := client.handler.backend.blockchain.CurrentHeader() + client.handler.syncEnd = func(header *types.Header) { if header.Number.Uint64() == expected { done <- nil } else { @@ -220,3 +219,144 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { t.Error("checkpoint syncing timeout") } } + +func TestSyncFromConfiguredCheckpoint(t *testing.T) { + config := light.TestServerIndexerConfig + + waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { + break + } + time.Sleep(10 * time.Millisecond) + } + } + // Generate 256+1 blocks (totally 2 CHT sections) + server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + defer tearDown() + + // Configure the local checkpoint(the first section) + head := server.handler.blockchain.GetHeaderByNumber(config.ChtSize - 1).Hash() + cp := ¶ms.TrustedCheckpoint{ + SectionIndex: 0, + SectionHead: head, + CHTRoot: light.GetChtRoot(server.db, 0, head), + BloomRoot: light.GetBloomTrieRoot(server.db, 0, head), + } + client.handler.backend.config.SyncFromCheckpoint = true + client.handler.backend.config.Checkpoint = cp + client.handler.checkpoint = cp + client.handler.backend.blockchain.AddTrustedCheckpoint(cp) + + var ( + start = make(chan error, 1) + end = make(chan error, 1) + expectStart = config.ChtSize - 1 + expectEnd = 2*config.ChtSize + config.ChtConfirms + ) + client.handler.syncStart = func(header *types.Header) { + if header.Number.Uint64() == expectStart { + start <- nil + } else { + start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number) + } + } + client.handler.syncEnd = func(header *types.Header) { + if header.Number.Uint64() == expectEnd { + end <- nil + } else { + end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number) + } + } + // Create connected peer pair. + if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler); err != nil { + t.Fatalf("Failed to connect testing peers %v", err) + } + + select { + case err := <-start: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } + + select { + case err := <-end: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } +} + +func TestSyncAll(t *testing.T) { + config := light.TestServerIndexerConfig + + waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 2 && bts >= 2 { + break + } + time.Sleep(10 * time.Millisecond) + } + } + // Generate 256+1 blocks (totally 2 CHT sections) + server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + defer tearDown() + + client.handler.backend.config.SyncFromCheckpoint = true + + var ( + start = make(chan error, 1) + end = make(chan error, 1) + expectStart = uint64(0) + expectEnd = 2*config.ChtSize + config.ChtConfirms + ) + client.handler.syncStart = func(header *types.Header) { + if header.Number.Uint64() == expectStart { + start <- nil + } else { + start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number) + } + } + client.handler.syncEnd = func(header *types.Header) { + if header.Number.Uint64() == expectEnd { + end <- nil + } else { + end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number) + } + } + // Create connected peer pair. + if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler); err != nil { + t.Fatalf("Failed to connect testing peers %v", err) + } + + select { + case err := <-start: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } + + select { + case err := <-end: + if err != nil { + t.Error("sync failed", err) + } + return + case <-time.NewTimer(10 * time.Second).C: + t.Error("checkpoint syncing timeout") + } +} diff --git a/les/test_helper.go b/les/test_helper.go index d108a8dace..04482ba68e 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -541,7 +541,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer ) if connect { done := make(chan struct{}) - client.syncDone = func() { close(done) } + client.syncEnd = func(_ *types.Header) { close(done) } cpeer, speer, err = newTestPeerPair("peer", protocol, server, client) if err != nil { t.Fatalf("Failed to connect testing peers %v", err) From d1301eb0df07219d576c56400128c56f4f65beab Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 20 Jan 2021 18:21:13 +0100 Subject: [PATCH 265/948] oss-fuzz: fix abi fuzzer (#22199) --- oss-fuzz.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 7ae4d77b29..860ce0952f 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -100,7 +100,7 @@ compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty -compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi +compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul From 7da8f75d5bab50ad5477d50bc96d383e5c8359b8 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 21 Jan 2021 02:34:21 +0800 Subject: [PATCH 266/948] go.mod: upgrade golang-lru (#22134) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 57c15a34f6..d630dc6996 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 - github.com/hashicorp/golang-lru v0.5.4 + github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v1.0.0 diff --git a/go.sum b/go.sum index d063cd8f08..f8939df2ba 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDF github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= From 81bf9f97c9edf0169e47b5b700715e2eae58e08a Mon Sep 17 00:00:00 2001 From: meowsbits Date: Wed, 20 Jan 2021 15:45:01 -0600 Subject: [PATCH 267/948] downloader: extract findAncestor search functions (#21744) This is a simple refactoring, extracting common ancestor negotiation logic to named function --- eth/downloader/downloader.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 315354ea99..31c1cb47c3 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -90,6 +90,7 @@ var ( errCanceled = errors.New("syncing canceled (requested)") errNoSyncActive = errors.New("no sync active") errTooOld = errors.New("peer's protocol version too old") + errNoAncestorFound = errors.New("no common ancestor found") ) type Downloader struct { @@ -814,6 +815,26 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) } } + ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor) + if err == nil { + return ancestor, nil + } + // The returned error was not nil. + // If the error returned does not reflect that a common ancestor was not found, return it. + // If the error reflects that a common ancestor was not found, continue to binary search, + // where the error value will be reassigned. + if !errors.Is(err, errNoAncestorFound) { + return 0, err + } + + ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor) + if err != nil { + return 0, err + } + return ancestor, nil +} + +func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (commonAncestor uint64, err error) { from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight) p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip) @@ -894,6 +915,12 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) p.log.Debug("Found common ancestor", "number", number, "hash", hash) return number, nil } + return 0, errNoAncestorFound +} + +func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (commonAncestor uint64, err error) { + hash := common.Hash{} + // Ancestor not found, we need to binary search over our chain start, end := uint64(0), remoteHeight if floor > 0 { From 1e1865b73f6b0d2fef656d2f37e20e41d13a5ef0 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 5 Feb 2020 13:12:09 +0100 Subject: [PATCH 268/948] core: implement background trie prefetcher Squashed from the following commits: core/state: lazily init snapshot storage map core/state: fix flawed meter on storage reads core/state: make statedb/stateobjects reuse a hasher core/blockchain, core/state: implement new trie prefetcher core: make trie prefetcher deliver tries to statedb core/state: refactor trie_prefetcher, export storage tries blockchain: re-enable the next-block-prefetcher state: remove panics in trie prefetcher core/state/trie_prefetcher: address some review concerns sq --- core/blockchain.go | 27 +++- core/state/database.go | 12 +- core/state/state_object.go | 72 +++++++--- core/state/statedb.go | 45 +++++- core/state/trie_prefetcher.go | 249 ++++++++++++++++++++++++++++++++++ crypto/crypto.go | 17 ++- crypto/crypto_test.go | 7 + 7 files changed, 395 insertions(+), 34 deletions(-) create mode 100644 core/state/trie_prefetcher.go diff --git a/core/blockchain.go b/core/blockchain.go index b8f483b85e..ccb99bded5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -201,11 +201,12 @@ type BlockChain struct { running int32 // 0 if chain is running, 1 when stopped procInterrupt int32 // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - prefetcher Prefetcher // Block state prefetcher interface - processor Processor // Block transaction processor interface - vmConfig vm.Config + engine consensus.Engine + validator Validator // Block and state validator interface + triePrefetcher *state.TriePrefetcher // Trie prefetcher interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + vmConfig vm.Config shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. @@ -249,6 +250,15 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) + tp := state.NewTriePrefetcher(bc.stateCache) + + bc.wg.Add(1) + go func() { + tp.Loop() + bc.wg.Done() + }() + bc.triePrefetcher = tp + bc.processor = NewStateProcessor(chainConfig, bc, engine) var err error @@ -991,6 +1001,9 @@ func (bc *BlockChain) Stop() { bc.scope.Close() close(bc.quit) bc.StopInsert() + if bc.triePrefetcher != nil { + bc.triePrefetcher.Close() + } bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. @@ -1857,6 +1870,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) + statedb.UsePrefetcher(bc.triePrefetcher) if err != nil { return it.index, err } @@ -1891,8 +1905,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them - - triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation + triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates diff --git a/core/state/database.go b/core/state/database.go index 83f7b2a839..1a06e33409 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -129,12 +129,20 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { - return trie.NewSecure(root, db.db) + tr, err := trie.NewSecure(root, db.db) + if err != nil { + return nil, err + } + return tr, nil } // OpenStorageTrie opens the storage trie of an account. func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) { - return trie.NewSecure(root, db.db) + tr, err := trie.NewSecure(root, db.db) + if err != nil { + return nil, err + } + return tr, nil } // CopyTrie returns an independent copy of the given trie. diff --git a/core/state/state_object.go b/core/state/state_object.go index d0d3b4513e..43c5074d92 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -157,11 +157,20 @@ func (s *stateObject) touch() { func (s *stateObject) getTrie(db Database) Trie { if s.trie == nil { - var err error - s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root) - if err != nil { - s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{}) - s.setError(fmt.Errorf("can't create storage trie: %v", err)) + // Try fetching from prefetcher first + // We don't prefetch empty tries + if s.data.Root != emptyRoot && s.db.prefetcher != nil { + // When the miner is creating the pending state, there is no + // prefetcher + s.trie = s.db.prefetcher.GetTrie(s.data.Root) + } + if s.trie == nil { + var err error + s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root) + if err != nil { + s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{}) + s.setError(fmt.Errorf("can't create storage trie: %v", err)) + } } } return s.trie @@ -197,12 +206,24 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } // If no live objects are available, attempt to use snapshots var ( - enc []byte - err error + enc []byte + err error + meter *time.Duration ) + readStart := time.Now() + if metrics.EnabledExpensive { + // If the snap is 'under construction', the first lookup may fail. If that + // happens, we don't want to double-count the time elapsed. Thus this + // dance with the metering. + defer func() { + if meter != nil { + *meter += time.Since(readStart) + } + }() + } if s.db.snap != nil { if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now()) + meter = &s.db.SnapshotStorageReads } // If the object was destructed in *this* block (and potentially resurrected), // the storage has been cleared out, and we should *not* consult the previous @@ -217,8 +238,14 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } // If snapshot unavailable or reading from it failed, load from the database if s.db.snap == nil || err != nil { + if meter != nil { + // If we already spent time checking the snapshot, account for it + // and reset the readStart + *meter += time.Since(readStart) + readStart = time.Now() + } if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) + meter = &s.db.StorageReads } if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil { s.setError(err) @@ -283,8 +310,13 @@ func (s *stateObject) setState(key, value common.Hash) { // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. func (s *stateObject) finalise() { + trieChanges := make([]common.Hash, 0, len(s.dirtyStorage)) for key, value := range s.dirtyStorage { s.pendingStorage[key] = value + trieChanges = append(trieChanges, key) + } + if len(trieChanges) > 0 && s.db.prefetcher != nil && s.data.Root != emptyRoot { + s.db.prefetcher.PrefetchStorage(s.data.Root, trieChanges) } if len(s.dirtyStorage) > 0 { s.dirtyStorage = make(Storage) @@ -303,18 +335,11 @@ func (s *stateObject) updateTrie(db Database) Trie { if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) } - // Retrieve the snapshot storage map for the object + // The snapshot storage map for the object var storage map[common.Hash][]byte - if s.db.snap != nil { - // Retrieve the old storage map, if available, create a new one otherwise - storage = s.db.snapStorage[s.addrHash] - if storage == nil { - storage = make(map[common.Hash][]byte) - s.db.snapStorage[s.addrHash] = storage - } - } // Insert all the pending updates into the trie tr := s.getTrie(db) + hasher := s.db.hasher for key, value := range s.pendingStorage { // Skip noop changes, persist actual changes if value == s.originStorage[key] { @@ -331,8 +356,15 @@ func (s *stateObject) updateTrie(db Database) Trie { s.setError(tr.TryUpdate(key[:], v)) } // If state snapshotting is active, cache the data til commit - if storage != nil { - storage[crypto.Keccak256Hash(key[:])] = v // v will be nil if value is 0x00 + if s.db.snap != nil { + if storage == nil { + // Retrieve the old storage map, if available, create a new one otherwise + if storage = s.db.snapStorage[s.addrHash]; storage == nil { + storage = make(map[common.Hash][]byte) + s.db.snapStorage[s.addrHash] = storage + } + } + storage[crypto.HashData(hasher, key[:])] = v // v will be nil if value is 0x00 } } if len(s.pendingStorage) > 0 { diff --git a/core/state/statedb.go b/core/state/statedb.go index a9d1de2e06..ce50962e8b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -62,8 +62,11 @@ func (n *proofList) Delete(key []byte) error { // * Contracts // * Accounts type StateDB struct { - db Database - trie Trie + db Database + prefetcher *TriePrefetcher + originalRoot common.Hash // The pre-state root, before any changes were made + trie Trie + hasher crypto.KeccakState snaps *snapshot.Tree snap snapshot.Snapshot @@ -125,6 +128,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) sdb := &StateDB{ db: db, trie: tr, + originalRoot: root, snaps: snaps, stateObjects: make(map[common.Address]*stateObject), stateObjectsPending: make(map[common.Address]struct{}), @@ -133,6 +137,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) preimages: make(map[common.Hash][]byte), journal: newJournal(), accessList: newAccessList(), + hasher: crypto.NewKeccakState(), } if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { @@ -144,6 +149,13 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } +func (s *StateDB) UsePrefetcher(prefetcher *TriePrefetcher) { + if prefetcher != nil { + s.prefetcher = prefetcher + s.prefetcher.Resume(s.originalRoot) + } +} + // setError remembers the first non-nil error it is called with. func (s *StateDB) setError(err error) { if s.dbErr == nil { @@ -532,7 +544,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) } var acc *snapshot.Account - if acc, err = s.snap.Account(crypto.Keccak256Hash(addr.Bytes())); err == nil { + if acc, err = s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())); err == nil { if acc == nil { return nil } @@ -675,6 +687,7 @@ func (s *StateDB) Copy() *StateDB { logSize: s.logSize, preimages: make(map[common.Hash][]byte, len(s.preimages)), journal: newJournal(), + hasher: crypto.NewKeccakState(), } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -760,6 +773,7 @@ func (s *StateDB) GetRefund() uint64 { // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { + var addressesToPrefetch []common.Address for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -788,7 +802,17 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} + // At this point, also ship the address off to the precacher. The precacher + // will start loading tries, and when the change is eventually committed, + // the commit-phase will be a lot faster + if s.prefetcher != nil { + addressesToPrefetch = append(addressesToPrefetch, addr) + } + } + if s.prefetcher != nil { + s.prefetcher.PrefetchAddresses(addressesToPrefetch) } + // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() } @@ -800,6 +824,21 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) + // Now we're about to start to write changes to the trie. The trie is so + // far _untouched_. We can check with the prefetcher, if it can give us + // a trie which has the same root, but also has some content loaded into it. + // If so, use that one instead. + if s.prefetcher != nil { + s.prefetcher.Pause() + // We only want to do this _once_, if someone calls IntermediateRoot again, + // we shouldn't fetch the trie again + if s.originalRoot != (common.Hash{}) { + if trie := s.prefetcher.GetTrie(s.originalRoot); trie != nil { + s.trie = trie + } + s.originalRoot = common.Hash{} + } + } for addr := range s.stateObjectsPending { obj := s.stateObjects[addr] if obj.deleted { diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go new file mode 100644 index 0000000000..8a1aab3253 --- /dev/null +++ b/core/state/trie_prefetcher.go @@ -0,0 +1,249 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + // trieDeliveryMeter counts how many times the prefetcher was unable to supply + // the statedb with a prefilled trie. This meter should be zero -- if it's not, that + // needs to be investigated + trieDeliveryMissMeter = metrics.NewRegisteredMeter("trie/prefetch/deliverymiss", nil) + + triePrefetchFetchMeter = metrics.NewRegisteredMeter("trie/prefetch/fetch", nil) + triePrefetchSkipMeter = metrics.NewRegisteredMeter("trie/prefetch/skip", nil) + triePrefetchDropMeter = metrics.NewRegisteredMeter("trie/prefetch/drop", nil) +) + +// TriePrefetcher is an active prefetcher, which receives accounts or storage +// items on two channels, and does trie-loading of the items. +// The goal is to get as much useful content into the caches as possible +type TriePrefetcher struct { + requestCh chan (fetchRequest) // Chan to receive requests for data to fetch + cmdCh chan (*cmd) // Chan to control activity, pause/new root + quitCh chan (struct{}) + deliveryCh chan (struct{}) + db Database + + paused bool + + storageTries map[common.Hash]Trie + accountTrie Trie + accountTrieRoot common.Hash +} + +func NewTriePrefetcher(db Database) *TriePrefetcher { + return &TriePrefetcher{ + requestCh: make(chan fetchRequest, 200), + cmdCh: make(chan *cmd), + quitCh: make(chan struct{}), + deliveryCh: make(chan struct{}), + db: db, + } +} + +type cmd struct { + root common.Hash +} + +type fetchRequest struct { + slots []common.Hash + storageRoot *common.Hash + addresses []common.Address +} + +func (p *TriePrefetcher) Loop() { + var ( + accountTrieRoot common.Hash + accountTrie Trie + storageTries map[common.Hash]Trie + + err error + // Some tracking of performance + skipped int64 + fetched int64 + + paused = true + ) + // The prefetcher loop has two distinct phases: + // 1: Paused: when in this state, the accumulated tries are accessible to outside + // callers. + // 2: Active prefetching, awaiting slots and accounts to prefetch + for { + select { + case <-p.quitCh: + return + case cmd := <-p.cmdCh: + // Clear out any old requests + drain: + for { + select { + case req := <-p.requestCh: + if req.slots != nil { + skipped += int64(len(req.slots)) + } else { + skipped += int64(len(req.addresses)) + } + default: + break drain + } + } + if paused { + // Clear old data + p.storageTries = nil + p.accountTrie = nil + p.accountTrieRoot = common.Hash{} + // Resume again + storageTries = make(map[common.Hash]Trie) + accountTrieRoot = cmd.root + accountTrie, err = p.db.OpenTrie(accountTrieRoot) + if err != nil { + log.Error("Trie prefetcher failed opening trie", "root", accountTrieRoot, "err", err) + } + if accountTrieRoot == (common.Hash{}) { + log.Error("Trie prefetcher unpaused with bad root") + } + paused = false + } else { + // Update metrics at new block events + triePrefetchFetchMeter.Mark(fetched) + triePrefetchSkipMeter.Mark(skipped) + fetched, skipped = 0, 0 + // Make the tries accessible + p.accountTrie = accountTrie + p.storageTries = storageTries + p.accountTrieRoot = accountTrieRoot + if cmd.root != (common.Hash{}) { + log.Error("Trie prefetcher paused with non-empty root") + } + paused = true + } + p.deliveryCh <- struct{}{} + case req := <-p.requestCh: + if paused { + continue + } + if sRoot := req.storageRoot; sRoot != nil { + // Storage slots to fetch + var ( + storageTrie Trie + err error + ) + if storageTrie = storageTries[*sRoot]; storageTrie == nil { + if storageTrie, err = p.db.OpenTrie(*sRoot); err != nil { + log.Warn("trie prefetcher failed opening storage trie", "root", *sRoot, "err", err) + skipped += int64(len(req.slots)) + continue + } + storageTries[*sRoot] = storageTrie + } + for _, key := range req.slots { + storageTrie.TryGet(key[:]) + } + fetched += int64(len(req.slots)) + } else { // an account + for _, addr := range req.addresses { + accountTrie.TryGet(addr[:]) + } + fetched += int64(len(req.addresses)) + } + } + } +} + +// Close stops the prefetcher +func (p *TriePrefetcher) Close() { + if p.quitCh != nil { + close(p.quitCh) + p.quitCh = nil + } +} + +// Resume causes the prefetcher to clear out old data, and get ready to +// fetch data concerning the new root +func (p *TriePrefetcher) Resume(root common.Hash) { + p.paused = false + p.cmdCh <- &cmd{ + root: root, + } + // Wait for it + <-p.deliveryCh +} + +// Pause causes the prefetcher to pause prefetching, and make tries +// accessible to callers via GetTrie +func (p *TriePrefetcher) Pause() { + if p.paused { + return + } + p.paused = true + p.cmdCh <- &cmd{ + root: common.Hash{}, + } + // Wait for it + <-p.deliveryCh +} + +// PrefetchAddresses adds an address for prefetching +func (p *TriePrefetcher) PrefetchAddresses(addresses []common.Address) { + cmd := fetchRequest{ + addresses: addresses, + } + // We do an async send here, to not cause the caller to block + //p.requestCh <- cmd + select { + case p.requestCh <- cmd: + default: + triePrefetchDropMeter.Mark(int64(len(addresses))) + } +} + +// PrefetchStorage adds a storage root and a set of keys for prefetching +func (p *TriePrefetcher) PrefetchStorage(root common.Hash, slots []common.Hash) { + cmd := fetchRequest{ + storageRoot: &root, + slots: slots, + } + // We do an async send here, to not cause the caller to block + //p.requestCh <- cmd + select { + case p.requestCh <- cmd: + default: + triePrefetchDropMeter.Mark(int64(len(slots))) + } +} + +// GetTrie returns the trie matching the root hash, or nil if the prefetcher +// doesn't have it. +func (p *TriePrefetcher) GetTrie(root common.Hash) Trie { + if root == p.accountTrieRoot { + return p.accountTrie + } + if storageTrie, ok := p.storageTries[root]; ok { + // Two accounts may well have the same storage root, but we cannot allow + // them both to make updates to the same trie instance. Therefore, + // we need to either delete the trie now, or deliver a copy of the trie. + delete(p.storageTries, root) + return storageTrie + } + trieDeliveryMissMeter.Mark(1) + return nil +} diff --git a/crypto/crypto.go b/crypto/crypto.go index a4a49136a8..40969a2895 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -60,10 +60,23 @@ type KeccakState interface { Read([]byte) (int, error) } +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +// HashData hashes the provided data using the KeccakState and returns a 32 byte hash +func HashData(kh KeccakState, data []byte) (h common.Hash) { + kh.Reset() + kh.Write(data) + kh.Read(h[:]) + return h +} + // Keccak256 calculates and returns the Keccak256 hash of the input data. func Keccak256(data ...[]byte) []byte { b := make([]byte, 32) - d := sha3.NewLegacyKeccak256().(KeccakState) + d := NewKeccakState() for _, b := range data { d.Write(b) } @@ -74,7 +87,7 @@ func Keccak256(data ...[]byte) []byte { // Keccak256Hash calculates and returns the Keccak256 hash of the input data, // converting it to an internal Hash data structure. func Keccak256Hash(data ...[]byte) (h common.Hash) { - d := sha3.NewLegacyKeccak256().(KeccakState) + d := NewKeccakState() for _, b := range data { d.Write(b) } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index f71ae8232a..f9b0d3e834 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -42,6 +42,13 @@ func TestKeccak256Hash(t *testing.T) { checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp) } +func TestKeccak256Hasher(t *testing.T) { + msg := []byte("abc") + exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45") + hasher := NewKeccakState() + checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := HashData(hasher, in); return h[:] }, msg, exp) +} + func TestToECDSAErrors(t *testing.T) { if _, err := HexToECDSA("0000000000000000000000000000000000000000000000000000000000000000"); err == nil { t.Fatal("HexToECDSA should've returned error") From 42f9f1f0738bde1126eaa6f6bed9c1ae03e304a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 8 Jan 2021 15:01:49 +0200 Subject: [PATCH 269/948] core/state: convert prefetcher to concurrent per-trie loader --- accounts/abi/bind/backends/simulated.go | 3 +- core/blockchain.go | 30 +- core/state/state_object.go | 22 +- core/state/state_test.go | 2 +- core/state/statedb.go | 127 ++++--- core/state/statedb_test.go | 6 +- core/state/trie_prefetcher.go | 453 ++++++++++++++---------- eth/api_tracer.go | 6 +- miner/worker.go | 16 +- 9 files changed, 384 insertions(+), 281 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 6e87e037f0..8be364d08f 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -125,10 +125,9 @@ func (b *SimulatedBackend) Rollback() { func (b *SimulatedBackend) rollback() { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) - stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil) } // stateByBlockNumber retrieves a state by a given blocknumber. diff --git a/core/blockchain.go b/core/blockchain.go index ccb99bded5..d6668cdcd2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -201,12 +201,11 @@ type BlockChain struct { running int32 // 0 if chain is running, 1 when stopped procInterrupt int32 // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - triePrefetcher *state.TriePrefetcher // Trie prefetcher interface - prefetcher Prefetcher - processor Processor // Block transaction processor interface - vmConfig vm.Config + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + vmConfig vm.Config shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. @@ -250,15 +249,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) - tp := state.NewTriePrefetcher(bc.stateCache) - - bc.wg.Add(1) - go func() { - tp.Loop() - bc.wg.Done() - }() - bc.triePrefetcher = tp - bc.processor = NewStateProcessor(chainConfig, bc, engine) var err error @@ -1001,9 +991,6 @@ func (bc *BlockChain) Stop() { bc.scope.Close() close(bc.quit) bc.StopInsert() - if bc.triePrefetcher != nil { - bc.triePrefetcher.Close() - } bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. @@ -1870,16 +1857,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) - statedb.UsePrefetcher(bc.triePrefetcher) if err != nil { return it.index, err } + // Enable prefetching to pull in trie node paths while processing transactions + statedb.StartPrefetcher("chain") + defer statedb.StopPrefetcher() // stopped on write anyway, defer meant to catch early error returns + // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. var followupInterrupt uint32 if !bc.cacheConfig.TrieCleanNoPrefetch { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) + go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) { bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) @@ -1933,7 +1924,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er if err != nil { return it.index, err } - // Update the metrics touched during block commit accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them diff --git a/core/state/state_object.go b/core/state/state_object.go index 43c5074d92..f93f47d5f5 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -162,7 +162,7 @@ func (s *stateObject) getTrie(db Database) Trie { if s.data.Root != emptyRoot && s.db.prefetcher != nil { // When the miner is creating the pending state, there is no // prefetcher - s.trie = s.db.prefetcher.GetTrie(s.data.Root) + s.trie = s.db.prefetcher.trie(s.data.Root) } if s.trie == nil { var err error @@ -309,14 +309,16 @@ func (s *stateObject) setState(key, value common.Hash) { // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. -func (s *stateObject) finalise() { - trieChanges := make([]common.Hash, 0, len(s.dirtyStorage)) +func (s *stateObject) finalise(prefetch bool) { + slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) for key, value := range s.dirtyStorage { s.pendingStorage[key] = value - trieChanges = append(trieChanges, key) + if value != s.originStorage[key] { + slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure + } } - if len(trieChanges) > 0 && s.db.prefetcher != nil && s.data.Root != emptyRoot { - s.db.prefetcher.PrefetchStorage(s.data.Root, trieChanges) + if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != emptyRoot { + s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch) } if len(s.dirtyStorage) > 0 { s.dirtyStorage = make(Storage) @@ -327,7 +329,7 @@ func (s *stateObject) finalise() { // It will return nil if the trie has not been loaded and no changes have been made func (s *stateObject) updateTrie(db Database) Trie { // Make sure all dirty slots are finalized into the pending storage area - s.finalise() + s.finalise(false) // Don't prefetch any more, pull directly if need be if len(s.pendingStorage) == 0 { return s.trie } @@ -340,6 +342,8 @@ func (s *stateObject) updateTrie(db Database) Trie { // Insert all the pending updates into the trie tr := s.getTrie(db) hasher := s.db.hasher + + usedStorage := make([][]byte, 0, len(s.pendingStorage)) for key, value := range s.pendingStorage { // Skip noop changes, persist actual changes if value == s.originStorage[key] { @@ -366,6 +370,10 @@ func (s *stateObject) updateTrie(db Database) Trie { } storage[crypto.HashData(hasher, key[:])] = v // v will be nil if value is 0x00 } + usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure + } + if s.db.prefetcher != nil { + s.db.prefetcher.used(s.data.Root, usedStorage) } if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) diff --git a/core/state/state_test.go b/core/state/state_test.go index 526d7f8177..22e93d7a95 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -170,7 +170,7 @@ func TestSnapshot2(t *testing.T) { state.setStateObject(so0) root, _ := state.Commit(false) - state.Reset(root) + state, _ = New(root, state.db, state.snaps) // and one with deleted == true so1 := state.getStateObject(stateobjaddr1) diff --git a/core/state/statedb.go b/core/state/statedb.go index ce50962e8b..49f457a99f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -63,7 +63,7 @@ func (n *proofList) Delete(key []byte) error { // * Accounts type StateDB struct { db Database - prefetcher *TriePrefetcher + prefetcher *triePrefetcher originalRoot common.Hash // The pre-state root, before any changes were made trie Trie hasher crypto.KeccakState @@ -149,10 +149,25 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return sdb, nil } -func (s *StateDB) UsePrefetcher(prefetcher *TriePrefetcher) { - if prefetcher != nil { - s.prefetcher = prefetcher - s.prefetcher.Resume(s.originalRoot) +// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the +// state trie concurrently while the state is mutated so that when we reach the +// commit phase, most of the needed data is already hot. +func (s *StateDB) StartPrefetcher(namespace string) { + if s.prefetcher != nil { + s.prefetcher.close() + s.prefetcher = nil + } + if s.snap != nil { + s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace) + } +} + +// StopPrefetcher terminates a running prefetcher and reports any leftover stats +// from the gathered metrics. +func (s *StateDB) StopPrefetcher() { + if s.prefetcher != nil { + s.prefetcher.close() + s.prefetcher = nil } } @@ -167,37 +182,6 @@ func (s *StateDB) Error() error { return s.dbErr } -// Reset clears out all ephemeral state objects from the state db, but keeps -// the underlying state trie to avoid reloading data for the next operations. -func (s *StateDB) Reset(root common.Hash) error { - tr, err := s.db.OpenTrie(root) - if err != nil { - return err - } - s.trie = tr - s.stateObjects = make(map[common.Address]*stateObject) - s.stateObjectsPending = make(map[common.Address]struct{}) - s.stateObjectsDirty = make(map[common.Address]struct{}) - s.thash = common.Hash{} - s.bhash = common.Hash{} - s.txIndex = 0 - s.logs = make(map[common.Hash][]*types.Log) - s.logSize = 0 - s.preimages = make(map[common.Hash][]byte) - s.clearJournalAndRefund() - - if s.snaps != nil { - s.snapAccounts, s.snapDestructs, s.snapStorage = nil, nil, nil - if s.snap = s.snaps.Snapshot(root); s.snap != nil { - s.snapDestructs = make(map[common.Hash]struct{}) - s.snapAccounts = make(map[common.Hash][]byte) - s.snapStorage = make(map[common.Hash]map[common.Hash][]byte) - } - } - s.accessList = newAccessList() - return nil -} - func (s *StateDB) AddLog(log *types.Log) { s.journal.append(addLogChange{txhash: s.thash}) @@ -737,6 +721,13 @@ func (s *StateDB) Copy() *StateDB { // However, it doesn't cost us much to copy an empty list, so we do it anyway // to not blow up if we ever decide copy it in the middle of a transaction state.accessList = s.accessList.Copy() + + // If there's a prefetcher running, make an inactive copy of it that can + // only access data but does not actively preload (since the user will not + // know that they need to explicitly terminate an active copy). + if s.prefetcher != nil { + state.prefetcher = s.prefetcher.copy() + } return state } @@ -773,7 +764,7 @@ func (s *StateDB) GetRefund() uint64 { // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { - var addressesToPrefetch []common.Address + addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -798,21 +789,19 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect) } } else { - obj.finalise() + obj.finalise(true) // Prefetch slots in the background } s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} + // At this point, also ship the address off to the precacher. The precacher // will start loading tries, and when the change is eventually committed, // the commit-phase will be a lot faster - if s.prefetcher != nil { - addressesToPrefetch = append(addressesToPrefetch, addr) - } + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure } - if s.prefetcher != nil { - s.prefetcher.PrefetchAddresses(addressesToPrefetch) + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch) } - // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() } @@ -824,29 +813,49 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) - // Now we're about to start to write changes to the trie. The trie is so - // far _untouched_. We can check with the prefetcher, if it can give us - // a trie which has the same root, but also has some content loaded into it. - // If so, use that one instead. + // If there was a trie prefetcher operating, it gets aborted and irrevocably + // modified after we start retrieving tries. Remove it from the statedb after + // this round of use. + // + // This is weird pre-byzantium since the first tx runs with a prefetcher and + // the remainder without, but pre-byzantium even the initial prefetcher is + // useless, so no sleep lost. + prefetcher := s.prefetcher if s.prefetcher != nil { - s.prefetcher.Pause() - // We only want to do this _once_, if someone calls IntermediateRoot again, - // we shouldn't fetch the trie again - if s.originalRoot != (common.Hash{}) { - if trie := s.prefetcher.GetTrie(s.originalRoot); trie != nil { - s.trie = trie - } - s.originalRoot = common.Hash{} + defer func() { + s.prefetcher.close() + s.prefetcher = nil + }() + } + // Although naively it makes sense to retrieve the account trie and then do + // the contract storage and account updates sequentially, that short circuits + // the account prefetcher. Instead, let's process all the storage updates + // first, giving the account prefeches just a few more milliseconds of time + // to pull useful data from disk. + for addr := range s.stateObjectsPending { + if obj := s.stateObjects[addr]; !obj.deleted { + obj.updateRoot(s.db) + } + } + // Now we're about to start to write changes to the trie. The trie is so far + // _untouched_. We can check with the prefetcher, if it can give us a trie + // which has the same root, but also has some content loaded into it. + if prefetcher != nil { + if trie := prefetcher.trie(s.originalRoot); trie != nil { + s.trie = trie } } + usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) for addr := range s.stateObjectsPending { - obj := s.stateObjects[addr] - if obj.deleted { + if obj := s.stateObjects[addr]; obj.deleted { s.deleteStateObject(obj) } else { - obj.updateRoot(s.db) s.updateStateObject(obj) } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure + } + if prefetcher != nil { + prefetcher.used(s.originalRoot, usedAddrs) } if len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 70d01ff3dd..220e28525c 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -474,7 +474,7 @@ func TestTouchDelete(t *testing.T) { s := newStateTest() s.state.GetOrNewStateObject(common.Address{}) root, _ := s.state.Commit(false) - s.state.Reset(root) + s.state, _ = New(root, s.state.db, s.state.snaps) snapshot := s.state.Snapshot() s.state.AddBalance(common.Address{}, new(big.Int)) @@ -676,7 +676,7 @@ func TestDeleteCreateRevert(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) root, _ := state.Commit(false) - state.Reset(root) + state, _ = New(root, state.db, state.snaps) // Simulate self-destructing in one transaction, then create-reverting in another state.Suicide(addr) @@ -688,7 +688,7 @@ func TestDeleteCreateRevert(t *testing.T) { // Commit the entire state and make sure we don't crash and have the correct state root, _ = state.Commit(true) - state.Reset(root) + state, _ = New(root, state.db, state.snaps) if state.getStateObject(addr) != nil { t.Fatalf("self-destructed contract came alive") diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 8a1aab3253..ac5e95c5c2 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -17,233 +17,318 @@ package state import ( + "sync" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" ) var ( - // trieDeliveryMeter counts how many times the prefetcher was unable to supply - // the statedb with a prefilled trie. This meter should be zero -- if it's not, that - // needs to be investigated - trieDeliveryMissMeter = metrics.NewRegisteredMeter("trie/prefetch/deliverymiss", nil) - - triePrefetchFetchMeter = metrics.NewRegisteredMeter("trie/prefetch/fetch", nil) - triePrefetchSkipMeter = metrics.NewRegisteredMeter("trie/prefetch/skip", nil) - triePrefetchDropMeter = metrics.NewRegisteredMeter("trie/prefetch/drop", nil) + // triePrefetchMetricsPrefix is the prefix under which to publis the metrics. + triePrefetchMetricsPrefix = "trie/prefetch/" ) -// TriePrefetcher is an active prefetcher, which receives accounts or storage -// items on two channels, and does trie-loading of the items. -// The goal is to get as much useful content into the caches as possible -type TriePrefetcher struct { - requestCh chan (fetchRequest) // Chan to receive requests for data to fetch - cmdCh chan (*cmd) // Chan to control activity, pause/new root - quitCh chan (struct{}) - deliveryCh chan (struct{}) - db Database - - paused bool - - storageTries map[common.Hash]Trie - accountTrie Trie - accountTrieRoot common.Hash +// triePrefetcher is an active prefetcher, which receives accounts or storage +// items and does trie-loading of them. The goal is to get as much useful content +// into the caches as possible. +// +// Note, the prefetcher's API is not thread safe. +type triePrefetcher struct { + db Database // Database to fetch trie nodes through + root common.Hash // Root hash of theaccount trie for metrics + fetches map[common.Hash]Trie // Partially or fully fetcher tries + fetchers map[common.Hash]*subfetcher // Subfetchers for each trie + + deliveryMissMeter metrics.Meter + accountLoadMeter metrics.Meter + accountDupMeter metrics.Meter + accountSkipMeter metrics.Meter + accountWasteMeter metrics.Meter + storageLoadMeter metrics.Meter + storageDupMeter metrics.Meter + storageSkipMeter metrics.Meter + storageWasteMeter metrics.Meter } -func NewTriePrefetcher(db Database) *TriePrefetcher { - return &TriePrefetcher{ - requestCh: make(chan fetchRequest, 200), - cmdCh: make(chan *cmd), - quitCh: make(chan struct{}), - deliveryCh: make(chan struct{}), - db: db, +// newTriePrefetcher +func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher { + prefix := triePrefetchMetricsPrefix + namespace + p := &triePrefetcher{ + db: db, + root: root, + fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map + + deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), + accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), + accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil), + accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil), + accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), + storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), + storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), + storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil), + storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), } + return p } -type cmd struct { - root common.Hash -} +// close iterates over all the subfetchers, aborts any that were left spinning +// and reports the stats to the metrics subsystem. +func (p *triePrefetcher) close() { + for _, fetcher := range p.fetchers { + fetcher.abort() // safe to do multiple times -type fetchRequest struct { - slots []common.Hash - storageRoot *common.Hash - addresses []common.Address -} + if metrics.Enabled { + if fetcher.root == p.root { + p.accountLoadMeter.Mark(int64(len(fetcher.seen))) + p.accountDupMeter.Mark(int64(fetcher.dups)) + p.accountSkipMeter.Mark(int64(len(fetcher.tasks))) -func (p *TriePrefetcher) Loop() { - var ( - accountTrieRoot common.Hash - accountTrie Trie - storageTries map[common.Hash]Trie - - err error - // Some tracking of performance - skipped int64 - fetched int64 - - paused = true - ) - // The prefetcher loop has two distinct phases: - // 1: Paused: when in this state, the accumulated tries are accessible to outside - // callers. - // 2: Active prefetching, awaiting slots and accounts to prefetch - for { - select { - case <-p.quitCh: - return - case cmd := <-p.cmdCh: - // Clear out any old requests - drain: - for { - select { - case req := <-p.requestCh: - if req.slots != nil { - skipped += int64(len(req.slots)) - } else { - skipped += int64(len(req.addresses)) - } - default: - break drain + for _, key := range fetcher.used { + delete(fetcher.seen, string(key)) } - } - if paused { - // Clear old data - p.storageTries = nil - p.accountTrie = nil - p.accountTrieRoot = common.Hash{} - // Resume again - storageTries = make(map[common.Hash]Trie) - accountTrieRoot = cmd.root - accountTrie, err = p.db.OpenTrie(accountTrieRoot) - if err != nil { - log.Error("Trie prefetcher failed opening trie", "root", accountTrieRoot, "err", err) - } - if accountTrieRoot == (common.Hash{}) { - log.Error("Trie prefetcher unpaused with bad root") - } - paused = false + p.accountWasteMeter.Mark(int64(len(fetcher.seen))) } else { - // Update metrics at new block events - triePrefetchFetchMeter.Mark(fetched) - triePrefetchSkipMeter.Mark(skipped) - fetched, skipped = 0, 0 - // Make the tries accessible - p.accountTrie = accountTrie - p.storageTries = storageTries - p.accountTrieRoot = accountTrieRoot - if cmd.root != (common.Hash{}) { - log.Error("Trie prefetcher paused with non-empty root") - } - paused = true - } - p.deliveryCh <- struct{}{} - case req := <-p.requestCh: - if paused { - continue - } - if sRoot := req.storageRoot; sRoot != nil { - // Storage slots to fetch - var ( - storageTrie Trie - err error - ) - if storageTrie = storageTries[*sRoot]; storageTrie == nil { - if storageTrie, err = p.db.OpenTrie(*sRoot); err != nil { - log.Warn("trie prefetcher failed opening storage trie", "root", *sRoot, "err", err) - skipped += int64(len(req.slots)) - continue - } - storageTries[*sRoot] = storageTrie - } - for _, key := range req.slots { - storageTrie.TryGet(key[:]) - } - fetched += int64(len(req.slots)) - } else { // an account - for _, addr := range req.addresses { - accountTrie.TryGet(addr[:]) + p.storageLoadMeter.Mark(int64(len(fetcher.seen))) + p.storageDupMeter.Mark(int64(fetcher.dups)) + p.storageSkipMeter.Mark(int64(len(fetcher.tasks))) + + for _, key := range fetcher.used { + delete(fetcher.seen, string(key)) } - fetched += int64(len(req.addresses)) + p.storageWasteMeter.Mark(int64(len(fetcher.seen))) } } } + // Clear out all fetchers (will crash on a second call, deliberate) + p.fetchers = nil } -// Close stops the prefetcher -func (p *TriePrefetcher) Close() { - if p.quitCh != nil { - close(p.quitCh) - p.quitCh = nil +// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data +// already loaded will be copied over, but no goroutines will be started. This +// is mostly used in the miner which creates a copy of it's actively mutated +// state to be sealed while it may further mutate the state. +func (p *triePrefetcher) copy() *triePrefetcher { + copy := &triePrefetcher{ + db: p.db, + root: p.root, + fetches: make(map[common.Hash]Trie), // Active prefetchers use the fetches map + + deliveryMissMeter: p.deliveryMissMeter, + accountLoadMeter: p.accountLoadMeter, + accountDupMeter: p.accountDupMeter, + accountSkipMeter: p.accountSkipMeter, + accountWasteMeter: p.accountWasteMeter, + storageLoadMeter: p.storageLoadMeter, + storageDupMeter: p.storageDupMeter, + storageSkipMeter: p.storageSkipMeter, + storageWasteMeter: p.storageWasteMeter, + } + // If the prefetcher is already a copy, duplicate the data + if p.fetches != nil { + for root, fetch := range p.fetches { + copy.fetches[root] = p.db.CopyTrie(fetch) + } + return copy + } + // Otherwise we're copying an active fetcher, retrieve the current states + for root, fetcher := range p.fetchers { + copy.fetches[root] = fetcher.peek() } + return copy } -// Resume causes the prefetcher to clear out old data, and get ready to -// fetch data concerning the new root -func (p *TriePrefetcher) Resume(root common.Hash) { - p.paused = false - p.cmdCh <- &cmd{ - root: root, +// prefetch schedules a batch of trie items to prefetch. +func (p *triePrefetcher) prefetch(root common.Hash, keys [][]byte) { + // If the prefetcher is an inactive one, bail out + if p.fetches != nil { + return + } + // Active fetcher, schedule the retrievals + fetcher := p.fetchers[root] + if fetcher == nil { + fetcher = newSubfetcher(p.db, root) + p.fetchers[root] = fetcher } - // Wait for it - <-p.deliveryCh + fetcher.schedule(keys) } -// Pause causes the prefetcher to pause prefetching, and make tries -// accessible to callers via GetTrie -func (p *TriePrefetcher) Pause() { - if p.paused { - return +// trie returns the trie matching the root hash, or nil if the prefetcher doesn't +// have it. +func (p *triePrefetcher) trie(root common.Hash) Trie { + // If the prefetcher is inactive, return from existing deep copies + if p.fetches != nil { + trie := p.fetches[root] + if trie == nil { + p.deliveryMissMeter.Mark(1) + return nil + } + return p.db.CopyTrie(trie) } - p.paused = true - p.cmdCh <- &cmd{ - root: common.Hash{}, + // Otherwise the prefetcher is active, bail if no trie was prefetched for this root + fetcher := p.fetchers[root] + if fetcher == nil { + p.deliveryMissMeter.Mark(1) + return nil } - // Wait for it - <-p.deliveryCh + // Interrupt the prefetcher if it's by any chance still running and return + // a copy of any pre-loaded trie. + fetcher.abort() // safe to do multiple times + + trie := fetcher.peek() + if trie == nil { + p.deliveryMissMeter.Mark(1) + return nil + } + return trie } -// PrefetchAddresses adds an address for prefetching -func (p *TriePrefetcher) PrefetchAddresses(addresses []common.Address) { - cmd := fetchRequest{ - addresses: addresses, +// used marks a batch of state items used to allow creating statistics as to +// how useful or wasteful the prefetcher is. +func (p *triePrefetcher) used(root common.Hash, used [][]byte) { + if fetcher := p.fetchers[root]; fetcher != nil { + fetcher.used = used } - // We do an async send here, to not cause the caller to block - //p.requestCh <- cmd +} + +// subfetcher is a trie fetcher goroutine responsible for pulling entries for a +// single trie. It is spawned when a new root is encountered and lives until the +// main prefetcher is paused and either all requested items are processed or if +// the trie being worked on is retrieved from the prefetcher. +type subfetcher struct { + db Database // Database to load trie nodes through + root common.Hash // Root hash of the trie to prefetch + trie Trie // Trie being populated with nodes + + tasks [][]byte // Items queued up for retrieval + lock sync.Mutex // Lock protecting the task queue + + wake chan struct{} // Wake channel if a new task is scheduled + stop chan struct{} // Channel to interrupt processing + term chan struct{} // Channel to signal iterruption + copy chan chan Trie // Channel to request a copy of the current trie + + seen map[string]struct{} // Tracks the entries already loaded + dups int // Number of duplicate preload tasks + used [][]byte // Tracks the entries used in the end +} + +// newSubfetcher creates a goroutine to prefetch state items belonging to a +// particular root hash. +func newSubfetcher(db Database, root common.Hash) *subfetcher { + sf := &subfetcher{ + db: db, + root: root, + wake: make(chan struct{}, 1), + stop: make(chan struct{}), + term: make(chan struct{}), + copy: make(chan chan Trie), + seen: make(map[string]struct{}), + } + go sf.loop() + return sf +} + +// schedule adds a batch of trie keys to the queue to prefetch. +func (sf *subfetcher) schedule(keys [][]byte) { + // Append the tasks to the current queue + sf.lock.Lock() + sf.tasks = append(sf.tasks, keys...) + sf.lock.Unlock() + + // Notify the prefetcher, it's fine if it's already terminated select { - case p.requestCh <- cmd: + case sf.wake <- struct{}{}: default: - triePrefetchDropMeter.Mark(int64(len(addresses))) } } -// PrefetchStorage adds a storage root and a set of keys for prefetching -func (p *TriePrefetcher) PrefetchStorage(root common.Hash, slots []common.Hash) { - cmd := fetchRequest{ - storageRoot: &root, - slots: slots, +// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it +// is currently. +func (sf *subfetcher) peek() Trie { + ch := make(chan Trie) + select { + case sf.copy <- ch: + // Subfetcher still alive, return copy from it + return <-ch + + case <-sf.term: + // Subfetcher already terminated, return a copy directly + if sf.trie == nil { + return nil + } + return sf.db.CopyTrie(sf.trie) } - // We do an async send here, to not cause the caller to block - //p.requestCh <- cmd +} + +// abort interrupts the subfetcher immediately. It is safe to call abort multiple +// times but it is not thread safe. +func (sf *subfetcher) abort() { select { - case p.requestCh <- cmd: + case <-sf.stop: default: - triePrefetchDropMeter.Mark(int64(len(slots))) + close(sf.stop) } + <-sf.term } -// GetTrie returns the trie matching the root hash, or nil if the prefetcher -// doesn't have it. -func (p *TriePrefetcher) GetTrie(root common.Hash) Trie { - if root == p.accountTrieRoot { - return p.accountTrie - } - if storageTrie, ok := p.storageTries[root]; ok { - // Two accounts may well have the same storage root, but we cannot allow - // them both to make updates to the same trie instance. Therefore, - // we need to either delete the trie now, or deliver a copy of the trie. - delete(p.storageTries, root) - return storageTrie - } - trieDeliveryMissMeter.Mark(1) - return nil +// loop waits for new tasks to be scheduled and keeps loading them until it runs +// out of tasks or its underlying trie is retrieved for committing. +func (sf *subfetcher) loop() { + // No matter how the loop stops, signal anyone waiting that it's terminated + defer close(sf.term) + + // Start by opening the trie and stop processing if it fails + trie, err := sf.db.OpenTrie(sf.root) + if err != nil { + log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) + return + } + sf.trie = trie + + // Trie opened successfully, keep prefetching items + for { + select { + case <-sf.wake: + // Subfetcher was woken up, retrieve any tasks to avoid spinning the lock + sf.lock.Lock() + tasks := sf.tasks + sf.tasks = nil + sf.lock.Unlock() + + // Prefetch any tasks until the loop is interrupted + for i, task := range tasks { + select { + case <-sf.stop: + // If termination is requested, add any leftover back and return + sf.lock.Lock() + sf.tasks = append(sf.tasks, tasks[i:]...) + sf.lock.Unlock() + return + + case ch := <-sf.copy: + // Somebody wants a copy of the current trie, grant them + ch <- sf.db.CopyTrie(sf.trie) + + default: + // No termination request yet, prefetch the next entry + taskid := string(task) + if _, ok := sf.seen[taskid]; ok { + sf.dups++ + } else { + sf.trie.TryGet(task) + sf.seen[taskid] = struct{}{} + } + } + } + + case ch := <-sf.copy: + // Somebody wants a copy of the current trie, grant them + ch <- sf.db.CopyTrie(sf.trie) + + case <-sf.stop: + // Termination is requested, abort and leave remaining tasks + return + } + } } diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 8e71945ee2..5dffb2a464 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -299,7 +299,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl failed = err break } - if err := statedb.Reset(root); err != nil { + statedb, err = state.New(root, database, nil) + if err != nil { failed = err break } @@ -699,7 +700,8 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* if err != nil { return nil, err } - if err := statedb.Reset(root); err != nil { + statedb, err = state.New(root, database, nil) + if err != nil { return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) } database.TrieDB().Reference(root, common.Hash{}) diff --git a/miner/worker.go b/miner/worker.go index 2c5032c656..82d08d4c7e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -303,6 +303,9 @@ func (w *worker) isRunning() bool { // close terminates all background threads maintained by the worker. // Note the worker does not support being closed multiple times. func (w *worker) close() { + if w.current != nil && w.current.state != nil { + w.current.state.StopPrefetcher() + } atomic.StoreInt32(&w.running, 0) close(w.exitCh) } @@ -642,10 +645,14 @@ func (w *worker) resultLoop() { // makeCurrent creates a new environment for the current cycle. func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { + // Retrieve the parent state to execute on top and start a prefetcher for + // the miner to speed block sealing up a bit state, err := w.chain.StateAt(parent.Root()) if err != nil { return err } + state.StartPrefetcher("miner") + env := &environment{ signer: types.NewEIP155Signer(w.chainConfig.ChainID), state: state, @@ -654,7 +661,6 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { uncles: mapset.NewSet(), header: header, } - // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { for _, uncle := range ancestor.Uncles() { @@ -663,9 +669,14 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { env.family.Add(ancestor.Hash()) env.ancestors.Add(ancestor.Hash()) } - // Keep track of transactions which return errors so they can be removed env.tcount = 0 + + // Swap out the old work with the new one, terminating any leftover prefetcher + // processes in the mean time and starting a new one. + if w.current != nil && w.current.state != nil { + w.current.state.StopPrefetcher() + } w.current = env return nil } @@ -719,7 +730,6 @@ func (w *worker) updateSnapshot() { w.current.receipts, new(trie.Trie), ) - w.snapshotState = w.current.state.Copy() } From c4307a9339fd4e66598fd5c378e8f6f97ad878e2 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Thu, 21 Jan 2021 12:17:10 +0100 Subject: [PATCH 270/948] eth/filters: fix potential deadlock in filter timeout loop (#22178) This fixes #22131 and adds a test reproducing the issue. --- eth/backend.go | 2 +- eth/filters/api.go | 33 +++++++----- eth/filters/filter_system_test.go | 86 ++++++++++++++++++++++++++++--- les/client.go | 2 +- 4 files changed, 101 insertions(+), 22 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index c1732d3ceb..a6390facb3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -335,7 +335,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: filters.NewPublicFilterAPI(s.APIBackend, false), + Service: filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute), Public: true, }, { Namespace: "admin", diff --git a/eth/filters/api.go b/eth/filters/api.go index b6f974c3ba..4b36a5379e 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -34,10 +34,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -var ( - deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline -) - // filter is a helper struct that holds meta information over the filter type // and associated subscription in the event system. type filter struct { @@ -59,25 +55,28 @@ type PublicFilterAPI struct { events *EventSystem filtersMu sync.Mutex filters map[rpc.ID]*filter + timeout time.Duration } // NewPublicFilterAPI returns a new PublicFilterAPI instance. -func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI { +func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) *PublicFilterAPI { api := &PublicFilterAPI{ backend: backend, chainDb: backend.ChainDb(), events: NewEventSystem(backend, lightMode), filters: make(map[rpc.ID]*filter), + timeout: timeout, } - go api.timeoutLoop() + go api.timeoutLoop(timeout) return api } // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. // Tt is started when the api is created. -func (api *PublicFilterAPI) timeoutLoop() { - ticker := time.NewTicker(5 * time.Minute) +func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) { + var toUninstall []*Subscription + ticker := time.NewTicker(timeout) defer ticker.Stop() for { <-ticker.C @@ -85,13 +84,21 @@ func (api *PublicFilterAPI) timeoutLoop() { for id, f := range api.filters { select { case <-f.deadline.C: - f.s.Unsubscribe() + toUninstall = append(toUninstall, f.s) delete(api.filters, id) default: continue } } api.filtersMu.Unlock() + + // Unsubscribes are processed outside the lock to avoid the following scenario: + // event loop attempts broadcasting events to still active filters while + // Unsubscribe is waiting for it to process the uninstall request. + for _, s := range toUninstall { + s.Unsubscribe() + } + toUninstall = nil } } @@ -109,7 +116,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { ) api.filtersMu.Lock() - api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} + api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub} api.filtersMu.Unlock() go func() { @@ -179,7 +186,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { ) api.filtersMu.Lock() - api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} + api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub} api.filtersMu.Unlock() go func() { @@ -296,7 +303,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { } api.filtersMu.Lock() - api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} + api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub} api.filtersMu.Unlock() go func() { @@ -421,7 +428,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { // receive timer value and reset timer <-f.deadline.C } - f.deadline.Reset(deadline) + f.deadline.Reset(api.timeout) switch f.typ { case PendingTransactionsSubscription, BlocksSubscription: diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index b534c1d47f..52150366c1 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -22,6 +22,7 @@ import ( "math/big" "math/rand" "reflect" + "runtime" "testing" "time" @@ -38,6 +39,10 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +var ( + deadline = 5 * time.Minute +) + type testBackend struct { mux *event.TypeMux db ethdb.Database @@ -163,7 +168,7 @@ func TestBlockSubscription(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) genesis = new(core.Genesis).MustCommit(db) chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {}) chainEvents = []core.ChainEvent{} @@ -215,7 +220,7 @@ func TestPendingTxFilter(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) transactions = []*types.Transaction{ types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil), @@ -270,7 +275,7 @@ func TestLogFilterCreation(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) testCases = []struct { crit FilterCriteria @@ -314,7 +319,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) ) // different situations where log filter creation should fail. @@ -336,7 +341,7 @@ func TestInvalidGetLogsRequest(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") ) @@ -361,7 +366,7 @@ func TestLogFilter(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") @@ -475,7 +480,7 @@ func TestPendingLogsSubscription(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() backend = &testBackend{db: db} - api = NewPublicFilterAPI(backend, false) + api = NewPublicFilterAPI(backend, false, deadline) firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111") secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222") @@ -601,6 +606,73 @@ func TestPendingLogsSubscription(t *testing.T) { } } +// TestPendingTxFilterDeadlock tests if the event loop hangs when pending +// txes arrive at the same time that one of multiple filters is timing out. +// Please refer to #22131 for more details. +func TestPendingTxFilterDeadlock(t *testing.T) { + t.Parallel() + timeout := 100 * time.Millisecond + + var ( + db = rawdb.NewMemoryDatabase() + backend = &testBackend{db: db} + api = NewPublicFilterAPI(backend, false, timeout) + done = make(chan struct{}) + ) + + go func() { + // Bombard feed with txes until signal was received to stop + i := uint64(0) + for { + select { + case <-done: + return + default: + } + + tx := types.NewTransaction(i, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil) + backend.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}}) + i++ + } + }() + + // Create a bunch of filters that will + // timeout either in 100ms or 200ms + fids := make([]rpc.ID, 20) + for i := 0; i < len(fids); i++ { + fid := api.NewPendingTransactionFilter() + fids[i] = fid + // Wait for at least one tx to arrive in filter + for { + hashes, err := api.GetFilterChanges(fid) + if err != nil { + t.Fatalf("Filter should exist: %v\n", err) + } + if len(hashes.([]common.Hash)) > 0 { + break + } + runtime.Gosched() + } + } + + // Wait until filters have timed out + time.Sleep(3 * timeout) + + // If tx loop doesn't consume `done` after a second + // it's hanging. + select { + case done <- struct{}{}: + // Check that all filters have been uninstalled + for _, fid := range fids { + if _, err := api.GetFilterChanges(fid); err == nil { + t.Errorf("Filter %s should have been uninstalled\n", fid) + } + } + case <-time.After(1 * time.Second): + t.Error("Tx sending loop hangs") + } +} + func flattenLogs(pl [][]*types.Log) []*types.Log { var logs []*types.Log for _, l := range pl { diff --git a/les/client.go b/les/client.go index 47997a098b..5ee50a7c3a 100644 --- a/les/client.go +++ b/les/client.go @@ -252,7 +252,7 @@ func (s *LightEthereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: filters.NewPublicFilterAPI(s.ApiBackend, true), + Service: filters.NewPublicFilterAPI(s.ApiBackend, true, 5*time.Minute), Public: true, }, { Namespace: "net", From 231040c633f24b2b0d56aaeb704a0738ba4adb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Zimnoch?= Date: Thu, 21 Jan 2021 13:47:38 +0100 Subject: [PATCH 271/948] event: add ResubscribeErr (#22191) This adds a way to get the error of the failing subscription for logging/debugging purposes. Co-authored-by: Felix Lange --- event/subscription.go | 32 ++++++++++++++++++++++++++++---- event/subscription_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/event/subscription.go b/event/subscription.go index c80d171f3a..6c62874719 100644 --- a/event/subscription.go +++ b/event/subscription.go @@ -95,6 +95,26 @@ func (s *funcSub) Err() <-chan error { // Resubscribe applies backoff between calls to fn. The time between calls is adapted // based on the error rate, but will never exceed backoffMax. func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription { + return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) { + return fn(ctx) + }) +} + +// A ResubscribeFunc attempts to establish a subscription. +type ResubscribeFunc func(context.Context) (Subscription, error) + +// ResubscribeErr calls fn repeatedly to keep a subscription established. When the +// subscription is established, ResubscribeErr waits for it to fail and calls fn again. This +// process repeats until Unsubscribe is called or the active subscription ends +// successfully. +// +// The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr, +// the error of the failing subscription is available to the callback for logging +// purposes. +// +// ResubscribeErr applies backoff between calls to fn. The time between calls is adapted +// based on the error rate, but will never exceed backoffMax. +func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription { s := &resubscribeSub{ waitTime: backoffMax / 10, backoffMax: backoffMax, @@ -106,15 +126,18 @@ func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription { return s } -// A ResubscribeFunc attempts to establish a subscription. -type ResubscribeFunc func(context.Context) (Subscription, error) +// A ResubscribeErrFunc attempts to establish a subscription. +// For every call but the first, the second argument to this function is +// the error that occurred with the previous subscription. +type ResubscribeErrFunc func(context.Context, error) (Subscription, error) type resubscribeSub struct { - fn ResubscribeFunc + fn ResubscribeErrFunc err chan error unsub chan struct{} unsubOnce sync.Once lastTry mclock.AbsTime + lastSubErr error waitTime, backoffMax time.Duration } @@ -149,7 +172,7 @@ func (s *resubscribeSub) subscribe() Subscription { s.lastTry = mclock.Now() ctx, cancel := context.WithCancel(context.Background()) go func() { - rsub, err := s.fn(ctx) + rsub, err := s.fn(ctx, s.lastSubErr) sub = rsub subscribed <- err }() @@ -178,6 +201,7 @@ func (s *resubscribeSub) waitForError(sub Subscription) bool { defer sub.Unsubscribe() select { case err := <-sub.Err(): + s.lastSubErr = err return err == nil case <-s.unsub: return true diff --git a/event/subscription_test.go b/event/subscription_test.go index c48be3aa30..ba081705c4 100644 --- a/event/subscription_test.go +++ b/event/subscription_test.go @@ -19,6 +19,8 @@ package event import ( "context" "errors" + "fmt" + "reflect" "testing" "time" ) @@ -118,3 +120,37 @@ func TestResubscribeAbort(t *testing.T) { t.Fatal(err) } } + +func TestResubscribeWithErrorHandler(t *testing.T) { + t.Parallel() + + var i int + nfails := 6 + subErrs := make([]string, 0) + sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) { + i++ + var lastErrVal string + if lastErr != nil { + lastErrVal = lastErr.Error() + } + subErrs = append(subErrs, lastErrVal) + sub := NewSubscription(func(unsubscribed <-chan struct{}) error { + if i < nfails { + return fmt.Errorf("err-%v", i) + } else { + return nil + } + }) + return sub, nil + }) + + <-sub.Err() + if i != nfails { + t.Fatalf("resubscribe function called %d times, want %d times", i, nfails) + } + + expectedSubErrs := []string{"", "err-1", "err-2", "err-3", "err-4", "err-5"} + if !reflect.DeepEqual(subErrs, expectedSubErrs) { + t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs) + } +} From 9e1bd0f3671d19d4964ed8c8a95edfd12413d8c3 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 22 Jan 2021 17:11:24 +0800 Subject: [PATCH 272/948] trie: fix range prover (#22210) Fixes a special case when the trie only has a single trie node and the range proof only contains a single element. --- trie/proof.go | 44 +++++++++++++++++++++++++++++--------------- trie/proof_test.go | 19 +++++++++++++++++++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/trie/proof.go b/trie/proof.go index e7102f12b7..61c35a8423 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -216,7 +216,7 @@ func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyV // // Note we have the assumption here the given boundary keys are different // and right is larger than left. -func unsetInternal(n node, left []byte, right []byte) error { +func unsetInternal(n node, left []byte, right []byte) (bool, error) { left, right = keybytesToHex(left), keybytesToHex(right) // Step down to the fork point. There are two scenarios can happen: @@ -278,45 +278,55 @@ findFork: // - left proof points to the shortnode, but right proof is greater // - right proof points to the shortnode, but left proof is less if shortForkLeft == -1 && shortForkRight == -1 { - return errors.New("empty range") + return false, errors.New("empty range") } if shortForkLeft == 1 && shortForkRight == 1 { - return errors.New("empty range") + return false, errors.New("empty range") } if shortForkLeft != 0 && shortForkRight != 0 { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } parent.(*fullNode).Children[left[pos-1]] = nil - return nil + return false, nil } // Only one proof points to non-existent key. if shortForkRight != 0 { - // Unset left proof's path if _, ok := rn.Val.(valueNode); ok { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } parent.(*fullNode).Children[left[pos-1]] = nil - return nil + return false, nil } - return unset(rn, rn.Val, left[pos:], len(rn.Key), false) + return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false) } if shortForkLeft != 0 { - // Unset right proof's path. if _, ok := rn.Val.(valueNode); ok { + // The fork point is root node, unset the entire trie + if parent == nil { + return true, nil + } parent.(*fullNode).Children[right[pos-1]] = nil - return nil + return false, nil } - return unset(rn, rn.Val, right[pos:], len(rn.Key), true) + return false, unset(rn, rn.Val, right[pos:], len(rn.Key), true) } - return nil + return false, nil case *fullNode: // unset all internal nodes in the forkpoint for i := left[pos] + 1; i < right[pos]; i++ { rn.Children[i] = nil } if err := unset(rn, rn.Children[left[pos]], left[pos:], 1, false); err != nil { - return err + return false, err } if err := unset(rn, rn.Children[right[pos]], right[pos:], 1, true); err != nil { - return err + return false, err } - return nil + return false, nil default: panic(fmt.Sprintf("%T: invalid node: %v", n, n)) } @@ -560,7 +570,8 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. - if err := unsetInternal(root, firstKey, lastKey); err != nil { + empty, err := unsetInternal(root, firstKey, lastKey) + if err != nil { return nil, nil, nil, false, err } // Rebuild the trie with the leaf stream, the shape of trie @@ -570,6 +581,9 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key triedb = NewDatabase(diskdb) ) tr := &Trie{root: root, db: triedb} + if empty { + tr.root = nil + } for index, key := range keys { tr.TryUpdate(key, values[index]) } diff --git a/trie/proof_test.go b/trie/proof_test.go index 3ecd318886..304affa9f2 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -384,6 +384,25 @@ func TestOneElementRangeProof(t *testing.T) { if err != nil { t.Fatalf("Expected no error, got %v", err) } + + // Test the mini trie with only a single element. + tinyTrie := new(Trie) + entry := &kv{randBytes(32), randBytes(20), false} + tinyTrie.Update(entry.k, entry.v) + + first = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() + last = entry.k + proof = memorydb.New() + if err := tinyTrie.Prove(first, 0, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) + } + if err := tinyTrie.Prove(last, 0, proof); err != nil { + t.Fatalf("Failed to prove the last node %v", err) + } + _, _, _, _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } } // TestAllElementsProof tests the range proof with all elements. From f26c19cbcd60175598824c7e09b64b8896daf721 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 22 Jan 2021 20:15:27 +0100 Subject: [PATCH 273/948] common/mclock: remove dependency on github.com/aristanetworks/goarista (#22211) It takes three lines of code to get to runtime.nanotime, no need to pull a dependency for that. --- common/mclock/mclock.go | 12 ++++++++---- common/mclock/mclock.s | 1 + go.mod | 1 - go.sum | 2 -- 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 common/mclock/mclock.s diff --git a/common/mclock/mclock.go b/common/mclock/mclock.go index 3aca257cb3..c05738cf2b 100644 --- a/common/mclock/mclock.go +++ b/common/mclock/mclock.go @@ -20,15 +20,19 @@ package mclock import ( "time" - "github.com/aristanetworks/goarista/monotime" + _ "unsafe" // for go:linkname ) +//go:noescape +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + // AbsTime represents absolute monotonic time. -type AbsTime time.Duration +type AbsTime int64 // Now returns the current absolute monotonic time. func Now() AbsTime { - return AbsTime(monotime.Now()) + return AbsTime(nanotime()) } // Add returns t + d as absolute time. @@ -74,7 +78,7 @@ type System struct{} // Now returns the current monotonic time. func (c System) Now() AbsTime { - return AbsTime(monotime.Now()) + return Now() } // Sleep blocks for the given duration. diff --git a/common/mclock/mclock.s b/common/mclock/mclock.s new file mode 100644 index 0000000000..99a7a878f0 --- /dev/null +++ b/common/mclock/mclock.s @@ -0,0 +1 @@ +// This file exists in order to be able to use go:linkname. diff --git a/go.mod b/go.mod index d630dc6996..9162754ea3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 github.com/aws/aws-sdk-go v1.25.48 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 github.com/cespare/cp v0.1.0 diff --git a/go.sum b/go.sum index f8939df2ba..327af03735 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= From db35d77b63ab462990ad95481e116f9340d52de2 Mon Sep 17 00:00:00 2001 From: ligi Date: Sun, 24 Jan 2021 11:37:08 +0100 Subject: [PATCH 274/948] cmd, geth: CLI help fixes (#22220) * cmd, geth: Reflect command being optional - closes 22218 * cmd, geth: Set current year to 2021 --- internal/flags/helpers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index f61ed4b689..eb5f5547b1 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -51,10 +51,10 @@ OPTIONS: AppHelpTemplate = `NAME: {{.App.Name}} - {{.App.Usage}} - Copyright 2013-2019 The go-ethereum Authors + Copyright 2013-2021 The go-ethereum Authors USAGE: - {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} + {{.App.HelpName}} [options]{{if .App.Commands}} [command] [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} {{if .App.Version}} VERSION: {{.App.Version}} @@ -77,7 +77,7 @@ COPYRIGHT: ClefAppHelpTemplate = `NAME: {{.App.Name}} - {{.App.Usage}} - Copyright 2013-2019 The go-ethereum Authors + Copyright 2013-2021 The go-ethereum Authors USAGE: {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} From 797b0812ab742b2a24e3c9277fa6564ff9eb9094 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 25 Jan 2021 07:17:05 +0100 Subject: [PATCH 276/948] eth/protocols/snap: snap sync testing (#22179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: make timeout configurable * eth/protocols/snap: snap sync testing * eth/protocols/snap: test to trigger panic * eth/protocols/snap: fix race condition on timeouts * eth/protocols/snap: return error on cancelled sync * squashme: updates + test causing panic + properly serve accounts in order * eth/protocols/snap: revert failing storage response * eth/protocols/snap: revert on bad responses (storage, code) * eth/protocols/snap: fix account handling stall * eth/protocols/snap: fix remaining revertal-issues * eth/protocols/snap: timeouthandler for bytecode requests * eth/protocols/snap: debugging + fix log message * eth/protocols/snap: fix misspelliings in docs * eth/protocols/snap: fix race in bytecode handling * eth/protocols/snap: undo deduplication of storage roots * synctests: refactor + minify panic testcase * eth/protocols/snap: minor polishes * eth: minor polishes to make logs more useful * eth/protocols/snap: remove excessive logs from the test runs * eth/protocols/snap: stress tests with concurrency * eth/protocols/snap: further fixes to test cancel channel handling * eth/protocols/snap: extend test timeouts on CI Co-authored-by: Péter Szilágyi --- eth/downloader/downloader.go | 4 +- eth/handler.go | 16 +- eth/protocols/snap/peer.go | 5 + eth/protocols/snap/protocol.go | 1 + eth/protocols/snap/sync.go | 349 ++++++----- eth/protocols/snap/sync_test.go | 1020 +++++++++++++++++++++++++++++++ 6 files changed, 1248 insertions(+), 147 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 31c1cb47c3..421803876e 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -298,7 +298,7 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { // Tests use short IDs, don't choke on them logger = log.New("peer", id) } else { - logger = log.New("peer", id[:16]) + logger = log.New("peer", id[:8]) } logger.Trace("Registering sync peer") if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { @@ -325,7 +325,7 @@ func (d *Downloader) UnregisterPeer(id string) error { // Tests use short IDs, don't choke on them logger = log.New("peer", id) } else { - logger = log.New("peer", id[:16]) + logger = log.New("peer", id[:8]) } logger.Trace("Unregistering sync peer") if err := d.peers.Unregister(id); err != nil { diff --git a/eth/handler.go b/eth/handler.go index a9506c4995..f6366d9af1 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -326,24 +326,32 @@ func (h *handler) runSnapPeer(peer *snap.Peer, handler snap.Handler) error { } func (h *handler) removePeer(id string) { + // Create a custom logger to avoid printing the entire id + var logger log.Logger + if len(id) < 16 { + // Tests use short IDs, don't choke on them + logger = log.New("peer", id) + } else { + logger = log.New("peer", id[:8]) + } // Remove the eth peer if it exists eth := h.peers.ethPeer(id) if eth != nil { - log.Debug("Removing Ethereum peer", "peer", id) + logger.Debug("Removing Ethereum peer") h.downloader.UnregisterPeer(id) h.txFetcher.Drop(id) if err := h.peers.unregisterEthPeer(id); err != nil { - log.Error("Peer removal failed", "peer", id, "err", err) + logger.Error("Ethereum peer removal failed", "err", err) } } // Remove the snap peer if it exists snap := h.peers.snapPeer(id) if snap != nil { - log.Debug("Removing Snapshot peer", "peer", id) + logger.Debug("Removing Snapshot peer") h.downloader.SnapSyncer.Unregister(id) if err := h.peers.unregisterSnapPeer(id); err != nil { - log.Error("Peer removal failed", "peer", id, "err", err) + logger.Error("Snapshot peer removel failed", "err", err) } } // Hard disconnect at the networking layer diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go index 73eaaadd09..4f3d550f1f 100644 --- a/eth/protocols/snap/peer.go +++ b/eth/protocols/snap/peer.go @@ -56,6 +56,11 @@ func (p *Peer) Version() uint { return p.version } +// Log overrides the P2P logget with the higher level one containing only the id. +func (p *Peer) Log() log.Logger { + return p.logger +} + // RequestAccountRange fetches a batch of accounts rooted in a specific account // trie, starting with the origin. func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error { diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index a1e4349691..f1a25a2066 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -61,6 +61,7 @@ var ( errDecode = errors.New("invalid message") errInvalidMsgCode = errors.New("invalid message code") errBadRequest = errors.New("bad request") + errCancelled = errors.New("sync cancelled") ) // Packet represents a p2p message in the `snap` protocol. diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index d6f0eb5472..e7720026bf 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -73,10 +73,6 @@ const ( // waste bandwidth. maxTrieRequestCount = 512 - // requestTimeout is the maximum time a peer is allowed to spend on serving - // a single network request. - requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? - // accountConcurrency is the number of chunks to split the account trie into // to allow concurrent retrievals. accountConcurrency = 16 @@ -86,6 +82,12 @@ const ( storageConcurrency = 16 ) +var ( + // requestTimeout is the maximum time a peer is allowed to spend on serving + // a single network request. + requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? +) + // accountRequest tracks a pending account range request to ensure responses are // to actual requests and to validate any security constraints. // @@ -331,6 +333,33 @@ type syncProgress struct { BytecodeHealNops uint64 // Number of bytecodes not requested } +// SyncPeer abstracts out the methods required for a peer to be synced against +// with the goal of allowing the construction of mock peers without the full +// blown networking. +type SyncPeer interface { + // ID retrieves the peer's unique identifier. + ID() string + + // RequestAccountRange fetches a batch of accounts rooted in a specific account + // trie, starting with the origin. + RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error + + // RequestStorageRange fetches a batch of storage slots belonging to one or + // more accounts. If slots from only one accout is requested, an origin marker + // may also be used to retrieve from there. + RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error + + // RequestByteCodes fetches a batch of bytecodes by hash. + RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error + + // RequestTrieNodes fetches a batch of account or storage trie nodes rooted in + // a specificstate trie. + RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error + + // Log retrieves the peer's own contextual logger. + Log() log.Logger +} + // Syncer is an Ethereum account and storage trie syncer based on snapshots and // the snap protocol. It's purpose is to download all the accounts and storage // slots from remote peers and reassemble chunks of the state trie, on top of @@ -346,14 +375,15 @@ type Syncer struct { db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) bloom *trie.SyncBloom // Bloom filter to deduplicate nodes for state fixup - root common.Hash // Current state trie root being synced - tasks []*accountTask // Current account task set being synced - healer *healTask // Current state healing task being executed - update chan struct{} // Notification channel for possible sync progression + root common.Hash // Current state trie root being synced + tasks []*accountTask // Current account task set being synced + snapped bool // Flag to signal that snap phase is done + healer *healTask // Current state healing task being executed + update chan struct{} // Notification channel for possible sync progression - peers map[string]*Peer // Currently active peers to download from - peerJoin *event.Feed // Event feed to react to peers joining - peerDrop *event.Feed // Event feed to react to peers dropping + peers map[string]SyncPeer // Currently active peers to download from + peerJoin *event.Feed // Event feed to react to peers joining + peerDrop *event.Feed // Event feed to react to peers dropping // Request tracking during syncing phase statelessPeers map[string]struct{} // Peers that failed to deliver state data @@ -410,12 +440,14 @@ type Syncer struct { lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) } +// NewSyncer creates a new snapshot syncer to download the Ethereum state over the +// snap protocol. func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { return &Syncer{ db: db, bloom: bloom, - peers: make(map[string]*Peer), + peers: make(map[string]SyncPeer), peerJoin: new(event.Feed), peerDrop: new(event.Feed), update: make(chan struct{}, 1), @@ -447,27 +479,29 @@ func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { } // Register injects a new data source into the syncer's peerset. -func (s *Syncer) Register(peer *Peer) error { +func (s *Syncer) Register(peer SyncPeer) error { // Make sure the peer is not registered yet + id := peer.ID() + s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - log.Error("Snap peer already registered", "id", peer.id) + if _, ok := s.peers[id]; ok { + log.Error("Snap peer already registered", "id", id) s.lock.Unlock() return errors.New("already registered") } - s.peers[peer.id] = peer + s.peers[id] = peer // Mark the peer as idle, even if no sync is running - s.accountIdlers[peer.id] = struct{}{} - s.storageIdlers[peer.id] = struct{}{} - s.bytecodeIdlers[peer.id] = struct{}{} - s.trienodeHealIdlers[peer.id] = struct{}{} - s.bytecodeHealIdlers[peer.id] = struct{}{} + s.accountIdlers[id] = struct{}{} + s.storageIdlers[id] = struct{}{} + s.bytecodeIdlers[id] = struct{}{} + s.trienodeHealIdlers[id] = struct{}{} + s.bytecodeHealIdlers[id] = struct{}{} s.lock.Unlock() // Notify any active syncs that a new peer can be assigned data - s.peerJoin.Send(peer.id) + s.peerJoin.Send(id) return nil } @@ -566,6 +600,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { s.assignAccountTasks(cancel) s.assignBytecodeTasks(cancel) s.assignStorageTasks(cancel) + if len(s.tasks) == 0 { // Sync phase done, run heal phase s.assignTrienodeHealTasks(cancel) @@ -580,7 +615,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { case id := <-peerDrop: s.revertRequests(id) case <-cancel: - return nil + return errCancelled case req := <-s.accountReqFails: s.revertAccountRequest(req) @@ -622,6 +657,7 @@ func (s *Syncer) loadSyncStatus() { log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) } s.tasks = progress.Tasks + s.snapped = len(s.tasks) == 0 s.accountSynced = progress.AccountSynced s.accountBytes = progress.AccountBytes @@ -701,6 +737,11 @@ func (s *Syncer) cleanAccountTasks() { i-- } } + if len(s.tasks) == 0 { + s.lock.Lock() + s.snapped = true + s.lock.Unlock() + } } // cleanStorageTasks iterates over all the account tasks and storage sub-tasks @@ -798,7 +839,7 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { delete(s.accountIdlers, idle) s.pend.Add(1) - go func(peer *Peer, root common.Hash) { + go func(peer SyncPeer, root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -885,7 +926,7 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { delete(s.bytecodeIdlers, idle) s.pend.Add(1) - go func(peer *Peer) { + go func(peer SyncPeer) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -962,7 +1003,6 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { // Found an incomplete storage chunk, schedule it accounts = append(accounts, account) roots = append(roots, st.root) - subtask = st break // Large contract chunks are downloaded individually } @@ -1010,7 +1050,7 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { delete(s.storageIdlers, idle) s.pend.Add(1) - go func(peer *Peer, root common.Hash) { + go func(peer SyncPeer, root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1125,7 +1165,7 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { delete(s.trienodeHealIdlers, idle) s.pend.Add(1) - go func(peer *Peer, root common.Hash) { + go func(peer SyncPeer, root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1223,7 +1263,7 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { delete(s.bytecodeHealIdlers, idle) s.pend.Add(1) - go func(peer *Peer) { + go func(peer SyncPeer) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1522,7 +1562,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { break } } - // Itereate over all the accounts and assemble which ones need further sub- + // Iterate over all the accounts and assemble which ones need further sub- // filling before the entire account range can be persisted. res.task.needCode = make([]bool, len(res.accounts)) res.task.needState = make([]bool, len(res.accounts)) @@ -1566,7 +1606,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { } } // Delete any subtasks that have been aborted but not resumed. This may undo - // some progress if a newpeer gives us less accounts than an old one, but for + // some progress if a new peer gives us less accounts than an old one, but for // now we have to live with that. for hash := range res.task.SubTasks { if _, ok := resumed[hash]; !ok { @@ -1650,95 +1690,92 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { ) // Iterate over all the accounts and reconstruct their storage tries from the // delivered slots - delivered := make(map[common.Hash]bool) - for i := 0; i < len(res.hashes); i++ { - delivered[res.roots[i]] = true - } for i, account := range res.accounts { // If the account was not delivered, reschedule it if i >= len(res.hashes) { - if !delivered[res.roots[i]] { - res.mainTask.stateTasks[account] = res.roots[i] - } + res.mainTask.stateTasks[account] = res.roots[i] continue } // State was delivered, if complete mark as not needed any more, otherwise // mark the account as needing healing - for j, acc := range res.mainTask.res.accounts { - if res.roots[i] == acc.Root { - // If the packet contains multiple contract storage slots, all - // but the last are surely complete. The last contract may be - // chunked, so check it's continuation flag. - if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { - res.mainTask.needState[j] = false - res.mainTask.pend-- - } - // If the last contract was chunked, mark it as needing healing - // to avoid writing it out to disk prematurely. - if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { - res.mainTask.needHeal[j] = true - } - // If the last contract was chunked, we need to switch to large - // contract handling mode - if res.subTask == nil && i == len(res.hashes)-1 && res.cont { - // If we haven't yet started a large-contract retrieval, create - // the subtasks for it within the main account task - if tasks, ok := res.mainTask.SubTasks[account]; !ok { - var ( - next common.Hash - ) - step := new(big.Int).Sub( - new(big.Int).Div( - new(big.Int).Exp(common.Big2, common.Big256, nil), - big.NewInt(storageConcurrency), - ), common.Big1, - ) - for k := 0; k < storageConcurrency; k++ { - last := common.BigToHash(new(big.Int).Add(next.Big(), step)) - if k == storageConcurrency-1 { - // Make sure we don't overflow if the step is not a proper divisor - last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - } - tasks = append(tasks, &storageTask{ - Next: next, - Last: last, - root: acc.Root, - }) - log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) - next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + for j, hash := range res.mainTask.res.hashes { + if account != hash { + continue + } + acc := res.mainTask.res.accounts[j] + + // If the packet contains multiple contract storage slots, all + // but the last are surely complete. The last contract may be + // chunked, so check it's continuation flag. + if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { + res.mainTask.needState[j] = false + res.mainTask.pend-- + } + // If the last contract was chunked, mark it as needing healing + // to avoid writing it out to disk prematurely. + if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont { + res.mainTask.needHeal[j] = true + } + // If the last contract was chunked, we need to switch to large + // contract handling mode + if res.subTask == nil && i == len(res.hashes)-1 && res.cont { + // If we haven't yet started a large-contract retrieval, create + // the subtasks for it within the main account task + if tasks, ok := res.mainTask.SubTasks[account]; !ok { + var ( + next common.Hash + ) + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(storageConcurrency), + ), common.Big1, + ) + for k := 0; k < storageConcurrency; k++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if k == storageConcurrency-1 { + // Make sure we don't overflow if the step is not a proper divisor + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } - res.mainTask.SubTasks[account] = tasks - - // Since we've just created the sub-tasks, this response - // is surely for the first one (zero origin) - res.subTask = tasks[0] + tasks = append(tasks, &storageTask{ + Next: next, + Last: last, + root: acc.Root, + }) + log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) } + res.mainTask.SubTasks[account] = tasks + + // Since we've just created the sub-tasks, this response + // is surely for the first one (zero origin) + res.subTask = tasks[0] } - // If we're in large contract delivery mode, forward the subtask - if res.subTask != nil { - // Ensure the response doesn't overflow into the subsequent task - last := res.subTask.Last.Big() - for k, hash := range res.hashes[i] { - if hash.Big().Cmp(last) > 0 { - // Chunk overflown, cut off excess, but also update the boundary - for l := k; l < len(res.hashes[i]); l++ { - if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { - panic(err) // Account range was already proven, what happened - } + } + // If we're in large contract delivery mode, forward the subtask + if res.subTask != nil { + // Ensure the response doesn't overflow into the subsequent task + last := res.subTask.Last.Big() + for k, hash := range res.hashes[i] { + if hash.Big().Cmp(last) > 0 { + // Chunk overflown, cut off excess, but also update the boundary + for l := k; l < len(res.hashes[i]); l++ { + if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { + panic(err) // Account range was already proven, what happened } - res.hashes[i] = res.hashes[i][:k] - res.slots[i] = res.slots[i][:k] - res.cont = false // Mark range completed - break } - } - // Forward the relevant storage chunk (even if created just now) - if res.cont { - res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) - } else { - res.subTask.done = true + res.hashes[i] = res.hashes[i][:k] + res.slots[i] = res.slots[i][:k] + res.cont = false // Mark range completed + break } } + // Forward the relevant storage chunk (even if created just now) + if res.cont { + res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) + } else { + res.subTask.done = true + } } } // Iterate over all the reconstructed trie nodes and push them to disk @@ -1941,7 +1978,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { // OnAccounts is a callback method to invoke when a range of accounts are // received from a remote peer. -func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { +func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error { size := common.StorageSize(len(hashes) * common.HashLength) for _, account := range accounts { size += common.StorageSize(len(account)) @@ -1949,15 +1986,15 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account for _, node := range proof { size += common.StorageSize(len(node)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.accountIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.accountIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -1975,7 +2012,11 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For account range queries that means the state being @@ -1983,7 +2024,7 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account // synced to our head. if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 { logger.Debug("Peer rejected account range request", "root", s.root) - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2011,6 +2052,8 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account db, tr, notary, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) if err != nil { logger.Warn("Account range failed proof", "err", err) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertAccountRequest(req) return err } // Partial trie reconstructed, send it to the scheduler for storage filling @@ -2050,9 +2093,9 @@ func (s *Syncer) OnAccounts(peer *Peer, id uint64, hashes []common.Hash, account // OnByteCodes is a callback method to invoke when a batch of contract // bytes codes are received from a remote peer. -func (s *Syncer) OnByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { +func (s *Syncer) OnByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { s.lock.RLock() - syncing := len(s.tasks) > 0 + syncing := !s.snapped s.lock.RUnlock() if syncing { @@ -2063,20 +2106,20 @@ func (s *Syncer) OnByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // onByteCodes is a callback method to invoke when a batch of contract // bytes codes are received from a remote peer in the syncing phase. -func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { +func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { var size common.StorageSize for _, code := range bytecodes { size += common.StorageSize(len(code)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.bytecodeIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.bytecodeIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2094,14 +2137,18 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For bytecode range queries that means the peer is not // yet synced. if len(bytecodes) == 0 { logger.Debug("Peer rejected bytecode request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2132,6 +2179,8 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { } // We've either ran out of hashes, or got unrequested data logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeRequest(req) return errors.New("unexpected bytecode") } // Response validated, send it to the scheduler for filling @@ -2150,7 +2199,7 @@ func (s *Syncer) onByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { // OnStorage is a callback method to invoke when ranges of storage slots // are received from a remote peer. -func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { +func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error { // Gather some trace stats to aid in debugging issues var ( hashCount int @@ -2170,15 +2219,15 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots for _, node := range proof { size += common.StorageSize(len(node)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.storageIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.storageIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2196,17 +2245,23 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Reject the response if the hash sets and slot sets don't match, or if the // peer sent more data than requested. if len(hashes) != len(slots) { s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots)) return errors.New("hash and slot set size mismatch") } if len(hashes) > len(req.accounts) { s.lock.Unlock() + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts)) return errors.New("hash set larger than requested") } @@ -2216,11 +2271,9 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // synced to our head. if len(hashes) == 0 { logger.Debug("Peer rejected storage request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() - - // Signal this request as failed, and ready for rescheduling - s.scheduleRevertStorageRequest(req) + s.scheduleRevertStorageRequest(req) // reschedule request return nil } s.lock.Unlock() @@ -2250,6 +2303,7 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // space and hash to the origin root. dbs[i], tries[i], _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) if err != nil { + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) return err } @@ -2264,6 +2318,7 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots } dbs[i], tries[i], notary, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) if err != nil { + s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage range failed proof", "err", err) return err } @@ -2302,20 +2357,20 @@ func (s *Syncer) OnStorage(peer *Peer, id uint64, hashes [][]common.Hash, slots // OnTrieNodes is a callback method to invoke when a batch of trie nodes // are received from a remote peer. -func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { +func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error { var size common.StorageSize for _, node := range trienodes { size += common.StorageSize(len(node)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.trienodeHealIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.trienodeHealIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2333,14 +2388,18 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For bytecode range queries that means the peer is not // yet synced. if len(trienodes) == 0 { logger.Debug("Peer rejected trienode heal request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2371,6 +2430,8 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { } // We've either ran out of hashes, or got unrequested data logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertTrienodeHealRequest(req) return errors.New("unexpected healing trienode") } // Response validated, send it to the scheduler for filling @@ -2390,20 +2451,20 @@ func (s *Syncer) OnTrieNodes(peer *Peer, id uint64, trienodes [][]byte) error { // onHealByteCodes is a callback method to invoke when a batch of contract // bytes codes are received from a remote peer in the healing phase. -func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) error { +func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error { var size common.StorageSize for _, code := range bytecodes { size += common.StorageSize(len(code)) } - logger := peer.logger.New("reqid", id) + logger := peer.Log().New("reqid", id) logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size) // Whether or not the response is valid, we can mark the peer as idle and // notify the scheduler to assign a new task. If the response is invalid, // we'll drop the peer in a bit. s.lock.Lock() - if _, ok := s.peers[peer.id]; ok { - s.bytecodeHealIdlers[peer.id] = struct{}{} + if _, ok := s.peers[peer.ID()]; ok { + s.bytecodeHealIdlers[peer.ID()] = struct{}{} } select { case s.update <- struct{}{}: @@ -2421,14 +2482,18 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content - req.timeout.Stop() + if !req.timeout.Stop() { + // The timeout is already triggered, and this request will be reverted+rescheduled + s.lock.Unlock() + return nil + } // Response is valid, but check if peer is signalling that it does not have // the requested data. For bytecode range queries that means the peer is not // yet synced. if len(bytecodes) == 0 { logger.Debug("Peer rejected bytecode heal request") - s.statelessPeers[peer.id] = struct{}{} + s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() // Signal this request as failed, and ready for rescheduling @@ -2459,6 +2524,8 @@ func (s *Syncer) onHealByteCodes(peer *Peer, id uint64, bytecodes [][]byte) erro } // We've either ran out of hashes, or got unrequested data logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i) + // Signal this request as failed, and ready for rescheduling + s.scheduleRevertBytecodeHealRequest(req) return errors.New("unexpected healing bytecode") } // Response validated, send it to the scheduler for filling diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 4f28b99bfe..0b048786e8 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -17,15 +17,29 @@ package snap import ( + "bytes" "crypto/rand" + "encoding/binary" "fmt" + "math/big" + "sort" "testing" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" "golang.org/x/crypto/sha3" ) func TestHashing(t *testing.T) { + t.Parallel() + var bytecodes = make([][]byte, 10) for i := 0; i < len(bytecodes); i++ { buf := make([]byte, 100) @@ -96,3 +110,1009 @@ func BenchmarkHashing(b *testing.B) { } }) } + +type storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error +type accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error +type trieHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error +type codeHandlerFunc func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error + +type testPeer struct { + id string + test *testing.T + remote *Syncer + logger log.Logger + accountTrie *trie.Trie + accountValues entrySlice + storageTries map[common.Hash]*trie.Trie + storageValues map[common.Hash]entrySlice + + accountRequestHandler accountHandlerFunc + storageRequestHandler storageHandlerFunc + trieRequestHandler trieHandlerFunc + codeRequestHandler codeHandlerFunc + cancelCh chan struct{} +} + +func newTestPeer(id string, t *testing.T, cancelCh chan struct{}) *testPeer { + peer := &testPeer{ + id: id, + test: t, + logger: log.New("id", id), + accountRequestHandler: defaultAccountRequestHandler, + trieRequestHandler: defaultTrieRequestHandler, + storageRequestHandler: defaultStorageRequestHandler, + codeRequestHandler: defaultCodeRequestHandler, + cancelCh: cancelCh, + } + //stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) + //peer.logger.SetHandler(stderrHandler) + return peer + +} + +func (t *testPeer) ID() string { return t.id } +func (t *testPeer) Log() log.Logger { return t.logger } + +func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error { + t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + go t.accountRequestHandler(t, id, root, origin, bytes) + return nil +} + +func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { + t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + go t.trieRequestHandler(t, id, root, paths, bytes) + return nil +} + +func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + if len(accounts) == 1 && origin != nil { + t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) + } else { + t.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) + } + go t.storageRequestHandler(t, id, root, accounts, origin, limit, bytes) + return nil +} + +func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + go t.codeRequestHandler(t, id, hashes, bytes) + return nil +} + +// defaultTrieRequestHandler is a well-behaving handler for trie healing requests +func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + // Pass the response + var nodes [][]byte + for _, pathset := range paths { + switch len(pathset) { + case 1: + blob, _, err := t.accountTrie.TryGetNode(pathset[0]) + if err != nil { + t.logger.Info("Error handling req", "error", err) + break + } + nodes = append(nodes, blob) + default: + account := t.storageTries[(common.BytesToHash(pathset[0]))] + for _, path := range pathset[1:] { + blob, _, err := account.TryGetNode(path) + if err != nil { + t.logger.Info("Error handling req", "error", err) + break + } + nodes = append(nodes, blob) + } + } + } + t.remote.OnTrieNodes(t, requestId, nodes) + return nil +} + +// defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests +func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, cap uint64) error { + keys, vals, proofs := createAccountRequestResponse(t, root, origin, cap) + if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil { + t.logger.Error("remote error on delivery", "error", err) + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.remote.Unregister(t.id) + close(t.cancelCh) + return err + } + return nil +} + +func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) { + var size uint64 + for _, entry := range t.accountValues { + if size > cap { + break + } + if bytes.Compare(origin[:], entry.k) <= 0 { + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + } + } + // Unless we send the entire trie, we need to supply proofs + // Actually, we need to supply proofs either way! This seems tob be an implementation + // quirk in go-ethereum + proof := light.NewNodeSet() + if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", origin, + "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := t.accountTrie.Prove(lastK, 0, proof); err != nil { + t.logger.Error("Could not prove last item", + "error", err) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + return keys, vals, proofs +} + +// defaultStorageRequestHandler is a well-behaving storage request handler +func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.logger.Error("remote error on delivery", "error", err) + t.test.Errorf("Remote side rejected our delivery: %v", err) + close(t.cancelCh) + } + return nil +} + +func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes { + bytecodes = append(bytecodes, getCode(h)) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Error("remote error on delivery", "error", err) + t.test.Errorf("Remote side rejected our delivery: %v", err) + close(t.cancelCh) + } + return nil +} + +func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { + var ( + size uint64 + limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + ) + if len(bLimit) > 0 { + limit = common.BytesToHash(bLimit) + } + var origin common.Hash + if len(bOrigin) > 0 { + origin = common.BytesToHash(bOrigin) + } + + var limitExceeded bool + var incomplete bool + for _, account := range accounts { + + var keys []common.Hash + var vals [][]byte + for _, entry := range t.storageValues[account] { + if limitExceeded { + incomplete = true + break + } + if bytes.Compare(entry.k, origin[:]) < 0 { + incomplete = true + continue + } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + if bytes.Compare(entry.k, limit[:]) >= 0 { + limitExceeded = true + } + if size > max { + limitExceeded = true + } + } + hashes = append(hashes, keys) + slots = append(slots, vals) + + if incomplete { + // If we're aborting, we need to prove the first and last item + // This terminates the response (and thus the loop) + proof := light.NewNodeSet() + stTrie := t.storageTries[account] + + // Here's a potential gotcha: when constructing the proof, we cannot + // use the 'origin' slice directly, but must use the full 32-byte + // hash form. + if err := stTrie.Prove(origin[:], 0, proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", origin, + "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := stTrie.Prove(lastK, 0, proof); err != nil { + t.logger.Error("Could not prove last item", "error", err) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + break + } + } + return hashes, slots, proofs +} + +// emptyRequestAccountRangeFn is a rejects AccountRangeRequests +func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + var proofs [][]byte + var keys []common.Hash + var vals [][]byte + t.remote.OnAccounts(t, requestId, keys, vals, proofs) + return nil +} + +func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + return nil +} + +func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + var nodes [][]byte + t.remote.OnTrieNodes(t, requestId, nodes) + return nil +} + +func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { + return nil +} + +func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + var hashes [][]common.Hash + var slots [][][]byte + var proofs [][]byte + t.remote.OnStorage(t, requestId, hashes, slots, proofs) + return nil +} + +func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return nil +} + +//func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { +// var bytecodes [][]byte +// t.remote.OnByteCodes(t, id, bytecodes) +// return nil +//} + +func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes { + // Send back the hashes + bytecodes = append(bytecodes, h[:]) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Error("remote error on delivery", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + var bytecodes [][]byte + for _, h := range hashes[:1] { + bytecodes = append(bytecodes, getCode(h)) + } + if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { + t.logger.Error("remote error on delivery", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// starvingStorageRequestHandler is somewhat well-behaving storage handler, but it caps the returned results to be very small +func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500) +} + +func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + return defaultAccountRequestHandler(t, requestId, root, origin, 500) +} + +//func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { +// return defaultAccountRequestHandler(t, requestId-1, root, origin, 500) +//} + +func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, cap) + if len(proofs) > 0 { + proofs = proofs[1:] + } + if err := t.remote.OnAccounts(t, requestId, hashes, accounts, proofs); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// corruptStorageRequestHandler doesn't provide good proofs +func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, origin, limit, max) + if len(proofs) > 0 { + proofs = proofs[1:] + } + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, _ := createStorageRequestResponse(t, root, accounts, origin, limit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, nil); err != nil { + t.logger.Info("remote error on delivery (as expected)", "error", err) + // Mimic the real-life handler, which drops a peer on errors + t.remote.Unregister(t.id) + } + return nil +} + +// TestSyncBloatedProof tests a scenario where we provide only _one_ value, but +// also ship the entire trie inside the proof. If the attack is successful, +// the remote side does not do any follow-up requests +func TestSyncBloatedProof(t *testing.T) { + t.Parallel() + + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + cancel := make(chan struct{}) + source := newTestPeer("source", t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + + source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { + var proofs [][]byte + var keys []common.Hash + var vals [][]byte + + // The values + for _, entry := range t.accountValues { + if bytes.Compare(origin[:], entry.k) <= 0 { + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + } + } + // The proofs + proof := light.NewNodeSet() + if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { + t.logger.Error("Could not prove origin", "origin", origin, "error", err) + } + // The bloat: add proof of every single element + for _, entry := range t.accountValues { + if err := t.accountTrie.Prove(entry.k, 0, proof); err != nil { + t.logger.Error("Could not prove item", "error", err) + } + } + // And remove one item from the elements + if len(keys) > 2 { + keys = append(keys[:1], keys[2:]...) + vals = append(vals[:1], vals[2:]...) + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil { + t.logger.Info("remote error on delivery", "error", err) + // This is actually correct, signal to exit the test successfully + close(t.cancelCh) + } + return nil + } + syncer := setupSyncer(source) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil { + t.Fatal("No error returned from incomplete/cancelled sync") + } +} + +func setupSyncer(peers ...*testPeer) *Syncer { + stateDb := rawdb.NewMemoryDatabase() + syncer := NewSyncer(stateDb, trie.NewSyncBloom(1, stateDb)) + for _, peer := range peers { + syncer.Register(peer) + peer.remote = syncer + } + return syncer +} + +// TestSync tests a basic sync with one peer +func TestSync(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + + syncer := setupSyncer(mkSource("sourceA")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a +// panic within the prover +func TestSyncTinyTriePanic(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(1) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + + syncer := setupSyncer( + mkSource("nice-a"), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestMultiSync tests a basic sync with multiple peers +func TestMultiSync(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + + syncer := setupSyncer(mkSource("sourceA"), mkSource("sourceB")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestSyncWithStorage tests basic sync using accounts + storage + code +func TestSyncWithStorage(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + return source + } + syncer := setupSyncer(mkSource("sourceA")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all +func TestMultiSyncManyUseless(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, a, b, c bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if !a { + source.accountRequestHandler = emptyRequestAccountRangeFn + } + if !b { + source.storageRequestHandler = emptyStorageRequestHandler + } + if !c { + source.trieRequestHandler = emptyTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all +func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { + // We're setting the timeout to very low, to increase the chance of the timeout + // being triggered. This was previously a cause of panic, when a response + // arrived simultaneously as a timeout was triggered. + defer func(old time.Duration) { requestTimeout = old }(requestTimeout) + requestTimeout = time.Millisecond + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, a, b, c bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if !a { + source.accountRequestHandler = emptyRequestAccountRangeFn + } + if !b { + source.storageRequestHandler = emptyStorageRequestHandler + } + if !c { + source.trieRequestHandler = emptyTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +// TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all +func TestMultiSyncManyUnresponsive(t *testing.T) { + // We're setting the timeout to very low, to make the test run a bit faster + defer func(old time.Duration) { requestTimeout = old }(requestTimeout) + requestTimeout = time.Millisecond + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, a, b, c bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if !a { + source.accountRequestHandler = nonResponsiveRequestAccountRangeFn + } + if !b { + source.storageRequestHandler = nonResponsiveStorageRequestHandler + } + if !c { + source.trieRequestHandler = nonResponsiveTrieRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("full", true, true, true), + mkSource("noAccounts", false, true, true), + mkSource("noStorage", true, false, true), + mkSource("noTrie", true, true, false), + ) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } +} + +func checkStall(t *testing.T, cancel chan struct{}) chan struct{} { + testDone := make(chan struct{}) + go func() { + select { + case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much + t.Log("Sync stalled") + close(cancel) + case <-testDone: + return + } + }() + return testDone +} + +// TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is +// consistently returning very small results +func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, slow bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + + if slow { + source.accountRequestHandler = starvingAccountRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("nice-a", false), + mkSource("nice-b", false), + mkSource("nice-c", false), + mkSource("capped", true), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver +// code requests properly. +func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.codeRequestHandler = codeFn + return source + } + // One is capped, one is corrupt. If we don't use a capped one, there's a 50% + // chance that the full set of codes requested are sent only to the + // non-corrupt peer, which delivers everything in one go, and makes the + // test moot + syncer := setupSyncer( + mkSource("capped", cappedCodeRequestHandler), + mkSource("corrupt", corruptCodeRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, accFn accountHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.accountRequestHandler = accFn + return source + } + // One is capped, one is corrupt. If we don't use a capped one, there's a 50% + // chance that the full set of codes requested are sent only to the + // non-corrupt peer, which delivers everything in one go, and makes the + // test moot + syncer := setupSyncer( + mkSource("capped", defaultAccountRequestHandler), + mkSource("corrupt", corruptAccountRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes +// one by one +func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + + mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.codeRequestHandler = codeFn + return source + } + // Count how many times it's invoked. Remember, there are only 8 unique hashes, + // so it shouldn't be more than that + var counter int + syncer := setupSyncer( + mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { + counter++ + return cappedCodeRequestHandler(t, id, hashes, max) + }), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + // There are only 8 unique hashes, and 3K accounts. However, the code + // deduplication is per request batch. If it were a perfect global dedup, + // we would expect only 8 requests. If there were no dedup, there would be + // 3k requests. + // We expect somewhere below 100 requests for these 8 unique hashes. + if threshold := 100; counter > threshold { + t.Fatalf("Error, expected < %d invocations, got %d", threshold, counter) + } +} + +// TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is +// consistently returning very small results +func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false) + + mkSource := func(name string, slow bool) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + + if slow { + source.storageRequestHandler = starvingStorageRequestHandler + } + return source + } + + syncer := setupSyncer( + mkSource("nice-a", false), + mkSource("slow", true), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +// TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is +// sometimes sending bad proofs +func TestSyncWithStorageAndCorruptPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, handler storageHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + source.storageRequestHandler = handler + return source + } + + syncer := setupSyncer( + mkSource("nice-a", defaultStorageRequestHandler), + mkSource("nice-b", defaultStorageRequestHandler), + mkSource("nice-c", defaultStorageRequestHandler), + mkSource("corrupt", corruptStorageRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { + t.Parallel() + + cancel := make(chan struct{}) + + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + + mkSource := func(name string, handler storageHandlerFunc) *testPeer { + source := newTestPeer(name, t, cancel) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + source.storageRequestHandler = handler + return source + } + + syncer := setupSyncer( + mkSource("nice-a", defaultStorageRequestHandler), + mkSource("nice-b", defaultStorageRequestHandler), + mkSource("nice-c", defaultStorageRequestHandler), + mkSource("corrupt", noProofStorageRequestHandler), + ) + done := checkStall(t, cancel) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) +} + +type kv struct { + k, v []byte + t bool +} + +// Some helpers for sorting +type entrySlice []*kv + +func (p entrySlice) Len() int { return len(p) } +func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } +func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func key32(i uint64) []byte { + key := make([]byte, 32) + binary.LittleEndian.PutUint64(key, i) + return key +} + +var ( + codehashes = []common.Hash{ + crypto.Keccak256Hash([]byte{0}), + crypto.Keccak256Hash([]byte{1}), + crypto.Keccak256Hash([]byte{2}), + crypto.Keccak256Hash([]byte{3}), + crypto.Keccak256Hash([]byte{4}), + crypto.Keccak256Hash([]byte{5}), + crypto.Keccak256Hash([]byte{6}), + crypto.Keccak256Hash([]byte{7}), + } +) + +// getACodeHash returns a pseudo-random code hash +func getACodeHash(i uint64) []byte { + h := codehashes[int(i)%len(codehashes)] + return common.CopyBytes(h[:]) +} + +// convenience function to lookup the code from the code hash +func getCode(hash common.Hash) []byte { + if hash == emptyCode { + return nil + } + for i, h := range codehashes { + if h == hash { + return []byte{byte(i)} + } + } + return nil +} + +// makeAccountTrieNoStorage spits out a trie, along with the leafs +func makeAccountTrieNoStorage(n int) (*trie.Trie, entrySlice) { + db := trie.NewDatabase(rawdb.NewMemoryDatabase()) + accTrie, _ := trie.New(common.Hash{}, db) + var entries entrySlice + for i := uint64(1); i <= uint64(n); i++ { + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: i, + Balance: big.NewInt(int64(i)), + Root: emptyRoot, + CodeHash: getACodeHash(i), + }) + key := key32(i) + elem := &kv{key, value, false} + accTrie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + sort.Sort(entries) + // Push to disk layer + accTrie.Commit(nil) + return accTrie, entries +} + +// makeAccountTrieWithStorage spits out a trie, along with the leafs +func makeAccountTrieWithStorage(accounts, slots int, code bool) (*trie.Trie, entrySlice, + map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { + + var ( + db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + accTrie, _ = trie.New(common.Hash{}, db) + entries entrySlice + storageTries = make(map[common.Hash]*trie.Trie) + storageEntries = make(map[common.Hash]entrySlice) + ) + + // Make a storage trie which we reuse for the whole lot + stTrie, stEntries := makeStorageTrie(slots, db) + stRoot := stTrie.Hash() + // Create n accounts in the trie + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + codehash := emptyCode[:] + if code { + codehash = getACodeHash(i) + } + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: i, + Balance: big.NewInt(int64(i)), + Root: stRoot, + CodeHash: codehash, + }) + elem := &kv{key, value, false} + accTrie.Update(elem.k, elem.v) + entries = append(entries, elem) + // we reuse the same one for all accounts + storageTries[common.BytesToHash(key)] = stTrie + storageEntries[common.BytesToHash(key)] = stEntries + } + sort.Sort(entries) + stTrie.Commit(nil) + accTrie.Commit(nil) + return accTrie, entries, storageTries, storageEntries +} + +// makeStorageTrie fills a storage trie with n items, returning the +// not-yet-committed trie and the sorted entries +func makeStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice) { + trie, _ := trie.New(common.Hash{}, db) + var entries entrySlice + for i := uint64(1); i <= uint64(n); i++ { + // store 'i' at slot 'i' + slotValue := key32(i) + rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:])) + + slotKey := key32(i) + key := crypto.Keccak256Hash(slotKey[:]) + + elem := &kv{key[:], rlpSlotValue, false} + trie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + sort.Sort(entries) + return trie, entries +} From 1770fe718af661334391766455c43157e378b9fa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 25 Jan 2021 10:42:07 +0100 Subject: [PATCH 277/948] go.mod: update dependencies (#22216) This updates go module dependencies as discussed in #22050. --- go.mod | 22 ++-- go.sum | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 338 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 9162754ea3..d3c75420b8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 github.com/aws/aws-sdk-go v1.25.48 - github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 + github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 github.com/davecgh/go-spew v1.1.1 @@ -17,28 +17,27 @@ require ( github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 - github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c + github.com/edsrzf/mmap-go v1.0.0 github.com/fatih/color v1.3.0 - github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc + github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 - github.com/golang/protobuf v1.4.2 + github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa - github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 - github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 + github.com/gorilla/websocket v1.4.2 + github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 github.com/huin/goupnp v1.0.0 - github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 + github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e - github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 + github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 - github.com/kr/pretty v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 @@ -49,11 +48,10 @@ require ( github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 github.com/rjeczalik/notify v0.9.1 - github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 - github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect + github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 diff --git a/go.sum b/go.sum index 327af03735..98420f89f9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,23 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= @@ -20,29 +40,61 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6 h1:Eey/GGQ/E5Xp1P2Lyx1qj007hLZfbi0+CoVeJruGCtI= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,6 +102,7 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= @@ -57,30 +110,53 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= -github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -88,10 +164,17 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -99,10 +182,20 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54= -github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM= -github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M= +github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -114,18 +207,47 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo8u40n2JMnyAsd6x7+SbvoOMHvQOU/n10P4= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21 h1:F/iKcka0K2LgnKy/fgSBf235AETtm1n1TvBzqu40LE0= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -135,17 +257,26 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= @@ -157,75 +288,178 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -233,17 +467,82 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -255,6 +554,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= @@ -265,10 +565,20 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From c0862f4f4c21c0ff54636d5122f09f310133d504 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 25 Jan 2021 11:31:18 +0100 Subject: [PATCH 278/948] graphql: change receipt status to decimal instead of hex (#22187) This PR fixes the receipt status field to be decimal instead of a hex string, as called for by the spec. Co-authored-by: Martin Holst Swende --- graphql/graphql.go | 16 ++++++++-------- graphql/graphql_test.go | 6 ++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index ea587106b4..5dc0f723d3 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -292,12 +292,12 @@ func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) { return receipts[t.index], nil } -func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) Status(ctx context.Context) (*Long, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil { return nil, err } - ret := hexutil.Uint64(receipt.Status) + ret := Long(receipt.Status) return &ret, nil } @@ -810,9 +810,9 @@ type CallData struct { // CallResult encapsulates the result of an invocation of the `call` accessor. type CallResult struct { - data hexutil.Bytes // The return data from the call - gasUsed Long // The amount of gas used - status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success. + data hexutil.Bytes // The return data from the call + gasUsed Long // The amount of gas used + status Long // The return status of the call - 0 for failure or 1 for success. } func (c *CallResult) Data() hexutil.Bytes { @@ -823,7 +823,7 @@ func (c *CallResult) GasUsed() Long { return c.gasUsed } -func (c *CallResult) Status() hexutil.Uint64 { +func (c *CallResult) Status() Long { return c.status } @@ -840,7 +840,7 @@ func (b *Block) Call(ctx context.Context, args struct { if err != nil { return nil, err } - status := hexutil.Uint64(1) + status := Long(1) if result.Failed() { status = 0 } @@ -910,7 +910,7 @@ func (p *Pending) Call(ctx context.Context, args struct { if err != nil { return nil, err } - status := hexutil.Uint64(1) + status := Long(1) if result.Failed() { status = 0 } diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index e9c129c44c..71320012d5 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -130,6 +130,12 @@ func TestGraphQLBlockSerialization(t *testing.T) { want: `{"data":{"block":{"estimateGas":53000}}}`, code: 200, }, + // should return `status` as decimal + { + body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`, + want: `{"data":{"block":{"number":10,"call":{"data":"0x","status":1}}}}`, + code: 200, + }, } { resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) if err != nil { From 59a79137b9c8740a3623cdf35512e1eb49c8a20b Mon Sep 17 00:00:00 2001 From: ucwong Date: Mon, 25 Jan 2021 19:46:09 +0800 Subject: [PATCH 279/948] go.mod: upgrade github.com/huin/goupnp (#22227) This updates the goupnp dependency, fixing huin/goupnp#33 --- go.mod | 2 +- go.sum | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index d3c75420b8..ffea94f6cf 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 - github.com/huin/goupnp v1.0.0 + github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e diff --git a/go.sum b/go.sum index 98420f89f9..fefe5afb14 100644 --- a/go.sum +++ b/go.sum @@ -204,8 +204,8 @@ github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= -github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= +github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -441,6 +441,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -460,7 +462,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -468,7 +469,6 @@ golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kY golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -502,7 +502,6 @@ golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -555,7 +554,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= @@ -566,7 +564,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= From 04a72260c5bcca0ec7c4a63532fb29f68db03384 Mon Sep 17 00:00:00 2001 From: Melvin Junhee Woo Date: Mon, 25 Jan 2021 22:25:55 +0900 Subject: [PATCH 280/948] snapshot: merge loops for better performance (#22160) --- core/state/snapshot/difflayer.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index cc82df9a54..9c86a679d1 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -191,19 +191,15 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s if blob == nil { panic(fmt.Sprintf("account %#x nil", accountHash)) } + // Determine memory size and track the dirty writes + dl.memory += uint64(common.HashLength + len(blob)) + snapshotDirtyAccountWriteMeter.Mark(int64(len(blob))) } for accountHash, slots := range storage { if slots == nil { panic(fmt.Sprintf("storage %#x nil", accountHash)) } - } - // Determine memory size and track the dirty writes - for _, data := range accounts { - dl.memory += uint64(common.HashLength + len(data)) - snapshotDirtyAccountWriteMeter.Mark(int64(len(data))) - } - // Determine memory size and track the dirty writes - for _, slots := range storage { + // Determine memory size and track the dirty writes for _, data := range slots { dl.memory += uint64(common.HashLength + len(data)) snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) From 49cdcf5c70735dc85bd9c22b45811a3ec7cef54d Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 25 Jan 2021 21:29:45 +0800 Subject: [PATCH 281/948] core: reset to genesis when middle block is missing (#22135) When a sethead/rewind finds that the targeted block is missing, it resets to genesis instead of crashing. Closes #22129 --- core/blockchain.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d6668cdcd2..c05ebfd549 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -524,8 +524,13 @@ func (bc *BlockChain) SetHeadBeyondRoot(head uint64, root common.Hash) (uint64, if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil { log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) if pivot == nil || newHeadBlock.NumberU64() > *pivot { - newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) - continue + parent := bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) + if parent != nil { + newHeadBlock = parent + continue + } + log.Error("Missing block in the middle, aiming genesis", "number", newHeadBlock.NumberU64()-1, "hash", newHeadBlock.ParentHash()) + newHeadBlock = bc.genesisBlock } else { log.Trace("Rewind passed pivot, aiming genesis", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "pivot", *pivot) newHeadBlock = bc.genesisBlock From adf130def83fbf7b7902ff4bacab7bb369517dcb Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 25 Jan 2021 21:36:39 +0800 Subject: [PATCH 282/948] eth/tracers: move tracing APIs into eth/tracers (#22161) This moves the tracing RPC API implementation to package eth/tracers. By doing so, package eth no longer depends on tracing and the duktape JS engine. The change also enables tracing using the light client. All tracing methods work with the light client, but it's a lot slower compared to using a full node. --- cmd/utils/flags.go | 3 + eth/api.go | 3 +- eth/api_backend.go | 12 + eth/state_accessor.go | 230 +++++++++++ eth/{api_tracer.go => tracers/api.go} | 558 +++++++++++--------------- eth/tracers/api_test.go | 487 ++++++++++++++++++++++ les/api_backend.go | 12 + les/state_accessor.go | 88 ++++ 8 files changed, 1071 insertions(+), 322 deletions(-) create mode 100644 eth/state_accessor.go rename eth/{api_tracer.go => tracers/api.go} (55%) create mode 100644 eth/tracers/api_test.go create mode 100644 les/state_accessor.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index df036cbbf0..954b5de2c2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -45,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/graphql" @@ -1724,6 +1725,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } + stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) return backend.ApiBackend } backend, err := eth.New(stack, cfg) @@ -1736,6 +1738,7 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { Fatalf("Failed to create the LES server: %v", err) } } + stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) return backend.APIBackend } diff --git a/eth/api.go b/eth/api.go index be1dcbb52a..53ef91392b 100644 --- a/eth/api.go +++ b/eth/api.go @@ -426,10 +426,11 @@ func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, c if block == nil { return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) } - _, _, statedb, err := api.computeTxEnv(block, txIndex, 0) + _, _, statedb, release, err := api.eth.stateAtTransaction(block, txIndex, 0) if err != nil { return StorageRangeResult{}, err } + defer release() st := statedb.StorageTrie(contractAddress) if st == nil { return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress) diff --git a/eth/api_backend.go b/eth/api_backend.go index 2f7020475f..17de83a28f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -326,3 +326,15 @@ func (b *EthAPIBackend) Miner() *miner.Miner { func (b *EthAPIBackend) StartMining(threads int) error { return b.eth.StartMining(threads) } + +func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + return b.eth.stateAtBlock(block, reexec) +} + +func (b *EthAPIBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + return b.eth.statesInRange(fromBlock, toBlock, reexec) +} + +func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + return b.eth.stateAtTransaction(block, txIndex, reexec) +} diff --git a/eth/state_accessor.go b/eth/state_accessor.go new file mode 100644 index 0000000000..869b3d7636 --- /dev/null +++ b/eth/state_accessor.go @@ -0,0 +1,230 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" +) + +// stateAtBlock retrieves the state database associated with a certain block. +// If no state is locally available for the given block, a number of blocks are +// attempted to be reexecuted to generate the desired state. +func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64) (statedb *state.StateDB, release func(), err error) { + // If we have the state fully available, use that + statedb, err = eth.blockchain.StateAt(block.Root()) + if err == nil { + return statedb, func() {}, nil + } + // Otherwise try to reexec blocks until we find a state or reach our limit + origin := block.NumberU64() + database := state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) + + for i := uint64(0); i < reexec; i++ { + if block.NumberU64() == 0 { + return nil, nil, errors.New("genesis state is missing") + } + parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, nil, fmt.Errorf("missing block %v %d", block.ParentHash(), block.NumberU64()-1) + } + block = parent + + statedb, err = state.New(block.Root(), database, nil) + if err == nil { + break + } + } + if err != nil { + switch err.(type) { + case *trie.MissingNodeError: + return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) + default: + return nil, nil, err + } + } + // State was available at historical point, regenerate + var ( + start = time.Now() + logged time.Time + parent common.Hash + ) + defer func() { + if err != nil && parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + }() + for block.NumberU64() < origin { + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second { + log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) + logged = time.Now() + } + // Retrieve the next block to regenerate and process it + if block = eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { + return nil, nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) + } + _, _, _, err := eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + if err != nil { + return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) + } + // Finalize the state so any modifications are written to the trie + root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(block.Number())) + if err != nil { + return nil, nil, err + } + statedb, err = state.New(root, database, nil) + if err != nil { + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + } + database.TrieDB().Reference(root, common.Hash{}) + if parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + parent = root + } + nodes, imgs := database.TrieDB().Size() + log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) + return statedb, func() { database.TrieDB().Dereference(parent) }, nil +} + +// statesInRange retrieves a batch of state databases associated with the specific +// block ranges. If no state is locally available for the given range, a number of +// blocks are attempted to be reexecuted to generate the ancestor state. +func (eth *Ethereum) statesInRange(fromBlock, toBlock *types.Block, reexec uint64) (states []*state.StateDB, release func(), err error) { + statedb, err := eth.blockchain.StateAt(fromBlock.Root()) + if err != nil { + statedb, _, err = eth.stateAtBlock(fromBlock, reexec) + } + if err != nil { + return nil, nil, err + } + states = append(states, statedb.Copy()) + + var ( + logged time.Time + parent common.Hash + start = time.Now() + refs = []common.Hash{fromBlock.Root()} + database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) + ) + // Release all resources(including the states referenced by `stateAtBlock`) + // if error is returned. + defer func() { + if err != nil { + for _, ref := range refs { + database.TrieDB().Dereference(ref) + } + } + }() + for i := fromBlock.NumberU64() + 1; i <= toBlock.NumberU64(); i++ { + // Print progress logs if long enough time elapsed + if time.Since(logged) > 8*time.Second { + logged = time.Now() + log.Info("Regenerating historical state", "block", i, "target", fromBlock.NumberU64(), "remaining", toBlock.NumberU64()-i, "elapsed", time.Since(start)) + } + // Retrieve the next block to regenerate and process it + block := eth.blockchain.GetBlockByNumber(i) + if block == nil { + return nil, nil, fmt.Errorf("block #%d not found", i) + } + _, _, _, err := eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + if err != nil { + return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) + } + // Finalize the state so any modifications are written to the trie + root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(block.Number())) + if err != nil { + return nil, nil, err + } + statedb, err := eth.blockchain.StateAt(root) + if err != nil { + return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + } + states = append(states, statedb.Copy()) + + // Reference the trie twice, once for us, once for the tracer + database.TrieDB().Reference(root, common.Hash{}) + database.TrieDB().Reference(root, common.Hash{}) + refs = append(refs, root) + + // Dereference all past tries we ourselves are done working with + if parent != (common.Hash{}) { + database.TrieDB().Dereference(parent) + } + parent = root + } + // release is handler to release all states referenced, including + // the one referenced in `stateAtBlock`. + release = func() { + for _, ref := range refs { + database.TrieDB().Dereference(ref) + } + } + return states, release, nil +} + +// stateAtTransaction returns the execution environment of a certain transaction. +func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + // Short circuit if it's genesis block. + if block.NumberU64() == 0 { + return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") + } + // Create the parent state database + parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + } + statedb, release, err := eth.stateAtBlock(parent, reexec) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, release, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(eth.blockchain.Config(), block.Number()) + for idx, tx := range block.Transactions() { + // Assemble the transaction call message and return if the requested offset + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) + if idx == txIndex { + return msg, context, statedb, release, nil + } + // Not yet the searched for transaction, execute on top of the current state + vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + release() + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + // Ensure any modifications are committed to the state + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + release() + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} diff --git a/eth/api_tracer.go b/eth/tracers/api.go similarity index 55% rename from eth/api_tracer.go rename to eth/tracers/api.go index 5dffb2a464..dca990ab95 100644 --- a/eth/api_tracer.go +++ b/eth/tracers/api.go @@ -1,4 +1,4 @@ -// Copyright 2017 The go-ethereum Authors +// Copyright 2021 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package eth +package tracers import ( "bufio" @@ -30,18 +30,18 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" ) const ( @@ -55,6 +55,105 @@ const ( defaultTraceReexec = uint64(128) ) +// Backend interface provides the common API services (that are provided by +// both full and light clients) with access to necessary functions. +type Backend interface { + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) + RPCGasCap() uint64 + ChainConfig() *params.ChainConfig + Engine() consensus.Engine + ChainDb() ethdb.Database + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) + StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) + StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) +} + +// API is the collection of tracing APIs exposed over the private debugging endpoint. +type API struct { + backend Backend +} + +// NewAPI creates a new API definition for the tracing methods of the Ethereum service. +func NewAPI(backend Backend) *API { + return &API{backend: backend} +} + +type chainContext struct { + api *API + ctx context.Context +} + +func (context *chainContext) Engine() consensus.Engine { + return context.api.backend.Engine() +} + +func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { + header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) + if err != nil { + return nil + } + if header.Hash() == hash { + return header + } + header, err = context.api.backend.HeaderByHash(context.ctx, hash) + if err != nil { + return nil + } + return header +} + +// chainContext construts the context reader which is used by the evm for reading +// the necessary chain context. +func (api *API) chainContext(ctx context.Context) core.ChainContext { + return &chainContext{api: api, ctx: ctx} +} + +// blockByNumber is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + block, err := api.backend.BlockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block #%d not found", number) + } + return block, nil +} + +// blockByHash is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + block, err := api.backend.BlockByHash(ctx, hash) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block %s not found", hash.Hex()) + } + return block, nil +} + +// blockByNumberAndHash is the wrapper of the chain access function offered by +// the backend. It will return an error if the block is not found. +// +// Note this function is friendly for the light client which can only retrieve the +// historical(before the CHT) header/block by number. +func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block.Hash() == hash { + return block, nil + } + return api.blockByHash(ctx, hash) +} + // TraceConfig holds extra parameters to trace functions. type TraceConfig struct { *vm.LogConfig @@ -81,7 +180,6 @@ type txTraceResult struct { type blockTraceTask struct { statedb *state.StateDB // Intermediate state prepped for tracing block *types.Block // Block to trace the transactions from - rootref common.Hash // Trie root reference held for this task results []*txTraceResult // Trace results procudes by the task } @@ -102,32 +200,14 @@ type txTraceTask struct { // TraceChain returns the structured logs created during the execution of EVM // between two blocks (excluding start) and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { - // Fetch the block interval that we want to trace - var from, to *types.Block - - switch start { - case rpc.PendingBlockNumber: - from = api.eth.miner.PendingBlock() - case rpc.LatestBlockNumber: - from = api.eth.blockchain.CurrentBlock() - default: - from = api.eth.blockchain.GetBlockByNumber(uint64(start)) - } - switch end { - case rpc.PendingBlockNumber: - to = api.eth.miner.PendingBlock() - case rpc.LatestBlockNumber: - to = api.eth.blockchain.CurrentBlock() - default: - to = api.eth.blockchain.GetBlockByNumber(uint64(end)) - } - // Trace the chain if we've found all our blocks - if from == nil { - return nil, fmt.Errorf("starting block #%d not found", start) +func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace + from, err := api.blockByNumber(ctx, start) + if err != nil { + return nil, err } - if to == nil { - return nil, fmt.Errorf("end block #%d not found", end) + to, err := api.blockByNumber(ctx, end) + if err != nil { + return nil, err } if from.Number().Cmp(to.Number()) >= 0 { return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start) @@ -138,7 +218,7 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block // traceChain configures a new tracer according to the provided configuration, and // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requested tracer. -func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { +func (api *API) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { // Tracing a chain is a **long** operation, only do with subscriptions notifier, supported := rpc.NotifierFromContext(ctx) if !supported { @@ -146,46 +226,25 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } sub := notifier.CreateSubscription() - // Ensure we have a valid starting state before doing any work - origin := start.NumberU64() - database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) - - if number := start.NumberU64(); number > 0 { - start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) - if start == nil { - return nil, fmt.Errorf("parent block #%d not found", number-1) - } + // Shift the border to a block ahead in order to get the states + // before these blocks. + endBlock, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(end.NumberU64()-1), end.ParentHash()) + if err != nil { + return nil, err + } + // Prepare all the states for tracing. Note this procedure can take very + // long time. Timeout mechanism is necessary. + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec } - statedb, err := state.New(start.Root(), database, nil) + states, release, err := api.backend.StatesInRange(ctx, start, endBlock, reexec) if err != nil { - // If the starting state is missing, allow some number of blocks to be reexecuted - reexec := defaultTraceReexec - if config != nil && config.Reexec != nil { - reexec = *config.Reexec - } - // Find the most recent block that has the state available - for i := uint64(0); i < reexec; i++ { - start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) - if start == nil { - break - } - if statedb, err = state.New(start.Root(), database, nil); err == nil { - break - } - } - // If we still don't have the state available, bail out - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, errors.New("required historical state unavailable") - default: - return nil, err - } - } + return nil, err } - // Execute all the transaction contained within the chain concurrently for each block - blocks := int(end.NumberU64() - origin) + defer release() // Release all the resources in the last step. + blocks := int(end.NumberU64() - start.NumberU64()) threads := runtime.NumCPU() if threads > blocks { threads = blocks @@ -202,8 +261,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Fetch and execute the next block trace tasks for task := range tasks { - signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number()) - blockCtx := core.NewEVMBlockContext(task.block.Header(), api.eth.blockchain, nil) + signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) + blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) @@ -214,7 +273,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl break } // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number())) + task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number())) task.results[i] = &txTraceResult{Result: res} } // Stream the result back to the user or abort on teardown @@ -235,7 +294,6 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl number uint64 traced uint64 failed error - proot common.Hash ) // Ensure everything is properly cleaned up on any exit path defer func() { @@ -262,60 +320,23 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } // Print progress logs if long enough time elapsed if time.Since(logged) > 8*time.Second { - if number > origin { - nodes, imgs := database.TrieDB().Size() - log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", nodes+imgs) - } else { - log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin)) - } logged = time.Now() + log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) } // Retrieve the next block to trace - block := api.eth.blockchain.GetBlockByNumber(number) - if block == nil { - failed = fmt.Errorf("block #%d not found", number) - break - } - // Send the block over to the concurrent tracers (if not in the fast-forward phase) - if number > origin { - txs := block.Transactions() - - select { - case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}: - case <-notifier.Closed(): - return - } - traced += uint64(len(txs)) - } - // Generate the next state snapshot fast without tracing - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + block, err := api.blockByNumber(ctx, rpc.BlockNumber(number)) if err != nil { failed = err break } - // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) - if err != nil { - failed = err - break - } - statedb, err = state.New(root, database, nil) - if err != nil { - failed = err - break - } - // Reference the trie twice, once for us, once for the tracer - database.TrieDB().Reference(root, common.Hash{}) - if number >= origin { - database.TrieDB().Reference(root, common.Hash{}) - } - // Dereference all past tries we ourselves are done working with - if proot != (common.Hash{}) { - database.TrieDB().Dereference(proot) + // Send the block over to the concurrent tracers (if not in the fast-forward phase) + txs := block.Transactions() + select { + case tasks <- &blockTraceTask{statedb: states[int(number-start.NumberU64()-1)], block: block, results: make([]*txTraceResult, len(txs))}: + case <-notifier.Closed(): + return } - proot = root - - // TODO(karalabe): Do we need the preimages? Won't they accumulate too much? + traced += uint64(len(txs)) } }() @@ -323,7 +344,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl go func() { var ( done = make(map[uint64]*blockTraceResult) - next = origin + 1 + next = start.NumberU64() + 1 ) for res := range results { // Queue up next received result @@ -334,9 +355,6 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl } done[uint64(result.Block)] = result - // Dereference any paret tries held in memory by this task - database.TrieDB().Dereference(res.rootref) - // Stream completed traces to the user, aborting on the first error for result, ok := done[next]; ok; result, ok = done[next] { if len(result.Traces) > 0 || next == end.NumberU64() { @@ -352,38 +370,27 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // TraceBlockByNumber returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { - // Fetch the block that we want to trace - var block *types.Block - - switch number { - case rpc.PendingBlockNumber: - block = api.eth.miner.PendingBlock() - case rpc.LatestBlockNumber: - block = api.eth.blockchain.CurrentBlock() - default: - block = api.eth.blockchain.GetBlockByNumber(uint64(number)) - } - // Trace the block if it was found - if block == nil { - return nil, fmt.Errorf("block #%d not found", number) +func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err } return api.traceBlock(ctx, block, config) } // TraceBlockByHash returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - block := api.eth.blockchain.GetBlockByHash(hash) - if block == nil { - return nil, fmt.Errorf("block %#x not found", hash) +func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err } return api.traceBlock(ctx, block, config) } // TraceBlock returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { block := new(types.Block) if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { return nil, fmt.Errorf("could not decode block: %v", err) @@ -393,7 +400,7 @@ func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config // TraceBlockFromFile returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { blob, err := ioutil.ReadFile(file) if err != nil { return nil, fmt.Errorf("could not read file: %v", err) @@ -404,8 +411,8 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, // TraceBadBlock returns the structured logs created during the execution of // EVM against a block pulled from the pool of bad ones and returns them as a JSON // object. -func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { +func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) { if block.Hash() == hash { return api.traceBlock(ctx, block, config) } @@ -416,10 +423,10 @@ func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, // StandardTraceBlockToFile dumps the structured logs created during the // execution of EVM to the local file system and returns a list of files // to the caller. -func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { - block := api.eth.blockchain.GetBlockByHash(hash) - if block == nil { - return nil, fmt.Errorf("block %#x not found", hash) +func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err } return api.standardTraceBlockToFile(ctx, block, config) } @@ -427,8 +434,8 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c // StandardTraceBadBlockToFile dumps the structured logs created during the // execution of EVM against a block pulled from the pool of bad ones to the // local file system and returns a list of files to the caller. -func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { - for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) { +func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { + for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) { if block.Hash() == hash { return api.standardTraceBlockToFile(ctx, block, config) } @@ -439,27 +446,27 @@ func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, has // traceBlock configures a new tracer according to the provided configuration, and // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requestd tracer. -func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { - // Create the parent state database - if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { - return nil, err +func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") } - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.computeStateDB(parent, reexec) + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec) if err != nil { return nil, err } + defer release() + // Execute all the transaction contained within the block concurrently var ( - signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) - + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) txs = block.Transactions() results = make([]*txTraceResult, len(txs)) @@ -470,7 +477,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, if threads > len(txs) { threads = len(txs) } - blockCtx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) for th := 0; th < threads; th++ { pend.Add(1) go func() { @@ -497,7 +504,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, msg, _ := tx.AsMessage(signer) txContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.backend.ChainConfig(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -519,29 +526,30 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // standardTraceBlockToFile configures a new tracer which uses standard JSON output, // and traces either a full block or an individual transaction. The return value will // be one filename per transaction traced. -func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { +func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { // If we're tracing a single transaction, make sure it's present if config != nil && config.TxHash != (common.Hash{}) { if !containsTx(block, config.TxHash) { return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) } } - // Create the parent state database - if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { - return nil, err + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") } - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.computeStateDB(parent, reexec) + statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec) if err != nil { return nil, err } + defer release() + // Retrieve the tracing configurations, or use default values var ( logConfig vm.LogConfig @@ -555,10 +563,10 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block // Execute transaction, either tracing all or just the requested one var ( - signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) dumps []string - chainConfig = api.eth.blockchain.Config() - vmctx = core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) + chainConfig = api.backend.ChainConfig() + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -645,139 +653,73 @@ func containsTx(block *types.Block, hash common.Hash) bool { return false } -// computeStateDB retrieves the state database associated with a certain block. -// If no state is locally available for the given block, a number of blocks are -// attempted to be reexecuted to generate the desired state. -func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) { - // If we have the state fully available, use that - statedb, err := api.eth.blockchain.StateAt(block.Root()) - if err == nil { - return statedb, nil - } - // Otherwise try to reexec blocks until we find a state or reach our limit - origin := block.NumberU64() - database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) - - for i := uint64(0); i < reexec; i++ { - block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if block == nil { - break - } - if statedb, err = state.New(block.Root(), database, nil); err == nil { - break - } - } - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) - default: - return nil, err - } - } - // State was available at historical point, regenerate - var ( - start = time.Now() - logged time.Time - proot common.Hash - ) - for block.NumberU64() < origin { - // Print progress logs if long enough time elapsed - if time.Since(logged) > 8*time.Second { - log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) - logged = time.Now() - } - // Retrieve the next block to regenerate and process it - if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { - return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) - } - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) - if err != nil { - return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) - } - // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) - if err != nil { - return nil, err - } - statedb, err = state.New(root, database, nil) - if err != nil { - return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) - } - database.TrieDB().Reference(root, common.Hash{}) - if proot != (common.Hash{}) { - database.TrieDB().Dereference(proot) - } - proot = root - } - nodes, imgs := database.TrieDB().Size() - log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) - return statedb, nil -} - // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - // Retrieve the transaction and assemble its EVM context - tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash) - if tx == nil { - return nil, fmt.Errorf("transaction %#x not found", hash) +func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { + _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + if err != nil { + return nil, err + } + // It shouldn't happen in practice. + if blockNumber == 0 { + return nil, errors.New("genesis is not traceable") } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - // Retrieve the block - block := api.eth.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, fmt.Errorf("block %#x not found", blockHash) + block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) + if err != nil { + return nil, err } - msg, vmctx, statedb, err := api.computeTxEnv(block, int(index), reexec) + msg, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) if err != nil { return nil, err } - // Trace the transaction and return + defer release() + return api.traceTx(ctx, msg, vmctx, statedb, config) } -// TraceCall lets you trace a given eth_call. It collects the structured logs created during the execution of EVM -// if the given transaction was added on top of the provided block and returns them as a JSON object. +// TraceCall lets you trace a given eth_call. It collects the structured logs +// created during the execution of EVM if the given transaction was added on +// top of the provided block and returns them as a JSON object. // You can provide -2 as a block number to trace on top of the pending block. -func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { - // First try to retrieve the state - statedb, header, err := api.eth.APIBackend.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) +func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { + // Try to retrieve the specified block + var ( + err error + block *types.Block + ) + if hash, ok := blockNrOrHash.Hash(); ok { + block, err = api.blockByHash(ctx, hash) + } else if number, ok := blockNrOrHash.Number(); ok { + block, err = api.blockByNumber(ctx, number) + } if err != nil { - // Try to retrieve the specified block - var block *types.Block - if hash, ok := blockNrOrHash.Hash(); ok { - block = api.eth.blockchain.GetBlockByHash(hash) - } else if number, ok := blockNrOrHash.Number(); ok { - block = api.eth.blockchain.GetBlockByNumber(uint64(number)) - } - if block == nil { - return nil, fmt.Errorf("block %v not found: %v", blockNrOrHash, err) - } - // try to recompute the state - reexec := defaultTraceReexec - if config != nil && config.Reexec != nil { - reexec = *config.Reexec - } - _, _, statedb, err = api.computeTxEnv(block, 0, reexec) - if err != nil { - return nil, err - } + return nil, err + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec } + statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec) + if err != nil { + return nil, err + } + defer release() // Execute the trace - msg := args.ToMessage(api.eth.APIBackend.RPCGasCap()) - vmctx := core.NewEVMBlockContext(header, api.eth.blockchain, nil) + msg := args.ToMessage(api.backend.RPCGasCap()) + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) return api.traceTx(ctx, msg, vmctx, statedb, config) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -794,14 +736,14 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } } // Constuct the JavaScript tracer to execute with - if tracer, err = tracers.New(*config.Tracer, txContext); err != nil { + if tracer, err = New(*config.Tracer, txContext); err != nil { return nil, err } // Handle timeouts and RPC cancellations deadlineCtx, cancel := context.WithTimeout(ctx, timeout) go func() { <-deadlineCtx.Done() - tracer.(*tracers.Tracer).Stop(errors.New("execution timeout")) + tracer.(*Tracer).Stop(errors.New("execution timeout")) }() defer cancel() @@ -812,7 +754,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer}) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { @@ -833,7 +775,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil - case *tracers.Tracer: + case *Tracer: return tracer.GetResult() default: @@ -841,41 +783,15 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } } -// computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { - // Create the parent state database - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) - } - statedb, err := api.computeStateDB(parent, reexec) - if err != nil { - return nil, vm.BlockContext{}, nil, err - } - - if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.BlockContext{}, statedb, nil - } - - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number()) - - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) - if idx == txIndex { - return msg, context, statedb, nil - } - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) +// APIs return the collection of RPC services the tracer package offers. +func APIs(backend Backend) []rpc.API { + // Append all the local APIs and return + return []rpc.API{ + { + Namespace: "debug", + Version: "1.0", + Service: NewAPI(backend), + Public: false, + }, } - return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go new file mode 100644 index 0000000000..688b983bab --- /dev/null +++ b/eth/tracers/api_test.go @@ -0,0 +1,487 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package tracers + +import ( + "bytes" + "context" + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "reflect" + "sort" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") + errTransactionNotFound = errors.New("transaction not found") +) + +type testBackend struct { + chainConfig *params.ChainConfig + engine consensus.Engine + chaindb ethdb.Database + chain *core.BlockChain +} + +func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + backend := &testBackend{ + chainConfig: params.TestChainConfig, + engine: ethash.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + } + // Generate blocks for testing + gspec.Config = backend.chainConfig + var ( + gendb = rawdb.NewMemoryDatabase() + genesis = gspec.MustCommit(gendb) + ) + blocks, _ := core.GenerateChain(backend.chainConfig, genesis, backend.engine, gendb, n, generator) + + // Import the canonical chain + gspec.MustCommit(backend.chaindb) + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 0, + TrieDirtyDisabled: true, // Archive mode + } + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, backend.chainConfig, backend.engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + backend.chain = chain + return backend +} + +func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return b.chain.GetHeaderByHash(hash), nil +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentHeader(), nil + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return b.chain.GetBlockByHash(hash), nil +} + +func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentBlock(), nil + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { + tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) + if tx == nil { + return nil, common.Hash{}, 0, 0, errTransactionNotFound + } + return tx, hash, blockNumber, index, nil +} + +func (b *testBackend) RPCGasCap() uint64 { + return 25000000 +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return b.chainConfig +} + +func (b *testBackend) Engine() consensus.Engine { + return b.engine +} + +func (b *testBackend) ChainDb() ethdb.Database { + return b.chaindb +} + +func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + statedb, err := b.chain.StateAt(block.Root()) + if err != nil { + return nil, nil, errStateNotFound + } + return statedb, func() {}, nil +} + +func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, nil, errBlockNotFound + } + statedb, err := b.chain.StateAt(parent.Root()) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, errStateNotFound + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, func() {}, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(b.chainConfig, block.Number()) + for idx, tx := range block.Transactions() { + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil) + if idx == txIndex { + return msg, context, statedb, func() {}, nil + } + vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} + +func (b *testBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + var result []*state.StateDB + for number := fromBlock.NumberU64(); number <= toBlock.NumberU64(); number += 1 { + block := b.chain.GetBlockByNumber(number) + if block == nil { + return nil, nil, errBlockNotFound + } + statedb, err := b.chain.StateAt(block.Root()) + if err != nil { + return nil, nil, errStateNotFound + } + result = append(result, statedb) + } + return result, func() {}, nil +} + +func TestTraceCall(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + call ethapi.CallArgs + config *TraceConfig + expectErr error + expect interface{} + }{ + // Standard JSON trace upon the genesis, plain transfer. + { + blockNumber: rpc.BlockNumber(0), + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the head, plain transfer. + { + blockNumber: rpc.BlockNumber(genBlocks), + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the non-existent block, error expects + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + expect: nil, + }, + // Standard JSON trace upon the latest block + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the pending block + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.CallArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !reflect.DeepEqual(err, testspec.expectErr) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + if !reflect.DeepEqual(result, testspec.expect) { + t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result) + } + } + } +} + +func TestTraceTransaction(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }} + target := common.Hash{} + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + target = tx.Hash() + })) + result, err := api.TraceTransaction(context.Background(), target, nil) + if err != nil { + t.Errorf("Failed to trace transaction %v", err) + } + if !reflect.DeepEqual(result, ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }) { + t.Error("Transaction tracing result is different") + } +} + +func TestTraceBlock(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *TraceConfig + expect interface{} + expectErr error + }{ + // Trace genesis block, expect error + { + blockNumber: rpc.BlockNumber(0), + config: nil, + expect: nil, + expectErr: errors.New("genesis is not traceable"), + }, + // Trace head block + { + blockNumber: rpc.BlockNumber(genBlocks), + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + // Trace non-existent block + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + expect: nil, + }, + // Trace latest block + { + blockNumber: rpc.LatestBlockNumber, + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + // Trace pending block + { + blockNumber: rpc.PendingBlockNumber, + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceBlockByNumber(context.Background(), testspec.blockNumber, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !reflect.DeepEqual(err, testspec.expectErr) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + if !reflect.DeepEqual(result, testspec.expect) { + t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result) + } + } + } +} + +type Account struct { + key *ecdsa.PrivateKey + addr common.Address +} + +type Accounts []Account + +func (a Accounts) Len() int { return len(a) } +func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } + +func newAccounts(n int) (accounts Accounts) { + for i := 0; i < n; i++ { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + accounts = append(accounts, Account{key: key, addr: addr}) + } + sort.Sort(accounts) + return accounts +} diff --git a/les/api_backend.go b/les/api_backend.go index 9fbc21f459..0839614901 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -292,3 +292,15 @@ func (b *LesApiBackend) Engine() consensus.Engine { func (b *LesApiBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } + +func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + return b.eth.stateAtBlock(ctx, block, reexec) +} + +func (b *LesApiBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + return b.eth.statesInRange(ctx, fromBlock, toBlock, reexec) +} + +func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) +} diff --git a/les/state_accessor.go b/les/state_accessor.go new file mode 100644 index 0000000000..3c9143c875 --- /dev/null +++ b/les/state_accessor.go @@ -0,0 +1,88 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/light" +) + +// stateAtBlock retrieves the state database associated with a certain block. +func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { + return light.NewState(ctx, block.Header(), leth.odr), func() {}, nil +} + +// statesInRange retrieves a batch of state databases associated with the specific +// block ranges. +func (leth *LightEthereum) statesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { + var states []*state.StateDB + for number := fromBlock.NumberU64(); number <= toBlock.NumberU64(); number++ { + header, err := leth.blockchain.GetHeaderByNumberOdr(ctx, number) + if err != nil { + return nil, nil, err + } + states = append(states, light.NewState(ctx, header, leth.odr)) + } + return states, nil, nil +} + +// stateAtTransaction returns the execution environment of a certain transaction. +func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { + // Short circuit if it's genesis block. + if block.NumberU64() == 0 { + return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") + } + // Create the parent state database + parent, err := leth.blockchain.GetBlock(ctx, block.ParentHash(), block.NumberU64()-1) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + statedb, _, err := leth.stateAtBlock(ctx, parent, reexec) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, func() {}, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(leth.blockchain.Config(), block.Number()) + for idx, tx := range block.Transactions() { + // Assemble the transaction call message and return if the requested offset + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) + if idx == txIndex { + return msg, context, statedb, func() {}, nil + } + // Not yet the searched for transaction, execute on top of the current state + vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + // Ensure any modifications are committed to the state + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} From d2779ed7acde5d0fa3ab53fcdc11ab1697703300 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 25 Jan 2021 19:06:52 +0100 Subject: [PATCH 283/948] eth, p2p: reserve half peer slots for snap peers during snap sync (#22171) * eth, p2p: reserve half peer slots for snap peers during snap sync * eth: less logging * eth: rework the eth/snap peer reservation logic * eth: rework the eth/snap peer reservation logic (again) --- eth/handler.go | 15 +++++++++++++-- eth/peerset.go | 11 ++++++++++- eth/sync.go | 4 ++++ p2p/peer.go | 10 ++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index f6366d9af1..5ae0925bb5 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -250,9 +250,20 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { peer.Log().Debug("Ethereum handshake failed", "err", err) return err } + reject := false // reserved peer slots + if atomic.LoadUint32(&h.snapSync) == 1 && !peer.SupportsCap("snap", 1) { + // If we are running snap-sync, we want to reserve roughly half the peer + // slots for peers supporting the snap protocol. + // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. + if all, snp := h.peers.Len(), h.peers.SnapLen(); all-snp > snp+5 { + reject = true + } + } // Ignore maxPeers if this is a trusted peer - if h.peers.Len() >= h.maxPeers && !peer.Peer.Info().Network.Trusted { - return p2p.DiscTooManyPeers + if !peer.Peer.Info().Network.Trusted { + if reject || h.peers.Len() >= h.maxPeers { + return p2p.DiscTooManyPeers + } } peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) diff --git a/eth/peerset.go b/eth/peerset.go index bf5785ff3f..663c5ce36b 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -259,7 +259,7 @@ func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { } // Len returns if the current number of `eth` peers in the set. Since the `snap` -// peers are tied to the existnce of an `eth` connection, that will always be a +// peers are tied to the existence of an `eth` connection, that will always be a // subset of `eth`. func (ps *peerSet) Len() int { ps.lock.RLock() @@ -268,6 +268,15 @@ func (ps *peerSet) Len() int { return len(ps.ethPeers) } +// SnapLen returns if the current number of `snap` peers in the set. Since the `snap` +// peers are tied to the existence of an `eth` connection, that will always be a +// subset of `eth`. +func (ps *peerSet) SnapLen() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + return len(ps.snapPeers) +} + // ethPeerWithHighestTD retrieves the known peer with the currently highest total // difficulty. func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { diff --git a/eth/sync.go b/eth/sync.go index 03a5165245..eedb8b7476 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -329,6 +329,10 @@ func (h *handler) doSync(op *chainSyncOp) error { log.Info("Fast sync complete, auto disabling") atomic.StoreUint32(&h.fastSync, 0) } + if atomic.LoadUint32(&h.snapSync) == 1 { + log.Info("Snap sync complete, auto disabling") + atomic.StoreUint32(&h.snapSync, 0) + } // If we've successfully finished a sync cycle and passed any required checkpoint, // enable accepting transactions from the network. head := h.chain.CurrentBlock() diff --git a/p2p/peer.go b/p2p/peer.go index a9c3cf01da..43ccef5c43 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -158,6 +158,16 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } +// SupportsCap returns true if the peer supports the given protocol/version +func (p *Peer) SupportsCap(protocol string, version uint) bool { + for _, cap := range p.rw.caps { + if cap.Name == protocol { + return version <= cap.Version + } + } + return false +} + // RemoteAddr returns the remote address of the network connection. func (p *Peer) RemoteAddr() net.Addr { return p.rw.fd.RemoteAddr() From 7202b410b064c17c0648c4c6c212dc4c2a787907 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 25 Jan 2021 21:40:14 +0100 Subject: [PATCH 284/948] tests/fuzzers/abi: fixed one-off panic with int.Min64 value (#22233) * tests/fuzzers/abi: fixed one-off panic with int.Min64 value * tests/fuzzers/abi: fixed one-off panic with int.Min64 value --- tests/fuzzers/abi/abifuzzer.go | 5 ++++- tests/fuzzers/abi/abifuzzer_test.go | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go index 8c083b371e..60233d158a 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/tests/fuzzers/abi/abifuzzer.go @@ -161,7 +161,10 @@ func getUInt(fuzzer *fuzz.Fuzzer) int { var i int fuzzer.Fuzz(&i) if i < 0 { - i *= -1 + i = -i + if i < 0 { + return 0 + } } return i } diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go index c59c45ab1a..423a3cd232 100644 --- a/tests/fuzzers/abi/abifuzzer_test.go +++ b/tests/fuzzers/abi/abifuzzer_test.go @@ -23,9 +23,7 @@ import ( // TestReplicate can be used to replicate crashers from the fuzzing tests. // Just replace testString with the data in .quoted func TestReplicate(t *testing.T) { - testString := "N\xef\xbf0\xef\xbf99000000000000" + - "000000000000" - + testString := "\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00" data := []byte(testString) runFuzzer(data) } From 573f373d2bb264af6a2e39c3b219c999fc242122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 26 Jan 2021 13:13:55 +0200 Subject: [PATCH 285/948] internal/ethapi: print tx details when submitting (#22170) This adds more info about submitted transactions in log messages. Co-authored-by: Felix Lange --- internal/ethapi/api.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b424435b50..9c3f6b9161 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1558,16 +1558,18 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } + // Print a log with full tx details for manual investigations and interventions + signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) + from, err := types.Sender(signer, tx) + if err != nil { + return common.Hash{}, err + } + if tx.To() == nil { - signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) - from, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } addr := crypto.CreateAddress(from, tx.Nonce()) - log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) + log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value()) } else { - log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To()) + log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value()) } return tx.Hash(), nil } From 14d495491ddf31464135563720705fc2c1e5eb22 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 26 Jan 2021 12:15:31 +0100 Subject: [PATCH 286/948] core/state: fix panic in state dumping (#22225) --- core/state/dump.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/dump.go b/core/state/dump.go index 9bb946d14b..b25da714fd 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -138,7 +138,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, account.SecureKey = it.Key } addr := common.BytesToAddress(addrBytes) - obj := newObject(nil, addr, data) + obj := newObject(s, addr, data) if !excludeCode { account.Code = common.Bytes2Hex(obj.Code(s.db)) } From 681618275cc5a4819af446029a064d266190ae8c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 26 Jan 2021 12:17:11 +0100 Subject: [PATCH 287/948] core: speed up header import (#21967) This PR implements the following modifications - Don't shortcut check if block is present, thus avoid disk lookup - Don't check hash ancestry in early-check (it's still done in parallel checker) - Don't check time.Now for every single header Charts and background info can be found here: https://github.com/holiman/headerimport/blob/main/README.md With these changes, writing 1M headers goes down to from 80s to 62s. --- consensus/ethash/consensus.go | 36 +++++++++++++++++------------------ core/headerchain.go | 11 ++++++----- core/rawdb/freezer_table.go | 5 +++++ 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index ae0905ee3a..c58fe7a530 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -39,11 +39,11 @@ import ( // Ethash proof-of-work protocol constants. var ( - FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block - ByzantiumBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium - ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople - maxUncles = 2 // Maximum number of uncles allowed in a single block - allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks + FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block + ByzantiumBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium + ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople + maxUncles = 2 // Maximum number of uncles allowed in a single block + allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks // calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384. // It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks. @@ -102,7 +102,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *ty return consensus.ErrUnknownAncestor } // Sanity checks passed, do a proper verification - return ethash.verifyHeader(chain, header, parent, false, seal) + return ethash.verifyHeader(chain, header, parent, false, seal, time.Now().Unix()) } // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers @@ -126,15 +126,16 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ // Create a task channel and spawn the verifiers var ( - inputs = make(chan int) - done = make(chan int, workers) - errors = make([]error, len(headers)) - abort = make(chan struct{}) + inputs = make(chan int) + done = make(chan int, workers) + errors = make([]error, len(headers)) + abort = make(chan struct{}) + unixNow = time.Now().Unix() ) for i := 0; i < workers; i++ { go func() { for index := range inputs { - errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index) + errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index, unixNow) done <- index } }() @@ -170,7 +171,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ return abort, errorsOut } -func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int) error { +func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int, unixNow int64) error { var parent *types.Header if index == 0 { parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) @@ -180,10 +181,7 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, head if parent == nil { return consensus.ErrUnknownAncestor } - if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil { - return nil // known block - } - return ethash.verifyHeader(chain, headers[index], parent, false, seals[index]) + return ethash.verifyHeader(chain, headers[index], parent, false, seals[index], unixNow) } // VerifyUncles verifies that the given block's uncles conform to the consensus @@ -234,7 +232,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() { return errDanglingUncle } - if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil { + if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true, time.Now().Unix()); err != nil { return err } } @@ -244,14 +242,14 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo // verifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum ethash engine. // See YP section 4.3.4. "Block Header Validity" -func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool) error { +func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool, unixNow int64) error { // Ensure that the header's extra-data section is of a reasonable size if uint64(len(header.Extra)) > params.MaximumExtraDataSize { return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) } // Verify the header's timestamp if !uncle { - if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) { + if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) { return consensus.ErrFutureBlock } } diff --git a/core/headerchain.go b/core/headerchain.go index dc354549c0..dcd3644cd1 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -299,17 +299,18 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWrit func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) { // Do a sanity check that the provided chain is actually ordered and linked for i := 1; i < len(chain); i++ { - parentHash := chain[i-1].Hash() - if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != parentHash { + if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 { + hash := chain[i].Hash() + parentHash := chain[i-1].Hash() // Chain broke ancestry, log a message (programming error) and skip insertion - log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(), + log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", hash, "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash) return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number, - parentHash.Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4]) + parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4]) } // If the header is a banned one, straight out abort - if BadHashes[parentHash] { + if BadHashes[chain[i].ParentHash] { return i - 1, ErrBlacklistedHash } // If it's the last header in the cunk, we need to check it too diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index b9d8a274a8..cd273222b1 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -103,6 +103,11 @@ type freezerTable struct { lock sync.RWMutex // Mutex protecting the data file descriptors } +// NewFreezerTable opens the given path as a freezer table. +func NewFreezerTable(path, name string, disableSnappy bool) (*freezerTable, error) { + return newTable(path, name, metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, disableSnappy) +} + // newTable opens a freezer table with default settings - 2G files func newTable(path string, name string, readMeter metrics.Meter, writeMeter metrics.Meter, sizeGauge metrics.Gauge, disableSnappy bool) (*freezerTable, error) { return newCustomTable(path, name, readMeter, writeMeter, sizeGauge, 2*1000*1000*1000, disableSnappy) From ad038b62899ae59160f9871573ec6786e95cb918 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 26 Jan 2021 16:01:13 +0100 Subject: [PATCH 288/948] accounts/scwallet: use go-ethereum crypto instead of go-ecdh (#22212) * accounts/scwallet: use go-ethereum crypto instead of go-ecdh github.com/wsddn/go-ecdh is a wrapper package for ECDH functionality with any elliptic curve. Since 'generic' ECDH is not required in accounts/scwallet (the curve is always secp256k1), we can just use the standard library functionality and our own crypto libraries to perform ECDH and save a dependency. * Update accounts/scwallet/securechannel.go Co-authored-by: Guillaume Ballet * Use the correct key Co-authored-by: Guillaume Ballet --- accounts/scwallet/securechannel.go | 21 +++++++-------------- go.mod | 1 - go.sum | 2 -- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go index 9b70c69dcc..10887a8b43 100644 --- a/accounts/scwallet/securechannel.go +++ b/accounts/scwallet/securechannel.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/aes" "crypto/cipher" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/sha512" @@ -27,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" pcsc "github.com/gballet/go-libpcsclite" - "github.com/wsddn/go-ecdh" "golang.org/x/crypto/pbkdf2" "golang.org/x/text/unicode/norm" ) @@ -63,26 +63,19 @@ type SecureChannelSession struct { // NewSecureChannelSession creates a new secure channel for the given card and public key. func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) { // Generate an ECDSA keypair for ourselves - gen := ecdh.NewEllipticECDH(crypto.S256()) - private, public, err := gen.GenerateKey(rand.Reader) + key, err := crypto.GenerateKey() if err != nil { return nil, err } - - cardPublic, ok := gen.Unmarshal(keyData) - if !ok { - return nil, fmt.Errorf("could not unmarshal public key from card") - } - - secret, err := gen.GenerateSharedSecret(private, cardPublic) + cardPublic, err := crypto.UnmarshalPubkey(keyData) if err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal public key from card: %v", err) } - + secret, _ := key.Curve.ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes()) return &SecureChannelSession{ card: card, - secret: secret, - publicKey: gen.Marshal(public), + secret: secret.Bytes(), + publicKey: elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y), }, nil } diff --git a/go.mod b/go.mod index ffea94f6cf..48c6889fbe 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef - github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 diff --git a/go.sum b/go.sum index fefe5afb14..4b799d77fa 100644 --- a/go.sum +++ b/go.sum @@ -365,8 +365,6 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= From 9c5729311e89bff5acc1f33c1f4a646dfcce7dab Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 26 Jan 2021 15:43:12 +0000 Subject: [PATCH 289/948] accounts/scwallet: update documentation (#22242) --- accounts/scwallet/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md index cfca916b3a..4313d9c6b2 100644 --- a/accounts/scwallet/README.md +++ b/accounts/scwallet/README.md @@ -31,12 +31,16 @@ Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet: ``` - > personal.openWallet("keycard://044def09") - Please enter the pairing password: + > personal.openWallet("keycard://044def09", "pairing password") ``` - Enter the pairing password that you have received during card initialization. Same with the PIN that you will subsequently be - asked for. + The pairing password has been generated during the card initialization process. + + The process needs to be repeated once more with the PIN: + + ``` + > personal.openWallet("keycard://044def09", "PIN number") + ``` If everything goes well, you should see your new account when typing `personal` on the console: From a72fa88a0d661f86a06d3d89c755a4e7dcff1e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 26 Jan 2021 21:41:35 +0100 Subject: [PATCH 290/948] les: switch to new discv5 (#21940) This PR enables running the new discv5 protocol in both LES client and server mode. In client mode it mixes discv5 and dnsdisc iterators (if both are enabled) and filters incoming ENRs for "les" tag and fork ID. The old p2p/discv5 package and all references to it are removed. Co-authored-by: Felix Lange --- cmd/bootnode/main.go | 15 +- cmd/faucet/faucet.go | 7 +- cmd/utils/flags.go | 15 +- les/client.go | 13 +- les/commons.go | 12 - les/enr_entry.go | 47 +- les/server.go | 21 - les/serverpool.go | 12 +- les/serverpool_test.go | 3 +- mobile/discover.go | 12 +- mobile/params.go | 10 +- p2p/discv5/README | 4 - p2p/discv5/database.go | 396 ---------- p2p/discv5/database_test.go | 380 --------- p2p/discv5/metrics.go | 24 - p2p/discv5/net.go | 1269 ------------------------------- p2p/discv5/net_test.go | 330 -------- p2p/discv5/node.go | 413 ---------- p2p/discv5/node_test.go | 305 -------- p2p/discv5/nodeevent_string.go | 17 - p2p/discv5/sim_run_test.go | 126 --- p2p/discv5/sim_test.go | 432 ----------- p2p/discv5/sim_testmain_test.go | 43 -- p2p/discv5/table.go | 318 -------- p2p/discv5/table_test.go | 238 ------ p2p/discv5/ticket.go | 884 --------------------- p2p/discv5/topic.go | 407 ---------- p2p/discv5/topic_test.go | 71 -- p2p/discv5/udp.go | 429 ----------- p2p/server.go | 26 +- params/bootnodes.go | 18 + 31 files changed, 113 insertions(+), 6184 deletions(-) delete mode 100644 p2p/discv5/README delete mode 100644 p2p/discv5/database.go delete mode 100644 p2p/discv5/database_test.go delete mode 100644 p2p/discv5/metrics.go delete mode 100644 p2p/discv5/net.go delete mode 100644 p2p/discv5/net_test.go delete mode 100644 p2p/discv5/node.go delete mode 100644 p2p/discv5/node_test.go delete mode 100644 p2p/discv5/nodeevent_string.go delete mode 100644 p2p/discv5/sim_run_test.go delete mode 100644 p2p/discv5/sim_test.go delete mode 100644 p2p/discv5/sim_testmain_test.go delete mode 100644 p2p/discv5/table.go delete mode 100644 p2p/discv5/table_test.go delete mode 100644 p2p/discv5/ticket.go delete mode 100644 p2p/discv5/topic.go delete mode 100644 p2p/discv5/topic_test.go delete mode 100644 p2p/discv5/udp.go diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 6c9ff615a1..036b968ef8 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -121,17 +120,17 @@ func main() { printNotice(&nodeKey.PublicKey, *realaddr) + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) + cfg := discover.Config{ + PrivateKey: nodeKey, + NetRestrict: restrictList, + } if *runv5 { - if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { + if _, err := discover.ListenV5(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } } else { - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, nodeKey) - cfg := discover.Config{ - PrivateKey: nodeKey, - NetRestrict: restrictList, - } if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index b9c4e1819a..763b8d25f8 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -55,7 +55,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" @@ -154,9 +153,9 @@ func main() { log.Crit("Failed to parse genesis block json", "err", err) } // Convert the bootnodes to internal enode representations - var enodes []*discv5.Node + var enodes []*enode.Node for _, boot := range strings.Split(*bootFlag, ",") { - if url, err := discv5.ParseNode(boot); err == nil { + if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil { enodes = append(enodes, url) } else { log.Error("Failed to parse bootnode URL", "url", boot, "err", err) @@ -228,7 +227,7 @@ type wsConn struct { wlock sync.Mutex } -func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { +func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ Name: "geth", diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 954b5de2c2..1d6d2d86b6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -59,7 +59,6 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -842,7 +841,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { // setBootstrapNodesV5 creates a list of bootstrap nodes from the command line // flags, reverting to pre-configured ones if none have been specified. func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { - urls := params.MainnetBootnodes + urls := params.V5Bootnodes switch { case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name): if ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name) { @@ -850,22 +849,14 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { } else { urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } - case ctx.GlobalBool(RopstenFlag.Name): - urls = params.RopstenBootnodes - case ctx.GlobalBool(RinkebyFlag.Name): - urls = params.RinkebyBootnodes - case ctx.GlobalBool(GoerliFlag.Name): - urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV2Flag.Name): - urls = params.YoloV2Bootnodes case cfg.BootstrapNodesV5 != nil: return // already set, don't apply defaults. } - cfg.BootstrapNodesV5 = make([]*discv5.Node, 0, len(urls)) + cfg.BootstrapNodesV5 = make([]*enode.Node, 0, len(urls)) for _, url := range urls { if url != "" { - node, err := discv5.ParseNode(url) + node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { log.Error("Bootstrap URL invalid", "enode", url, "err", err) continue diff --git a/les/client.go b/les/client.go index 5ee50a7c3a..d8cb6e385a 100644 --- a/les/client.go +++ b/les/client.go @@ -72,6 +72,7 @@ type LightEthereum struct { netRPCService *ethapi.PublicNetAPI p2pServer *p2p.Server + p2pConfig *p2p.Config } // New creates an instance of the light client. @@ -109,14 +110,11 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), + p2pConfig: &stack.Config().P2P, } peers.subscribe((*vtSubscription)(leth.valueTracker)) - dnsdisc, err := leth.setupDiscovery() - if err != nil { - return nil, err - } - leth.serverPool = newServerPool(lespayDb, []byte("serverpool:"), leth.valueTracker, dnsdisc, time.Second, nil, &mclock.System{}, config.UltraLightServers) + leth.serverPool = newServerPool(lespayDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) peers.subscribe(leth.serverPool) leth.dialCandidates = leth.serverPool.dialIterator @@ -299,6 +297,11 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { func (s *LightEthereum) Start() error { log.Warn("Light client mode is an experimental feature") + discovery, err := s.setupDiscovery(s.p2pConfig) + if err != nil { + return err + } + s.serverPool.addSource(discovery) s.serverPool.start() // Start bloom request workers. s.wg.Add(bloomServiceThreads) diff --git a/les/commons.go b/les/commons.go index 8de1057d26..73334497ad 100644 --- a/les/commons.go +++ b/les/commons.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" ) @@ -42,17 +41,6 @@ func errResp(code errCode, format string, v ...interface{}) error { return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) } -func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic { - var name string - switch protocolVersion { - case lpv2: - name = "LES2" - default: - panic(nil) - } - return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8])) -} - type chainReader interface { CurrentHeader() *types.Header } diff --git a/les/enr_entry.go b/les/enr_entry.go index a357f689df..1e56c1f175 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -17,6 +17,8 @@ package les import ( + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -25,19 +27,46 @@ import ( // lesEntry is the "les" ENR entry. This is set for LES servers only. type lesEntry struct { // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` + _ []rlp.RawValue `rlp:"tail"` } -// ENRKey implements enr.Entry. -func (e lesEntry) ENRKey() string { - return "les" +func (lesEntry) ENRKey() string { return "les" } + +// ethEntry is the "eth" ENR entry. This is redeclared here to avoid depending on package eth. +type ethEntry struct { + ForkID forkid.ID + _ []rlp.RawValue `rlp:"tail"` } +func (ethEntry) ENRKey() string { return "eth" } + // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { - if len(eth.config.EthDiscoveryURLs) == 0 { - return nil, nil +func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { + it := enode.NewFairMix(0) + + // Enable DNS discovery. + if len(eth.config.EthDiscoveryURLs) != 0 { + client := dnsdisc.NewClient(dnsdisc.Config{}) + dns, err := client.NewIterator(eth.config.EthDiscoveryURLs...) + if err != nil { + return nil, err + } + it.AddSource(dns) + } + + // Enable DHT. + if cfg.DiscoveryV5 && eth.p2pServer.DiscV5 != nil { + it.AddSource(eth.p2pServer.DiscV5.RandomNodes()) } - client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(eth.config.EthDiscoveryURLs...) + + forkFilter := forkid.NewFilter(eth.blockchain) + iterator := enode.Filter(it, func(n *enode.Node) bool { return nodeIsServer(forkFilter, n) }) + return iterator, nil +} + +// nodeIsServer checks whether n is an LES server node. +func nodeIsServer(forkFilter forkid.Filter, n *enode.Node) bool { + var les lesEntry + var eth ethEntry + return n.Load(&les) == nil && n.Load(ð) == nil && forkFilter(eth.ForkID) == nil } diff --git a/les/server.go b/les/server.go index cbedce136c..6b12b6f8f3 100644 --- a/les/server.go +++ b/les/server.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" @@ -58,7 +57,6 @@ type LesServer struct { archiveMode bool // Flag whether the ethereum node runs in archive mode. handler *serverHandler broadcaster *broadcaster - lesTopics []discv5.Topic privateKey *ecdsa.PrivateKey // Flow control and capacity management @@ -77,11 +75,6 @@ type LesServer struct { func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) - // Collect les protocol version information supported by local node. - lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) - for i, pv := range AdvertiseProtocolVersions { - lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv) - } // Calculate the number of threads used to service the light client // requests based on the user-specified value. threads := config.LightServ * 4 / 100 @@ -103,7 +96,6 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer ns: ns, archiveMode: e.ArchiveMode(), broadcaster: newBroadcaster(ns), - lesTopics: lesTopics, fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), threadsBusy: config.LightServ/100 + 1, @@ -203,19 +195,6 @@ func (s *LesServer) Start() error { s.wg.Add(1) go s.capacityManagement() - if s.p2pSrv.DiscV5 != nil { - for _, topic := range s.lesTopics { - topic := topic - go func() { - logger := log.New("topic", topic) - logger.Info("Starting topic registration") - defer logger.Info("Terminated topic registration") - - s.p2pSrv.DiscV5.RegisterTopic(topic, s.closeCh) - }() - } - } - return nil } diff --git a/les/serverpool.go b/les/serverpool.go index 6cf4affff0..ac87acf6da 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -131,7 +131,7 @@ var ( ) // newServerPool creates a new server pool -func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, discovery enode.Iterator, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { +func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { s := &serverPool{ db: db, clock: clock, @@ -147,9 +147,6 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, d alwaysConnect := lpc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) - if discovery != nil { - s.mixSources = append(s.mixSources, discovery) - } iter := enode.Iterator(s.mixer) if query != nil { @@ -175,6 +172,13 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, d return s } +// addSource adds a node discovery source to the server pool (should be called before start) +func (s *serverPool) addSource(source enode.Iterator) { + if source != nil { + s.mixSources = append(s.mixSources, source) + } +} + // addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. diff --git a/les/serverpool_test.go b/les/serverpool_test.go index 70a41b74c6..3b7ae65d5d 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -145,7 +145,8 @@ func (s *serverPoolTest) start() { } s.vt = lpc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) - s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, s.input, 0, testQuery, s.clock, s.trusted) + s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted) + s.sp.addSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } s.disconnect = make(map[int][]int) diff --git a/mobile/discover.go b/mobile/discover.go index 9b3c93ccd9..2c699f08be 100644 --- a/mobile/discover.go +++ b/mobile/discover.go @@ -22,12 +22,12 @@ package geth import ( "errors" - "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/ethereum/go-ethereum/p2p/enode" ) // Enode represents a host on the network. type Enode struct { - node *discv5.Node + node *enode.Node } // NewEnode parses a node designator. @@ -53,8 +53,8 @@ type Enode struct { // and UDP discovery port 30301. // // enode://@10.3.58.6:30303?discport=30301 -func NewEnode(rawurl string) (enode *Enode, _ error) { - node, err := discv5.ParseNode(rawurl) +func NewEnode(rawurl string) (*Enode, error) { + node, err := enode.Parse(enode.ValidSchemes, rawurl) if err != nil { return nil, err } @@ -62,12 +62,12 @@ func NewEnode(rawurl string) (enode *Enode, _ error) { } // Enodes represents a slice of accounts. -type Enodes struct{ nodes []*discv5.Node } +type Enodes struct{ nodes []*enode.Node } // NewEnodes creates a slice of uninitialized enodes. func NewEnodes(size int) *Enodes { return &Enodes{ - nodes: make([]*discv5.Node, size), + nodes: make([]*enode.Node, size), } } diff --git a/mobile/params.go b/mobile/params.go index 43ac004740..0fc197c9e5 100644 --- a/mobile/params.go +++ b/mobile/params.go @@ -22,7 +22,7 @@ import ( "encoding/json" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" ) @@ -62,9 +62,13 @@ func GoerliGenesis() string { // FoundationBootnodes returns the enode URLs of the P2P bootstrap nodes operated // by the foundation running the V5 discovery protocol. func FoundationBootnodes() *Enodes { - nodes := &Enodes{nodes: make([]*discv5.Node, len(params.MainnetBootnodes))} + nodes := &Enodes{nodes: make([]*enode.Node, len(params.MainnetBootnodes))} for i, url := range params.MainnetBootnodes { - nodes.nodes[i] = discv5.MustParseNode(url) + var err error + nodes.nodes[i], err = enode.Parse(enode.ValidSchemes, url) + if err != nil { + panic("invalid node URL: " + err.Error()) + } } return nodes } diff --git a/p2p/discv5/README b/p2p/discv5/README deleted file mode 100644 index 617a473d7f..0000000000 --- a/p2p/discv5/README +++ /dev/null @@ -1,4 +0,0 @@ -This package is an early prototype of Discovery v5. Do not use this code. - -See https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md for the -current Discovery v5 specification. \ No newline at end of file diff --git a/p2p/discv5/database.go b/p2p/discv5/database.go deleted file mode 100644 index ca118e7f80..0000000000 --- a/p2p/discv5/database.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the node database, storing previously seen nodes and any collected -// metadata about them for QoS purposes. - -package discv5 - -import ( - "bytes" - "crypto/rand" - "encoding/binary" - "fmt" - "os" - "sync" - "time" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/errors" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/opt" - "github.com/syndtr/goleveldb/leveldb/storage" - "github.com/syndtr/goleveldb/leveldb/util" -) - -var ( - nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element. - nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped. - nodeDBCleanupCycle = time.Hour // Time period for running the expiration task. -) - -// nodeDB stores all nodes we know about. -type nodeDB struct { - lvl *leveldb.DB // Interface to the database itself - self NodeID // Own node id to prevent adding it into the database - runner sync.Once // Ensures we can start at most one expirer - quit chan struct{} // Channel to signal the expiring thread to stop -} - -// Schema layout for the node database -var ( - nodeDBVersionKey = []byte("version") // Version of the database to flush if changes - nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with - - nodeDBDiscoverRoot = ":discover" - nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping" - nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong" - nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail" - nodeDBTopicRegTickets = ":tickets" -) - -// newNodeDB creates a new node database for storing and retrieving infos about -// known peers in the network. If no path is given, an in-memory, temporary -// database is constructed. -func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) { - if path == "" { - return newMemoryNodeDB(self) - } - return newPersistentNodeDB(path, version, self) -} - -// newMemoryNodeDB creates a new in-memory node database without a persistent -// backend. -func newMemoryNodeDB(self NodeID) (*nodeDB, error) { - db, err := leveldb.Open(storage.NewMemStorage(), nil) - if err != nil { - return nil, err - } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil -} - -// newPersistentNodeDB creates/opens a leveldb backed persistent node database, -// also flushing its contents in case of a version mismatch. -func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) { - opts := &opt.Options{OpenFilesCacheCapacity: 5} - db, err := leveldb.OpenFile(path, opts) - if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { - db, err = leveldb.RecoverFile(path, nil) - } - if err != nil { - return nil, err - } - // The nodes contained in the cache correspond to a certain protocol version. - // Flush all nodes if the version doesn't match. - currentVer := make([]byte, binary.MaxVarintLen64) - currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))] - - blob, err := db.Get(nodeDBVersionKey, nil) - switch err { - case leveldb.ErrNotFound: - // Version not found (i.e. empty cache), insert it - if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil { - db.Close() - return nil, err - } - - case nil: - // Version present, flush if different - if !bytes.Equal(blob, currentVer) { - db.Close() - if err = os.RemoveAll(path); err != nil { - return nil, err - } - return newPersistentNodeDB(path, version, self) - } - } - return &nodeDB{ - lvl: db, - self: self, - quit: make(chan struct{}), - }, nil -} - -// makeKey generates the leveldb key-blob from a node id and its particular -// field of interest. -func makeKey(id NodeID, field string) []byte { - if bytes.Equal(id[:], nodeDBNilNodeID[:]) { - return []byte(field) - } - return append(nodeDBItemPrefix, append(id[:], field...)...) -} - -// splitKey tries to split a database key into a node id and a field part. -func splitKey(key []byte) (id NodeID, field string) { - // If the key is not of a node, return it plainly - if !bytes.HasPrefix(key, nodeDBItemPrefix) { - return NodeID{}, string(key) - } - // Otherwise split the id and field - item := key[len(nodeDBItemPrefix):] - copy(id[:], item[:len(id)]) - field = string(item[len(id):]) - - return id, field -} - -// fetchInt64 retrieves an integer instance associated with a particular -// database key. -func (db *nodeDB) fetchInt64(key []byte) int64 { - blob, err := db.lvl.Get(key, nil) - if err != nil { - return 0 - } - val, read := binary.Varint(blob) - if read <= 0 { - return 0 - } - return val -} - -// storeInt64 update a specific database entry to the current time instance as a -// unix timestamp. -func (db *nodeDB) storeInt64(key []byte, n int64) error { - blob := make([]byte, binary.MaxVarintLen64) - blob = blob[:binary.PutVarint(blob, n)] - return db.lvl.Put(key, blob, nil) -} - -func (db *nodeDB) storeRLP(key []byte, val interface{}) error { - blob, err := rlp.EncodeToBytes(val) - if err != nil { - return err - } - return db.lvl.Put(key, blob, nil) -} - -func (db *nodeDB) fetchRLP(key []byte, val interface{}) error { - blob, err := db.lvl.Get(key, nil) - if err != nil { - return err - } - err = rlp.DecodeBytes(blob, val) - if err != nil { - log.Warn(fmt.Sprintf("key %x (%T) %v", key, val, err)) - } - return err -} - -// node retrieves a node with a given id from the database. -func (db *nodeDB) node(id NodeID) *Node { - var node Node - if err := db.fetchRLP(makeKey(id, nodeDBDiscoverRoot), &node); err != nil { - return nil - } - node.sha = crypto.Keccak256Hash(node.ID[:]) - return &node -} - -// updateNode inserts - potentially overwriting - a node into the peer database. -func (db *nodeDB) updateNode(node *Node) error { - return db.storeRLP(makeKey(node.ID, nodeDBDiscoverRoot), node) -} - -// deleteNode deletes all information/keys associated with a node. -func (db *nodeDB) deleteNode(id NodeID) error { - deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil) - for deleter.Next() { - if err := db.lvl.Delete(deleter.Key(), nil); err != nil { - return err - } - } - return nil -} - -// ensureExpirer is a small helper method ensuring that the data expiration -// mechanism is running. If the expiration goroutine is already running, this -// method simply returns. -// -// The goal is to start the data evacuation only after the network successfully -// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since -// it would require significant overhead to exactly trace the first successful -// convergence, it's simpler to "ensure" the correct state when an appropriate -// condition occurs (i.e. a successful bonding), and discard further events. -func (db *nodeDB) ensureExpirer() { - db.runner.Do(func() { go db.expirer() }) -} - -// expirer should be started in a go routine, and is responsible for looping ad -// infinitum and dropping stale data from the database. -func (db *nodeDB) expirer() { - tick := time.NewTicker(nodeDBCleanupCycle) - defer tick.Stop() - for { - select { - case <-tick.C: - if err := db.expireNodes(); err != nil { - log.Error(fmt.Sprintf("Failed to expire nodedb items: %v", err)) - } - case <-db.quit: - return - } - } -} - -// expireNodes iterates over the database and deletes all nodes that have not -// been seen (i.e. received a pong from) for some allotted time. -func (db *nodeDB) expireNodes() error { - threshold := time.Now().Add(-nodeDBNodeExpiration) - - // Find discovered nodes that are older than the allowance - it := db.lvl.NewIterator(nil, nil) - defer it.Release() - - for it.Next() { - // Skip the item if not a discovery node - id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { - continue - } - // Skip the node if not expired yet (and not self) - if !bytes.Equal(id[:], db.self[:]) { - if seen := db.lastPong(id); seen.After(threshold) { - continue - } - } - // Otherwise delete all associated information - db.deleteNode(id) - } - return nil -} - -// lastPing retrieves the time of the last ping packet send to a remote node, -// requesting binding. -func (db *nodeDB) lastPing(id NodeID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0) -} - -// updateLastPing updates the last time we tried contacting a remote node. -func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix()) -} - -// lastPong retrieves the time of the last successful contact from remote node. -func (db *nodeDB) lastPong(id NodeID) time.Time { - return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0) -} - -// updateLastPong updates the last time a remote node successfully contacted. -func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix()) -} - -// findFails retrieves the number of findnode failures since bonding. -func (db *nodeDB) findFails(id NodeID) int { - return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails))) -} - -// updateFindFails updates the number of findnode failures since bonding. -func (db *nodeDB) updateFindFails(id NodeID, fails int) error { - return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails)) -} - -// querySeeds retrieves random nodes to be used as potential seed nodes -// for bootstrapping. -func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node { - var ( - now = time.Now() - nodes = make([]*Node, 0, n) - it = db.lvl.NewIterator(nil, nil) - id NodeID - ) - defer it.Release() - -seek: - for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ { - // Seek to a random entry. The first byte is incremented by a - // random amount each time in order to increase the likelihood - // of hitting all existing nodes in very small databases. - ctr := id[0] - rand.Read(id[:]) - id[0] = ctr + id[0]%16 - it.Seek(makeKey(id, nodeDBDiscoverRoot)) - - n := nextNode(it) - if n == nil { - id[0] = 0 - continue seek // iterator exhausted - } - if n.ID == db.self { - continue seek - } - if now.Sub(db.lastPong(n.ID)) > maxAge { - continue seek - } - for i := range nodes { - if nodes[i].ID == n.ID { - continue seek // duplicate - } - } - nodes = append(nodes, n) - } - return nodes -} - -func (db *nodeDB) fetchTopicRegTickets(id NodeID) (issued, used uint32) { - key := makeKey(id, nodeDBTopicRegTickets) - blob, _ := db.lvl.Get(key, nil) - if len(blob) != 8 { - return 0, 0 - } - issued = binary.BigEndian.Uint32(blob[0:4]) - used = binary.BigEndian.Uint32(blob[4:8]) - return -} - -func (db *nodeDB) updateTopicRegTickets(id NodeID, issued, used uint32) error { - key := makeKey(id, nodeDBTopicRegTickets) - blob := make([]byte, 8) - binary.BigEndian.PutUint32(blob[0:4], issued) - binary.BigEndian.PutUint32(blob[4:8], used) - return db.lvl.Put(key, blob, nil) -} - -// reads the next node record from the iterator, skipping over other -// database entries. -func nextNode(it iterator.Iterator) *Node { - for end := false; !end; end = !it.Next() { - id, field := splitKey(it.Key()) - if field != nodeDBDiscoverRoot { - continue - } - var n Node - if err := rlp.DecodeBytes(it.Value(), &n); err != nil { - log.Warn(fmt.Sprintf("invalid node %x: %v", id, err)) - continue - } - return &n - } - return nil -} - -// close flushes and closes the database files. -func (db *nodeDB) close() { - close(db.quit) - db.lvl.Close() -} diff --git a/p2p/discv5/database_test.go b/p2p/discv5/database_test.go deleted file mode 100644 index 2b86dc9cec..0000000000 --- a/p2p/discv5/database_test.go +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "io/ioutil" - "net" - "os" - "path/filepath" - "reflect" - "testing" - "time" -) - -var nodeDBKeyTests = []struct { - id NodeID - field string - key []byte -}{ - { - id: NodeID{}, - field: "version", - key: []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field - }, - { - id: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - field: ":discover", - key: []byte{0x6e, 0x3a, // prefix - 0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // node id - 0x43, 0xd5, 0xad, 0x55, 0xa2, 0xee, 0x3f, 0x56, // - 0xc6, 0xcb, 0xc1, 0xc6, 0x4a, 0x5c, 0x8d, 0x65, // - 0x9f, 0x51, 0xfc, 0xd5, 0x1b, 0xac, 0xe2, 0x43, // - 0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, // - 0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, // - 0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, // - 0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, // - 0x3a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, // field - }, - }, -} - -func TestNodeDBKeys(t *testing.T) { - for i, tt := range nodeDBKeyTests { - if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) { - t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key) - } - id, field := splitKey(tt.key) - if !bytes.Equal(id[:], tt.id[:]) { - t.Errorf("split test %d: id mismatch: have 0x%x, want 0x%x", i, id, tt.id) - } - if field != tt.field { - t.Errorf("split test %d: field mismatch: have 0x%x, want 0x%x", i, field, tt.field) - } - } -} - -var nodeDBInt64Tests = []struct { - key []byte - value int64 -}{ - {key: []byte{0x01}, value: 1}, - {key: []byte{0x02}, value: 2}, - {key: []byte{0x03}, value: 3}, -} - -func TestNodeDBInt64(t *testing.T) { - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - tests := nodeDBInt64Tests - for i := 0; i < len(tests); i++ { - // Insert the next value - if err := db.storeInt64(tests[i].key, tests[i].value); err != nil { - t.Errorf("test %d: failed to store value: %v", i, err) - } - // Check all existing and non existing values - for j := 0; j < len(tests); j++ { - num := db.fetchInt64(tests[j].key) - switch { - case j <= i && num != tests[j].value: - t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, tests[j].value) - case j > i && num != 0: - t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, 0) - } - } - } -} - -func TestNodeDBFetchStore(t *testing.T) { - node := NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{192, 168, 0, 1}, - 30303, - 30303, - ) - inst := time.Now() - num := 314 - - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - // Check fetch/store operations on a node ping object - if stored := db.lastPing(node.ID); stored.Unix() != 0 { - t.Errorf("ping: non-existing object: %v", stored) - } - if err := db.updateLastPing(node.ID, inst); err != nil { - t.Errorf("ping: failed to update: %v", err) - } - if stored := db.lastPing(node.ID); stored.Unix() != inst.Unix() { - t.Errorf("ping: value mismatch: have %v, want %v", stored, inst) - } - // Check fetch/store operations on a node pong object - if stored := db.lastPong(node.ID); stored.Unix() != 0 { - t.Errorf("pong: non-existing object: %v", stored) - } - if err := db.updateLastPong(node.ID, inst); err != nil { - t.Errorf("pong: failed to update: %v", err) - } - if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() { - t.Errorf("pong: value mismatch: have %v, want %v", stored, inst) - } - // Check fetch/store operations on a node findnode-failure object - if stored := db.findFails(node.ID); stored != 0 { - t.Errorf("find-node fails: non-existing object: %v", stored) - } - if err := db.updateFindFails(node.ID, num); err != nil { - t.Errorf("find-node fails: failed to update: %v", err) - } - if stored := db.findFails(node.ID); stored != num { - t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num) - } - // Check fetch/store operations on an actual node object - if stored := db.node(node.ID); stored != nil { - t.Errorf("node: non-existing object: %v", stored) - } - if err := db.updateNode(node); err != nil { - t.Errorf("node: failed to update: %v", err) - } - if stored := db.node(node.ID); stored == nil { - t.Errorf("node: not found") - } else if !reflect.DeepEqual(stored, node) { - t.Errorf("node: data mismatch: have %v, want %v", stored, node) - } -} - -var nodeDBSeedQueryNodes = []struct { - node *Node - pong time.Time -}{ - // This one should not be in the result set because its last - // pong time is too far in the past. - { - node: NewNode( - MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 30303, - 30303, - ), - pong: time.Now().Add(-3 * time.Hour), - }, - // This one shouldn't be in the result set because its - // nodeID is the local node's ID. - { - node: NewNode( - MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 30303, - 30303, - ), - pong: time.Now().Add(-4 * time.Second), - }, - - // These should be in the result set. - { - node: NewNode( - MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 1}, - 30303, - 30303, - ), - pong: time.Now().Add(-2 * time.Second), - }, - { - node: NewNode( - MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 2}, - 30303, - 30303, - ), - pong: time.Now().Add(-3 * time.Second), - }, - { - node: NewNode( - MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 3}, - 30303, - 30303, - ), - pong: time.Now().Add(-1 * time.Second), - }, -} - -func TestNodeDBSeedQuery(t *testing.T) { - db, _ := newNodeDB("", Version, nodeDBSeedQueryNodes[1].node.ID) - defer db.close() - - // Insert a batch of nodes for querying - for i, seed := range nodeDBSeedQueryNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to insert lastPong: %v", i, err) - } - } - - // Retrieve the entire batch and check for duplicates - seeds := db.querySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour) - have := make(map[NodeID]struct{}) - for _, seed := range seeds { - have[seed.ID] = struct{}{} - } - want := make(map[NodeID]struct{}) - for _, seed := range nodeDBSeedQueryNodes[2:] { - want[seed.node.ID] = struct{}{} - } - if len(seeds) != len(want) { - t.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want)) - } - for id := range have { - if _, ok := want[id]; !ok { - t.Errorf("extra seed: %v", id) - } - } - for id := range want { - if _, ok := have[id]; !ok { - t.Errorf("missing seed: %v", id) - } - } -} - -func TestNodeDBPersistency(t *testing.T) { - root, err := ioutil.TempDir("", "nodedb-") - if err != nil { - t.Fatalf("failed to create temporary data folder: %v", err) - } - defer os.RemoveAll(root) - - var ( - testKey = []byte("somekey") - testInt = int64(314) - ) - - // Create a persistent database and store some values - db, err := newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) - if err != nil { - t.Fatalf("failed to create persistent database: %v", err) - } - if err := db.storeInt64(testKey, testInt); err != nil { - t.Fatalf("failed to store value: %v.", err) - } - db.close() - - // Reopen the database and check the value - db, err = newNodeDB(filepath.Join(root, "database"), Version, NodeID{}) - if err != nil { - t.Fatalf("failed to open persistent database: %v", err) - } - if val := db.fetchInt64(testKey); val != testInt { - t.Fatalf("value mismatch: have %v, want %v", val, testInt) - } - db.close() - - // Change the database version and check flush - db, err = newNodeDB(filepath.Join(root, "database"), Version+1, NodeID{}) - if err != nil { - t.Fatalf("failed to open persistent database: %v", err) - } - if val := db.fetchInt64(testKey); val != 0 { - t.Fatalf("value mismatch: have %v, want %v", val, 0) - } - db.close() -} - -var nodeDBExpirationNodes = []struct { - node *Node - pong time.Time - exp bool -}{ - { - node: NewNode( - MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 1}, - 30303, - 30303, - ), - pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute), - exp: false, - }, { - node: NewNode( - MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{127, 0, 0, 2}, - 30303, - 30303, - ), - pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute), - exp: true, - }, -} - -func TestNodeDBExpiration(t *testing.T) { - db, _ := newNodeDB("", Version, NodeID{}) - defer db.close() - - // Add all the test nodes and set their last pong time - for i, seed := range nodeDBExpirationNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to update pong: %v", i, err) - } - } - // Expire some of them, and check the rest - if err := db.expireNodes(); err != nil { - t.Fatalf("failed to expire nodes: %v", err) - } - for i, seed := range nodeDBExpirationNodes { - node := db.node(seed.node.ID) - if (node == nil && !seed.exp) || (node != nil && seed.exp) { - t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp) - } - } -} - -func TestNodeDBSelfExpiration(t *testing.T) { - // Find a node in the tests that shouldn't expire, and assign it as self - var self NodeID - for _, node := range nodeDBExpirationNodes { - if !node.exp { - self = node.node.ID - break - } - } - db, _ := newNodeDB("", Version, self) - defer db.close() - - // Add all the test nodes and set their last pong time - for i, seed := range nodeDBExpirationNodes { - if err := db.updateNode(seed.node); err != nil { - t.Fatalf("node %d: failed to insert: %v", i, err) - } - if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil { - t.Fatalf("node %d: failed to update pong: %v", i, err) - } - } - // Expire the nodes and make sure self has been evacuated too - if err := db.expireNodes(); err != nil { - t.Fatalf("failed to expire nodes: %v", err) - } - node := db.node(self) - if node != nil { - t.Errorf("self not evacuated") - } -} diff --git a/p2p/discv5/metrics.go b/p2p/discv5/metrics.go deleted file mode 100644 index e68d53c13c..0000000000 --- a/p2p/discv5/metrics.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import "github.com/ethereum/go-ethereum/metrics" - -var ( - ingressTrafficMeter = metrics.NewRegisteredMeter("discv5/InboundTraffic", nil) - egressTrafficMeter = metrics.NewRegisteredMeter("discv5/OutboundTraffic", nil) -) diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go deleted file mode 100644 index 53e00a3881..0000000000 --- a/p2p/discv5/net.go +++ /dev/null @@ -1,1269 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "crypto/ecdsa" - "errors" - "fmt" - "net" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/netutil" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" -) - -var ( - errInvalidEvent = errors.New("invalid in current state") - errNoQuery = errors.New("no pending query") -) - -const ( - autoRefreshInterval = 1 * time.Hour - bucketRefreshInterval = 1 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour - lowPort = 1024 -) - -const testTopic = "foo" - -const ( - printTestImgLogs = false -) - -// Network manages the table and all protocol interaction. -type Network struct { - db *nodeDB // database of known nodes - conn transport - netrestrict *netutil.Netlist - - closed chan struct{} // closed when loop is done - closeReq chan struct{} // 'request to close' - refreshReq chan []*Node // lookups ask for refresh on this channel - refreshResp chan (<-chan struct{}) // ...and get the channel to block on from this one - read chan ingressPacket // ingress packets arrive here - timeout chan timeoutEvent - queryReq chan *findnodeQuery // lookups submit findnode queries on this channel - tableOpReq chan func() - tableOpResp chan struct{} - topicRegisterReq chan topicRegisterReq - topicSearchReq chan topicSearchReq - - // State of the main loop. - tab *Table - topictab *topicTable - ticketStore *ticketStore - nursery []*Node - nodes map[NodeID]*Node // tracks active nodes with state != known - timeoutTimers map[timeoutEvent]*time.Timer -} - -// transport is implemented by the UDP transport. -// it is an interface so we can test without opening lots of UDP -// sockets and without generating a private key. -type transport interface { - sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) (hash []byte) - sendNeighbours(remote *Node, nodes []*Node) - sendFindnodeHash(remote *Node, target common.Hash) - sendTopicRegister(remote *Node, topics []Topic, topicIdx int, pong []byte) - sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) - - send(remote *Node, ptype nodeEvent, p interface{}) (hash []byte) - - localAddr() *net.UDPAddr - Close() -} - -type findnodeQuery struct { - remote *Node - target common.Hash - reply chan<- []*Node -} - -type topicRegisterReq struct { - add bool - topic Topic -} - -type topicSearchReq struct { - topic Topic - found chan<- *Node - lookup chan<- bool - delay time.Duration -} - -type topicSearchResult struct { - target lookupInfo - nodes []*Node -} - -type timeoutEvent struct { - ev nodeEvent - node *Node -} - -func newNetwork(conn transport, ourPubkey ecdsa.PublicKey, dbPath string, netrestrict *netutil.Netlist) (*Network, error) { - ourID := PubkeyID(&ourPubkey) - - var db *nodeDB - if dbPath != "" { - var err error - if db, err = newNodeDB(dbPath, Version, ourID); err != nil { - return nil, err - } - } - - tab := newTable(ourID, conn.localAddr()) - net := &Network{ - db: db, - conn: conn, - netrestrict: netrestrict, - tab: tab, - topictab: newTopicTable(db, tab.self), - ticketStore: newTicketStore(), - refreshReq: make(chan []*Node), - refreshResp: make(chan (<-chan struct{})), - closed: make(chan struct{}), - closeReq: make(chan struct{}), - read: make(chan ingressPacket, 100), - timeout: make(chan timeoutEvent), - timeoutTimers: make(map[timeoutEvent]*time.Timer), - tableOpReq: make(chan func()), - tableOpResp: make(chan struct{}), - queryReq: make(chan *findnodeQuery), - topicRegisterReq: make(chan topicRegisterReq), - topicSearchReq: make(chan topicSearchReq), - nodes: make(map[NodeID]*Node), - } - go net.loop() - return net, nil -} - -// Close terminates the network listener and flushes the node database. -func (net *Network) Close() { - net.conn.Close() - select { - case <-net.closed: - case net.closeReq <- struct{}{}: - <-net.closed - } -} - -// Self returns the local node. -// The returned node should not be modified by the caller. -func (net *Network) Self() *Node { - return net.tab.self -} - -// ReadRandomNodes fills the given slice with random nodes from the -// table. It will not write the same node more than once. The nodes in -// the slice are copies and can be modified by the caller. -func (net *Network) ReadRandomNodes(buf []*Node) (n int) { - net.reqTableOp(func() { n = net.tab.readRandomNodes(buf) }) - return n -} - -// SetFallbackNodes sets the initial points of contact. These nodes -// are used to connect to the network if the table is empty and there -// are no known nodes in the database. -func (net *Network) SetFallbackNodes(nodes []*Node) error { - nursery := make([]*Node, 0, len(nodes)) - for _, n := range nodes { - if err := n.validateComplete(); err != nil { - return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err) - } - // Recompute cpy.sha because the node might not have been - // created by NewNode or ParseNode. - cpy := *n - cpy.sha = crypto.Keccak256Hash(n.ID[:]) - nursery = append(nursery, &cpy) - } - net.reqRefresh(nursery) - return nil -} - -// Resolve searches for a specific node with the given ID. -// It returns nil if the node could not be found. -func (net *Network) Resolve(targetID NodeID) *Node { - result := net.lookup(crypto.Keccak256Hash(targetID[:]), true) - for _, n := range result { - if n.ID == targetID { - return n - } - } - return nil -} - -// Lookup performs a network search for nodes close -// to the given target. It approaches the target by querying -// nodes that are closer to it on each iteration. -// The given target does not need to be an actual node -// identifier. -// -// The local node may be included in the result. -func (net *Network) Lookup(targetID NodeID) []*Node { - return net.lookup(crypto.Keccak256Hash(targetID[:]), false) -} - -func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node { - var ( - asked = make(map[NodeID]bool) - seen = make(map[NodeID]bool) - reply = make(chan []*Node, alpha) - result = nodesByDistance{target: target} - pendingQueries = 0 - ) - // Get initial answers from the local node. - result.push(net.tab.self, bucketSize) - for { - // Ask the α closest nodes that we haven't asked yet. - for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ { - n := result.entries[i] - if !asked[n.ID] { - asked[n.ID] = true - pendingQueries++ - net.reqQueryFindnode(n, target, reply) - } - } - if pendingQueries == 0 { - // We have asked all closest nodes, stop the search. - break - } - // Wait for the next reply. - select { - case nodes := <-reply: - for _, n := range nodes { - if n != nil && !seen[n.ID] { - seen[n.ID] = true - result.push(n, bucketSize) - if stopOnMatch && n.sha == target { - return result.entries - } - } - } - pendingQueries-- - case <-time.After(respTimeout): - // forget all pending requests, start new ones - pendingQueries = 0 - reply = make(chan []*Node, alpha) - } - } - return result.entries -} - -func (net *Network) RegisterTopic(topic Topic, stop <-chan struct{}) { - select { - case net.topicRegisterReq <- topicRegisterReq{true, topic}: - case <-net.closed: - return - } - select { - case <-net.closed: - case <-stop: - select { - case net.topicRegisterReq <- topicRegisterReq{false, topic}: - case <-net.closed: - } - } -} - -func (net *Network) SearchTopic(topic Topic, setPeriod <-chan time.Duration, found chan<- *Node, lookup chan<- bool) { - for { - select { - case <-net.closed: - return - case delay, ok := <-setPeriod: - select { - case net.topicSearchReq <- topicSearchReq{topic: topic, found: found, lookup: lookup, delay: delay}: - case <-net.closed: - return - } - if !ok { - return - } - } - } -} - -func (net *Network) reqRefresh(nursery []*Node) <-chan struct{} { - select { - case net.refreshReq <- nursery: - return <-net.refreshResp - case <-net.closed: - return net.closed - } -} - -func (net *Network) reqQueryFindnode(n *Node, target common.Hash, reply chan []*Node) bool { - q := &findnodeQuery{remote: n, target: target, reply: reply} - select { - case net.queryReq <- q: - return true - case <-net.closed: - return false - } -} - -func (net *Network) reqReadPacket(pkt ingressPacket) { - select { - case net.read <- pkt: - case <-net.closed: - } -} - -func (net *Network) reqTableOp(f func()) (called bool) { - select { - case net.tableOpReq <- f: - <-net.tableOpResp - return true - case <-net.closed: - return false - } -} - -// TODO: external address handling. - -type topicSearchInfo struct { - lookupChn chan<- bool - period time.Duration -} - -const maxSearchCount = 5 - -func (net *Network) loop() { - var ( - refreshTimer = time.NewTicker(autoRefreshInterval) - bucketRefreshTimer = time.NewTimer(bucketRefreshInterval) - refreshDone chan struct{} // closed when the 'refresh' lookup has ended - ) - defer refreshTimer.Stop() - defer bucketRefreshTimer.Stop() - - // Tracking the next ticket to register. - var ( - nextTicket *ticketRef - nextRegisterTimer *time.Timer - nextRegisterTime <-chan time.Time - ) - defer func() { - if nextRegisterTimer != nil { - nextRegisterTimer.Stop() - } - }() - resetNextTicket := func() { - ticket, timeout := net.ticketStore.nextFilteredTicket() - if nextTicket != ticket { - nextTicket = ticket - if nextRegisterTimer != nil { - nextRegisterTimer.Stop() - nextRegisterTime = nil - } - if ticket != nil { - nextRegisterTimer = time.NewTimer(timeout) - nextRegisterTime = nextRegisterTimer.C - } - } - } - - // Tracking registration and search lookups. - var ( - topicRegisterLookupTarget lookupInfo - topicRegisterLookupDone chan []*Node - topicRegisterLookupTick = time.NewTimer(0) - searchReqWhenRefreshDone []topicSearchReq - searchInfo = make(map[Topic]topicSearchInfo) - activeSearchCount int - ) - defer topicRegisterLookupTick.Stop() - topicSearchLookupDone := make(chan topicSearchResult, 100) - topicSearch := make(chan Topic, 100) - <-topicRegisterLookupTick.C - - statsDump := time.NewTicker(10 * time.Second) - defer statsDump.Stop() - -loop: - for { - resetNextTicket() - - select { - case <-net.closeReq: - log.Trace("<-net.closeReq") - break loop - - // Ingress packet handling. - case pkt := <-net.read: - //fmt.Println("read", pkt.ev) - log.Trace("<-net.read") - n := net.internNode(&pkt) - prestate := n.state - status := "ok" - if err := net.handle(n, pkt.ev, &pkt); err != nil { - status = err.Error() - } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("<<< (%d) %v from %x@%v: %v -> %v (%v)", - net.tab.count, pkt.ev, pkt.remoteID[:8], pkt.remoteAddr, prestate, n.state, status) - }}) - // TODO: persist state if n.state goes >= known, delete if it goes <= known - - // State transition timeouts. - case timeout := <-net.timeout: - log.Trace("<-net.timeout") - if net.timeoutTimers[timeout] == nil { - // Stale timer (was aborted). - continue - } - delete(net.timeoutTimers, timeout) - prestate := timeout.node.state - status := "ok" - if err := net.handle(timeout.node, timeout.ev, nil); err != nil { - status = err.Error() - } - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprintf("--- (%d) %v for %x@%v: %v -> %v (%v)", - net.tab.count, timeout.ev, timeout.node.ID[:8], timeout.node.addr(), prestate, timeout.node.state, status) - }}) - - // Querying. - case q := <-net.queryReq: - log.Trace("<-net.queryReq") - if !q.start(net) { - q.remote.deferQuery(q) - } - - // Interacting with the table. - case f := <-net.tableOpReq: - log.Trace("<-net.tableOpReq") - f() - net.tableOpResp <- struct{}{} - - // Topic registration stuff. - case req := <-net.topicRegisterReq: - log.Trace("<-net.topicRegisterReq") - if !req.add { - net.ticketStore.removeRegisterTopic(req.topic) - continue - } - net.ticketStore.addTopic(req.topic, true) - // If we're currently waiting idle (nothing to look up), give the ticket store a - // chance to start it sooner. This should speed up convergence of the radius - // determination for new topics. - // if topicRegisterLookupDone == nil { - if topicRegisterLookupTarget.target == (common.Hash{}) { - log.Trace("topicRegisterLookupTarget == null") - if topicRegisterLookupTick.Stop() { - <-topicRegisterLookupTick.C - } - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - } - - case nodes := <-topicRegisterLookupDone: - log.Trace("<-topicRegisterLookupDone") - net.ticketStore.registerLookupDone(topicRegisterLookupTarget, nodes, func(n *Node) []byte { - net.ping(n, n.addr()) - return n.pingEcho - }) - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - topicRegisterLookupDone = nil - - case <-topicRegisterLookupTick.C: - log.Trace("<-topicRegisterLookupTick") - if (topicRegisterLookupTarget.target == common.Hash{}) { - target, delay := net.ticketStore.nextRegisterLookup() - topicRegisterLookupTarget = target - topicRegisterLookupTick.Reset(delay) - topicRegisterLookupDone = nil - } else { - topicRegisterLookupDone = make(chan []*Node) - target := topicRegisterLookupTarget.target - go func() { topicRegisterLookupDone <- net.lookup(target, false) }() - } - - case <-nextRegisterTime: - log.Trace("<-nextRegisterTime") - net.ticketStore.ticketRegistered(*nextTicket) - //fmt.Println("sendTopicRegister", nextTicket.t.node.addr().String(), nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong) - net.conn.sendTopicRegister(nextTicket.t.node, nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong) - - case req := <-net.topicSearchReq: - if refreshDone == nil { - log.Trace("<-net.topicSearchReq") - info, ok := searchInfo[req.topic] - if ok { - if req.delay == time.Duration(0) { - delete(searchInfo, req.topic) - net.ticketStore.removeSearchTopic(req.topic) - } else { - info.period = req.delay - searchInfo[req.topic] = info - } - continue - } - if req.delay != time.Duration(0) { - var info topicSearchInfo - info.period = req.delay - info.lookupChn = req.lookup - searchInfo[req.topic] = info - net.ticketStore.addSearchTopic(req.topic, req.found) - topicSearch <- req.topic - } - } else { - searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req) - } - - case topic := <-topicSearch: - if activeSearchCount < maxSearchCount { - activeSearchCount++ - target := net.ticketStore.nextSearchLookup(topic) - go func() { - nodes := net.lookup(target.target, false) - topicSearchLookupDone <- topicSearchResult{target: target, nodes: nodes} - }() - } - period := searchInfo[topic].period - if period != time.Duration(0) { - go func() { - time.Sleep(period) - topicSearch <- topic - }() - } - - case res := <-topicSearchLookupDone: - activeSearchCount-- - if lookupChn := searchInfo[res.target.topic].lookupChn; lookupChn != nil { - lookupChn <- net.ticketStore.radius[res.target.topic].converged - } - net.ticketStore.searchLookupDone(res.target, res.nodes, func(n *Node, topic Topic) []byte { - if n.state != nil && n.state.canQuery { - return net.conn.send(n, topicQueryPacket, topicQuery{Topic: topic}) // TODO: set expiration - } - if n.state == unknown { - net.ping(n, n.addr()) - } - return nil - }) - - case <-statsDump.C: - log.Trace("<-statsDump.C") - /*r, ok := net.ticketStore.radius[testTopic] - if !ok { - fmt.Printf("(%x) no radius @ %v\n", net.tab.self.ID[:8], time.Now()) - } else { - topics := len(net.ticketStore.tickets) - tickets := len(net.ticketStore.nodes) - rad := r.radius / (maxRadius/10000+1) - fmt.Printf("(%x) topics:%d radius:%d tickets:%d @ %v\n", net.tab.self.ID[:8], topics, rad, tickets, time.Now()) - }*/ - - tm := mclock.Now() - for topic, r := range net.ticketStore.radius { - if printTestImgLogs { - rad := r.radius / (maxRadius/1000000 + 1) - minrad := r.minRadius / (maxRadius/1000000 + 1) - fmt.Printf("*R %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], rad) - fmt.Printf("*MR %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], minrad) - } - } - for topic, t := range net.topictab.topics { - wp := t.wcl.nextWaitPeriod(tm) - if printTestImgLogs { - fmt.Printf("*W %d %v %016x %d\n", tm/1000000, topic, net.tab.self.sha[:8], wp/1000000) - } - } - - // Periodic / lookup-initiated bucket refresh. - case <-refreshTimer.C: - log.Trace("<-refreshTimer.C") - // TODO: ideally we would start the refresh timer after - // fallback nodes have been set for the first time. - if refreshDone == nil { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - case <-bucketRefreshTimer.C: - target := net.tab.chooseBucketRefreshTarget() - go func() { - net.lookup(target, false) - bucketRefreshTimer.Reset(bucketRefreshInterval) - }() - case newNursery := <-net.refreshReq: - log.Trace("<-net.refreshReq") - if newNursery != nil { - net.nursery = newNursery - } - if refreshDone == nil { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - net.refreshResp <- refreshDone - case <-refreshDone: - log.Trace("<-net.refreshDone", "table size", net.tab.count) - if net.tab.count != 0 { - refreshDone = nil - list := searchReqWhenRefreshDone - searchReqWhenRefreshDone = nil - go func() { - for _, req := range list { - net.topicSearchReq <- req - } - }() - } else { - refreshDone = make(chan struct{}) - net.refresh(refreshDone) - } - } - } - log.Trace("loop stopped") - - log.Debug("shutting down") - if net.conn != nil { - net.conn.Close() - } - // TODO: wait for pending refresh. - // if refreshDone != nil { - // <-refreshResults - // } - // Cancel all pending timeouts. - for _, timer := range net.timeoutTimers { - timer.Stop() - } - if net.db != nil { - net.db.close() - } - close(net.closed) -} - -// Everything below runs on the Network.loop goroutine -// and can modify Node, Table and Network at any time without locking. - -func (net *Network) refresh(done chan<- struct{}) { - var seeds []*Node - if net.db != nil { - seeds = net.db.querySeeds(seedCount, seedMaxAge) - } - if len(seeds) == 0 { - seeds = net.nursery - } - if len(seeds) == 0 { - log.Trace("no seed nodes found") - time.AfterFunc(time.Second*10, func() { close(done) }) - return - } - for _, n := range seeds { - log.Debug("", "msg", log.Lazy{Fn: func() string { - var age string - if net.db != nil { - age = time.Since(net.db.lastPong(n.ID)).String() - } else { - age = "unknown" - } - return fmt.Sprintf("seed node (age %s): %v", age, n) - }}) - n = net.internNodeFromDB(n) - if n.state == unknown { - net.transition(n, verifyinit) - } - // Force-add the seed node so Lookup does something. - // It will be deleted again if verification fails. - net.tab.add(n) - } - // Start self lookup to fill up the buckets. - go func() { - net.Lookup(net.tab.self.ID) - close(done) - }() -} - -// Node Interning. - -func (net *Network) internNode(pkt *ingressPacket) *Node { - if n := net.nodes[pkt.remoteID]; n != nil { - n.IP = pkt.remoteAddr.IP - n.UDP = uint16(pkt.remoteAddr.Port) - n.TCP = uint16(pkt.remoteAddr.Port) - return n - } - n := NewNode(pkt.remoteID, pkt.remoteAddr.IP, uint16(pkt.remoteAddr.Port), uint16(pkt.remoteAddr.Port)) - n.state = unknown - net.nodes[pkt.remoteID] = n - return n -} - -func (net *Network) internNodeFromDB(dbn *Node) *Node { - if n := net.nodes[dbn.ID]; n != nil { - return n - } - n := NewNode(dbn.ID, dbn.IP, dbn.UDP, dbn.TCP) - n.state = unknown - net.nodes[n.ID] = n - return n -} - -func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n *Node, err error) { - if rn.ID == net.tab.self.ID { - return nil, errors.New("is self") - } - if rn.UDP <= lowPort { - return nil, errors.New("low port") - } - n = net.nodes[rn.ID] - if n == nil { - // We haven't seen this node before. - n, err = nodeFromRPC(sender, rn) - if net.netrestrict != nil && !net.netrestrict.Contains(n.IP) { - return n, errors.New("not contained in netrestrict whitelist") - } - if err == nil { - n.state = unknown - net.nodes[n.ID] = n - } - return n, err - } - if !n.IP.Equal(rn.IP) || n.UDP != rn.UDP || n.TCP != rn.TCP { - if n.state == known { - // reject address change if node is known by us - err = fmt.Errorf("metadata mismatch: got %v, want %v", rn, n) - } else { - // accept otherwise; this will be handled nicer with signed ENRs - n.IP = rn.IP - n.UDP = rn.UDP - n.TCP = rn.TCP - } - } - return n, err -} - -// nodeNetGuts is embedded in Node and contains fields. -type nodeNetGuts struct { - // This is a cached copy of sha3(ID) which is used for node - // distance calculations. This is part of Node in order to make it - // possible to write tests that need a node at a certain distance. - // In those tests, the content of sha will not actually correspond - // with ID. - sha common.Hash - - // State machine fields. Access to these fields - // is restricted to the Network.loop goroutine. - state *nodeState - pingEcho []byte // hash of last ping sent by us - pingTopics []Topic // topic set sent by us in last ping - deferredQueries []*findnodeQuery // queries that can't be sent yet - pendingNeighbours *findnodeQuery // current query, waiting for reply - queryTimeouts int -} - -func (n *nodeNetGuts) deferQuery(q *findnodeQuery) { - n.deferredQueries = append(n.deferredQueries, q) -} - -func (n *nodeNetGuts) startNextQuery(net *Network) { - if len(n.deferredQueries) == 0 { - return - } - nextq := n.deferredQueries[0] - if nextq.start(net) { - n.deferredQueries = append(n.deferredQueries[:0], n.deferredQueries[1:]...) - } -} - -func (q *findnodeQuery) start(net *Network) bool { - // Satisfy queries against the local node directly. - if q.remote == net.tab.self { - closest := net.tab.closest(q.target, bucketSize) - q.reply <- closest.entries - return true - } - if q.remote.state.canQuery && q.remote.pendingNeighbours == nil { - net.conn.sendFindnodeHash(q.remote, q.target) - net.timedEvent(respTimeout, q.remote, neighboursTimeout) - q.remote.pendingNeighbours = q - return true - } - // If the node is not known yet, it won't accept queries. - // Initiate the transition to known. - // The request will be sent later when the node reaches known state. - if q.remote.state == unknown { - net.transition(q.remote, verifyinit) - } - return false -} - -// Node Events (the input to the state machine). - -type nodeEvent uint - -//go:generate stringer -type=nodeEvent - -const ( - - // Packet type events. - // These correspond to packet types in the UDP protocol. - pingPacket = iota + 1 - pongPacket - findnodePacket - neighborsPacket - findnodeHashPacket - topicRegisterPacket - topicQueryPacket - topicNodesPacket - - // Non-packet events. - // Event values in this category are allocated outside - // the packet type range (packet types are encoded as a single byte). - pongTimeout nodeEvent = iota + 256 - pingTimeout - neighboursTimeout -) - -// Node State Machine. - -type nodeState struct { - name string - handle func(*Network, *Node, nodeEvent, *ingressPacket) (next *nodeState, err error) - enter func(*Network, *Node) - canQuery bool -} - -func (s *nodeState) String() string { - return s.name -} - -var ( - unknown *nodeState - verifyinit *nodeState - verifywait *nodeState - remoteverifywait *nodeState - known *nodeState - contested *nodeState - unresponsive *nodeState -) - -func init() { - unknown = &nodeState{ - name: "unknown", - enter: func(net *Network, n *Node) { - net.tab.delete(n) - n.pingEcho = nil - // Abort active queries. - for _, q := range n.deferredQueries { - q.reply <- nil - } - n.deferredQueries = nil - if n.pendingNeighbours != nil { - n.pendingNeighbours.reply <- nil - n.pendingNeighbours = nil - } - n.queryTimeouts = 0 - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - net.ping(n, pkt.remoteAddr) - return verifywait, nil - default: - return unknown, errInvalidEvent - } - }, - } - - verifyinit = &nodeState{ - name: "verifyinit", - enter: func(net *Network, n *Node) { - net.ping(n, n.addr()) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return verifywait, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return remoteverifywait, err - case pongTimeout: - return unknown, nil - default: - return verifyinit, errInvalidEvent - } - }, - } - - verifywait = &nodeState{ - name: "verifywait", - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return verifywait, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - case pongTimeout: - return unknown, nil - default: - return verifywait, errInvalidEvent - } - }, - } - - remoteverifywait = &nodeState{ - name: "remoteverifywait", - enter: func(net *Network, n *Node) { - net.timedEvent(respTimeout, n, pingTimeout) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return remoteverifywait, nil - case pingTimeout: - return known, nil - default: - return remoteverifywait, errInvalidEvent - } - }, - } - - known = &nodeState{ - name: "known", - canQuery: true, - enter: func(net *Network, n *Node) { - n.queryTimeouts = 0 - n.startNextQuery(net) - // Insert into the table and start revalidation of the last node - // in the bucket if it is full. - last := net.tab.add(n) - if last != nil && last.state == known { - // TODO: do this asynchronously - net.transition(last, contested) - } - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return known, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } - - contested = &nodeState{ - name: "contested", - canQuery: true, - enter: func(net *Network, n *Node) { - net.ping(n, n.addr()) - }, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pongPacket: - // Node is still alive. - err := net.handleKnownPong(n, pkt) - return known, err - case pongTimeout: - net.tab.deleteReplace(n) - return unresponsive, nil - case pingPacket: - net.handlePing(n, pkt) - return contested, nil - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } - - unresponsive = &nodeState{ - name: "unresponsive", - canQuery: true, - handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case pingPacket: - net.handlePing(n, pkt) - return known, nil - case pongPacket: - err := net.handleKnownPong(n, pkt) - return known, err - default: - return net.handleQueryEvent(n, ev, pkt) - } - }, - } -} - -// handle processes packets sent by n and events related to n. -func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error { - //fmt.Println("handle", n.addr().String(), n.state, ev) - if pkt != nil { - if err := net.checkPacket(n, ev, pkt); err != nil { - //fmt.Println("check err:", err) - return err - } - // Start the background expiration goroutine after the first - // successful communication. Subsequent calls have no effect if it - // is already running. We do this here instead of somewhere else - // so that the search for seed nodes also considers older nodes - // that would otherwise be removed by the expirer. - if net.db != nil { - net.db.ensureExpirer() - } - } - if ev == pongTimeout { - n.pingEcho = nil // clean up if pongtimeout - } - if n.state == nil { - n.state = unknown //??? - } - next, err := n.state.handle(net, n, ev, pkt) - net.transition(n, next) - //fmt.Println("new state:", n.state) - return err -} - -func (net *Network) checkPacket(n *Node, ev nodeEvent, pkt *ingressPacket) error { - // Replay prevention checks. - switch ev { - case pingPacket, findnodeHashPacket, neighborsPacket: - // TODO: check date is > last date seen - // TODO: check ping version - case pongPacket: - if !bytes.Equal(pkt.data.(*pong).ReplyTok, n.pingEcho) { - // fmt.Println("pong reply token mismatch") - return fmt.Errorf("pong reply token mismatch") - } - n.pingEcho = nil - } - // Address validation. - // TODO: Ideally we would do the following: - // - reject all packets with wrong address except ping. - // - for ping with new address, transition to verifywait but keep the - // previous node (with old address) around. if the new one reaches known, - // swap it out. - return nil -} - -func (net *Network) transition(n *Node, next *nodeState) { - if n.state != next { - n.state = next - if next.enter != nil { - next.enter(net, n) - } - } - - // TODO: persist/unpersist node -} - -func (net *Network) timedEvent(d time.Duration, n *Node, ev nodeEvent) { - timeout := timeoutEvent{ev, n} - net.timeoutTimers[timeout] = time.AfterFunc(d, func() { - select { - case net.timeout <- timeout: - case <-net.closed: - } - }) -} - -func (net *Network) abortTimedEvent(n *Node, ev nodeEvent) { - timer := net.timeoutTimers[timeoutEvent{ev, n}] - if timer != nil { - timer.Stop() - delete(net.timeoutTimers, timeoutEvent{ev, n}) - } -} - -func (net *Network) ping(n *Node, addr *net.UDPAddr) { - //fmt.Println("ping", n.addr().String(), n.ID.String(), n.sha.Hex()) - if n.pingEcho != nil || n.ID == net.tab.self.ID { - //fmt.Println(" not sent") - return - } - log.Trace("Pinging remote node", "node", n.ID) - n.pingTopics = net.ticketStore.regTopicSet() - n.pingEcho = net.conn.sendPing(n, addr, n.pingTopics) - net.timedEvent(respTimeout, n, pongTimeout) -} - -func (net *Network) handlePing(n *Node, pkt *ingressPacket) { - log.Trace("Handling remote ping", "node", n.ID) - ping := pkt.data.(*ping) - n.TCP = ping.From.TCP - t := net.topictab.getTicket(n, ping.Topics) - - pong := &pong{ - To: makeEndpoint(n.addr(), n.TCP), // TODO: maybe use known TCP port from DB - ReplyTok: pkt.hash, - Expiration: uint64(time.Now().Add(expiration).Unix()), - } - ticketToPong(t, pong) - net.conn.send(n, pongPacket, pong) -} - -func (net *Network) handleKnownPong(n *Node, pkt *ingressPacket) error { - log.Trace("Handling known pong", "node", n.ID) - net.abortTimedEvent(n, pongTimeout) - now := mclock.Now() - ticket, err := pongToTicket(now, n.pingTopics, n, pkt) - if err == nil { - // fmt.Printf("(%x) ticket: %+v\n", net.tab.self.ID[:8], pkt.data) - net.ticketStore.addTicket(now, pkt.data.(*pong).ReplyTok, ticket) - } else { - log.Trace("Failed to convert pong to ticket", "err", err) - } - n.pingEcho = nil - n.pingTopics = nil - return err -} - -func (net *Network) handleQueryEvent(n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) { - switch ev { - case findnodePacket: - target := crypto.Keccak256Hash(pkt.data.(*findnode).Target[:]) - results := net.tab.closest(target, bucketSize).entries - net.conn.sendNeighbours(n, results) - return n.state, nil - case neighborsPacket: - err := net.handleNeighboursPacket(n, pkt) - return n.state, err - case neighboursTimeout: - if n.pendingNeighbours != nil { - n.pendingNeighbours.reply <- nil - n.pendingNeighbours = nil - } - n.queryTimeouts++ - if n.queryTimeouts > maxFindnodeFailures && n.state == known { - return contested, errors.New("too many timeouts") - } - return n.state, nil - - // v5 - - case findnodeHashPacket: - results := net.tab.closest(pkt.data.(*findnodeHash).Target, bucketSize).entries - net.conn.sendNeighbours(n, results) - return n.state, nil - case topicRegisterPacket: - //fmt.Println("got topicRegisterPacket") - regdata := pkt.data.(*topicRegister) - pong, err := net.checkTopicRegister(regdata) - if err != nil { - //fmt.Println(err) - return n.state, fmt.Errorf("bad waiting ticket: %v", err) - } - net.topictab.useTicket(n, pong.TicketSerial, regdata.Topics, int(regdata.Idx), pong.Expiration, pong.WaitPeriods) - return n.state, nil - case topicQueryPacket: - // TODO: handle expiration - topic := pkt.data.(*topicQuery).Topic - results := net.topictab.getEntries(topic) - if _, ok := net.ticketStore.tickets[topic]; ok { - results = append(results, net.tab.self) // we're not registering in our own table but if we're advertising, return ourselves too - } - if len(results) > 10 { - results = results[:10] - } - var hash common.Hash - copy(hash[:], pkt.hash) - net.conn.sendTopicNodes(n, hash, results) - return n.state, nil - case topicNodesPacket: - p := pkt.data.(*topicNodes) - if net.ticketStore.gotTopicNodes(n, p.Echo, p.Nodes) { - n.queryTimeouts++ - if n.queryTimeouts > maxFindnodeFailures && n.state == known { - return contested, errors.New("too many timeouts") - } - } - return n.state, nil - - default: - return n.state, errInvalidEvent - } -} - -func (net *Network) checkTopicRegister(data *topicRegister) (*pong, error) { - var pongpkt ingressPacket - if err := decodePacket(data.Pong, &pongpkt); err != nil { - return nil, err - } - if pongpkt.ev != pongPacket { - return nil, errors.New("is not pong packet") - } - if pongpkt.remoteID != net.tab.self.ID { - return nil, errors.New("not signed by us") - } - // check that we previously authorised all topics - // that the other side is trying to register. - if rlpHash(data.Topics) != pongpkt.data.(*pong).TopicHash { - return nil, errors.New("topic hash mismatch") - } - if data.Idx >= uint(len(data.Topics)) { - return nil, errors.New("topic index out of range") - } - return pongpkt.data.(*pong), nil -} - -func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) - return h -} - -func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error { - if n.pendingNeighbours == nil { - return errNoQuery - } - net.abortTimedEvent(n, neighboursTimeout) - - req := pkt.data.(*neighbors) - nodes := make([]*Node, len(req.Nodes)) - for i, rn := range req.Nodes { - nn, err := net.internNodeFromNeighbours(pkt.remoteAddr, rn) - if err != nil { - log.Debug(fmt.Sprintf("invalid neighbour (%v) from %x@%v: %v", rn.IP, n.ID[:8], pkt.remoteAddr, err)) - continue - } - nodes[i] = nn - // Start validation of query results immediately. - // This fills the table quickly. - // TODO: generates way too many packets, maybe do it via queue. - if nn.state == unknown { - net.transition(nn, verifyinit) - } - } - // TODO: don't ignore second packet - n.pendingNeighbours.reply <- nodes - n.pendingNeighbours = nil - // Now that this query is done, start the next one. - n.startNextQuery(net) - return nil -} diff --git a/p2p/discv5/net_test.go b/p2p/discv5/net_test.go deleted file mode 100644 index 29321bc86f..0000000000 --- a/p2p/discv5/net_test.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "net" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func TestNetwork_Lookup(t *testing.T) { - key, _ := crypto.GenerateKey() - network, err := newNetwork(lookupTestnet, key.PublicKey, "", nil) - if err != nil { - t.Fatal(err) - } - lookupTestnet.net = network - defer network.Close() - - // lookup on empty table returns no nodes - // if results := network.Lookup(lookupTestnet.target, false); len(results) > 0 { - // t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) - // } - // seed table with initial node (otherwise lookup will terminate immediately) - seeds := []*Node{NewNode(lookupTestnet.dists[256][0], net.IP{10, 0, 2, 99}, lowPort+256, 999)} - if err := network.SetFallbackNodes(seeds); err != nil { - t.Fatal(err) - } - time.Sleep(3 * time.Second) - - results := network.Lookup(lookupTestnet.target) - t.Logf("results:") - for _, e := range results { - t.Logf(" ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:]) - } - if len(results) != bucketSize { - t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) - } - if hasDuplicates(results) { - t.Errorf("result set contains duplicate entries") - } - if !sortedByDistanceTo(lookupTestnet.targetSha, results) { - t.Errorf("result set not sorted by distance to target") - } - // TODO: check result nodes are actually closest -} - -// This is the test network for the Lookup test. -// The nodes were obtained by running testnet.mine with a random NodeID as target. -var lookupTestnet = &preminedTestnet{ - target: MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"), - targetSha: common.Hash{0x5c, 0x94, 0x4e, 0xe5, 0x1c, 0x5a, 0xe9, 0xf7, 0x2a, 0x95, 0xec, 0xcb, 0x8a, 0xed, 0x3, 0x74, 0xee, 0xcb, 0x51, 0x19, 0xd7, 0x20, 0xcb, 0xea, 0x68, 0x13, 0xe8, 0xe0, 0xd6, 0xad, 0x92, 0x61}, - dists: [257][]NodeID{ - 240: { - MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"), - MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"), - }, - 244: { - MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"), - }, - 246: { - MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"), - MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"), - MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"), - }, - 247: { - MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), - MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), - MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), - MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), - MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"), - MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"), - MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"), - MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"), - MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"), - MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"), - }, - 248: { - MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"), - MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"), - MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"), - MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"), - MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"), - MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"), - MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"), - MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"), - MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"), - MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"), - MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"), - MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"), - MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"), - MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"), - MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"), - MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"), - }, - 249: { - MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"), - MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"), - MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"), - MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"), - MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"), - MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"), - MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"), - MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"), - MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"), - MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"), - MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"), - MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"), - MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"), - MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"), - MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"), - MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"), - }, - 250: { - MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"), - MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"), - MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"), - MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"), - MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"), - MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"), - MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"), - MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"), - MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"), - MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"), - MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"), - MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"), - MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"), - MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"), - MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"), - MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"), - }, - 251: { - MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"), - MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"), - MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"), - MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"), - MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"), - MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"), - MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"), - MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"), - MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"), - MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"), - MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"), - MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"), - MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"), - MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"), - MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"), - MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"), - }, - 252: { - MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"), - MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"), - MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"), - MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"), - MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"), - MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"), - MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"), - MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"), - MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"), - MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"), - MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"), - MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"), - MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"), - MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"), - MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"), - MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"), - }, - 253: { - MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"), - MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"), - MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"), - MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"), - MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"), - MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"), - MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"), - MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"), - MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"), - MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"), - MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"), - MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"), - MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"), - MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"), - MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"), - MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"), - }, - 254: { - MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"), - MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"), - MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"), - MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"), - MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"), - MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"), - MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"), - MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"), - MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"), - MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"), - MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"), - MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"), - MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"), - MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"), - MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"), - MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"), - }, - 255: { - MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"), - MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"), - MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"), - MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"), - MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"), - MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"), - MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"), - MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"), - MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"), - MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"), - MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"), - MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"), - MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"), - MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"), - MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"), - MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"), - }, - 256: { - MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"), - MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"), - MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"), - MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"), - MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"), - MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"), - MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"), - MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"), - MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"), - MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"), - MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"), - MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"), - MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"), - MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"), - MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"), - MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"), - }, - }, -} - -type preminedTestnet struct { - target NodeID - targetSha common.Hash // sha3(target) - dists [hashBits + 1][]NodeID - net *Network -} - -func (tn *preminedTestnet) sendFindnodeHash(to *Node, target common.Hash) { - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port) - if to.UDP <= lowPort { - panic("query to node at or below distance 0") - } - next := to.UDP - 1 - var result []rpcNode - for i, id := range tn.dists[to.UDP-lowPort] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) - } - injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) -} - -func (tn *preminedTestnet) sendPing(to *Node, addr *net.UDPAddr, topics []Topic) []byte { - injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}}) - return []byte{1} -} - -func (tn *preminedTestnet) send(to *Node, ptype nodeEvent, data interface{}) (hash []byte) { - switch ptype { - case pingPacket: - injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}}) - case pongPacket: - // ignored - case findnodeHashPacket: - // current log distance is encoded in port number - // fmt.Println("findnode query at dist", toaddr.Port-lowPort) - if to.UDP <= lowPort { - panic("query to node at or below distance 0") - } - next := to.UDP - 1 - var result []rpcNode - for i, id := range tn.dists[to.UDP-lowPort] { - result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort))) - } - injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result}) - default: - panic("send(" + ptype.String() + ")") - } - return []byte{2} -} - -func (tn *preminedTestnet) sendNeighbours(to *Node, nodes []*Node) { - panic("sendNeighbours called") -} - -func (tn *preminedTestnet) sendTopicNodes(to *Node, queryHash common.Hash, nodes []*Node) { - panic("sendTopicNodes called") -} - -func (tn *preminedTestnet) sendTopicRegister(to *Node, topics []Topic, idx int, pong []byte) { - panic("sendTopicRegister called") -} - -func (*preminedTestnet) Close() {} - -func (*preminedTestnet) localAddr() *net.UDPAddr { - return &net.UDPAddr{IP: net.ParseIP("10.0.1.1"), Port: 40000} -} - -func injectResponse(net *Network, from *Node, ev nodeEvent, packet interface{}) { - go net.reqReadPacket(ingressPacket{remoteID: from.ID, remoteAddr: from.addr(), ev: ev, data: packet}) -} diff --git a/p2p/discv5/node.go b/p2p/discv5/node.go deleted file mode 100644 index 44d3025b70..0000000000 --- a/p2p/discv5/node.go +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "encoding/hex" - "errors" - "fmt" - "math/big" - "math/rand" - "net" - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -// Node represents a host on the network. -// The public fields of Node may not be modified. -type Node struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP, TCP uint16 // port numbers - ID NodeID // the node's public key - - // Network-related fields are contained in nodeNetGuts. - // These fields are not supposed to be used off the - // Network.loop goroutine. - nodeNetGuts -} - -// NewNode creates a new node. It is mostly meant to be used for -// testing purposes. -func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 - } - return &Node{ - IP: ip, - UDP: udpPort, - TCP: tcpPort, - ID: id, - nodeNetGuts: nodeNetGuts{sha: crypto.Keccak256Hash(id[:])}, - } -} - -func (n *Node) addr() *net.UDPAddr { - return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)} -} - -// Incomplete returns true for nodes with no IP address. -func (n *Node) Incomplete() bool { - return n.IP == nil -} - -// checks whether n is a valid complete node. -func (n *Node) validateComplete() error { - if n.Incomplete() { - return errors.New("incomplete node") - } - if n.UDP == 0 { - return errors.New("missing UDP port") - } - if n.TCP == 0 { - return errors.New("missing TCP port") - } - if n.IP.IsMulticast() || n.IP.IsUnspecified() { - return errors.New("invalid IP (multicast/unspecified)") - } - _, err := n.ID.Pubkey() // validate the key (on curve, etc.) - return err -} - -// The string representation of a Node is a URL. -// Please see ParseNode for a description of the format. -func (n *Node) String() string { - u := url.URL{Scheme: "enode"} - if n.Incomplete() { - u.Host = fmt.Sprintf("%x", n.ID[:]) - } else { - addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} - u.User = url.User(fmt.Sprintf("%x", n.ID[:])) - u.Host = addr.String() - if n.UDP != n.TCP { - u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) - } - } - return u.String() -} - -var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") - -// ParseNode parses a node designator. -// -// There are two basic forms of node designators -// - incomplete nodes, which only have the public key (node ID) -// - complete nodes, which contain the public key and IP/Port information -// -// For incomplete nodes, the designator must look like one of these -// -// enode:// -// -// -// For complete nodes, the node ID is encoded in the username portion -// of the URL, separated from the host by an @ sign. The hostname can -// only be given as an IP address, DNS domain names are not allowed. -// The port in the host name section is the TCP listening port. If the -// TCP and UDP (discovery) ports differ, the UDP port is specified as -// query parameter "discport". -// -// In the following example, the node URL describes -// a node with IP address 10.3.58.6, TCP listening port 30303 -// and UDP discovery port 30301. -// -// enode://@10.3.58.6:30303?discport=30301 -func ParseNode(rawurl string) (*Node, error) { - if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { - id, err := HexID(m[1]) - if err != nil { - return nil, fmt.Errorf("invalid node ID (%v)", err) - } - return NewNode(id, nil, 0, 0), nil - } - return parseComplete(rawurl) -} - -func parseComplete(rawurl string) (*Node, error) { - var ( - id NodeID - ip net.IP - tcpPort, udpPort uint64 - ) - u, err := url.Parse(rawurl) - if err != nil { - return nil, err - } - if u.Scheme != "enode" { - return nil, errors.New("invalid URL scheme, want \"enode\"") - } - // Parse the Node ID from the user portion. - if u.User == nil { - return nil, errors.New("does not contain node ID") - } - if id, err = HexID(u.User.String()); err != nil { - return nil, fmt.Errorf("invalid node ID (%v)", err) - } - // Parse the IP address. - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - return nil, fmt.Errorf("invalid host: %v", err) - } - if ip = net.ParseIP(host); ip == nil { - return nil, errors.New("invalid IP address") - } - // Ensure the IP is 4 bytes long for IPv4 addresses. - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 - } - // Parse the port numbers. - if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil { - return nil, errors.New("invalid port") - } - udpPort = tcpPort - qv := u.Query() - if qv.Get("discport") != "" { - udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16) - if err != nil { - return nil, errors.New("invalid discport in query") - } - } - return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil -} - -// MustParseNode parses a node URL. It panics if the URL is not valid. -func MustParseNode(rawurl string) *Node { - n, err := ParseNode(rawurl) - if err != nil { - panic("invalid node URL: " + err.Error()) - } - return n -} - -// MarshalText implements encoding.TextMarshaler. -func (n *Node) MarshalText() ([]byte, error) { - return []byte(n.String()), nil -} - -// UnmarshalText implements encoding.TextUnmarshaler. -func (n *Node) UnmarshalText(text []byte) error { - dec, err := ParseNode(string(text)) - if err == nil { - *n = *dec - } - return err -} - -// type nodeQueue []*Node -// -// // pushNew adds n to the end if it is not present. -// func (nl *nodeList) appendNew(n *Node) { -// for _, entry := range n { -// if entry == n { -// return -// } -// } -// *nq = append(*nq, n) -// } -// -// // popRandom removes a random node. Nodes closer to -// // to the head of the beginning of the have a slightly higher probability. -// func (nl *nodeList) popRandom() *Node { -// ix := rand.Intn(len(*nq)) -// //TODO: probability as mentioned above. -// nl.removeIndex(ix) -// } -// -// func (nl *nodeList) removeIndex(i int) *Node { -// slice = *nl -// if len(*slice) <= i { -// return nil -// } -// *nl = append(slice[:i], slice[i+1:]...) -// } - -const nodeIDBits = 512 - -// NodeID is a unique identifier for each node. -// The node identifier is a marshaled elliptic curve public key. -type NodeID [nodeIDBits / 8]byte - -// NodeID prints as a long hexadecimal number. -func (n NodeID) String() string { - return fmt.Sprintf("%x", n[:]) -} - -// The Go syntax representation of a NodeID is a call to HexID. -func (n NodeID) GoString() string { - return fmt.Sprintf("discover.HexID(\"%x\")", n[:]) -} - -// TerminalString returns a shortened hex string for terminal logging. -func (n NodeID) TerminalString() string { - return hex.EncodeToString(n[:8]) -} - -// HexID converts a hex string to a NodeID. -// The string may be prefixed with 0x. -func HexID(in string) (NodeID, error) { - var id NodeID - b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) - if err != nil { - return id, err - } else if len(b) != len(id) { - return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) - } - copy(id[:], b) - return id, nil -} - -// MustHexID converts a hex string to a NodeID. -// It panics if the string is not a valid NodeID. -func MustHexID(in string) NodeID { - id, err := HexID(in) - if err != nil { - panic(err) - } - return id -} - -// PubkeyID returns a marshaled representation of the given public key. -func PubkeyID(pub *ecdsa.PublicKey) NodeID { - var id NodeID - pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y) - if len(pbytes)-1 != len(id) { - panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes))) - } - copy(id[:], pbytes[1:]) - return id -} - -// Pubkey returns the public key represented by the node ID. -// It returns an error if the ID is not a point on the curve. -func (n NodeID) Pubkey() (*ecdsa.PublicKey, error) { - p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} - half := len(n) / 2 - p.X.SetBytes(n[:half]) - p.Y.SetBytes(n[half:]) - if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("id is invalid secp256k1 curve point") - } - return p, nil -} - -// recoverNodeID computes the public key used to sign the -// given hash from the signature. -func recoverNodeID(hash, sig []byte) (id NodeID, err error) { - pubkey, err := crypto.Ecrecover(hash, sig) - if err != nil { - return id, err - } - if len(pubkey)-1 != len(id) { - return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8) - } - for i := range id { - id[i] = pubkey[i+1] - } - return id, nil -} - -// distcmp compares the distances a->target and b->target. -// Returns -1 if a is closer to target, 1 if b is closer to target -// and 0 if they are equal. -func distcmp(target, a, b common.Hash) int { - for i := range target { - da := a[i] ^ target[i] - db := b[i] ^ target[i] - if da > db { - return 1 - } else if da < db { - return -1 - } - } - return 0 -} - -// table of leading zero counts for bytes [0..255] -var lzcount = [256]int{ - 8, 7, 6, 6, 5, 5, 5, 5, - 4, 4, 4, 4, 4, 4, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -} - -// logdist returns the logarithmic distance between a and b, log2(a ^ b). -func logdist(a, b common.Hash) int { - lz := 0 - for i := range a { - x := a[i] ^ b[i] - if x == 0 { - lz += 8 - } else { - lz += lzcount[x] - break - } - } - return len(a)*8 - lz -} - -// hashAtDistance returns a random hash such that logdist(a, b) == n -func hashAtDistance(a common.Hash, n int) (b common.Hash) { - if n == 0 { - return a - } - // flip bit at position n, fill the rest with random bits - b = a - pos := len(a) - n/8 - 1 - bit := byte(0x01) << (byte(n%8) - 1) - if bit == 0 { - pos++ - bit = 0x80 - } - b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits - for i := pos + 1; i < len(a); i++ { - b[i] = byte(rand.Intn(255)) - } - return b -} diff --git a/p2p/discv5/node_test.go b/p2p/discv5/node_test.go deleted file mode 100644 index 4e0fdbe3db..0000000000 --- a/p2p/discv5/node_test.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "fmt" - "math/big" - "math/rand" - "net" - "reflect" - "strings" - "testing" - "testing/quick" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func ExampleNewNode() { - id := MustHexID("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439") - - // Complete nodes contain UDP and TCP endpoints: - n1 := NewNode(id, net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, 30303) - fmt.Println("n1:", n1) - fmt.Println("n1.Incomplete() ->", n1.Incomplete()) - - // An incomplete node can be created by passing zero values - // for all parameters except id. - n2 := NewNode(id, nil, 0, 0) - fmt.Println("n2:", n2) - fmt.Println("n2.Incomplete() ->", n2.Incomplete()) - - // Output: - // n1: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:30303?discport=52150 - // n1.Incomplete() -> false - // n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439 - // n2.Incomplete() -> true -} - -var parseNodeTests = []struct { - rawurl string - wantError string - wantResult *Node -}{ - { - rawurl: "http://foobar", - wantError: `invalid URL scheme, want "enode"`, - }, - { - rawurl: "enode://01010101@123.124.125.126:3", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - // Complete nodes with IP address. - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", - wantError: `invalid IP address`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", - wantError: `invalid port`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", - wantError: `invalid discport in query`, - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("::"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), - 52150, - 52150, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - net.IP{0x7f, 0x0, 0x0, 0x1}, - 22334, - 52150, - ), - }, - // Incomplete nodes with no address. - { - rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - { - rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439", - wantResult: NewNode( - MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"), - nil, 0, 0, - ), - }, - // Invalid URLs - { - rawurl: "01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - rawurl: "enode://01010101", - wantError: `invalid node ID (wrong length, want 128 hex chars)`, - }, - { - // This test checks that errors from url.Parse are handled. - rawurl: "://foo", - wantError: `missing protocol scheme`, - }, -} - -func TestParseNode(t *testing.T) { - for _, test := range parseNodeTests { - n, err := ParseNode(test.rawurl) - if test.wantError != "" { - if err == nil { - t.Errorf("test %q:\n got nil error, expected %#q", test.rawurl, test.wantError) - continue - } else if !strings.Contains(err.Error(), test.wantError) { - t.Errorf("test %q:\n got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError) - continue - } - } else { - if err != nil { - t.Errorf("test %q:\n unexpected error: %v", test.rawurl, err) - continue - } - if !reflect.DeepEqual(n, test.wantResult) { - t.Errorf("test %q:\n result mismatch:\ngot: %#v, want: %#v", test.rawurl, n, test.wantResult) - } - } - } -} - -func TestNodeString(t *testing.T) { - for i, test := range parseNodeTests { - if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") { - str := test.wantResult.String() - if str != test.rawurl { - t.Errorf("test %d: Node.String() mismatch:\ngot: %s\nwant: %s", i, str, test.rawurl) - } - } - } -} - -func TestHexID(t *testing.T) { - ref := NodeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188} - id1 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc") - - if id1 != ref { - t.Errorf("wrong id1\ngot %v\nwant %v", id1[:], ref[:]) - } - if id2 != ref { - t.Errorf("wrong id2\ngot %v\nwant %v", id2[:], ref[:]) - } -} - -func TestNodeID_recover(t *testing.T) { - prv := newkey() - hash := make([]byte, 32) - sig, err := crypto.Sign(hash, prv) - if err != nil { - t.Fatalf("signing error: %v", err) - } - - pub := PubkeyID(&prv.PublicKey) - recpub, err := recoverNodeID(hash, sig) - if err != nil { - t.Fatalf("recovery error: %v", err) - } - if pub != recpub { - t.Errorf("recovered wrong pubkey:\ngot: %v\nwant: %v", recpub, pub) - } - - ecdsa, err := pub.Pubkey() - if err != nil { - t.Errorf("Pubkey error: %v", err) - } - if !reflect.DeepEqual(ecdsa, &prv.PublicKey) { - t.Errorf("Pubkey mismatch:\n got: %#v\n want: %#v", ecdsa, &prv.PublicKey) - } -} - -func TestNodeID_pubkeyBad(t *testing.T) { - ecdsa, err := NodeID{}.Pubkey() - if err == nil { - t.Error("expected error for zero ID") - } - if ecdsa != nil { - t.Error("expected nil result") - } -} - -func TestNodeID_distcmp(t *testing.T) { - distcmpBig := func(target, a, b common.Hash) int { - tbig := new(big.Int).SetBytes(target[:]) - abig := new(big.Int).SetBytes(a[:]) - bbig := new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig)) - } - if err := quick.CheckEqual(distcmp, distcmpBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_distcmpEqual(t *testing.T) { - base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - x := common.Hash{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} - if distcmp(base, x, x) != 0 { - t.Errorf("distcmp(base, x, x) != 0") - } -} - -func TestNodeID_logdist(t *testing.T) { - logdistBig := func(a, b common.Hash) int { - abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) - return new(big.Int).Xor(abig, bbig).BitLen() - } - if err := quick.CheckEqual(logdist, logdistBig, quickcfg()); err != nil { - t.Error(err) - } -} - -// the random tests is likely to miss the case where they're equal. -func TestNodeID_logdistEqual(t *testing.T) { - x := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - if logdist(x, x) != 0 { - t.Errorf("logdist(x, x) != 0") - } -} - -func TestNodeID_hashAtDistance(t *testing.T) { - // we don't use quick.Check here because its output isn't - // very helpful when the test fails. - cfg := quickcfg() - for i := 0; i < cfg.MaxCount; i++ { - a := gen(common.Hash{}, cfg.Rand).(common.Hash) - dist := cfg.Rand.Intn(len(common.Hash{}) * 8) - result := hashAtDistance(a, dist) - actualdist := logdist(result, a) - - if dist != actualdist { - t.Log("a: ", a) - t.Log("result:", result) - t.Fatalf("#%d: distance of result is %d, want %d", i, actualdist, dist) - } - } -} - -func quickcfg() *quick.Config { - return &quick.Config{ - MaxCount: 5000, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - } -} - -// TODO: The Generate method can be dropped when we require Go >= 1.5 -// because testing/quick learned to generate arrays in 1.5. - -func (NodeID) Generate(rand *rand.Rand, size int) reflect.Value { - var id NodeID - m := rand.Intn(len(id)) - for i := len(id) - 1; i > m; i-- { - id[i] = byte(rand.Uint32()) - } - return reflect.ValueOf(id) -} diff --git a/p2p/discv5/nodeevent_string.go b/p2p/discv5/nodeevent_string.go deleted file mode 100644 index 38c1993bac..0000000000 --- a/p2p/discv5/nodeevent_string.go +++ /dev/null @@ -1,17 +0,0 @@ -// Code generated by "stringer -type=nodeEvent"; DO NOT EDIT. - -package discv5 - -import "strconv" - -const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout" - -var _nodeEvent_index = [...]uint8{0, 11, 22, 39} - -func (i nodeEvent) String() string { - i -= 264 - if i >= nodeEvent(len(_nodeEvent_index)-1) { - return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")" - } - return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]] -} diff --git a/p2p/discv5/sim_run_test.go b/p2p/discv5/sim_run_test.go deleted file mode 100644 index bded0cc023..0000000000 --- a/p2p/discv5/sim_run_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bufio" - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "os" - "os/exec" - "runtime" - "strings" - "testing" -) - -func getnacl() (string, error) { - switch runtime.GOARCH { - case "amd64": - _, err := exec.LookPath("sel_ldr_x86_64") - return "amd64p32", err - case "i386": - _, err := exec.LookPath("sel_ldr_i386") - return "i386", err - default: - return "", errors.New("nacl is not supported on " + runtime.GOARCH) - } -} - -// runWithPlaygroundTime executes the caller -// in the NaCl sandbox with faketime enabled. -// -// This function must be called from a Test* function -// and the caller must skip the actual test when isHost is true. -func runWithPlaygroundTime(t *testing.T) (isHost bool) { - if runtime.GOOS == "nacl" { - return false - } - - // Get the caller. - callerPC, _, _, ok := runtime.Caller(1) - if !ok { - panic("can't get caller") - } - callerFunc := runtime.FuncForPC(callerPC) - if callerFunc == nil { - panic("can't get caller") - } - callerName := callerFunc.Name()[strings.LastIndexByte(callerFunc.Name(), '.')+1:] - if !strings.HasPrefix(callerName, "Test") { - panic("must be called from witin a Test* function") - } - testPattern := "^" + callerName + "$" - - // Unfortunately runtime.faketime (playground time mode) only works on NaCl. The NaCl - // SDK must be installed and linked into PATH for this to work. - arch, err := getnacl() - if err != nil { - t.Skip(err) - } - - // Compile and run the calling test using NaCl. - // The extra tag ensures that the TestMain function in sim_main_test.go is used. - cmd := exec.Command("go", "test", "-v", "-tags", "faketime_simulation", "-timeout", "100h", "-run", testPattern, ".") - cmd.Env = append([]string{"GOOS=nacl", "GOARCH=" + arch}, os.Environ()...) - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - go skipPlaygroundOutputHeaders(os.Stdout, stdout) - go skipPlaygroundOutputHeaders(os.Stderr, stderr) - if err := cmd.Run(); err != nil { - t.Error(err) - } - - // Ensure that the test function doesn't run in the (non-NaCl) host process. - return true -} - -func skipPlaygroundOutputHeaders(out io.Writer, in io.Reader) { - // Additional output can be printed without the headers - // before the NaCl binary starts running (e.g. compiler error messages). - bufin := bufio.NewReader(in) - output, err := bufin.ReadBytes(0) - output = bytes.TrimSuffix(output, []byte{0}) - if len(output) > 0 { - out.Write(output) - } - if err != nil { - return - } - bufin.UnreadByte() - - // Playback header: 0 0 P B <8-byte time> <4-byte data length> - head := make([]byte, 4+8+4) - for { - if _, err := io.ReadFull(bufin, head); err != nil { - if err != io.EOF { - fmt.Fprintln(out, "read error:", err) - } - return - } - if !bytes.HasPrefix(head, []byte{0x00, 0x00, 'P', 'B'}) { - fmt.Fprintf(out, "expected playback header, got %q\n", head) - io.Copy(out, bufin) - return - } - // Copy data until next header. - size := binary.BigEndian.Uint32(head[12:]) - io.CopyN(out, bufin, int64(size)) - } -} diff --git a/p2p/discv5/sim_test.go b/p2p/discv5/sim_test.go deleted file mode 100644 index 3d1e610d3a..0000000000 --- a/p2p/discv5/sim_test.go +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "crypto/ecdsa" - "encoding/binary" - "fmt" - "math/rand" - "net" - "strconv" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" -) - -// In this test, nodes try to randomly resolve each other. -func TestSimRandomResolve(t *testing.T) { - t.Skip("boring") - if runWithPlaygroundTime(t) { - return - } - - sim := newSimulation() - bootnode := sim.launchNode(false) - - // A new node joins every 10s. - launcher := time.NewTicker(10 * time.Second) - defer launcher.Stop() - go func() { - for range launcher.C { - net := sim.launchNode(false) - go randomResolves(t, sim, net) - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - t.Logf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16]) - } - }() - - time.Sleep(3 * time.Hour) - sim.shutdown() - sim.printStats() -} - -func TestSimTopics(t *testing.T) { - t.Skip("NaCl test") - if runWithPlaygroundTime(t) { - return - } - sim := newSimulation() - bootnode := sim.launchNode(false) - - go func() { - nets := make([]*Network, 1024) - for i := range nets { - net := sim.launchNode(false) - nets[i] = net - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - time.Sleep(time.Second * 5) - } - - for i, net := range nets { - if i < 256 { - stop := make(chan struct{}) - go net.RegisterTopic(testTopic, stop) - go func() { - //time.Sleep(time.Second * 36000) - time.Sleep(time.Second * 40000) - close(stop) - }() - time.Sleep(time.Millisecond * 100) - } - // time.Sleep(time.Second * 10) - //time.Sleep(time.Second) - /*if i%500 == 499 { - time.Sleep(time.Second * 9501) - } else { - time.Sleep(time.Second) - }*/ - } - }() - - // A new node joins every 10s. - /* launcher := time.NewTicker(5 * time.Second) - cnt := 0 - var printNet *Network - go func() { - for range launcher.C { - cnt++ - if cnt <= 1000 { - log := false //(cnt == 500) - net := sim.launchNode(log) - if log { - printNet = net - } - if cnt > 500 { - go net.RegisterTopic(testTopic, nil) - } - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - } - //fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16]) - } - }() - */ - time.Sleep(55000 * time.Second) - //launcher.Stop() - sim.shutdown() - //sim.printStats() - //printNet.log.printLogs() -} - -/*func testHierarchicalTopics(i int) []Topic { - digits := strconv.FormatInt(int64(256+i/4), 4) - res := make([]Topic, 5) - for i, _ := range res { - res[i] = Topic("foo" + digits[1:i+1]) - } - return res -}*/ - -func testHierarchicalTopics(i int) []Topic { - digits := strconv.FormatInt(int64(128+i/8), 2) - res := make([]Topic, 8) - for i := range res { - res[i] = Topic("foo" + digits[1:i+1]) - } - return res -} - -func TestSimTopicHierarchy(t *testing.T) { - t.Skip("NaCl test") - if runWithPlaygroundTime(t) { - return - } - sim := newSimulation() - bootnode := sim.launchNode(false) - - go func() { - nets := make([]*Network, 1024) - for i := range nets { - net := sim.launchNode(false) - nets[i] = net - if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil { - panic(err) - } - time.Sleep(time.Second * 5) - } - - stop := make(chan struct{}) - for i, net := range nets { - //if i < 256 { - for _, topic := range testHierarchicalTopics(i)[:5] { - //fmt.Println("reg", topic) - go net.RegisterTopic(topic, stop) - } - time.Sleep(time.Millisecond * 100) - //} - } - time.Sleep(time.Second * 90000) - close(stop) - }() - - time.Sleep(100000 * time.Second) - sim.shutdown() -} - -func randomResolves(t *testing.T, s *simulation, net *Network) { - randtime := func() time.Duration { - return time.Duration(rand.Intn(50)+20) * time.Second - } - lookup := func(target NodeID) bool { - result := net.Resolve(target) - return result != nil && result.ID == target - } - - timer := time.NewTimer(randtime()) - defer timer.Stop() - for { - select { - case <-timer.C: - target := s.randomNode().Self().ID - if !lookup(target) { - t.Errorf("node %x: target %x not found", net.Self().ID[:8], target[:8]) - } - timer.Reset(randtime()) - case <-net.closed: - return - } - } -} - -type simulation struct { - mu sync.RWMutex - nodes map[NodeID]*Network - nodectr uint32 -} - -func newSimulation() *simulation { - return &simulation{nodes: make(map[NodeID]*Network)} -} - -func (s *simulation) shutdown() { - s.mu.RLock() - alive := make([]*Network, 0, len(s.nodes)) - for _, n := range s.nodes { - alive = append(alive, n) - } - defer s.mu.RUnlock() - - for _, n := range alive { - n.Close() - } -} - -func (s *simulation) printStats() { - s.mu.Lock() - defer s.mu.Unlock() - fmt.Println("node counter:", s.nodectr) - fmt.Println("alive nodes:", len(s.nodes)) - - // for _, n := range s.nodes { - // fmt.Printf("%x\n", n.tab.self.ID[:8]) - // transport := n.conn.(*simTransport) - // fmt.Println(" joined:", transport.joinTime) - // fmt.Println(" sends:", transport.hashctr) - // fmt.Println(" table size:", n.tab.count) - // } - - /*for _, n := range s.nodes { - fmt.Println() - fmt.Printf("*** Node %x\n", n.tab.self.ID[:8]) - n.log.printLogs() - }*/ - -} - -func (s *simulation) randomNode() *Network { - s.mu.Lock() - defer s.mu.Unlock() - - n := rand.Intn(len(s.nodes)) - for _, net := range s.nodes { - if n == 0 { - return net - } - n-- - } - return nil -} - -func (s *simulation) launchNode(log bool) *Network { - var ( - num = s.nodectr - key = newkey() - id = PubkeyID(&key.PublicKey) - ip = make(net.IP, 4) - ) - s.nodectr++ - binary.BigEndian.PutUint32(ip, num) - ip[0] = 10 - addr := &net.UDPAddr{IP: ip, Port: 30303} - - transport := &simTransport{joinTime: time.Now(), sender: id, senderAddr: addr, sim: s, priv: key} - net, err := newNetwork(transport, key.PublicKey, "", nil) - if err != nil { - panic("cannot launch new node: " + err.Error()) - } - - s.mu.Lock() - s.nodes[id] = net - s.mu.Unlock() - - return net -} - -type simTransport struct { - joinTime time.Time - sender NodeID - senderAddr *net.UDPAddr - sim *simulation - hashctr uint64 - priv *ecdsa.PrivateKey -} - -func (st *simTransport) localAddr() *net.UDPAddr { - return st.senderAddr -} - -func (st *simTransport) Close() {} - -func (st *simTransport) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) { - hash = st.nextHash() - var raw []byte - if ptype == pongPacket { - var err error - raw, _, err = encodePacket(st.priv, byte(ptype), data) - if err != nil { - panic(err) - } - } - - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: hash, - ev: ptype, - data: data, - rawData: raw, - }) - return hash -} - -func (st *simTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) []byte { - hash := st.nextHash() - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: hash, - ev: pingPacket, - data: &ping{ - Version: 4, - From: rpcEndpoint{IP: st.senderAddr.IP, UDP: uint16(st.senderAddr.Port), TCP: 30303}, - To: rpcEndpoint{IP: remoteAddr.IP, UDP: uint16(remoteAddr.Port), TCP: 30303}, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - Topics: topics, - }, - }) - return hash -} - -func (st *simTransport) sendFindnodeHash(remote *Node, target common.Hash) { - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: findnodeHashPacket, - data: &findnodeHash{ - Target: target, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) { - //fmt.Println("send", topics, pong) - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: topicRegisterPacket, - data: &topicRegister{ - Topics: topics, - Idx: uint(idx), - Pong: pong, - }, - }) -} - -func (st *simTransport) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) { - rnodes := make([]rpcNode, len(nodes)) - for i := range nodes { - rnodes[i] = nodeToRPC(nodes[i]) - } - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: topicNodesPacket, - data: &topicNodes{Echo: queryHash, Nodes: rnodes}, - }) -} - -func (st *simTransport) sendNeighbours(remote *Node, nodes []*Node) { - // TODO: send multiple packets - rnodes := make([]rpcNode, len(nodes)) - for i := range nodes { - rnodes[i] = nodeToRPC(nodes[i]) - } - st.sendPacket(remote.ID, ingressPacket{ - remoteID: st.sender, - remoteAddr: st.senderAddr, - hash: st.nextHash(), - ev: neighborsPacket, - data: &neighbors{ - Nodes: rnodes, - Expiration: uint64(time.Now().Unix() + int64(expiration)), - }, - }) -} - -func (st *simTransport) nextHash() []byte { - v := atomic.AddUint64(&st.hashctr, 1) - var hash common.Hash - binary.BigEndian.PutUint64(hash[:], v) - return hash[:] -} - -const packetLoss = 0 // 1/1000 - -func (st *simTransport) sendPacket(remote NodeID, p ingressPacket) { - if rand.Int31n(1000) >= packetLoss { - st.sim.mu.RLock() - recipient := st.sim.nodes[remote] - st.sim.mu.RUnlock() - - time.AfterFunc(200*time.Millisecond, func() { - recipient.reqReadPacket(p) - }) - } -} diff --git a/p2p/discv5/sim_testmain_test.go b/p2p/discv5/sim_testmain_test.go deleted file mode 100644 index 77e751c419..0000000000 --- a/p2p/discv5/sim_testmain_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build go1.4,nacl,faketime_simulation - -package discv5 - -import ( - "os" - "runtime" - "testing" - "unsafe" -) - -// Enable fake time mode in the runtime, like on the go playground. -// There is a slight chance that this won't work because some go code -// might have executed before the variable is set. - -//go:linkname faketime runtime.faketime -var faketime = 1 - -func TestMain(m *testing.M) { - // We need to use unsafe somehow in order to get access to go:linkname. - _ = unsafe.Sizeof(0) - - // Run the actual test. runWithPlaygroundTime ensures that the only test - // that runs is the one calling it. - runtime.GOMAXPROCS(8) - os.Exit(m.Run()) -} diff --git a/p2p/discv5/table.go b/p2p/discv5/table.go deleted file mode 100644 index 64c3ecd1c7..0000000000 --- a/p2p/discv5/table.go +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package discv5 is a prototype implementation of Discvery v5. -// Deprecated: do not use this package. -package discv5 - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "net" - "sort" - - "github.com/ethereum/go-ethereum/common" -) - -const ( - alpha = 3 // Kademlia concurrency factor - bucketSize = 16 // Kademlia bucket size - hashBits = len(common.Hash{}) * 8 - nBuckets = hashBits + 1 // Number of buckets - - maxFindnodeFailures = 5 -) - -type Table struct { - count int // number of nodes - buckets [nBuckets]*bucket // index of known nodes by distance - nodeAddedHook func(*Node) // for testing - self *Node // metadata of the local node -} - -// bucket contains nodes, ordered by their last activity. the entry -// that was most recently active is the first element in entries. -type bucket struct { - entries []*Node - replacements []*Node -} - -func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table { - self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port)) - tab := &Table{self: self} - for i := range tab.buckets { - tab.buckets[i] = new(bucket) - } - return tab -} - -const printTable = false - -// chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia -// buckets filled with live connections and keep the network topology healthy. -// This requires selecting addresses closer to our own with a higher probability -// in order to refresh closer buckets too. -// -// This algorithm approximates the distance distribution of existing nodes in the -// table by selecting a random node from the table and selecting a target address -// with a distance less than twice of that of the selected node. -// This algorithm will be improved later to specifically target the least recently -// used buckets. -func (tab *Table) chooseBucketRefreshTarget() common.Hash { - entries := 0 - if printTable { - fmt.Println() - } - for i, b := range &tab.buckets { - entries += len(b.entries) - if printTable { - for _, e := range b.entries { - fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex()) - } - } - } - - prefix := binary.BigEndian.Uint64(tab.self.sha[0:8]) - dist := ^uint64(0) - entry := int(randUint(uint32(entries + 1))) - for _, b := range &tab.buckets { - if entry < len(b.entries) { - n := b.entries[entry] - dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix - break - } - entry -= len(b.entries) - } - - ddist := ^uint64(0) - if dist+dist > dist { - ddist = dist - } - targetPrefix := prefix ^ randUint64n(ddist) - - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], targetPrefix) - rand.Read(target[8:]) - return target -} - -// readRandomNodes fills the given slice with random nodes from the -// table. It will not write the same node more than once. The nodes in -// the slice are copies and can be modified by the caller. -func (tab *Table) readRandomNodes(buf []*Node) (n int) { - // TODO: tree-based buckets would help here - // Find all non-empty buckets and get a fresh slice of their entries. - var buckets [][]*Node - for _, b := range &tab.buckets { - if len(b.entries) > 0 { - buckets = append(buckets, b.entries) - } - } - if len(buckets) == 0 { - return 0 - } - // Shuffle the buckets. - for i := uint32(len(buckets)) - 1; i > 0; i-- { - j := randUint(i) - buckets[i], buckets[j] = buckets[j], buckets[i] - } - // Move head of each bucket into buf, removing buckets that become empty. - var i, j int - for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) { - b := buckets[j] - buf[i] = &(*b[0]) - buckets[j] = b[1:] - if len(b) == 1 { - buckets = append(buckets[:j], buckets[j+1:]...) - } - if len(buckets) == 0 { - break - } - } - return i + 1 -} - -func randUint(max uint32) uint32 { - if max < 2 { - return 0 - } - var b [4]byte - rand.Read(b[:]) - return binary.BigEndian.Uint32(b[:]) % max -} - -func randUint64n(max uint64) uint64 { - if max < 2 { - return 0 - } - var b [8]byte - rand.Read(b[:]) - return binary.BigEndian.Uint64(b[:]) % max -} - -// closest returns the n nodes in the table that are closest to the -// given id. The caller must hold tab.mutex. -func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance { - // This is a very wasteful way to find the closest nodes but - // obviously correct. I believe that tree-based buckets would make - // this easier to implement efficiently. - close := &nodesByDistance{target: target} - for _, b := range &tab.buckets { - for _, n := range b.entries { - close.push(n, nresults) - } - } - return close -} - -// add attempts to add the given node its corresponding bucket. If the -// bucket has space available, adding the node succeeds immediately. -// Otherwise, the node is added to the replacement cache for the bucket. -func (tab *Table) add(n *Node) (contested *Node) { - //fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex()) - if n.ID == tab.self.ID { - return - } - b := tab.buckets[logdist(tab.self.sha, n.sha)] - switch { - case b.bump(n): - // n exists in b. - return nil - case len(b.entries) < bucketSize: - // b has space available. - b.addFront(n) - tab.count++ - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - return nil - default: - // b has no space left, add to replacement cache - // and revalidate the last entry. - // TODO: drop previous node - b.replacements = append(b.replacements, n) - if len(b.replacements) > bucketSize { - copy(b.replacements, b.replacements[1:]) - b.replacements = b.replacements[:len(b.replacements)-1] - } - return b.entries[len(b.entries)-1] - } -} - -// stuff adds nodes the table to the end of their corresponding bucket -// if the bucket is not full. -func (tab *Table) stuff(nodes []*Node) { -outer: - for _, n := range nodes { - if n.ID == tab.self.ID { - continue // don't add self - } - bucket := tab.buckets[logdist(tab.self.sha, n.sha)] - for i := range bucket.entries { - if bucket.entries[i].ID == n.ID { - continue outer // already in bucket - } - } - if len(bucket.entries) < bucketSize { - bucket.entries = append(bucket.entries, n) - tab.count++ - if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) - } - } - } -} - -// delete removes an entry from the node table (used to evacuate -// failed/non-bonded discovery peers). -func (tab *Table) delete(node *Node) { - //fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex()) - bucket := tab.buckets[logdist(tab.self.sha, node.sha)] - for i := range bucket.entries { - if bucket.entries[i].ID == node.ID { - bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...) - tab.count-- - return - } - } -} - -func (tab *Table) deleteReplace(node *Node) { - b := tab.buckets[logdist(tab.self.sha, node.sha)] - i := 0 - for i < len(b.entries) { - if b.entries[i].ID == node.ID { - b.entries = append(b.entries[:i], b.entries[i+1:]...) - tab.count-- - } else { - i++ - } - } - // refill from replacement cache - // TODO: maybe use random index - if len(b.entries) < bucketSize && len(b.replacements) > 0 { - ri := len(b.replacements) - 1 - b.addFront(b.replacements[ri]) - tab.count++ - b.replacements[ri] = nil - b.replacements = b.replacements[:ri] - } -} - -func (b *bucket) addFront(n *Node) { - b.entries = append(b.entries, nil) - copy(b.entries[1:], b.entries) - b.entries[0] = n -} - -func (b *bucket) bump(n *Node) bool { - for i := range b.entries { - if b.entries[i].ID == n.ID { - // move it to the front - copy(b.entries[1:], b.entries[:i]) - b.entries[0] = n - return true - } - } - return false -} - -// nodesByDistance is a list of nodes, ordered by -// distance to target. -type nodesByDistance struct { - entries []*Node - target common.Hash -} - -// push adds the given node to the list, keeping the total size below maxElems. -func (h *nodesByDistance) push(n *Node, maxElems int) { - ix := sort.Search(len(h.entries), func(i int) bool { - return distcmp(h.target, h.entries[i].sha, n.sha) > 0 - }) - if len(h.entries) < maxElems { - h.entries = append(h.entries, n) - } - if ix == len(h.entries) { - // farther away than all nodes we already have. - // if there was room for it, the node is now the last element. - } else { - // slide existing entries down to make room - // this will overwrite the entry we just appended. - copy(h.entries[ix+1:], h.entries[ix:]) - h.entries[ix] = n - } -} diff --git a/p2p/discv5/table_test.go b/p2p/discv5/table_test.go deleted file mode 100644 index 872a4f6836..0000000000 --- a/p2p/discv5/table_test.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "crypto/ecdsa" - "fmt" - "math/rand" - - "net" - "reflect" - "testing" - "testing/quick" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -func TestBucket_bumpNoDuplicates(t *testing.T) { - t.Parallel() - cfg := &quick.Config{ - MaxCount: 1000, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - // generate a random list of nodes. this will be the content of the bucket. - n := rand.Intn(bucketSize-1) + 1 - nodes := make([]*Node, n) - for i := range nodes { - nodes[i] = nodeAtDistance(common.Hash{}, 200) - } - args[0] = reflect.ValueOf(nodes) - // generate random bump positions. - bumps := make([]int, rand.Intn(100)) - for i := range bumps { - bumps[i] = rand.Intn(len(nodes)) - } - args[1] = reflect.ValueOf(bumps) - }, - } - - prop := func(nodes []*Node, bumps []int) (ok bool) { - b := &bucket{entries: make([]*Node, len(nodes))} - copy(b.entries, nodes) - for i, pos := range bumps { - b.bump(b.entries[pos]) - if hasDuplicates(b.entries) { - t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps)) - for _, n := range b.entries { - t.Logf(" %p", n) - } - return false - } - } - return true - } - if err := quick.Check(prop, cfg); err != nil { - t.Error(err) - } -} - -// nodeAtDistance creates a node for which logdist(base, n.sha) == ld. -// The node's ID does not correspond to n.sha. -func nodeAtDistance(base common.Hash, ld int) (n *Node) { - n = new(Node) - n.sha = hashAtDistance(base, ld) - copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID - return n -} - -func TestTable_closest(t *testing.T) { - t.Parallel() - - test := func(test *closeTest) bool { - // for any node table, Target and N - tab := newTable(test.Self, &net.UDPAddr{}) - tab.stuff(test.All) - - // check that doClosest(Target, N) returns nodes - result := tab.closest(test.Target, test.N).entries - if hasDuplicates(result) { - t.Errorf("result contains duplicates") - return false - } - if !sortedByDistanceTo(test.Target, result) { - t.Errorf("result is not sorted by distance to target") - return false - } - - // check that the number of results is min(N, tablen) - wantN := test.N - if tab.count < test.N { - wantN = tab.count - } - if len(result) != wantN { - t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN) - return false - } else if len(result) == 0 { - return true // no need to check distance - } - - // check that the result nodes have minimum distance to target. - for _, b := range tab.buckets { - for _, n := range b.entries { - if contains(result, n.ID) { - continue // don't run the check below for nodes in result - } - farthestResult := result[len(result)-1].sha - if distcmp(test.Target, n.sha, farthestResult) < 0 { - t.Errorf("table contains node that is closer to target but it's not in result") - t.Logf(" Target: %v", test.Target) - t.Logf(" Farthest Result: %v", farthestResult) - t.Logf(" ID: %v", n.ID) - return false - } - } - } - return true - } - if err := quick.Check(test, quickcfg()); err != nil { - t.Error(err) - } -} - -func TestTable_ReadRandomNodesGetAll(t *testing.T) { - cfg := &quick.Config{ - MaxCount: 200, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000))) - }, - } - test := func(buf []*Node) bool { - tab := newTable(NodeID{}, &net.UDPAddr{}) - for i := 0; i < len(buf); i++ { - ld := cfg.Rand.Intn(len(tab.buckets)) - tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)}) - } - gotN := tab.readRandomNodes(buf) - if gotN != tab.count { - t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.count) - return false - } - if hasDuplicates(buf[:gotN]) { - t.Errorf("result contains duplicates") - return false - } - return true - } - if err := quick.Check(test, cfg); err != nil { - t.Error(err) - } -} - -type closeTest struct { - Self NodeID - Target common.Hash - All []*Node - N int -} - -func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value { - t := &closeTest{ - Self: gen(NodeID{}, rand).(NodeID), - Target: gen(common.Hash{}, rand).(common.Hash), - N: rand.Intn(bucketSize), - } - for _, id := range gen([]NodeID{}, rand).([]NodeID) { - t.All = append(t.All, &Node{ID: id}) - } - return reflect.ValueOf(t) -} - -func hasDuplicates(slice []*Node) bool { - seen := make(map[NodeID]bool) - for i, e := range slice { - if e == nil { - panic(fmt.Sprintf("nil *Node at %d", i)) - } - if seen[e.ID] { - return true - } - seen[e.ID] = true - } - return false -} - -func sortedByDistanceTo(distbase common.Hash, slice []*Node) bool { - var last common.Hash - for i, e := range slice { - if i > 0 && distcmp(distbase, e.sha, last) < 0 { - return false - } - last = e.sha - } - return true -} - -func contains(ns []*Node, id NodeID) bool { - for _, n := range ns { - if n.ID == id { - return true - } - } - return false -} - -// gen wraps quick.Value so it's easier to use. -// it generates a random value of the given value's type. -func gen(typ interface{}, rand *rand.Rand) interface{} { - v, ok := quick.Value(reflect.TypeOf(typ), rand) - if !ok { - panic(fmt.Sprintf("couldn't generate random value of type %T", typ)) - } - return v.Interface() -} - -func newkey() *ecdsa.PrivateKey { - key, err := crypto.GenerateKey() - if err != nil { - panic("couldn't generate key: " + err.Error()) - } - return key -} diff --git a/p2p/discv5/ticket.go b/p2p/discv5/ticket.go deleted file mode 100644 index c5e3d6c08f..0000000000 --- a/p2p/discv5/ticket.go +++ /dev/null @@ -1,884 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "math/rand" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" -) - -const ( - ticketTimeBucketLen = time.Minute - collectFrequency = time.Second * 30 - registerFrequency = time.Second * 60 - maxCollectDebt = 10 - maxRegisterDebt = 5 - keepTicketConst = time.Minute * 10 - keepTicketExp = time.Minute * 5 - targetWaitTime = time.Minute * 10 - topicQueryTimeout = time.Second * 5 - topicQueryResend = time.Minute - // topic radius detection - maxRadius = 0xffffffffffffffff - radiusTC = time.Minute * 20 - radiusBucketsPerBit = 8 - minSlope = 1 - minPeakSize = 40 - maxNoAdjust = 20 - lookupWidth = 8 - minRightSum = 20 - searchForceQuery = 4 -) - -// timeBucket represents absolute monotonic time in minutes. -// It is used as the index into the per-topic ticket buckets. -type timeBucket int - -type ticket struct { - topics []Topic - regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used. - - // The serial number that was issued by the server. - serial uint32 - // Used by registrar, tracks absolute time when the ticket was created. - issueTime mclock.AbsTime - - // Fields used only by registrants - node *Node // the registrar node that signed this ticket - refCnt int // tracks number of topics that will be registered using this ticket - pong []byte // encoded pong packet signed by the registrar -} - -// ticketRef refers to a single topic in a ticket. -type ticketRef struct { - t *ticket - idx int // index of the topic in t.topics and t.regTime -} - -func (ref ticketRef) topic() Topic { - return ref.t.topics[ref.idx] -} - -func (ref ticketRef) topicRegTime() mclock.AbsTime { - return ref.t.regTime[ref.idx] -} - -func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) { - wps := p.data.(*pong).WaitPeriods - if len(topics) != len(wps) { - return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps)) - } - if rlpHash(topics) != p.data.(*pong).TopicHash { - return nil, fmt.Errorf("bad topic hash") - } - t := &ticket{ - issueTime: localTime, - node: node, - topics: topics, - pong: p.rawData, - regTime: make([]mclock.AbsTime, len(wps)), - } - // Convert wait periods to local absolute time. - for i, wp := range wps { - t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp)) - } - return t, nil -} - -func ticketToPong(t *ticket, pong *pong) { - pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second)) - pong.TopicHash = rlpHash(t.topics) - pong.TicketSerial = t.serial - pong.WaitPeriods = make([]uint32, len(t.regTime)) - for i, regTime := range t.regTime { - pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second) - } -} - -type ticketStore struct { - // radius detector and target address generator - // exists for both searched and registered topics - radius map[Topic]*topicRadius - - // Contains buckets (for each absolute minute) of tickets - // that can be used in that minute. - // This is only set if the topic is being registered. - tickets map[Topic]*topicTickets - - regQueue []Topic // Topic registration queue for round robin attempts - regSet map[Topic]struct{} // Topic registration queue contents for fast filling - - nodes map[*Node]*ticket - nodeLastReq map[*Node]reqInfo - - lastBucketFetched timeBucket - nextTicketCached *ticketRef - - searchTopicMap map[Topic]searchTopic - nextTopicQueryCleanup mclock.AbsTime - queriesSent map[*Node]map[common.Hash]sentQuery -} - -type searchTopic struct { - foundChn chan<- *Node -} - -type sentQuery struct { - sent mclock.AbsTime - lookup lookupInfo -} - -type topicTickets struct { - buckets map[timeBucket][]ticketRef - nextLookup mclock.AbsTime - nextReg mclock.AbsTime -} - -func newTicketStore() *ticketStore { - return &ticketStore{ - radius: make(map[Topic]*topicRadius), - tickets: make(map[Topic]*topicTickets), - regSet: make(map[Topic]struct{}), - nodes: make(map[*Node]*ticket), - nodeLastReq: make(map[*Node]reqInfo), - searchTopicMap: make(map[Topic]searchTopic), - queriesSent: make(map[*Node]map[common.Hash]sentQuery), - } -} - -// addTopic starts tracking a topic. If register is true, -// the local node will register the topic and tickets will be collected. -func (s *ticketStore) addTopic(topic Topic, register bool) { - log.Trace("Adding discovery topic", "topic", topic, "register", register) - if s.radius[topic] == nil { - s.radius[topic] = newTopicRadius(topic) - } - if register && s.tickets[topic] == nil { - s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)} - } -} - -func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) { - s.addTopic(t, false) - if s.searchTopicMap[t].foundChn == nil { - s.searchTopicMap[t] = searchTopic{foundChn: foundChn} - } -} - -func (s *ticketStore) removeSearchTopic(t Topic) { - if st := s.searchTopicMap[t]; st.foundChn != nil { - delete(s.searchTopicMap, t) - } -} - -// removeRegisterTopic deletes all tickets for the given topic. -func (s *ticketStore) removeRegisterTopic(topic Topic) { - log.Trace("Removing discovery topic", "topic", topic) - if s.tickets[topic] == nil { - log.Warn("Removing non-existent discovery topic", "topic", topic) - return - } - for _, list := range s.tickets[topic].buckets { - for _, ref := range list { - ref.t.refCnt-- - if ref.t.refCnt == 0 { - delete(s.nodes, ref.t.node) - delete(s.nodeLastReq, ref.t.node) - } - } - } - delete(s.tickets, topic) -} - -func (s *ticketStore) regTopicSet() []Topic { - topics := make([]Topic, 0, len(s.tickets)) - for topic := range s.tickets { - topics = append(topics, topic) - } - return topics -} - -// nextRegisterLookup returns the target of the next lookup for ticket collection. -func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) { - // Queue up any new topics (or discarded ones), preserving iteration order - for topic := range s.tickets { - if _, ok := s.regSet[topic]; !ok { - s.regQueue = append(s.regQueue, topic) - s.regSet[topic] = struct{}{} - } - } - // Iterate over the set of all topics and look up the next suitable one - for len(s.regQueue) > 0 { - // Fetch the next topic from the queue, and ensure it still exists - topic := s.regQueue[0] - s.regQueue = s.regQueue[1:] - delete(s.regSet, topic) - - if s.tickets[topic] == nil { - continue - } - // If the topic needs more tickets, return it - if s.tickets[topic].nextLookup < mclock.Now() { - next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond - log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay) - return next, delay - } - } - // No registration topics found or all exhausted, sleep - delay := 40 * time.Second - log.Trace("No topic found to register", "delay", delay) - return lookupInfo{}, delay -} - -func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo { - tr := s.radius[topic] - target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery) - if target.radiusLookup { - tr.radiusLookupCnt++ - } else { - tr.radiusLookupCnt = 0 - } - return target -} - -func (s *ticketStore) addTicketRef(r ticketRef) { - topic := r.t.topics[r.idx] - tickets := s.tickets[topic] - if tickets == nil { - log.Warn("Adding ticket to non-existent topic", "topic", topic) - return - } - bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen)) - tickets.buckets[bucket] = append(tickets.buckets[bucket], r) - r.t.refCnt++ - - min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt - if tickets.nextLookup < min { - tickets.nextLookup = min - } - tickets.nextLookup += mclock.AbsTime(collectFrequency) - - //s.removeExcessTickets(topic) -} - -func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) { - now := mclock.Now() - for { - ticket, wait := s.nextRegisterableTicket() - if ticket == nil { - return ticket, wait - } - log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait) - - regTime := now + mclock.AbsTime(wait) - topic := ticket.t.topics[ticket.idx] - if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg { - return ticket, wait - } - s.removeTicketRef(*ticket) - } -} - -func (s *ticketStore) ticketRegistered(ref ticketRef) { - now := mclock.Now() - - topic := ref.t.topics[ref.idx] - tickets := s.tickets[topic] - min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt - if min > tickets.nextReg { - tickets.nextReg = min - } - tickets.nextReg += mclock.AbsTime(registerFrequency) - s.tickets[topic] = tickets - - s.removeTicketRef(ref) -} - -// nextRegisterableTicket returns the next ticket that can be used -// to register. -// -// If the returned wait time <= zero the ticket can be used. For a positive -// wait time, the caller should requery the next ticket later. -// -// A ticket can be returned more than once with <= zero wait time in case -// the ticket contains multiple topics. -func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) { - now := mclock.Now() - if s.nextTicketCached != nil { - return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now) - } - - for bucket := s.lastBucketFetched; ; bucket++ { - var ( - empty = true // true if there are no tickets - nextTicket ticketRef // uninitialized if this bucket is empty - ) - for _, tickets := range s.tickets { - //s.removeExcessTickets(topic) - if len(tickets.buckets) != 0 { - empty = false - - list := tickets.buckets[bucket] - for _, ref := range list { - //debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now))) - if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() { - nextTicket = ref - } - } - } - } - if empty { - return nil, 0 - } - if nextTicket.t != nil { - s.nextTicketCached = &nextTicket - return &nextTicket, time.Duration(nextTicket.topicRegTime() - now) - } - s.lastBucketFetched = bucket - } -} - -// removeTicket removes a ticket from the ticket store -func (s *ticketStore) removeTicketRef(ref ticketRef) { - log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial) - - // Make nextRegisterableTicket return the next available ticket. - s.nextTicketCached = nil - - topic := ref.topic() - tickets := s.tickets[topic] - - if tickets == nil { - log.Trace("Removing tickets from unknown topic", "topic", topic) - return - } - bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen)) - list := tickets.buckets[bucket] - idx := -1 - for i, bt := range list { - if bt.t == ref.t { - idx = i - break - } - } - if idx == -1 { - panic(nil) - } - list = append(list[:idx], list[idx+1:]...) - if len(list) != 0 { - tickets.buckets[bucket] = list - } else { - delete(tickets.buckets, bucket) - } - ref.t.refCnt-- - if ref.t.refCnt == 0 { - delete(s.nodes, ref.t.node) - delete(s.nodeLastReq, ref.t.node) - } -} - -type lookupInfo struct { - target common.Hash - topic Topic - radiusLookup bool -} - -type reqInfo struct { - pingHash []byte - lookup lookupInfo - time mclock.AbsTime -} - -// returns -1 if not found -func (t *ticket) findIdx(topic Topic) int { - for i, tt := range t.topics { - if tt == topic { - return i - } - } - return -1 -} - -func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) { - now := mclock.Now() - for i, n := range nodes { - if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { - if lookup.radiusLookup { - if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { - s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} - } - } else { - if s.nodes[n] == nil { - s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} - } - } - } - } -} - -func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, query func(n *Node, topic Topic) []byte) { - now := mclock.Now() - for i, n := range nodes { - if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { - if lookup.radiusLookup { - if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { - s.nodeLastReq[n] = reqInfo{pingHash: nil, lookup: lookup, time: now} - } - } // else { - if s.canQueryTopic(n, lookup.topic) { - hash := query(n, lookup.topic) - if hash != nil { - s.addTopicQuery(common.BytesToHash(hash), n, lookup) - } - } - //} - } - } -} - -func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) { - for i, topic := range t.topics { - if tt, ok := s.radius[topic]; ok { - tt.adjustWithTicket(now, targetHash, ticketRef{t, i}) - } - } -} - -func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, ticket *ticket) { - log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial) - - lastReq, ok := s.nodeLastReq[ticket.node] - if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) { - return - } - s.adjustWithTicket(localTime, lastReq.lookup.target, ticket) - - if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil { - return - } - - topic := lastReq.lookup.topic - topicIdx := ticket.findIdx(topic) - if topicIdx == -1 { - return - } - - bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen)) - if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched { - s.lastBucketFetched = bucket - } - - if _, ok := s.tickets[topic]; ok { - wait := ticket.regTime[topicIdx] - localTime - rnd := rand.ExpFloat64() - if rnd > 10 { - rnd = 10 - } - if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd { - // use the ticket to register this topic - //fmt.Println("addTicket", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong) - s.addTicketRef(ticketRef{ticket, topicIdx}) - } - } - - if ticket.refCnt > 0 { - s.nextTicketCached = nil - s.nodes[ticket.node] = ticket - } -} - -func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool { - qq := s.queriesSent[node] - if qq != nil { - now := mclock.Now() - for _, sq := range qq { - if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) { - return false - } - } - } - return true -} - -func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) { - now := mclock.Now() - qq := s.queriesSent[node] - if qq == nil { - qq = make(map[common.Hash]sentQuery) - s.queriesSent[node] = qq - } - qq[hash] = sentQuery{sent: now, lookup: lookup} - s.cleanupTopicQueries(now) -} - -func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) { - if s.nextTopicQueryCleanup > now { - return - } - exp := now - mclock.AbsTime(topicQueryResend) - for n, qq := range s.queriesSent { - for h, q := range qq { - if q.sent < exp { - delete(qq, h) - } - } - if len(qq) == 0 { - delete(s.queriesSent, n) - } - } - s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout) -} - -func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) { - now := mclock.Now() - //fmt.Println("got", from.addr().String(), hash, len(nodes)) - qq := s.queriesSent[from] - if qq == nil { - return true - } - q, ok := qq[hash] - if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) { - return true - } - inside := float64(0) - if len(nodes) > 0 { - inside = 1 - } - s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside) - chn := s.searchTopicMap[q.lookup.topic].foundChn - if chn == nil { - //fmt.Println("no channel") - return false - } - for _, node := range nodes { - ip := node.IP - if ip.IsUnspecified() || ip.IsLoopback() { - ip = from.IP - } - n := NewNode(node.ID, ip, node.UDP, node.TCP) - select { - case chn <- n: - default: - return false - } - } - return false -} - -type topicRadius struct { - topic Topic - topicHashPrefix uint64 - radius, minRadius uint64 - buckets []topicRadiusBucket - converged bool - radiusLookupCnt int -} - -type topicRadiusEvent int - -const ( - trOutside topicRadiusEvent = iota - trInside - trNoAdjust - trCount -) - -type topicRadiusBucket struct { - weights [trCount]float64 - lastTime mclock.AbsTime - value float64 - lookupSent map[common.Hash]mclock.AbsTime -} - -func (b *topicRadiusBucket) update(now mclock.AbsTime) { - if now == b.lastTime { - return - } - exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC)) - for i, w := range b.weights { - b.weights[i] = w * exp - } - b.lastTime = now - - for target, tm := range b.lookupSent { - if now-tm > mclock.AbsTime(respTimeout) { - b.weights[trNoAdjust] += 1 - delete(b.lookupSent, target) - } - } -} - -func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) { - b.update(now) - if inside <= 0 { - b.weights[trOutside] += 1 - } else { - if inside >= 1 { - b.weights[trInside] += 1 - } else { - b.weights[trInside] += inside - b.weights[trOutside] += 1 - inside - } - } -} - -func newTopicRadius(t Topic) *topicRadius { - topicHash := crypto.Keccak256Hash([]byte(t)) - topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8]) - - return &topicRadius{ - topic: t, - topicHashPrefix: topicHashPrefix, - radius: maxRadius, - minRadius: maxRadius, - } -} - -func (r *topicRadius) getBucketIdx(addrHash common.Hash) int { - prefix := binary.BigEndian.Uint64(addrHash[0:8]) - var log2 float64 - if prefix != r.topicHashPrefix { - log2 = math.Log2(float64(prefix ^ r.topicHashPrefix)) - } - bucket := int((64 - log2) * radiusBucketsPerBit) - max := 64*radiusBucketsPerBit - 1 - if bucket > max { - return max - } - if bucket < 0 { - return 0 - } - return bucket -} - -func (r *topicRadius) targetForBucket(bucket int) common.Hash { - min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit) - max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit) - a := uint64(min) - b := randUint64n(uint64(max - min)) - xor := a + b - if xor < a { - xor = ^uint64(0) - } - prefix := r.topicHashPrefix ^ xor - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], prefix) - globalRandRead(target[8:]) - return target -} - -// package rand provides a Read function in Go 1.6 and later, but -// we can't use it yet because we still support Go 1.5. -func globalRandRead(b []byte) { - pos := 0 - val := 0 - for n := 0; n < len(b); n++ { - if pos == 0 { - val = rand.Int() - pos = 7 - } - b[n] = byte(val) - val >>= 8 - pos-- - } -} - -func (r *topicRadius) chooseLookupBucket(a, b int) int { - if a < 0 { - a = 0 - } - if a > b { - return -1 - } - c := 0 - for i := a; i <= b; i++ { - if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { - c++ - } - } - if c == 0 { - return -1 - } - rnd := randUint(uint32(c)) - for i := a; i <= b; i++ { - if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { - if rnd == 0 { - return i - } - rnd-- - } - } - panic(nil) // should never happen -} - -func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool { - var max float64 - if a < 0 { - a = 0 - } - if b >= len(r.buckets) { - b = len(r.buckets) - 1 - if r.buckets[b].value > max { - max = r.buckets[b].value - } - } - if b >= a { - for i := a; i <= b; i++ { - if r.buckets[i].value > max { - max = r.buckets[i].value - } - } - } - return maxValue-max < minPeakSize -} - -func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) { - maxBucket := 0 - maxValue := float64(0) - now := mclock.Now() - v := float64(0) - for i := range r.buckets { - r.buckets[i].update(now) - v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside] - r.buckets[i].value = v - //fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust]) - } - //fmt.Println() - slopeCross := -1 - for i, b := range r.buckets { - v := b.value - if v < float64(i)*minSlope { - slopeCross = i - break - } - if v > maxValue { - maxValue = v - maxBucket = i + 1 - } - } - - minRadBucket := len(r.buckets) - sum := float64(0) - for minRadBucket > 0 && sum < minRightSum { - minRadBucket-- - b := r.buckets[minRadBucket] - sum += b.weights[trInside] + b.weights[trOutside] - } - r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit)) - - lookupLeft := -1 - if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) { - lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1) - } - lookupRight := -1 - if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) { - for len(r.buckets) <= maxBucket+lookupWidth { - r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)}) - } - lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1) - } - if lookupLeft == -1 { - radiusLookup = lookupRight - } else { - if lookupRight == -1 { - radiusLookup = lookupLeft - } else { - if randUint(2) == 0 { - radiusLookup = lookupLeft - } else { - radiusLookup = lookupRight - } - } - } - - //fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue) - - if radiusLookup == -1 { - // no more radius lookups needed at the moment, return a radius - r.converged = true - rad := maxBucket - if minRadBucket < rad { - rad = minRadBucket - } - radius = ^uint64(0) - if rad > 0 { - radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit)) - } - r.radius = radius - } - - return -} - -func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo { - if !forceRegular { - _, radiusLookup := r.recalcRadius() - if radiusLookup != -1 { - target := r.targetForBucket(radiusLookup) - r.buckets[radiusLookup].lookupSent[target] = mclock.Now() - return lookupInfo{target: target, topic: r.topic, radiusLookup: true} - } - } - - radExt := r.radius / 2 - if radExt > maxRadius-r.radius { - radExt = maxRadius - r.radius - } - rnd := randUint64n(r.radius) + randUint64n(2*radExt) - if rnd > radExt { - rnd -= radExt - } else { - rnd = radExt - rnd - } - - prefix := r.topicHashPrefix ^ rnd - var target common.Hash - binary.BigEndian.PutUint64(target[0:8], prefix) - globalRandRead(target[8:]) - return lookupInfo{target: target, topic: r.topic, radiusLookup: false} -} - -func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) { - wait := t.t.regTime[t.idx] - t.t.issueTime - inside := float64(wait)/float64(targetWaitTime) - 0.5 - if inside > 1 { - inside = 1 - } - if inside < 0 { - inside = 0 - } - r.adjust(now, targetHash, t.t.node.sha, inside) -} - -func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) { - bucket := r.getBucketIdx(addrHash) - //fmt.Println("adjust", bucket, len(r.buckets), inside) - if bucket >= len(r.buckets) { - return - } - r.buckets[bucket].adjust(now, inside) - delete(r.buckets[bucket].lookupSent, targetHash) -} diff --git a/p2p/discv5/topic.go b/p2p/discv5/topic.go deleted file mode 100644 index 609a41297f..0000000000 --- a/p2p/discv5/topic.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "container/heap" - "fmt" - "math" - "math/rand" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/log" -) - -const ( - maxEntries = 10000 - maxEntriesPerTopic = 50 - - fallbackRegistrationExpiry = 1 * time.Hour -) - -type Topic string - -type topicEntry struct { - topic Topic - fifoIdx uint64 - node *Node - expire mclock.AbsTime -} - -type topicInfo struct { - entries map[uint64]*topicEntry - fifoHead, fifoTail uint64 - rqItem *topicRequestQueueItem - wcl waitControlLoop -} - -// removes tail element from the fifo -func (t *topicInfo) getFifoTail() *topicEntry { - for t.entries[t.fifoTail] == nil { - t.fifoTail++ - } - tail := t.entries[t.fifoTail] - t.fifoTail++ - return tail -} - -type nodeInfo struct { - entries map[Topic]*topicEntry - lastIssuedTicket, lastUsedTicket uint32 - // you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time) - noRegUntil mclock.AbsTime -} - -type topicTable struct { - db *nodeDB - self *Node - nodes map[*Node]*nodeInfo - topics map[Topic]*topicInfo - globalEntries uint64 - requested topicRequestQueue - requestCnt uint64 - lastGarbageCollection mclock.AbsTime -} - -func newTopicTable(db *nodeDB, self *Node) *topicTable { - if printTestImgLogs { - fmt.Printf("*N %016x\n", self.sha[:8]) - } - return &topicTable{ - db: db, - nodes: make(map[*Node]*nodeInfo), - topics: make(map[Topic]*topicInfo), - self: self, - } -} - -func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo { - ti := t.topics[topic] - if ti == nil { - rqItem := &topicRequestQueueItem{ - topic: topic, - priority: t.requestCnt, - } - ti = &topicInfo{ - entries: make(map[uint64]*topicEntry), - rqItem: rqItem, - } - t.topics[topic] = ti - heap.Push(&t.requested, rqItem) - } - return ti -} - -func (t *topicTable) checkDeleteTopic(topic Topic) { - ti := t.topics[topic] - if ti == nil { - return - } - if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() { - delete(t.topics, topic) - heap.Remove(&t.requested, ti.rqItem.index) - } -} - -func (t *topicTable) getOrNewNode(node *Node) *nodeInfo { - n := t.nodes[node] - if n == nil { - //fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) - var issued, used uint32 - if t.db != nil { - issued, used = t.db.fetchTopicRegTickets(node.ID) - } - n = &nodeInfo{ - entries: make(map[Topic]*topicEntry), - lastIssuedTicket: issued, - lastUsedTicket: used, - } - t.nodes[node] = n - } - return n -} - -func (t *topicTable) checkDeleteNode(node *Node) { - if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < mclock.Now() { - //fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8]) - delete(t.nodes, node) - } -} - -func (t *topicTable) storeTicketCounters(node *Node) { - n := t.getOrNewNode(node) - if t.db != nil { - t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket) - } -} - -func (t *topicTable) getEntries(topic Topic) []*Node { - t.collectGarbage() - - te := t.topics[topic] - if te == nil { - return nil - } - nodes := make([]*Node, len(te.entries)) - i := 0 - for _, e := range te.entries { - nodes[i] = e.node - i++ - } - t.requestCnt++ - t.requested.update(te.rqItem, t.requestCnt) - return nodes -} - -func (t *topicTable) addEntry(node *Node, topic Topic) { - n := t.getOrNewNode(node) - // clear previous entries by the same node - for _, e := range n.entries { - t.deleteEntry(e) - } - // *** - n = t.getOrNewNode(node) - - tm := mclock.Now() - te := t.getOrNewTopic(topic) - - if len(te.entries) == maxEntriesPerTopic { - t.deleteEntry(te.getFifoTail()) - } - - if t.globalEntries == maxEntries { - t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil - } - - fifoIdx := te.fifoHead - te.fifoHead++ - entry := &topicEntry{ - topic: topic, - fifoIdx: fifoIdx, - node: node, - expire: tm + mclock.AbsTime(fallbackRegistrationExpiry), - } - if printTestImgLogs { - fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8]) - } - te.entries[fifoIdx] = entry - n.entries[topic] = entry - t.globalEntries++ - te.wcl.registered(tm) -} - -// removes least requested element from the fifo -func (t *topicTable) leastRequested() *topicEntry { - for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil { - heap.Pop(&t.requested) - } - if t.requested.Len() == 0 { - return nil - } - return t.topics[t.requested[0].topic].getFifoTail() -} - -// entry should exist -func (t *topicTable) deleteEntry(e *topicEntry) { - if printTestImgLogs { - fmt.Printf("*- %d %v %016x %016x\n", mclock.Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8]) - } - ne := t.nodes[e.node].entries - delete(ne, e.topic) - if len(ne) == 0 { - t.checkDeleteNode(e.node) - } - te := t.topics[e.topic] - delete(te.entries, e.fifoIdx) - if len(te.entries) == 0 { - t.checkDeleteTopic(e.topic) - } - t.globalEntries-- -} - -// It is assumed that topics and waitPeriods have the same length. -func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) { - log.Trace("Using discovery ticket", "serial", serialNo, "topics", topics, "waits", waitPeriods) - //fmt.Println("useTicket", serialNo, topics, waitPeriods) - t.collectGarbage() - - n := t.getOrNewNode(node) - if serialNo < n.lastUsedTicket { - return false - } - - tm := mclock.Now() - if serialNo > n.lastUsedTicket && tm < n.noRegUntil { - return false - } - if serialNo != n.lastUsedTicket { - n.lastUsedTicket = serialNo - n.noRegUntil = tm + mclock.AbsTime(noRegTimeout()) - t.storeTicketCounters(node) - } - - currTime := uint64(tm / mclock.AbsTime(time.Second)) - regTime := issueTime + uint64(waitPeriods[idx]) - relTime := int64(currTime - regTime) - if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends - if e := n.entries[topics[idx]]; e == nil { - t.addEntry(node, topics[idx]) - } else { - // if there is an active entry, don't move to the front of the FIFO but prolong expire time - e.expire = tm + mclock.AbsTime(fallbackRegistrationExpiry) - } - return true - } - - return false -} - -func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket { - t.collectGarbage() - - now := mclock.Now() - n := t.getOrNewNode(node) - n.lastIssuedTicket++ - t.storeTicketCounters(node) - - tic := &ticket{ - issueTime: now, - topics: topics, - serial: n.lastIssuedTicket, - regTime: make([]mclock.AbsTime, len(topics)), - } - for i, topic := range topics { - var waitPeriod time.Duration - if topic := t.topics[topic]; topic != nil { - waitPeriod = topic.wcl.waitPeriod - } else { - waitPeriod = minWaitPeriod - } - - tic.regTime[i] = now + mclock.AbsTime(waitPeriod) - } - return tic -} - -const gcInterval = time.Minute - -func (t *topicTable) collectGarbage() { - tm := mclock.Now() - if time.Duration(tm-t.lastGarbageCollection) < gcInterval { - return - } - t.lastGarbageCollection = tm - - for node, n := range t.nodes { - for _, e := range n.entries { - if e.expire <= tm { - t.deleteEntry(e) - } - } - - t.checkDeleteNode(node) - } - - for topic := range t.topics { - t.checkDeleteTopic(topic) - } -} - -const ( - minWaitPeriod = time.Minute - regTimeWindow = 10 // seconds - avgnoRegTimeout = time.Minute * 10 - // target average interval between two incoming ad requests - wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic - // - wcTimeConst = time.Minute * 10 -) - -// initialization is not required, will set to minWaitPeriod at first registration -type waitControlLoop struct { - lastIncoming mclock.AbsTime - waitPeriod time.Duration -} - -func (w *waitControlLoop) registered(tm mclock.AbsTime) { - w.waitPeriod = w.nextWaitPeriod(tm) - w.lastIncoming = tm -} - -func (w *waitControlLoop) nextWaitPeriod(tm mclock.AbsTime) time.Duration { - period := tm - w.lastIncoming - wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst))) - if wp < minWaitPeriod { - wp = minWaitPeriod - } - return wp -} - -func (w *waitControlLoop) hasMinimumWaitPeriod() bool { - return w.nextWaitPeriod(mclock.Now()) == minWaitPeriod -} - -func noRegTimeout() time.Duration { - e := rand.ExpFloat64() - if e > 100 { - e = 100 - } - return time.Duration(float64(avgnoRegTimeout) * e) -} - -type topicRequestQueueItem struct { - topic Topic - priority uint64 - index int -} - -// A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems. -type topicRequestQueue []*topicRequestQueueItem - -func (tq topicRequestQueue) Len() int { return len(tq) } - -func (tq topicRequestQueue) Less(i, j int) bool { - return tq[i].priority < tq[j].priority -} - -func (tq topicRequestQueue) Swap(i, j int) { - tq[i], tq[j] = tq[j], tq[i] - tq[i].index = i - tq[j].index = j -} - -func (tq *topicRequestQueue) Push(x interface{}) { - n := len(*tq) - item := x.(*topicRequestQueueItem) - item.index = n - *tq = append(*tq, item) -} - -func (tq *topicRequestQueue) Pop() interface{} { - old := *tq - n := len(old) - item := old[n-1] - item.index = -1 - *tq = old[0 : n-1] - return item -} - -func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) { - item.priority = priority - heap.Fix(tq, item.index) -} diff --git a/p2p/discv5/topic_test.go b/p2p/discv5/topic_test.go deleted file mode 100644 index ba79993f29..0000000000 --- a/p2p/discv5/topic_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "encoding/binary" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" -) - -func TestTopicRadius(t *testing.T) { - now := mclock.Now() - topic := Topic("qwerty") - rad := newTopicRadius(topic) - targetRad := (^uint64(0)) / 100 - - waitFn := func(addr common.Hash) time.Duration { - prefix := binary.BigEndian.Uint64(addr[0:8]) - dist := prefix ^ rad.topicHashPrefix - relDist := float64(dist) / float64(targetRad) - relTime := (1 - relDist/2) * 2 - if relTime < 0 { - relTime = 0 - } - return time.Duration(float64(targetWaitTime) * relTime) - } - - bcnt := 0 - cnt := 0 - var sum float64 - for cnt < 100 { - addr := rad.nextTarget(false).target - wait := waitFn(addr) - ticket := &ticket{ - topics: []Topic{topic}, - regTime: []mclock.AbsTime{mclock.AbsTime(wait)}, - node: &Node{nodeNetGuts: nodeNetGuts{sha: addr}}, - } - rad.adjustWithTicket(now, addr, ticketRef{ticket, 0}) - if rad.radius != maxRadius { - cnt++ - sum += float64(rad.radius) - } else { - bcnt++ - if bcnt > 500 { - t.Errorf("Radius did not converge in 500 iterations") - } - } - } - avgRel := sum / float64(cnt) / float64(targetRad) - if avgRel > 1.05 || avgRel < 0.95 { - t.Errorf("Average/target ratio is too far from 1 (%v)", avgRel) - } -} diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go deleted file mode 100644 index 088f95cac6..0000000000 --- a/p2p/discv5/udp.go +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discv5 - -import ( - "bytes" - "crypto/ecdsa" - "errors" - "fmt" - "net" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/netutil" - "github.com/ethereum/go-ethereum/rlp" -) - -const Version = 4 - -// Errors -var ( - errPacketTooSmall = errors.New("too small") - errBadPrefix = errors.New("bad prefix") -) - -// Timeouts -const ( - respTimeout = 500 * time.Millisecond - expiration = 20 * time.Second -) - -// RPC request structures -type ( - ping struct { - Version uint - From, To rpcEndpoint - Expiration uint64 - - // v5 - Topics []Topic - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // pong is the reply to ping. - pong struct { - // This field should mirror the UDP envelope address - // of the ping packet, which provides a way to discover the - // the external address (after NAT). - To rpcEndpoint - - ReplyTok []byte // This contains the hash of the ping packet. - Expiration uint64 // Absolute timestamp at which the packet becomes invalid. - - // v5 - TopicHash common.Hash - TicketSerial uint32 - WaitPeriods []uint32 - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnode is a query for nodes close to the given target. - findnode struct { - Target NodeID // doesn't need to be an actual public key - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // findnode is a query for nodes close to the given target. - findnodeHash struct { - Target common.Hash - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - // reply to findnode - neighbors struct { - Nodes []rpcNode - Expiration uint64 - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` - } - - topicRegister struct { - Topics []Topic - Idx uint - Pong []byte - } - - topicQuery struct { - Topic Topic - Expiration uint64 - } - - // reply to topicQuery - topicNodes struct { - Echo common.Hash - Nodes []rpcNode - } - - rpcNode struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - ID NodeID - } - - rpcEndpoint struct { - IP net.IP // len 4 for IPv4 or 16 for IPv6 - UDP uint16 // for discovery protocol - TCP uint16 // for RLPx protocol - } -) - -var ( - versionPrefix = []byte("temporary discovery v5") - versionPrefixSize = len(versionPrefix) - sigSize = 520 / 8 - headSize = versionPrefixSize + sigSize // space of packet frame data -) - -// Neighbors replies are sent across multiple packets to -// stay below the 1280 byte limit. We compute the maximum number -// of entries by stuffing a packet until it grows too large. -var maxNeighbors = func() int { - p := neighbors{Expiration: ^uint64(0)} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= 1280 { - return n - } - } -}() - -var maxTopicNodes = func() int { - p := topicNodes{} - maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)} - for n := 0; ; n++ { - p.Nodes = append(p.Nodes, maxSizeNode) - size, _, err := rlp.EncodeToReader(p) - if err != nil { - // If this ever happens, it will be caught by the unit tests. - panic("cannot encode: " + err.Error()) - } - if headSize+size+1 >= 1280 { - return n - } - } -}() - -func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint { - ip := addr.IP.To4() - if ip == nil { - ip = addr.IP.To16() - } - return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort} -} - -func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) { - if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil { - return nil, err - } - n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP) - err := n.validateComplete() - return n, err -} - -func nodeToRPC(n *Node) rpcNode { - return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP} -} - -type ingressPacket struct { - remoteID NodeID - remoteAddr *net.UDPAddr - ev nodeEvent - hash []byte - data interface{} // one of the RPC structs - rawData []byte -} - -type conn interface { - ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) - WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) - Close() error - LocalAddr() net.Addr -} - -// udp implements the RPC protocol. -type udp struct { - conn conn - priv *ecdsa.PrivateKey - ourEndpoint rpcEndpoint - net *Network -} - -// ListenUDP returns a new table that listens for UDP packets on laddr. -func ListenUDP(priv *ecdsa.PrivateKey, conn conn, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) { - realaddr := conn.LocalAddr().(*net.UDPAddr) - transport, err := listenUDP(priv, conn, realaddr) - if err != nil { - return nil, err - } - net, err := newNetwork(transport, priv.PublicKey, nodeDBPath, netrestrict) - if err != nil { - return nil, err - } - log.Info("UDP listener up", "net", net.tab.self) - transport.net = net - go transport.readLoop() - return net, nil -} - -func listenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr) (*udp, error) { - return &udp{conn: conn, priv: priv, ourEndpoint: makeEndpoint(realaddr, uint16(realaddr.Port))}, nil -} - -func (t *udp) localAddr() *net.UDPAddr { - return t.conn.LocalAddr().(*net.UDPAddr) -} - -func (t *udp) Close() { - t.conn.Close() -} - -func (t *udp) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) { - hash, _ = t.sendPacket(remote.ID, remote.addr(), byte(ptype), data) - return hash -} - -func (t *udp) sendPing(remote *Node, toaddr *net.UDPAddr, topics []Topic) (hash []byte) { - hash, _ = t.sendPacket(remote.ID, toaddr, byte(pingPacket), ping{ - Version: Version, - From: t.ourEndpoint, - To: makeEndpoint(toaddr, uint16(toaddr.Port)), // TODO: maybe use known TCP port from DB - Expiration: uint64(time.Now().Add(expiration).Unix()), - Topics: topics, - }) - return hash -} - -func (t *udp) sendNeighbours(remote *Node, results []*Node) { - // Send neighbors in chunks with at most maxNeighbors per packet - // to stay below the 1280 byte limit. - p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())} - for i, result := range results { - p.Nodes = append(p.Nodes, nodeToRPC(result)) - if len(p.Nodes) == maxNeighbors || i == len(results)-1 { - t.sendPacket(remote.ID, remote.addr(), byte(neighborsPacket), p) - p.Nodes = p.Nodes[:0] - } - } -} - -func (t *udp) sendFindnodeHash(remote *Node, target common.Hash) { - t.sendPacket(remote.ID, remote.addr(), byte(findnodeHashPacket), findnodeHash{ - Target: target, - Expiration: uint64(time.Now().Add(expiration).Unix()), - }) -} - -func (t *udp) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) { - t.sendPacket(remote.ID, remote.addr(), byte(topicRegisterPacket), topicRegister{ - Topics: topics, - Idx: uint(idx), - Pong: pong, - }) -} - -func (t *udp) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) { - p := topicNodes{Echo: queryHash} - var sent bool - for _, result := range nodes { - if result.IP.Equal(t.net.tab.self.IP) || netutil.CheckRelayIP(remote.IP, result.IP) == nil { - p.Nodes = append(p.Nodes, nodeToRPC(result)) - } - if len(p.Nodes) == maxTopicNodes { - t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) - p.Nodes = p.Nodes[:0] - sent = true - } - } - if !sent || len(p.Nodes) > 0 { - t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p) - } -} - -func (t *udp) sendPacket(toid NodeID, toaddr *net.UDPAddr, ptype byte, req interface{}) (hash []byte, err error) { - //fmt.Println("sendPacket", nodeEvent(ptype), toaddr.String(), toid.String()) - packet, hash, err := encodePacket(t.priv, ptype, req) - if err != nil { - //fmt.Println(err) - return hash, err - } - log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr)) - if nbytes, err := t.conn.WriteToUDP(packet, toaddr); err != nil { - log.Trace(fmt.Sprint("UDP send failed:", err)) - } else { - egressTrafficMeter.Mark(int64(nbytes)) - } - //fmt.Println(err) - return hash, err -} - -// zeroed padding space for encodePacket. -var headSpace = make([]byte, headSize) - -func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (p, hash []byte, err error) { - b := new(bytes.Buffer) - b.Write(headSpace) - b.WriteByte(ptype) - if err := rlp.Encode(b, req); err != nil { - log.Error(fmt.Sprint("error encoding packet:", err)) - return nil, nil, err - } - packet := b.Bytes() - sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv) - if err != nil { - log.Error(fmt.Sprint("could not sign packet:", err)) - return nil, nil, err - } - copy(packet, versionPrefix) - copy(packet[versionPrefixSize:], sig) - hash = crypto.Keccak256(packet[versionPrefixSize:]) - return packet, hash, nil -} - -// readLoop runs in its own goroutine. it injects ingress UDP packets -// into the network loop. -func (t *udp) readLoop() { - defer t.conn.Close() - // Discovery packets are defined to be no larger than 1280 bytes. - // Packets larger than this size will be cut at the end and treated - // as invalid because their hash won't match. - buf := make([]byte, 1280) - for { - nbytes, from, err := t.conn.ReadFromUDP(buf) - ingressTrafficMeter.Mark(int64(nbytes)) - if netutil.IsTemporaryError(err) { - // Ignore temporary read errors. - log.Debug(fmt.Sprintf("Temporary read error: %v", err)) - continue - } else if err != nil { - // Shut down the loop for permament errors. - log.Debug(fmt.Sprintf("Read error: %v", err)) - return - } - t.handlePacket(from, buf[:nbytes]) - } -} - -func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error { - pkt := ingressPacket{remoteAddr: from} - if err := decodePacket(buf, &pkt); err != nil { - log.Debug(fmt.Sprintf("Bad packet from %v: %v", from, err)) - //fmt.Println("bad packet", err) - return err - } - t.net.reqReadPacket(pkt) - return nil -} - -func decodePacket(buffer []byte, pkt *ingressPacket) error { - if len(buffer) < headSize+1 { - return errPacketTooSmall - } - buf := make([]byte, len(buffer)) - copy(buf, buffer) - prefix, sig, sigdata := buf[:versionPrefixSize], buf[versionPrefixSize:headSize], buf[headSize:] - if !bytes.Equal(prefix, versionPrefix) { - return errBadPrefix - } - fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig) - if err != nil { - return err - } - pkt.rawData = buf - pkt.hash = crypto.Keccak256(buf[versionPrefixSize:]) - pkt.remoteID = fromID - switch pkt.ev = nodeEvent(sigdata[0]); pkt.ev { - case pingPacket: - pkt.data = new(ping) - case pongPacket: - pkt.data = new(pong) - case findnodePacket: - pkt.data = new(findnode) - case neighborsPacket: - pkt.data = new(neighbors) - case findnodeHashPacket: - pkt.data = new(findnodeHash) - case topicRegisterPacket: - pkt.data = new(topicRegister) - case topicQueryPacket: - pkt.data = new(topicQuery) - case topicNodesPacket: - pkt.data = new(topicNodes) - default: - return fmt.Errorf("unknown packet type: %d", sigdata[0]) - } - s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) - err = s.Decode(pkt.data) - return err -} diff --git a/p2p/server.go b/p2p/server.go index 275cb5ea5c..fc71548554 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nat" @@ -105,7 +104,7 @@ type Config struct { // BootstrapNodesV5 are used to establish connectivity // with the rest of the network using the V5 discovery // protocol. - BootstrapNodesV5 []*discv5.Node `toml:",omitempty"` + BootstrapNodesV5 []*enode.Node `toml:",omitempty"` // Static nodes are used as pre-configured connections which are always // maintained and re-connected on disconnects. @@ -182,7 +181,7 @@ type Server struct { nodedb *enode.DB localnode *enode.LocalNode ntab *discover.UDPv4 - DiscV5 *discv5.Network + DiscV5 *discover.UDPv5 discmix *enode.FairMix dialsched *dialScheduler @@ -413,7 +412,7 @@ type sharedUDPConn struct { unhandled chan discover.ReadPacket } -// ReadFromUDP implements discv5.conn +// ReadFromUDP implements discover.UDPConn func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { packet, ok := <-s.unhandled if !ok { @@ -427,7 +426,7 @@ func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err err return l, packet.Addr, nil } -// Close implements discv5.conn +// Close implements discover.UDPConn func (s *sharedUDPConn) Close() error { return nil } @@ -586,7 +585,7 @@ func (srv *Server) setupDiscovery() error { Unhandled: unhandled, Log: srv.log, } - ntab, err := discover.ListenUDP(conn, srv.localnode, cfg) + ntab, err := discover.ListenV4(conn, srv.localnode, cfg) if err != nil { return err } @@ -596,20 +595,21 @@ func (srv *Server) setupDiscovery() error { // Discovery V5 if srv.DiscoveryV5 { - var ntab *discv5.Network + cfg := discover.Config{ + PrivateKey: srv.PrivateKey, + NetRestrict: srv.NetRestrict, + Bootnodes: srv.BootstrapNodesV5, + Log: srv.log, + } var err error if sconn != nil { - ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, "", srv.NetRestrict) + srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg) } else { - ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, "", srv.NetRestrict) + srv.DiscV5, err = discover.ListenV5(conn, srv.localnode, cfg) } if err != nil { return err } - if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil { - return err - } - srv.DiscV5 = ntab } return nil } diff --git a/params/bootnodes.go b/params/bootnodes.go index d4512bf789..66f917015e 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -73,6 +73,24 @@ var YoloV2Bootnodes = []string{ "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", } +var V5Bootnodes = []string{ + // Teku team's bootnode + "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", + "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA", + // Prylab team's bootnodes + "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", + "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", + "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", + // Lighthouse team's bootnodes + "enr:-IS4QLkKqDMy_ExrpOEWa59NiClemOnor-krjp4qoeZwIw2QduPC-q7Kz4u1IOWf3DDbdxqQIgC4fejavBOuUPy-HE4BgmlkgnY0gmlwhCLzAHqJc2VjcDI1NmsxoQLQSJfEAHZApkm5edTCZ_4qps_1k_ub2CxHFxi-gr2JMIN1ZHCCIyg", + "enr:-IS4QDAyibHCzYZmIYZCjXwU9BqpotWmv2BsFlIq1V31BwDDMJPFEbox1ijT5c2Ou3kvieOKejxuaCqIcjxBjJ_3j_cBgmlkgnY0gmlwhAMaHiCJc2VjcDI1NmsxoQJIdpj_foZ02MXz4It8xKD7yUHTBx7lVFn3oeRP21KRV4N1ZHCCIyg", + // EF bootnodes + "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", + "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", + "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", + "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", +} + const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@" // KnownDNSNetwork returns the address of a public DNS-based node list for the given From 2e5d14170846ae72adc47467a1129e41d6800349 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 27 Jan 2021 09:20:34 +0000 Subject: [PATCH 291/948] rpc: deprecate Client.ShhSubscribe (#22239) It never worked, whisper uses polling. Co-authored-by: Felix Lange --- rpc/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rpc/client.go b/rpc/client.go index 9393adb779..198ce63573 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -414,6 +414,7 @@ func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ... } // ShhSubscribe registers a subscripion under the "shh" namespace. +// Deprecated: use Subscribe(ctx, "shh", ...). func (c *Client) ShhSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) { return c.Subscribe(ctx, "shh", channel, args...) } From eb21c652c0a9d8f651efc0251cc5797a3328d863 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Thu, 28 Jan 2021 21:19:07 +0100 Subject: [PATCH 292/948] cmd,core,eth,params,tests: define yolov3 + enable EIP-2565 (#22213) Removes the yolov2 definition, adds yolov3, including EIP-2565. This PR also disables some of the erroneously generated blockchain and statetests, and adds the new genesis hash + alloc for yolov3. This PR disables the CLI switches for yolo, since it's not complete until we merge support for 2930. --- cmd/evm/internal/t8ntool/execution.go | 2 +- cmd/geth/chaincmd.go | 4 +-- cmd/geth/consolecmd.go | 4 +-- cmd/geth/main.go | 4 ++- cmd/geth/usage.go | 3 ++- cmd/puppeth/wizard_genesis.go | 4 +-- cmd/utils/flags.go | 28 ++++++++++----------- core/genesis.go | 14 +++++------ core/genesis_alloc.go | 3 ++- core/state_processor.go | 2 +- core/vm/contracts.go | 35 +++++++++++++++------------ core/vm/evm.go | 10 ++++---- core/vm/interpreter.go | 4 +-- core/vm/jump_table.go | 6 ++--- core/vm/runtime/runtime.go | 8 +++--- eth/tracers/api.go | 4 +-- params/bootnodes.go | 7 +++--- params/config.go | 31 ++++++++++++------------ tests/block_test.go | 7 ++++++ tests/fuzzers/bls12381/bls_fuzzer.go | 2 +- tests/init.go | 8 +++--- tests/state_test.go | 8 ++++++ tests/state_test_util.go | 2 +- 23 files changed, 112 insertions(+), 88 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 171443e156..95e6de37cb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -145,7 +145,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txContext := core.NewEVMTxContext(msg) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) - if chainConfig.IsYoloV2(vmContext.BlockNumber) { + if chainConfig.IsYoloV3(vmContext.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index f539322654..9eec30f813 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -164,7 +164,7 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.RinkebyFlag, utils.TxLookupLimitFlag, utils.GoerliFlag, - utils.YoloV2Flag, + utils.YoloV3Flag, utils.LegacyTestnetFlag, }, Category: "BLOCKCHAIN COMMANDS", @@ -215,7 +215,7 @@ Use "ethereum dump 0" to dump the genesis block.`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV2Flag, + utils.YoloV3Flag, utils.LegacyTestnetFlag, utils.SyncModeFlag, }, diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 7822c73b31..5369612e87 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -136,8 +136,8 @@ func remoteConsole(ctx *cli.Context) error { path = filepath.Join(path, "rinkeby") } else if ctx.GlobalBool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") - } else if ctx.GlobalBool(utils.YoloV2Flag.Name) { - path = filepath.Join(path, "yolo-v2") + } else if ctx.GlobalBool(utils.YoloV3Flag.Name) { + path = filepath.Join(path, "yolo-v3") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e577ab370d..86dc6f40fe 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -148,7 +148,9 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV2Flag, + // YOLOv3 is not yet complete! + // TODO: enable this once 2718/2930 is added + //utils.YoloV3Flag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 25accc9b79..ba311bf7f6 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -44,7 +44,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, - utils.YoloV2Flag, + // TODO: Re-enable this when 2718/2930 is added + //utils.YoloV3Flag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 2d014e83bc..52093975cb 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -236,8 +236,8 @@ func (w *wizard) manageGenesis() { w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock) fmt.Println() - fmt.Printf("Which block should YOLOv2 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV2Block) - w.conf.Genesis.Config.YoloV2Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV2Block) + fmt.Printf("Which block should YOLOv3 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV3Block) + w.conf.Genesis.Config.YoloV3Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV3Block) out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ") fmt.Printf("Chain configuration updated:\n\n%s\n", out) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1d6d2d86b6..8f9f68ba68 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -147,9 +147,9 @@ var ( Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", } - YoloV2Flag = cli.BoolFlag{ - Name: "yolov2", - Usage: "YOLOv2 network: pre-configured proof-of-authority shortlived test network.", + YoloV3Flag = cli.BoolFlag{ + Name: "yolov3", + Usage: "YOLOv3 network: pre-configured proof-of-authority shortlived test network.", } RinkebyFlag = cli.BoolFlag{ Name: "rinkeby", @@ -760,8 +760,8 @@ func MakeDataDir(ctx *cli.Context) string { if ctx.GlobalBool(GoerliFlag.Name) { return filepath.Join(path, "goerli") } - if ctx.GlobalBool(YoloV2Flag.Name) { - return filepath.Join(path, "yolo-v2") + if ctx.GlobalBool(YoloV3Flag.Name) { + return filepath.Join(path, "yolo-v3") } return path } @@ -819,8 +819,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls = params.RinkebyBootnodes case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV2Flag.Name): - urls = params.YoloV2Bootnodes + case ctx.GlobalBool(YoloV3Flag.Name): + urls = params.YoloV3Bootnodes case cfg.BootstrapNodes != nil: return // already set, don't apply defaults. } @@ -1280,7 +1280,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") - case ctx.GlobalBool(YoloV2Flag.Name) && cfg.DataDir == node.DefaultDataDir(): + case ctx.GlobalBool(YoloV3Flag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v2") } } @@ -1494,7 +1494,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) @@ -1631,11 +1631,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } cfg.Genesis = core.DefaultGoerliGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) - case ctx.GlobalBool(YoloV2Flag.Name): + case ctx.GlobalBool(YoloV3Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = 133519467574834 // "yolov2" + cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3")).Uint64() // "yolov3" } - cfg.Genesis = core.DefaultYoloV2GenesisBlock() + cfg.Genesis = core.DefaultYoloV3GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 @@ -1824,8 +1824,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { genesis = core.DefaultRinkebyGenesisBlock() case ctx.GlobalBool(GoerliFlag.Name): genesis = core.DefaultGoerliGenesisBlock() - case ctx.GlobalBool(YoloV2Flag.Name): - genesis = core.DefaultYoloV2GenesisBlock() + case ctx.GlobalBool(YoloV3Flag.Name): + genesis = core.DefaultYoloV3GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): Fatalf("Developer chains are ephemeral") } diff --git a/core/genesis.go b/core/genesis.go index 908a969afd..f678a3bbca 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -243,8 +243,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig - case ghash == params.YoloV2GenesisHash: - return params.YoloV2ChainConfig + case ghash == params.YoloV3GenesisHash: + return params.YoloV3ChainConfig default: return params.AllEthashProtocolChanges } @@ -380,15 +380,15 @@ func DefaultGoerliGenesisBlock() *Genesis { } } -func DefaultYoloV2GenesisBlock() *Genesis { - // TODO: Update with yolov2 values + regenerate alloc data +func DefaultYoloV3GenesisBlock() *Genesis { + // Full genesis: https://gist.github.com/holiman/b2c32a05ff2e2712e11c0787d987d46f return &Genesis{ - Config: params.YoloV2ChainConfig, - Timestamp: 0x5f91b932, + Config: params.YoloV3ChainConfig, + Timestamp: 0x60117f8b, ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), GasLimit: 0x47b760, Difficulty: big.NewInt(1), - Alloc: decodePrealloc(yoloV1AllocData), + Alloc: decodePrealloc(yoloV3AllocData), } } diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 3e03d16407..6eecbbf0e8 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -25,4 +25,5 @@ const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908' const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" -const yoloV1AllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x8a7\x86o\xd3b|\x92\x05\xa3|\x86\x85fo2\xec\a\xbb\x1b\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +const yoloV3AllocData = "\xf9\x05\x01\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + diff --git a/core/state_processor.go b/core/state_processor.go index cdc86a694e..c5b0bb1609 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -93,7 +93,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon // Create a new context to be used in the EVM environment txContext := NewEVMTxContext(msg) // Add addresses to access list if applicable - if config.IsYoloV2(header.Number) { + if config.IsYoloV3(header.Number) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 0c828b1cd9..9ea19d38a7 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -78,18 +78,23 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } -// PrecompiledContractsYoloV2 contains the default set of pre-compiled Ethereum -// contracts used in the Yolo v2 test release. -var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, +// PrecompiledContractsYoloV3 contains the default set of pre-compiled Ethereum +// contracts used in the Yolo v3 test release. +var PrecompiledContractsYoloV3 = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, +} + +// PrecompiledContractsBLS contains the set of pre-compiled Ethereum +// contracts specified in EIP-2537. These are exported for testing purposes. +var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{10}): &bls12381G1Add{}, common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, @@ -102,7 +107,7 @@ var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ } var ( - PrecompiledAddressesYoloV2 []common.Address + PrecompiledAddressesYoloV3 []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address @@ -118,8 +123,8 @@ func init() { for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) } - for k := range PrecompiledContractsYoloV2 { - PrecompiledAddressesYoloV2 = append(PrecompiledAddressesYoloV2, k) + for k := range PrecompiledContractsYoloV3 { + PrecompiledAddressesYoloV3 = append(PrecompiledAddressesYoloV3, k) } } diff --git a/core/vm/evm.go b/core/vm/evm.go index 9afef76643..3617d77b12 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -46,8 +46,8 @@ type ( // configuration func (evm *EVM) ActivePrecompiles() []common.Address { switch { - case evm.chainRules.IsYoloV2: - return PrecompiledAddressesYoloV2 + case evm.chainRules.IsYoloV3: + return PrecompiledAddressesYoloV3 case evm.chainRules.IsIstanbul: return PrecompiledAddressesIstanbul case evm.chainRules.IsByzantium: @@ -60,8 +60,8 @@ func (evm *EVM) ActivePrecompiles() []common.Address { func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { - case evm.chainRules.IsYoloV2: - precompiles = PrecompiledContractsYoloV2 + case evm.chainRules.IsYoloV3: + precompiles = PrecompiledContractsYoloV3 case evm.chainRules.IsIstanbul: precompiles = PrecompiledContractsIstanbul case evm.chainRules.IsByzantium: @@ -446,7 +446,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back - if evm.chainRules.IsYoloV2 { + if evm.chainRules.IsYoloV3 { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index bffc5013a6..967db32780 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { - case evm.chainRules.IsYoloV2: - jt = yoloV2InstructionSet + case evm.chainRules.IsYoloV3: + jt = yoloV3InstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet case evm.chainRules.IsConstantinople: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 83fb2c1ed6..954f657a94 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,16 +56,16 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() - yoloV2InstructionSet = newYoloV2InstructionSet() + yoloV3InstructionSet = newYoloV3InstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation -// newYoloV2InstructionSet creates an instructionset containing +// newYoloV3InstructionSet creates an instructionset containing // - "EIP-2315: Simple Subroutines" // - "EIP-2929: Gas cost increases for state access opcodes" -func newYoloV2InstructionSet() JumpTable { +func newYoloV3InstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 2586535f9d..7cdb4ebd22 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -65,7 +65,7 @@ func setDefaults(cfg *Config) { PetersburgBlock: new(big.Int), IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), - YoloV2Block: nil, + YoloV3Block: nil, } } @@ -113,7 +113,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { @@ -149,7 +149,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) for _, addr := range vmenv.ActivePrecompiles() { cfg.State.AddAddressToAccessList(addr) @@ -177,7 +177,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) - if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index dca990ab95..1bf59552c9 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -581,8 +581,8 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block chainConfigCopy := new(params.ChainConfig) *chainConfigCopy = *chainConfig chainConfig = chainConfigCopy - if yolov2 := config.LogConfig.Overrides.YoloV2Block; yolov2 != nil { - chainConfig.YoloV2Block = yolov2 + if yolov3 := config.LogConfig.Overrides.YoloV3Block; yolov3 != nil { + chainConfig.YoloV3Block = yolov3 canon = false } } diff --git a/params/bootnodes.go b/params/bootnodes.go index 66f917015e..f36ad61729 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -67,9 +67,10 @@ var GoerliBootnodes = []string{ "enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303", } -// YoloV2Bootnodes are the enode URLs of the P2P bootstrap nodes running on the -// YOLOv2 ephemeral test network. -var YoloV2Bootnodes = []string{ +// YoloV3Bootnodes are the enode URLs of the P2P bootstrap nodes running on the +// YOLOv3 ephemeral test network. +// TODO: Set Yolov3 bootnodes +var YoloV3Bootnodes = []string{ "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", } diff --git a/params/config.go b/params/config.go index 588d848f1c..0dbf592d19 100644 --- a/params/config.go +++ b/params/config.go @@ -31,8 +31,7 @@ var ( RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") - // TODO: update with yolov2 values - YoloV2GenesisHash = common.HexToHash("0x498a7239036dd2cd09e2bb8a80922b78632017958c332b42044c250d603a8a3e") + YoloV3GenesisHash = common.HexToHash("0x374f07cc7fa7c251fc5f36849f574b43db43600526410349efdca2bcea14101a") ) // TrustedCheckpoints associates each known checkpoint with the genesis hash of @@ -214,8 +213,8 @@ var ( Threshold: 2, } - // YoloV2ChainConfig contains the chain parameters to run a node on the YOLOv2 test network. - YoloV2ChainConfig = &ChainConfig{ + // YoloV3ChainConfig contains the chain parameters to run a node on the YOLOv3 test network. + YoloV3ChainConfig = &ChainConfig{ ChainID: big.NewInt(133519467574834), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -228,7 +227,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, - YoloV2Block: big.NewInt(0), + YoloV3Block: big.NewInt(0), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -321,7 +320,7 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) - YoloV2Block *big.Int `json:"yoloV2Block,omitempty"` // YOLO v2: Gas repricings TODO @holiman add EIP references + YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) // Various consensus engines @@ -359,7 +358,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v2: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v3: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -372,7 +371,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, - c.YoloV2Block, + c.YoloV3Block, engine, ) } @@ -429,9 +428,9 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { return isForked(c.IstanbulBlock, num) } -// IsYoloV2 returns whether num is either equal to the YoloV1 fork block or greater. -func (c *ChainConfig) IsYoloV2(num *big.Int) bool { - return isForked(c.YoloV2Block, num) +// IsYoloV3 returns whether num is either equal to the YoloV3 fork block or greater. +func (c *ChainConfig) IsYoloV3(num *big.Int) bool { + return isForked(c.YoloV3Block, num) } // IsEWASM returns whether num represents a block number after the EWASM fork @@ -477,7 +476,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "petersburgBlock", block: c.PetersburgBlock}, {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, - {name: "yoloV2Block", block: c.YoloV2Block}, + {name: "yoloV3Block", block: c.YoloV3Block}, } { if lastFork.name != "" { // Next one must be higher number @@ -541,8 +540,8 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } - if isForkIncompatible(c.YoloV2Block, newcfg.YoloV2Block, head) { - return newCompatError("YOLOv2 fork block", c.YoloV2Block, newcfg.YoloV2Block) + if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { + return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) } if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) @@ -614,7 +613,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsYoloV2 bool + IsYoloV3 bool } // Rules ensures c's ChainID is not nil. @@ -633,6 +632,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), - IsYoloV2: c.IsYoloV2(num), + IsYoloV3: c.IsYoloV3(num), } } diff --git a/tests/block_test.go b/tests/block_test.go index 8fa90e3e39..84618fd0be 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -45,6 +45,13 @@ func TestBlockchain(t *testing.T) { bt.skipLoad(`.*randomStatetest94.json.*`) bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { + if test.json.Network == "Berlin" { + // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them + // fail when berlin is defined as YOLOv3. We skip those, until they've been + // regenerated and re-imported + // TODO (@holiman) + return + } if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) } diff --git a/tests/fuzzers/bls12381/bls_fuzzer.go b/tests/fuzzers/bls12381/bls_fuzzer.go index 7e3f94c2aa..bc3c456526 100644 --- a/tests/fuzzers/bls12381/bls_fuzzer.go +++ b/tests/fuzzers/bls12381/bls_fuzzer.go @@ -79,7 +79,7 @@ func checkInput(id byte, inputLen int) bool { // other values are reserved for future use. func fuzz(id byte, data []byte) int { // Even on bad input, it should not crash, so we still test the gas calc - precompile := vm.PrecompiledContractsYoloV2[common.BytesToAddress([]byte{id})] + precompile := vm.PrecompiledContractsBLS[common.BytesToAddress([]byte{id})] gas := precompile.RequiredGas(data) if !checkInput(id, len(data)) { return 0 diff --git a/tests/init.go b/tests/init.go index 607c69ddb3..a2ef040786 100644 --- a/tests/init.go +++ b/tests/init.go @@ -141,7 +141,7 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(5), }, - "YOLOv2": { + "YOLOv3": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -151,9 +151,9 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV2Block: big.NewInt(0), + YoloV3Block: big.NewInt(0), }, - // This specification is subject to change, but is for now identical to YOLOv2 + // This specification is subject to change, but is for now identical to YOLOv3 // for cross-client testing purposes "Berlin": { ChainID: big.NewInt(1), @@ -165,7 +165,7 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV2Block: big.NewInt(0), + YoloV3Block: big.NewInt(0), }, } diff --git a/tests/state_test.go b/tests/state_test.go index b77a898c21..0f2bd38532 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -62,6 +62,14 @@ func TestState(t *testing.T) { } { st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { + if subtest.Fork == "Berlin" { + // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them + // fail when berlin is defined as YOLOv3. We skip those, until they've been + // regenerated and re-imported + // TODO (@holiman) + continue + } + subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) name := name + "/" + key diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 96c7316969..7bd2e801e4 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -187,7 +187,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) - if config.IsYoloV2(context.BlockNumber) { + if config.IsYoloV3(context.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) From 7a800f98f66103d934f24231ffec1a656d25b496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 28 Jan 2021 22:47:15 +0100 Subject: [PATCH 293/948] les/utils: UDP rate limiter (#21930) * les/utils: Limiter * les/utils: dropped prior weight vs variable cost logic, using fixed weights * les/utils: always create node selector in addressGroup * les/utils: renamed request weight to request cost * les/utils: simplified and improved the DoS penalty mechanism * les/utils: minor fixes * les/utils: made selection weight calculation nicer * les/utils: fixed linter warning * les/utils: more precise and reliable probabilistic test * les/utils: fixed linter warning --- les/utils/limiter.go | 405 +++++++++++++++++++++++++++++++++++ les/utils/limiter_test.go | 206 ++++++++++++++++++ les/utils/weighted_select.go | 28 +-- 3 files changed, 625 insertions(+), 14 deletions(-) create mode 100644 les/utils/limiter.go create mode 100644 les/utils/limiter_test.go diff --git a/les/utils/limiter.go b/les/utils/limiter.go new file mode 100644 index 0000000000..0cc2d7b262 --- /dev/null +++ b/les/utils/limiter.go @@ -0,0 +1,405 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "sort" + "sync" + + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const maxSelectionWeight = 1000000000 // maximum selection weight of each individual node/address group + +// Limiter protects a network request serving mechanism from denial-of-service attacks. +// It limits the total amount of resources used for serving requests while ensuring that +// the most valuable connections always have a reasonable chance of being served. +type Limiter struct { + lock sync.Mutex + cond *sync.Cond + quit bool + + nodes map[enode.ID]*nodeQueue + addresses map[string]*addressGroup + addressSelect, valueSelect *WeightedRandomSelect + maxValue float64 + maxCost, sumCost, sumCostLimit uint + selectAddressNext bool +} + +// nodeQueue represents queued requests coming from a single node ID +type nodeQueue struct { + queue []request // always nil if penaltyCost != 0 + id enode.ID + address string + value float64 + flatWeight, valueWeight uint64 // current selection weights in the address/value selectors + sumCost uint // summed cost of requests queued by the node + penaltyCost uint // cumulative cost of dropped requests since last processed request + groupIndex int +} + +// addressGroup is a group of node IDs that have sent their last requests from the same +// network address +type addressGroup struct { + nodes []*nodeQueue + nodeSelect *WeightedRandomSelect + sumFlatWeight, groupWeight uint64 +} + +// request represents an incoming request scheduled for processing +type request struct { + process chan chan struct{} + cost uint +} + +// flatWeight distributes weights equally between each active network address +func flatWeight(item interface{}) uint64 { return item.(*nodeQueue).flatWeight } + +// add adds the node queue to the address group. It is the caller's responsibility to +// add the address group to the address map and the address selector if it wasn't +// there before. +func (ag *addressGroup) add(nq *nodeQueue) { + if nq.groupIndex != -1 { + panic("added node queue is already in an address group") + } + l := len(ag.nodes) + nq.groupIndex = l + ag.nodes = append(ag.nodes, nq) + ag.sumFlatWeight += nq.flatWeight + ag.groupWeight = ag.sumFlatWeight / uint64(l+1) + ag.nodeSelect.Update(ag.nodes[l]) +} + +// update updates the selection weight of the node queue inside the address group. +// It is the caller's responsibility to update the group's selection weight in the +// address selector. +func (ag *addressGroup) update(nq *nodeQueue, weight uint64) { + if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq { + panic("updated node queue is not in this address group") + } + ag.sumFlatWeight += weight - nq.flatWeight + nq.flatWeight = weight + ag.groupWeight = ag.sumFlatWeight / uint64(len(ag.nodes)) + ag.nodeSelect.Update(nq) +} + +// remove removes the node queue from the address group. It is the caller's responsibility +// to remove the address group from the address map if it is empty. +func (ag *addressGroup) remove(nq *nodeQueue) { + if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq { + panic("removed node queue is not in this address group") + } + + l := len(ag.nodes) - 1 + if nq.groupIndex != l { + ag.nodes[nq.groupIndex] = ag.nodes[l] + ag.nodes[nq.groupIndex].groupIndex = nq.groupIndex + } + nq.groupIndex = -1 + ag.nodes = ag.nodes[:l] + ag.sumFlatWeight -= nq.flatWeight + if l >= 1 { + ag.groupWeight = ag.sumFlatWeight / uint64(l) + } else { + ag.groupWeight = 0 + } + ag.nodeSelect.Remove(nq) +} + +// choose selects one of the node queues belonging to the address group +func (ag *addressGroup) choose() *nodeQueue { + return ag.nodeSelect.Choose().(*nodeQueue) +} + +// NewLimiter creates a new Limiter +func NewLimiter(sumCostLimit uint) *Limiter { + l := &Limiter{ + addressSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*addressGroup).groupWeight }), + valueSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*nodeQueue).valueWeight }), + nodes: make(map[enode.ID]*nodeQueue), + addresses: make(map[string]*addressGroup), + sumCostLimit: sumCostLimit, + } + l.cond = sync.NewCond(&l.lock) + go l.processLoop() + return l +} + +// selectionWeights calculates the selection weights of a node for both the address and +// the value selector. The selection weight depends on the next request cost or the +// summed cost of recently dropped requests. +func (l *Limiter) selectionWeights(reqCost uint, value float64) (flatWeight, valueWeight uint64) { + if value > l.maxValue { + l.maxValue = value + } + if value > 0 { + // normalize value to <= 1 + value /= l.maxValue + } + if reqCost > l.maxCost { + l.maxCost = reqCost + } + relCost := float64(reqCost) / float64(l.maxCost) + var f float64 + if relCost <= 0.001 { + f = 1 + } else { + f = 0.001 / relCost + } + f *= maxSelectionWeight + flatWeight, valueWeight = uint64(f), uint64(f*value) + if flatWeight == 0 { + flatWeight = 1 + } + return +} + +// Add adds a new request to the node queue belonging to the given id. Value belongs +// to the requesting node. A higher value gives the request a higher chance of being +// served quickly in case of heavy load or a DDoS attack. Cost is a rough estimate +// of the serving cost of the request. A lower cost also gives the request a +// better chance. +func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) chan chan struct{} { + l.lock.Lock() + defer l.lock.Unlock() + + process := make(chan chan struct{}, 1) + if l.quit { + close(process) + return process + } + if reqCost == 0 { + reqCost = 1 + } + if nq, ok := l.nodes[id]; ok { + if nq.queue != nil { + nq.queue = append(nq.queue, request{process, reqCost}) + nq.sumCost += reqCost + nq.value = value + if address != nq.address { + // known id sending request from a new address, move to different address group + l.removeFromGroup(nq) + l.addToGroup(nq, address) + } + } else { + // already waiting on a penalty, just add to the penalty cost and drop the request + nq.penaltyCost += reqCost + l.update(nq) + close(process) + return process + } + } else { + nq := &nodeQueue{ + queue: []request{{process, reqCost}}, + id: id, + value: value, + sumCost: reqCost, + groupIndex: -1, + } + nq.flatWeight, nq.valueWeight = l.selectionWeights(reqCost, value) + if len(l.nodes) == 0 { + l.cond.Signal() + } + l.nodes[id] = nq + if nq.valueWeight != 0 { + l.valueSelect.Update(nq) + } + l.addToGroup(nq, address) + } + l.sumCost += reqCost + if l.sumCost > l.sumCostLimit { + l.dropRequests() + } + return process +} + +// update updates the selection weights of the node queue +func (l *Limiter) update(nq *nodeQueue) { + var cost uint + if nq.queue != nil { + cost = nq.queue[0].cost + } else { + cost = nq.penaltyCost + } + flatWeight, valueWeight := l.selectionWeights(cost, nq.value) + ag := l.addresses[nq.address] + ag.update(nq, flatWeight) + l.addressSelect.Update(ag) + nq.valueWeight = valueWeight + l.valueSelect.Update(nq) +} + +// addToGroup adds the node queue to the given address group. The group is created if +// it does not exist yet. +func (l *Limiter) addToGroup(nq *nodeQueue, address string) { + nq.address = address + ag := l.addresses[address] + if ag == nil { + ag = &addressGroup{nodeSelect: NewWeightedRandomSelect(flatWeight)} + l.addresses[address] = ag + } + ag.add(nq) + l.addressSelect.Update(ag) +} + +// removeFromGroup removes the node queue from its address group +func (l *Limiter) removeFromGroup(nq *nodeQueue) { + ag := l.addresses[nq.address] + ag.remove(nq) + if len(ag.nodes) == 0 { + delete(l.addresses, nq.address) + } + l.addressSelect.Update(ag) +} + +// remove removes the node queue from its address group, the nodes map and the value +// selector +func (l *Limiter) remove(nq *nodeQueue) { + l.removeFromGroup(nq) + if nq.valueWeight != 0 { + l.valueSelect.Remove(nq) + } + delete(l.nodes, nq.id) +} + +// choose selects the next node queue to process. +func (l *Limiter) choose() *nodeQueue { + if l.valueSelect.IsEmpty() || l.selectAddressNext { + if ag, ok := l.addressSelect.Choose().(*addressGroup); ok { + l.selectAddressNext = false + return ag.choose() + } + } + nq, _ := l.valueSelect.Choose().(*nodeQueue) + l.selectAddressNext = true + return nq +} + +// processLoop processes requests sequentially +func (l *Limiter) processLoop() { + l.lock.Lock() + defer l.lock.Unlock() + + for { + if l.quit { + for _, nq := range l.nodes { + for _, request := range nq.queue { + close(request.process) + } + } + return + } + nq := l.choose() + if nq == nil { + l.cond.Wait() + continue + } + if nq.queue != nil { + request := nq.queue[0] + nq.queue = nq.queue[1:] + nq.sumCost -= request.cost + l.sumCost -= request.cost + l.lock.Unlock() + ch := make(chan struct{}) + request.process <- ch + <-ch + l.lock.Lock() + if len(nq.queue) > 0 { + l.update(nq) + } else { + l.remove(nq) + } + } else { + // penalized queue removed, next request will be added to a clean queue + l.remove(nq) + } + } +} + +// Stop stops the processing loop. All queued and future requests are rejected. +func (l *Limiter) Stop() { + l.lock.Lock() + defer l.lock.Unlock() + + l.quit = true + l.cond.Signal() +} + +type ( + dropList []dropListItem + dropListItem struct { + nq *nodeQueue + priority float64 + } +) + +func (l dropList) Len() int { + return len(l) +} + +func (l dropList) Less(i, j int) bool { + return l[i].priority < l[j].priority +} + +func (l dropList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +// dropRequests selects the nodes with the highest queued request cost to selection +// weight ratio and drops their queued request. The empty node queues stay in the +// selectors with a low selection weight in order to penalize these nodes. +func (l *Limiter) dropRequests() { + var ( + sumValue float64 + list dropList + ) + for _, nq := range l.nodes { + sumValue += nq.value + } + for _, nq := range l.nodes { + if nq.sumCost == 0 { + continue + } + w := 1 / float64(len(l.addresses)*len(l.addresses[nq.address].nodes)) + if sumValue > 0 { + w += nq.value / sumValue + } + list = append(list, dropListItem{ + nq: nq, + priority: w / float64(nq.sumCost), + }) + } + sort.Sort(list) + for _, item := range list { + for _, request := range item.nq.queue { + close(request.process) + } + // make the queue penalized; no more requests are accepted until the node is + // selected based on the penalty cost which is the cumulative cost of all dropped + // requests. This ensures that sending excess requests is always penalized + // and incentivizes the sender to stop for a while if no replies are received. + item.nq.queue = nil + item.nq.penaltyCost = item.nq.sumCost + l.sumCost -= item.nq.sumCost // penalty costs are not counted in sumCost + item.nq.sumCost = 0 + l.update(item.nq) + if l.sumCost <= l.sumCostLimit/2 { + return + } + } +} diff --git a/les/utils/limiter_test.go b/les/utils/limiter_test.go new file mode 100644 index 0000000000..43af3309ab --- /dev/null +++ b/les/utils/limiter_test.go @@ -0,0 +1,206 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const ( + ltTolerance = 0.03 + ltRounds = 7 +) + +type ( + ltNode struct { + addr, id int + value, exp float64 + cost uint + reqRate float64 + reqMax, runCount int + lastTotalCost uint + + served, dropped int + } + + ltResult struct { + node *ltNode + ch chan struct{} + } + + limTest struct { + limiter *Limiter + results chan ltResult + runCount int + expCost, totalCost uint + } +) + +func (lt *limTest) request(n *ltNode) { + var ( + address string + id enode.ID + ) + if n.addr >= 0 { + address = string([]byte{byte(n.addr)}) + } else { + var b [32]byte + rand.Read(b[:]) + address = string(b[:]) + } + if n.id >= 0 { + id = enode.ID{byte(n.id)} + } else { + rand.Read(id[:]) + } + lt.runCount++ + n.runCount++ + cch := lt.limiter.Add(id, address, n.value, n.cost) + go func() { + lt.results <- ltResult{n, <-cch} + }() +} + +func (lt *limTest) moreRequests(n *ltNode) { + maxStart := int(float64(lt.totalCost-n.lastTotalCost) * n.reqRate) + if maxStart != 0 { + n.lastTotalCost = lt.totalCost + } + for n.reqMax > n.runCount && maxStart > 0 { + lt.request(n) + maxStart-- + } +} + +func (lt *limTest) process() { + res := <-lt.results + lt.runCount-- + res.node.runCount-- + if res.ch != nil { + res.node.served++ + if res.node.exp != 0 { + lt.expCost += res.node.cost + } + lt.totalCost += res.node.cost + close(res.ch) + } else { + res.node.dropped++ + } +} + +func TestLimiter(t *testing.T) { + limTests := [][]*ltNode{ + { // one id from an individual address and two ids from a shared address + {addr: 0, id: 0, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.5}, + {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + }, + { // varying request costs + {addr: 0, id: 0, value: 0, cost: 10, reqRate: 0.2, reqMax: 1, exp: 0.5}, + {addr: 1, id: 1, value: 0, cost: 3, reqRate: 0.5, reqMax: 1, exp: 0.25}, + {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + }, + { // different request rate + {addr: 0, id: 0, value: 0, cost: 1, reqRate: 2, reqMax: 2, exp: 0.5}, + {addr: 1, id: 1, value: 0, cost: 1, reqRate: 10, reqMax: 10, exp: 0.25}, + {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, + }, + { // adding value + {addr: 0, id: 0, value: 3, cost: 1, reqRate: 1, reqMax: 1, exp: (0.5 + 0.3) / 2}, + {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25 / 2}, + {addr: 1, id: 2, value: 7, cost: 1, reqRate: 1, reqMax: 1, exp: (0.25 + 0.7) / 2}, + }, + { // DoS attack from a single address with a single id + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 3, id: 3, value: 0, cost: 1, reqRate: 10, reqMax: 1000000000, exp: 0}, + }, + { // DoS attack from a single address with different ids + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 3, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, + }, + { // DDoS attack from different addresses with a single id + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: -1, id: 3, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, + }, + { // DDoS attack from different addresses with different ids + {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, + {addr: -1, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, + }, + } + + lt := &limTest{ + limiter: NewLimiter(100), + results: make(chan ltResult), + } + for _, test := range limTests { + lt.expCost, lt.totalCost = 0, 0 + iterCount := 10000 + for j := 0; j < ltRounds; j++ { + // try to reach expected target range in multiple rounds with increasing iteration counts + last := j == ltRounds-1 + for _, n := range test { + lt.request(n) + } + for i := 0; i < iterCount; i++ { + lt.process() + for _, n := range test { + lt.moreRequests(n) + } + } + for lt.runCount > 0 { + lt.process() + } + if spamRatio := 1 - float64(lt.expCost)/float64(lt.totalCost); spamRatio > 0.5*(1+ltTolerance) { + t.Errorf("Spam ratio too high (%f)", spamRatio) + } + fail, success := false, true + for _, n := range test { + if n.exp != 0 { + if n.dropped > 0 { + t.Errorf("Dropped %d requests of non-spam node", n.dropped) + fail = true + } + r := float64(n.served) * float64(n.cost) / float64(lt.expCost) + if r < n.exp*(1-ltTolerance) || r > n.exp*(1+ltTolerance) { + if last { + // print error only if the target is still not reached in the last round + t.Errorf("Request ratio (%f) does not match expected value (%f)", r, n.exp) + } + success = false + } + } + } + if fail || success { + break + } + // neither failed nor succeeded; try more iterations to reach probability targets + iterCount *= 2 + } + } + lt.limiter.Stop() +} diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go index 9b4413afb5..486b00820a 100644 --- a/les/utils/weighted_select.go +++ b/les/utils/weighted_select.go @@ -52,17 +52,17 @@ func (w *WeightedRandomSelect) Remove(item WrsItem) { // IsEmpty returns true if the set is empty func (w *WeightedRandomSelect) IsEmpty() bool { - return w.root.sumWeight == 0 + return w.root.sumCost == 0 } // setWeight sets an item's weight to a specific value (removes it if zero) func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { - if weight > math.MaxInt64-w.root.sumWeight { - // old weight is still included in sumWeight, remove and check again + if weight > math.MaxInt64-w.root.sumCost { + // old weight is still included in sumCost, remove and check again w.setWeight(item, 0) - if weight > math.MaxInt64-w.root.sumWeight { - log.Error("WeightedRandomSelect overflow", "sumWeight", w.root.sumWeight, "new weight", weight) - weight = math.MaxInt64 - w.root.sumWeight + if weight > math.MaxInt64-w.root.sumCost { + log.Error("WeightedRandomSelect overflow", "sumCost", w.root.sumCost, "new weight", weight) + weight = math.MaxInt64 - w.root.sumCost } } idx, ok := w.idx[item] @@ -75,9 +75,9 @@ func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { if weight != 0 { if w.root.itemCnt == w.root.maxItems { // add a new level - newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} + newRoot := &wrsNode{sumCost: w.root.sumCost, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} newRoot.items[0] = w.root - newRoot.weights[0] = w.root.sumWeight + newRoot.weights[0] = w.root.sumCost w.root = newRoot } w.idx[item] = w.root.insert(item, weight) @@ -91,10 +91,10 @@ func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { // updates its weight and selects another one func (w *WeightedRandomSelect) Choose() WrsItem { for { - if w.root.sumWeight == 0 { + if w.root.sumCost == 0 { return nil } - val := uint64(rand.Int63n(int64(w.root.sumWeight))) + val := uint64(rand.Int63n(int64(w.root.sumCost))) choice, lastWeight := w.root.choose(val) weight := w.wfn(choice) if weight != lastWeight { @@ -112,7 +112,7 @@ const wrsBranches = 8 // max number of branches in the wrsNode tree type wrsNode struct { items [wrsBranches]interface{} weights [wrsBranches]uint64 - sumWeight uint64 + sumCost uint64 level, itemCnt, maxItems int } @@ -126,7 +126,7 @@ func (n *wrsNode) insert(item WrsItem, weight uint64) int { } } n.itemCnt++ - n.sumWeight += weight + n.sumCost += weight n.weights[branch] += weight if n.level == 0 { n.items[branch] = item @@ -150,7 +150,7 @@ func (n *wrsNode) setWeight(idx int, weight uint64) uint64 { oldWeight := n.weights[idx] n.weights[idx] = weight diff := weight - oldWeight - n.sumWeight += diff + n.sumCost += diff if weight == 0 { n.items[idx] = nil n.itemCnt-- @@ -161,7 +161,7 @@ func (n *wrsNode) setWeight(idx int, weight uint64) uint64 { branch := idx / branchItems diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) n.weights[branch] += diff - n.sumWeight += diff + n.sumCost += diff if weight == 0 { n.itemCnt-- } From f25b437b70dd8be8ab7677fae607e01cd86c1ef4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 29 Jan 2021 16:53:44 +0100 Subject: [PATCH 294/948] cmd/clef: don't check file permissions on windows (#22251) Fixes #20123 --- cmd/clef/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 273919cb41..090edd4507 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -805,14 +805,16 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { // checkFile is a convenience function to check if a file // * exists -// * is mode 0400 +// * is mode 0400 (unix only) func checkFile(filename string) error { info, err := os.Stat(filename) if err != nil { return fmt.Errorf("failed stat on %s: %v", filename, err) } // Check the unix permission bits - if info.Mode().Perm()&0377 != 0 { + // However, on windows, we cannot use the unix perm-bits, see + // https://github.com/ethereum/go-ethereum/issues/20123 + if runtime.GOOS != "windows" && info.Mode().Perm()&0377 != 0 { return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) } return nil From 3c728fb129210c044ae71aad55e4c059d70a318d Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Mon, 1 Feb 2021 14:41:43 +0100 Subject: [PATCH 295/948] eth/tracers: fix unigram tracer (#22248) --- eth/tracers/internal/tracers/assets.go | 37 ++++++++----------- .../internal/tracers/unigram_tracer.js | 4 +- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go index 8b89ad4182..7f45ab286e 100644 --- a/eth/tracers/internal/tracers/assets.go +++ b/eth/tracers/internal/tracers/assets.go @@ -8,7 +8,7 @@ // opcount_tracer.js (1.372kB) // prestate_tracer.js (4.287kB) // trigram_tracer.js (1.788kB) -// unigram_tracer.js (1.51kB) +// unigram_tracer.js (1.469kB) package tracers @@ -28,7 +28,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } var buf bytes.Buffer @@ -36,7 +36,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } if clErr != nil { return nil, err @@ -237,7 +237,7 @@ func trigram_tracerJs() (*asset, error) { return a, nil } -var _unigram_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x54\x4d\x6f\xdb\x46\x10\xbd\xeb\x57\xbc\xa3\x8c\xa8\xa4\xd3\x5e\x0a\xa5\x09\xc0\x1a\x76\x22\xc0\x91\x0d\x89\x6e\x60\x14\x3d\x2c\xc9\x21\xb9\xe8\x6a\x87\xd8\x9d\x95\x42\x04\xfa\xef\xc5\x92\xa2\xe5\x1a\x6e\x13\x9e\x04\xcd\xbc\x8f\x79\x33\x64\x9a\xe2\x8a\xbb\xde\xe9\xa6\x15\xfc\x7c\xf9\xf6\x57\xe4\x2d\xa1\xe1\x9f\x48\x5a\x72\x14\x76\xc8\x82\xb4\xec\xfc\x2c\x4d\x91\xb7\xda\xa3\xd6\x86\xa0\x3d\x3a\xe5\x04\x5c\x43\x5e\xf4\x1b\x5d\x38\xe5\xfa\x64\x96\xa6\x23\xe6\xd5\x72\x64\xa8\x1d\x11\x3c\xd7\x72\x50\x8e\x96\xe8\x39\xa0\x54\x16\x8e\x2a\xed\xc5\xe9\x22\x08\x41\x0b\x94\xad\x52\x76\xd8\x71\xa5\xeb\x3e\x52\x6a\x41\xb0\x15\xb9\x41\x5a\xc8\xed\xfc\xe4\xe3\xe3\xfa\x01\xb7\xe4\x3d\x39\x7c\x24\x4b\x4e\x19\xdc\x87\xc2\xe8\x12\xb7\xba\x24\xeb\x09\xca\xa3\x8b\xff\xf8\x96\x2a\x14\x03\x5d\x04\xde\x44\x2b\xdb\x93\x15\xdc\x70\xb0\x95\x12\xcd\x76\x01\xd2\xd1\x39\xf6\xe4\xbc\x66\x8b\x5f\x26\xa9\x13\xe1\x02\xec\x22\xc9\x5c\x49\x1c\xc0\x81\xbb\x88\xbb\x80\xb2\x3d\x8c\x92\x33\xf4\x07\x02\x39\xcf\x5d\x41\xdb\x41\xa6\xe5\x8e\x20\xad\x92\x38\xf5\x41\x1b\x83\x82\x10\x3c\xd5\xc1\x2c\x22\x5b\x11\x04\x5f\x56\xf9\xa7\xbb\x87\x1c\xd9\xfa\x11\x5f\xb2\xcd\x26\x5b\xe7\x8f\xef\x70\xd0\xd2\x72\x10\xd0\x9e\x46\x2a\xbd\xeb\x8c\xa6\x0a\x07\xe5\x9c\xb2\xd2\x83\xeb\xc8\xf0\xf9\x7a\x73\xf5\x29\x5b\xe7\xd9\xef\xab\xdb\x55\xfe\x08\x76\xb8\x59\xe5\xeb\xeb\xed\x16\x37\x77\x1b\x64\xb8\xcf\x36\xf9\xea\xea\xe1\x36\xdb\xe0\xfe\x61\x73\x7f\xb7\xbd\x4e\xb0\xa5\xe8\x8a\x22\xfe\xfb\x99\xd7\xc3\xf6\x1c\xa1\x22\x51\xda\xf8\x29\x89\x47\x0e\xf0\x2d\x07\x53\xa1\x55\x7b\x82\xa3\x92\xf4\x9e\x2a\x28\x94\xdc\xf5\x3f\xbc\xd4\xc8\xa5\x0c\xdb\x66\x98\xf9\x3f\x0f\x12\xab\x1a\x96\x65\x01\x4f\x84\xdf\x5a\x91\x6e\x99\xa6\x87\xc3\x21\x69\x6c\x48\xd8\x35\xa9\x19\xe9\x7c\xfa\x21\x99\xcd\xbe\xcd\x00\x20\x4d\xd1\x6a\x2f\x71\x39\x91\x76\xa7\xba\xe8\x8a\xbb\x92\x2b\xf2\x10\x46\xc9\xc1\x0a\x39\x3f\x74\xc7\xd6\x25\xbe\x1d\x17\x13\xd6\x72\xe7\xc7\x16\x0f\x1b\x76\x05\xb9\x11\x3e\xb6\xc7\xea\x12\x97\x4f\xdd\x5e\xa8\x8b\x4a\xda\xee\xf9\x6f\xaa\x86\xdc\x68\x4f\xae\x3f\x09\x8e\x77\x10\x7d\xfc\xf1\x19\xf4\x95\xca\x20\xe4\x93\x01\x1d\xa1\x4b\xd4\xc1\x96\xf1\xfa\xe6\x86\x9b\x05\xaa\xe2\x02\xe3\x14\xf1\xd9\xab\x78\x9b\x78\x0f\xc3\x4d\xc2\x5d\x22\xbc\x15\xa7\x6d\x33\xbf\x78\xf7\xd4\xa3\x6b\xcc\xa5\xd5\x3e\x89\x83\xfc\xc9\xdd\x5f\x17\x67\x7c\x7c\xfe\x55\x7b\xf3\xe6\x0c\x3c\x3e\xfd\x22\xe3\x09\xff\x83\xc2\x7b\xbc\x7d\x0d\x37\x34\xc5\x40\x26\xda\x73\x88\xb5\x0a\x46\x9e\xe7\x72\x68\x4f\x17\xad\x4a\x09\xca\x9c\xa2\x88\x6f\x27\xd7\x50\x76\x4a\xab\x1e\x6f\x2d\xb2\x0c\x14\xaf\xe6\x73\x5c\xcc\x26\x1d\x47\xfe\x35\x21\x65\xcc\x20\x36\x2d\x7d\x38\xd5\x82\xc8\x42\x0b\x39\x15\xdf\x55\xde\x93\x8b\x9f\x29\x38\x92\xe0\xac\x9f\x18\x23\xac\xd6\x56\x99\x89\xfb\x74\xd1\xe2\x54\xa9\x6d\x33\x7a\x1b\x4b\xcf\xcc\x95\xf2\xf5\xf9\xe2\x74\x3d\x7f\x0a\x07\x1f\x70\xf9\x62\x27\xa3\xe4\x39\xe4\x97\xe1\x1e\x17\xb3\xe3\xec\x9f\x00\x00\x00\xff\xff\x8d\xba\x8d\xa8\xe6\x05\x00\x00") +var _unigram_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x94\x41\x6f\xdb\xc6\x13\xc5\xef\xfa\x14\xef\x68\x23\xfa\x8b\xc9\xbf\x97\x42\x69\x0a\xb0\x86\x9d\x08\x70\x64\x43\xa2\x1b\x18\x45\x0f\x4b\x72\x48\x2e\xba\xda\x21\x76\x67\xa5\x08\x81\xbf\x7b\x31\xa4\x68\xb9\x85\xdb\x86\x27\x41\x3b\xef\x37\x6f\xde\x0e\x99\x65\xb8\xe2\xfe\x18\x6c\xdb\x09\xfe\xff\xf6\xdd\x8f\x28\x3a\x42\xcb\xff\x23\xe9\x28\x50\xda\x21\x4f\xd2\x71\x88\xb3\x2c\x43\xd1\xd9\x88\xc6\x3a\x82\x8d\xe8\x4d\x10\x70\x03\xf9\x5b\xbd\xb3\x65\x30\xe1\xb8\x98\x65\xd9\xa8\x79\xf5\x58\x09\x4d\x20\x42\xe4\x46\x0e\x26\xd0\x12\x47\x4e\xa8\x8c\x47\xa0\xda\x46\x09\xb6\x4c\x42\xb0\x02\xe3\xeb\x8c\x03\x76\x5c\xdb\xe6\xa8\x48\x2b\x48\xbe\xa6\x30\xb4\x16\x0a\xbb\x38\xf9\xf8\xb8\x7e\xc0\x2d\xc5\x48\x01\x1f\xc9\x53\x30\x0e\xf7\xa9\x74\xb6\xc2\xad\xad\xc8\x47\x82\x89\xe8\xf5\x9f\xd8\x51\x8d\x72\xc0\xa9\xf0\x46\xad\x6c\x4f\x56\x70\xc3\xc9\xd7\x46\x2c\xfb\x39\xc8\xaa\x73\xec\x29\x44\xcb\x1e\x3f\x4c\xad\x4e\xc0\x39\x38\x28\xe4\xc2\x88\x0e\x10\xc0\xbd\xea\x2e\x61\xfc\x11\xce\xc8\x59\xfa\x1d\x81\x9c\xe7\xae\x61\xfd\xd0\xa6\xe3\x9e\x20\x9d\x11\x9d\xfa\x60\x9d\x43\x49\x48\x91\x9a\xe4\xe6\x4a\x2b\x93\xe0\xcb\xaa\xf8\x74\xf7\x50\x20\x5f\x3f\xe2\x4b\xbe\xd9\xe4\xeb\xe2\xf1\x3d\x0e\x56\x3a\x4e\x02\xda\xd3\x88\xb2\xbb\xde\x59\xaa\x71\x30\x21\x18\x2f\x47\x70\xa3\x84\xcf\xd7\x9b\xab\x4f\xf9\xba\xc8\x7f\x59\xdd\xae\x8a\x47\x70\xc0\xcd\xaa\x58\x5f\x6f\xb7\xb8\xb9\xdb\x20\xc7\x7d\xbe\x29\x56\x57\x0f\xb7\xf9\x06\xf7\x0f\x9b\xfb\xbb\xed\xf5\x02\x5b\x52\x57\xa4\xfa\xff\xce\xbc\x19\x6e\x2f\x10\x6a\x12\x63\x5d\x9c\x92\x78\xe4\x84\xd8\x71\x72\x35\x3a\xb3\x27\x04\xaa\xc8\xee\xa9\x86\x41\xc5\xfd\xf1\xbb\x2f\x55\x59\xc6\xb1\x6f\x87\x99\xff\x71\x21\xb1\x6a\xe0\x59\xe6\x88\x44\xf8\xa9\x13\xe9\x97\x59\x76\x38\x1c\x16\xad\x4f\x0b\x0e\x6d\xe6\x46\x5c\xcc\x7e\x5e\xcc\x66\xdf\x66\x00\x90\x65\xe8\x6c\x14\xbd\x1c\xc5\xee\x4c\xaf\xae\xb8\xaf\xb8\xa6\x08\x61\x54\x9c\xbc\x50\x88\x43\xb5\x96\x2e\xf1\xed\x69\x3e\x69\x3d\xf7\x71\x2c\x89\xf0\x69\x57\x52\x18\xe5\x63\xb9\x9e\x2e\xf1\xf6\xb9\x3a\x0a\xf5\xda\xc9\xfa\x3d\xff\x41\xf5\x90\x1b\xed\x29\x1c\x4f\x0d\xc7\x3d\x50\x1f\xbf\x7e\x06\x7d\xa5\x2a\x09\xc5\xc5\xa0\x56\xe9\x12\x4d\xf2\x95\x6e\xdf\x85\xe3\x76\x8e\xba\xbc\xc4\x38\x85\x3e\x7b\xa3\xbb\x89\x0f\x70\xdc\x2e\xb8\x5f\x08\x6f\x25\x58\xdf\x5e\x5c\xbe\x7f\xae\xb1\x0d\x2e\xa4\xb3\x71\xa1\x83\xfc\xc6\xfd\xef\x97\x67\xbd\x3e\x7f\x39\x7b\xf3\xe6\x2c\x7c\x7a\xfe\x45\x2e\x12\xfe\x45\x85\x0f\x78\xf7\x9a\x6e\x28\xd2\x40\x26\xec\x39\xc4\xc6\x24\x27\x2f\x73\x39\x74\xa7\x8d\x36\x95\x24\xe3\x4e\x51\xe8\xdb\xc9\x0d\x8c\x9f\xd2\x6a\xc6\x5d\x53\xca\x80\x78\x35\x9f\xa7\xf9\x6c\xea\x13\x28\xbe\xd6\xc8\x38\x37\x34\x9b\x2e\x7d\x58\xd5\x92\xc8\xc3\x0a\x05\xa3\xef\x2a\xef\x29\xe8\x67\x0a\x81\x24\x05\x1f\x27\xa2\xca\x1a\xeb\x8d\x9b\xd8\xa7\x8d\x96\x60\x2a\xeb\xdb\xd1\xdb\x78\xf4\xc2\x5c\x25\x5f\x5f\x5e\xdc\xc8\x3c\xa7\xf8\x1c\xcf\xd3\xec\xcf\x00\x00\x00\xff\xff\xf1\x91\x30\xae\xbd\x05\x00\x00") func unigram_tracerJsBytes() ([]byte, error) { return bindataRead( @@ -253,7 +253,7 @@ func unigram_tracerJs() (*asset, error) { } info := bindataFileInfo{name: "unigram_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2f, 0x36, 0x14, 0xc2, 0xf6, 0xc3, 0x80, 0x2b, 0x4a, 0x11, 0x7d, 0xd5, 0x3e, 0xef, 0x23, 0xb5, 0xd6, 0xe6, 0xe6, 0x5, 0x41, 0xf6, 0x14, 0x7a, 0x39, 0xf7, 0xf8, 0xac, 0x89, 0x8e, 0x43, 0xe6}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0xe6, 0x5c, 0x88, 0x18, 0xa7, 0x85, 0x61, 0x18, 0xc6, 0xec, 0x17, 0xfc, 0xdf, 0x9d, 0xc0, 0x1b, 0x49, 0xf8, 0x8d, 0xf1, 0xeb, 0x35, 0xf3, 0xd, 0x3e, 0xf6, 0xa3, 0xac, 0x8c, 0xba, 0x74}} return a, nil } @@ -348,25 +348,20 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "4byte_tracer.js": _4byte_tracerJs, - - "bigram_tracer.js": bigram_tracerJs, - - "call_tracer.js": call_tracerJs, - - "evmdis_tracer.js": evmdis_tracerJs, - - "noop_tracer.js": noop_tracerJs, - - "opcount_tracer.js": opcount_tracerJs, - + "4byte_tracer.js": _4byte_tracerJs, + "bigram_tracer.js": bigram_tracerJs, + "call_tracer.js": call_tracerJs, + "evmdis_tracer.js": evmdis_tracerJs, + "noop_tracer.js": noop_tracerJs, + "opcount_tracer.js": opcount_tracerJs, "prestate_tracer.js": prestate_tracerJs, - - "trigram_tracer.js": trigram_tracerJs, - - "unigram_tracer.js": unigram_tracerJs, + "trigram_tracer.js": trigram_tracerJs, + "unigram_tracer.js": unigram_tracerJs, } +// AssetDebug is true if the assets were built with the debug flag enabled. +const AssetDebug = false + // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the diff --git a/eth/tracers/internal/tracers/unigram_tracer.js b/eth/tracers/internal/tracers/unigram_tracer.js index 000fb13b1e..51107d8f3d 100644 --- a/eth/tracers/internal/tracers/unigram_tracer.js +++ b/eth/tracers/internal/tracers/unigram_tracer.js @@ -36,8 +36,6 @@ // result is invoked when all the opcodes have been iterated over and returns // the final result of the tracing. result: function(ctx) { - if(this.nops > 0){ - return this.hist; - } + return this.hist; }, } From e3430ac7df8a7c232f6374120eb1297c7fdfe5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Feb 2021 10:44:36 +0200 Subject: [PATCH 296/948] eth: check snap satelliteness, delegate drop to eth (#22235) * eth: check snap satelliteness, delegate drop to eth * eth: better handle eth/snap satellite relation, merge reg/unreg paths --- eth/handler.go | 106 +++++++------ eth/handler_eth.go | 4 +- eth/handler_eth_test.go | 4 +- eth/handler_snap.go | 8 +- eth/peer.go | 9 +- eth/peerset.go | 261 +++++++++++++-------------------- eth/protocols/eth/handler.go | 6 +- eth/protocols/eth/protocol.go | 8 +- eth/protocols/snap/handler.go | 6 +- eth/protocols/snap/protocol.go | 8 +- eth/sync.go | 4 +- eth/sync_test.go | 2 +- p2p/peer.go | 11 +- 13 files changed, 201 insertions(+), 236 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 5ae0925bb5..a5a62b894d 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -218,7 +218,7 @@ func newHandler(config *handlerConfig) (*handler, error) { h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) fetchTx := func(peer string, hashes []common.Hash) error { - p := h.peers.ethPeer(peer) + p := h.peers.peer(peer) if p == nil { return errors.New("unknown peer") } @@ -229,8 +229,17 @@ func newHandler(config *handlerConfig) (*handler, error) { return h, nil } -// runEthPeer +// runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to +// various subsistems and starts handling messages. func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + // If the peer has a `snap` extension, wait for it to connect so we can have + // a uniform initialization/teardown mechanism + snap, err := h.peers.waitSnapExtension(peer) + if err != nil { + peer.Log().Error("Snapshot extension barrier failed", "err", err) + return err + } + // TODO(karalabe): Not sure why this is needed if !h.chainSync.handlePeerEvent(peer) { return p2p.DiscQuitting } @@ -251,37 +260,46 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return err } reject := false // reserved peer slots - if atomic.LoadUint32(&h.snapSync) == 1 && !peer.SupportsCap("snap", 1) { - // If we are running snap-sync, we want to reserve roughly half the peer - // slots for peers supporting the snap protocol. - // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. - if all, snp := h.peers.Len(), h.peers.SnapLen(); all-snp > snp+5 { - reject = true + if atomic.LoadUint32(&h.snapSync) == 1 { + if snap == nil { + // If we are running snap-sync, we want to reserve roughly half the peer + // slots for peers supporting the snap protocol. + // The logic here is; we only allow up to 5 more non-snap peers than snap-peers. + if all, snp := h.peers.len(), h.peers.snapLen(); all-snp > snp+5 { + reject = true + } } } // Ignore maxPeers if this is a trusted peer if !peer.Peer.Info().Network.Trusted { - if reject || h.peers.Len() >= h.maxPeers { + if reject || h.peers.len() >= h.maxPeers { return p2p.DiscTooManyPeers } } peer.Log().Debug("Ethereum peer connected", "name", peer.Name()) // Register the peer locally - if err := h.peers.registerEthPeer(peer); err != nil { + if err := h.peers.registerPeer(peer, snap); err != nil { peer.Log().Error("Ethereum peer registration failed", "err", err) return err } defer h.removePeer(peer.ID()) - p := h.peers.ethPeer(peer.ID()) + p := h.peers.peer(peer.ID()) if p == nil { return errors.New("peer dropped during handling") } // Register the peer in the downloader. If the downloader considers it banned, we disconnect if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil { + peer.Log().Error("Failed to register peer in eth syncer", "err", err) return err } + if snap != nil { + if err := h.downloader.SnapSyncer.Register(snap); err != nil { + peer.Log().Error("Failed to register peer in snap syncer", "err", err) + return err + } + } h.chainSync.handlePeerEvent(peer) // Propagate existing transactions. new transactions appearing @@ -317,25 +335,23 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return handler(peer) } -// runSnapPeer -func (h *handler) runSnapPeer(peer *snap.Peer, handler snap.Handler) error { +// runSnapExtension registers a `snap` peer into the joint eth/snap peerset and +// starts handling inbound messages. As `snap` is only a satellite protocol to +// `eth`, all subsystem registrations and lifecycle management will be done by +// the main `eth` handler to prevent strange races. +func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error { h.peerWG.Add(1) defer h.peerWG.Done() - // Register the peer locally - if err := h.peers.registerSnapPeer(peer); err != nil { - peer.Log().Error("Snapshot peer registration failed", "err", err) - return err - } - defer h.removePeer(peer.ID()) - - if err := h.downloader.SnapSyncer.Register(peer); err != nil { + if err := h.peers.registerSnapExtension(peer); err != nil { + peer.Log().Error("Snapshot extension registration failed", "err", err) return err } - // Handle incoming messages until the connection is torn down return handler(peer) } +// removePeer unregisters a peer from the downloader and fetchers, removes it from +// the set of tracked peers and closes the network connection to it. func (h *handler) removePeer(id string) { // Create a custom logger to avoid printing the entire id var logger log.Logger @@ -345,33 +361,27 @@ func (h *handler) removePeer(id string) { } else { logger = log.New("peer", id[:8]) } - // Remove the eth peer if it exists - eth := h.peers.ethPeer(id) - if eth != nil { - logger.Debug("Removing Ethereum peer") - h.downloader.UnregisterPeer(id) - h.txFetcher.Drop(id) - - if err := h.peers.unregisterEthPeer(id); err != nil { - logger.Error("Ethereum peer removal failed", "err", err) - } + // Abort if the peer does not exist + peer := h.peers.peer(id) + if peer == nil { + logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered) + return } - // Remove the snap peer if it exists - snap := h.peers.snapPeer(id) - if snap != nil { - logger.Debug("Removing Snapshot peer") + // Remove the `eth` peer if it exists + logger.Debug("Removing Ethereum peer", "snap", peer.snapExt != nil) + + // Remove the `snap` extension if it exists + if peer.snapExt != nil { h.downloader.SnapSyncer.Unregister(id) - if err := h.peers.unregisterSnapPeer(id); err != nil { - logger.Error("Snapshot peer removel failed", "err", err) - } - } - // Hard disconnect at the networking layer - if eth != nil { - eth.Peer.Disconnect(p2p.DiscUselessPeer) } - if snap != nil { - snap.Peer.Disconnect(p2p.DiscUselessPeer) + h.downloader.UnregisterPeer(id) + h.txFetcher.Drop(id) + + if err := h.peers.unregisterPeer(id); err != nil { + logger.Error("Ethereum peer removal failed", "err", err) } + // Hard disconnect at the networking layer + peer.Peer.Disconnect(p2p.DiscUselessPeer) } func (h *handler) Start(maxPeers int) { @@ -417,7 +427,7 @@ func (h *handler) Stop() { // will only announce its availability (depending what's requested). func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { hash := block.Hash() - peers := h.peers.ethPeersWithoutBlock(hash) + peers := h.peers.peersWithoutBlock(hash) // If propagation is requested, send to a subset of the peer if propagate { @@ -456,7 +466,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) // Broadcast transactions to a batch of peers not knowing about it if propagate { for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) + peers := h.peers.peersWithoutTransaction(tx.Hash()) // Send the block to a subset of our peers transfer := peers[:int(math.Sqrt(float64(len(peers))))] @@ -472,7 +482,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) } // Otherwise only broadcast the announcement to peers for _, tx := range txs { - peers := h.peers.ethPeersWithoutTransaction(tx.Hash()) + peers := h.peers.peersWithoutTransaction(tx.Hash()) for _, peer := range peers { annos[peer] = append(annos[peer], tx.Hash()) } diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 84bdac659a..3ff9f2245b 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -47,7 +47,7 @@ func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { // PeerInfo retrieves all known `eth` information about a peer. func (h *ethHandler) PeerInfo(id enode.ID) interface{} { - if p := h.peers.ethPeer(id.String()); p != nil { + if p := h.peers.peer(id.String()); p != nil { return p.info() } return nil @@ -107,7 +107,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { // handleHeaders is invoked from a peer's message handler when it transmits a batch // of headers for the local node to process. func (h *ethHandler) handleHeaders(peer *eth.Peer, headers []*types.Header) error { - p := h.peers.ethPeer(peer.ID()) + p := h.peers.peer(peer.ID()) if p == nil { return errors.New("unregistered during callback") } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 0e5c0c90ee..5f5d4e9e82 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -574,11 +574,11 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo // Verify that the remote peer is maintained or dropped if drop { - if peers := handler.handler.peers.Len(); peers != 0 { + if peers := handler.handler.peers.len(); peers != 0 { t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) } } else { - if peers := handler.handler.peers.Len(); peers != 1 { + if peers := handler.handler.peers.len(); peers != 1 { t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) } } diff --git a/eth/handler_snap.go b/eth/handler_snap.go index 25975bf60b..767416ffd6 100644 --- a/eth/handler_snap.go +++ b/eth/handler_snap.go @@ -30,13 +30,15 @@ func (h *snapHandler) Chain() *core.BlockChain { return h.chain } // RunPeer is invoked when a peer joins on the `snap` protocol. func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error { - return (*handler)(h).runSnapPeer(peer, hand) + return (*handler)(h).runSnapExtension(peer, hand) } // PeerInfo retrieves all known `snap` information about a peer. func (h *snapHandler) PeerInfo(id enode.ID) interface{} { - if p := h.peers.snapPeer(id.String()); p != nil { - return p.info() + if p := h.peers.peer(id.String()); p != nil { + if p.snapExt != nil { + return p.snapExt.info() + } } return nil } diff --git a/eth/peer.go b/eth/peer.go index 6970c8afd3..1cea9c640e 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -36,9 +36,11 @@ type ethPeerInfo struct { // ethPeer is a wrapper around eth.Peer to maintain a few extra metadata. type ethPeer struct { *eth.Peer + snapExt *snapPeer // Satellite `snap` connection - syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time - lock sync.RWMutex // Mutex protecting the internal fields + syncDrop *time.Timer // Connection dropper if `eth` sync progress isn't validated in time + snapWait chan struct{} // Notification channel for snap connections + lock sync.RWMutex // Mutex protecting the internal fields } // info gathers and returns some `eth` protocol metadata known about a peer. @@ -61,9 +63,6 @@ type snapPeerInfo struct { // snapPeer is a wrapper around snap.Peer to maintain a few extra metadata. type snapPeer struct { *snap.Peer - - ethDrop *time.Timer // Connection dropper if `eth` doesn't connect in time - lock sync.RWMutex // Mutex protecting the internal fields } // info gathers and returns some `snap` protocol metadata known about a peer. diff --git a/eth/peerset.go b/eth/peerset.go index 663c5ce36b..f0657e140b 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -20,12 +20,10 @@ import ( "errors" "math/big" "sync" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" ) @@ -42,22 +40,19 @@ var ( // a peer set, but no peer with the given id exists. errPeerNotRegistered = errors.New("peer not registered") - // ethConnectTimeout is the `snap` timeout for `eth` to connect too. - ethConnectTimeout = 3 * time.Second + // errSnapWithoutEth is returned if a peer attempts to connect only on the + // snap protocol without advertizing the eth main protocol. + errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support") ) // peerSet represents the collection of active peers currently participating in -// the `eth` or `snap` protocols. +// the `eth` protocol, with or without the `snap` extension. type peerSet struct { - ethPeers map[string]*ethPeer // Peers connected on the `eth` protocol - snapPeers map[string]*snapPeer // Peers connected on the `snap` protocol + peers map[string]*ethPeer // Peers connected on the `eth` protocol + snapPeers int // Number of `snap` compatible peers for connection prioritization - ethJoinFeed event.Feed // Events when an `eth` peer successfully joins - ethDropFeed event.Feed // Events when an `eth` peer gets dropped - snapJoinFeed event.Feed // Events when a `snap` peer joins on both `eth` and `snap` - snapDropFeed event.Feed // Events when a `snap` peer gets dropped (only if fully joined) - - scope event.SubscriptionScope // Subscription group to unsubscribe everyone at once + snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension + snapPend map[string]*snap.Peer // Peers connected on the `snap` protocol, but not yet on `eth` lock sync.RWMutex closed bool @@ -66,176 +61,134 @@ type peerSet struct { // newPeerSet creates a new peer set to track the active participants. func newPeerSet() *peerSet { return &peerSet{ - ethPeers: make(map[string]*ethPeer), - snapPeers: make(map[string]*snapPeer), + peers: make(map[string]*ethPeer), + snapWait: make(map[string]chan *snap.Peer), + snapPend: make(map[string]*snap.Peer), } } -// subscribeEthJoin registers a subscription for peers joining (and completing -// the handshake) on the `eth` protocol. -func (ps *peerSet) subscribeEthJoin(ch chan<- *eth.Peer) event.Subscription { - return ps.scope.Track(ps.ethJoinFeed.Subscribe(ch)) -} - -// subscribeEthDrop registers a subscription for peers being dropped from the -// `eth` protocol. -func (ps *peerSet) subscribeEthDrop(ch chan<- *eth.Peer) event.Subscription { - return ps.scope.Track(ps.ethDropFeed.Subscribe(ch)) -} - -// subscribeSnapJoin registers a subscription for peers joining (and completing -// the `eth` join) on the `snap` protocol. -func (ps *peerSet) subscribeSnapJoin(ch chan<- *snap.Peer) event.Subscription { - return ps.scope.Track(ps.snapJoinFeed.Subscribe(ch)) -} - -// subscribeSnapDrop registers a subscription for peers being dropped from the -// `snap` protocol. -func (ps *peerSet) subscribeSnapDrop(ch chan<- *snap.Peer) event.Subscription { - return ps.scope.Track(ps.snapDropFeed.Subscribe(ch)) -} - -// registerEthPeer injects a new `eth` peer into the working set, or returns an -// error if the peer is already known. The peer is announced on the `eth` join -// feed and if it completes a pending `snap` peer, also on that feed. -func (ps *peerSet) registerEthPeer(peer *eth.Peer) error { - ps.lock.Lock() - if ps.closed { - ps.lock.Unlock() - return errPeerSetClosed +// registerSnapExtension unblocks an already connected `eth` peer waiting for its +// `snap` extension, or if no such peer exists, tracks the extension for the time +// being until the `eth` main protocol starts looking for it. +func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { + // Reject the peer if it advertises `snap` without `eth` as `snap` is only a + // satellite protocol meaningful with the chain selection of `eth` + if !peer.SupportsCap(eth.ProtocolName, eth.ProtocolVersions) { + return errSnapWithoutEth } + // Ensure nobody can double connect + ps.lock.Lock() + defer ps.lock.Unlock() + id := peer.ID() - if _, ok := ps.ethPeers[id]; ok { - ps.lock.Unlock() - return errPeerAlreadyRegistered + if _, ok := ps.peers[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as existing ones } - ps.ethPeers[id] = ðPeer{Peer: peer} - - snap, ok := ps.snapPeers[id] - ps.lock.Unlock() - - if ok { - // Previously dangling `snap` peer, stop it's timer since `eth` connected - snap.lock.Lock() - if snap.ethDrop != nil { - snap.ethDrop.Stop() - snap.ethDrop = nil - } - snap.lock.Unlock() + if _, ok := ps.snapPend[id]; ok { + return errPeerAlreadyRegistered // avoid connections with the same id as pending ones } - ps.ethJoinFeed.Send(peer) - if ok { - ps.snapJoinFeed.Send(snap.Peer) + // Inject the peer into an `eth` counterpart is available, otherwise save for later + if wait, ok := ps.snapWait[id]; ok { + delete(ps.snapWait, id) + wait <- peer + return nil } + ps.snapPend[id] = peer return nil } -// unregisterEthPeer removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. The drop is announced on the `eth` drop -// feed and also on the `snap` feed if the eth/snap duality was broken just now. -func (ps *peerSet) unregisterEthPeer(id string) error { +// waitExtensions blocks until all satellite protocols are connected and tracked +// by the peerset. +func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { + // If the peer does not support a compatible `snap`, don't wait + if !peer.SupportsCap(snap.ProtocolName, snap.ProtocolVersions) { + return nil, nil + } + // Ensure nobody can double connect ps.lock.Lock() - eth, ok := ps.ethPeers[id] - if !ok { + + id := peer.ID() + if _, ok := ps.peers[id]; ok { ps.lock.Unlock() - return errPeerNotRegistered + return nil, errPeerAlreadyRegistered // avoid connections with the same id as existing ones + } + if _, ok := ps.snapWait[id]; ok { + ps.lock.Unlock() + return nil, errPeerAlreadyRegistered // avoid connections with the same id as pending ones } - delete(ps.ethPeers, id) + // If `snap` already connected, retrieve the peer from the pending set + if snap, ok := ps.snapPend[id]; ok { + delete(ps.snapPend, id) - snap, ok := ps.snapPeers[id] + ps.lock.Unlock() + return snap, nil + } + // Otherwise wait for `snap` to connect concurrently + wait := make(chan *snap.Peer) + ps.snapWait[id] = wait ps.lock.Unlock() - ps.ethDropFeed.Send(eth) - if ok { - ps.snapDropFeed.Send(snap) - } - return nil + return <-wait, nil } -// registerSnapPeer injects a new `snap` peer into the working set, or returns -// an error if the peer is already known. The peer is announced on the `snap` -// join feed if it completes an existing `eth` peer. -// -// If the peer isn't yet connected on `eth` and fails to do so within a given -// amount of time, it is dropped. This enforces that `snap` is an extension to -// `eth`, not a standalone leeching protocol. -func (ps *peerSet) registerSnapPeer(peer *snap.Peer) error { +// registerPeer injects a new `eth` peer into the working set, or returns an error +// if the peer is already known. +func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error { + // Start tracking the new peer ps.lock.Lock() + defer ps.lock.Unlock() + if ps.closed { - ps.lock.Unlock() return errPeerSetClosed } id := peer.ID() - if _, ok := ps.snapPeers[id]; ok { - ps.lock.Unlock() + if _, ok := ps.peers[id]; ok { return errPeerAlreadyRegistered } - ps.snapPeers[id] = &snapPeer{Peer: peer} - - _, ok := ps.ethPeers[id] - if !ok { - // Dangling `snap` peer, start a timer to drop if `eth` doesn't connect - ps.snapPeers[id].ethDrop = time.AfterFunc(ethConnectTimeout, func() { - peer.Log().Warn("Snapshot peer missing eth, dropping", "addr", peer.RemoteAddr(), "type", peer.Name()) - peer.Disconnect(p2p.DiscUselessPeer) - }) + eth := ðPeer{ + Peer: peer, } - ps.lock.Unlock() - - if ok { - ps.snapJoinFeed.Send(peer) + if ext != nil { + eth.snapExt = &snapPeer{ext} + ps.snapPeers++ } + ps.peers[id] = eth return nil } -// unregisterSnapPeer removes a remote peer from the active set, disabling any -// further actions to/from that particular entity. The drop is announced on the -// `snap` drop feed. -func (ps *peerSet) unregisterSnapPeer(id string) error { +// unregisterPeer removes a remote peer from the active set, disabling any further +// actions to/from that particular entity. +func (ps *peerSet) unregisterPeer(id string) error { ps.lock.Lock() - peer, ok := ps.snapPeers[id] + defer ps.lock.Unlock() + + peer, ok := ps.peers[id] if !ok { - ps.lock.Unlock() return errPeerNotRegistered } - delete(ps.snapPeers, id) - ps.lock.Unlock() - - peer.lock.Lock() - if peer.ethDrop != nil { - peer.ethDrop.Stop() - peer.ethDrop = nil + delete(ps.peers, id) + if peer.snapExt != nil { + ps.snapPeers-- } - peer.lock.Unlock() - - ps.snapDropFeed.Send(peer) return nil } -// ethPeer retrieves the registered `eth` peer with the given id. -func (ps *peerSet) ethPeer(id string) *ethPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.ethPeers[id] -} - -// snapPeer retrieves the registered `snap` peer with the given id. -func (ps *peerSet) snapPeer(id string) *snapPeer { +// peer retrieves the registered peer with the given id. +func (ps *peerSet) peer(id string) *ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() - return ps.snapPeers[id] + return ps.peers[id] } -// ethPeersWithoutBlock retrieves a list of `eth` peers that do not have a given -// block in their set of known hashes so it might be propagated to them. -func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { +// peersWithoutBlock retrieves a list of peers that do not have a given block in +// their set of known hashes so it might be propagated to them. +func (ps *peerSet) peersWithoutBlock(hash common.Hash) []*ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() - list := make([]*ethPeer, 0, len(ps.ethPeers)) - for _, p := range ps.ethPeers { + list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { if !p.KnownBlock(hash) { list = append(list, p) } @@ -243,14 +196,14 @@ func (ps *peerSet) ethPeersWithoutBlock(hash common.Hash) []*ethPeer { return list } -// ethPeersWithoutTransaction retrieves a list of `eth` peers that do not have a -// given transaction in their set of known hashes. -func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { +// peersWithoutTransaction retrieves a list of peers that do not have a given +// transaction in their set of known hashes. +func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer { ps.lock.RLock() defer ps.lock.RUnlock() - list := make([]*ethPeer, 0, len(ps.ethPeers)) - for _, p := range ps.ethPeers { + list := make([]*ethPeer, 0, len(ps.peers)) + for _, p := range ps.peers { if !p.KnownTransaction(hash) { list = append(list, p) } @@ -258,28 +211,27 @@ func (ps *peerSet) ethPeersWithoutTransaction(hash common.Hash) []*ethPeer { return list } -// Len returns if the current number of `eth` peers in the set. Since the `snap` +// len returns if the current number of `eth` peers in the set. Since the `snap` // peers are tied to the existence of an `eth` connection, that will always be a // subset of `eth`. -func (ps *peerSet) Len() int { +func (ps *peerSet) len() int { ps.lock.RLock() defer ps.lock.RUnlock() - return len(ps.ethPeers) + return len(ps.peers) } -// SnapLen returns if the current number of `snap` peers in the set. Since the `snap` -// peers are tied to the existence of an `eth` connection, that will always be a -// subset of `eth`. -func (ps *peerSet) SnapLen() int { +// snapLen returns if the current number of `snap` peers in the set. +func (ps *peerSet) snapLen() int { ps.lock.RLock() defer ps.lock.RUnlock() - return len(ps.snapPeers) + + return ps.snapPeers } -// ethPeerWithHighestTD retrieves the known peer with the currently highest total +// peerWithHighestTD retrieves the known peer with the currently highest total // difficulty. -func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { +func (ps *peerSet) peerWithHighestTD() *eth.Peer { ps.lock.RLock() defer ps.lock.RUnlock() @@ -287,7 +239,7 @@ func (ps *peerSet) ethPeerWithHighestTD() *eth.Peer { bestPeer *eth.Peer bestTd *big.Int ) - for _, p := range ps.ethPeers { + for _, p := range ps.peers { if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 { bestPeer, bestTd = p.Peer, td } @@ -300,10 +252,7 @@ func (ps *peerSet) close() { ps.lock.Lock() defer ps.lock.Unlock() - for _, p := range ps.ethPeers { - p.Disconnect(p2p.DiscQuitting) - } - for _, p := range ps.snapPeers { + for _, p := range ps.peers { p.Disconnect(p2p.DiscQuitting) } ps.closed = true diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 25ddcd93ec..e32008fb41 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -103,12 +103,12 @@ type TxPool interface { // MakeProtocols constructs the P2P protocol definitions for `eth`. func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { - protocols := make([]p2p.Protocol, len(protocolVersions)) - for i, version := range protocolVersions { + protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { version := version // Closure protocols[i] = p2p.Protocol{ - Name: protocolName, + Name: ProtocolName, Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 63d3494ec4..9fff64b72a 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -34,13 +34,13 @@ const ( ETH65 = 65 ) -// protocolName is the official short name of the `eth` protocol used during +// ProtocolName is the official short name of the `eth` protocol used during // devp2p capability negotiation. -const protocolName = "eth" +const ProtocolName = "eth" -// protocolVersions are the supported versions of the `eth` protocol (first +// ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var protocolVersions = []uint{ETH65, ETH64} +var ProtocolVersions = []uint{ETH65, ETH64} // protocolLengths are the number of implemented message corresponding to // different protocol versions. diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 36322e648b..24c8599552 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -77,12 +77,12 @@ type Backend interface { // MakeProtocols constructs the P2P protocol definitions for `snap`. func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { - protocols := make([]p2p.Protocol, len(protocolVersions)) - for i, version := range protocolVersions { + protocols := make([]p2p.Protocol, len(ProtocolVersions)) + for i, version := range ProtocolVersions { version := version // Closure protocols[i] = p2p.Protocol{ - Name: protocolName, + Name: ProtocolName, Version: version, Length: protocolLengths[version], Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index f1a25a2066..a74142cafb 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -30,13 +30,13 @@ const ( snap1 = 1 ) -// protocolName is the official short name of the `snap` protocol used during +// ProtocolName is the official short name of the `snap` protocol used during // devp2p capability negotiation. -const protocolName = "snap" +const ProtocolName = "snap" -// protocolVersions are the supported versions of the `snap` protocol (first +// ProtocolVersions are the supported versions of the `snap` protocol (first // is primary). -var protocolVersions = []uint{snap1} +var ProtocolVersions = []uint{snap1} // protocolLengths are the number of implemented message corresponding to // different protocol versions. diff --git a/eth/sync.go b/eth/sync.go index eedb8b7476..dc72e88388 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -247,11 +247,11 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp { } else if minPeers > cs.handler.maxPeers { minPeers = cs.handler.maxPeers } - if cs.handler.peers.Len() < minPeers { + if cs.handler.peers.len() < minPeers { return nil } // We have enough peers, check TD - peer := cs.handler.peers.ethPeerWithHighestTD() + peer := cs.handler.peers.peerWithHighestTD() if peer == nil { return nil } diff --git a/eth/sync_test.go b/eth/sync_test.go index 473e19518f..9cc806b18a 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -70,7 +70,7 @@ func testFastSyncDisabling(t *testing.T, protocol uint) { time.Sleep(250 * time.Millisecond) // Check that fast sync was disabled - op := peerToSyncOp(downloader.FastSync, empty.handler.peers.ethPeerWithHighestTD()) + op := peerToSyncOp(downloader.FastSync, empty.handler.peers.peerWithHighestTD()) if err := empty.handler.doSync(op); err != nil { t.Fatal("sync failed:", err) } diff --git a/p2p/peer.go b/p2p/peer.go index 43ccef5c43..08881e2583 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -158,11 +158,16 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } -// SupportsCap returns true if the peer supports the given protocol/version -func (p *Peer) SupportsCap(protocol string, version uint) bool { +// SupportsCap returns true if the peer supports any of the enumerated versions +// of a specific protocol. +func (p *Peer) SupportsCap(protocol string, versions []uint) bool { for _, cap := range p.rw.caps { if cap.Name == protocol { - return version <= cap.Version + for _, ver := range versions { + if cap.Version == ver { + return true + } + } } } return false From 4eae0c6b6f2bcaa450df50077bb991e3bbbb106f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 2 Feb 2021 10:05:46 +0100 Subject: [PATCH 297/948] cmd/geth, node: allow configuring JSON-RPC on custom path prefix (#22184) This change allows users to set a custom path prefix on which to mount the http-rpc or ws-rpc handlers via the new flags --http.rpcprefix and --ws.rpcprefix. Fixes #21826 Co-authored-by: Felix Lange --- cmd/geth/main.go | 2 + cmd/geth/usage.go | 2 + cmd/utils/flags.go | 18 ++++++ go.sum | 5 -- graphql/graphql_test.go | 14 +--- node/api_test.go | 3 + node/config.go | 6 ++ node/node.go | 20 ++++-- node/node_test.go | 107 ++++++++++++++++++++++++++++++- node/rpcstack.go | 71 +++++++++++++++++---- node/rpcstack_test.go | 137 +++++++++++++++++++++++++++++++--------- 11 files changed, 320 insertions(+), 65 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 86dc6f40fe..11829cbad9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -181,6 +181,7 @@ var ( utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, utils.LegacyRPCApiFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, @@ -190,6 +191,7 @@ var ( utils.WSApiFlag, utils.LegacyWSApiFlag, utils.WSAllowedOriginsFlag, + utils.WSPathPrefixFlag, utils.LegacyWSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index ba311bf7f6..0ed31d7da6 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -138,12 +138,14 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.HTTPListenAddrFlag, utils.HTTPPortFlag, utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, utils.HTTPCORSDomainFlag, utils.HTTPVirtualHostsFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, utils.WSPortFlag, utils.WSApiFlag, + utils.WSPathPrefixFlag, utils.WSAllowedOriginsFlag, utils.GraphQLEnabledFlag, utils.GraphQLCORSDomainFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 8f9f68ba68..aa5180dd9f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -531,6 +531,11 @@ var ( Usage: "API's offered over the HTTP-RPC interface", Value: "", } + HTTPPathPrefixFlag = cli.StringFlag{ + Name: "http.rpcprefix", + Usage: "HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", + Value: "", + } GraphQLEnabledFlag = cli.BoolFlag{ Name: "graphql", Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.", @@ -569,6 +574,11 @@ var ( Usage: "Origins from which to accept websockets requests", Value: "", } + WSPathPrefixFlag = cli.StringFlag{ + Name: "ws.rpcprefix", + Usage: "HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.", + Value: "", + } ExecFlag = cli.StringFlag{ Name: "exec", Usage: "Execute JavaScript statement", @@ -946,6 +956,10 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) { cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name)) } + + if ctx.GlobalIsSet(HTTPPathPrefixFlag.Name) { + cfg.HTTPPathPrefix = ctx.GlobalString(HTTPPathPrefixFlag.Name) + } } // setGraphQL creates the GraphQL listener interface string from the set @@ -995,6 +1009,10 @@ func setWS(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(WSApiFlag.Name) { cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name)) } + + if ctx.GlobalIsSet(WSPathPrefixFlag.Name) { + cfg.WSPathPrefix = ctx.GlobalString(WSPathPrefixFlag.Name) + } } // setIPC creates an IPC path configuration from the set command line flags, diff --git a/go.sum b/go.sum index 4b799d77fa..2c16d81f04 100644 --- a/go.sum +++ b/go.sum @@ -433,7 +433,6 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -501,7 +500,6 @@ golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -549,15 +547,12 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 71320012d5..a88c9b30b1 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -30,6 +30,8 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + + "github.com/stretchr/testify/assert" ) func TestBuildSchema(t *testing.T) { @@ -166,18 +168,8 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { if err != nil { t.Fatalf("could not post: %v", err) } - bodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("could not read from response body: %v", err) - } - resp.Body.Close() // make sure the request is not handled successfully - if want, have := "404 page not found\n", string(bodyBytes); have != want { - t.Errorf("have:\n%v\nwant:\n%v", have, want) - } - if want, have := 404, resp.StatusCode; want != have { - t.Errorf("wrong statuscode, have:\n%v\nwant:%v", have, want) - } + assert.Equal(t, http.StatusNotFound, resp.StatusCode) } func createNode(t *testing.T, gqlEnabled bool) *node.Node { diff --git a/node/api_test.go b/node/api_test.go index a07ce833c4..9c3fa3a31d 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -244,7 +244,10 @@ func TestStartRPC(t *testing.T) { } for _, test := range tests { + test := test t.Run(test.name, func(t *testing.T) { + t.Parallel() + // Apply some sane defaults. config := test.cfg // config.Logger = testlog.Logger(t, log.LvlDebug) diff --git a/node/config.go b/node/config.go index 61e41cd7d0..447a69505c 100644 --- a/node/config.go +++ b/node/config.go @@ -139,6 +139,9 @@ type Config struct { // interface. HTTPTimeouts rpc.HTTPTimeouts + // HTTPPathPrefix specifies a path prefix on which http-rpc is to be served. + HTTPPathPrefix string `toml:",omitempty"` + // WSHost is the host interface on which to start the websocket RPC server. If // this field is empty, no websocket API endpoint will be started. WSHost string @@ -148,6 +151,9 @@ type Config struct { // ephemeral nodes). WSPort int `toml:",omitempty"` + // WSPathPrefix specifies a path prefix on which ws-rpc is to be served. + WSPathPrefix string `toml:",omitempty"` + // WSOrigins is the list of domain to accept websocket requests from. Please be // aware that the server can only act upon the HTTP request the client sends and // cannot verify the validity of the request header. diff --git a/node/node.go b/node/node.go index b58594ef1d..2ed4c31f60 100644 --- a/node/node.go +++ b/node/node.go @@ -135,6 +135,14 @@ func New(conf *Config) (*Node, error) { node.server.Config.NodeDatabase = node.config.NodeDB() } + // Check HTTP/WS prefixes are valid. + if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil { + return nil, err + } + if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil { + return nil, err + } + // Configure RPC servers. node.http = newHTTPServer(node.log, conf.HTTPTimeouts) node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts) @@ -346,6 +354,7 @@ func (n *Node) startRPC() error { CorsAllowedOrigins: n.config.HTTPCors, Vhosts: n.config.HTTPVirtualHosts, Modules: n.config.HTTPModules, + prefix: n.config.HTTPPathPrefix, } if err := n.http.setListenAddr(n.config.HTTPHost, n.config.HTTPPort); err != nil { return err @@ -361,6 +370,7 @@ func (n *Node) startRPC() error { config := wsConfig{ Modules: n.config.WSModules, Origins: n.config.WSOrigins, + prefix: n.config.WSPathPrefix, } if err := server.setListenAddr(n.config.WSHost, n.config.WSPort); err != nil { return err @@ -457,6 +467,7 @@ func (n *Node) RegisterHandler(name, path string, handler http.Handler) { if n.state != initializingState { panic("can't register HTTP handler on running/stopped node") } + n.http.mux.Handle(path, handler) n.http.handlerNames[path] = name } @@ -513,17 +524,18 @@ func (n *Node) IPCEndpoint() string { return n.ipc.endpoint } -// HTTPEndpoint returns the URL of the HTTP server. +// HTTPEndpoint returns the URL of the HTTP server. Note that this URL does not +// contain the JSON-RPC path prefix set by HTTPPathPrefix. func (n *Node) HTTPEndpoint() string { return "http://" + n.http.listenAddr() } -// WSEndpoint retrieves the current WS endpoint used by the protocol stack. +// WSEndpoint returns the current JSON-RPC over WebSocket endpoint. func (n *Node) WSEndpoint() string { if n.http.wsAllowed() { - return "ws://" + n.http.listenAddr() + return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix } - return "ws://" + n.ws.listenAddr() + return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix } // EventMux retrieves the event multiplexer used by all the network services in diff --git a/node/node_test.go b/node/node_test.go index 8f306ef021..6731dbac1f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -390,7 +390,7 @@ func TestLifecycleTerminationGuarantee(t *testing.T) { } // Tests whether a handler can be successfully mounted on the canonical HTTP server -// on the given path +// on the given prefix func TestRegisterHandler_Successful(t *testing.T) { node := createNode(t, 7878, 7979) @@ -483,7 +483,112 @@ func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) { if !checkRPC(node.HTTPEndpoint()) { t.Fatalf("http request failed") } +} + +type rpcPrefixTest struct { + httpPrefix, wsPrefix string + // These lists paths on which JSON-RPC should be served / not served. + wantHTTP []string + wantNoHTTP []string + wantWS []string + wantNoWS []string +} + +func TestNodeRPCPrefix(t *testing.T) { + t.Parallel() + + tests := []rpcPrefixTest{ + // both off + { + httpPrefix: "", wsPrefix: "", + wantHTTP: []string{"/", "/?p=1"}, + wantNoHTTP: []string{"/test", "/test?p=1"}, + wantWS: []string{"/", "/?p=1"}, + wantNoWS: []string{"/test", "/test?p=1"}, + }, + // only http prefix + { + httpPrefix: "/testprefix", wsPrefix: "", + wantHTTP: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"}, + wantWS: []string{"/", "/?p=1"}, + wantNoWS: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"}, + }, + // only ws prefix + { + httpPrefix: "", wsPrefix: "/testprefix", + wantHTTP: []string{"/", "/?p=1"}, + wantNoHTTP: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"}, + wantWS: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoWS: []string{"/", "/?p=1", "/test", "/test?p=1"}, + }, + // both set + { + httpPrefix: "/testprefix", wsPrefix: "/testprefix", + wantHTTP: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"}, + wantWS: []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"}, + wantNoWS: []string{"/", "/?p=1", "/test", "/test?p=1"}, + }, + } + + for _, test := range tests { + test := test + name := fmt.Sprintf("http=%s ws=%s", test.httpPrefix, test.wsPrefix) + t.Run(name, func(t *testing.T) { + cfg := &Config{ + HTTPHost: "127.0.0.1", + HTTPPathPrefix: test.httpPrefix, + WSHost: "127.0.0.1", + WSPathPrefix: test.wsPrefix, + } + node, err := New(cfg) + if err != nil { + t.Fatal("can't create node:", err) + } + defer node.Close() + if err := node.Start(); err != nil { + t.Fatal("can't start node:", err) + } + test.check(t, node) + }) + } +} + +func (test rpcPrefixTest) check(t *testing.T, node *Node) { + t.Helper() + httpBase := "http://" + node.http.listenAddr() + wsBase := "ws://" + node.http.listenAddr() + + if node.WSEndpoint() != wsBase+test.wsPrefix { + t.Errorf("Error: node has wrong WSEndpoint %q", node.WSEndpoint()) + } + + for _, path := range test.wantHTTP { + resp := rpcRequest(t, httpBase+path) + if resp.StatusCode != 200 { + t.Errorf("Error: %s: bad status code %d, want 200", path, resp.StatusCode) + } + } + for _, path := range test.wantNoHTTP { + resp := rpcRequest(t, httpBase+path) + if resp.StatusCode != 404 { + t.Errorf("Error: %s: bad status code %d, want 404", path, resp.StatusCode) + } + } + for _, path := range test.wantWS { + err := wsRequest(t, wsBase+path, "") + if err != nil { + t.Errorf("Error: %s: WebSocket connection failed: %v", path, err) + } + } + for _, path := range test.wantNoWS { + err := wsRequest(t, wsBase+path, "") + if err == nil { + t.Errorf("Error: %s: WebSocket connection succeeded for path in wantNoWS", path) + } + } } func createNode(t *testing.T, httpPort, wsPort int) *Node { diff --git a/node/rpcstack.go b/node/rpcstack.go index 81e054ec99..d693bb0bbd 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -39,12 +39,14 @@ type httpConfig struct { Modules []string CorsAllowedOrigins []string Vhosts []string + prefix string // path prefix on which to mount http handler } // wsConfig is the JSON-RPC/Websocket configuration type wsConfig struct { Origins []string Modules []string + prefix string // path prefix on which to mount ws handler } type rpcHandler struct { @@ -62,6 +64,7 @@ type httpServer struct { listener net.Listener // non-nil when server is running // HTTP RPC handler things. + httpConfig httpConfig httpHandler atomic.Value // *rpcHandler @@ -79,6 +82,7 @@ type httpServer struct { func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} + h.httpHandler.Store((*rpcHandler)(nil)) h.wsHandler.Store((*rpcHandler)(nil)) return h @@ -142,12 +146,17 @@ func (h *httpServer) start() error { // if server is websocket only, return after logging if h.wsAllowed() && !h.rpcAllowed() { - h.log.Info("WebSocket enabled", "url", fmt.Sprintf("ws://%v", listener.Addr())) + url := fmt.Sprintf("ws://%v", listener.Addr()) + if h.wsConfig.prefix != "" { + url += h.wsConfig.prefix + } + h.log.Info("WebSocket enabled", "url", url) return nil } // Log http endpoint. h.log.Info("HTTP server started", "endpoint", listener.Addr(), + "prefix", h.httpConfig.prefix, "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","), "vhosts", strings.Join(h.httpConfig.Vhosts, ","), ) @@ -170,26 +179,60 @@ func (h *httpServer) start() error { } func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - rpc := h.httpHandler.Load().(*rpcHandler) - if r.RequestURI == "/" { - // Serve JSON-RPC on the root path. - ws := h.wsHandler.Load().(*rpcHandler) - if ws != nil && isWebsocket(r) { + // check if ws request and serve if ws enabled + ws := h.wsHandler.Load().(*rpcHandler) + if ws != nil && isWebsocket(r) { + if checkPath(r, h.wsConfig.prefix) { ws.ServeHTTP(w, r) + } + return + } + // if http-rpc is enabled, try to serve request + rpc := h.httpHandler.Load().(*rpcHandler) + if rpc != nil { + // First try to route in the mux. + // Requests to a path below root are handled by the mux, + // which has all the handlers registered via Node.RegisterHandler. + // These are made available when RPC is enabled. + muxHandler, pattern := h.mux.Handler(r) + if pattern != "" { + muxHandler.ServeHTTP(w, r) return } - if rpc != nil { + + if checkPath(r, h.httpConfig.prefix) { rpc.ServeHTTP(w, r) return } - } else if rpc != nil { - // Requests to a path below root are handled by the mux, - // which has all the handlers registered via Node.RegisterHandler. - // These are made available when RPC is enabled. - h.mux.ServeHTTP(w, r) - return } - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) +} + +// checkPath checks whether a given request URL matches a given path prefix. +func checkPath(r *http.Request, path string) bool { + // if no prefix has been specified, request URL must be on root + if path == "" { + return r.URL.Path == "/" + } + // otherwise, check to make sure prefix matches + return len(r.URL.Path) >= len(path) && r.URL.Path[:len(path)] == path +} + +// validatePrefix checks if 'path' is a valid configuration value for the RPC prefix option. +func validatePrefix(what, path string) error { + if path == "" { + return nil + } + if path[0] != '/' { + return fmt.Errorf(`%s RPC path prefix %q does not contain leading "/"`, what, path) + } + if strings.ContainsAny(path, "?#") { + // This is just to avoid confusion. While these would match correctly (i.e. they'd + // match if URL-escaped into path), it's not easy to understand for users when + // setting that on the command line. + return fmt.Errorf("%s RPC path prefix %q contains URL meta-characters", what, path) + } + return nil } // stop shuts down the HTTP server. diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 8267fb2f1d..f92f0ba396 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -18,7 +18,10 @@ package node import ( "bytes" + "fmt" "net/http" + "net/url" + "strconv" "strings" "testing" @@ -31,25 +34,27 @@ import ( // TestCorsHandler makes sure CORS are properly handled on the http server. func TestCorsHandler(t *testing.T) { - srv := createAndStartServer(t, httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, wsConfig{}) + srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{}) defer srv.stop() + url := "http://" + srv.listenAddr() - resp := testRequest(t, "origin", "test.com", "", srv) + resp := rpcRequest(t, url, "origin", "test.com") assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin")) - resp2 := testRequest(t, "origin", "bad", "", srv) + resp2 := rpcRequest(t, url, "origin", "bad") assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin")) } // TestVhosts makes sure vhosts are properly handled on the http server. func TestVhosts(t *testing.T) { - srv := createAndStartServer(t, httpConfig{Vhosts: []string{"test"}}, false, wsConfig{}) + srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{}) defer srv.stop() + url := "http://" + srv.listenAddr() - resp := testRequest(t, "", "", "test", srv) + resp := rpcRequest(t, url, "host", "test") assert.Equal(t, resp.StatusCode, http.StatusOK) - resp2 := testRequest(t, "", "", "bad", srv) + resp2 := rpcRequest(t, url, "host", "bad") assert.Equal(t, resp2.StatusCode, http.StatusForbidden) } @@ -138,14 +143,15 @@ func TestWebsocketOrigins(t *testing.T) { }, } for _, tc := range tests { - srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: splitAndTrim(tc.spec)}) + srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)}) + url := fmt.Sprintf("ws://%v", srv.listenAddr()) for _, origin := range tc.expOk { - if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err != nil { + if err := wsRequest(t, url, origin); err != nil { t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) } } for _, origin := range tc.expFail { - if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err == nil { + if err := wsRequest(t, url, origin); err == nil { t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) } } @@ -168,47 +174,118 @@ func TestIsWebsocket(t *testing.T) { assert.True(t, isWebsocket(r)) } -func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer { +func Test_checkPath(t *testing.T) { + tests := []struct { + req *http.Request + prefix string + expected bool + }{ + { + req: &http.Request{URL: &url.URL{Path: "/test"}}, + prefix: "/test", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/testing"}}, + prefix: "/test", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/"}}, + prefix: "/test", + expected: false, + }, + { + req: &http.Request{URL: &url.URL{Path: "/fail"}}, + prefix: "/test", + expected: false, + }, + { + req: &http.Request{URL: &url.URL{Path: "/"}}, + prefix: "", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/fail"}}, + prefix: "", + expected: false, + }, + { + req: &http.Request{URL: &url.URL{Path: "/"}}, + prefix: "/", + expected: true, + }, + { + req: &http.Request{URL: &url.URL{Path: "/testing"}}, + prefix: "/", + expected: true, + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix)) + }) + } +} + +func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer { t.Helper() srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts) - - assert.NoError(t, srv.enableRPC(nil, conf)) + assert.NoError(t, srv.enableRPC(nil, *conf)) if ws { - assert.NoError(t, srv.enableWS(nil, wsConf)) + assert.NoError(t, srv.enableWS(nil, *wsConf)) } assert.NoError(t, srv.setListenAddr("localhost", 0)) assert.NoError(t, srv.start()) - return srv } -func attemptWebsocketConnectionFromOrigin(t *testing.T, srv *httpServer, browserOrigin string) error { +// wsRequest attempts to open a WebSocket connection to the given URL. +func wsRequest(t *testing.T, url, browserOrigin string) error { t.Helper() - dialer := websocket.DefaultDialer - _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ - "Content-type": []string{"application/json"}, - "Sec-WebSocket-Version": []string{"13"}, - "Origin": []string{browserOrigin}, - }) + t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin) + + headers := make(http.Header) + if browserOrigin != "" { + headers.Set("Origin", browserOrigin) + } + conn, _, err := websocket.DefaultDialer.Dial(url, headers) + if conn != nil { + conn.Close() + } return err } -func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response { +// rpcRequest performs a JSON-RPC request to the given URL. +func rpcRequest(t *testing.T, url string, extraHeaders ...string) *http.Response { t.Helper() - body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,method":"rpc_modules"}`)) - req, _ := http.NewRequest("POST", "http://"+srv.listenAddr(), body) + // Create the request. + body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`)) + req, err := http.NewRequest("POST", url, body) + if err != nil { + t.Fatal("could not create http request:", err) + } req.Header.Set("content-type", "application/json") - if key != "" && value != "" { - req.Header.Set(key, value) + + // Apply extra headers. + if len(extraHeaders)%2 != 0 { + panic("odd extraHeaders length") } - if host != "" { - req.Host = host + for i := 0; i < len(extraHeaders); i += 2 { + key, value := extraHeaders[i], extraHeaders[i+1] + if strings.ToLower(key) == "host" { + req.Host = value + } else { + req.Header.Set(key, value) + } } - client := http.DefaultClient - resp, err := client.Do(req) + // Perform the request. + t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders) + resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } From ef84da8481feedce8616c43ec48e15b7a4838290 Mon Sep 17 00:00:00 2001 From: Alex Prut <1648497+alexprut@users.noreply.github.com> Date: Tue, 2 Feb 2021 10:32:44 +0100 Subject: [PATCH 298/948] all: remove unneeded parentheses (#21921) * remove uneeded convertion type * remove redundant type in composite literal * omit explicit type where implicit * remove unused redundant parenthesis * remove redundant import alias duktape --- accounts/keystore/account_cache.go | 2 +- cmd/puppeth/genesis.go | 2 +- cmd/puppeth/wizard_genesis.go | 2 +- core/state/snapshot/conversion.go | 4 ++-- core/vm/runtime/runtime_test.go | 4 ++-- crypto/bls12381/bls12_381_test.go | 2 +- crypto/signify/signify.go | 2 +- eth/protocols/snap/sync.go | 2 +- eth/tracers/tracer.go | 2 +- metrics/cpu_syscall.go | 2 +- metrics/exp/exp.go | 2 +- metrics/gauge_float64_test.go | 12 ++++++------ node/utils_test.go | 4 ++-- signer/core/api.go | 2 +- trie/trie_test.go | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 8f660e282f..a3ec6e9c56 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -262,7 +262,7 @@ func (ac *accountCache) scanAccounts() error { switch { case err != nil: log.Debug("Failed to decode keystore key", "path", path, "err", err) - case (addr == common.Address{}): + case addr == common.Address{}: log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") default: return &accounts.Account{ diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go index 46268ec11b..ef1f977bf0 100644 --- a/cmd/puppeth/genesis.go +++ b/cmd/puppeth/genesis.go @@ -425,7 +425,7 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin spec.Params.EIP98Transition = math.MaxInt64 spec.Genesis.Seal.Ethereum.Nonce = types.EncodeNonce(genesis.Nonce) - spec.Genesis.Seal.Ethereum.MixHash = (genesis.Mixhash[:]) + spec.Genesis.Seal.Ethereum.MixHash = genesis.Mixhash[:] spec.Genesis.Difficulty = (*hexutil.Big)(genesis.Difficulty) spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 52093975cb..78f63e1c7a 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -259,7 +259,7 @@ func (w *wizard) manageGenesis() { // Export the native genesis spec used by puppeth and Geth gethJson := filepath.Join(folder, fmt.Sprintf("%s.json", w.network)) - if err := ioutil.WriteFile((gethJson), out, 0644); err != nil { + if err := ioutil.WriteFile(gethJson, out, 0644); err != nil { log.Error("Failed to save genesis file", "err", err) return } diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 9832225345..4ec229b7ac 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -38,7 +38,7 @@ type trieKV struct { type ( // trieGeneratorFn is the interface of trie generation which can // be implemented by different trie algorithm. - trieGeneratorFn func(in chan (trieKV), out chan (common.Hash)) + trieGeneratorFn func(in chan trieKV, out chan common.Hash) // leafCallbackFn is the callback invoked at the leaves of the trie, // returns the subtrie root with the specified subtrie identifier. @@ -266,7 +266,7 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato // stdGenerate is a very basic hexary trie builder which uses the same Trie // as the rest of geth, with no enhancements or optimizations -func stdGenerate(in chan (trieKV), out chan (common.Hash)) { +func stdGenerate(in chan trieKV, out chan common.Hash) { t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New())) for leaf := range in { t.TryUpdate(leaf.key[:], leaf.value) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index b185258dad..af69e3333f 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -500,7 +500,7 @@ func DisabledTestEipExampleCases(t *testing.T) { { code := []byte{ - byte(vm.PUSH9), 0x00, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, (4 + 8), + byte(vm.PUSH9), 0x00, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 4 + 8, byte(vm.JUMPSUB), byte(vm.STOP), byte(vm.BEGINSUB), @@ -516,7 +516,7 @@ func DisabledTestEipExampleCases(t *testing.T) { // out the trace. { code := []byte{ - byte(vm.PUSH9), 0x01, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, (4 + 8), + byte(vm.PUSH9), 0x01, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 4 + 8, byte(vm.JUMPSUB), byte(vm.STOP), byte(vm.BEGINSUB), diff --git a/crypto/bls12381/bls12_381_test.go b/crypto/bls12381/bls12_381_test.go index 51523c9ee7..6bf5834105 100644 --- a/crypto/bls12381/bls12_381_test.go +++ b/crypto/bls12381/bls12_381_test.go @@ -5,7 +5,7 @@ import ( "math/big" ) -var fuz int = 10 +var fuz = 10 func randScalar(max *big.Int) *big.Int { a, _ := rand.Int(rand.Reader, max) diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go index 7ba9705491..e280f87268 100644 --- a/crypto/signify/signify.go +++ b/crypto/signify/signify.go @@ -46,7 +46,7 @@ func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum [] if string(keydata[:2]) != "Ed" { return nil, nil, nil, errInvalidKeyHeader } - return ed25519.PrivateKey(keydata[40:]), keydata[:2], keydata[32:40], nil + return keydata[40:], keydata[:2], keydata[32:40], nil } // SignFile creates a signature of the input file. diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index e7720026bf..422cdf8f72 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -1494,7 +1494,7 @@ func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { // retrievals as not-pending, ready for resheduling req.timeout.Stop() for i, hash := range req.hashes { - req.task.trieTasks[hash] = [][]byte(req.paths[i]) + req.task.trieTasks[hash] = req.paths[i] } } diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index c9f00d7371..dba7ce87cf 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -31,7 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - duktape "gopkg.in/olebedev/go-duktape.v3" + "gopkg.in/olebedev/go-duktape.v3" ) // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js. diff --git a/metrics/cpu_syscall.go b/metrics/cpu_syscall.go index e245453e82..106637af5a 100644 --- a/metrics/cpu_syscall.go +++ b/metrics/cpu_syscall.go @@ -31,5 +31,5 @@ func getProcessCPUTime() int64 { log.Warn("Failed to retrieve CPU time", "err", err) return 0 } - return int64(usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert + return (usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert } diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index f510b8381e..3ebe8cc68a 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -128,7 +128,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) { exp.getInt(name + ".count").Set(m.Count()) exp.getFloat(name + ".one-minute").Set(m.Rate1()) exp.getFloat(name + ".five-minute").Set(m.Rate5()) - exp.getFloat(name + ".fifteen-minute").Set((m.Rate15())) + exp.getFloat(name + ".fifteen-minute").Set(m.Rate15()) exp.getFloat(name + ".mean").Set(m.RateMean()) } diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go index 3ee568e7ba..02b75580c4 100644 --- a/metrics/gauge_float64_test.go +++ b/metrics/gauge_float64_test.go @@ -12,27 +12,27 @@ func BenchmarkGuageFloat64(b *testing.B) { func TestGaugeFloat64(t *testing.T) { g := NewGaugeFloat64() - g.Update(float64(47.0)) - if v := g.Value(); float64(47.0) != v { + g.Update(47.0) + if v := g.Value(); 47.0 != v { t.Errorf("g.Value(): 47.0 != %v\n", v) } } func TestGaugeFloat64Snapshot(t *testing.T) { g := NewGaugeFloat64() - g.Update(float64(47.0)) + g.Update(47.0) snapshot := g.Snapshot() g.Update(float64(0)) - if v := snapshot.Value(); float64(47.0) != v { + if v := snapshot.Value(); 47.0 != v { t.Errorf("g.Value(): 47.0 != %v\n", v) } } func TestGetOrRegisterGaugeFloat64(t *testing.T) { r := NewRegistry() - NewRegisteredGaugeFloat64("foo", r).Update(float64(47.0)) + NewRegisteredGaugeFloat64("foo", r).Update(47.0) t.Logf("registry: %v", r) - if g := GetOrRegisterGaugeFloat64("foo", r); float64(47.0) != g.Value() { + if g := GetOrRegisterGaugeFloat64("foo", r); 47.0 != g.Value() { t.Fatal(g) } } diff --git a/node/utils_test.go b/node/utils_test.go index 44c83e22da..b7474bb706 100644 --- a/node/utils_test.go +++ b/node/utils_test.go @@ -82,11 +82,11 @@ func (f *FullService) Stop() error { return nil } func (f *FullService) Protocols() []p2p.Protocol { return []p2p.Protocol{ - p2p.Protocol{ + { Name: "test1", Version: uint(1), }, - p2p.Protocol{ + { Name: "test2", Version: uint(2), }, diff --git a/signer/core/api.go b/signer/core/api.go index 7595d2d484..07e206a74c 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -439,7 +439,7 @@ func (api *SignerAPI) newAccount() (common.Address, error) { continue } if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil { - api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr)) + api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", i+1, pwErr)) } else { // No error acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text) diff --git a/trie/trie_test.go b/trie/trie_test.go index ddbdcbbd5b..87bce9abca 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -322,7 +322,7 @@ func TestLargeValue(t *testing.T) { // TestRandomCases tests som cases that were found via random fuzzing func TestRandomCases(t *testing.T) { - var rt []randTestStep = []randTestStep{ + var rt = []randTestStep{ {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 0 {op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")}, // step 1 {op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000002")}, // step 2 From 83e4c49e2b8d3fa83675cf5647e45a6ade7b8b4f Mon Sep 17 00:00:00 2001 From: ucwong Date: Tue, 2 Feb 2021 20:09:23 +0800 Subject: [PATCH 299/948] trie : use trie.NewStackTrie instead of new(trie.Trie) (#22246) The PR makes use of the stacktrie, which is is more lenient on resource consumption, than the regular trie, in cases where we only need it for DeriveSha --- cmd/evm/internal/t8ntool/execution.go | 4 ++-- consensus/clique/clique.go | 2 +- consensus/ethash/consensus.go | 2 +- core/blockchain_test.go | 4 ++-- core/genesis.go | 2 +- core/state_processor_test.go | 2 +- core/tx_pool_test.go | 2 +- eth/fetcher/block_fetcher.go | 2 +- eth/fetcher/block_fetcher_test.go | 2 +- les/odr_requests.go | 4 ++-- miner/miner_test.go | 2 +- miner/worker.go | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 95e6de37cb..525723e3cb 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -229,8 +229,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } execRs := &ExecutionResult{ StateRoot: root, - TxRoot: types.DeriveSha(includedTxs, new(trie.Trie)), - ReceiptRoot: types.DeriveSha(receipts, new(trie.Trie)), + TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), + ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), Bloom: types.CreateBloom(receipts), LogsHash: rlpHash(statedb.Logs()), Receipts: receipts, diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 6c667804d8..61d4358b4e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -565,7 +565,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * c.Finalize(chain, header, state, txs, uncles) // Assemble and return the final block for sealing - return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil + return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil } // Authorize injects a private key into the consensus engine to mint new blocks diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index c58fe7a530..1b801b253f 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -586,7 +586,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea ethash.Finalize(chain, header, state, txs, uncles) // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil + return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil } // SealHash returns the hash of a block prior to it being sealed. diff --git a/core/blockchain_test.go b/core/blockchain_test.go index d60a235981..c33e7321ec 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -683,12 +683,12 @@ func TestFastVsFullChains(t *testing.T) { } if fblock, arblock, anblock := fast.GetBlockByHash(hash), archive.GetBlockByHash(hash), ancient.GetBlockByHash(hash); fblock.Hash() != arblock.Hash() || anblock.Hash() != arblock.Hash() { t.Errorf("block #%d [%x]: block mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock, anblock, arblock) - } else if types.DeriveSha(fblock.Transactions(), new(trie.Trie)) != types.DeriveSha(arblock.Transactions(), new(trie.Trie)) || types.DeriveSha(anblock.Transactions(), new(trie.Trie)) != types.DeriveSha(arblock.Transactions(), new(trie.Trie)) { + } else if types.DeriveSha(fblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) || types.DeriveSha(anblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) { t.Errorf("block #%d [%x]: transactions mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Transactions(), anblock.Transactions(), arblock.Transactions()) } else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) || types.CalcUncleHash(anblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) { t.Errorf("block #%d [%x]: uncles mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Uncles(), anblock, arblock.Uncles()) } - if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts, new(trie.Trie)) != types.DeriveSha(areceipts, new(trie.Trie)) { + if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts, trie.NewStackTrie(nil)) != types.DeriveSha(areceipts, trie.NewStackTrie(nil)) { t.Errorf("block #%d [%x]: receipts mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, freceipts, anreceipts, areceipts) } } diff --git a/core/genesis.go b/core/genesis.go index f678a3bbca..a01eee9eb2 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -288,7 +288,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { statedb.Commit(false) statedb.Database().TrieDB().Commit(root, true, nil) - return types.NewBlock(head, nil, nil, nil, new(trie.Trie)) + return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) } // Commit writes the block and state of a genesis specification to the database. diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 6e63975ac1..5976ecc3d4 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -148,5 +148,5 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr } header.Root = common.BytesToHash(hasher.Sum(nil)) // Assemble and return the final block for sealing - return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)) + return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)) } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 47d3830b06..5d555f5a9c 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -55,7 +55,7 @@ type testBlockChain struct { func (bc *testBlockChain) CurrentBlock() *types.Block { return types.NewBlock(&types.Header{ GasLimit: bc.gasLimit, - }, nil, nil, nil, new(trie.Trie)) + }, nil, nil, nil, trie.NewStackTrie(nil)) } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 270aaf5918..5ea8a128d9 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -620,7 +620,7 @@ func (f *BlockFetcher) loop() { continue } if txnHash == (common.Hash{}) { - txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), new(trie.Trie)) + txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil)) } if txnHash != announce.header.TxHash { continue diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 3220002a99..a6eef71da0 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -39,7 +39,7 @@ var ( testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testAddress = crypto.PubkeyToAddress(testKey.PublicKey) genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) - unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit}, nil, nil, nil, new(trie.Trie)) + unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit}, nil, nil, nil, trie.NewStackTrie(nil)) ) // makeChain creates a chain of n blocks starting at and including parent. diff --git a/les/odr_requests.go b/les/odr_requests.go index 962b88a322..711c54b40f 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -116,7 +116,7 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { if r.Header == nil { return errHeaderUnavailable } - if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), new(trie.Trie)) { + if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)) { return errTxHashMismatch } if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) { @@ -174,7 +174,7 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { if r.Header == nil { return errHeaderUnavailable } - if r.Header.ReceiptHash != types.DeriveSha(receipt, new(trie.Trie)) { + if r.Header.ReceiptHash != types.DeriveSha(receipt, trie.NewStackTrie(nil)) { return errReceiptHashMismatch } // Validations passed, store and return diff --git a/miner/miner_test.go b/miner/miner_test.go index 127b4c7687..da1e472dbd 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -63,7 +63,7 @@ type testBlockChain struct { func (bc *testBlockChain) CurrentBlock() *types.Block { return types.NewBlock(&types.Header{ GasLimit: bc.gasLimit, - }, nil, nil, nil, new(trie.Trie)) + }, nil, nil, nil, trie.NewStackTrie(nil)) } func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { diff --git a/miner/worker.go b/miner/worker.go index 82d08d4c7e..e81d50e46e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -728,7 +728,7 @@ func (w *worker) updateSnapshot() { w.current.txs, uncles, w.current.receipts, - new(trie.Trie), + trie.NewStackTrie(nil), ) w.snapshotState = w.current.state.Copy() } From 3512b41c5cd024421a2048c70688c1b82f122dff Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Feb 2021 11:02:35 +0100 Subject: [PATCH 300/948] core: reset txpool state on sethead (#22247) fixes an issue where local transactions that were included in the chain before a SetHead were rejected if resubmitted, since the txpool had not reset the state to the current (older) state. --- core/tx_pool.go | 59 +++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 4a17c31ca8..36546cde75 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1137,44 +1137,45 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // head from the chain. // If that is the case, we don't have the lost transactions any more, and // there's nothing to add - if newNum < oldNum { - // If the reorg ended up on a lower number, it's indicative of setHead being the cause - log.Debug("Skipping transaction reset caused by setHead", - "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) - } else { + if newNum >= oldNum { // If we reorged to a same or higher number, then it's not a case of setHead log.Warn("Transaction pool reset with missing oldhead", "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) - } - return - } - for rem.NumberU64() > add.NumberU64() { - discarded = append(discarded, rem.Transactions()...) - if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { - log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) return } - } - for add.NumberU64() > rem.NumberU64() { - included = append(included, add.Transactions()...) - if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { - log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) - return + // If the reorg ended up on a lower number, it's indicative of setHead being the cause + log.Debug("Skipping transaction reset caused by setHead", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + // We still need to update the current state s.th. the lost transactions can be readded by the user + } else { + for rem.NumberU64() > add.NumberU64() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } } - } - for rem.Hash() != add.Hash() { - discarded = append(discarded, rem.Transactions()...) - if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { - log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) - return + for add.NumberU64() > rem.NumberU64() { + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } } - included = append(included, add.Transactions()...) - if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { - log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) - return + for rem.Hash() != add.Hash() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } } + reinject = types.TxDifference(discarded, included) } - reinject = types.TxDifference(discarded, included) } } // Initialize the internal state to the current head From 54735a67239d19d3f29e3f8ebd348a6c88ed31fa Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Feb 2021 15:04:28 +0100 Subject: [PATCH 301/948] fuzzers: added consensys/gurvy library to bn256 differential fuzzer (#21812) This pr adds consensys' gurvy bn256 variant into the code for differential fuzzing. --- go.mod | 1 + go.sum | 124 ++++++++++++++++++++++++++++++ tests/fuzzers/bn256/bn256_fuzz.go | 73 +++++++++++++----- 3 files changed, 180 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 48c6889fbe..5fd3f772a4 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 + github.com/consensys/gurvy v0.3.8 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea github.com/dlclark/regexp2 v1.2.0 // indirect diff --git a/go.sum b/go.sum index 2c16d81f04..1ac0cf81f2 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbf cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= @@ -56,12 +57,19 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= @@ -92,7 +100,17 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc= +github.com/consensys/gurvy v0.3.8 h1:H2hvjvT2OFMgdMn5ZbhXqHt+F8DJ2clZW7Vmc0kFFxc= +github.com/consensys/gurvy v0.3.8/go.mod h1:sN75xnsiD593XnhbhvG2PkOy194pZBzqShWF/kwuW/g= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -110,13 +128,19 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -126,6 +150,7 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -145,10 +170,12 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -190,26 +217,52 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -226,6 +279,7 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QU github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -233,12 +287,15 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kilic/bls12-381 v0.0.0-20201226121925-69dacb279461/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= @@ -257,13 +314,18 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -273,6 +335,15 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= @@ -300,9 +371,11 @@ github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= @@ -314,25 +387,36 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -342,12 +426,20 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= +github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -358,22 +450,30 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -387,6 +487,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -404,14 +505,19 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -440,9 +546,11 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -464,6 +572,10 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -472,6 +584,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -491,15 +604,19 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -547,18 +664,25 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/tests/fuzzers/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go index 477fe0a160..c98fbc33ae 100644 --- a/tests/fuzzers/bn256/bn256_fuzz.go +++ b/tests/fuzzers/bn256/bn256_fuzz.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. +// +build gofuzz + package bn256 import ( @@ -10,44 +12,53 @@ import ( "io" "math/big" + gurvy "github.com/consensys/gurvy/bn256" cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" google "github.com/ethereum/go-ethereum/crypto/bn256/google" ) -func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1) { +func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *gurvy.G1Affine) { _, xc, err := cloudflare.RandomG1(input) if err != nil { // insufficient input - return nil, nil + return nil, nil, nil } xg := new(google.G1) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + } + xs := new(gurvy.G1Affine) + if err := xs.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> consensys:", err)) } - return xc, xg + return xc, xg, xs } -func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2) { +func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *gurvy.G2Affine) { _, xc, err := cloudflare.RandomG2(input) if err != nil { // insufficient input - return nil, nil + return nil, nil, nil } xg := new(google.G2) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) } - return xc, xg + xs := new(gurvy.G2Affine) + if err := xs.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> consensys:", err)) + } + return xc, xg, xs } // FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. func FuzzAdd(data []byte) int { input := bytes.NewReader(data) - xc, xg := getG1Points(input) + xc, xg, xs := getG1Points(input) if xc == nil { return 0 } - yc, yg := getG1Points(input) + yc, yg, ys := getG1Points(input) if yc == nil { return 0 } @@ -59,8 +70,16 @@ func FuzzAdd(data []byte) int { rg := new(google.G1) rg.Add(xg, yg) + tmpX := new(gurvy.G1Jac).FromAffine(xs) + tmpY := new(gurvy.G1Jac).FromAffine(ys) + rs := new(gurvy.G1Affine).FromJacobian(tmpX.AddAssign(tmpY)) + if !bytes.Equal(rc.Marshal(), rg.Marshal()) { - panic("add mismatch") + panic("add mismatch: cloudflare/google") + } + + if !bytes.Equal(rc.Marshal(), rs.Marshal()) { + panic("add mismatch: cloudflare/consensys") } return 1 } @@ -69,7 +88,7 @@ func FuzzAdd(data []byte) int { // libraries. func FuzzMul(data []byte) int { input := bytes.NewReader(data) - pc, pg := getG1Points(input) + pc, pg, ps := getG1Points(input) if pc == nil { return 0 } @@ -93,25 +112,43 @@ func FuzzMul(data []byte) int { rg := new(google.G1) rg.ScalarMult(pg, new(big.Int).SetBytes(buf)) + rs := new(gurvy.G1Jac) + psJac := new(gurvy.G1Jac).FromAffine(ps) + rs.ScalarMultiplication(psJac, new(big.Int).SetBytes(buf)) + rsAffine := new(gurvy.G1Affine).FromJacobian(rs) + if !bytes.Equal(rc.Marshal(), rg.Marshal()) { - panic("scalar mul mismatch") + panic("scalar mul mismatch: cloudflare/google") + } + if !bytes.Equal(rc.Marshal(), rsAffine.Marshal()) { + panic("scalar mul mismatch: cloudflare/consensys") } return 1 } func FuzzPair(data []byte) int { input := bytes.NewReader(data) - pc, pg := getG1Points(input) + pc, pg, ps := getG1Points(input) if pc == nil { return 0 } - tc, tg := getG2Points(input) + tc, tg, ts := getG2Points(input) if tc == nil { return 0 } - // Pair the two points and ensure thet result in the same output - if cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { - panic("pair mismatch") + // Pair the two points and ensure they result in the same output + clPair := cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) + if clPair != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { + panic("pairing mismatch: cloudflare/google") + } + + coPair, err := gurvy.PairingCheck([]gurvy.G1Affine{*ps}, []gurvy.G2Affine{*ts}) + if err != nil { + panic(fmt.Sprintf("gurvy encountered error: %v", err)) + } + if clPair != coPair { + panic("pairing mismatch: cloudflare/consensys") } + return 1 } From 28121324ac42ad88b911da514ae2c092f5718f5d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 5 Feb 2021 11:35:55 +0100 Subject: [PATCH 302/948] internal/ethapi: comment nitpick (#22270) --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9c3f6b9161..ed109cd201 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -368,7 +368,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg } // SendTransaction will create a transaction from the given arguments and -// tries to sign it with the key associated with args.To. If the given passwd isn't +// tries to sign it with the key associated with args.From. If the given passwd isn't // able to decrypt the key it fails. func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { if args.Nonce == nil { From 098a2b6e26e59ce09c72869f7919724ba7fd97ee Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 5 Feb 2021 20:51:15 +0800 Subject: [PATCH 303/948] eth: move eth.Config to a common package (#22205) This moves the eth config definition into a separate package, eth/ethconfig. Packages eth and les can now import this common package instead of importing eth from les, reducing dependencies. Co-authored-by: Felix Lange --- cmd/faucet/faucet.go | 4 +- cmd/geth/config.go | 6 +- cmd/utils/cmd.go | 4 +- cmd/utils/flags.go | 109 +++++++++++++++--------------- cmd/utils/flags_legacy.go | 14 ++-- console/console_test.go | 5 +- core/bloom_indexer.go | 92 +++++++++++++++++++++++++ eth/backend.go | 51 +++----------- eth/bloombits.go | 69 ------------------- eth/{ => ethconfig}/config.go | 64 ++++++++++++++---- eth/{ => ethconfig}/gen_config.go | 2 +- ethclient/ethclient_test.go | 3 +- graphql/graphql_test.go | 3 +- les/api_test.go | 5 +- les/client.go | 8 +-- les/commons.go | 6 +- les/costtracker.go | 4 +- les/server.go | 15 +++- les/test_helper.go | 8 +-- miner/stress_clique.go | 2 +- miner/stress_ethash.go | 2 +- mobile/geth.go | 4 +- 22 files changed, 265 insertions(+), 215 deletions(-) create mode 100644 core/bloom_indexer.go rename eth/{ => ethconfig}/config.go (72%) rename eth/{ => ethconfig}/gen_config.go (99%) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 763b8d25f8..e839f1c886 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -47,8 +47,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/les" @@ -247,7 +247,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui } // Assemble the Ethereum light client protocol - cfg := eth.DefaultConfig + cfg := ethconfig.Defaults cfg.SyncMode = downloader.LightSync cfg.NetworkId = network cfg.Genesis = genesis diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c5b330b2d8..c71d5eb653 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -27,7 +27,7 @@ import ( "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -85,7 +85,7 @@ type whisperDeprecatedConfig struct { } type gethConfig struct { - Eth eth.Config + Eth ethconfig.Config Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig @@ -121,7 +121,7 @@ func defaultNodeConfig() node.Config { func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ - Eth: eth.DefaultConfig, + Eth: ethconfig.Defaults, Node: defaultNodeConfig(), Metrics: metrics.DefaultConfig, } diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 4306216892..d4051e59ef 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/log" @@ -75,7 +75,7 @@ func StartNode(ctx *cli.Context, stack *node.Node) { signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigc) - minFreeDiskSpace := eth.DefaultConfig.TrieDirtyCache + minFreeDiskSpace := ethconfig.Defaults.TrieDirtyCache if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) { minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name) } else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index aa5180dd9f..1ea26be457 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -44,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/ethdb" @@ -137,7 +138,7 @@ var ( NetworkIdFlag = cli.Uint64Flag{ Name: "networkid", Usage: "Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead)", - Value: eth.DefaultConfig.NetworkId, + Value: ethconfig.Defaults.NetworkId, } MainnetFlag = cli.BoolFlag{ Name: "mainnet", @@ -196,7 +197,7 @@ var ( Name: "nocode", Usage: "Exclude contract code (save db lookups)", } - defaultSyncMode = eth.DefaultConfig.SyncMode + defaultSyncMode = ethconfig.Defaults.SyncMode SyncModeFlag = TextMarshalerFlag{ Name: "syncmode", Usage: `Blockchain sync mode ("fast", "full", "snap" or "light")`, @@ -228,32 +229,32 @@ var ( LightServeFlag = cli.IntFlag{ Name: "light.serve", Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)", - Value: eth.DefaultConfig.LightServ, + Value: ethconfig.Defaults.LightServ, } LightIngressFlag = cli.IntFlag{ Name: "light.ingress", Usage: "Incoming bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", - Value: eth.DefaultConfig.LightIngress, + Value: ethconfig.Defaults.LightIngress, } LightEgressFlag = cli.IntFlag{ Name: "light.egress", Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", - Value: eth.DefaultConfig.LightEgress, + Value: ethconfig.Defaults.LightEgress, } LightMaxPeersFlag = cli.IntFlag{ Name: "light.maxpeers", Usage: "Maximum number of light clients to serve, or light servers to attach to", - Value: eth.DefaultConfig.LightPeers, + Value: ethconfig.Defaults.LightPeers, } UltraLightServersFlag = cli.StringFlag{ Name: "ulc.servers", Usage: "List of trusted ultra-light servers", - Value: strings.Join(eth.DefaultConfig.UltraLightServers, ","), + Value: strings.Join(ethconfig.Defaults.UltraLightServers, ","), } UltraLightFractionFlag = cli.IntFlag{ Name: "ulc.fraction", Usage: "Minimum % of trusted ultra-light servers required to announce a new head", - Value: eth.DefaultConfig.UltraLightFraction, + Value: ethconfig.Defaults.UltraLightFraction, } UltraLightOnlyAnnounceFlag = cli.BoolFlag{ Name: "ulc.onlyannounce", @@ -271,12 +272,12 @@ var ( EthashCachesInMemoryFlag = cli.IntFlag{ Name: "ethash.cachesinmem", Usage: "Number of recent ethash caches to keep in memory (16MB each)", - Value: eth.DefaultConfig.Ethash.CachesInMem, + Value: ethconfig.Defaults.Ethash.CachesInMem, } EthashCachesOnDiskFlag = cli.IntFlag{ Name: "ethash.cachesondisk", Usage: "Number of recent ethash caches to keep on disk (16MB each)", - Value: eth.DefaultConfig.Ethash.CachesOnDisk, + Value: ethconfig.Defaults.Ethash.CachesOnDisk, } EthashCachesLockMmapFlag = cli.BoolFlag{ Name: "ethash.cacheslockmmap", @@ -285,17 +286,17 @@ var ( EthashDatasetDirFlag = DirectoryFlag{ Name: "ethash.dagdir", Usage: "Directory to store the ethash mining DAGs", - Value: DirectoryString(eth.DefaultConfig.Ethash.DatasetDir), + Value: DirectoryString(ethconfig.Defaults.Ethash.DatasetDir), } EthashDatasetsInMemoryFlag = cli.IntFlag{ Name: "ethash.dagsinmem", Usage: "Number of recent ethash mining DAGs to keep in memory (1+GB each)", - Value: eth.DefaultConfig.Ethash.DatasetsInMem, + Value: ethconfig.Defaults.Ethash.DatasetsInMem, } EthashDatasetsOnDiskFlag = cli.IntFlag{ Name: "ethash.dagsondisk", Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)", - Value: eth.DefaultConfig.Ethash.DatasetsOnDisk, + Value: ethconfig.Defaults.Ethash.DatasetsOnDisk, } EthashDatasetsLockMmapFlag = cli.BoolFlag{ Name: "ethash.dagslockmmap", @@ -323,37 +324,37 @@ var ( TxPoolPriceLimitFlag = cli.Uint64Flag{ Name: "txpool.pricelimit", Usage: "Minimum gas price limit to enforce for acceptance into the pool", - Value: eth.DefaultConfig.TxPool.PriceLimit, + Value: ethconfig.Defaults.TxPool.PriceLimit, } TxPoolPriceBumpFlag = cli.Uint64Flag{ Name: "txpool.pricebump", Usage: "Price bump percentage to replace an already existing transaction", - Value: eth.DefaultConfig.TxPool.PriceBump, + Value: ethconfig.Defaults.TxPool.PriceBump, } TxPoolAccountSlotsFlag = cli.Uint64Flag{ Name: "txpool.accountslots", Usage: "Minimum number of executable transaction slots guaranteed per account", - Value: eth.DefaultConfig.TxPool.AccountSlots, + Value: ethconfig.Defaults.TxPool.AccountSlots, } TxPoolGlobalSlotsFlag = cli.Uint64Flag{ Name: "txpool.globalslots", Usage: "Maximum number of executable transaction slots for all accounts", - Value: eth.DefaultConfig.TxPool.GlobalSlots, + Value: ethconfig.Defaults.TxPool.GlobalSlots, } TxPoolAccountQueueFlag = cli.Uint64Flag{ Name: "txpool.accountqueue", Usage: "Maximum number of non-executable transaction slots permitted per account", - Value: eth.DefaultConfig.TxPool.AccountQueue, + Value: ethconfig.Defaults.TxPool.AccountQueue, } TxPoolGlobalQueueFlag = cli.Uint64Flag{ Name: "txpool.globalqueue", Usage: "Maximum number of non-executable transaction slots for all accounts", - Value: eth.DefaultConfig.TxPool.GlobalQueue, + Value: ethconfig.Defaults.TxPool.GlobalQueue, } TxPoolLifetimeFlag = cli.DurationFlag{ Name: "txpool.lifetime", Usage: "Maximum amount of time non-executable transaction are queued", - Value: eth.DefaultConfig.TxPool.Lifetime, + Value: ethconfig.Defaults.TxPool.Lifetime, } // Performance tuning settings CacheFlag = cli.IntFlag{ @@ -374,12 +375,12 @@ var ( CacheTrieJournalFlag = cli.StringFlag{ Name: "cache.trie.journal", Usage: "Disk journal directory for trie cache to survive node restarts", - Value: eth.DefaultConfig.TrieCleanCacheJournal, + Value: ethconfig.Defaults.TrieCleanCacheJournal, } CacheTrieRejournalFlag = cli.DurationFlag{ Name: "cache.trie.rejournal", Usage: "Time interval to regenerate the trie cache journal", - Value: eth.DefaultConfig.TrieCleanCacheRejournal, + Value: ethconfig.Defaults.TrieCleanCacheRejournal, } CacheGCFlag = cli.IntFlag{ Name: "cache.gc", @@ -416,17 +417,17 @@ var ( MinerGasTargetFlag = cli.Uint64Flag{ Name: "miner.gastarget", Usage: "Target gas floor for mined blocks", - Value: eth.DefaultConfig.Miner.GasFloor, + Value: ethconfig.Defaults.Miner.GasFloor, } MinerGasLimitFlag = cli.Uint64Flag{ Name: "miner.gaslimit", Usage: "Target gas ceiling for mined blocks", - Value: eth.DefaultConfig.Miner.GasCeil, + Value: ethconfig.Defaults.Miner.GasCeil, } MinerGasPriceFlag = BigFlag{ Name: "miner.gasprice", Usage: "Minimum gas price for mining a transaction", - Value: eth.DefaultConfig.Miner.GasPrice, + Value: ethconfig.Defaults.Miner.GasPrice, } MinerEtherbaseFlag = cli.StringFlag{ Name: "miner.etherbase", @@ -440,7 +441,7 @@ var ( MinerRecommitIntervalFlag = cli.DurationFlag{ Name: "miner.recommit", Usage: "Time interval to recreate the block being mined", - Value: eth.DefaultConfig.Miner.Recommit, + Value: ethconfig.Defaults.Miner.Recommit, } MinerNoVerfiyFlag = cli.BoolFlag{ Name: "miner.noverify", @@ -473,12 +474,12 @@ var ( RPCGlobalGasCapFlag = cli.Uint64Flag{ Name: "rpc.gascap", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", - Value: eth.DefaultConfig.RPCGasCap, + Value: ethconfig.Defaults.RPCGasCap, } RPCGlobalTxFeeCapFlag = cli.Float64Flag{ Name: "rpc.txfeecap", Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)", - Value: eth.DefaultConfig.RPCTxFeeCap, + Value: ethconfig.Defaults.RPCTxFeeCap, } // Logging and debug settings EthStatsURLFlag = cli.StringFlag{ @@ -650,17 +651,17 @@ var ( GpoBlocksFlag = cli.IntFlag{ Name: "gpo.blocks", Usage: "Number of recent blocks to check for gas prices", - Value: eth.DefaultConfig.GPO.Blocks, + Value: ethconfig.Defaults.GPO.Blocks, } GpoPercentileFlag = cli.IntFlag{ Name: "gpo.percentile", Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices", - Value: eth.DefaultConfig.GPO.Percentile, + Value: ethconfig.Defaults.GPO.Percentile, } GpoMaxGasPriceFlag = cli.Int64Flag{ Name: "gpo.maxprice", Usage: "Maximum gas price will be recommended by gpo", - Value: eth.DefaultConfig.GPO.MaxPrice.Int64(), + Value: ethconfig.Defaults.GPO.MaxPrice.Int64(), } WhisperEnabledFlag = cli.BoolFlag{ Name: "shh", @@ -1028,7 +1029,7 @@ func setIPC(ctx *cli.Context, cfg *node.Config) { } // setLes configures the les server and ultra light client settings from the command line flags. -func setLes(ctx *cli.Context, cfg *eth.Config) { +func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(LegacyLightServFlag.Name) { cfg.LightServ = ctx.GlobalInt(LegacyLightServFlag.Name) log.Warn("The flag --lightserv is deprecated and will be removed in the future, please use --light.serve") @@ -1056,8 +1057,8 @@ func setLes(ctx *cli.Context, cfg *eth.Config) { cfg.UltraLightFraction = ctx.GlobalInt(UltraLightFractionFlag.Name) } if cfg.UltraLightFraction <= 0 && cfg.UltraLightFraction > 100 { - log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", eth.DefaultConfig.UltraLightFraction) - cfg.UltraLightFraction = eth.DefaultConfig.UltraLightFraction + log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", ethconfig.Defaults.UltraLightFraction) + cfg.UltraLightFraction = ethconfig.Defaults.UltraLightFraction } if ctx.GlobalIsSet(UltraLightOnlyAnnounceFlag.Name) { cfg.UltraLightOnlyAnnounce = ctx.GlobalBool(UltraLightOnlyAnnounceFlag.Name) @@ -1108,7 +1109,7 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error // setEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. -func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { +func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *ethconfig.Config) { // Extract the current etherbase, new flag overriding legacy one var etherbase string if ctx.GlobalIsSet(LegacyMinerEtherbaseFlag.Name) { @@ -1307,8 +1308,8 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { // If we are running the light client, apply another group // settings for gas oracle. if light { - cfg.Blocks = eth.DefaultLightGPOConfig.Blocks - cfg.Percentile = eth.DefaultLightGPOConfig.Percentile + cfg.Blocks = ethconfig.LightClientGPO.Blocks + cfg.Percentile = ethconfig.LightClientGPO.Percentile } if ctx.GlobalIsSet(LegacyGpoBlocksFlag.Name) { cfg.Blocks = ctx.GlobalInt(LegacyGpoBlocksFlag.Name) @@ -1372,7 +1373,7 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { } } -func setEthash(ctx *cli.Context, cfg *eth.Config) { +func setEthash(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(EthashCacheDirFlag.Name) { cfg.Ethash.CacheDir = ctx.GlobalString(EthashCacheDirFlag.Name) } @@ -1435,7 +1436,7 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { } } -func setWhitelist(ctx *cli.Context, cfg *eth.Config) { +func setWhitelist(ctx *cli.Context, cfg *ethconfig.Config) { whitelist := ctx.GlobalString(WhitelistFlag.Name) if whitelist == "" { return @@ -1510,7 +1511,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { } // SetEthConfig applies eth-related command line flags to the config. -func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { +func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") @@ -1709,7 +1710,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // SetDNSDiscoveryDefaults configures DNS discovery with the given URL if // no URLs are set. -func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { +func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { if cfg.EthDiscoveryURLs != nil { return // already set through flags/config } @@ -1728,7 +1729,7 @@ func SetDNSDiscoveryDefaults(cfg *eth.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *eth.Config) ethapi.Backend { +func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend { if cfg.SyncMode == downloader.LightSync { backend, err := les.New(stack, cfg) if err != nil { @@ -1865,14 +1866,14 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B engine = ethash.NewFaker() if !ctx.GlobalBool(FakePoWFlag.Name) { engine = ethash.New(ethash.Config{ - CacheDir: stack.ResolvePath(eth.DefaultConfig.Ethash.CacheDir), - CachesInMem: eth.DefaultConfig.Ethash.CachesInMem, - CachesOnDisk: eth.DefaultConfig.Ethash.CachesOnDisk, - CachesLockMmap: eth.DefaultConfig.Ethash.CachesLockMmap, - DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir), - DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem, - DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk, - DatasetsLockMmap: eth.DefaultConfig.Ethash.DatasetsLockMmap, + CacheDir: stack.ResolvePath(ethconfig.Defaults.Ethash.CacheDir), + CachesInMem: ethconfig.Defaults.Ethash.CachesInMem, + CachesOnDisk: ethconfig.Defaults.Ethash.CachesOnDisk, + CachesLockMmap: ethconfig.Defaults.Ethash.CachesLockMmap, + DatasetDir: stack.ResolvePath(ethconfig.Defaults.Ethash.DatasetDir), + DatasetsInMem: ethconfig.Defaults.Ethash.DatasetsInMem, + DatasetsOnDisk: ethconfig.Defaults.Ethash.DatasetsOnDisk, + DatasetsLockMmap: ethconfig.Defaults.Ethash.DatasetsLockMmap, }, nil, false) } } @@ -1880,12 +1881,12 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } cache := &core.CacheConfig{ - TrieCleanLimit: eth.DefaultConfig.TrieCleanCache, + TrieCleanLimit: ethconfig.Defaults.TrieCleanCache, TrieCleanNoPrefetch: ctx.GlobalBool(CacheNoPrefetchFlag.Name), - TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache, + TrieDirtyLimit: ethconfig.Defaults.TrieDirtyCache, TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive", - TrieTimeLimit: eth.DefaultConfig.TrieTimeout, - SnapshotLimit: eth.DefaultConfig.SnapshotCache, + TrieTimeLimit: ethconfig.Defaults.TrieTimeout, + SnapshotLimit: ethconfig.Defaults.SnapshotCache, Preimages: ctx.GlobalBool(CachePreimagesFlag.Name), } if cache.TrieDirtyDisabled && !cache.Preimages { diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index 1376d47c05..ff45ab9094 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" ) @@ -55,12 +55,12 @@ var ( LegacyMinerGasTargetFlag = cli.Uint64Flag{ Name: "targetgaslimit", Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)", - Value: eth.DefaultConfig.Miner.GasFloor, + Value: ethconfig.Defaults.Miner.GasFloor, } LegacyMinerGasPriceFlag = BigFlag{ Name: "gasprice", Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)", - Value: eth.DefaultConfig.Miner.GasPrice, + Value: ethconfig.Defaults.Miner.GasPrice, } LegacyMinerEtherbaseFlag = cli.StringFlag{ Name: "etherbase", @@ -76,12 +76,12 @@ var ( LegacyLightServFlag = cli.IntFlag{ Name: "lightserv", Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)", - Value: eth.DefaultConfig.LightServ, + Value: ethconfig.Defaults.LightServ, } LegacyLightPeersFlag = cli.IntFlag{ Name: "lightpeers", Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated, use --light.maxpeers)", - Value: eth.DefaultConfig.LightPeers, + Value: ethconfig.Defaults.LightPeers, } // (Deprecated April 2020) @@ -143,12 +143,12 @@ var ( LegacyGpoBlocksFlag = cli.IntFlag{ Name: "gpoblocks", Usage: "Number of recent blocks to check for gas prices (deprecated, use --gpo.blocks)", - Value: eth.DefaultConfig.GPO.Blocks, + Value: ethconfig.Defaults.GPO.Blocks, } LegacyGpoPercentileFlag = cli.IntFlag{ Name: "gpopercentile", Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices (deprecated, use --gpo.percentile)", - Value: eth.DefaultConfig.GPO.Percentile, + Value: ethconfig.Defaults.GPO.Percentile, } LegacyBootnodesV4Flag = cli.StringFlag{ Name: "bootnodesv4", diff --git a/console/console_test.go b/console/console_test.go index 68c03d108d..f6ab781410 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/jsre" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" @@ -85,7 +86,7 @@ type tester struct { // newTester creates a test environment based on which the console can operate. // Please ensure you call Close() on the returned tester to avoid leaks. -func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { +func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { // Create a temporary storage for the node keys and initialize it workspace, err := ioutil.TempDir("", "console-tester-") if err != nil { @@ -97,7 +98,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { if err != nil { t.Fatalf("failed to create node: %v", err) } - ethConf := ð.Config{ + ethConf := ðconfig.Config{ Genesis: core.DeveloperGenesisBlock(15, common.Address{}), Miner: miner.Config{ Etherbase: common.HexToAddress(testAddress), diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go new file mode 100644 index 0000000000..856746a1c0 --- /dev/null +++ b/core/bloom_indexer.go @@ -0,0 +1,92 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/bitutil" + "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" +) + +const ( + // bloomThrottling is the time to wait between processing two consecutive index + // sections. It's useful during chain upgrades to prevent disk overload. + bloomThrottling = 100 * time.Millisecond +) + +// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index +// for the Ethereum header bloom filters, permitting blazing fast filtering. +type BloomIndexer struct { + size uint64 // section size to generate bloombits for + db ethdb.Database // database instance to write index data and metadata into + gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index + section uint64 // Section is the section number being processed currently + head common.Hash // Head is the hash of the last header processed +} + +// NewBloomIndexer returns a chain indexer that generates bloom bits data for the +// canonical chain for fast logs filtering. +func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *ChainIndexer { + backend := &BloomIndexer{ + db: db, + size: size, + } + table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) + + return NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits") +} + +// Reset implements core.ChainIndexerBackend, starting a new bloombits index +// section. +func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { + gen, err := bloombits.NewGenerator(uint(b.size)) + b.gen, b.section, b.head = gen, section, common.Hash{} + return err +} + +// Process implements core.ChainIndexerBackend, adding a new header's bloom into +// the index. +func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error { + b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom) + b.head = header.Hash() + return nil +} + +// Commit implements core.ChainIndexerBackend, finalizing the bloom section and +// writing it out into the database. +func (b *BloomIndexer) Commit() error { + batch := b.db.NewBatch() + for i := 0; i < types.BloomBitLength; i++ { + bits, err := b.gen.Bitset(uint(i)) + if err != nil { + return err + } + rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) + } + return batch.Write() +} + +// Prune returns an empty error since we don't support pruning here. +func (b *BloomIndexer) Prune(threshold uint64) error { + return nil +} diff --git a/eth/backend.go b/eth/backend.go index a6390facb3..51ad0ccf3f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -31,13 +31,13 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" - "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/protocols/eth" @@ -55,9 +55,13 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +// Config contains the configuration options of the ETH protocol. +// Deprecated: use ethconfig.Config instead. +type Config = ethconfig.Config + // Ethereum implements the Ethereum full node service. type Ethereum struct { - config *Config + config *ethconfig.Config // Handlers txPool *core.TxPool @@ -93,7 +97,7 @@ type Ethereum struct { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(stack *node.Node, config *Config) (*Ethereum, error) { +func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -102,8 +106,8 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) } if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { - log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", DefaultConfig.Miner.GasPrice) - config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice) + log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) + config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice) } if config.NoPruning && config.TrieDirtyCache > 0 { if config.SnapshotCache > 0 { @@ -132,13 +136,13 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { chainDb: chainDb, eventMux: stack.EventMux(), accountManager: stack.AccountManager(), - engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), p2pServer: stack.Server(), } @@ -269,39 +273,6 @@ func makeExtraData(extra []byte) []byte { return extra } -// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { - // If proof-of-authority is requested, set it up - if chainConfig.Clique != nil { - return clique.New(chainConfig.Clique, db) - } - // Otherwise assume proof-of-work - switch config.PowMode { - case ethash.ModeFake: - log.Warn("Ethash used in fake mode") - return ethash.NewFaker() - case ethash.ModeTest: - log.Warn("Ethash used in test mode") - return ethash.NewTester(nil, noverify) - case ethash.ModeShared: - log.Warn("Ethash used in shared mode") - return ethash.NewShared() - default: - engine := ethash.New(ethash.Config{ - CacheDir: stack.ResolvePath(config.CacheDir), - CachesInMem: config.CachesInMem, - CachesOnDisk: config.CachesOnDisk, - CachesLockMmap: config.CachesLockMmap, - DatasetDir: config.DatasetDir, - DatasetsInMem: config.DatasetsInMem, - DatasetsOnDisk: config.DatasetsOnDisk, - DatasetsLockMmap: config.DatasetsLockMmap, - }, notify, noverify) - engine.SetThreads(-1) // Disable CPU mining - return engine - } -} - // APIs return the collection of RPC services the ethereum package offers. // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { diff --git a/eth/bloombits.go b/eth/bloombits.go index bd34bd7b69..0cb7050d23 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -17,16 +17,10 @@ package eth import ( - "context" "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" ) const ( @@ -78,66 +72,3 @@ func (eth *Ethereum) startBloomHandlers(sectionSize uint64) { }() } } - -const ( - // bloomThrottling is the time to wait between processing two consecutive index - // sections. It's useful during chain upgrades to prevent disk overload. - bloomThrottling = 100 * time.Millisecond -) - -// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index -// for the Ethereum header bloom filters, permitting blazing fast filtering. -type BloomIndexer struct { - size uint64 // section size to generate bloombits for - db ethdb.Database // database instance to write index data and metadata into - gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index - section uint64 // Section is the section number being processed currently - head common.Hash // Head is the hash of the last header processed -} - -// NewBloomIndexer returns a chain indexer that generates bloom bits data for the -// canonical chain for fast logs filtering. -func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *core.ChainIndexer { - backend := &BloomIndexer{ - db: db, - size: size, - } - table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) - - return core.NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits") -} - -// Reset implements core.ChainIndexerBackend, starting a new bloombits index -// section. -func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { - gen, err := bloombits.NewGenerator(uint(b.size)) - b.gen, b.section, b.head = gen, section, common.Hash{} - return err -} - -// Process implements core.ChainIndexerBackend, adding a new header's bloom into -// the index. -func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error { - b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom) - b.head = header.Hash() - return nil -} - -// Commit implements core.ChainIndexerBackend, finalizing the bloom section and -// writing it out into the database. -func (b *BloomIndexer) Commit() error { - batch := b.db.NewBatch() - for i := 0; i < types.BloomBitLength; i++ { - bits, err := b.gen.Bitset(uint(i)) - if err != nil { - return err - } - rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) - } - return batch.Write() -} - -// Prune returns an empty error since we don't support pruning here. -func (b *BloomIndexer) Prune(threshold uint64) error { - return nil -} diff --git a/eth/config.go b/eth/ethconfig/config.go similarity index 72% rename from eth/config.go rename to eth/ethconfig/config.go index 446467d364..9147a602d5 100644 --- a/eth/config.go +++ b/eth/ethconfig/config.go @@ -14,7 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package eth +// Package ethconfig contains the configuration of the ETH and LES protocols. +package ethconfig import ( "math/big" @@ -25,30 +26,35 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" ) -// DefaultFullGPOConfig contains default gasprice oracle settings for full node. -var DefaultFullGPOConfig = gasprice.Config{ +// FullNodeGPO contains default gasprice oracle settings for full node. +var FullNodeGPO = gasprice.Config{ Blocks: 20, Percentile: 60, MaxPrice: gasprice.DefaultMaxPrice, } -// DefaultLightGPOConfig contains default gasprice oracle settings for light client. -var DefaultLightGPOConfig = gasprice.Config{ +// LightClientGPO contains default gasprice oracle settings for light client. +var LightClientGPO = gasprice.Config{ Blocks: 2, Percentile: 60, MaxPrice: gasprice.DefaultMaxPrice, } -// DefaultConfig contains default settings for use on the Ethereum main net. -var DefaultConfig = Config{ +// Defaults contains default settings for use on the Ethereum main net. +var Defaults = Config{ SyncMode: downloader.FastSync, Ethash: ethash.Config{ CacheDir: "ethash", @@ -77,7 +83,7 @@ var DefaultConfig = Config{ }, TxPool: core.DefaultTxPoolConfig, RPCGasCap: 25000000, - GPO: DefaultFullGPOConfig, + GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether } @@ -89,21 +95,22 @@ func init() { } } if runtime.GOOS == "darwin" { - DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash") + Defaults.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash") } else if runtime.GOOS == "windows" { localappdata := os.Getenv("LOCALAPPDATA") if localappdata != "" { - DefaultConfig.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash") + Defaults.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash") } else { - DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash") + Defaults.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash") } } else { - DefaultConfig.Ethash.DatasetDir = filepath.Join(home, ".ethash") + Defaults.Ethash.DatasetDir = filepath.Join(home, ".ethash") } } //go:generate gencodec -type Config -formats toml -out gen_config.go +// Config contains configuration options for of the ETH and LES protocols. type Config struct { // The genesis block, which is inserted if the database is empty. // If nil, the Ethereum main net block is used. @@ -190,3 +197,36 @@ type Config struct { // CheckpointOracle is the configuration for checkpoint oracle. CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` } + +// CreateConsensusEngine creates a consensus engine for the given chain configuration. +func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { + // If proof-of-authority is requested, set it up + if chainConfig.Clique != nil { + return clique.New(chainConfig.Clique, db) + } + // Otherwise assume proof-of-work + switch config.PowMode { + case ethash.ModeFake: + log.Warn("Ethash used in fake mode") + return ethash.NewFaker() + case ethash.ModeTest: + log.Warn("Ethash used in test mode") + return ethash.NewTester(nil, noverify) + case ethash.ModeShared: + log.Warn("Ethash used in shared mode") + return ethash.NewShared() + default: + engine := ethash.New(ethash.Config{ + CacheDir: stack.ResolvePath(config.CacheDir), + CachesInMem: config.CachesInMem, + CachesOnDisk: config.CachesOnDisk, + CachesLockMmap: config.CachesLockMmap, + DatasetDir: config.DatasetDir, + DatasetsInMem: config.DatasetsInMem, + DatasetsOnDisk: config.DatasetsOnDisk, + DatasetsLockMmap: config.DatasetsLockMmap, + }, notify, noverify) + engine.SetThreads(-1) // Disable CPU mining + return engine + } +} diff --git a/eth/gen_config.go b/eth/ethconfig/gen_config.go similarity index 99% rename from eth/gen_config.go rename to eth/ethconfig/gen_config.go index e68b29ce5e..5814b81b09 100644 --- a/eth/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package eth +package ethconfig import ( "time" diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index d700022e8f..8b175ee066 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -195,7 +196,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { t.Fatalf("can't create new node: %v", err) } // Create Ethereum Service - config := ð.Config{Genesis: genesis} + config := ðconfig.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake ethservice, err := eth.New(n, config) if err != nil { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index a88c9b30b1..2f3b230329 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -191,7 +192,7 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { func createGQLService(t *testing.T, stack *node.Node) { // create backend - ethConf := ð.Config{ + ethConf := ðconfig.Config{ Genesis: &core.Genesis{ Config: params.AllEthashProtocolChanges, GasLimit: 11500000, diff --git a/les/api_test.go b/les/api_test.go index 2895264f67..f7017c5d98 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -492,14 +493,14 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] } func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - config := eth.DefaultConfig + config := ethconfig.Defaults config.SyncMode = downloader.LightSync config.Ethash.PowMode = ethash.ModeFake return New(stack, &config) } func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - config := eth.DefaultConfig + config := ethconfig.Defaults config.SyncMode = downloader.FullSync config.LightServ = testServerCapacity config.LightPeers = testMaxClients diff --git a/les/client.go b/les/client.go index d8cb6e385a..1b26e9a9b5 100644 --- a/les/client.go +++ b/les/client.go @@ -30,8 +30,8 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" @@ -76,7 +76,7 @@ type LightEthereum struct { } // New creates an instance of the light client. -func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { +func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") if err != nil { return nil, err @@ -105,9 +105,9 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) { eventMux: stack.EventMux(), reqDist: newRequestDistributor(peers, &mclock.System{}), accountManager: stack.AccountManager(), - engine: eth.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), + bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, diff --git a/les/commons.go b/les/commons.go index 73334497ad..a2fce1dc97 100644 --- a/les/commons.go +++ b/les/commons.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/checkpointoracle" @@ -48,7 +48,7 @@ type chainReader interface { // lesCommons contains fields needed by both server and client. type lesCommons struct { genesis common.Hash - config *eth.Config + config *ethconfig.Config chainConfig *params.ChainConfig iConfig *light.IndexerConfig chainDb ethdb.Database @@ -138,7 +138,7 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint { } // setupOracle sets up the checkpoint oracle contract client. -func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *eth.Config) *checkpointoracle.CheckpointOracle { +func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *ethconfig.Config) *checkpointoracle.CheckpointOracle { config := ethconfig.CheckpointOracle if config == nil { // Try loading default config. diff --git a/les/costtracker.go b/les/costtracker.go index 0558779bc5..43e32a5b2d 100644 --- a/les/costtracker.go +++ b/les/costtracker.go @@ -24,7 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/log" @@ -137,7 +137,7 @@ type costTracker struct { // newCostTracker creates a cost tracker and loads the cost factor statistics from the database. // It also returns the minimum capacity that can be assigned to any peer. -func newCostTracker(db ethdb.Database, config *eth.Config) (*costTracker, uint64) { +func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, uint64) { utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100 ct := &costTracker{ db: db, diff --git a/les/server.go b/les/server.go index 6b12b6f8f3..44495eb311 100644 --- a/les/server.go +++ b/les/server.go @@ -22,7 +22,9 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/light" @@ -50,6 +52,15 @@ func init() { priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority } +type ethBackend interface { + ArchiveMode() bool + BlockChain() *core.BlockChain + BloomIndexer() *core.ChainIndexer + ChainDb() ethdb.Database + Synced() bool + TxPool() *core.TxPool +} + type LesServer struct { lesCommons @@ -73,7 +84,7 @@ type LesServer struct { p2pSrv *p2p.Server } -func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { +func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) // Calculate the number of threads used to service the light client // requests based on the user-specified value. diff --git a/les/test_helper.go b/les/test_helper.go index 04482ba68e..e3f0616a88 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les/checkpointoracle" @@ -163,7 +163,7 @@ func prepare(n int, backend *backends.SimulatedBackend) { func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig, disablePruning bool) []*core.ChainIndexer { var indexers [3]*core.ChainIndexer indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms, disablePruning) - indexers[1] = eth.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms) + indexers[1] = core.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms) indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize, disablePruning) // make bloomTrieIndexer as a child indexer of bloom indexer. indexers[1].AddChildIndexer(indexers[2]) @@ -204,7 +204,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index client := &LightEthereum{ lesCommons: lesCommons{ genesis: genesis.Hash(), - config: ð.Config{LightPeers: 100, NetworkId: NetworkId}, + config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId}, chainConfig: params.AllEthashProtocolChanges, iConfig: light.TestClientIndexerConfig, chainDb: db, @@ -269,7 +269,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da server := &LesServer{ lesCommons: lesCommons{ genesis: genesis.Hash(), - config: ð.Config{LightPeers: 100, NetworkId: NetworkId}, + config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId}, chainConfig: params.AllEthashProtocolChanges, iConfig: light.TestServerIndexerConfig, chainDb: db, diff --git a/miner/stress_clique.go b/miner/stress_clique.go index a0fd596098..c585e0b1f6 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -185,7 +185,7 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { return nil, nil, err } // Create and register the backend - ethBackend, err := eth.New(stack, ð.Config{ + ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 1713af9d23..0b838d48b9 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -162,7 +162,7 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { if err != nil { return nil, nil, err } - ethBackend, err := eth.New(stack, ð.Config{ + ethBackend, err := eth.New(stack, ðconfig.Config{ Genesis: genesis, NetworkId: genesis.Config.ChainID.Uint64(), SyncMode: downloader.FullSync, diff --git a/mobile/geth.go b/mobile/geth.go index b561e33675..704d432e04 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -25,8 +25,8 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/internal/debug" @@ -182,7 +182,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { } // Register the Ethereum protocol if requested if config.EthereumEnabled { - ethConf := eth.DefaultConfig + ethConf := ethconfig.Defaults ethConf.Genesis = genesis ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) From fba5a63afe0993da70896c763b4fdfa953e066ff Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 5 Feb 2021 13:51:53 +0100 Subject: [PATCH 304/948] internal/ethapi: fix typo in comment (#22271) --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ed109cd201..d3c007b9bf 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -386,7 +386,7 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs } // SignTransaction will create a transaction from the given arguments and -// tries to sign it with the key associated with args.To. If the given passwd isn't +// tries to sign it with the key associated with args.From. If the given passwd isn't // able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast // to other nodes func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) { From 7ed860d4f17884ac9f1f6b927244a70e2e92eb94 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 5 Feb 2021 14:15:22 +0100 Subject: [PATCH 305/948] eth: don't wait for snap registration if we're not running snap (#22272) Prevents a situation where we (not running snap) connects with a peer running snap, and get stalled waiting for snap registration to succeed (which will never happen), which cause a waitgroup wait to halt shutdown --- eth/peerset.go | 4 ++-- p2p/peer.go | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/eth/peerset.go b/eth/peerset.go index f0657e140b..1e864a8e46 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -73,7 +73,7 @@ func newPeerSet() *peerSet { func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { // Reject the peer if it advertises `snap` without `eth` as `snap` is only a // satellite protocol meaningful with the chain selection of `eth` - if !peer.SupportsCap(eth.ProtocolName, eth.ProtocolVersions) { + if !peer.RunningCap(eth.ProtocolName, eth.ProtocolVersions) { return errSnapWithoutEth } // Ensure nobody can double connect @@ -101,7 +101,7 @@ func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { // by the peerset. func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) { // If the peer does not support a compatible `snap`, don't wait - if !peer.SupportsCap(snap.ProtocolName, snap.ProtocolVersions) { + if !peer.RunningCap(snap.ProtocolName, snap.ProtocolVersions) { return nil, nil } // Ensure nobody can double connect diff --git a/p2p/peer.go b/p2p/peer.go index 08881e2583..8ebc858392 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -158,15 +158,14 @@ func (p *Peer) Caps() []Cap { return p.rw.caps } -// SupportsCap returns true if the peer supports any of the enumerated versions -// of a specific protocol. -func (p *Peer) SupportsCap(protocol string, versions []uint) bool { - for _, cap := range p.rw.caps { - if cap.Name == protocol { - for _, ver := range versions { - if cap.Version == ver { - return true - } +// RunningCap returns true if the peer is actively connected using any of the +// enumerated versions of a specific protocol, meaning that at least one of the +// versions is supported by both this node and the peer p. +func (p *Peer) RunningCap(protocol string, versions []uint) bool { + if proto, ok := p.running[protocol]; ok { + for _, ver := range versions { + if proto.Version == ver { + return true } } } From e74bd587f730fcdb5a9b625390da8aa85a2cbbc8 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 5 Feb 2021 19:44:34 +0100 Subject: [PATCH 306/948] consensus: remove seal verification from the consensus engine interface (#22274) --- consensus/clique/clique.go | 6 ------ consensus/consensus.go | 4 ---- consensus/ethash/algorithm_test.go | 2 +- consensus/ethash/consensus.go | 8 +------- consensus/ethash/ethash_test.go | 4 ++-- 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 61d4358b4e..c62e180faa 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -434,12 +434,6 @@ func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) e return nil } -// VerifySeal implements consensus.Engine, checking whether the signature contained -// in the header satisfies the consensus protocol requirements. -func (c *Clique) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { - return c.verifySeal(chain, header, nil) -} - // verifySeal checks whether the signature contained in the header satisfies the // consensus protocol requirements. The method accepts an optional list of parent // headers that aren't yet part of the local blockchain to generate the snapshots diff --git a/consensus/consensus.go b/consensus/consensus.go index f7a4d0ff0b..2a5aac945d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -77,10 +77,6 @@ type Engine interface { // rules of a given engine. VerifyUncles(chain ChainReader, block *types.Block) error - // VerifySeal checks whether the crypto seal on a header is valid according to - // the consensus rules of the given engine. - VerifySeal(chain ChainHeaderReader, header *types.Header) error - // Prepare initializes the consensus fields of a block header according to the // rules of a particular engine. The changes are executed inline. Prepare(chain ChainHeaderReader, header *types.Header) error diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go index 51fb6b124d..663687b81c 100644 --- a/consensus/ethash/algorithm_test.go +++ b/consensus/ethash/algorithm_test.go @@ -731,7 +731,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { defer pend.Done() ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false) defer ethash.Close() - if err := ethash.VerifySeal(nil, block.Header()); err != nil { + if err := ethash.verifySeal(nil, block.Header(), false); err != nil { t.Errorf("proc %d: block verification failed: %v", idx, err) } }(i) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 1b801b253f..011a5688ef 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -288,7 +288,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa } // Verify the engine specific seal securing the block if seal { - if err := ethash.VerifySeal(chain, header); err != nil { + if err := ethash.verifySeal(chain, header, false); err != nil { return err } } @@ -488,12 +488,6 @@ var FrontierDifficultyCalulator = calcDifficultyFrontier var HomesteadDifficultyCalulator = calcDifficultyHomestead var DynamicDifficultyCalculator = makeDifficultyCalculator -// VerifySeal implements consensus.Engine, checking whether the given block satisfies -// the PoW difficulty requirements. -func (ethash *Ethash) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { - return ethash.verifySeal(chain, header, false) -} - // verifySeal checks whether a block satisfies the PoW difficulty requirements, // either using the usual ethash cache for it, or alternatively using a full DAG // to make remote mining fast. diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index adbf1ccfeb..2639707eb2 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -46,7 +46,7 @@ func TestTestMode(t *testing.T) { case block := <-results: header.Nonce = types.EncodeNonce(block.Nonce()) header.MixDigest = block.MixDigest() - if err := ethash.VerifySeal(nil, header); err != nil { + if err := ethash.verifySeal(nil, header, false); err != nil { t.Fatalf("unexpected verification error: %v", err) } case <-time.NewTimer(4 * time.Second).C: @@ -86,7 +86,7 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) { block = 0 } header := &types.Header{Number: big.NewInt(block), Difficulty: big.NewInt(100)} - e.VerifySeal(nil, header) + e.verifySeal(nil, header, false) } } From 994cdc69c8ab79b97f490c64721f2908df2070d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 7 Feb 2021 20:13:59 +0200 Subject: [PATCH 307/948] cmd/utils: enable snapshots by default --- cmd/utils/flags.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1ea26be457..87295fb2f4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -208,9 +208,9 @@ var ( Usage: `Blockchain garbage collection mode ("full", "archive")`, Value: "full", } - SnapshotFlag = cli.BoolFlag{ + SnapshotFlag = cli.BoolTFlag{ Name: "snapshot", - Usage: `Enables snapshot-database mode -- experimental work in progress feature`, + Usage: `Enables snapshot-database mode (default = enable)`, } TxLookupLimitFlag = cli.Int64Flag{ Name: "txlookuplimit", @@ -1579,7 +1579,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) { cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100 } - if !ctx.GlobalIsSet(SnapshotFlag.Name) { + if !ctx.GlobalBool(SnapshotFlag.Name) { // If snap-sync is requested, this flag is also required if cfg.SyncMode == downloader.SnapSync { log.Info("Snap sync requested, enabling --snapshot") @@ -1893,7 +1893,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B cache.Preimages = true log.Info("Enabling recording of key preimages since archive mode is used") } - if !ctx.GlobalIsSet(SnapshotFlag.Name) { + if !ctx.GlobalBool(SnapshotFlag.Name) { cache.SnapshotLimit = 0 // Disabled } if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) { From 477fd420b393e3e5b962ac8c5af90a1add54d9d7 Mon Sep 17 00:00:00 2001 From: isdyaufh8o7cq Date: Mon, 8 Feb 2021 11:36:49 +0100 Subject: [PATCH 308/948] metrics: fix cast omission in cpu_syscall.go (#22262) fixes an regression which caused build failure on certain platforms --- metrics/cpu_syscall.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/cpu_syscall.go b/metrics/cpu_syscall.go index 106637af5a..e245453e82 100644 --- a/metrics/cpu_syscall.go +++ b/metrics/cpu_syscall.go @@ -31,5 +31,5 @@ func getProcessCPUTime() int64 { log.Warn("Failed to retrieve CPU time", "err", err) return 0 } - return (usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert + return int64(usage.Utime.Sec+usage.Stime.Sec)*100 + int64(usage.Utime.Usec+usage.Stime.Usec)/10000 //nolint:unconvert } From d86906f1e6040e4e57c164fc5dfab0f97329b229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Feb 2021 13:03:06 +0200 Subject: [PATCH 309/948] params: just to make snapshots a bit more official --- params/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/params/version.go b/params/version.go index b6d6bd3f1d..3447f0f283 100644 --- a/params/version.go +++ b/params/version.go @@ -22,8 +22,8 @@ import ( const ( VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 26 // Patch version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "unstable" // Version metadata to append to the version string ) From f566dd305e7db3a629a783ce89697f49c4ba4a75 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 8 Feb 2021 19:16:30 +0800 Subject: [PATCH 310/948] all: bloom-filter based pruning mechanism (#21724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cmd, core, tests: initial state pruner core: fix db inspector cmd/geth: add verify-state cmd/geth: add verification tool core/rawdb: implement flatdb cmd, core: fix rebase core/state: use new contract code layout core/state/pruner: avoid deleting genesis state cmd/geth: add helper function core, cmd: fix extract genesis core: minor fixes contracts: remove useless core/state/snapshot: plugin stacktrie core: polish core/state/snapshot: iterate storage concurrently core/state/snapshot: fix iteration core: add comments core/state/snapshot: polish code core/state: polish core/state/snapshot: rebase core/rawdb: add comments core/rawdb: fix tests core/rawdb: improve tests core/state/snapshot: fix concurrent iteration core/state: run pruning during the recovery core, trie: implement martin's idea core, eth: delete flatdb and polish pruner trie: fix import core/state/pruner: add log core/state/pruner: fix issues core/state/pruner: don't read back core/state/pruner: fix contract code write core/state/pruner: check root node presence cmd, core: polish log core/state: use HEAD-127 as the target core/state/snapshot: improve tests cmd/geth: fix verification tool cmd/geth: use HEAD as the verification default target all: replace the bloomfilter with martin's fork cmd, core: polish code core, cmd: forcibly delete state root core/state/pruner: add hash64 core/state/pruner: fix blacklist core/state: remove blacklist cmd, core: delete trie clean cache before pruning cmd, core: fix lint cmd, core: fix rebase core/state: fix the special case for clique networks core/state/snapshot: remove useless code core/state/pruner: capping the snapshot after pruning cmd, core, eth: fixes core/rawdb: update db inspector cmd/geth: polish code core/state/pruner: fsync bloom filter cmd, core: print warning log core/state/pruner: adjust the parameters for bloom filter cmd, core: create the bloom filter by size core: polish core/state/pruner: sanitize invalid bloomfilter size cmd: address comments cmd/geth: address comments cmd/geth: address comment core/state/pruner: address comments core/state/pruner: rename homedir to datadir cmd, core: address comments core/state/pruner: address comment core/state: address comments core, cmd, tests: address comments core: address comments core/state/pruner: release the iterator after each commit core/state/pruner: improve pruner cmd, core: adjust bloom paramters core/state/pruner: fix lint core/state/pruner: fix tests core: fix rebase core/state/pruner: remove atomic rename core/state/pruner: address comments all: run go mod tidy core/state/pruner: avoid false-positive for the middle state roots core/state/pruner: add checks for middle roots cmd/geth: replace crit with error * core/state/pruner: fix lint * core: drop legacy bloom filter * core/state/snapshot: improve pruner * core/state/snapshot: polish concurrent logs to report ETA vs. hashes * core/state/pruner: add progress report for pruning and compaction too * core: fix snapshot test API * core/state: fix some pruning logs * core/state/pruner: support recovering from bloom flush fail Co-authored-by: Péter Szilágyi --- cmd/geth/main.go | 3 + cmd/geth/snapshot.go | 437 ++++++++++++++++++++++ cmd/geth/usage.go | 1 + cmd/utils/flags.go | 5 + core/blockchain.go | 2 +- core/blockchain_snapshot_test.go | 3 +- core/genesis.go | 4 - core/rawdb/database.go | 19 +- core/rawdb/schema.go | 10 +- core/state/pruner/bloom.go | 132 +++++++ core/state/pruner/pruner.go | 537 +++++++++++++++++++++++++++ core/state/snapshot/conversion.go | 284 +++++++++----- core/state/snapshot/snapshot.go | 78 +++- core/state/snapshot/snapshot_test.go | 67 ++++ eth/backend.go | 4 + go.mod | 16 +- go.sum | 11 +- tests/block_test_util.go | 3 +- tests/state_test_util.go | 2 +- trie/stacktrie.go | 21 +- 20 files changed, 1491 insertions(+), 148 deletions(-) create mode 100644 cmd/geth/snapshot.go create mode 100644 core/state/pruner/bloom.go create mode 100644 core/state/pruner/pruner.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 11829cbad9..0236ffb7d3 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -107,6 +107,7 @@ var ( utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, utils.WhitelistFlag, + utils.BloomFilterSizeFlag, utils.CacheFlag, utils.CacheDatabaseFlag, utils.CacheTrieFlag, @@ -255,6 +256,8 @@ func init() { dumpConfigCommand, // See cmd/utils/flags_legacy.go utils.ShowDeprecated, + // See snapshot.go + snapshotCommand, } sort.Sort(cli.CommandsByName(app.Commands)) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go new file mode 100644 index 0000000000..6805a42585 --- /dev/null +++ b/cmd/geth/snapshot.go @@ -0,0 +1,437 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bytes" + "errors" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/pruner" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + cli "gopkg.in/urfave/cli.v1" +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256(nil) +) + +var ( + snapshotCommand = cli.Command{ + Name: "snapshot", + Usage: "A set of commands based on the snapshot", + Category: "MISCELLANEOUS COMMANDS", + Description: "", + Subcommands: []cli.Command{ + { + Name: "prune-state", + Usage: "Prune stale ethereum state data based on the snapshot", + ArgsUsage: "", + Action: utils.MigrateFlags(pruneState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + utils.CacheTrieJournalFlag, + utils.BloomFilterSizeFlag, + }, + Description: ` +geth snapshot prune-state +will prune historical state data with the help of the state snapshot. +All trie nodes and contract codes that do not belong to the specified +version state will be deleted from the database. After pruning, only +two version states are available: genesis and the specific one. + +The default pruning target is the HEAD-127 state. + +WARNING: It's necessary to delete the trie clean cache after the pruning. +If you specify another directory for the trie clean cache via "--cache.trie.journal" +during the use of Geth, please also specify it here for correct deletion. Otherwise +the trie clean cache with default directory will be deleted. +`, + }, + { + Name: "verify-state", + Usage: "Recalculate state hash based on the snapshot for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(verifyState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + }, + Description: ` +geth snapshot verify-state +will traverse the whole accounts and storages set based on the specified +snapshot and recalculate the root hash of state for verification. +In other words, this command does the snapshot to trie conversion. +`, + }, + { + Name: "traverse-state", + Usage: "Traverse the state with given root hash for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(traverseState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + }, + Description: ` +geth snapshot traverse-state +will traverse the whole state from the given state root and will abort if any +referenced trie node or contract code is missing. This command can be used for +state integrity verification. The default checking target is the HEAD state. + +It's also usable without snapshot enabled. +`, + }, + { + Name: "traverse-rawstate", + Usage: "Traverse the state with given root hash for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(traverseRawState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, + }, + Description: ` +geth snapshot traverse-rawstate +will traverse the whole state from the given root and will abort if any referenced +trie node or contract code is missing. This command can be used for state integrity +verification. The default checking target is the HEAD state. It's basically identical +to traverse-state, but the check granularity is smaller. + +It's also usable without snapshot enabled. +`, + }, + }, + } +) + +func pruneState(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + pruner, err := pruner.NewPruner(chaindb, chain.CurrentBlock().Header(), stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) + if err != nil { + log.Error("Failed to open snapshot tree", "error", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var targetRoot common.Hash + if ctx.NArg() == 1 { + targetRoot, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + } + if err = pruner.Prune(targetRoot); err != nil { + log.Error("Failed to prune state", "error", err) + return err + } + return nil +} + +func verifyState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, chain.CurrentBlock().Root(), false, false, false) + if err != nil { + log.Error("Failed to open snapshot tree", "error", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var root = chain.CurrentBlock().Root() + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + } + if err := snaptree.Verify(root); err != nil { + log.Error("Failed to verfiy state", "error", err) + return err + } + log.Info("Verified the state") + return nil +} + +// traverseState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. +func traverseState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + // Use the HEAD root as the default + head := chain.CurrentBlock() + if head == nil { + log.Error("Head block is missing") + return errors.New("head block is missing") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = head.Root() + log.Info("Start traversing the state", "root", root, "number", head.NumberU64()) + } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewSecure(root, triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "error", err) + return err + } + var ( + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + accIter := trie.NewIterator(t.NodeIterator(nil)) + for accIter.Next() { + accounts += 1 + var acc state.Account + if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { + log.Error("Invalid account encountered during traversal", "error", err) + return err + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "error", err) + return err + } + storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) + for storageIter.Next() { + slots += 1 + } + if storageIter.Err != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Err) + return storageIter.Err + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) + if len(code) == 0 { + log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) + return errors.New("missing code") + } + codes += 1 + } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + if accIter.Err != nil { + log.Error("Failed to traverse state trie", "root", root, "error", accIter.Err) + return accIter.Err + } + log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// traverseRawState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. It's basically identical to traverseState +// but it will check each trie node. +func traverseRawState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chain, chaindb := utils.MakeChain(ctx, stack, true) + defer chaindb.Close() + + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + // Use the HEAD root as the default + head := chain.CurrentBlock() + if head == nil { + log.Error("Head block is missing") + return errors.New("head block is missing") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "error", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = head.Root() + log.Info("Start traversing the state", "root", root, "number", head.NumberU64()) + } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewSecure(root, triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "error", err) + return err + } + var ( + nodes int + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + accIter := t.NodeIterator(nil) + for accIter.Next(true) { + nodes += 1 + node := accIter.Hash() + + if node != (common.Hash{}) { + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + blob := rawdb.ReadTrieNode(chaindb, node) + if len(blob) == 0 { + log.Error("Missing trie node(account)", "hash", node) + return errors.New("missing account") + } + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + accounts += 1 + var acc state.Account + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + log.Error("Invalid account encountered during traversal", "error", err) + return errors.New("invalid account") + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "error", err) + return errors.New("missing storage trie") + } + storageIter := storageTrie.NodeIterator(nil) + for storageIter.Next(true) { + nodes += 1 + node := storageIter.Hash() + + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + if node != (common.Hash{}) { + blob := rawdb.ReadTrieNode(chaindb, node) + if len(blob) == 0 { + log.Error("Missing trie node(storage)", "hash", node) + return errors.New("missing storage") + } + } + // Bump the counter if it's leaf node. + if storageIter.Leaf() { + slots += 1 + } + } + if storageIter.Error() != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Error()) + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) + if len(code) == 0 { + log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) + return errors.New("missing code") + } + codes += 1 + } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + } + if accIter.Error() != nil { + log.Error("Failed to traverse state trie", "root", root, "error", accIter.Error()) + return accIter.Error() + } + log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +func parseRoot(input string) (common.Hash, error) { + var h common.Hash + if err := h.UnmarshalText([]byte(input)); err != nil { + return h, err + } + return h, nil +} diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 0ed31d7da6..55e934d31b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -245,6 +245,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "MISC", Flags: []cli.Flag{ utils.SnapshotFlag, + utils.BloomFilterSizeFlag, cli.HelpFlag, }, }, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 87295fb2f4..f9c5553961 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -225,6 +225,11 @@ var ( Name: "whitelist", Usage: "Comma separated block number-to-hash mappings to enforce (=)", } + BloomFilterSizeFlag = cli.Uint64Flag{ + Name: "bloomfilter.size", + Usage: "Megabytes of memory allocated to bloom-filter for pruning", + Value: 2048, + } // Light server and client settings LightServeFlag = cli.IntFlag{ Name: "light.serve", diff --git a/core/blockchain.go b/core/blockchain.go index c05ebfd549..7dd1097e98 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -372,7 +372,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer) recover = true } - bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, recover) + bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover) } // Take ownership of this particular state go bc.update() diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index cb634a451d..96a5c7a8d4 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" @@ -163,7 +162,7 @@ func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks [ } // Check the snapshot, ensure it's integrated - if err := snapshot.VerifyState(chain.snaps, block.Root()); err != nil { + if err := chain.snaps.Verify(block.Root()); err != nil { t.Errorf("The disk layer is not integrated %v", err) } } diff --git a/core/genesis.go b/core/genesis.go index a01eee9eb2..462aec45bd 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -171,7 +171,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } return genesis.Config, block.Hash(), nil } - // We have the genesis block in database(perhaps in ancient database) // but the corresponding state is missing. header := rawdb.ReadHeader(db, stored, 0) @@ -190,7 +189,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } return genesis.Config, block.Hash(), nil } - // Check whether the genesis block is already written. if genesis != nil { hash := genesis.ToBlock(nil).Hash() @@ -198,7 +196,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig return genesis.Config, hash, &GenesisMismatchError{stored, hash} } } - // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) if err := newcfg.CheckConfigForkOrder(); err != nil { @@ -216,7 +213,6 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig if genesis == nil && stored != params.MainnetGenesisHash { return storedcfg, stored, nil } - // Check config compatibility and write the config. Compatibility errors // are returned to the caller unless we're already at block zero. height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db)) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index a2cc9c7be9..1f8c3f4542 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -335,7 +335,7 @@ func InspectDatabase(db ethdb.Database) error { hashNumPairings.Add(size) case len(key) == common.HashLength: tries.Add(size) - case bytes.HasPrefix(key, codePrefix) && len(key) == len(codePrefix)+common.HashLength: + case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: codes.Add(size) case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): txLookups.Add(size) @@ -347,15 +347,26 @@ func InspectDatabase(db ethdb.Database) error { preimages.Add(size) case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): bloomBits.Add(size) + case bytes.HasPrefix(key, BloomBitsIndexPrefix): + bloomBits.Add(size) case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength: cliqueSnaps.Add(size) - case bytes.HasPrefix(key, []byte("cht-")) && len(key) == 4+common.HashLength: + case bytes.HasPrefix(key, []byte("cht-")) || + bytes.HasPrefix(key, []byte("chtIndexV2-")) || + bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie chtTrieNodes.Add(size) - case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength: + case bytes.HasPrefix(key, []byte("blt-")) || + bytes.HasPrefix(key, []byte("bltIndex-")) || + bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey} { + for _, meta := range [][]byte{ + databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey, + fastTrieProgressKey, snapshotRootKey, snapshotJournalKey, snapshotGeneratorKey, + snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, + badBlockKey, + } { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 9749a30d8a..0b411057f8 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -85,7 +85,7 @@ var ( bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value - codePrefix = []byte("c") // codePrefix + code hash -> account code + CodePrefix = []byte("c") // CodePrefix + code hash -> account code preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -209,16 +209,16 @@ func preimageKey(hash common.Hash) []byte { return append(preimagePrefix, hash.Bytes()...) } -// codeKey = codePrefix + hash +// codeKey = CodePrefix + hash func codeKey(hash common.Hash) []byte { - return append(codePrefix, hash.Bytes()...) + return append(CodePrefix, hash.Bytes()...) } // IsCodeKey reports whether the given byte slice is the key of contract code, // if so return the raw code hash as well. func IsCodeKey(key []byte) (bool, []byte) { - if bytes.HasPrefix(key, codePrefix) && len(key) == common.HashLength+len(codePrefix) { - return true, key[len(codePrefix):] + if bytes.HasPrefix(key, CodePrefix) && len(key) == common.HashLength+len(CodePrefix) { + return true, key[len(CodePrefix):] } return false, nil } diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go new file mode 100644 index 0000000000..4aeeb176e8 --- /dev/null +++ b/core/state/pruner/bloom.go @@ -0,0 +1,132 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pruner + +import ( + "encoding/binary" + "errors" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/log" + bloomfilter "github.com/holiman/bloomfilter/v2" +) + +// stateBloomHasher is a wrapper around a byte blob to satisfy the interface API +// requirements of the bloom library used. It's used to convert a trie hash or +// contract code hash into a 64 bit mini hash. +type stateBloomHasher []byte + +func (f stateBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } +func (f stateBloomHasher) Sum(b []byte) []byte { panic("not implemented") } +func (f stateBloomHasher) Reset() { panic("not implemented") } +func (f stateBloomHasher) BlockSize() int { panic("not implemented") } +func (f stateBloomHasher) Size() int { return 8 } +func (f stateBloomHasher) Sum64() uint64 { return binary.BigEndian.Uint64(f) } + +// stateBloom is a bloom filter used during the state convesion(snapshot->state). +// The keys of all generated entries will be recorded here so that in the pruning +// stage the entries belong to the specific version can be avoided for deletion. +// +// The false-positive is allowed here. The "false-positive" entries means they +// actually don't belong to the specific version but they are not deleted in the +// pruning. The downside of the false-positive allowance is we may leave some "dangling" +// nodes in the disk. But in practice the it's very unlike the dangling node is +// state root. So in theory this pruned state shouldn't be visited anymore. Another +// potential issue is for fast sync. If we do another fast sync upon the pruned +// database, it's problematic which will stop the expansion during the syncing. +// TODO address it @rjl493456442 @holiman @karalabe. +// +// After the entire state is generated, the bloom filter should be persisted into +// the disk. It indicates the whole generation procedure is finished. +type stateBloom struct { + bloom *bloomfilter.Filter +} + +// newStateBloomWithSize creates a brand new state bloom for state generation. +// The bloom filter will be created by the passing bloom filter size. According +// to the https://hur.st/bloomfilter/?n=600000000&p=&m=2048MB&k=4, the parameters +// are picked so that the false-positive rate for mainnet is low enough. +func newStateBloomWithSize(size uint64) (*stateBloom, error) { + bloom, err := bloomfilter.New(size*1024*1024*8, 4) + if err != nil { + return nil, err + } + log.Info("Initialized state bloom", "size", common.StorageSize(float64(bloom.M()/8))) + return &stateBloom{bloom: bloom}, nil +} + +// NewStateBloomFromDisk loads the state bloom from the given file. +// In this case the assumption is held the bloom filter is complete. +func NewStateBloomFromDisk(filename string) (*stateBloom, error) { + bloom, _, err := bloomfilter.ReadFile(filename) + if err != nil { + return nil, err + } + return &stateBloom{bloom: bloom}, nil +} + +// Commit flushes the bloom filter content into the disk and marks the bloom +// as complete. +func (bloom *stateBloom) Commit(filename, tempname string) error { + // Write the bloom out into a temporary file + _, err := bloom.bloom.WriteFile(tempname) + if err != nil { + return err + } + // Ensure the file is synced to disk + f, err := os.Open(tempname) + if err != nil { + return err + } + if err := f.Sync(); err != nil { + f.Close() + return err + } + f.Close() + + // Move the teporary file into it's final location + return os.Rename(tempname, filename) +} + +// Put implements the KeyValueWriter interface. But here only the key is needed. +func (bloom *stateBloom) Put(key []byte, value []byte) error { + // If the key length is not 32bytes, ensure it's contract code + // entry with new scheme. + if len(key) != common.HashLength { + isCode, codeKey := rawdb.IsCodeKey(key) + if !isCode { + return errors.New("invalid entry") + } + bloom.bloom.Add(stateBloomHasher(codeKey)) + return nil + } + bloom.bloom.Add(stateBloomHasher(key)) + return nil +} + +// Delete removes the key from the key-value data store. +func (bloom *stateBloom) Delete(key []byte) error { panic("not supported") } + +// Contain is the wrapper of the underlying contains function which +// reports whether the key is contained. +// - If it says yes, the key may be contained +// - If it says no, the key is definitely not contained. +func (bloom *stateBloom) Contain(key []byte) (bool, error) { + return bloom.bloom.Contains(stateBloomHasher(key)), nil +} diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go new file mode 100644 index 0000000000..1cb65b38b1 --- /dev/null +++ b/core/state/pruner/pruner.go @@ -0,0 +1,537 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pruner + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +const ( + // stateBloomFilePrefix is the filename prefix of state bloom filter. + stateBloomFilePrefix = "statebloom" + + // stateBloomFilePrefix is the filename suffix of state bloom filter. + stateBloomFileSuffix = "bf.gz" + + // stateBloomFileTempSuffix is the filename suffix of state bloom filter + // while it is being written out to detect write aborts. + stateBloomFileTempSuffix = ".tmp" + + // rangeCompactionThreshold is the minimal deleted entry number for + // triggering range compaction. It's a quite arbitrary number but just + // to avoid triggering range compaction because of small deletion. + rangeCompactionThreshold = 100000 +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256(nil) +) + +// Pruner is an offline tool to prune the stale state with the +// help of the snapshot. The workflow of pruner is very simple: +// +// - iterate the snapshot, reconstruct the relevant state +// - iterate the database, delete all other state entries which +// don't belong to the target state and the genesis state +// +// It can take several hours(around 2 hours for mainnet) to finish +// the whole pruning work. It's recommended to run this offline tool +// periodically in order to release the disk usage and improve the +// disk read performance to some extent. +type Pruner struct { + db ethdb.Database + stateBloom *stateBloom + datadir string + trieCachePath string + headHeader *types.Header + snaptree *snapshot.Tree +} + +// NewPruner creates the pruner instance. +func NewPruner(db ethdb.Database, headHeader *types.Header, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) { + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, false) + if err != nil { + return nil, err // The relevant snapshot(s) might not exist + } + // Sanitize the bloom filter size if it's too small. + if bloomSize < 256 { + log.Warn("Sanitizing bloomfilter size", "provided(MB)", bloomSize, "updated(MB)", 256) + bloomSize = 256 + } + stateBloom, err := newStateBloomWithSize(bloomSize) + if err != nil { + return nil, err + } + return &Pruner{ + db: db, + stateBloom: stateBloom, + datadir: datadir, + trieCachePath: trieCachePath, + headHeader: headHeader, + snaptree: snaptree, + }, nil +} + +func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[common.Hash]struct{}, start time.Time) error { + // Delete all stale trie nodes in the disk. With the help of state bloom + // the trie nodes(and codes) belong to the active state will be filtered + // out. A very small part of stale tries will also be filtered because of + // the false-positive rate of bloom filter. But the assumption is held here + // that the false-positive is low enough(~0.05%). The probablity of the + // dangling node is the state root is super low. So the dangling nodes in + // theory will never ever be visited again. + var ( + count int + size common.StorageSize + pstart = time.Now() + logged = time.Now() + batch = maindb.NewBatch() + iter = maindb.NewIterator(nil, nil) + ) + for iter.Next() { + key := iter.Key() + + // All state entries don't belong to specific state and genesis are deleted here + // - trie node + // - legacy contract code + // - new-scheme contract code + isCode, codeKey := rawdb.IsCodeKey(key) + if len(key) == common.HashLength || isCode { + checkKey := key + if isCode { + checkKey = codeKey + } + if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist { + log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey)) + } else { + if ok, err := stateBloom.Contain(checkKey); err != nil { + return err + } else if ok { + continue + } + } + count += 1 + size += common.StorageSize(len(key) + len(iter.Value())) + batch.Delete(key) + + var eta time.Duration // Realistically will never remain uninited + if done := binary.BigEndian.Uint64(key[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + eta = time.Duration(left/speed) * time.Millisecond + } + if time.Since(logged) > 8*time.Second { + log.Info("Pruning state data", "nodes", count, "size", size, + "elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta)) + logged = time.Now() + } + // Recreate the iterator after every batch commit in order + // to allow the underlying compactor to delete the entries. + if batch.ValueSize() >= ethdb.IdealBatchSize { + batch.Write() + batch.Reset() + + iter.Release() + iter = maindb.NewIterator(nil, key) + } + } + } + if batch.ValueSize() > 0 { + batch.Write() + batch.Reset() + } + iter.Release() + log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart))) + + // Start compactions, will remove the deleted data from the disk immediately. + // Note for small pruning, the compaction is skipped. + if count >= rangeCompactionThreshold { + cstart := time.Now() + + for b := byte(0); b < byte(16); b++ { + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", b, b+1), "elapsed", common.PrettyDuration(time.Since(cstart))) + if err := maindb.Compact([]byte{b}, []byte{b + 1}); err != nil { + log.Error("Database compaction failed", "error", err) + return err + } + } + log.Info("Database compaction finished", "elapsed", common.PrettyDuration(time.Since(cstart))) + } + log.Info("State pruning successful", "pruned", size, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// Prune deletes all historical state nodes except the nodes belong to the +// specified state version. If user doesn't specify the state version, use +// the bottom-most snapshot diff layer as the target. +func (p *Pruner) Prune(root common.Hash) error { + // If the state bloom filter is already committed previously, + // reuse it for pruning instead of generating a new one. It's + // mandatory because a part of state may already be deleted, + // the recovery procedure is necessary. + _, stateBloomRoot, err := findBloomFilter(p.datadir) + if err != nil { + return err + } + if stateBloomRoot != (common.Hash{}) { + return RecoverPruning(p.datadir, p.db, p.trieCachePath) + } + // If the target state root is not specified, use the HEAD-127 as the + // target. The reason for picking it is: + // - in most of the normal cases, the related state is available + // - the probability of this layer being reorg is very low + var layers []snapshot.Snapshot + if root == (common.Hash{}) { + // Retrieve all snapshot layers from the current HEAD. + // In theory there are 128 difflayers + 1 disk layer present, + // so 128 diff layers are expected to be returned. + layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true) + if len(layers) != 128 { + // Reject if the accumulated diff layers are less than 128. It + // means in most of normal cases, there is no associated state + // with bottom-most diff layer. + return errors.New("the snapshot difflayers are less than 128") + } + // Use the bottom-most diff layer as the target + root = layers[len(layers)-1].Root() + } + // Ensure the root is really present. The weak assumption + // is the presence of root can indicate the presence of the + // entire trie. + if blob := rawdb.ReadTrieNode(p.db, root); len(blob) == 0 { + // The special case is for clique based networks(rinkeby, goerli + // and some other private networks), it's possible that two + // consecutive blocks will have same root. In this case snapshot + // difflayer won't be created. So HEAD-127 may not paired with + // head-127 layer. Instead the paired layer is higher than the + // bottom-most diff layer. Try to find the bottom-most snapshot + // layer with state available. + // + // Note HEAD and HEAD-1 is ignored. Usually there is the associated + // state available, but we don't want to use the topmost state + // as the pruning target. + var found bool + for i := len(layers) - 2; i >= 2; i-- { + if blob := rawdb.ReadTrieNode(p.db, layers[i].Root()); len(blob) != 0 { + root = layers[i].Root() + found = true + log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i) + break + } + } + if !found { + if len(layers) > 0 { + return errors.New("no snapshot paired state") + } + return fmt.Errorf("associated state[%x] is not present", root) + } + } else { + if len(layers) > 0 { + log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.headHeader.Number.Uint64()-127) + } else { + log.Info("Selecting user-specified state as the pruning target", "root", root) + } + } + // Before start the pruning, delete the clean trie cache first. + // It's necessary otherwise in the next restart we will hit the + // deleted state root in the "clean cache" so that the incomplete + // state is picked for usage. + deleteCleanTrieCache(p.trieCachePath) + + // All the state roots of the middle layer should be forcibly pruned, + // otherwise the dangling state will be left. + middleRoots := make(map[common.Hash]struct{}) + for _, layer := range layers { + if layer.Root() == root { + break + } + middleRoots[layer.Root()] = struct{}{} + } + // Traverse the target state, re-construct the whole state trie and + // commit to the given bloom filter. + start := time.Now() + if err := snapshot.GenerateTrie(p.snaptree, root, p.db, p.stateBloom); err != nil { + return err + } + // Traverse the genesis, put all genesis state entries into the + // bloom filter too. + if err := extractGenesis(p.db, p.stateBloom); err != nil { + return err + } + filterName := bloomFilterName(p.datadir, root) + + log.Info("Writing state bloom to disk", "name", filterName) + if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil { + return err + } + log.Info("State bloom filter committed", "name", filterName) + + if err := prune(p.db, p.stateBloom, middleRoots, start); err != nil { + return err + } + // Pruning is done, now drop the "useless" layers from the snapshot. + // Firstly, flushing the target layer into the disk. After that all + // diff layers below the target will all be merged into the disk. + if err := p.snaptree.Cap(root, 0); err != nil { + return err + } + // Secondly, flushing the snapshot journal into the disk. All diff + // layers upon the target layer are dropped silently. Eventually the + // entire snapshot tree is converted into a single disk layer with + // the pruning target as the root. + if _, err := p.snaptree.Journal(root); err != nil { + return err + } + // Delete the state bloom, it marks the entire pruning procedure is + // finished. If any crashes or manual exit happens before this, + // `RecoverPruning` will pick it up in the next restarts to redo all + // the things. + os.RemoveAll(filterName) + return nil +} + +// RecoverPruning will resume the pruning procedure during the system restart. +// This function is used in this case: user tries to prune state data, but the +// system was interrupted midway because of crash or manual-kill. In this case +// if the bloom filter for filtering active state is already constructed, the +// pruning can be resumed. What's more if the bloom filter is constructed, the +// pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left +// in the disk. +func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) error { + stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir) + if err != nil { + return err + } + if stateBloomPath == "" { + return nil // nothing to recover + } + headHeader, err := getHeadHeader(db) + if err != nil { + return err + } + // Initialize the snapshot tree in recovery mode to handle this special case: + // - Users run the `prune-state` command multiple times + // - Neither these `prune-state` running is finished(e.g. interrupted manually) + // - The state bloom filter is already generated, a part of state is deleted, + // so that resuming the pruning here is mandatory + // - The state HEAD is rewound already because of multiple incomplete `prune-state` + // In this case, even the state HEAD is not exactly matched with snapshot, it + // still feasible to recover the pruning correctly. + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, true) + if err != nil { + return err // The relevant snapshot(s) might not exist + } + stateBloom, err := NewStateBloomFromDisk(stateBloomPath) + if err != nil { + return err + } + log.Info("Loaded state bloom filter", "path", stateBloomPath) + + // Before start the pruning, delete the clean trie cache first. + // It's necessary otherwise in the next restart we will hit the + // deleted state root in the "clean cache" so that the incomplete + // state is picked for usage. + deleteCleanTrieCache(trieCachePath) + + // All the state roots of the middle layers should be forcibly pruned, + // otherwise the dangling state will be left. + var ( + found bool + layers = snaptree.Snapshots(headHeader.Root, 128, true) + middleRoots = make(map[common.Hash]struct{}) + ) + for _, layer := range layers { + if layer.Root() == stateBloomRoot { + found = true + break + } + middleRoots[layer.Root()] = struct{}{} + } + if !found { + log.Error("Pruning target state is not existent") + return errors.New("non-existent target state") + } + if err := prune(db, stateBloom, middleRoots, time.Now()); err != nil { + return err + } + // Pruning is done, now drop the "useless" layers from the snapshot. + // Firstly, flushing the target layer into the disk. After that all + // diff layers below the target will all be merged into the disk. + if err := snaptree.Cap(stateBloomRoot, 0); err != nil { + return err + } + // Secondly, flushing the snapshot journal into the disk. All diff + // layers upon are dropped silently. Eventually the entire snapshot + // tree is converted into a single disk layer with the pruning target + // as the root. + if _, err := snaptree.Journal(stateBloomRoot); err != nil { + return err + } + // Delete the state bloom, it marks the entire pruning procedure is + // finished. If any crashes or manual exit happens before this, + // `RecoverPruning` will pick it up in the next restarts to redo all + // the things. + os.RemoveAll(stateBloomPath) + return nil +} + +// extractGenesis loads the genesis state and commits all the state entries +// into the given bloomfilter. +func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { + genesisHash := rawdb.ReadCanonicalHash(db, 0) + if genesisHash == (common.Hash{}) { + return errors.New("missing genesis hash") + } + genesis := rawdb.ReadBlock(db, genesisHash, 0) + if genesis == nil { + return errors.New("missing genesis block") + } + t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db)) + if err != nil { + return err + } + accIter := t.NodeIterator(nil) + for accIter.Next(true) { + hash := accIter.Hash() + + // Embedded nodes don't have hash. + if hash != (common.Hash{}) { + stateBloom.Put(hash.Bytes(), nil) + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + var acc state.Account + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + return err + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db)) + if err != nil { + return err + } + storageIter := storageTrie.NodeIterator(nil) + for storageIter.Next(true) { + hash := storageIter.Hash() + if hash != (common.Hash{}) { + stateBloom.Put(hash.Bytes(), nil) + } + } + if storageIter.Error() != nil { + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + stateBloom.Put(acc.CodeHash, nil) + } + } + } + return accIter.Error() +} + +func bloomFilterName(datadir string, hash common.Hash) string { + return filepath.Join(datadir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix)) +} + +func isBloomFilter(filename string) (bool, common.Hash) { + filename = filepath.Base(filename) + if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) { + return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1]) + } + return false, common.Hash{} +} + +func findBloomFilter(datadir string) (string, common.Hash, error) { + var ( + stateBloomPath string + stateBloomRoot common.Hash + ) + if err := filepath.Walk(datadir, func(path string, info os.FileInfo, err error) error { + if info != nil && !info.IsDir() { + ok, root := isBloomFilter(path) + if ok { + stateBloomPath = path + stateBloomRoot = root + } + } + return nil + }); err != nil { + return "", common.Hash{}, err + } + return stateBloomPath, stateBloomRoot, nil +} + +func getHeadHeader(db ethdb.Database) (*types.Header, error) { + headHeaderHash := rawdb.ReadHeadBlockHash(db) + if headHeaderHash == (common.Hash{}) { + return nil, errors.New("empty head block hash") + } + headHeaderNumber := rawdb.ReadHeaderNumber(db, headHeaderHash) + if headHeaderNumber == nil { + return nil, errors.New("empty head block number") + } + headHeader := rawdb.ReadHeader(db, headHeaderHash, *headHeaderNumber) + if headHeader == nil { + return nil, errors.New("empty head header") + } + return headHeader, nil +} + +const warningLog = ` + +WARNING! + +The clean trie cache is not found. Please delete it by yourself after the +pruning. Remember don't start the Geth without deleting the clean trie cache +otherwise the entire database may be damaged! + +Check the command description "geth snapshot prune-state --help" for more details. +` + +func deleteCleanTrieCache(path string) { + if _, err := os.Stat(path); os.IsNotExist(err) { + log.Warn(warningLog) + return + } + os.RemoveAll(path) + log.Info("Deleted trie clean cache", "path", path) +} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 4ec229b7ac..bb87ecddf1 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -18,12 +18,17 @@ package snapshot import ( "bytes" + "encoding/binary" + "errors" "fmt" + "math" + "runtime" "sync" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -38,46 +43,56 @@ type trieKV struct { type ( // trieGeneratorFn is the interface of trie generation which can // be implemented by different trie algorithm. - trieGeneratorFn func(in chan trieKV, out chan common.Hash) + trieGeneratorFn func(db ethdb.KeyValueWriter, in chan (trieKV), out chan (common.Hash)) // leafCallbackFn is the callback invoked at the leaves of the trie, // returns the subtrie root with the specified subtrie identifier. - leafCallbackFn func(hash common.Hash, stat *generateStats) common.Hash + leafCallbackFn func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) ) // GenerateAccountTrieRoot takes an account iterator and reproduces the root hash. func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) { - return generateTrieRoot(it, common.Hash{}, stdGenerate, nil, &generateStats{start: time.Now()}, true) + return generateTrieRoot(nil, it, common.Hash{}, stackTrieGenerate, nil, newGenerateStats(), true) } // GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash. func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) { - return generateTrieRoot(it, account, stdGenerate, nil, &generateStats{start: time.Now()}, true) + return generateTrieRoot(nil, it, account, stackTrieGenerate, nil, newGenerateStats(), true) } -// VerifyState takes the whole snapshot tree as the input, traverses all the accounts -// as well as the corresponding storages and compares the re-computed hash with the -// original one(state root and the storage root). -func VerifyState(snaptree *Tree, root common.Hash) error { +// GenerateTrie takes the whole snapshot tree as the input, traverses all the +// accounts as well as the corresponding storages and regenerate the whole state +// (account trie + all storage tries). +func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethdb.KeyValueWriter) error { + // Traverse all state by snapshot, re-generate the whole state trie acctIt, err := snaptree.AccountIterator(root, common.Hash{}) if err != nil { - return err + return err // The required snapshot might not exist. } defer acctIt.Release() - got, err := generateTrieRoot(acctIt, common.Hash{}, stdGenerate, func(account common.Hash, stat *generateStats) common.Hash { - storageIt, err := snaptree.StorageIterator(root, account, common.Hash{}) + got, err := generateTrieRoot(dst, acctIt, common.Hash{}, stackTrieGenerate, func(dst ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + // Migrate the code first, commit the contract code into the tmp db. + if codeHash != emptyCode { + code := rawdb.ReadCode(src, codeHash) + if len(code) == 0 { + return common.Hash{}, errors.New("failed to read contract code") + } + rawdb.WriteCode(dst, codeHash, code) + } + // Then migrate all storage trie nodes into the tmp db. + storageIt, err := snaptree.StorageIterator(root, accountHash, common.Hash{}) if err != nil { - return common.Hash{} + return common.Hash{}, err } defer storageIt.Release() - hash, err := generateTrieRoot(storageIt, account, stdGenerate, nil, stat, false) + hash, err := generateTrieRoot(dst, storageIt, accountHash, stackTrieGenerate, nil, stat, false) if err != nil { - return common.Hash{} + return common.Hash{}, err } - return hash - }, &generateStats{start: time.Now()}, true) + return hash, nil + }, newGenerateStats(), true) if err != nil { return err @@ -91,23 +106,64 @@ func VerifyState(snaptree *Tree, root common.Hash) error { // generateStats is a collection of statistics gathered by the trie generator // for logging purposes. type generateStats struct { - accounts uint64 - slots uint64 - curAccount common.Hash - curSlot common.Hash - start time.Time - lock sync.RWMutex + head common.Hash + start time.Time + + accounts uint64 // Number of accounts done (including those being crawled) + slots uint64 // Number of storage slots done (including those being crawled) + + slotsStart map[common.Hash]time.Time // Start time for account slot crawling + slotsHead map[common.Hash]common.Hash // Slot head for accounts being crawled + + lock sync.RWMutex } -// progress records the progress trie generator made recently. -func (stat *generateStats) progress(accounts, slots uint64, curAccount common.Hash, curSlot common.Hash) { +// newGenerateStats creates a new generator stats. +func newGenerateStats() *generateStats { + return &generateStats{ + slotsStart: make(map[common.Hash]time.Time), + slotsHead: make(map[common.Hash]common.Hash), + start: time.Now(), + } +} + +// progressAccounts updates the generator stats for the account range. +func (stat *generateStats) progressAccounts(account common.Hash, done uint64) { stat.lock.Lock() defer stat.lock.Unlock() - stat.accounts += accounts - stat.slots += slots - stat.curAccount = curAccount - stat.curSlot = curSlot + stat.accounts += done + stat.head = account +} + +// finishAccounts updates the gemerator stats for the finished account range. +func (stat *generateStats) finishAccounts(done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.accounts += done +} + +// progressContract updates the generator stats for a specific in-progress contract. +func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + stat.slotsHead[account] = slot + if _, ok := stat.slotsStart[account]; !ok { + stat.slotsStart[account] = time.Now() + } +} + +// finishContract updates the generator stats for a specific just-finished contract. +func (stat *generateStats) finishContract(account common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + delete(stat.slotsHead, account) + delete(stat.slotsStart, account) } // report prints the cumulative progress statistic smartly. @@ -115,22 +171,39 @@ func (stat *generateStats) report() { stat.lock.RLock() defer stat.lock.RUnlock() - var ctx []interface{} - if stat.curSlot != (common.Hash{}) { - ctx = append(ctx, []interface{}{ - "in", stat.curAccount, - "at", stat.curSlot, - }...) - } else { - ctx = append(ctx, []interface{}{"at", stat.curAccount}...) + ctx := []interface{}{ + "accounts", stat.accounts, + "slots", stat.slots, + "elapsed", common.PrettyDuration(time.Since(stat.start)), } - // Add the usual measurements - ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) - if stat.slots != 0 { - ctx = append(ctx, []interface{}{"slots", stat.slots}...) + if stat.accounts > 0 { + // If there's progress on the account trie, estimate the time to finish crawling it + if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 { + var ( + left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts + speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + eta = time.Duration(left/speed) * time.Millisecond + ) + // If there are large contract crawls in progress, estimate their finish time + for acc, head := range stat.slotsHead { + start := stat.slotsStart[acc] + if done := binary.BigEndian.Uint64(head[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8]) + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + // Override the ETA if larger than the largest until now + if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA { + eta = slotETA + } + } + } + ctx = append(ctx, []interface{}{ + "eta", common.PrettyDuration(eta), + }...) + } } - ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) - log.Info("Generating trie hash from snapshot", ctx...) + log.Info("Iterating state snapshot", ctx...) } // reportDone prints the last log when the whole generation is finished. @@ -144,13 +217,32 @@ func (stat *generateStats) reportDone() { ctx = append(ctx, []interface{}{"slots", stat.slots}...) } ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) - log.Info("Generated trie hash from snapshot", ctx...) + log.Info("Iterated snapshot", ctx...) +} + +// runReport periodically prints the progress information. +func runReport(stats *generateStats, stop chan bool) { + timer := time.NewTimer(0) + defer timer.Stop() + + for { + select { + case <-timer.C: + stats.report() + timer.Reset(time.Second * 8) + case success := <-stop: + if success { + stats.reportDone() + } + return + } + } } // generateTrieRoot generates the trie hash based on the snapshot iterator. // It can be used for generating account trie, storage trie or even the // whole state which connects the accounts and the corresponding storages. -func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { +func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { var ( in = make(chan trieKV) // chan to pass leaves out = make(chan common.Hash, 1) // chan to collect result @@ -161,46 +253,43 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato wg.Add(1) go func() { defer wg.Done() - generatorFn(in, out) + generatorFn(db, in, out) }() - // Spin up a go-routine for progress logging if report && stats != nil { wg.Add(1) go func() { defer wg.Done() - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-timer.C: - stats.report() - timer.Reset(time.Second * 8) - case success := <-stoplog: - if success { - stats.reportDone() - } - return - } - } + runReport(stats, stoplog) }() } + // Create a semaphore to assign tasks and collect results through. We'll pre- + // fill it with nils, thus using the same channel for both limiting concurrent + // processing and gathering results. + threads := runtime.NumCPU() + results := make(chan error, threads) + for i := 0; i < threads; i++ { + results <- nil // fill the semaphore + } // stop is a helper function to shutdown the background threads // and return the re-generated trie hash. - stop := func(success bool) common.Hash { + stop := func(fail error) (common.Hash, error) { close(in) result := <-out - stoplog <- success + for i := 0; i < threads; i++ { + if err := <-results; err != nil && fail == nil { + fail = err + } + } + stoplog <- fail == nil + wg.Wait() - return result + return result, fail } var ( logged = time.Now() processed = uint64(0) leaf trieKV - last common.Hash ) // Start to feed leaves for it.Next() { @@ -212,26 +301,35 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato if leafCallback == nil { fullData, err = FullAccountRLP(it.(AccountIterator).Account()) if err != nil { - stop(false) - return common.Hash{}, err + return stop(err) } } else { + // Wait until the semaphore allows us to continue, aborting if + // a sub-task failed + if err := <-results; err != nil { + results <- nil // stop will drain the results, add a noop back for this error we just consumed + return stop(err) + } + // Fetch the next account and process it concurrently account, err := FullAccount(it.(AccountIterator).Account()) if err != nil { - stop(false) - return common.Hash{}, err - } - // Apply the leaf callback. Normally the callback is used to traverse - // the storage trie and re-generate the subtrie root. - subroot := leafCallback(it.Hash(), stats) - if !bytes.Equal(account.Root, subroot.Bytes()) { - stop(false) - return common.Hash{}, fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot) + return stop(err) } + go func(hash common.Hash) { + subroot, err := leafCallback(db, hash, common.BytesToHash(account.CodeHash), stats) + if err != nil { + results <- err + return + } + if !bytes.Equal(account.Root, subroot.Bytes()) { + results <- fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot) + return + } + results <- nil + }(it.Hash()) fullData, err = rlp.EncodeToBytes(account) if err != nil { - stop(false) - return common.Hash{}, err + return stop(err) } } leaf = trieKV{it.Hash(), fullData} @@ -244,32 +342,34 @@ func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieGenerato processed++ if time.Since(logged) > 3*time.Second && stats != nil { if account == (common.Hash{}) { - stats.progress(processed, 0, it.Hash(), common.Hash{}) + stats.progressAccounts(it.Hash(), processed) } else { - stats.progress(0, processed, account, it.Hash()) + stats.progressContract(account, it.Hash(), processed) } logged, processed = time.Now(), 0 } - last = it.Hash() } // Commit the last part statistic. if processed > 0 && stats != nil { if account == (common.Hash{}) { - stats.progress(processed, 0, last, common.Hash{}) + stats.finishAccounts(processed) } else { - stats.progress(0, processed, account, last) + stats.finishContract(account, processed) } } - result := stop(true) - return result, nil + return stop(nil) } -// stdGenerate is a very basic hexary trie builder which uses the same Trie -// as the rest of geth, with no enhancements or optimizations -func stdGenerate(in chan trieKV, out chan common.Hash) { - t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New())) +func stackTrieGenerate(db ethdb.KeyValueWriter, in chan trieKV, out chan common.Hash) { + t := trie.NewStackTrie(db) for leaf := range in { t.TryUpdate(leaf.key[:], leaf.value) } - out <- t.Hash() + var root common.Hash + if db == nil { + root = t.Hash() + } else { + root, _ = t.Commit() + } + out <- root } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 443fc8e5c7..df2b1ed330 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -178,7 +178,7 @@ type Tree struct { // store, on a background thread. If the memory layers from the journal is not // continuous with disk layer or the journal is missing, all diffs will be discarded // iff it's in "recovery" mode, otherwise rebuild is mandatory. -func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, recovery bool) *Tree { +func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool) (*Tree, error) { // Create a new, empty snapshot tree snap := &Tree{ diskdb: diskdb, @@ -192,16 +192,19 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm // Attempt to load a previously persisted snapshot and rebuild one if failed head, err := loadSnapshot(diskdb, triedb, cache, root, recovery) if err != nil { - log.Warn("Failed to load snapshot, regenerating", "err", err) - snap.Rebuild(root) - return snap + if rebuild { + log.Warn("Failed to load snapshot, regenerating", "err", err) + snap.Rebuild(root) + return snap, nil + } + return nil, err // Bail out the error, don't rebuild automatically. } // Existing snapshot loaded, seed all the layers for head != nil { snap.layers[head.Root()] = head head = head.Parent() } - return snap + return snap, nil } // waitBuild blocks until the snapshot finishes rebuilding. This method is meant @@ -234,6 +237,39 @@ func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { return t.layers[blockRoot] } +// Snapshots returns all visited layers from the topmost layer with specific +// root and traverses downward. The layer amount is limited by the given number. +// If nodisk is set, then disk layer is excluded. +func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot { + t.lock.RLock() + defer t.lock.RUnlock() + + if limits == 0 { + return nil + } + layer := t.layers[root] + if layer == nil { + return nil + } + var ret []Snapshot + for { + if _, isdisk := layer.(*diskLayer); isdisk && nodisk { + break + } + ret = append(ret, layer) + limits -= 1 + if limits == 0 { + break + } + parent := layer.Parent() + if parent == nil { + break + } + layer = parent + } + return ret +} + // Update adds a new snapshot into the tree, if that can be linked to an existing // old parent. It is disallowed to insert a disk layer (the origin of all). func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { @@ -681,6 +717,38 @@ func (t *Tree) StorageIterator(root common.Hash, account common.Hash, seek commo return newFastStorageIterator(t, root, account, seek) } +// Verify iterates the whole state(all the accounts as well as the corresponding storages) +// with the specific root and compares the re-computed hash with the original one. +func (t *Tree) Verify(root common.Hash) error { + acctIt, err := t.AccountIterator(root, common.Hash{}) + if err != nil { + return err + } + defer acctIt.Release() + + got, err := generateTrieRoot(nil, acctIt, common.Hash{}, stackTrieGenerate, func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + storageIt, err := t.StorageIterator(root, accountHash, common.Hash{}) + if err != nil { + return common.Hash{}, err + } + defer storageIt.Release() + + hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + + if err != nil { + return err + } + if got != root { + return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root) + } + return nil +} + // disklayer is an internal helper function to return the disk layer. // The lock of snapTree is assumed to be held already. func (t *Tree) disklayer() *diskLayer { diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index ca4fa0a055..a315fd216c 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -17,6 +17,7 @@ package snapshot import ( + "encoding/binary" "fmt" "math/big" "math/rand" @@ -369,3 +370,69 @@ func TestPostCapBasicDataAccess(t *testing.T) { t.Error("expected error capping the disk layer, got none") } } + +// TestSnaphots tests the functionality for retrieveing the snapshot +// with given head root and the desired depth. +func TestSnaphots(t *testing.T) { + // setAccount is a helper to construct a random account entry and assign it to + // an account slot in a snapshot + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + makeRoot := func(height uint64) common.Hash { + var buffer [8]byte + binary.BigEndian.PutUint64(buffer[:], height) + return common.BytesToHash(buffer[:]) + } + // Create a starting base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Construct the snapshots with 128 layers + var ( + last = common.HexToHash("0x01") + head common.Hash + ) + // Flush another 128 layers, one diff will be flatten into the parent. + for i := 0; i < 128; i++ { + head = makeRoot(uint64(i + 2)) + snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) + last = head + snaps.Cap(head, 128) // 129 layers(128 diffs + 1 disk) are allowed, 129th is the persistent layer + } + var cases = []struct { + headRoot common.Hash + limit int + nodisk bool + expected int + expectBottom common.Hash + }{ + {head, 0, false, 0, common.Hash{}}, + {head, 64, false, 64, makeRoot(127 + 2 - 63)}, + {head, 128, false, 128, makeRoot(2)}, // All diff layers + {head, 129, true, 128, makeRoot(2)}, // All diff layers + {head, 129, false, 129, common.HexToHash("0x01")}, // All diff layers + disk layer + } + for _, c := range cases { + layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) + if len(layers) != c.expected { + t.Fatalf("Returned snapshot layers are mismatched, want %v, got %v", c.expected, len(layers)) + } + if len(layers) == 0 { + continue + } + bottommost := layers[len(layers)-1] + if bottommost.Root() != c.expectBottom { + t.Fatalf("Snapshot mismatch, want %v, get %v", c.expectBottom, bottommost.Root()) + } + } +} diff --git a/eth/backend.go b/eth/backend.go index 51ad0ccf3f..4170ecc94e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/pruner" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" @@ -131,6 +132,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } log.Info("Initialised chain configuration", "config", chainConfig) + if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { + log.Error("Failed to recover state", "error", err) + } eth := &Ethereum{ config: config, chainDb: chainDb, diff --git a/go.mod b/go.mod index 5fd3f772a4..cddce4e0d8 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,7 @@ module github.com/ethereum/go-ethereum go 1.13 require ( - github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 - github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 github.com/aws/aws-sdk-go v1.25.48 github.com/btcsuite/btcd v0.20.1-beta @@ -15,15 +12,12 @@ require ( github.com/consensys/gurvy v0.3.8 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea - github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 github.com/edsrzf/mmap-go v1.0.0 - github.com/fatih/color v1.3.0 + github.com/fatih/color v1.7.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/go-ole/go-ole v1.2.1 // indirect - github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 @@ -39,15 +33,13 @@ require ( github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 - github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 - github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 + github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v2.20.5+incompatible @@ -56,12 +48,10 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect - golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 - gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index 1ac0cf81f2..4b788824c7 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,7 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -253,8 +254,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= @@ -263,6 +264,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -275,12 +277,10 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJye github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -298,7 +298,6 @@ github.com/kilic/bls12-381 v0.0.0-20201226121925-69dacb279461/go.mod h1:vDTTHJON github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -401,7 +400,6 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150 h1:ZeU+auZj1iNzN8iVhff6M38Mfu73FQiJve/GEXYJBjE= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= @@ -440,13 +438,11 @@ github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57N github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -570,7 +566,6 @@ golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 h1:AvbQYmiaaaza3cW3QXRyPo5kYgpFIzOAfeAAN7m3qQ4= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/tests/block_test_util.go b/tests/block_test_util.go index c043f0b3eb..745208b5b8 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -147,7 +146,7 @@ func (t *BlockTest) Run(snapshotter bool) error { } // Cross-check the snapshot-to-hash against the trie hash if snapshotter { - if err := snapshot.VerifyState(chain.Snapshots(), chain.CurrentBlock().Root()); err != nil { + if err := chain.Snapshots().Verify(chain.CurrentBlock().Root()); err != nil { return err } } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 7bd2e801e4..53c7d955be 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -236,7 +236,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo var snaps *snapshot.Tree if snapshotter { - snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false, false) + snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false) } statedb, _ = state.New(root, sdb, snaps) return snaps, statedb diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 575a04022f..a198eb204b 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -35,7 +35,7 @@ var stPool = sync.Pool{ }, } -func stackTrieFromPool(db ethdb.KeyValueStore) *StackTrie { +func stackTrieFromPool(db ethdb.KeyValueWriter) *StackTrie { st := stPool.Get().(*StackTrie) st.db = db return st @@ -50,24 +50,23 @@ func returnToPool(st *StackTrie) { // in order. Once it determines that a subtree will no longer be inserted // into, it will hash it and free up the memory it uses. type StackTrie struct { - nodeType uint8 // node type (as in branch, ext, leaf) - val []byte // value contained by this node if it's a leaf - key []byte // key chunk covered by this (full|ext) node - keyOffset int // offset of the key chunk inside a full key - children [16]*StackTrie // list of children (for fullnodes and exts) - - db ethdb.KeyValueStore // Pointer to the commit db, can be nil + nodeType uint8 // node type (as in branch, ext, leaf) + val []byte // value contained by this node if it's a leaf + key []byte // key chunk covered by this (full|ext) node + keyOffset int // offset of the key chunk inside a full key + children [16]*StackTrie // list of children (for fullnodes and exts) + db ethdb.KeyValueWriter // Pointer to the commit db, can be nil } // NewStackTrie allocates and initializes an empty trie. -func NewStackTrie(db ethdb.KeyValueStore) *StackTrie { +func NewStackTrie(db ethdb.KeyValueWriter) *StackTrie { return &StackTrie{ nodeType: emptyNode, db: db, } } -func newLeaf(ko int, key, val []byte, db ethdb.KeyValueStore) *StackTrie { +func newLeaf(ko int, key, val []byte, db ethdb.KeyValueWriter) *StackTrie { st := stackTrieFromPool(db) st.nodeType = leafNode st.keyOffset = ko @@ -76,7 +75,7 @@ func newLeaf(ko int, key, val []byte, db ethdb.KeyValueStore) *StackTrie { return st } -func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueStore) *StackTrie { +func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie { st := stackTrieFromPool(db) st.nodeType = extNode st.keyOffset = ko From 2728672c28183da21028379ea5497debe92325b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Feb 2021 17:02:30 +0200 Subject: [PATCH 311/948] core/state/pruner: fix compaction after pruning --- core/state/pruner/pruner.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 1cb65b38b1..84a8952b57 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -188,8 +188,15 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c cstart := time.Now() for b := byte(0); b < byte(16); b++ { - log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", b, b+1), "elapsed", common.PrettyDuration(time.Since(cstart))) - if err := maindb.Compact([]byte{b}, []byte{b + 1}); err != nil { + var ( + start = []byte{b << 4} + end = []byte{(b+1)<<4 - 1} + ) + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) + if b == 15 { + end = nil + } + if err := maindb.Compact(start, end); err != nil { log.Error("Database compaction failed", "error", err) return err } @@ -229,7 +236,7 @@ func (p *Pruner) Prune(root common.Hash) error { // Reject if the accumulated diff layers are less than 128. It // means in most of normal cases, there is no associated state // with bottom-most diff layer. - return errors.New("the snapshot difflayers are less than 128") + return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers)) } // Use the bottom-most diff layer as the target root = layers[len(layers)-1].Root() From 74dbc20260caac0159ca59bfad1f41321130e676 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 8 Feb 2021 20:31:52 +0100 Subject: [PATCH 312/948] core/state/pruner: fix compaction range error --- core/state/pruner/pruner.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 84a8952b57..1fbfa55b6a 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -186,16 +186,15 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c // Note for small pruning, the compaction is skipped. if count >= rangeCompactionThreshold { cstart := time.Now() - - for b := byte(0); b < byte(16); b++ { + for b := 0x00; b <= 0xf0; b += 0x10 { var ( - start = []byte{b << 4} - end = []byte{(b+1)<<4 - 1} + start = []byte{byte(b)} + end = []byte{byte(b + 0x10)} ) - log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) - if b == 15 { + if b == 0xf0 { end = nil } + log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) if err := maindb.Compact(start, end); err != nil { log.Error("Database compaction failed", "error", err) return err From 27786671d28705159f15cd458045d29d732110e5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Tue, 9 Feb 2021 10:42:55 +0100 Subject: [PATCH 313/948] internal/debug: add switch to format logs with json (#22207) adds a flag --log.json which if enabled makes the client format logs with JSON. --- internal/debug/flags.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/internal/debug/flags.go b/internal/debug/flags.go index e3a01378ca..fc4ea9f518 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -41,6 +41,10 @@ var ( Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail", Value: 3, } + logjsonFlag = cli.BoolFlag{ + Name: "log.json", + Usage: "Format logs with JSON", + } vmoduleFlag = cli.StringFlag{ Name: "vmodule", Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4)", @@ -114,7 +118,7 @@ var ( // Flags holds all command-line flags required for debugging. var Flags = []cli.Flag{ - verbosityFlag, vmoduleFlag, backtraceAtFlag, debugFlag, + verbosityFlag, logjsonFlag, vmoduleFlag, backtraceAtFlag, debugFlag, pprofFlag, pprofAddrFlag, pprofPortFlag, memprofilerateFlag, blockprofilerateFlag, cpuprofileFlag, traceFlag, } @@ -124,24 +128,29 @@ var DeprecatedFlags = []cli.Flag{ legacyBlockprofilerateFlag, legacyCpuprofileFlag, } -var ( - ostream log.Handler - glogger *log.GlogHandler -) +var glogger *log.GlogHandler func init() { - usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" - output := io.Writer(os.Stderr) - if usecolor { - output = colorable.NewColorableStderr() - } - ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) - glogger = log.NewGlogHandler(ostream) + glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger.Verbosity(log.LvlInfo) + log.Root().SetHandler(glogger) } // Setup initializes profiling and logging based on the CLI flags. // It should be called as early as possible in the program. func Setup(ctx *cli.Context) error { + var ostream log.Handler + output := io.Writer(os.Stderr) + if ctx.GlobalBool(logjsonFlag.Name) { + ostream = log.StreamHandler(output, log.JSONFormat()) + } else { + usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" + if usecolor { + output = colorable.NewColorableStderr() + } + ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) + } + glogger.SetHandler(ostream) // logging log.PrintOrigins(ctx.GlobalBool(debugFlag.Name)) glogger.Verbosity(log.Lvl(ctx.GlobalInt(verbosityFlag.Name))) From cb3c7e431978f0bd5efb19b378aa9e42d940986d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 10 Feb 2021 13:12:13 +0100 Subject: [PATCH 314/948] accounts/abi/bind: fixed unpacking error (#22230) There was a dormant error with structured inputs that failed unpacking. This commit fixes the error by switching casting to the better abi.ConvertType function. It also adds a test for calling a view function that returns a struct --- accounts/abi/abi_test.go | 3 ++ accounts/abi/bind/bind_test.go | 64 ++++++++++++++++++++++++++++++++++ accounts/abi/bind/template.go | 2 +- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index ad8acdf522..a022ec5f9d 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -43,6 +43,7 @@ const jsondata = ` { "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, { "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, { "type" : "function", "name" : "int8", "inputs" : [ { "name" : "inputs", "type" : "int8" } ] }, + { "type" : "function", "name" : "bytes32", "inputs" : [ { "name" : "inputs", "type" : "bytes32" } ] }, { "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, { "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, { "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, @@ -68,6 +69,7 @@ var ( String, _ = NewType("string", "", nil) Bool, _ = NewType("bool", "", nil) Bytes, _ = NewType("bytes", "", nil) + Bytes32, _ = NewType("bytes32", "", nil) Address, _ = NewType("address", "", nil) Uint64Arr, _ = NewType("uint64[]", "", nil) AddressArr, _ = NewType("address[]", "", nil) @@ -98,6 +100,7 @@ var methods = map[string]Method{ "uint64[]": NewMethod("uint64[]", "uint64[]", Function, "", false, false, []Argument{{"inputs", Uint64Arr, false}}, nil), "uint64[2]": NewMethod("uint64[2]", "uint64[2]", Function, "", false, false, []Argument{{"inputs", Uint64Arr2, false}}, nil), "int8": NewMethod("int8", "int8", Function, "", false, false, []Argument{{"inputs", Int8, false}}, nil), + "bytes32": NewMethod("bytes32", "bytes32", Function, "", false, false, []Argument{{"inputs", Bytes32, false}}, nil), "foo": NewMethod("foo", "foo", Function, "", false, false, []Argument{{"inputs", Uint32, false}}, nil), "bar": NewMethod("bar", "bar", Function, "", false, false, []Argument{{"inputs", Uint32, false}, {"string", Uint16, false}}, nil), "slice": NewMethod("slice", "slice", Function, "", false, false, []Argument{{"inputs", Uint32Arr2, false}}, nil), diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index db87703d03..400545fd23 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -529,6 +529,70 @@ var bindTests = []struct { nil, nil, }, + // Tests that structs are correctly unpacked + { + + `Structs`, + ` + pragma solidity ^0.6.5; + pragma experimental ABIEncoderV2; + contract Structs { + struct A { + bytes32 B; + } + + function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + uint256[] memory c; + bool[] memory d; + return (a, c, d); + } + + function G() public view returns (A[] memory a) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + return a; + } + } + `, + []string{`608060405234801561001057600080fd5b50610278806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806328811f591461003b5780636fecb6231461005b575b600080fd5b610043610070565b604051610052939291906101a0565b60405180910390f35b6100636100d6565b6040516100529190610186565b604080516002808252606082810190935282918291829190816020015b610095610131565b81526020019060019003908161008d575050805190915061026960611b9082906000906100be57fe5b60209081029190910101515293606093508392509050565b6040805160028082526060828101909352829190816020015b6100f7610131565b8152602001906001900390816100ef575050805190915061026960611b90829060009061012057fe5b602090810291909101015152905090565b60408051602081019091526000815290565b815260200190565b6000815180845260208085019450808401835b8381101561017b578151518752958201959082019060010161015e565b509495945050505050565b600060208252610199602083018461014b565b9392505050565b6000606082526101b3606083018661014b565b6020838203818501528186516101c98185610239565b91508288019350845b818110156101f3576101e5838651610143565b9484019492506001016101d2565b505084810360408601528551808252908201925081860190845b8181101561022b57825115158552938301939183019160010161020d565b509298975050505050505050565b9081526020019056fea2646970667358221220eb85327e285def14230424c52893aebecec1e387a50bb6b75fc4fdbed647f45f64736f6c63430006050033`}, + []string{`[{"inputs":[],"name":"F","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"},{"internalType":"uint256[]","name":"c","type":"uint256[]"},{"internalType":"bool[]","name":"d","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + defer sim.Close() + + // Deploy a structs method invoker contract and execute its default method + _, _, structs, err := DeployStructs(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy defaulter contract: %v", err) + } + sim.Commit() + opts := bind.CallOpts{} + if _, err := structs.F(&opts); err != nil { + t.Fatalf("Failed to invoke F method: %v", err) + } + if _, err := structs.G(&opts); err != nil { + t.Fatalf("Failed to invoke G method: %v", err) + } + `, + nil, + nil, + nil, + nil, + }, // Tests that non-existent contracts are reported as such (though only simulator test) { `NonExistent`, diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 351eabd258..e9bdd3d414 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -308,7 +308,7 @@ var ( return *outstruct, err } {{range $i, $t := .Normalized.Outputs}} - outstruct.{{.Name}} = out[{{$i}}].({{bindtype .Type $structs}}){{end}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} return *outstruct, err {{else}} From 409b16e5abac3a48c21142fdfa68d33cf6c95fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Feb 2021 20:44:05 +0200 Subject: [PATCH 315/948] cmd/utils, eth/ethconfig: unindex txs older than ~1 year --- cmd/utils/flags.go | 6 +++--- eth/ethconfig/config.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f9c5553961..0d7b0e1bf5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -212,10 +212,10 @@ var ( Name: "snapshot", Usage: `Enables snapshot-database mode (default = enable)`, } - TxLookupLimitFlag = cli.Int64Flag{ + TxLookupLimitFlag = cli.Uint64Flag{ Name: "txlookuplimit", - Usage: "Number of recent blocks to maintain transactions index by-hash for (default = index all blocks)", - Value: 0, + Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)", + Value: ethconfig.Defaults.TxLookupLimit, } LightKDFFlag = cli.BoolFlag{ Name: "lightkdf", diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 9147a602d5..e192e4d333 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -66,6 +66,7 @@ var Defaults = Config{ DatasetsLockMmap: false, }, NetworkId: 1, + TxLookupLimit: 2350000, LightPeers: 100, UltraLightFraction: 75, DatabaseCache: 512, From 111abdcfbdc3c73b527589dce7863d3b93eca91d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 11 Feb 2021 12:09:13 +0100 Subject: [PATCH 316/948] cmd/devp2p: fix documentation for eth-test (#22298) --- cmd/devp2p/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index e1372d0158..ca0b852fda 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -94,10 +94,12 @@ To run the eth protocol test suite against your implementation, the node needs t geth --datadir --nodiscover --nat=none --networkid 19763 --verbosity 5 ``` -Then, run the following command, replacing `` with the enode of the geth node: +Then, run the following command, replacing `` with the enode of the geth node: ``` - devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/fullchain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json + devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json ``` + +Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. [eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md [dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup From ef227c5f42a2e180b0e3b57d38ef5018fc8733d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 12 Feb 2021 12:45:34 +0200 Subject: [PATCH 317/948] core: fix temp memory blowup caused by defers holding on to state --- core/blockchain.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 7dd1097e98..d65ce4f048 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1805,6 +1805,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er return it.index, err } // No validation errors for the first block (or chain prefix skipped) + var activeState *state.StateDB + defer func() { + // The chain importer is starting and stopping trie prefetchers. If a bad + // block or other error is hit however, an early return may not properly + // terminate the background threads. This defer ensures that we clean up + // and dangling prefetcher, without defering each and holding on live refs. + if activeState != nil { + activeState.StopPrefetcher() + } + }() + for ; block != nil && err == nil || err == ErrKnownBlock; block, err = it.next() { // If the chain is terminating, stop processing blocks if bc.insertStopped() { @@ -1867,7 +1878,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er } // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") - defer statedb.StopPrefetcher() // stopped on write anyway, defer meant to catch early error returns + activeState = statedb // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. From 7d1b711c7d0f27efd7772c81bb73b9b29720515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 12 Feb 2021 20:48:18 +0100 Subject: [PATCH 318/948] les: enable les/4 and add tests (#22321) --- les/handler_test.go | 10 ++++++++++ les/odr_test.go | 5 +++++ les/protocol.go | 4 ++-- les/request_test.go | 4 ++++ les/test_helper.go | 12 ++++++++++-- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/les/handler_test.go b/les/handler_test.go index f5cbeb8efc..83be312081 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -49,6 +49,7 @@ func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{} // Tests that block headers can be retrieved from a remote chain based on user queries. func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } +func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) } func testGetBlockHeaders(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0) @@ -178,6 +179,7 @@ func testGetBlockHeaders(t *testing.T, protocol int) { // Tests that block contents can be retrieved from a remote chain based on their hashes. func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) } func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) } +func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) } func testGetBlockBodies(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0) @@ -255,6 +257,7 @@ func testGetBlockBodies(t *testing.T, protocol int) { // Tests that the contract codes can be retrieved based on account addresses. func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) } func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) } +func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) } func testGetCode(t *testing.T, protocol int) { // Assemble the test environment @@ -285,6 +288,7 @@ func testGetCode(t *testing.T, protocol int) { // Tests that the stale contract codes can't be retrieved based on account addresses. func TestGetStaleCodeLes2(t *testing.T) { testGetStaleCode(t, 2) } func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) } +func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) } func testGetStaleCode(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) @@ -309,6 +313,7 @@ func testGetStaleCode(t *testing.T, protocol int) { // Tests that the transaction receipts can be retrieved based on hashes. func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) } +func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) } func testGetReceipt(t *testing.T, protocol int) { // Assemble the test environment @@ -336,6 +341,7 @@ func testGetReceipt(t *testing.T, protocol int) { // Tests that trie merkle proofs can be retrieved func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) } func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) } +func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) } func testGetProofs(t *testing.T, protocol int) { // Assemble the test environment @@ -371,6 +377,7 @@ func testGetProofs(t *testing.T, protocol int) { // Tests that the stale contract codes can't be retrieved based on account addresses. func TestGetStaleProofLes2(t *testing.T) { testGetStaleProof(t, 2) } func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) } +func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) } func testGetStaleProof(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) @@ -407,6 +414,7 @@ func testGetStaleProof(t *testing.T, protocol int) { // Tests that CHT proofs can be correctly retrieved. func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) } func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) } +func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) } func testGetCHTProofs(t *testing.T, protocol int) { config := light.TestServerIndexerConfig @@ -454,6 +462,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { func TestGetBloombitsProofsLes2(t *testing.T) { testGetBloombitsProofs(t, 2) } func TestGetBloombitsProofsLes3(t *testing.T) { testGetBloombitsProofs(t, 3) } +func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) } // Tests that bloombits proofs can be correctly retrieved. func testGetBloombitsProofs(t *testing.T, protocol int) { @@ -503,6 +512,7 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) } func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) } +func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, 4) } func testTransactionStatus(t *testing.T, protocol int) { server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0) diff --git a/les/odr_test.go b/les/odr_test.go index c157507dd2..2a70a296c8 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -40,6 +40,7 @@ type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.Chain func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) } func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) } +func TestOdrGetBlockLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetBlock) } func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var block *types.Block @@ -57,6 +58,7 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainCon func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) } func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) } +func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipts) } func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var receipts types.Receipts @@ -78,6 +80,7 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.Chain func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) } func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) } +func TestOdrAccountsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrAccounts) } func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") @@ -107,6 +110,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) } func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) } +func TestOdrContractCallLes4(t *testing.T) { testOdr(t, 4, 2, true, odrContractCall) } type callmsg struct { types.Message @@ -159,6 +163,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) } func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) } +func TestOdrTxStatusLes4(t *testing.T) { testOdr(t, 4, 1, false, odrTxStatus) } func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var txs types.Transactions diff --git a/les/protocol.go b/les/protocol.go index 39d9f5152f..9eb6ec7471 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -39,8 +39,8 @@ const ( // Supported versions of the les protocol (first is primary) var ( - ClientProtocolVersions = []uint{lpv2, lpv3} - ServerProtocolVersions = []uint{lpv2, lpv3} + ClientProtocolVersions = []uint{lpv2, lpv3, lpv4} + ServerProtocolVersions = []uint{lpv2, lpv3, lpv4} AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list ) diff --git a/les/request_test.go b/les/request_test.go index 4851274382..b054fd88df 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -38,6 +38,7 @@ type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) ligh func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) } func TestBlockAccessLes3(t *testing.T) { testAccess(t, 3, tfBlockAccess) } +func TestBlockAccessLes4(t *testing.T) { testAccess(t, 4, tfBlockAccess) } func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { return &light.BlockRequest{Hash: bhash, Number: number} @@ -45,6 +46,7 @@ func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.Od func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) } func TestReceiptsAccessLes3(t *testing.T) { testAccess(t, 3, tfReceiptsAccess) } +func TestReceiptsAccessLes4(t *testing.T) { testAccess(t, 4, tfReceiptsAccess) } func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { return &light.ReceiptsRequest{Hash: bhash, Number: number} @@ -52,6 +54,7 @@ func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) } func TestTrieEntryAccessLes3(t *testing.T) { testAccess(t, 3, tfTrieEntryAccess) } +func TestTrieEntryAccessLes4(t *testing.T) { testAccess(t, 4, tfTrieEntryAccess) } func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { @@ -62,6 +65,7 @@ func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) ligh func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) } func TestCodeAccessLes3(t *testing.T) { testAccess(t, 3, tfCodeAccess) } +func TestCodeAccessLes4(t *testing.T) { testAccess(t, 4, tfCodeAccess) } func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest { number := rawdb.ReadHeaderNumber(db, bhash) diff --git a/les/test_helper.go b/les/test_helper.go index e3f0616a88..1afc9be7d8 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -344,7 +345,8 @@ func newTestPeer(t *testing.T, name string, version int, handler *serverHandler, head = handler.blockchain.CurrentHeader() td = handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) ) - tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), testCostList(testCost)) + forkID := forkid.NewID(handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(testCost)) } return tp, errCh } @@ -402,7 +404,7 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl // handshake simulates a trivial handshake that expects the same state from the // remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, costList RequestCostList) { +func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList) { var expList keyValueList expList = expList.add("protocolVersion", uint64(p.cpeer.version)) expList = expList.add("networkId", uint64(NetworkId)) @@ -410,6 +412,9 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu expList = expList.add("headHash", head) expList = expList.add("headNum", headNum) expList = expList.add("genesisHash", genesis) + if p.cpeer.version >= lpv4 { + expList = expList.add("forkID", &forkID) + } sendList := make(keyValueList, len(expList)) copy(sendList, expList) expList = expList.add("serveHeaders", nil) @@ -417,6 +422,9 @@ func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNu expList = expList.add("serveStateSince", uint64(0)) expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4)) expList = expList.add("txRelay", nil) + if p.cpeer.version >= lpv4 { + expList = expList.add("recentTxLookup", uint64(0)) + } expList = expList.add("flowControl/BL", testBufLimit) expList = expList.add("flowControl/MRR", testBufRecharge) expList = expList.add("flowControl/MRC", costList) From 08c878acd235fdc908b3a7a3c43dfc9fc5e9b2ef Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 15 Feb 2021 19:37:09 +0100 Subject: [PATCH 319/948] cmd/utils: add workaround for FreeBSD statfs quirk (#22310) Make geth build on FreeBSD, fixes #22309. --- cmd/utils/diskusage.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go index a822118a39..da696de6bf 100644 --- a/cmd/utils/diskusage.go +++ b/cmd/utils/diskusage.go @@ -31,5 +31,12 @@ func getFreeDiskSpace(path string) (uint64, error) { } // Available blocks * size per block = available space in bytes - return stat.Bavail * uint64(stat.Bsize), nil + var bavail = stat.Bavail + if stat.Bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.Bsize), nil } From 77787802fe8f8415638480066ecace73037f1eed Mon Sep 17 00:00:00 2001 From: Alex Mazalov Date: Mon, 15 Feb 2021 18:47:47 +0000 Subject: [PATCH 320/948] cmd/geth: fix js unclean shutdown (#22302) --- cmd/geth/consolecmd.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 5369612e87..cc88b9eec4 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -19,10 +19,8 @@ package main import ( "fmt" "os" - "os/signal" "path/filepath" "strings" - "syscall" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" @@ -218,13 +216,10 @@ func ephemeralConsole(ctx *cli.Context) error { utils.Fatalf("Failed to execute %s: %v", file, err) } } - // Wait for pending callbacks, but stop for Ctrl-C. - abort := make(chan os.Signal, 1) - signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM) go func() { - <-abort - os.Exit(0) + stack.Wait() + console.Stop(false) }() console.Stop(true) From e991bdae2458dbee5a28addd188a897858aa34dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 16 Feb 2021 10:44:38 +0200 Subject: [PATCH 321/948] trie: fix bloom crash on fast sync restart --- trie/sync.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/trie/sync.go b/trie/sync.go index 05b9f55d33..dd8279b665 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -313,11 +313,15 @@ func (s *Sync) Commit(dbw ethdb.Batch) error { // Dump the membatch into a database dbw for key, value := range s.membatch.nodes { rawdb.WriteTrieNode(dbw, key, value) - s.bloom.Add(key[:]) + if s.bloom != nil { + s.bloom.Add(key[:]) + } } for key, value := range s.membatch.codes { rawdb.WriteCode(dbw, key, value) - s.bloom.Add(key[:]) + if s.bloom != nil { + s.bloom.Add(key[:]) + } } // Drop the membatch data and return s.membatch = newSyncMemBatch() From f4fcd4f506661c7cece755b90b8a84e51d5925ac Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Tue, 16 Feb 2021 10:40:59 +0100 Subject: [PATCH 322/948] rpc: increase the number of subscriptions in storm test (#22316) --- rpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/client_test.go b/rpc/client_test.go index 5b1f960352..5d301a07a7 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -427,7 +427,7 @@ func TestClientNotificationStorm(t *testing.T) { } doTest(8000, false) - doTest(23000, true) + doTest(24000, true) } func TestClientSetHeader(t *testing.T) { From 9ec3329899a0ff62ed2f83c61b50140881a577a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 16 Feb 2021 09:04:07 +0200 Subject: [PATCH 323/948] core/state/snapshot: ensure Cap retains a min number of layers --- core/state/snapshot/snapshot.go | 47 ++++-------- core/state/snapshot/snapshot_test.go | 111 ++++++++++++--------------- 2 files changed, 65 insertions(+), 93 deletions(-) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index df2b1ed330..aa5f5900b0 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -300,6 +300,12 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m // Cap traverses downwards the snapshot tree from a head block hash until the // number of allowed layers are crossed. All layers beyond the permitted number // are flattened downwards. +// +// Note, the final diff layer count in general will be one more than the amount +// requested. This happens because the bottom-most diff layer is the accumulator +// which may or may not overflow and cascade to disk. Since this last layer's +// survival is only known *after* capping, we need to omit it from the count if +// we want to ensure that *at least* the requested number of diff layers remain. func (t *Tree) Cap(root common.Hash, layers int) error { // Retrieve the head snapshot to cap from snap := t.Snapshot(root) @@ -324,10 +330,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // Flattening the bottom-most diff layer requires special casing since there's // no child to rewire to the grandparent. In that case we can fake a temporary // child for the capping and then remove it. - var persisted *diskLayer - - switch layers { - case 0: + if layers == 0 { // If full commit was requested, flatten the diffs and merge onto disk diff.lock.RLock() base := diffToDisk(diff.flatten().(*diffLayer)) @@ -336,33 +339,9 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // Replace the entire snapshot tree with the flat base t.layers = map[common.Hash]snapshot{base.root: base} return nil - - case 1: - // If full flattening was requested, flatten the diffs but only merge if the - // memory limit was reached - var ( - bottom *diffLayer - base *diskLayer - ) - diff.lock.RLock() - bottom = diff.flatten().(*diffLayer) - if bottom.memory >= aggregatorMemoryLimit { - base = diffToDisk(bottom) - } - diff.lock.RUnlock() - - // If all diff layers were removed, replace the entire snapshot tree - if base != nil { - t.layers = map[common.Hash]snapshot{base.root: base} - return nil - } - // Merge the new aggregated layer into the snapshot tree, clean stales below - t.layers[bottom.root] = bottom - - default: - // Many layers requested to be retained, cap normally - persisted = t.cap(diff, layers) } + persisted := t.cap(diff, layers) + // Remove any layer that is stale or links into a stale layer children := make(map[common.Hash][]common.Hash) for root, snap := range t.layers { @@ -405,9 +384,15 @@ func (t *Tree) Cap(root common.Hash, layers int) error { // layer limit is reached, memory cap is also enforced (but not before). // // The method returns the new disk layer if diffs were persisted into it. +// +// Note, the final diff layer count in general will be one more than the amount +// requested. This happens because the bottom-most diff layer is the accumulator +// which may or may not overflow and cascade to disk. Since this last layer's +// survival is only known *after* capping, we need to omit it from the count if +// we want to ensure that *at least* the requested number of diff layers remain. func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { // Dive until we run out of layers or reach the persistent database - for ; layers > 2; layers-- { + for i := 0; i < layers-1; i++ { // If we still have diff layers below, continue down if parent, ok := diff.parent.(*diffLayer); ok { diff = parent diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index a315fd216c..4b787cfe2e 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -162,57 +162,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) aggregatorMemoryLimit = 0 - if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil { - t.Fatalf("failed to merge diff layer onto disk: %v", err) - } - // Since the base layer was modified, ensure that data retrievald on the external reference fail - if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { - t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) - } - if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { - t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) - } - if n := len(snaps.layers); n != 2 { - t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) - fmt.Println(snaps.layers) - } -} - -// Tests that if a diff layer becomes stale, no active external references will -// be returned with junk data. This version of the test flattens every diff layer -// to check internal corner case around the bottom-most memory accumulator. -func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { - // Create an empty base layer and a snapshot tree out of it - base := &diskLayer{ - diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), - } - snaps := &Tree{ - layers: map[common.Hash]snapshot{ - base.root: base, - }, - } - // Commit two diffs on top and retrieve a reference to the bottommost - accounts := map[common.Hash][]byte{ - common.HexToHash("0xa1"): randomAccount(), - } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { - t.Fatalf("failed to create a diff layer: %v", err) - } - if n := len(snaps.layers); n != 3 { - t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) - } - ref := snaps.Snapshot(common.HexToHash("0x02")) - - // Flatten the diff layer into the bottom accumulator if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { - t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + t.Fatalf("failed to merge accumulator onto disk: %v", err) } - // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail + // Since the base layer was modified, ensure that data retrievald on the external reference fail if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) } @@ -267,7 +220,7 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { t.Errorf("layers modified, got %d exp %d", got, exp) } // Flatten the diff layer into the bottom accumulator - if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil { + if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) } // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail @@ -389,7 +342,7 @@ func TestSnaphots(t *testing.T) { // Create a starting base layer and a snapshot tree out of it base := &diskLayer{ diskdb: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), + root: makeRoot(1), cache: fastcache.New(1024 * 500), } snaps := &Tree{ @@ -397,17 +350,16 @@ func TestSnaphots(t *testing.T) { base.root: base, }, } - // Construct the snapshots with 128 layers + // Construct the snapshots with 129 layers, flattening whatever's above that var ( last = common.HexToHash("0x01") head common.Hash ) - // Flush another 128 layers, one diff will be flatten into the parent. - for i := 0; i < 128; i++ { + for i := 0; i < 129; i++ { head = makeRoot(uint64(i + 2)) snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) last = head - snaps.Cap(head, 128) // 129 layers(128 diffs + 1 disk) are allowed, 129th is the persistent layer + snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk) } var cases = []struct { headRoot common.Hash @@ -417,22 +369,57 @@ func TestSnaphots(t *testing.T) { expectBottom common.Hash }{ {head, 0, false, 0, common.Hash{}}, - {head, 64, false, 64, makeRoot(127 + 2 - 63)}, - {head, 128, false, 128, makeRoot(2)}, // All diff layers - {head, 129, true, 128, makeRoot(2)}, // All diff layers - {head, 129, false, 129, common.HexToHash("0x01")}, // All diff layers + disk layer + {head, 64, false, 64, makeRoot(129 + 2 - 64)}, + {head, 128, false, 128, makeRoot(3)}, // Normal diff layers, no accumulator + {head, 129, true, 129, makeRoot(2)}, // All diff layers, including accumulator + {head, 130, false, 130, makeRoot(1)}, // All diff layers + disk layer + } + for i, c := range cases { + layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) + if len(layers) != c.expected { + t.Errorf("non-overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) + } + if len(layers) == 0 { + continue + } + bottommost := layers[len(layers)-1] + if bottommost.Root() != c.expectBottom { + t.Errorf("non-overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) + } + } + // Above we've tested the normal capping, which leaves the accumulator live. + // Test that if the bottommost accumulator diff layer overflows the allowed + // memory limit, the snapshot tree gets capped to one less layer. + // Commit the diff layer onto the disk and ensure it's persisted + defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) + aggregatorMemoryLimit = 0 + + snaps.Cap(head, 128) // 129 (128 diffs + 1 overflown accumulator + 1 disk) + + cases = []struct { + headRoot common.Hash + limit int + nodisk bool + expected int + expectBottom common.Hash + }{ + {head, 0, false, 0, common.Hash{}}, + {head, 64, false, 64, makeRoot(129 + 2 - 64)}, + {head, 128, false, 128, makeRoot(3)}, // All diff layers, accumulator was flattened + {head, 129, true, 128, makeRoot(3)}, // All diff layers, accumulator was flattened + {head, 130, false, 129, makeRoot(2)}, // All diff layers + disk layer } - for _, c := range cases { + for i, c := range cases { layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) if len(layers) != c.expected { - t.Fatalf("Returned snapshot layers are mismatched, want %v, got %v", c.expected, len(layers)) + t.Errorf("overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) } if len(layers) == 0 { continue } bottommost := layers[len(layers)-1] if bottommost.Root() != c.expectBottom { - t.Fatalf("Snapshot mismatch, want %v, get %v", c.expectBottom, bottommost.Root()) + t.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) } } } From bfdff4c5b83cc09b2f91377f87e7757ddbe7fd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 16 Feb 2021 16:11:33 +0200 Subject: [PATCH 324/948] eth: fix snap sync cancellation --- eth/downloader/downloader.go | 3 +-- eth/protocols/snap/protocol.go | 1 - eth/protocols/snap/sync.go | 6 +++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 421803876e..416a387e31 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -346,7 +346,6 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode case nil, errBusy, errCanceled: return err } - if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) || errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) || errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) { @@ -1764,7 +1763,7 @@ func (d *Downloader) processFastSyncContent() error { }() closeOnErr := func(s *stateSync) { - if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled { + if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled && err != snap.ErrCancelled { d.queue.Close() // wake up Results } } diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index a74142cafb..5528e9212e 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -61,7 +61,6 @@ var ( errDecode = errors.New("invalid message") errInvalidMsgCode = errors.New("invalid message code") errBadRequest = errors.New("bad request") - errCancelled = errors.New("sync cancelled") ) // Packet represents a p2p message in the `snap` protocol. diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 422cdf8f72..c31e4a5dae 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -88,6 +88,10 @@ var ( requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? ) +// ErrCancelled is returned from snap syncing if the operation was prematurely +// terminated. +var ErrCancelled = errors.New("sync cancelled") + // accountRequest tracks a pending account range request to ensure responses are // to actual requests and to validate any security constraints. // @@ -615,7 +619,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { case id := <-peerDrop: s.revertRequests(id) case <-cancel: - return errCancelled + return ErrCancelled case req := <-s.accountReqFails: s.revertAccountRequest(req) From f9445e93bb72aedec953e65734ec18b4e1eaac3d Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 16 Feb 2021 15:23:03 +0100 Subject: [PATCH 325/948] cmd/devp2p/internal/ethtest: use shared message types (#22315) This updates the eth protocol test suite to use the message type definitions of the 'production' protocol implementation in eth/protocols/eth. --- cmd/devp2p/internal/ethtest/chain_test.go | 7 +- cmd/devp2p/internal/ethtest/suite.go | 19 +++-- cmd/devp2p/internal/ethtest/transaction.go | 4 +- cmd/devp2p/internal/ethtest/types.go | 90 +++++----------------- 4 files changed, 36 insertions(+), 84 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index ac3907ce8d..5e4289d80a 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -21,6 +21,7 @@ import ( "strconv" "testing" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/p2p" "github.com/stretchr/testify/assert" ) @@ -93,7 +94,7 @@ func TestChain_GetHeaders(t *testing.T) { }{ { req: GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Number: uint64(2), }, Amount: uint64(5), @@ -110,7 +111,7 @@ func TestChain_GetHeaders(t *testing.T) { }, { req: GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Number: uint64(chain.Len() - 1), }, Amount: uint64(3), @@ -125,7 +126,7 @@ func TestChain_GetHeaders(t *testing.T) { }, { req: GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Hash: chain.Head().Hash(), }, Amount: uint64(1), diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 5d0cdda720..edf7bb7e31 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -143,7 +144,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { // get block headers req := &GetBlockHeaders{ - Origin: hashOrNumber{ + Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), }, Amount: 2, @@ -157,8 +158,8 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *BlockHeaders: - headers := msg - for _, header := range *headers { + headers := *msg + for _, header := range headers { num := header.Number.Uint64() t.Logf("received header (%d): %s", num, pretty.Sdump(header)) assert.Equal(t, s.chain.blocks[int(num)].Header(), header) @@ -179,7 +180,10 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { conn.handshake(t) conn.statusExchange(t, s.chain, nil) // create block bodies request - req := &GetBlockBodies{s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash()} + req := &GetBlockBodies{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + } if err := conn.Write(req); err != nil { t.Fatalf("could not write to connection: %v", err) } @@ -357,10 +361,9 @@ func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBl "wrong TD in announcement", ) case *NewBlockHashes: - hashes := *msg - t.Logf("received NewBlockHashes message: %s", pretty.Sdump(hashes)) - assert.Equal(t, - blockAnnouncement.Block.Hash(), hashes[0].Hash, + message := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(message)) + assert.Equal(t, blockAnnouncement.Block.Hash(), message[0].Hash, "wrong block hash in announcement", ) default: diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index 4aaab8bf97..effcc3af29 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -32,7 +32,7 @@ func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn := s.setupConnection(t) t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) // Send the transaction - if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + if err := sendConn.Write(&Transactions{tx}); err != nil { t.Fatal(err) } time.Sleep(100 * time.Millisecond) @@ -70,7 +70,7 @@ func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { t.Logf("unexpected message, logging: %v", pretty.Sdump(msg)) } // Send the transaction - if err := sendConn.Write(Transactions([]*types.Transaction{tx})); err != nil { + if err := sendConn.Write(&Transactions{tx}); err != nil { t.Fatal(err) } // Wait for another transaction announcement diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index f768d61d5a..b901f50700 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -19,15 +19,12 @@ package ethtest import ( "crypto/ecdsa" "fmt" - "io" - "math/big" "reflect" "time" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/rlpx" @@ -81,102 +78,48 @@ type Pong struct{} func (p Pong) Code() int { return 0x03 } // Status is the network packet for the status message for eth/64 and later. -type Status struct { - ProtocolVersion uint32 - NetworkID uint64 - TD *big.Int - Head common.Hash - Genesis common.Hash - ForkID forkid.ID -} +type Status eth.StatusPacket func (s Status) Code() int { return 16 } // NewBlockHashes is the network packet for the block announcements. -type NewBlockHashes []struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced -} +type NewBlockHashes eth.NewBlockHashesPacket func (nbh NewBlockHashes) Code() int { return 17 } -type Transactions []*types.Transaction +type Transactions eth.TransactionsPacket func (t Transactions) Code() int { return 18 } // GetBlockHeaders represents a block header query. -type GetBlockHeaders struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} +type GetBlockHeaders eth.GetBlockHeadersPacket func (g GetBlockHeaders) Code() int { return 19 } -type BlockHeaders []*types.Header +type BlockHeaders eth.BlockHeadersPacket func (bh BlockHeaders) Code() int { return 20 } // GetBlockBodies represents a GetBlockBodies request -type GetBlockBodies []common.Hash +type GetBlockBodies eth.GetBlockBodiesPacket func (gbb GetBlockBodies) Code() int { return 21 } // BlockBodies is the network packet for block content distribution. -type BlockBodies []*types.Body +type BlockBodies eth.BlockBodiesPacket func (bb BlockBodies) Code() int { return 22 } // NewBlock is the network packet for the block propagation message. -type NewBlock struct { - Block *types.Block - TD *big.Int -} +type NewBlock eth.NewBlockPacket func (nb NewBlock) Code() int { return 23 } // NewPooledTransactionHashes is the network packet for the tx hash propagation message. -type NewPooledTransactionHashes [][32]byte +type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket func (nb NewPooledTransactionHashes) Code() int { return 24 } -// HashOrNumber is a combined field for specifying an origin block. -type hashOrNumber struct { - Hash common.Hash // Block hash from which to retrieve headers (excludes Number) - Number uint64 // Block hash from which to retrieve headers (excludes Hash) -} - -// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the -// two contained union fields. -func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { - if hn.Hash == (common.Hash{}) { - return rlp.Encode(w, hn.Number) - } - if hn.Number != 0 { - return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) - } - return rlp.Encode(w, hn.Hash) -} - -// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents -// into either a block hash or a block number. -func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } - } - return err -} - // Conn represents an individual connection with a peer type Conn struct { *rlpx.Conn @@ -221,7 +164,7 @@ func (c *Conn) Read() Message { default: return errorf("invalid message code: %d", code) } - + // if message is devp2p, decode here if err := rlp.DecodeBytes(rawData, msg); err != nil { return errorf("could not rlp decode message: %v", err) } @@ -256,7 +199,12 @@ func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message { } func (c *Conn) Write(msg Message) error { - payload, err := rlp.EncodeToBytes(msg) + // check if message is eth protocol message + var ( + payload []byte + err error + ) + payload, err = rlp.EncodeToBytes(msg) if err != nil { return err } @@ -363,7 +311,7 @@ loop: } } - if err := c.Write(*status); err != nil { + if err := c.Write(status); err != nil { t.Fatalf("could not write to connection: %v", err) } @@ -378,7 +326,7 @@ func (c *Conn) waitForBlock(block *types.Block) error { timeout := time.Now().Add(20 * time.Second) c.SetReadDeadline(timeout) for { - req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} + req := &GetBlockHeaders{Origin: eth.HashOrNumber{Hash: block.Hash()}, Amount: 1} if err := c.Write(req); err != nil { return err } From e01096f531862b982833732514376cead8d58e82 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 17 Feb 2021 14:59:00 +0100 Subject: [PATCH 326/948] eth/handler, broadcast: optimize tx broadcast mechanism (#22176) This PR optimizes the broadcast loop. Instead of iterating twice through a given set of transactions to weed out which peers have and which do not have a tx, to send/announce transactions, we do it only once. --- eth/handler.go | 56 ++++++++++++++++++---------------- eth/protocols/eth/broadcast.go | 12 ++++---- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index a5a62b894d..13fa701935 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -456,44 +456,51 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { } } -// BroadcastTransactions will propagate a batch of transactions to all peers which are not known to +// BroadcastTransactions will propagate a batch of transactions +// - To a square root of all peers +// - And, separately, as announcements to all peers which are not known to // already have the given transaction. -func (h *handler) BroadcastTransactions(txs types.Transactions, propagate bool) { +func (h *handler) BroadcastTransactions(txs types.Transactions) { var ( - txset = make(map[*ethPeer][]common.Hash) - annos = make(map[*ethPeer][]common.Hash) + annoCount int // Count of announcements made + annoPeers int + directCount int // Count of the txs sent directly to peers + directPeers int // Count of the peers that were sent transactions directly + + txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly + annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce + ) // Broadcast transactions to a batch of peers not knowing about it - if propagate { - for _, tx := range txs { - peers := h.peers.peersWithoutTransaction(tx.Hash()) - - // Send the block to a subset of our peers - transfer := peers[:int(math.Sqrt(float64(len(peers))))] - for _, peer := range transfer { - txset[peer] = append(txset[peer], tx.Hash()) - } - log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(transfer)) - } - for peer, hashes := range txset { - peer.AsyncSendTransactions(hashes) - } - return - } - // Otherwise only broadcast the announcement to peers for _, tx := range txs { peers := h.peers.peersWithoutTransaction(tx.Hash()) - for _, peer := range peers { + // Send the tx unconditionally to a subset of our peers + numDirect := int(math.Sqrt(float64(len(peers)))) + for _, peer := range peers[:numDirect] { + txset[peer] = append(txset[peer], tx.Hash()) + } + // For the remaining peers, send announcement only + for _, peer := range peers[numDirect:] { annos[peer] = append(annos[peer], tx.Hash()) } } + for peer, hashes := range txset { + directPeers++ + directCount += len(hashes) + peer.AsyncSendTransactions(hashes) + } for peer, hashes := range annos { + annoPeers++ + annoCount += len(hashes) if peer.Version() >= eth.ETH65 { peer.AsyncSendPooledTransactionHashes(hashes) } else { peer.AsyncSendTransactions(hashes) } } + log.Debug("Transaction broadcast", "txs", len(txs), + "announce packs", annoPeers, "announced hashes", annoCount, + "tx packs", directPeers, "broadcast txs", directCount) } // minedBroadcastLoop sends mined blocks to connected peers. @@ -511,13 +518,10 @@ func (h *handler) minedBroadcastLoop() { // txBroadcastLoop announces new transactions to connected peers. func (h *handler) txBroadcastLoop() { defer h.wg.Done() - for { select { case event := <-h.txsCh: - h.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers - h.BroadcastTransactions(event.Txs, false) // Only then announce to the rest - + h.BroadcastTransactions(event.Txs) case <-h.txsSub.Err(): return } diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go index 74ec2f0654..328396d510 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -142,18 +142,18 @@ func (p *Peer) announceTransactions() { if done == nil && len(queue) > 0 { // Pile transaction hashes until we reach our allowed network limit var ( - hashes []common.Hash + count int pending []common.Hash size common.StorageSize ) - for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { - if p.txpool.Get(queue[i]) != nil { - pending = append(pending, queue[i]) + for count = 0; count < len(queue) && size < maxTxPacketSize; count++ { + if p.txpool.Get(queue[count]) != nil { + pending = append(pending, queue[count]) size += common.HashLength } - hashes = append(hashes, queue[i]) } - queue = queue[:copy(queue, queue[len(hashes):])] + // Shift and trim queue + queue = queue[:copy(queue, queue[count:])] // If there's anything available to transfer, fire up an async writer if len(pending) > 0 { From 52e5c38aa5dcc01566bb6d05a5312b5b642899b4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 18 Feb 2021 09:05:47 +0100 Subject: [PATCH 327/948] core/state: copy the snap when copying the state (#22340) * core/state: copy the snap when copying the state * core/state: deep-copy snap stuff during state Copy --- core/state/statedb.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index 49f457a99f..8fd066e24f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -728,6 +728,31 @@ func (s *StateDB) Copy() *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } + if s.snaps != nil { + // In order for the miner to be able to use and make additions + // to the snapshot tree, we need to copy that aswell. + // Otherwise, any block mined by ourselves will cause gaps in the tree, + // and force the miner to operate trie-backed only + state.snaps = s.snaps + state.snap = s.snap + // deep copy needed + state.snapDestructs = make(map[common.Hash]struct{}) + for k, v := range s.snapDestructs { + state.snapDestructs[k] = v + } + state.snapAccounts = make(map[common.Hash][]byte) + for k, v := range s.snapAccounts { + state.snapAccounts[k] = v + } + state.snapStorage = make(map[common.Hash]map[common.Hash][]byte) + for k, v := range s.snapStorage { + temp := make(map[common.Hash][]byte) + for kk, vv := range v { + temp[kk] = vv + } + state.snapStorage[k] = temp + } + } return state } From 9ec32a9e7b2a39103c905d57e270d99463e6aa99 Mon Sep 17 00:00:00 2001 From: Or Neeman Date: Thu, 18 Feb 2021 03:19:49 -0600 Subject: [PATCH 328/948] rlp: handle case of normal EOF in Stream.readFull (#22336) io.Reader may return n > 0 and io.EOF at the end of the input stream. readFull did not handle this correctly, looking only at the error. This fixes it to check for n == len(buf) as well. --- rlp/decode.go | 8 +++++++- rlp/decode_test.go | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/rlp/decode.go b/rlp/decode.go index 5f3f5eedfd..79b7ef0626 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -952,7 +952,13 @@ func (s *Stream) readFull(buf []byte) (err error) { n += nn } if err == io.EOF { - err = io.ErrUnexpectedEOF + if n < len(buf) { + err = io.ErrUnexpectedEOF + } else { + // Readers are allowed to give EOF even though the read succeeded. + // In such cases, we discard the EOF, like io.ReadFull() does. + err = nil + } } return err } diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 167e9974b9..d94c3969b2 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -665,6 +665,26 @@ func TestDecodeWithByteReader(t *testing.T) { }) } +func testDecodeWithEncReader(t *testing.T, n int) { + s := strings.Repeat("0", n) + _, r, _ := EncodeToReader(s) + var decoded string + err := Decode(r, &decoded) + if err != nil { + t.Errorf("Unexpected decode error with n=%v: %v", n, err) + } + if decoded != s { + t.Errorf("Decode mismatch with n=%v", n) + } +} + +// This is a regression test checking that decoding from encReader +// works for RLP values of size 8192 bytes or more. +func TestDecodeWithEncReader(t *testing.T) { + testDecodeWithEncReader(t, 8188) // length with header is 8191 + testDecodeWithEncReader(t, 8189) // length with header is 8192 +} + // plainReader reads from a byte slice but does not // implement ReadByte. It is also not recognized by the // size validation. This is useful to test how the decoder From b1835b3855ebee0aa8c63d18b8f0671168ceced5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 18 Feb 2021 10:40:19 +0100 Subject: [PATCH 329/948] node: always show websocket url in logs (#22307) --- node/rpcstack.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index d693bb0bbd..56e23cc5c7 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -144,13 +144,15 @@ func (h *httpServer) start() error { h.listener = listener go h.server.Serve(listener) - // if server is websocket only, return after logging - if h.wsAllowed() && !h.rpcAllowed() { + if h.wsAllowed() { url := fmt.Sprintf("ws://%v", listener.Addr()) if h.wsConfig.prefix != "" { url += h.wsConfig.prefix } h.log.Info("WebSocket enabled", "url", url) + } + // if server is websocket only, return after logging + if !h.rpcAllowed() { return nil } // Log http endpoint. From 6ec15610443b28eabf665199f1dc5be2b3e3f7cb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 18 Feb 2021 17:54:29 +0100 Subject: [PATCH 330/948] eth: implement eth66 (#22241) * eth/protocols/eth: split up the eth protocol handlers * eth/protocols/eth: define eth-66 protocol messages * eth/protocols/eth: poc implement getblockheaders on eth/66 * eth/protocols/eth: implement remaining eth-66 handlers * eth/protocols: define handler map for eth 66 * eth/downloader: use protocol constants from eth package * eth/protocols/eth: add ETH66 capability * eth/downloader: tests for eth66 * eth/downloader: fix error in tests * eth/protocols/eth: use eth66 for outgoing requests * eth/protocols/eth: remove unused error type * eth/protocols/eth: define protocol length * eth/protocols/eth: fix pooled tx over eth66 * protocols/eth/handlers: revert behavioural change which caused tests to fail * eth/downloader: fix failing test * eth/protocols/eth: add testcases + fix flaw with header requests * eth/protocols: change comments * eth/protocols/eth: review fixes + fixed flaw in RequestOneHeader * eth/protocols: documentation * eth/protocols/eth: review concerns about types --- eth/downloader/downloader_test.go | 185 ++++++++--- eth/downloader/peer.go | 9 +- eth/protocols/eth/handler.go | 399 ++++------------------ eth/protocols/eth/handlers.go | 510 +++++++++++++++++++++++++++++ eth/protocols/eth/peer.go | 113 ++++++- eth/protocols/eth/protocol.go | 95 +++++- eth/protocols/eth/protocol_test.go | 200 +++++++++++ p2p/message.go | 4 + 8 files changed, 1120 insertions(+), 395 deletions(-) create mode 100644 eth/protocols/eth/handlers.go diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 5de1ef3f8e..2917116144 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -515,18 +515,18 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng } } -// Tests that simple synchronization against a canonical chain works correctly. -// In this test common ancestor lookup should be short circuited and not require -// binary searching. -func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } -func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } -func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonicalSynchronisation(t, 65, FullSync) } -func TestCanonicalSynchronisation65Fast(t *testing.T) { testCanonicalSynchronisation(t, 65, FastSync) } -func TestCanonicalSynchronisation65Light(t *testing.T) { - testCanonicalSynchronisation(t, 65, LightSync) -} +func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonSync(t, 64, FullSync) } +func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonSync(t, 64, FastSync) } + +func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonSync(t, 65, FullSync) } +func TestCanonicalSynchronisation65Fast(t *testing.T) { testCanonSync(t, 65, FastSync) } +func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonSync(t, 65, LightSync) } + +func TestCanonicalSynchronisation66Full(t *testing.T) { testCanonSync(t, 66, FullSync) } +func TestCanonicalSynchronisation66Fast(t *testing.T) { testCanonSync(t, 66, FastSync) } +func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, 66, LightSync) } -func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { +func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -547,9 +547,13 @@ func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { // until the cached blocks are retrieved. func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } + func TestThrottling65Full(t *testing.T) { testThrottling(t, 65, FullSync) } func TestThrottling65Fast(t *testing.T) { testThrottling(t, 65, FastSync) } +func TestThrottling66Full(t *testing.T) { testThrottling(t, 66, FullSync) } +func TestThrottling66Fast(t *testing.T) { testThrottling(t, 66, FastSync) } + func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -629,12 +633,17 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { // Tests that simple synchronization against a forked chain works correctly. In // this test common ancestor lookup should *not* be short circuited, and a full // binary search should be executed. -func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } -func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } +func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } +func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } + func TestForkedSync65Full(t *testing.T) { testForkedSync(t, 65, FullSync) } func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, 65, FastSync) } func TestForkedSync65Light(t *testing.T) { testForkedSync(t, 65, LightSync) } +func TestForkedSync66Full(t *testing.T) { testForkedSync(t, 66, FullSync) } +func TestForkedSync66Fast(t *testing.T) { testForkedSync(t, 66, FastSync) } +func TestForkedSync66Light(t *testing.T) { testForkedSync(t, 66, LightSync) } + func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -660,12 +669,17 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronising against a much shorter but much heavyer fork works // corrently and is not dropped. -func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } -func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } +func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } +func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } + func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, 65, FullSync) } func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, 65, FastSync) } func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, 65, LightSync) } +func TestHeavyForkedSync66Full(t *testing.T) { testHeavyForkedSync(t, 66, FullSync) } +func TestHeavyForkedSync66Fast(t *testing.T) { testHeavyForkedSync(t, 66, FastSync) } +func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, 66, LightSync) } + func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -693,12 +707,17 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head, ensuring that malicious peers cannot waste resources by feeding // long dead chains. -func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } -func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } +func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } +func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } + func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, 65, FullSync) } func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, 65, FastSync) } func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, 65, LightSync) } +func TestBoundedForkedSync66Full(t *testing.T) { testBoundedForkedSync(t, 66, FullSync) } +func TestBoundedForkedSync66Fast(t *testing.T) { testBoundedForkedSync(t, 66, FastSync) } +func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, 66, LightSync) } + func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -725,12 +744,17 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head for short but heavy forks too. These are a bit special because they // take different ancestor lookup paths. -func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } -func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } +func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } +func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } + func TestBoundedHeavyForkedSync65Full(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FullSync) } func TestBoundedHeavyForkedSync65Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FastSync) } func TestBoundedHeavyForkedSync65Light(t *testing.T) { testBoundedHeavyForkedSync(t, 65, LightSync) } +func TestBoundedHeavyForkedSync66Full(t *testing.T) { testBoundedHeavyForkedSync(t, 66, FullSync) } +func TestBoundedHeavyForkedSync66Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 66, FastSync) } +func TestBoundedHeavyForkedSync66Light(t *testing.T) { testBoundedHeavyForkedSync(t, 66, LightSync) } + func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() tester := newTester() @@ -775,12 +799,17 @@ func TestInactiveDownloader63(t *testing.T) { } // Tests that a canceled download wipes all previously accumulated state. -func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } -func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } +func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } +func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } + func TestCancel65Full(t *testing.T) { testCancel(t, 65, FullSync) } func TestCancel65Fast(t *testing.T) { testCancel(t, 65, FastSync) } func TestCancel65Light(t *testing.T) { testCancel(t, 65, LightSync) } +func TestCancel66Full(t *testing.T) { testCancel(t, 66, FullSync) } +func TestCancel66Fast(t *testing.T) { testCancel(t, 66, FastSync) } +func TestCancel66Light(t *testing.T) { testCancel(t, 66, LightSync) } + func testCancel(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -806,12 +835,17 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { } // Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } -func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } +func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } +func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } + func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, 65, FullSync) } func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, 65, FastSync) } func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, 65, LightSync) } +func TestMultiSynchronisation66Full(t *testing.T) { testMultiSynchronisation(t, 66, FullSync) } +func TestMultiSynchronisation66Fast(t *testing.T) { testMultiSynchronisation(t, 66, FastSync) } +func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, 66, LightSync) } + func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -834,12 +868,17 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronisations behave well in multi-version protocol environments // and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } -func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } +func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } +func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } + func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, 65, FullSync) } func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, 65, FastSync) } func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, 65, LightSync) } +func TestMultiProtoSynchronisation66Full(t *testing.T) { testMultiProtoSync(t, 66, FullSync) } +func TestMultiProtoSynchronisation66Fast(t *testing.T) { testMultiProtoSync(t, 66, FastSync) } +func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, 66, LightSync) } + func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -850,9 +889,9 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { chain := testChainBase.shorten(blockCacheMaxItems - 15) // Create peers of every type - tester.newPeer("peer 63", 63, chain) tester.newPeer("peer 64", 64, chain) tester.newPeer("peer 65", 65, chain) + tester.newPeer("peer 66", 66, chain) // Synchronise with the requested peer and make sure all blocks were retrieved if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { @@ -861,7 +900,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { assertOwnChain(t, tester, chain.len()) // Check that no peers have been dropped off - for _, version := range []int{63, 64, 65} { + for _, version := range []int{64, 65, 66} { peer := fmt.Sprintf("peer %d", version) if _, ok := tester.peers[peer]; !ok { t.Errorf("%s dropped", peer) @@ -871,12 +910,17 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that if a block is empty (e.g. header only), no body request should be // made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } -func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } +func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } +func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } + func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, 65, FullSync) } func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, 65, FastSync) } func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, 65, LightSync) } +func TestEmptyShortCircuit66Full(t *testing.T) { testEmptyShortCircuit(t, 66, FullSync) } +func TestEmptyShortCircuit66Fast(t *testing.T) { testEmptyShortCircuit(t, 66, FastSync) } +func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, 66, LightSync) } + func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -923,12 +967,17 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { // Tests that headers are enqueued continuously, preventing malicious nodes from // stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } -func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } +func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } +func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } + func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, 65, FullSync) } func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, 65, FastSync) } func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, 65, LightSync) } +func TestMissingHeaderAttack66Full(t *testing.T) { testMissingHeaderAttack(t, 66, FullSync) } +func TestMissingHeaderAttack66Fast(t *testing.T) { testMissingHeaderAttack(t, 66, FastSync) } +func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, 66, LightSync) } + func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -953,12 +1002,17 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that if requested headers are shifted (i.e. first is missing), the queue // detects the invalid numbering. -func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } -func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } +func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } +func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } + func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, 65, FullSync) } func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, 65, FastSync) } func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, 65, LightSync) } +func TestShiftedHeaderAttack66Full(t *testing.T) { testShiftedHeaderAttack(t, 66, FullSync) } +func TestShiftedHeaderAttack66Fast(t *testing.T) { testShiftedHeaderAttack(t, 66, FastSync) } +func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, 66, LightSync) } + func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -990,6 +1044,7 @@ func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // sure no state was corrupted. func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } +func TestInvalidHeaderRollback66Fast(t *testing.T) { testInvalidHeaderRollback(t, 66, FastSync) } func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1079,12 +1134,17 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { // Tests that a peer advertising a high TD doesn't get to stall the downloader // afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } -func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } +func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } +func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } + func TestHighTDStarvationAttack65Full(t *testing.T) { testHighTDStarvationAttack(t, 65, FullSync) } func TestHighTDStarvationAttack65Fast(t *testing.T) { testHighTDStarvationAttack(t, 65, FastSync) } func TestHighTDStarvationAttack65Light(t *testing.T) { testHighTDStarvationAttack(t, 65, LightSync) } +func TestHighTDStarvationAttack66Full(t *testing.T) { testHighTDStarvationAttack(t, 66, FullSync) } +func TestHighTDStarvationAttack66Fast(t *testing.T) { testHighTDStarvationAttack(t, 66, FastSync) } +func TestHighTDStarvationAttack66Light(t *testing.T) { testHighTDStarvationAttack(t, 66, LightSync) } + func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1101,6 +1161,7 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that misbehaving peers are disconnected, whilst behaving ones are not. func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, 65) } +func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, 66) } func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { t.Parallel() @@ -1152,12 +1213,17 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { // Tests that synchronisation progress (origin block number, current block number // and highest block number) is tracked and updated correctly. -func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } -func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } +func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } +func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } + func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, 65, FullSync) } func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, 65, FastSync) } func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, 65, LightSync) } +func TestSyncProgress66Full(t *testing.T) { testSyncProgress(t, 66, FullSync) } +func TestSyncProgress66Fast(t *testing.T) { testSyncProgress(t, 66, FastSync) } +func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, 66, LightSync) } + func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1234,12 +1300,17 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync // Tests that synchronisation progress (origin block number and highest block // number) is tracked and updated correctly in case of a fork (or manual head // revertal). -func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } -func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } +func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } +func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } + func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, 65, FullSync) } func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, 65, FastSync) } func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, 65, LightSync) } +func TestForkedSyncProgress66Full(t *testing.T) { testForkedSyncProgress(t, 66, FullSync) } +func TestForkedSyncProgress66Fast(t *testing.T) { testForkedSyncProgress(t, 66, FastSync) } +func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, 66, LightSync) } + func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1308,12 +1379,17 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if synchronisation is aborted due to some failure, then the progress // origin is not updated in the next sync cycle, as it should be considered the // continuation of the previous sync and not a new instance. -func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } -func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } +func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } +func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } + func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, 65, FullSync) } func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, 65, FastSync) } func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, 65, LightSync) } +func TestFailedSyncProgress66Full(t *testing.T) { testFailedSyncProgress(t, 66, FullSync) } +func TestFailedSyncProgress66Fast(t *testing.T) { testFailedSyncProgress(t, 66, FastSync) } +func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, 66, LightSync) } + func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1379,12 +1455,17 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if an attacker fakes a chain height, after the attack is detected, // the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } -func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } +func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } +func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } + func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, 65, FullSync) } func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, 65, FastSync) } func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, 65, LightSync) } +func TestFakedSyncProgress66Full(t *testing.T) { testFakedSyncProgress(t, 66, FullSync) } +func TestFakedSyncProgress66Fast(t *testing.T) { testFakedSyncProgress(t, 66, FastSync) } +func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, 66, LightSync) } + func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1454,12 +1535,17 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // This test reproduces an issue where unexpected deliveries would // block indefinitely if they arrived at the right time. -func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } -func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } +func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } +func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } + func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, 65, FullSync) } func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, 65, FastSync) } func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, 65, LightSync) } +func TestDeliverHeadersHang66Full(t *testing.T) { testDeliverHeadersHang(t, 66, FullSync) } +func TestDeliverHeadersHang66Fast(t *testing.T) { testDeliverHeadersHang(t, 66, FastSync) } +func TestDeliverHeadersHang66Light(t *testing.T) { testDeliverHeadersHang(t, 66, LightSync) } + func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1613,12 +1699,17 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { // Tests that peers below a pre-configured checkpoint block are prevented from // being fast-synced from, avoiding potential cheap eclipse attacks. -func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } -func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } +func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } +func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } + func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, 65, FullSync) } func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, 65, FastSync) } func TestCheckpointEnforcement65Light(t *testing.T) { testCheckpointEnforcement(t, 65, LightSync) } +func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, 66, FullSync) } +func TestCheckpointEnforcement66Fast(t *testing.T) { testCheckpointEnforcement(t, 66, FastSync) } +func TestCheckpointEnforcement66Light(t *testing.T) { testCheckpointEnforcement(t, 66, LightSync) } + func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index ba90bf31cb..7852569d8e 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -29,6 +29,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" ) @@ -457,7 +458,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.headerThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within @@ -471,7 +472,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.blockThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers @@ -485,7 +486,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.receiptThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle @@ -499,7 +500,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.stateThroughput } - return ps.idlePeers(64, 65, idle, throughput) + return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) } // idlePeers retrieves a flat list of all currently idle peers satisfying the diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index e32008fb41..64648ed419 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -17,19 +17,17 @@ package eth import ( - "encoding/json" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -166,6 +164,64 @@ func Handle(backend Backend, peer *Peer) error { } } +type msgHandler func(backend Backend, msg Decoder, peer *Peer) error +type Decoder interface { + Decode(val interface{}) error + Time() time.Time +} + +var eth64 = map[uint64]msgHandler{ + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetNodeDataMsg: handleGetNodeData, + NodeDataMsg: handleNodeData, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, +} +var eth65 = map[uint64]msgHandler{ + // old 64 messages + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetNodeDataMsg: handleGetNodeData, + NodeDataMsg: handleNodeData, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, + // New eth65 messages + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + GetPooledTransactionsMsg: handleGetPooledTransactions, + PooledTransactionsMsg: handlePooledTransactions, +} + +var eth66 = map[uint64]msgHandler{ + // eth64 announcement messages (no id) + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, + // eth65 announcement messages (no id) + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + // eth66 messages with request-id + GetBlockHeadersMsg: handleGetBlockHeaders66, + BlockHeadersMsg: handleBlockHeaders66, + GetBlockBodiesMsg: handleGetBlockBodies66, + BlockBodiesMsg: handleBlockBodies66, + GetNodeDataMsg: handleGetNodeData66, + NodeDataMsg: handleNodeData66, + GetReceiptsMsg: handleGetReceipts66, + ReceiptsMsg: handleReceipts66, + GetPooledTransactionsMsg: handleGetPooledTransactions66, + PooledTransactionsMsg: handlePooledTransactions66, +} + // handleMessage is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. func handleMessage(backend Backend, peer *Peer) error { @@ -179,334 +235,15 @@ func handleMessage(backend Backend, peer *Peer) error { } defer msg.Discard() - // Handle the message depending on its contents - switch { - case msg.Code == StatusMsg: - // Status messages should never arrive after the handshake - return fmt.Errorf("%w: uncontrolled status message", errExtraStatusMsg) - - // Block header query, collect the requested headers and reply - case msg.Code == GetBlockHeadersMsg: - // Decode the complex header query - var query GetBlockHeadersPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - lookups int - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && - len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { - lookups++ - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - first = false - origin = backend.Chain().GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = backend.Chain().GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = (query.Origin.Hash == common.Hash{}) - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") - peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := backend.Chain().GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - } - return peer.SendBlockHeaders(headers) - - case msg.Code == BlockHeadersMsg: - // A batch of headers arrived to one of our previous requests - res := new(BlockHeadersPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == GetBlockBodiesMsg: - // Decode the block body retrieval message - var query GetBlockBodiesPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather blocks until the fetch or network limits is reached - var ( - bytes int - bodies []rlp.RawValue - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || - lookups >= 2*maxBodiesServe { - break - } - if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 { - bodies = append(bodies, data) - bytes += len(data) - } - } - return peer.SendBlockBodiesRLP(bodies) - - case msg.Code == BlockBodiesMsg: - // A batch of block bodies arrived to one of our previous requests - res := new(BlockBodiesPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == GetNodeDataMsg: - // Decode the trie node data retrieval message - var query GetNodeDataPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - nodes [][]byte - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || - lookups >= 2*maxNodeDataServe { - break - } - // Retrieve the requested state entry - if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) { - // Only lookup the trie node if there's chance that we actually have it - continue - } - entry, err := backend.Chain().TrieNode(hash) - if len(entry) == 0 || err != nil { - // Read the contract code with prefix only to save unnecessary lookups. - entry, err = backend.Chain().ContractCodeWithPrefix(hash) - } - if err == nil && len(entry) > 0 { - nodes = append(nodes, entry) - bytes += len(entry) - } - } - return peer.SendNodeData(nodes) - - case msg.Code == NodeDataMsg: - // A batch of node state data arrived to one of our previous requests - res := new(NodeDataPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == GetReceiptsMsg: - // Decode the block receipts retrieval message - var query GetReceiptsPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - bytes int - receipts []rlp.RawValue - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || - lookups >= 2*maxReceiptsServe { - break - } - // Retrieve the requested block's receipts - results := backend.Chain().GetReceiptsByHash(hash) - if results == nil { - if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - return peer.SendReceiptsRLP(receipts) - - case msg.Code == ReceiptsMsg: - // A batch of receipts arrived to one of our previous requests - res := new(ReceiptsPacket) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return backend.Handle(peer, res) - - case msg.Code == NewBlockHashesMsg: - // A batch of new block announcements just arrived - ann := new(NewBlockHashesPacket) - if err := msg.Decode(ann); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Mark the hashes as present at the remote node - for _, block := range *ann { - peer.markBlock(block.Hash) - } - // Deliver them all to the backend for queuing - return backend.Handle(peer, ann) - - case msg.Code == NewBlockMsg: - // Retrieve and decode the propagated block - ann := new(NewBlockPacket) - if err := msg.Decode(ann); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { - log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() { - log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) - break // TODO(karalabe): return error eventually, but wait a few releases - } - if err := ann.sanityCheck(); err != nil { - return err - } - ann.Block.ReceivedAt = msg.ReceivedAt - ann.Block.ReceivedFrom = peer - - // Mark the peer as owning the block - peer.markBlock(ann.Block.Hash()) - - return backend.Handle(peer, ann) - - case msg.Code == NewPooledTransactionHashesMsg && peer.version >= ETH65: - // New transaction announcement arrived, make sure we have - // a valid and fresh chain to handle them - if !backend.AcceptTxs() { - break - } - ann := new(NewPooledTransactionHashesPacket) - if err := msg.Decode(ann); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Schedule all the unknown hashes for retrieval - for _, hash := range *ann { - peer.markTransaction(hash) - } - return backend.Handle(peer, ann) - - case msg.Code == GetPooledTransactionsMsg && peer.version >= ETH65: - // Decode the pooled transactions retrieval message - var query GetPooledTransactionsPacket - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - // Gather transactions until the fetch or network limits is reached - var ( - bytes int - hashes []common.Hash - txs []rlp.RawValue - ) - for _, hash := range query { - if bytes >= softResponseLimit { - break - } - // Retrieve the requested transaction, skipping if unknown to us - tx := backend.TxPool().Get(hash) - if tx == nil { - continue - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(tx); err != nil { - log.Error("Failed to encode transaction", "err", err) - } else { - hashes = append(hashes, hash) - txs = append(txs, encoded) - bytes += len(encoded) - } - } - return peer.SendPooledTransactionsRLP(hashes, txs) - - case msg.Code == TransactionsMsg || (msg.Code == PooledTransactionsMsg && peer.version >= ETH65): - // Transactions arrived, make sure we have a valid and fresh chain to handle them - if !backend.AcceptTxs() { - break - } - // Transactions can be processed, parse all of them and deliver to the pool - var txs []*types.Transaction - if err := msg.Decode(&txs); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - for i, tx := range txs { - // Validate and mark the remote transaction - if tx == nil { - return fmt.Errorf("%w: transaction %d is nil", errDecode, i) - } - peer.markTransaction(tx.Hash()) - } - if msg.Code == PooledTransactionsMsg { - return backend.Handle(peer, (*PooledTransactionsPacket)(&txs)) - } - return backend.Handle(peer, (*TransactionsPacket)(&txs)) + var handlers = eth64 + if peer.Version() == ETH65 { + handlers = eth65 + } else if peer.Version() >= ETH66 { + handlers = eth66 + } - default: - return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) + if handler := handlers[msg.Code]; handler != nil { + return handler(backend, msg, peer) } - return nil + return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code) } diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go new file mode 100644 index 0000000000..8433fa343a --- /dev/null +++ b/eth/protocols/eth/handlers.go @@ -0,0 +1,510 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// handleGetBlockHeaders handles Block header query, collect the requested headers and reply +func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { + // Decode the complex header query + var query GetBlockHeadersPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockHeadersQuery(backend, &query, peer) + return peer.SendBlockHeaders(response) +} + +// handleGetBlockHeaders66 is the eth/66 version of handleGetBlockHeaders +func handleGetBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the complex header query + var query GetBlockHeadersPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockHeadersQuery(backend, query.GetBlockHeadersPacket, peer) + return peer.ReplyBlockHeaders(query.RequestId, response) +} + +func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, peer *Peer) []*types.Header { + hashMode := query.Origin.Hash != (common.Hash{}) + first := true + maxNonCanonical := uint64(100) + + // Gather headers until the fetch or network limits is reached + var ( + bytes common.StorageSize + headers []*types.Header + unknown bool + lookups int + ) + for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && + len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe { + lookups++ + // Retrieve the next header satisfying the query + var origin *types.Header + if hashMode { + if first { + first = false + origin = backend.Chain().GetHeaderByHash(query.Origin.Hash) + if origin != nil { + query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number) + } + } else { + origin = backend.Chain().GetHeaderByNumber(query.Origin.Number) + } + if origin == nil { + break + } + headers = append(headers, origin) + bytes += estHeaderSize + + // Advance to the next header of the query + switch { + case hashMode && query.Reverse: + // Hash based traversal towards the genesis block + ancestor := query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) + unknown = (query.Origin.Hash == common.Hash{}) + } + case hashMode && !query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(peer.Peer.Info(), "", " ") + peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := backend.Chain().GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) + if expOldHash == query.Origin.Hash { + query.Origin.Hash, query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case query.Reverse: + // Number based traversal towards the genesis block + if query.Origin.Number >= query.Skip+1 { + query.Origin.Number -= query.Skip + 1 + } else { + unknown = true + } + + case !query.Reverse: + // Number based traversal towards the leaf block + query.Origin.Number += query.Skip + 1 + } + } + return headers +} + +func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block body retrieval message + var query GetBlockBodiesPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockBodiesQuery(backend, query, peer) + return peer.SendBlockBodiesRLP(response) +} + +func handleGetBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block body retrieval message + var query GetBlockBodiesPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetBlockBodiesQuery(backend, query.GetBlockBodiesPacket, peer) + return peer.ReplyBlockBodiesRLP(query.RequestId, response) +} + +func answerGetBlockBodiesQuery(backend Backend, query GetBlockBodiesPacket, peer *Peer) []rlp.RawValue { + // Gather blocks until the fetch or network limits is reached + var ( + bytes int + bodies []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe || + lookups >= 2*maxBodiesServe { + break + } + if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 { + bodies = append(bodies, data) + bytes += len(data) + } + } + return bodies +} + +func handleGetNodeData(backend Backend, msg Decoder, peer *Peer) error { + // Decode the trie node data retrieval message + var query GetNodeDataPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetNodeDataQuery(backend, query, peer) + return peer.SendNodeData(response) +} + +func handleGetNodeData66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the trie node data retrieval message + var query GetNodeDataPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetNodeDataQuery(backend, query.GetNodeDataPacket, peer) + return peer.ReplyNodeData(query.RequestId, response) +} + +func answerGetNodeDataQuery(backend Backend, query GetNodeDataPacket, peer *Peer) [][]byte { + // Gather state data until the fetch or network limits is reached + var ( + bytes int + nodes [][]byte + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || + lookups >= 2*maxNodeDataServe { + break + } + // Retrieve the requested state entry + if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) { + // Only lookup the trie node if there's chance that we actually have it + continue + } + entry, err := backend.Chain().TrieNode(hash) + if len(entry) == 0 || err != nil { + // Read the contract code with prefix only to save unnecessary lookups. + entry, err = backend.Chain().ContractCodeWithPrefix(hash) + } + if err == nil && len(entry) > 0 { + nodes = append(nodes, entry) + bytes += len(entry) + } + } + return nodes +} + +func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block receipts retrieval message + var query GetReceiptsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetReceiptsQuery(backend, query, peer) + return peer.SendReceiptsRLP(response) +} + +func handleGetReceipts66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block receipts retrieval message + var query GetReceiptsPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := answerGetReceiptsQuery(backend, query.GetReceiptsPacket, peer) + return peer.ReplyReceiptsRLP(query.RequestId, response) +} + +func answerGetReceiptsQuery(backend Backend, query GetReceiptsPacket, peer *Peer) []rlp.RawValue { + // Gather state data until the fetch or network limits is reached + var ( + bytes int + receipts []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || + lookups >= 2*maxReceiptsServe { + break + } + // Retrieve the requested block's receipts + results := backend.Chain().GetReceiptsByHash(hash) + if results == nil { + if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + continue + } + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(results); err != nil { + log.Error("Failed to encode receipt", "err", err) + } else { + receipts = append(receipts, encoded) + bytes += len(encoded) + } + } + return receipts +} + +func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error { + // A batch of new block announcements just arrived + ann := new(NewBlockHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Mark the hashes as present at the remote node + for _, block := range *ann { + peer.markBlock(block.Hash) + } + // Deliver them all to the backend for queuing + return backend.Handle(peer, ann) +} + +func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { + // Retrieve and decode the propagated block + ann := new(NewBlockPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { + log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) + return nil // TODO(karalabe): return error eventually, but wait a few releases + } + if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() { + log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) + return nil // TODO(karalabe): return error eventually, but wait a few releases + } + if err := ann.sanityCheck(); err != nil { + return err + } + ann.Block.ReceivedAt = msg.Time() + ann.Block.ReceivedFrom = peer + + // Mark the peer as owning the block + peer.markBlock(ann.Block.Hash()) + + return backend.Handle(peer, ann) +} + +func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { + // A batch of headers arrived to one of our previous requests + res := new(BlockHeadersPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of headers arrived to one of our previous requests + res := new(BlockHeadersPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.BlockHeadersPacket) +} + +func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { + // A batch of block bodies arrived to one of our previous requests + res := new(BlockBodiesPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of block bodies arrived to one of our previous requests + res := new(BlockBodiesPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.BlockBodiesPacket) +} + +func handleNodeData(backend Backend, msg Decoder, peer *Peer) error { + // A batch of node state data arrived to one of our previous requests + res := new(NodeDataPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of node state data arrived to one of our previous requests + res := new(NodeDataPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.NodeDataPacket) +} + +func handleReceipts(backend Backend, msg Decoder, peer *Peer) error { + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, res) +} + +func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error { + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket66) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + return backend.Handle(peer, &res.ReceiptsPacket) +} + +func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error { + // New transaction announcement arrived, make sure we have + // a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + ann := new(NewPooledTransactionHashesPacket) + if err := msg.Decode(ann); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + // Schedule all the unknown hashes for retrieval + for _, hash := range *ann { + peer.markTransaction(hash) + } + return backend.Handle(peer, ann) +} + +func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Decode the pooled transactions retrieval message + var query GetPooledTransactionsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + hashes, txs := answerGetPooledTransactions(backend, query, peer) + return peer.SendPooledTransactionsRLP(hashes, txs) +} + +func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) error { + // Decode the pooled transactions retrieval message + var query GetPooledTransactionsPacket66 + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsPacket, peer) + return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs) +} + +func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPacket, peer *Peer) ([]common.Hash, []rlp.RawValue) { + // Gather transactions until the fetch or network limits is reached + var ( + bytes int + hashes []common.Hash + txs []rlp.RawValue + ) + for _, hash := range query { + if bytes >= softResponseLimit { + break + } + // Retrieve the requested transaction, skipping if unknown to us + tx := backend.TxPool().Get(hash) + if tx == nil { + continue + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(tx); err != nil { + log.Error("Failed to encode transaction", "err", err) + } else { + hashes = append(hashes, hash) + txs = append(txs, encoded) + bytes += len(encoded) + } + } + return hashes, txs +} + +func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs TransactionsPacket + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs) +} + +func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error { + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs PooledTransactionsPacket + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs) +} + +func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error { + // Transactions arrived, make sure we have a valid and fresh chain to handle them + if !backend.AcceptTxs() { + return nil + } + // Transactions can be processed, parse all of them and deliver to the pool + var txs PooledTransactionsPacket66 + if err := msg.Decode(&txs); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + for i, tx := range txs.PooledTransactionsPacket { + // Validate and mark the remote transaction + if tx == nil { + return fmt.Errorf("%w: transaction %d is nil", errDecode, i) + } + peer.markTransaction(tx.Hash()) + } + return backend.Handle(peer, &txs.PooledTransactionsPacket) +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 735ef78ce7..709fca8655 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -18,6 +18,7 @@ package eth import ( "math/big" + "math/rand" "sync" mapset "github.com/deckarep/golang-set" @@ -267,6 +268,22 @@ func (p *Peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValu return p2p.Send(p.rw, PooledTransactionsMsg, txs) // Not packed into PooledTransactionsPacket to avoid RLP decoding } +// ReplyPooledTransactionsRLP is the eth/66 version of SendPooledTransactionsRLP. +func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs []rlp.RawValue) error { + // Mark all the transactions as known, but ensure we don't overflow our limits + for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) { + p.knownTxs.Pop() + } + for _, hash := range hashes { + p.knownTxs.Add(hash) + } + // Not packed into PooledTransactionsPacket to avoid RLP decoding + return p2p.Send(p.rw, PooledTransactionsMsg, PooledTransactionsRLPPacket66{ + RequestId: id, + PooledTransactionsRLPPacket: txs, + }) +} + // SendNewBlockHashes announces the availability of a number of blocks through // a hash notification. func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error { @@ -308,7 +325,10 @@ func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { p.knownBlocks.Pop() } p.knownBlocks.Add(block.Hash()) - return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{block, td}) + return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ + Block: block, + TD: td, + }) } // AsyncSendNewBlock queues an entire block for propagation to a remote peer. If @@ -331,9 +351,12 @@ func (p *Peer) SendBlockHeaders(headers []*types.Header) error { return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket(headers)) } -// SendBlockBodies sends a batch of block contents to the remote peer. -func (p *Peer) SendBlockBodies(bodies []*BlockBody) error { - return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesPacket(bodies)) +// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders. +func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error { + return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{ + RequestId: id, + BlockHeadersPacket: headers, + }) } // SendBlockBodiesRLP sends a batch of block contents to the remote peer from @@ -342,52 +365,98 @@ func (p *Peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error { return p2p.Send(p.rw, BlockBodiesMsg, bodies) // Not packed into BlockBodiesPacket to avoid RLP decoding } +// ReplyBlockBodiesRLP is the eth/66 version of SendBlockBodiesRLP. +func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error { + // Not packed into BlockBodiesPacket to avoid RLP decoding + return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesRLPPacket66{ + RequestId: id, + BlockBodiesRLPPacket: bodies, + }) +} + // SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the // hashes requested. func (p *Peer) SendNodeData(data [][]byte) error { return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket(data)) } +// ReplyNodeData is the eth/66 response to GetNodeData. +func (p *Peer) ReplyNodeData(id uint64, data [][]byte) error { + return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket66{ + RequestId: id, + NodeDataPacket: data, + }) +} + // SendReceiptsRLP sends a batch of transaction receipts, corresponding to the // ones requested from an already RLP encoded format. func (p *Peer) SendReceiptsRLP(receipts []rlp.RawValue) error { return p2p.Send(p.rw, ReceiptsMsg, receipts) // Not packed into ReceiptsPacket to avoid RLP decoding } +// ReplyReceiptsRLP is the eth/66 response to GetReceipts. +func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error { + return p2p.Send(p.rw, ReceiptsMsg, ReceiptsRLPPacket66{ + RequestId: id, + ReceiptsRLPPacket: receipts, + }) +} + // RequestOneHeader is a wrapper around the header query functions to fetch a // single header. It is used solely by the fetcher. func (p *Peer) RequestOneHeader(hash common.Hash) error { p.Log().Debug("Fetching single header", "hash", hash) - return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + query := GetBlockHeadersPacket{ Origin: HashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false, - }) + } + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ + RequestId: rand.Uint64(), + GetBlockHeadersPacket: &query, + }) + } + return p2p.Send(p.rw, GetBlockHeadersMsg, &query) } // RequestHeadersByHash fetches a batch of blocks' headers corresponding to the // specified header query, based on the hash of an origin block. func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + query := GetBlockHeadersPacket{ Origin: HashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse, - }) + } + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ + RequestId: rand.Uint64(), + GetBlockHeadersPacket: &query, + }) + } + return p2p.Send(p.rw, GetBlockHeadersMsg, &query) } // RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the // specified header query, based on the number of an origin block. func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + query := GetBlockHeadersPacket{ Origin: HashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse, - }) + } + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ + RequestId: rand.Uint64(), + GetBlockHeadersPacket: &query, + }) + } + return p2p.Send(p.rw, GetBlockHeadersMsg, &query) } // ExpectRequestHeadersByNumber is a testing method to mirror the recipient side @@ -406,6 +475,12 @@ func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, // specified. func (p *Peer) RequestBodies(hashes []common.Hash) error { p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetBlockBodiesMsg, &GetBlockBodiesPacket66{ + RequestId: rand.Uint64(), + GetBlockBodiesPacket: hashes, + }) + } return p2p.Send(p.rw, GetBlockBodiesMsg, GetBlockBodiesPacket(hashes)) } @@ -413,17 +488,35 @@ func (p *Peer) RequestBodies(hashes []common.Hash) error { // data, corresponding to the specified hashes. func (p *Peer) RequestNodeData(hashes []common.Hash) error { p.Log().Debug("Fetching batch of state data", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetNodeDataMsg, &GetNodeDataPacket66{ + RequestId: rand.Uint64(), + GetNodeDataPacket: hashes, + }) + } return p2p.Send(p.rw, GetNodeDataMsg, GetNodeDataPacket(hashes)) } // RequestReceipts fetches a batch of transaction receipts from a remote node. func (p *Peer) RequestReceipts(hashes []common.Hash) error { p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetReceiptsMsg, &GetReceiptsPacket66{ + RequestId: rand.Uint64(), + GetReceiptsPacket: hashes, + }) + } return p2p.Send(p.rw, GetReceiptsMsg, GetReceiptsPacket(hashes)) } // RequestTxs fetches a batch of transactions from a remote node. func (p *Peer) RequestTxs(hashes []common.Hash) error { p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) + if p.Version() >= ETH66 { + return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{ + RequestId: rand.Uint64(), + GetPooledTransactionsPacket: hashes, + }) + } return p2p.Send(p.rw, GetPooledTransactionsMsg, GetPooledTransactionsPacket(hashes)) } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 9fff64b72a..7f1832754f 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -32,6 +32,7 @@ import ( const ( ETH64 = 64 ETH65 = 65 + ETH66 = 66 ) // ProtocolName is the official short name of the `eth` protocol used during @@ -40,11 +41,11 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH65, ETH64} +var ProtocolVersions = []uint{ETH66, ETH65, ETH64} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{ETH65: 17, ETH64: 17} +var protocolLengths = map[uint]uint64{ETH66: 17, ETH65: 17, ETH64: 17} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 @@ -79,7 +80,6 @@ var ( errNetworkIDMismatch = errors.New("network ID mismatch") errGenesisMismatch = errors.New("genesis mismatch") errForkIDRejected = errors.New("fork ID rejected") - errExtraStatusMsg = errors.New("extra status message") ) // Packet represents a p2p message in the `eth` protocol. @@ -129,6 +129,12 @@ type GetBlockHeadersPacket struct { Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) } +// GetBlockHeadersPacket represents a block header query over eth/66 +type GetBlockHeadersPacket66 struct { + RequestId uint64 + *GetBlockHeadersPacket +} + // HashOrNumber is a combined field for specifying an origin block. type HashOrNumber struct { Hash common.Hash // Block hash from which to retrieve headers (excludes Number) @@ -168,6 +174,12 @@ func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { // BlockHeadersPacket represents a block header response. type BlockHeadersPacket []*types.Header +// BlockHeadersPacket represents a block header response over eth/66. +type BlockHeadersPacket66 struct { + RequestId uint64 + BlockHeadersPacket +} + // NewBlockPacket is the network packet for the block propagation message. type NewBlockPacket struct { Block *types.Block @@ -190,9 +202,32 @@ func (request *NewBlockPacket) sanityCheck() error { // GetBlockBodiesPacket represents a block body query. type GetBlockBodiesPacket []common.Hash +// GetBlockBodiesPacket represents a block body query over eth/66. +type GetBlockBodiesPacket66 struct { + RequestId uint64 + GetBlockBodiesPacket +} + // BlockBodiesPacket is the network packet for block content distribution. type BlockBodiesPacket []*BlockBody +// BlockBodiesPacket is the network packet for block content distribution over eth/66. +type BlockBodiesPacket66 struct { + RequestId uint64 + BlockBodiesPacket +} + +// BlockBodiesRLPPacket is used for replying to block body requests, in cases +// where we already have them RLP-encoded, and thus can avoid the decode-encode +// roundtrip. +type BlockBodiesRLPPacket []rlp.RawValue + +// BlockBodiesRLPPacket66 is the BlockBodiesRLPPacket over eth/66 +type BlockBodiesRLPPacket66 struct { + RequestId uint64 + BlockBodiesRLPPacket +} + // BlockBody represents the data content of a single block. type BlockBody struct { Transactions []*types.Transaction // Transactions contained within a block @@ -215,24 +250,78 @@ func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) // GetNodeDataPacket represents a trie node data query. type GetNodeDataPacket []common.Hash +// GetNodeDataPacket represents a trie node data query over eth/66. +type GetNodeDataPacket66 struct { + RequestId uint64 + GetNodeDataPacket +} + // NodeDataPacket is the network packet for trie node data distribution. type NodeDataPacket [][]byte +// NodeDataPacket is the network packet for trie node data distribution over eth/66. +type NodeDataPacket66 struct { + RequestId uint64 + NodeDataPacket +} + // GetReceiptsPacket represents a block receipts query. type GetReceiptsPacket []common.Hash +// GetReceiptsPacket represents a block receipts query over eth/66. +type GetReceiptsPacket66 struct { + RequestId uint64 + GetReceiptsPacket +} + // ReceiptsPacket is the network packet for block receipts distribution. type ReceiptsPacket [][]*types.Receipt +// ReceiptsPacket is the network packet for block receipts distribution over eth/66. +type ReceiptsPacket66 struct { + RequestId uint64 + ReceiptsPacket +} + +// ReceiptsRLPPacket is used for receipts, when we already have it encoded +type ReceiptsRLPPacket []rlp.RawValue + +// ReceiptsPacket66 is the eth-66 version of ReceiptsRLPPacket +type ReceiptsRLPPacket66 struct { + RequestId uint64 + ReceiptsRLPPacket +} + // NewPooledTransactionHashesPacket represents a transaction announcement packet. type NewPooledTransactionHashesPacket []common.Hash // GetPooledTransactionsPacket represents a transaction query. type GetPooledTransactionsPacket []common.Hash +type GetPooledTransactionsPacket66 struct { + RequestId uint64 + GetPooledTransactionsPacket +} + // PooledTransactionsPacket is the network packet for transaction distribution. type PooledTransactionsPacket []*types.Transaction +// PooledTransactionsPacket is the network packet for transaction distribution over eth/66. +type PooledTransactionsPacket66 struct { + RequestId uint64 + PooledTransactionsPacket +} + +// PooledTransactionsPacket is the network packet for transaction distribution, used +// in the cases we already have them in rlp-encoded form +type PooledTransactionsRLPPacket []rlp.RawValue + +// PooledTransactionsRLPPacket66 is the eth/66 form of PooledTransactionsRLPPacket +type PooledTransactionsRLPPacket66 struct { + RequestId uint64 + PooledTransactionsRLPPacket +} + func (*StatusPacket) Name() string { return "Status" } func (*StatusPacket) Kind() byte { return StatusMsg } diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go index 056ea56480..d92f3ea837 100644 --- a/eth/protocols/eth/protocol_test.go +++ b/eth/protocols/eth/protocol_test.go @@ -17,9 +17,12 @@ package eth import ( + "bytes" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" ) @@ -66,3 +69,200 @@ func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { } } } + +// TestEth66EmptyMessages tests encoding of empty eth66 messages +func TestEth66EmptyMessages(t *testing.T) { + // All empty messages encodes to the same format + want := common.FromHex("c4820457c0") + + for i, msg := range []interface{}{ + // Headers + GetBlockHeadersPacket66{1111, nil}, + BlockHeadersPacket66{1111, nil}, + // Bodies + GetBlockBodiesPacket66{1111, nil}, + BlockBodiesPacket66{1111, nil}, + BlockBodiesRLPPacket66{1111, nil}, + // Node data + GetNodeDataPacket66{1111, nil}, + NodeDataPacket66{1111, nil}, + // Receipts + GetReceiptsPacket66{1111, nil}, + ReceiptsPacket66{1111, nil}, + // Transactions + GetPooledTransactionsPacket66{1111, nil}, + PooledTransactionsPacket66{1111, nil}, + PooledTransactionsRLPPacket66{1111, nil}, + + // Headers + BlockHeadersPacket66{1111, BlockHeadersPacket([]*types.Header{})}, + // Bodies + GetBlockBodiesPacket66{1111, GetBlockBodiesPacket([]common.Hash{})}, + BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{})}, + BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{})}, + // Node data + GetNodeDataPacket66{1111, GetNodeDataPacket([]common.Hash{})}, + NodeDataPacket66{1111, NodeDataPacket([][]byte{})}, + // Receipts + GetReceiptsPacket66{1111, GetReceiptsPacket([]common.Hash{})}, + ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{})}, + // Transactions + GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket([]common.Hash{})}, + PooledTransactionsPacket66{1111, PooledTransactionsPacket([]*types.Transaction{})}, + PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket([]rlp.RawValue{})}, + } { + if have, _ := rlp.EncodeToBytes(msg); !bytes.Equal(have, want) { + t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, msg, have, want) + } + } + +} + +// TestEth66Messages tests the encoding of all redefined eth66 messages +func TestEth66Messages(t *testing.T) { + + // Some basic structs used during testing + var ( + header *types.Header + blockBody *BlockBody + blockBodyRlp rlp.RawValue + txs []*types.Transaction + txRlps []rlp.RawValue + hashes []common.Hash + receipts []*types.Receipt + receiptsRlp rlp.RawValue + + err error + ) + header = &types.Header{ + Difficulty: big.NewInt(2222), + Number: big.NewInt(3333), + GasLimit: 4444, + GasUsed: 5555, + Time: 6666, + Extra: []byte{0x77, 0x88}, + } + // Init the transactions, taken from a different test + { + for _, hexrlp := range []string{ + "f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", + "f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", + } { + var tx *types.Transaction + rlpdata := common.FromHex(hexrlp) + if err := rlp.DecodeBytes(rlpdata, &tx); err != nil { + t.Fatal(err) + } + txs = append(txs, tx) + txRlps = append(txRlps, rlpdata) + } + } + // init the block body data, both object and rlp form + blockBody = &BlockBody{ + Transactions: txs, + Uncles: []*types.Header{header}, + } + blockBodyRlp, err = rlp.EncodeToBytes(blockBody) + if err != nil { + t.Fatal(err) + } + + hashes = []common.Hash{ + common.HexToHash("deadc0de"), + common.HexToHash("feedbeef"), + } + byteSlices := [][]byte{ + common.FromHex("deadc0de"), + common.FromHex("feedbeef"), + } + // init the receipts + { + receipts = []*types.Receipt{ + &types.Receipt{ + Status: types.ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*types.Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + TxHash: hashes[0], + ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), + GasUsed: 111111, + }, + } + rlpData, err := rlp.EncodeToBytes(receipts) + if err != nil { + t.Fatal(err) + } + receiptsRlp = rlpData + } + + for i, tc := range []struct { + message interface{} + want []byte + }{ + { + GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{hashes[0], 0}, 5, 5, false}}, + common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"), + }, + { + GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}}, + common.FromHex("ca820457c682270f050580"), + }, + { + BlockHeadersPacket66{1111, BlockHeadersPacket{header}}, + common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { + GetBlockBodiesPacket66{1111, GetBlockBodiesPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{blockBody})}, + common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { // Identical to non-rlp-shortcut version + BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{blockBodyRlp})}, + common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), + }, + { + GetNodeDataPacket66{1111, GetNodeDataPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + NodeDataPacket66{1111, NodeDataPacket(byteSlices)}, + common.FromHex("ce820457ca84deadc0de84feedbeef"), + }, + { + GetReceiptsPacket66{1111, GetReceiptsPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{receipts})}, + common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + }, + { + ReceiptsRLPPacket66{1111, ReceiptsRLPPacket([]rlp.RawValue{receiptsRlp})}, + common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + }, + { + GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket(hashes)}, + common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), + }, + { + PooledTransactionsPacket66{1111, PooledTransactionsPacket(txs)}, + common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), + }, + { + PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket(txRlps)}, + common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), + }, + } { + if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) { + t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want) + } + } +} diff --git a/p2p/message.go b/p2p/message.go index 10b55a939c..bd048138c3 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -70,6 +70,10 @@ func (msg Msg) Discard() error { return err } +func (msg Msg) Time() time.Time { + return msg.ReceivedAt +} + type MsgReader interface { ReadMsg() (Msg, error) } From d36276d85e39f7a0071d3f5d948785e008ca1519 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 19 Feb 2021 09:54:46 +0100 Subject: [PATCH 331/948] p2p/dnsdisc: fix hot-spin when all trees are empty (#22313) In the random sync algorithm used by the DNS node iterator, we first pick a random tree and then perform one sync action on that tree. This happens in a loop until any node is found. If no trees contain any nodes, the iterator will enter a hot loop spinning at 100% CPU. The fix is complicated. The iterator now checks if a meaningful sync action can be performed on any tree. If there is nothing to do, it waits for the next root record recheck time to arrive and then tries again. Fixes #22306 --- p2p/dnsdisc/client.go | 82 ++++++++++++++++++++++++++++++++------ p2p/dnsdisc/client_test.go | 47 ++++++++++++++++++++++ p2p/dnsdisc/sync.go | 28 ++++++++++--- 3 files changed, 138 insertions(+), 19 deletions(-) diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index b872784828..d3e8111ab5 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -217,8 +217,11 @@ type randomIterator struct { c *Client mu sync.Mutex - trees map[string]*clientTree // all trees lc linkCache // tracks tree dependencies + trees map[string]*clientTree // all trees + // buffers for syncableTrees + syncableList []*clientTree + disabledList []*clientTree } func (c *Client) newRandomIterator() *randomIterator { @@ -238,10 +241,10 @@ func (it *randomIterator) Node() *enode.Node { // Close closes the iterator. func (it *randomIterator) Close() { + it.cancelFn() + it.mu.Lock() defer it.mu.Unlock() - - it.cancelFn() it.trees = nil } @@ -264,7 +267,7 @@ func (it *randomIterator) addTree(url string) error { // nextNode syncs random tree entries until it finds a node. func (it *randomIterator) nextNode() *enode.Node { for { - ct := it.nextTree() + ct := it.pickTree() if ct == nil { return nil } @@ -282,26 +285,79 @@ func (it *randomIterator) nextNode() *enode.Node { } } -// nextTree returns a random tree. -func (it *randomIterator) nextTree() *clientTree { +// pickTree returns a random tree to sync from. +func (it *randomIterator) pickTree() *clientTree { it.mu.Lock() defer it.mu.Unlock() + // Rebuild the trees map if any links have changed. if it.lc.changed { it.rebuildTrees() it.lc.changed = false } - if len(it.trees) == 0 { - return nil + + for { + canSync, trees := it.syncableTrees() + switch { + case canSync: + // Pick a random tree. + return trees[rand.Intn(len(trees))] + case len(trees) > 0: + // No sync action can be performed on any tree right now. The only meaningful + // thing to do is waiting for any root record to get updated. + if !it.waitForRootUpdates(trees) { + // Iterator was closed while waiting. + return nil + } + default: + // There are no trees left, the iterator was closed. + return nil + } } - limit := rand.Intn(len(it.trees)) +} + +// syncableTrees finds trees on which any meaningful sync action can be performed. +func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) { + // Resize tree lists. + it.syncableList = it.syncableList[:0] + it.disabledList = it.disabledList[:0] + + // Partition them into the two lists. for _, ct := range it.trees { - if limit == 0 { - return ct + if ct.canSyncRandom() { + it.syncableList = append(it.syncableList, ct) + } else { + it.disabledList = append(it.disabledList, ct) } - limit-- } - return nil + if len(it.syncableList) > 0 { + return true, it.syncableList + } + return false, it.disabledList +} + +// waitForRootUpdates waits for the closest scheduled root check time on the given trees. +func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool { + var minTree *clientTree + var nextCheck mclock.AbsTime + for _, ct := range trees { + check := ct.nextScheduledRootCheck() + if minTree == nil || check < nextCheck { + minTree = ct + nextCheck = check + } + } + + sleep := nextCheck.Sub(it.c.clock.Now()) + it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain) + timeout := it.c.clock.NewTimer(sleep) + defer timeout.Stop() + select { + case <-timeout.C(): + return true + case <-it.ctx.Done(): + return false // Iterator was closed. + } } // rebuildTrees rebuilds the 'trees' map. diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go index 6a6705abf2..741bee4230 100644 --- a/p2p/dnsdisc/client_test.go +++ b/p2p/dnsdisc/client_test.go @@ -231,6 +231,53 @@ func TestIteratorRootRecheckOnFail(t *testing.T) { checkIterator(t, it, nodes) } +// This test checks that the iterator works correctly when the tree is initially empty. +func TestIteratorEmptyTree(t *testing.T) { + var ( + clock = new(mclock.Simulated) + nodes = testNodes(nodesSeed1, 1) + resolver = newMapResolver() + c = NewClient(Config{ + Resolver: resolver, + Logger: testlog.Logger(t, log.LvlTrace), + RecheckInterval: 20 * time.Minute, + RateLimit: 500, + }) + ) + c.clock = clock + tree1, url := makeTestTree("n", nil, nil) + tree2, _ := makeTestTree("n", nodes, nil) + resolver.add(tree1.ToTXT("n")) + + // Start the iterator. + node := make(chan *enode.Node) + it, err := c.NewIterator(url) + if err != nil { + t.Fatal(err) + } + go func() { + it.Next() + node <- it.Node() + }() + + // Wait for the client to get stuck in waitForRootUpdates. + clock.WaitForTimers(1) + + // Now update the root. + resolver.add(tree2.ToTXT("n")) + + // Wait for it to pick up the root change. + clock.Run(c.cfg.RecheckInterval) + select { + case n := <-node: + if n.ID() != nodes[0].ID() { + t.Fatalf("wrong node returned") + } + case <-time.After(5 * time.Second): + t.Fatal("it.Next() did not unblock within 5s of real time") + } +} + // updateSomeNodes applies ENR updates to some of the given nodes. func updateSomeNodes(keySeed int64, nodes []*enode.Node) { keys := testKeys(nodesSeed1, len(nodes)) diff --git a/p2p/dnsdisc/sync.go b/p2p/dnsdisc/sync.go index 36f02acba6..073547c90d 100644 --- a/p2p/dnsdisc/sync.go +++ b/p2p/dnsdisc/sync.go @@ -25,9 +25,9 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" ) -const ( - rootRecheckFailCount = 5 // update root if this many leaf requests fail -) +// This is the number of consecutive leaf requests that may fail before +// we consider re-resolving the tree root. +const rootRecheckFailCount = 5 // clientTree is a full tree being synced. type clientTree struct { @@ -89,13 +89,22 @@ func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) ct.gcLinks() // Sync next random entry in ENR tree. Once every node has been visited, we simply - // start over. This is fine because entries are cached. + // start over. This is fine because entries are cached internally by the client LRU + // also by DNS resolvers. if ct.enrs.done() { ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false) } return ct.syncNextRandomENR(ctx) } +// canSyncRandom checks if any meaningful action can be performed by syncRandom. +func (ct *clientTree) canSyncRandom() bool { + // Note: the check for non-zero leaf count is very important here. + // If we're done syncing all nodes, and no leaves were found, the tree + // is empty and we can't use it for sync. + return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0 +} + // gcLinks removes outdated links from the global link cache. GC runs once // when the link sync finishes. func (ct *clientTree) gcLinks() { @@ -184,10 +193,14 @@ func (ct *clientTree) updateRoot(ctx context.Context) error { // rootUpdateDue returns true when a root update is needed. func (ct *clientTree) rootUpdateDue() bool { tooManyFailures := ct.leafFailCount > rootRecheckFailCount - scheduledCheck := ct.c.clock.Now().Sub(ct.lastRootCheck) > ct.c.cfg.RecheckInterval + scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck() return ct.root == nil || tooManyFailures || scheduledCheck } +func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime { + return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval) +} + // slowdownRootUpdate applies a delay to root resolution if is tried // too frequently. This avoids busy polling when the client is offline. // Returns true if the timeout passed, false if sync was canceled. @@ -218,10 +231,11 @@ type subtreeSync struct { root string missing []string // missing tree node hashes link bool // true if this sync is for the link tree + leaves int // counter of synced leaves } func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync { - return &subtreeSync{c, loc, root, []string{root}, link} + return &subtreeSync{c, loc, root, []string{root}, link, 0} } func (ts *subtreeSync) done() bool { @@ -253,10 +267,12 @@ func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, err if ts.link { return nil, errENRInLinkTree } + ts.leaves++ case *linkEntry: if !ts.link { return nil, errLinkInENRTree } + ts.leaves++ case *branchEntry: ts.missing = append(ts.missing, e.children...) } From c027507e036683f555f63baa4cd02a81696fea6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 19 Feb 2021 14:44:16 +0100 Subject: [PATCH 332/948] les: renamed lespay to vflux (#22347) --- les/api.go | 6 ++-- les/client.go | 16 +++++------ les/clientpool.go | 26 ++++++++--------- les/clientpool_test.go | 28 +++++++++---------- les/peer.go | 20 ++++++------- les/protocol.go | 14 +++++----- les/server.go | 8 +++--- les/server_handler.go | 4 +-- les/serverpool.go | 28 +++++++++---------- les/serverpool_test.go | 8 +++--- les/{lespay => vflux}/client/api.go | 2 +- les/{lespay => vflux}/client/fillset.go | 0 les/{lespay => vflux}/client/fillset_test.go | 0 les/{lespay => vflux}/client/queueiterator.go | 0 .../client/queueiterator_test.go | 0 les/{lespay => vflux}/client/requestbasket.go | 0 .../client/requestbasket_test.go | 0 les/{lespay => vflux}/client/timestats.go | 0 .../client/timestats_test.go | 0 les/{lespay => vflux}/client/valuetracker.go | 0 .../client/valuetracker_test.go | 0 les/{lespay => vflux}/client/wrsiterator.go | 0 .../client/wrsiterator_test.go | 0 les/{lespay => vflux}/server/balance.go | 0 les/{lespay => vflux}/server/balance_test.go | 0 .../server/balance_tracker.go | 0 les/{lespay => vflux}/server/clientdb.go | 0 les/{lespay => vflux}/server/clientdb_test.go | 0 les/{lespay => vflux}/server/prioritypool.go | 0 .../server/prioritypool_test.go | 0 30 files changed, 80 insertions(+), 80 deletions(-) rename les/{lespay => vflux}/client/api.go (98%) rename les/{lespay => vflux}/client/fillset.go (100%) rename les/{lespay => vflux}/client/fillset_test.go (100%) rename les/{lespay => vflux}/client/queueiterator.go (100%) rename les/{lespay => vflux}/client/queueiterator_test.go (100%) rename les/{lespay => vflux}/client/requestbasket.go (100%) rename les/{lespay => vflux}/client/requestbasket_test.go (100%) rename les/{lespay => vflux}/client/timestats.go (100%) rename les/{lespay => vflux}/client/timestats_test.go (100%) rename les/{lespay => vflux}/client/valuetracker.go (100%) rename les/{lespay => vflux}/client/valuetracker_test.go (100%) rename les/{lespay => vflux}/client/wrsiterator.go (100%) rename les/{lespay => vflux}/client/wrsiterator_test.go (100%) rename les/{lespay => vflux}/server/balance.go (100%) rename les/{lespay => vflux}/server/balance_test.go (100%) rename les/{lespay => vflux}/server/balance_tracker.go (100%) rename les/{lespay => vflux}/server/clientdb.go (100%) rename les/{lespay => vflux}/server/clientdb_test.go (100%) rename les/{lespay => vflux}/server/prioritypool.go (100%) rename les/{lespay => vflux}/server/prioritypool_test.go (100%) diff --git a/les/api.go b/les/api.go index 66d133b854..6491c4dcc4 100644 --- a/les/api.go +++ b/les/api.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/mclock" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -37,7 +37,7 @@ var ( // PrivateLightServerAPI provides an API to access the LES light server. type PrivateLightServerAPI struct { server *LesServer - defaultPosFactors, defaultNegFactors lps.PriceFactors + defaultPosFactors, defaultNegFactors vfs.PriceFactors } // NewPrivateLightServerAPI creates a new LES light server API. @@ -107,7 +107,7 @@ func (api *PrivateLightServerAPI) clientInfo(c *clientInfo) map[string]interface // setParams either sets the given parameters for a single connected client (if specified) // or the default parameters applicable to clients connected in the future -func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *lps.PriceFactors) (updateFactors bool, err error) { +func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) { defParams := client == nil for name, value := range params { errValue := func() error { diff --git a/les/client.go b/les/client.go index 1b26e9a9b5..9f0afc96c5 100644 --- a/les/client.go +++ b/les/client.go @@ -36,7 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -58,7 +58,7 @@ type LightEthereum struct { txPool *light.TxPool blockchain *light.LightChain serverPool *serverPool - valueTracker *lpc.ValueTracker + valueTracker *vfc.ValueTracker dialCandidates enode.Iterator pruner *pruner @@ -108,7 +108,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + valueTracker: vfc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, } @@ -193,18 +193,18 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { } // vtSubscription implements serverPeerSubscriber -type vtSubscription lpc.ValueTracker +type vtSubscription vfc.ValueTracker // registerPeer implements serverPeerSubscriber func (v *vtSubscription) registerPeer(p *serverPeer) { - vt := (*lpc.ValueTracker)(v) + vt := (*vfc.ValueTracker)(v) p.setValueTracker(vt, vt.Register(p.ID())) p.updateVtParams() } // unregisterPeer implements serverPeerSubscriber func (v *vtSubscription) unregisterPeer(p *serverPeer) { - vt := (*lpc.ValueTracker)(v) + vt := (*vfc.ValueTracker)(v) vt.Unregister(p.ID()) p.setValueTracker(nil, nil) } @@ -263,9 +263,9 @@ func (s *LightEthereum) APIs() []rpc.API { Service: NewPrivateLightAPI(&s.lesCommons), Public: false, }, { - Namespace: "lespay", + Namespace: "vflux", Version: "1.0", - Service: lpc.NewPrivateClientAPI(s.valueTracker), + Service: vfc.NewPrivateClientAPI(s.valueTracker), Public: false, }, }...) diff --git a/les/clientpool.go b/les/clientpool.go index da0db6e622..96c0f0f99e 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -23,8 +23,8 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" - lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/les/utils" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -64,17 +64,17 @@ const ( // and negative banalce. Boeth positive balance and negative balance will decrease // exponentially. If the balance is low enough, then the record will be dropped. type clientPool struct { - lps.BalanceTrackerSetup - lps.PriorityPoolSetup + vfs.BalanceTrackerSetup + vfs.PriorityPoolSetup lock sync.Mutex clock mclock.Clock closed bool removePeer func(enode.ID) ns *nodestate.NodeStateMachine - pp *lps.PriorityPool - bt *lps.BalanceTracker + pp *vfs.PriorityPool + bt *vfs.BalanceTracker - defaultPosFactors, defaultNegFactors lps.PriceFactors + defaultPosFactors, defaultNegFactors vfs.PriceFactors posExpTC, negExpTC uint64 minCap uint64 // The minimal capacity value allowed for any client connectedBias time.Duration @@ -101,7 +101,7 @@ type clientInfo struct { peer clientPoolPeer connected, priority bool connectedAt mclock.AbsTime - balance *lps.NodeBalance + balance *vfs.NodeBalance } // newClientPool creates a new client pool @@ -115,8 +115,8 @@ func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minC connectedBias: connectedBias, removePeer: removePeer, } - pool.bt = lps.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) - pool.pp = lps.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) + pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) + pool.pp = vfs.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) // set default expiration constants used by tests // Note: server overwrites this if token sale is active @@ -221,7 +221,7 @@ func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) { } f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, freeID) - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { f.disconnect(peer) return 0, nil } @@ -266,7 +266,7 @@ func (f *clientPool) disconnectNode(node *enode.Node) { } // setDefaultFactors sets the default price factors applied to subsequently connected clients -func (f *clientPool) setDefaultFactors(posFactors, negFactors lps.PriceFactors) { +func (f *clientPool) setDefaultFactors(posFactors, negFactors vfs.PriceFactors) { f.lock.Lock() defer f.lock.Unlock() @@ -305,7 +305,7 @@ func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint6 c = &clientInfo{node: node} f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, freeID) - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { log.Error("BalanceField is missing", "node", node.ID()) return 0, fmt.Errorf("BalanceField of %064x is missing", node.ID()) } @@ -371,7 +371,7 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { c = &clientInfo{node: node} f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, "") - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance != nil { + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance != nil { cb(c) } else { log.Error("BalanceField is missing") diff --git a/les/clientpool_test.go b/les/clientpool_test.go index b1c38d374c..5cff010409 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/rawdb" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" @@ -100,7 +100,7 @@ func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) { if temp { pool.ns.SetField(p.node, connAddressField, p.freeClientId()) } - n, _ := pool.ns.GetField(p.node, pool.BalanceField).(*lps.NodeBalance) + n, _ := pool.ns.GetField(p.node, pool.BalanceField).(*vfs.NodeBalance) pos, neg = n.GetBalance() if temp { pool.ns.SetField(p.node, connAddressField, nil) @@ -138,7 +138,7 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando pool.ns.Start() pool.setLimits(activeLimit, uint64(activeLimit)) - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // pool should accept new peers up to its connected limit for i := 0; i < activeLimit; i++ { @@ -243,7 +243,7 @@ func TestConnectPaidClient(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) @@ -259,7 +259,7 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) @@ -278,7 +278,7 @@ func TestConnectPaidClientToFullPool(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20)) @@ -309,7 +309,7 @@ func TestPaidClientKickedOut(t *testing.T) { pool.bt.SetExpirationTCs(0, 0) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance @@ -339,7 +339,7 @@ func TestConnectFreeClient(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) if cap, _ := pool.connect(newPoolTestPeer(0, nil)); cap == 0 { t.Fatalf("Failed to connect free client") } @@ -356,7 +356,7 @@ func TestConnectFreeClientToFullPool(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { pool.connect(newPoolTestPeer(i, nil)) @@ -386,7 +386,7 @@ func TestFreeClientKickedOut(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { pool.connect(newPoolTestPeer(i, kicked)) @@ -428,7 +428,7 @@ func TestPositiveBalanceCalculation(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3)) testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true) @@ -452,7 +452,7 @@ func TestDowngradePriorityClient(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) p := newPoolTestPeer(0, kicked) addBalance(pool, p.node.ID(), int64(time.Minute)) @@ -487,7 +487,7 @@ func TestNegativeBalanceCalculation(t *testing.T) { pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { pool.connect(newPoolTestPeer(i, nil)) @@ -564,7 +564,7 @@ func TestInactiveClient(t *testing.T) { if p2.cap != 0 { t.Fatalf("Failed to deactivate peer #2") } - pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) + pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) p4 := newPoolTestPeer(4, nil) addBalance(pool, p4.node.ID(), 1500*int64(time.Second)) // p1: 1000 p2: 500 p3: 2000 p4: 1500 diff --git a/les/peer.go b/les/peer.go index 0e2ed52c12..52ab506368 100644 --- a/les/peer.go +++ b/les/peer.go @@ -32,9 +32,9 @@ import ( "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/les/flowcontrol" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" - lps "github.com/ethereum/go-ethereum/les/lespay/server" "github.com/ethereum/go-ethereum/les/utils" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" @@ -349,8 +349,8 @@ type serverPeer struct { fcServer *flowcontrol.ServerNode // Client side mirror token bucket. vtLock sync.Mutex - valueTracker *lpc.ValueTracker - nodeValueTracker *lpc.NodeValueTracker + valueTracker *vfc.ValueTracker + nodeValueTracker *vfc.NodeValueTracker sentReqs map[uint64]sentReqEntry // Statistics @@ -676,7 +676,7 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter // setValueTracker sets the value tracker references for connected servers. Note that the // references should be removed upon disconnection by setValueTracker(nil, nil). -func (p *serverPeer) setValueTracker(vt *lpc.ValueTracker, nvt *lpc.NodeValueTracker) { +func (p *serverPeer) setValueTracker(vt *vfc.ValueTracker, nvt *vfc.NodeValueTracker) { p.vtLock.Lock() p.valueTracker = vt p.nodeValueTracker = nvt @@ -739,17 +739,17 @@ func (p *serverPeer) answeredRequest(id uint64) { return } var ( - vtReqs [2]lpc.ServedRequest + vtReqs [2]vfc.ServedRequest reqCount int ) m := requestMapping[e.reqType] if m.rest == -1 || e.amount <= 1 { reqCount = 1 - vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount} + vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount} } else { reqCount = 2 - vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: 1} - vtReqs[1] = lpc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} + vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: 1} + vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} } dt := time.Duration(mclock.Now() - e.at) vt.Served(nvt, vtReqs[:reqCount], dt) @@ -765,7 +765,7 @@ type clientPeer struct { responseLock sync.Mutex responseCount uint64 // Counter to generate an unique id for request processing. - balance *lps.NodeBalance + balance *vfs.NodeBalance // invalidLock is used for protecting invalidCount. invalidLock sync.RWMutex diff --git a/les/protocol.go b/les/protocol.go index 9eb6ec7471..909d25d375 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" ) @@ -89,7 +89,7 @@ type requestInfo struct { refBasketFirst, refBasketRest float64 } -// reqMapping maps an LES request to one or two lespay service vector entries. +// reqMapping maps an LES request to one or two vflux service vector entries. // If rest != -1 and the request type is used with amounts larger than one then the // first one of the multi-request is mapped to first while the rest is mapped to rest. type reqMapping struct { @@ -98,7 +98,7 @@ type reqMapping struct { var ( // requests describes the available LES request types and their initializing amounts - // in the lespay/client.ValueTracker reference basket. Initial values are estimates + // in the vfc.ValueTracker reference basket. Initial values are estimates // based on the same values as the server's default cost estimates (reqAvgTimeCost). requests = map[uint64]requestInfo{ GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch, 10, 1000}, @@ -110,25 +110,25 @@ var ( SendTxV2Msg: {"SendTxV2", MaxTxSend, 1, 0}, GetTxStatusMsg: {"GetTxStatus", MaxTxStatus, 10, 0}, } - requestList []lpc.RequestInfo + requestList []vfc.RequestInfo requestMapping map[uint32]reqMapping ) -// init creates a request list and mapping between protocol message codes and lespay +// init creates a request list and mapping between protocol message codes and vflux // service vector indices. func init() { requestMapping = make(map[uint32]reqMapping) for code, req := range requests { cost := reqAvgTimeCost[code] rm := reqMapping{len(requestList), -1} - requestList = append(requestList, lpc.RequestInfo{ + requestList = append(requestList, vfc.RequestInfo{ Name: req.name + ".first", InitAmount: req.refBasketFirst, InitValue: float64(cost.baseCost + cost.reqCost), }) if req.refBasketRest != 0 { rm.rest = len(requestList) - requestList = append(requestList, lpc.RequestInfo{ + requestList = append(requestList, vfc.RequestInfo{ Name: req.name + ".rest", InitAmount: req.refBasketRest, InitValue: float64(cost.reqCost), diff --git a/les/server.go b/les/server.go index 44495eb311..351a53f690 100644 --- a/les/server.go +++ b/les/server.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -43,8 +43,8 @@ var ( clientPeerField = serverSetup.NewField("clientPeer", reflect.TypeOf(&clientPeer{})) clientInfoField = serverSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{})) connAddressField = serverSetup.NewField("connAddr", reflect.TypeOf("")) - balanceTrackerSetup = lps.NewBalanceTrackerSetup(serverSetup) - priorityPoolSetup = lps.NewPriorityPoolSetup(serverSetup) + balanceTrackerSetup = vfs.NewBalanceTrackerSetup(serverSetup) + priorityPoolSetup = vfs.NewPriorityPoolSetup(serverSetup) ) func init() { @@ -137,7 +137,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) - srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) + srv.clientPool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() if !checkpoint.Empty() { diff --git a/les/server_handler.go b/les/server_handler.go index bec4206e2b..fd81e273c9 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - lps "github.com/ethereum/go-ethereum/les/lespay/server" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -169,7 +169,7 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool) return errFullClientPool } - p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance) + p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*vfs.NodeBalance) if p.balance == nil { return p2p.DiscRequested } diff --git a/les/serverpool.go b/les/serverpool.go index ac87acf6da..977579988e 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -26,8 +26,8 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/les/utils" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -58,23 +58,23 @@ type serverPool struct { db ethdb.KeyValueStore ns *nodestate.NodeStateMachine - vt *lpc.ValueTracker + vt *vfc.ValueTracker mixer *enode.FairMix mixSources []enode.Iterator dialIterator enode.Iterator validSchemes enr.IdentityScheme trustedURLs []string - fillSet *lpc.FillSet + fillSet *vfc.FillSet queryFails uint32 timeoutLock sync.RWMutex timeout time.Duration - timeWeights lpc.ResponseTimeWeights + timeWeights vfc.ResponseTimeWeights timeoutRefreshed mclock.AbsTime } // nodeHistory keeps track of dial costs which determine node weight together with the -// service value calculated by lpc.ValueTracker. +// service value calculated by vfc.ValueTracker. type nodeHistory struct { dialCost utils.ExpiredValue redialWaitStart, redialWaitEnd int64 // unix time (seconds) @@ -127,11 +127,11 @@ var ( }, ) sfiNodeWeight = serverPoolSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) - sfiConnectedStats = serverPoolSetup.NewField("connectedStats", reflect.TypeOf(lpc.ResponseTimeStats{})) + sfiConnectedStats = serverPoolSetup.NewField("connectedStats", reflect.TypeOf(vfc.ResponseTimeStats{})) ) // newServerPool creates a new server pool -func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { +func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *vfc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { s := &serverPool{ db: db, clock: clock, @@ -143,8 +143,8 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *lpc.ValueTracker, m } s.recalTimeout() s.mixer = enode.NewFairMix(mixTimeout) - knownSelector := lpc.NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) - alwaysConnect := lpc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) + knownSelector := vfc.NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) + alwaysConnect := vfc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) @@ -183,7 +183,7 @@ func (s *serverPool) addSource(source enode.Iterator) { // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { - s.fillSet = lpc.NewFillSet(s.ns, input, sfQueried) + s.fillSet = vfc.NewFillSet(s.ns, input, sfQueried) s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { if newState.Equals(sfQueried) { fails := atomic.LoadUint32(&s.queryFails) @@ -221,7 +221,7 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod }() } }) - return lpc.NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { + return vfc.NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { if waiting { s.fillSet.SetTarget(preNegLimit) } else { @@ -330,7 +330,7 @@ func (s *serverPool) recalTimeout() { s.timeoutLock.Lock() if s.timeout != timeout { s.timeout = timeout - s.timeWeights = lpc.TimeoutWeights(s.timeout) + s.timeWeights = vfc.TimeoutWeights(s.timeout) suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) @@ -349,7 +349,7 @@ func (s *serverPool) getTimeout() time.Duration { // getTimeoutAndWeight returns the recommended request timeout as well as the // response time weight which is necessary to calculate service value. -func (s *serverPool) getTimeoutAndWeight() (time.Duration, lpc.ResponseTimeWeights) { +func (s *serverPool) getTimeoutAndWeight() (time.Duration, vfc.ResponseTimeWeights) { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() @@ -381,7 +381,7 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl expFactor := s.vt.StatsExpFactor() totalValue = currentStats.Value(timeWeights, expFactor) - if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(lpc.ResponseTimeStats); ok { + if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(vfc.ResponseTimeStats); ok { diff := currentStats diff.SubStats(&connStats) sessionValue = diff.Value(timeWeights, expFactor) diff --git a/les/serverpool_test.go b/les/serverpool_test.go index 3b7ae65d5d..5c8ae56f6c 100644 --- a/les/serverpool_test.go +++ b/les/serverpool_test.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" - lpc "github.com/ethereum/go-ethereum/les/lespay/client" + vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -55,7 +55,7 @@ type serverPoolTest struct { clock *mclock.Simulated quit chan struct{} preNeg, preNegFail bool - vt *lpc.ValueTracker + vt *vfc.ValueTracker sp *serverPool input enode.Iterator testNodes []spTestNode @@ -144,7 +144,7 @@ func (s *serverPoolTest) start() { } } - s.vt = lpc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) + s.vt = vfc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted) s.sp.addSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting @@ -224,7 +224,7 @@ func (s *serverPoolTest) run() { n.peer = &serverPeer{peerCommons: peerCommons{Peer: p2p.NewPeer(id, "", nil)}} s.sp.registerPeer(n.peer) if n.service { - s.vt.Served(s.vt.GetNode(id), []lpc.ServedRequest{{ReqType: 0, Amount: 100}}, 0) + s.vt.Served(s.vt.GetNode(id), []vfc.ServedRequest{{ReqType: 0, Amount: 100}}, 0) } } } diff --git a/les/lespay/client/api.go b/les/vflux/client/api.go similarity index 98% rename from les/lespay/client/api.go rename to les/vflux/client/api.go index 5ad6ffd77e..135273ef96 100644 --- a/les/lespay/client/api.go +++ b/les/vflux/client/api.go @@ -24,7 +24,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" ) -// PrivateClientAPI implements the lespay client side API +// PrivateClientAPI implements the vflux client side API type PrivateClientAPI struct { vt *ValueTracker } diff --git a/les/lespay/client/fillset.go b/les/vflux/client/fillset.go similarity index 100% rename from les/lespay/client/fillset.go rename to les/vflux/client/fillset.go diff --git a/les/lespay/client/fillset_test.go b/les/vflux/client/fillset_test.go similarity index 100% rename from les/lespay/client/fillset_test.go rename to les/vflux/client/fillset_test.go diff --git a/les/lespay/client/queueiterator.go b/les/vflux/client/queueiterator.go similarity index 100% rename from les/lespay/client/queueiterator.go rename to les/vflux/client/queueiterator.go diff --git a/les/lespay/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go similarity index 100% rename from les/lespay/client/queueiterator_test.go rename to les/vflux/client/queueiterator_test.go diff --git a/les/lespay/client/requestbasket.go b/les/vflux/client/requestbasket.go similarity index 100% rename from les/lespay/client/requestbasket.go rename to les/vflux/client/requestbasket.go diff --git a/les/lespay/client/requestbasket_test.go b/les/vflux/client/requestbasket_test.go similarity index 100% rename from les/lespay/client/requestbasket_test.go rename to les/vflux/client/requestbasket_test.go diff --git a/les/lespay/client/timestats.go b/les/vflux/client/timestats.go similarity index 100% rename from les/lespay/client/timestats.go rename to les/vflux/client/timestats.go diff --git a/les/lespay/client/timestats_test.go b/les/vflux/client/timestats_test.go similarity index 100% rename from les/lespay/client/timestats_test.go rename to les/vflux/client/timestats_test.go diff --git a/les/lespay/client/valuetracker.go b/les/vflux/client/valuetracker.go similarity index 100% rename from les/lespay/client/valuetracker.go rename to les/vflux/client/valuetracker.go diff --git a/les/lespay/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go similarity index 100% rename from les/lespay/client/valuetracker_test.go rename to les/vflux/client/valuetracker_test.go diff --git a/les/lespay/client/wrsiterator.go b/les/vflux/client/wrsiterator.go similarity index 100% rename from les/lespay/client/wrsiterator.go rename to les/vflux/client/wrsiterator.go diff --git a/les/lespay/client/wrsiterator_test.go b/les/vflux/client/wrsiterator_test.go similarity index 100% rename from les/lespay/client/wrsiterator_test.go rename to les/vflux/client/wrsiterator_test.go diff --git a/les/lespay/server/balance.go b/les/vflux/server/balance.go similarity index 100% rename from les/lespay/server/balance.go rename to les/vflux/server/balance.go diff --git a/les/lespay/server/balance_test.go b/les/vflux/server/balance_test.go similarity index 100% rename from les/lespay/server/balance_test.go rename to les/vflux/server/balance_test.go diff --git a/les/lespay/server/balance_tracker.go b/les/vflux/server/balance_tracker.go similarity index 100% rename from les/lespay/server/balance_tracker.go rename to les/vflux/server/balance_tracker.go diff --git a/les/lespay/server/clientdb.go b/les/vflux/server/clientdb.go similarity index 100% rename from les/lespay/server/clientdb.go rename to les/vflux/server/clientdb.go diff --git a/les/lespay/server/clientdb_test.go b/les/vflux/server/clientdb_test.go similarity index 100% rename from les/lespay/server/clientdb_test.go rename to les/vflux/server/clientdb_test.go diff --git a/les/lespay/server/prioritypool.go b/les/vflux/server/prioritypool.go similarity index 100% rename from les/lespay/server/prioritypool.go rename to les/vflux/server/prioritypool.go diff --git a/les/lespay/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go similarity index 100% rename from les/lespay/server/prioritypool_test.go rename to les/vflux/server/prioritypool_test.go From ca76db6116b64bb10c83085a70898750668593d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 19 Feb 2021 15:53:05 +0200 Subject: [PATCH 333/948] cmd/utils: disable caching preimages by default --- cmd/utils/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0d7b0e1bf5..d065e02047 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -401,9 +401,9 @@ var ( Name: "cache.noprefetch", Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", } - CachePreimagesFlag = cli.BoolTFlag{ + CachePreimagesFlag = cli.BoolFlag{ Name: "cache.preimages", - Usage: "Enable recording the SHA3/keccak preimages of trie keys (default: true)", + Usage: "Enable recording the SHA3/keccak preimages of trie keys", } // Miner settings MiningEnabledFlag = cli.BoolFlag{ From c5023e1dc56f3ced0e3a24733e533bf962515844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 19 Feb 2021 16:03:17 +0200 Subject: [PATCH 334/948] travis, appveyor, build: bump Go to 1.16 --- .travis.yml | 22 +++++++++++----------- appveyor.yml | 4 ++-- build/checksums.txt | 24 ++++++++++++------------ build/ci.go | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1268c6d657..39a0456c3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ jobs: allow_failures: - stage: build os: osx - go: 1.14.x + go: 1.15.x env: - azure-osx - azure-ios @@ -16,7 +16,7 @@ jobs: - stage: lint os: linux dist: xenial - go: 1.15.x + go: 1.16.x env: - lint git: @@ -29,7 +29,7 @@ jobs: if: type = push os: linux dist: xenial - go: 1.15.x + go: 1.16.x env: - ubuntu-ppa - GO111MODULE=on @@ -54,7 +54,7 @@ jobs: os: linux dist: xenial sudo: required - go: 1.15.x + go: 1.16.x env: - azure-linux - GO111MODULE=on @@ -91,7 +91,7 @@ jobs: dist: xenial services: - docker - go: 1.15.x + go: 1.16.x env: - azure-linux-mips - GO111MODULE=on @@ -139,7 +139,7 @@ jobs: git: submodules: false # avoid cloning ethereum/tests before_install: - - curl https://dl.google.com/go/go1.15.5.linux-amd64.tar.gz | tar -xz + - curl https://dl.google.com/go/go1.16.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go @@ -157,7 +157,7 @@ jobs: - stage: build if: type = push os: osx - go: 1.15.x + go: 1.16.x env: - azure-osx - azure-ios @@ -189,7 +189,7 @@ jobs: os: linux arch: amd64 dist: xenial - go: 1.15.x + go: 1.16.x env: - GO111MODULE=on script: @@ -200,7 +200,7 @@ jobs: os: linux arch: arm64 dist: xenial - go: 1.15.x + go: 1.16.x env: - GO111MODULE=on script: @@ -209,7 +209,7 @@ jobs: - stage: build os: linux dist: xenial - go: 1.14.x + go: 1.15.x env: - GO111MODULE=on script: @@ -220,7 +220,7 @@ jobs: if: type = cron os: linux dist: xenial - go: 1.15.x + go: 1.16.x env: - azure-purge - GO111MODULE=on diff --git a/appveyor.yml b/appveyor.yml index 2bf67d4568..052280be15 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,8 +24,8 @@ environment: install: - git submodule update --init - rmdir C:\go /s /q - - appveyor DownloadFile https://dl.google.com/go/go1.15.5.windows-%GETH_ARCH%.zip - - 7z x go1.15.5.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://dl.google.com/go/go1.16.windows-%GETH_ARCH%.zip + - 7z x go1.16.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/build/checksums.txt b/build/checksums.txt index a7a6a657e9..d5bd4d0cd3 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,17 +1,17 @@ # This file contains sha256 checksums of optional build dependencies. -890bba73c5e2b19ffb1180e385ea225059eb008eb91b694875dd86ea48675817 go1.15.6.src.tar.gz -940a73b45993a3bae5792cf324140dded34af97c548af4864d22fd6d49f3bd9f go1.15.6.darwin-amd64.tar.gz -ad187f02158b9a9013ef03f41d14aa69c402477f178825a3940280814bcbb755 go1.15.6.linux-386.tar.gz -3918e6cc85e7eaaa6f859f1bdbaac772e7a825b0eb423c63d3ae68b21f84b844 go1.15.6.linux-amd64.tar.gz -f87515b9744154ffe31182da9341d0a61eb0795551173d242c8cad209239e492 go1.15.6.linux-arm64.tar.gz -40ba9a57764e374195018ef37c38a5fbac9bbce908eab436370631a84bfc5788 go1.15.6.linux-armv6l.tar.gz -5872eff6746a0a5f304272b27cbe9ce186f468454e95749cce01e903fbfc0e17 go1.15.6.windows-386.zip -b7b3808bb072c2bab73175009187fd5a7f20ffe0a31739937003a14c5c4d9006 go1.15.6.windows-amd64.zip -9d9dd5c217c1392f1b2ed5e03e1c71bf4cf8553884e57a38e68fd37fdcfe31a8 go1.15.6.freebsd-386.tar.gz -609f065d855aed5a0b40ef0245aacbcc0b4b7882dc3b1e75ae50576cf25265ee go1.15.6.freebsd-amd64.tar.gz -d4174fc217e749ac049eacc8827df879689f2246ac230d04991ae7df336f7cb2 go1.15.6.linux-ppc64le.tar.gz -839cc6b67687d8bb7cb044e4a9a2eac0c090765cc8ec55ffe714dfb7cd51cf3a go1.15.6.linux-s390x.tar.gz +7688063d55656105898f323d90a79a39c378d86fe89ae192eb3b7fc46347c95a go1.16.src.tar.gz +6000a9522975d116bf76044967d7e69e04e982e9625330d9a539a8b45395f9a8 go1.16.darwin-amd64.tar.gz +ea435a1ac6d497b03e367fdfb74b33e961d813883468080f6e239b3b03bea6aa go1.16.linux-386.tar.gz +013a489ebb3e24ef3d915abe5b94c3286c070dfe0818d5bca8108f1d6e8440d2 go1.16.linux-amd64.tar.gz +3770f7eb22d05e25fbee8fb53c2a4e897da043eb83c69b9a14f8d98562cd8098 go1.16.linux-arm64.tar.gz +d1d9404b1dbd77afa2bdc70934e10fbfcf7d785c372efc29462bb7d83d0a32fd go1.16.linux-armv6l.tar.gz +481492a17d42193d471b93b7a06da3555331bd833b76336afc87be820c48933f go1.16.windows-386.zip +5cc88fa506b3d5c453c54c3ea218fc8dd05d7362ae1de15bb67986b72089ce93 go1.16.windows-amd64.zip +d7d6c70b05a7c2f68b48aab5ab8cb5116b8444c9ddad131673b152e7cff7c726 go1.16.freebsd-386.tar.gz +40b03216f6945fb6883a50604fc7f409a83f62171607229a9c598e701e684f8a go1.16.freebsd-amd64.tar.gz +27a1aaa988e930b7932ce459c8a63ad5b3333b3a06b016d87ff289f2a11aacd6 go1.16.linux-ppc64le.tar.gz +be4c9e4e2cf058efc4e3eb013a760cb989ddc4362f111950c990d1c63b27ccbe go1.16.linux-s390x.tar.gz d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index 73d8961629..756fba8399 100644 --- a/build/ci.go +++ b/build/ci.go @@ -152,7 +152,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.15.6" + dlgoVersion = "1.16" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) From 8647233a8ec2a2410a078013ca12c38fdc229866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 19 Feb 2021 15:53:12 +0100 Subject: [PATCH 335/948] les: fix balance expiration (#22343) * les/lespay/server: fix balance expiration and add test * les: move client balances to a new db * les: rename lespayDb to lesDb --- les/client.go | 8 ++-- les/clientpool.go | 4 +- les/commons.go | 2 +- les/server.go | 8 +++- les/vflux/server/balance_test.go | 73 ++++++++++++++++++++++++++++- les/vflux/server/balance_tracker.go | 6 ++- 6 files changed, 92 insertions(+), 9 deletions(-) diff --git a/les/client.go b/les/client.go index 9f0afc96c5..d08c9feba5 100644 --- a/les/client.go +++ b/les/client.go @@ -81,7 +81,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - lespayDb, err := stack.OpenDatabase("lespay", 0, 0, "eth/db/lespay") + lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/les.client") if err != nil { return nil, err } @@ -99,6 +99,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { chainConfig: chainConfig, iConfig: light.DefaultClientIndexerConfig, chainDb: chainDb, + lesDb: lesDb, closeCh: make(chan struct{}), }, peers: peers, @@ -108,13 +109,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - valueTracker: vfc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + valueTracker: vfc.NewValueTracker(lesDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, } peers.subscribe((*vtSubscription)(leth.valueTracker)) - leth.serverPool = newServerPool(lespayDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) + leth.serverPool = newServerPool(lesDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) peers.subscribe(leth.serverPool) leth.dialCandidates = leth.serverPool.dialIterator @@ -331,6 +332,7 @@ func (s *LightEthereum) Stop() error { s.eventMux.Stop() rawdb.PopUncleanShutdownMarker(s.chainDb) s.chainDb.Close() + s.lesDb.Close() s.wg.Wait() log.Info("Light ethereum stopped") return nil diff --git a/les/clientpool.go b/les/clientpool.go index 96c0f0f99e..4e1499bf5d 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -105,7 +105,7 @@ type clientInfo struct { } // newClientPool creates a new client pool -func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { +func newClientPool(ns *nodestate.NodeStateMachine, lesDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { pool := &clientPool{ ns: ns, BalanceTrackerSetup: balanceTrackerSetup, @@ -115,7 +115,7 @@ func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minC connectedBias: connectedBias, removePeer: removePeer, } - pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lespayDb, clock, &utils.Expirer{}, &utils.Expirer{}) + pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lesDb, clock, &utils.Expirer{}, &utils.Expirer{}) pool.pp = vfs.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) // set default expiration constants used by tests diff --git a/les/commons.go b/les/commons.go index a2fce1dc97..d090fc21fc 100644 --- a/les/commons.go +++ b/les/commons.go @@ -51,7 +51,7 @@ type lesCommons struct { config *ethconfig.Config chainConfig *params.ChainConfig iConfig *light.IndexerConfig - chainDb ethdb.Database + chainDb, lesDb ethdb.Database chainReader chainReader chtIndexer, bloomTrieIndexer *core.ChainIndexer oracle *checkpointoracle.CheckpointOracle diff --git a/les/server.go b/les/server.go index 351a53f690..e34647f290 100644 --- a/les/server.go +++ b/les/server.go @@ -85,6 +85,10 @@ type LesServer struct { } func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { + lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/les.server") + if err != nil { + return nil, err + } ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) // Calculate the number of threads used to service the light client // requests based on the user-specified value. @@ -99,6 +103,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les chainConfig: e.BlockChain().Config(), iConfig: light.DefaultServerIndexerConfig, chainDb: e.ChainDb(), + lesDb: lesDb, chainReader: e.BlockChain(), chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true), bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true), @@ -136,7 +141,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les srv.maxCapacity = totalRecharge } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) - srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) + srv.clientPool = newClientPool(ns, lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) srv.clientPool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() @@ -222,6 +227,7 @@ func (s *LesServer) Stop() error { // Note, bloom trie indexer is closed by parent bloombits indexer. s.chtIndexer.Close() + s.lesDb.Close() s.wg.Wait() log.Info("Les server stopped") diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index 67e1944373..6c817aa26c 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -17,6 +17,7 @@ package server import ( + "math" "math/rand" "reflect" "testing" @@ -69,7 +70,9 @@ func (b *balanceTestSetup) newNode(capacity uint64) *NodeBalance { node := enode.SignNull(&enr.Record{}, enode.ID{}) b.ns.SetState(node, testFlag, nodestate.Flags{}, 0) b.ns.SetField(node, btTestSetup.connAddressField, "") - b.ns.SetField(node, ppTestSetup.CapacityField, capacity) + if capacity != 0 { + b.ns.SetField(node, ppTestSetup.CapacityField, capacity) + } n, _ := b.ns.GetField(node, btTestSetup.BalanceField).(*NodeBalance) return n } @@ -398,3 +401,71 @@ func TestCallback(t *testing.T) { case <-time.NewTimer(time.Millisecond * 100).C: } } + +func TestBalancePersistence(t *testing.T) { + clock := &mclock.Simulated{} + ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + db := memorydb.New() + posExp := &utils.Expirer{} + negExp := &utils.Expirer{} + posExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour*2)) // halves every two hours + negExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour)) // halves every hour + bt := NewBalanceTracker(ns, btTestSetup, db, clock, posExp, negExp) + ns.Start() + bts := &balanceTestSetup{ + clock: clock, + ns: ns, + bt: bt, + } + var nb *NodeBalance + exp := func(expPos, expNeg uint64) { + pos, neg := nb.GetBalance() + if pos != expPos { + t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) + } + if neg != expNeg { + t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) + } + } + expTotal := func(expTotal uint64) { + total := bt.TotalTokenAmount() + if total != expTotal { + t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total) + } + } + + expTotal(0) + nb = bts.newNode(0) + expTotal(0) + nb.SetBalance(16000000000, 16000000000) + exp(16000000000, 16000000000) + expTotal(16000000000) + clock.Run(time.Hour * 2) + exp(8000000000, 4000000000) + expTotal(8000000000) + bt.Stop() + ns.Stop() + + clock = &mclock.Simulated{} + ns = nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + posExp = &utils.Expirer{} + negExp = &utils.Expirer{} + posExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour*2)) // halves every two hours + negExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour)) // halves every hour + bt = NewBalanceTracker(ns, btTestSetup, db, clock, posExp, negExp) + ns.Start() + bts = &balanceTestSetup{ + clock: clock, + ns: ns, + bt: bt, + } + expTotal(8000000000) + nb = bts.newNode(0) + exp(8000000000, 4000000000) + expTotal(8000000000) + clock.Run(time.Hour * 2) + exp(4000000000, 1000000000) + expTotal(4000000000) + bt.Stop() + ns.Stop() +} diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go index edd4b288d9..1708019de4 100644 --- a/les/vflux/server/balance_tracker.go +++ b/les/vflux/server/balance_tracker.go @@ -99,6 +99,10 @@ func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), quit: make(chan struct{}), } + posOffset, negOffset := bt.ndb.getExpiration() + posExp.SetLogOffset(clock.Now(), posOffset) + negExp.SetLogOffset(clock.Now(), negOffset) + bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool { bt.inactive.AddExp(balance) return true @@ -177,7 +181,7 @@ func (bt *BalanceTracker) TotalTokenAmount() uint64 { bt.balanceTimer.Update(func(_ time.Duration) bool { bt.active = utils.ExpiredValue{} bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok { + if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok && n.active { pos, _ := n.GetRawBalance() bt.active.AddExp(pos) } From 8f03e3b107c0f7a39de31a9e7deb658431a937ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Sat, 20 Feb 2021 10:40:38 +0100 Subject: [PATCH 336/948] tests/fuzzers/les: add fuzzer for les server handler (#22282) * les: refactored server handler * tests/fuzzers/les: add fuzzer for les server handler * tests, les: update les fuzzer tests: update les fuzzer tests/fuzzer/les: release resources tests/fuzzer/les: pre-initialize all resources * les: refactored server handler and fuzzer Co-authored-by: rjl493456442 --- les/handler_test.go | 34 +- les/peer.go | 4 +- les/protocol.go | 65 ++- les/server_handler.go | 780 +++++--------------------------- les/server_requests.go | 569 +++++++++++++++++++++++ les/test_helper.go | 4 + oss-fuzz.sh | 1 + tests/fuzzers/les/debug/main.go | 41 ++ tests/fuzzers/les/les-fuzzer.go | 407 +++++++++++++++++ 9 files changed, 1218 insertions(+), 687 deletions(-) create mode 100644 les/server_requests.go create mode 100644 tests/fuzzers/les/debug/main.go create mode 100644 tests/fuzzers/les/les-fuzzer.go diff --git a/les/handler_test.go b/les/handler_test.go index 83be312081..e251f4503b 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -65,27 +65,27 @@ func testGetBlockHeaders(t *testing.T, protocol int) { // Create a batch of tests for various scenarios limit := uint64(MaxHeaderFetch) tests := []struct { - query *getBlockHeadersData // The query to execute for header retrieval + query *GetBlockHeadersData // The query to execute for header retrieval expect []common.Hash // The hashes of the block whose headers are expected }{ // A single random block should be retrievable by hash and number too { - &getBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, }, // Multiple headers should be retrievable in both directions { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 + 1).Hash(), bc.GetBlockByNumber(limit/2 + 2).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 - 1).Hash(), @@ -94,14 +94,14 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // Multiple headers with skip lists should be retrievable { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 + 4).Hash(), bc.GetBlockByNumber(limit/2 + 8).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(limit / 2).Hash(), bc.GetBlockByNumber(limit/2 - 4).Hash(), @@ -110,26 +110,26 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // The chain endpoints should be retrievable { - &getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, []common.Hash{bc.GetBlockByNumber(0).Hash()}, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1}, []common.Hash{bc.CurrentBlock().Hash()}, }, // Ensure protocol limits are honored //{ - // &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, + // &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true}, // []common.Hash{}, //}, // Check that requesting more than available is handled gracefully { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(), bc.GetBlockByNumber(bc.CurrentBlock().NumberU64()).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(4).Hash(), bc.GetBlockByNumber(0).Hash(), @@ -137,13 +137,13 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // Check that requesting more than available is handled gracefully, even if mid skip { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3}, []common.Hash{ bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(), bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1).Hash(), }, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, []common.Hash{ bc.GetBlockByNumber(4).Hash(), bc.GetBlockByNumber(1).Hash(), @@ -151,10 +151,10 @@ func testGetBlockHeaders(t *testing.T, protocol int) { }, // Check that non existing headers aren't returned { - &getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, []common.Hash{}, }, { - &getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1}, + &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1}, []common.Hash{}, }, } @@ -619,7 +619,7 @@ func TestStopResumeLes3(t *testing.T) { header := server.handler.blockchain.CurrentHeader() req := func() { reqID++ - sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) + sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) } for i := 1; i <= 5; i++ { // send requests while we still have enough buffer and expect a response diff --git a/les/peer.go b/les/peer.go index 52ab506368..479b4034bc 100644 --- a/les/peer.go +++ b/les/peer.go @@ -432,14 +432,14 @@ func (p *serverPeer) sendRequest(msgcode, reqID uint64, data interface{}, amount // specified header query, based on the hash of an origin block. func (p *serverPeer) requestHeadersByHash(reqID uint64, origin common.Hash, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) + return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) } // requestHeadersByNumber fetches a batch of blocks' headers corresponding to the // specified header query, based on the number of an origin block. func (p *serverPeer) requestHeadersByNumber(reqID, origin uint64, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) + return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) } // requestBodies fetches a batch of blocks' bodies corresponding to the hashes diff --git a/les/protocol.go b/les/protocol.go index 909d25d375..07a4452f40 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -24,6 +24,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/p2p/enode" @@ -83,6 +84,62 @@ const ( ResumeMsg = 0x17 ) +// GetBlockHeadersData represents a block header query (the request ID is not included) +type GetBlockHeadersData struct { + Origin hashOrNumber // Block from which to retrieve headers + Amount uint64 // Maximum number of headers to retrieve + Skip uint64 // Blocks to skip between consecutive headers + Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) +} + +// GetBlockHeadersPacket represents a block header request +type GetBlockHeadersPacket struct { + ReqID uint64 + Query GetBlockHeadersData +} + +// GetBlockBodiesPacket represents a block body request +type GetBlockBodiesPacket struct { + ReqID uint64 + Hashes []common.Hash +} + +// GetCodePacket represents a contract code request +type GetCodePacket struct { + ReqID uint64 + Reqs []CodeReq +} + +// GetReceiptsPacket represents a block receipts request +type GetReceiptsPacket struct { + ReqID uint64 + Hashes []common.Hash +} + +// GetProofsPacket represents a proof request +type GetProofsPacket struct { + ReqID uint64 + Reqs []ProofReq +} + +// GetHelperTrieProofsPacket represents a helper trie proof request +type GetHelperTrieProofsPacket struct { + ReqID uint64 + Reqs []HelperTrieReq +} + +// SendTxPacket represents a transaction propagation request +type SendTxPacket struct { + ReqID uint64 + Txs []*types.Transaction +} + +// GetTxStatusPacket represents a transaction status query +type GetTxStatusPacket struct { + ReqID uint64 + Hashes []common.Hash +} + type requestInfo struct { name string maxCount uint64 @@ -229,14 +286,6 @@ type blockInfo struct { Td *big.Int // Total difficulty of one particular block being announced } -// getBlockHeadersData represents a block header query. -type getBlockHeadersData struct { - Origin hashOrNumber // Block from which to retrieve headers - Amount uint64 // Maximum number of headers to retrieve - Skip uint64 // Blocks to skip between consecutive headers - Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) -} - // hashOrNumber is a combined field for specifying an origin block. type hashOrNumber struct { Hash common.Hash // Block hash from which to retrieve headers (excludes Number) diff --git a/les/server_handler.go b/les/server_handler.go index fd81e273c9..b6e8b050b1 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -18,8 +18,6 @@ package les import ( "crypto/ecdsa" - "encoding/binary" - "encoding/json" "errors" "sync" "sync/atomic" @@ -223,648 +221,109 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { } defer msg.Discard() - var ( - maxCost uint64 - task *servingTask - ) p.responseCount++ responseCount := p.responseCount - // accept returns an indicator whether the request can be served. - // If so, deduct the max cost from the flow control buffer. - accept := func(reqID, reqCnt, maxCnt uint64) bool { - // Short circuit if the peer is already frozen or the request is invalid. - inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) - if p.isFrozen() || reqCnt == 0 || reqCnt > maxCnt { - p.fcClient.OneTimeCost(inSizeCost) - return false - } - // Prepaid max cost units before request been serving. - maxCost = p.fcCosts.getMaxCost(msg.Code, reqCnt) - accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) - if !accepted { - p.freeze() - p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) - p.fcClient.OneTimeCost(inSizeCost) - return false - } - // Create a multi-stage task, estimate the time it takes for the task to - // execute, and cache it in the request service queue. - factor := h.server.costTracker.globalFactor() - if factor < 0.001 { - factor = 1 - p.Log().Error("Invalid global cost factor", "factor", factor) - } - maxTime := uint64(float64(maxCost) / factor) - task = h.server.servingQueue.newTask(p, maxTime, priority) - if task.start() { - return true - } - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) - return false - } - // sendResponse sends back the response and updates the flow control statistic. - sendResponse := func(reqID, amount uint64, reply *reply, servingTime uint64) { - p.responseLock.Lock() - defer p.responseLock.Unlock() - // Short circuit if the client is already frozen. - if p.isFrozen() { - realCost := h.server.costTracker.realCost(servingTime, msg.Size, 0) - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - return - } - // Positive correction buffer value with real cost. - var replySize uint32 - if reply != nil { - replySize = reply.size() - } - var realCost uint64 - if h.server.costTracker.testing { - realCost = maxCost // Assign a fake cost for testing purpose - } else { - realCost = h.server.costTracker.realCost(servingTime, msg.Size, replySize) - if realCost > maxCost { - realCost = maxCost - } - } - bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - if amount != 0 { - // Feed cost tracker request serving statistic. - h.server.costTracker.updateStats(msg.Code, amount, servingTime, realCost) - // Reduce priority "balance" for the specific peer. - p.balance.RequestServed(realCost) - } - if reply != nil { - p.queueSend(func() { - if err := reply.send(bv); err != nil { - select { - case p.errCh <- err: - default: - } - } - }) - } + req, ok := Les3[msg.Code] + if !ok { + p.Log().Trace("Received invalid message", "code", msg.Code) + clientErrorMeter.Mark(1) + return errResp(ErrInvalidMsgCode, "%v", msg.Code) } - switch msg.Code { - case GetBlockHeadersMsg: - p.Log().Trace("Received block header request") - if metrics.EnabledExpensive { - miscInHeaderPacketsMeter.Mark(1) - miscInHeaderTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Query getBlockHeadersData - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "%v: %v", msg, err) - } - query := req.Query - if accept(req.ReqID, query.Amount, MaxHeaderFetch) { - wg.Add(1) - go func() { - defer wg.Done() - hashMode := query.Origin.Hash != (common.Hash{}) - first := true - maxNonCanonical := uint64(100) - - // Gather headers until the fetch or network limits is reached - var ( - bytes common.StorageSize - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit { - if !first && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // Retrieve the next header satisfying the query - var origin *types.Header - if hashMode { - if first { - origin = h.blockchain.GetHeaderByHash(query.Origin.Hash) - if origin != nil { - query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = h.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number) - } - } else { - origin = h.blockchain.GetHeaderByNumber(query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderRlpSize - - // Advance to the next header of the query - switch { - case hashMode && query.Reverse: - // Hash based traversal towards the genesis block - ancestor := query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - query.Origin.Hash, query.Origin.Number = h.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical) - unknown = query.Origin.Hash == common.Hash{} - } - case hashMode && !query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + query.Skip + 1 - ) - if next <= current { - infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos) - unknown = true - } else { - if header := h.blockchain.GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := h.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical) - if expOldHash == query.Origin.Hash { - query.Origin.Hash, query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case query.Reverse: - // Number based traversal towards the genesis block - if query.Origin.Number >= query.Skip+1 { - query.Origin.Number -= query.Skip + 1 - } else { - unknown = true - } - - case !query.Reverse: - // Number based traversal towards the leaf block - query.Origin.Number += query.Skip + 1 - } - first = false - } - reply := p.replyBlockHeaders(req.ReqID, headers) - sendResponse(req.ReqID, query.Amount, reply, task.done()) - if metrics.EnabledExpensive { - miscOutHeaderPacketsMeter.Mark(1) - miscOutHeaderTrafficMeter.Mark(int64(reply.size())) - miscServingTimeHeaderTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetBlockBodiesMsg: - p.Log().Trace("Received block bodies request") - if metrics.EnabledExpensive { - miscInBodyPacketsMeter.Mark(1) - miscInBodyTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - var ( - bytes int - bodies []rlp.RawValue - ) - reqCnt := len(req.Hashes) - if accept(req.ReqID, uint64(reqCnt), MaxBodyFetch) { - wg.Add(1) - go func() { - defer wg.Done() - for i, hash := range req.Hashes { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if bytes >= softResponseLimit { - break - } - body := h.blockchain.GetBodyRLP(hash) - if body == nil { - p.bumpInvalid() - continue - } - bodies = append(bodies, body) - bytes += len(body) - } - reply := p.replyBlockBodiesRLP(req.ReqID, bodies) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutBodyPacketsMeter.Mark(1) - miscOutBodyTrafficMeter.Mark(int64(reply.size())) - miscServingTimeBodyTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetCodeMsg: - p.Log().Trace("Received code request") - if metrics.EnabledExpensive { - miscInCodePacketsMeter.Mark(1) - miscInCodeTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Reqs []CodeReq - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - var ( - bytes int - data [][]byte - ) - reqCnt := len(req.Reqs) - if accept(req.ReqID, uint64(reqCnt), MaxCodeFetch) { - wg.Add(1) - go func() { - defer wg.Done() - for i, request := range req.Reqs { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // Look up the root hash belonging to the request - header := h.blockchain.GetHeaderByHash(request.BHash) - if header == nil { - p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash) - p.bumpInvalid() - continue - } - // Refuse to search stale state data in the database since looking for - // a non-exist key is kind of expensive. - local := h.blockchain.CurrentHeader().Number.Uint64() - if !h.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local { - p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local) - p.bumpInvalid() - continue - } - triedb := h.blockchain.StateCache().TrieDB() - - account, err := h.getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) - if err != nil { - p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) - p.bumpInvalid() - continue - } - code, err := h.blockchain.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash)) - if err != nil { - p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err) - continue - } - // Accumulate the code and abort if enough data was retrieved - data = append(data, code) - if bytes += len(code); bytes >= softResponseLimit { - break - } - } - reply := p.replyCode(req.ReqID, data) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutCodePacketsMeter.Mark(1) - miscOutCodeTrafficMeter.Mark(int64(reply.size())) - miscServingTimeCodeTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetReceiptsMsg: - p.Log().Trace("Received receipts request") - if metrics.EnabledExpensive { - miscInReceiptPacketsMeter.Mark(1) - miscInReceiptTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - var ( - bytes int - receipts []rlp.RawValue - ) - reqCnt := len(req.Hashes) - if accept(req.ReqID, uint64(reqCnt), MaxReceiptFetch) { - wg.Add(1) - go func() { - defer wg.Done() - for i, hash := range req.Hashes { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if bytes >= softResponseLimit { - break - } - // Retrieve the requested block's receipts, skipping if unknown to us - results := h.blockchain.GetReceiptsByHash(hash) - if results == nil { - if header := h.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - p.bumpInvalid() - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - reply := p.replyReceiptsRLP(req.ReqID, receipts) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutReceiptPacketsMeter.Mark(1) - miscOutReceiptTrafficMeter.Mark(int64(reply.size())) - miscServingTimeReceiptTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetProofsV2Msg: - p.Log().Trace("Received les/2 proofs request") - if metrics.EnabledExpensive { - miscInTrieProofPacketsMeter.Mark(1) - miscInTrieProofTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Reqs []ProofReq - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - lastBHash common.Hash - root common.Hash - header *types.Header - ) - reqCnt := len(req.Reqs) - if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) { - wg.Add(1) - go func() { - defer wg.Done() - nodes := light.NewNodeSet() + p.Log().Trace("Received " + req.Name) - for i, request := range req.Reqs { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // Look up the root hash belonging to the request - if request.BHash != lastBHash { - root, lastBHash = common.Hash{}, request.BHash + serve, reqID, reqCnt, err := req.Handle(msg) + if err != nil { + clientErrorMeter.Mark(1) + return errResp(ErrDecode, "%v: %v", msg, err) + } - if header = h.blockchain.GetHeaderByHash(request.BHash); header == nil { - p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash) - p.bumpInvalid() - continue - } - // Refuse to search stale state data in the database since looking for - // a non-exist key is kind of expensive. - local := h.blockchain.CurrentHeader().Number.Uint64() - if !h.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local { - p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local) - p.bumpInvalid() - continue - } - root = header.Root - } - // If a header lookup failed (non existent), ignore subsequent requests for the same header - if root == (common.Hash{}) { - p.bumpInvalid() - continue - } - // Open the account or storage trie for the request - statedb := h.blockchain.StateCache() + if metrics.EnabledExpensive { + req.InPacketsMeter.Mark(1) + req.InTrafficMeter.Mark(int64(msg.Size)) + } - var trie state.Trie - switch len(request.AccKey) { - case 0: - // No account key specified, open an account trie - trie, err = statedb.OpenTrie(root) - if trie == nil || err != nil { - p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err) - continue - } - default: - // Account key specified, open a storage trie - account, err := h.getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey)) - if err != nil { - p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) - p.bumpInvalid() - continue - } - trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root) - if trie == nil || err != nil { - p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err) - continue - } - } - // Prove the user's request from the account or stroage trie - if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil { - p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err) - continue - } - if nodes.DataSize() >= softResponseLimit { - break - } - } - reply := p.replyProofsV2(req.ReqID, nodes.NodeList()) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutTrieProofPacketsMeter.Mark(1) - miscOutTrieProofTrafficMeter.Mark(int64(reply.size())) - miscServingTimeTrieProofTimer.Update(time.Duration(task.servingTime)) - } - }() - } + // Short circuit if the peer is already frozen or the request is invalid. + inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) + if p.isFrozen() || reqCnt == 0 || reqCnt > req.MaxCount { + p.fcClient.OneTimeCost(inSizeCost) + return nil + } + // Prepaid max cost units before request been serving. + maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) + accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) + if !accepted { + p.freeze() + p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) + p.fcClient.OneTimeCost(inSizeCost) + return nil + } + // Create a multi-stage task, estimate the time it takes for the task to + // execute, and cache it in the request service queue. + factor := h.server.costTracker.globalFactor() + if factor < 0.001 { + factor = 1 + p.Log().Error("Invalid global cost factor", "factor", factor) + } + maxTime := uint64(float64(maxCost) / factor) + task := h.server.servingQueue.newTask(p, maxTime, priority) + if task.start() { + wg.Add(1) + go func() { + defer wg.Done() + reply := serve(h, p, task.waitOrStop) + if reply != nil { + task.done() + } - case GetHelperTrieProofsMsg: - p.Log().Trace("Received helper trie proof request") - if metrics.EnabledExpensive { - miscInHelperTriePacketsMeter.Mark(1) - miscInHelperTrieTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Reqs []HelperTrieReq - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - // Gather state data until the fetch or network limits is reached - var ( - auxBytes int - auxData [][]byte - ) - reqCnt := len(req.Reqs) - if accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) { - wg.Add(1) - go func() { - defer wg.Done() - var ( - lastIdx uint64 - lastType uint - root common.Hash - auxTrie *trie.Trie - ) - nodes := light.NewNodeSet() - for i, request := range req.Reqs { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx { - auxTrie, lastType, lastIdx = nil, request.Type, request.TrieIdx + p.responseLock.Lock() + defer p.responseLock.Unlock() - var prefix string - if root, prefix = h.getHelperTrie(request.Type, request.TrieIdx); root != (common.Hash{}) { - auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) - } - } - if auxTrie == nil { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - // TODO(rjl493456442) short circuit if the proving is failed. - // The original client side code has a dirty hack to retrieve - // the headers with no valid proof. Keep the compatibility for - // legacy les protocol and drop this hack when the les2/3 are - // not supported. - err := auxTrie.Prove(request.Key, request.FromLevel, nodes) - if p.version >= lpv4 && err != nil { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return - } - if request.AuxReq == htAuxHeader { - data := h.getAuxiliaryHeaders(request) - auxData = append(auxData, data) - auxBytes += len(data) - } - if nodes.DataSize()+auxBytes >= softResponseLimit { - break - } - } - reply := p.replyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutHelperTriePacketsMeter.Mark(1) - miscOutHelperTrieTrafficMeter.Mark(int64(reply.size())) - miscServingTimeHelperTrieTimer.Update(time.Duration(task.servingTime)) + // Short circuit if the client is already frozen. + if p.isFrozen() { + realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + return + } + // Positive correction buffer value with real cost. + var replySize uint32 + if reply != nil { + replySize = reply.size() + } + var realCost uint64 + if h.server.costTracker.testing { + realCost = maxCost // Assign a fake cost for testing purpose + } else { + realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) + if realCost > maxCost { + realCost = maxCost } - }() - } - - case SendTxV2Msg: - p.Log().Trace("Received new transactions") - if metrics.EnabledExpensive { - miscInTxsPacketsMeter.Mark(1) - miscInTxsTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Txs []*types.Transaction - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(req.Txs) - if accept(req.ReqID, uint64(reqCnt), MaxTxSend) { - wg.Add(1) - go func() { - defer wg.Done() - stats := make([]light.TxStatus, len(req.Txs)) - for i, tx := range req.Txs { - if i != 0 && !task.waitOrStop() { - return - } - hash := tx.Hash() - stats[i] = h.txStatus(hash) - if stats[i].Status == core.TxStatusUnknown { - addFn := h.txpool.AddRemotes - // Add txs synchronously for testing purpose - if h.addTxsSync { - addFn = h.txpool.AddRemotesSync - } - if errs := addFn([]*types.Transaction{tx}); errs[0] != nil { - stats[i].Error = errs[0].Error() - continue + } + bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + if reply != nil { + // Feed cost tracker request serving statistic. + h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) + // Reduce priority "balance" for the specific peer. + p.balance.RequestServed(realCost) + p.queueSend(func() { + if err := reply.send(bv); err != nil { + select { + case p.errCh <- err: + default: } - stats[i] = h.txStatus(hash) - } - } - reply := p.replyTxStatus(req.ReqID, stats) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) - if metrics.EnabledExpensive { - miscOutTxsPacketsMeter.Mark(1) - miscOutTxsTrafficMeter.Mark(int64(reply.size())) - miscServingTimeTxTimer.Update(time.Duration(task.servingTime)) - } - }() - } - - case GetTxStatusMsg: - p.Log().Trace("Received transaction status query request") - if metrics.EnabledExpensive { - miscInTxStatusPacketsMeter.Mark(1) - miscInTxStatusTrafficMeter.Mark(int64(msg.Size)) - } - var req struct { - ReqID uint64 - Hashes []common.Hash - } - if err := msg.Decode(&req); err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - reqCnt := len(req.Hashes) - if accept(req.ReqID, uint64(reqCnt), MaxTxStatus) { - wg.Add(1) - go func() { - defer wg.Done() - stats := make([]light.TxStatus, len(req.Hashes)) - for i, hash := range req.Hashes { - if i != 0 && !task.waitOrStop() { - sendResponse(req.ReqID, 0, nil, task.servingTime) - return } - stats[i] = h.txStatus(hash) - } - reply := p.replyTxStatus(req.ReqID, stats) - sendResponse(req.ReqID, uint64(reqCnt), reply, task.done()) + }) if metrics.EnabledExpensive { - miscOutTxStatusPacketsMeter.Mark(1) - miscOutTxStatusTrafficMeter.Mark(int64(reply.size())) - miscServingTimeTxStatusTimer.Update(time.Duration(task.servingTime)) + req.OutPacketsMeter.Mark(1) + req.OutTrafficMeter.Mark(int64(replySize)) + req.ServingTimeMeter.Update(time.Duration(task.servingTime)) } - }() - } - - default: - p.Log().Trace("Received invalid message", "code", msg.Code) - clientErrorMeter.Mark(1) - return errResp(ErrInvalidMsgCode, "%v", msg.Code) + } + }() + } else { + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) } + // If the client has made too much invalid request(e.g. request a non-existent data), // reject them to prevent SPAM attack. if p.getInvalid() > maxRequestErrors { @@ -874,8 +333,28 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { return nil } +// BlockChain implements serverBackend +func (h *serverHandler) BlockChain() *core.BlockChain { + return h.blockchain +} + +// TxPool implements serverBackend +func (h *serverHandler) TxPool() *core.TxPool { + return h.txpool +} + +// ArchiveMode implements serverBackend +func (h *serverHandler) ArchiveMode() bool { + return h.server.archiveMode +} + +// AddTxsSync implements serverBackend +func (h *serverHandler) AddTxsSync() bool { + return h.addTxsSync +} + // getAccount retrieves an account from the state based on root. -func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) { +func getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) { trie, err := trie.New(root, triedb) if err != nil { return state.Account{}, err @@ -892,43 +371,24 @@ func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash } // getHelperTrie returns the post-processed trie root for the given trie ID and section index -func (h *serverHandler) getHelperTrie(typ uint, index uint64) (common.Hash, string) { +func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { + var ( + root common.Hash + prefix string + ) switch typ { case htCanonical: sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1) - return light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix + root, prefix = light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix case htBloomBits: sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1) - return light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix - } - return common.Hash{}, "" -} - -// getAuxiliaryHeaders returns requested auxiliary headers for the CHT request. -func (h *serverHandler) getAuxiliaryHeaders(req HelperTrieReq) []byte { - if req.Type == htCanonical && req.AuxReq == htAuxHeader && len(req.Key) == 8 { - blockNum := binary.BigEndian.Uint64(req.Key) - hash := rawdb.ReadCanonicalHash(h.chainDb, blockNum) - return rawdb.ReadHeaderRLP(h.chainDb, hash, blockNum) + root, prefix = light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix } - return nil -} - -// txStatus returns the status of a specified transaction. -func (h *serverHandler) txStatus(hash common.Hash) light.TxStatus { - var stat light.TxStatus - // Looking the transaction in txpool first. - stat.Status = h.txpool.Status([]common.Hash{hash})[0] - - // If the transaction is unknown to the pool, try looking it up locally. - if stat.Status == core.TxStatusUnknown { - lookup := h.blockchain.GetTransactionLookup(hash) - if lookup != nil { - stat.Status = core.TxStatusIncluded - stat.Lookup = lookup - } + if root == (common.Hash{}) { + return nil } - return stat + trie, _ := trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) + return trie } // broadcastLoop broadcasts new block information to all connected light diff --git a/les/server_requests.go b/les/server_requests.go new file mode 100644 index 0000000000..d4af8006f0 --- /dev/null +++ b/les/server_requests.go @@ -0,0 +1,569 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "encoding/binary" + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// serverBackend defines the backend functions needed for serving LES requests +type serverBackend interface { + ArchiveMode() bool + AddTxsSync() bool + BlockChain() *core.BlockChain + TxPool() *core.TxPool + GetHelperTrie(typ uint, index uint64) *trie.Trie +} + +// Decoder is implemented by the messages passed to the handler functions +type Decoder interface { + Decode(val interface{}) error +} + +// RequestType is a static struct that describes an LES request type and references +// its handler function. +type RequestType struct { + Name string + MaxCount uint64 + InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter + ServingTimeMeter metrics.Timer + Handle func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error) +} + +// serveRequestFn is returned by the request handler functions after decoding the request. +// This function does the actual request serving using the supplied backend. waitOrStop is +// called between serving individual request items and may block if the serving process +// needs to be throttled. If it returns false then the process is terminated. +// The reply is not sent by this function yet. The flow control feedback value is supplied +// by the protocol handler when calling the send function of the returned reply struct. +type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply + +// Les3 contains the request types supported by les/2 and les/3 +var Les3 = map[uint64]RequestType{ + GetBlockHeadersMsg: RequestType{ + Name: "block header request", + MaxCount: MaxHeaderFetch, + InPacketsMeter: miscInHeaderPacketsMeter, + InTrafficMeter: miscInHeaderTrafficMeter, + OutPacketsMeter: miscOutHeaderPacketsMeter, + OutTrafficMeter: miscOutHeaderTrafficMeter, + ServingTimeMeter: miscServingTimeHeaderTimer, + Handle: handleGetBlockHeaders, + }, + GetBlockBodiesMsg: RequestType{ + Name: "block bodies request", + MaxCount: MaxBodyFetch, + InPacketsMeter: miscInBodyPacketsMeter, + InTrafficMeter: miscInBodyTrafficMeter, + OutPacketsMeter: miscOutBodyPacketsMeter, + OutTrafficMeter: miscOutBodyTrafficMeter, + ServingTimeMeter: miscServingTimeBodyTimer, + Handle: handleGetBlockBodies, + }, + GetCodeMsg: RequestType{ + Name: "code request", + MaxCount: MaxCodeFetch, + InPacketsMeter: miscInCodePacketsMeter, + InTrafficMeter: miscInCodeTrafficMeter, + OutPacketsMeter: miscOutCodePacketsMeter, + OutTrafficMeter: miscOutCodeTrafficMeter, + ServingTimeMeter: miscServingTimeCodeTimer, + Handle: handleGetCode, + }, + GetReceiptsMsg: RequestType{ + Name: "receipts request", + MaxCount: MaxReceiptFetch, + InPacketsMeter: miscInReceiptPacketsMeter, + InTrafficMeter: miscInReceiptTrafficMeter, + OutPacketsMeter: miscOutReceiptPacketsMeter, + OutTrafficMeter: miscOutReceiptTrafficMeter, + ServingTimeMeter: miscServingTimeReceiptTimer, + Handle: handleGetReceipts, + }, + GetProofsV2Msg: RequestType{ + Name: "les/2 proofs request", + MaxCount: MaxProofsFetch, + InPacketsMeter: miscInTrieProofPacketsMeter, + InTrafficMeter: miscInTrieProofTrafficMeter, + OutPacketsMeter: miscOutTrieProofPacketsMeter, + OutTrafficMeter: miscOutTrieProofTrafficMeter, + ServingTimeMeter: miscServingTimeTrieProofTimer, + Handle: handleGetProofs, + }, + GetHelperTrieProofsMsg: RequestType{ + Name: "helper trie proof request", + MaxCount: MaxHelperTrieProofsFetch, + InPacketsMeter: miscInHelperTriePacketsMeter, + InTrafficMeter: miscInHelperTrieTrafficMeter, + OutPacketsMeter: miscOutHelperTriePacketsMeter, + OutTrafficMeter: miscOutHelperTrieTrafficMeter, + ServingTimeMeter: miscServingTimeHelperTrieTimer, + Handle: handleGetHelperTrieProofs, + }, + SendTxV2Msg: RequestType{ + Name: "new transactions", + MaxCount: MaxTxSend, + InPacketsMeter: miscInTxsPacketsMeter, + InTrafficMeter: miscInTxsTrafficMeter, + OutPacketsMeter: miscOutTxsPacketsMeter, + OutTrafficMeter: miscOutTxsTrafficMeter, + ServingTimeMeter: miscServingTimeTxTimer, + Handle: handleSendTx, + }, + GetTxStatusMsg: RequestType{ + Name: "transaction status query request", + MaxCount: MaxTxStatus, + InPacketsMeter: miscInTxStatusPacketsMeter, + InTrafficMeter: miscInTxStatusTrafficMeter, + OutPacketsMeter: miscOutTxStatusPacketsMeter, + OutTrafficMeter: miscOutTxStatusTrafficMeter, + ServingTimeMeter: miscServingTimeTxStatusTimer, + Handle: handleGetTxStatus, + }, +} + +// handleGetBlockHeaders handles a block header request +func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetBlockHeadersPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + // Gather headers until the fetch or network limits is reached + var ( + bc = backend.BlockChain() + hashMode = r.Query.Origin.Hash != (common.Hash{}) + first = true + maxNonCanonical = uint64(100) + bytes common.StorageSize + headers []*types.Header + unknown bool + ) + for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit { + if !first && !waitOrStop() { + return nil + } + // Retrieve the next header satisfying the r + var origin *types.Header + if hashMode { + if first { + origin = bc.GetHeaderByHash(r.Query.Origin.Hash) + if origin != nil { + r.Query.Origin.Number = origin.Number.Uint64() + } + } else { + origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number) + } + } else { + origin = bc.GetHeaderByNumber(r.Query.Origin.Number) + } + if origin == nil { + break + } + headers = append(headers, origin) + bytes += estHeaderRlpSize + + // Advance to the next header of the r + switch { + case hashMode && r.Query.Reverse: + // Hash based traversal towards the genesis block + ancestor := r.Query.Skip + 1 + if ancestor == 0 { + unknown = true + } else { + r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical) + unknown = r.Query.Origin.Hash == common.Hash{} + } + case hashMode && !r.Query.Reverse: + // Hash based traversal towards the leaf block + var ( + current = origin.Number.Uint64() + next = current + r.Query.Skip + 1 + ) + if next <= current { + infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") + p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", infos) + unknown = true + } else { + if header := bc.GetHeaderByNumber(next); header != nil { + nextHash := header.Hash() + expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical) + if expOldHash == r.Query.Origin.Hash { + r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next + } else { + unknown = true + } + } else { + unknown = true + } + } + case r.Query.Reverse: + // Number based traversal towards the genesis block + if r.Query.Origin.Number >= r.Query.Skip+1 { + r.Query.Origin.Number -= r.Query.Skip + 1 + } else { + unknown = true + } + + case !r.Query.Reverse: + // Number based traversal towards the leaf block + r.Query.Origin.Number += r.Query.Skip + 1 + } + first = false + } + return p.replyBlockHeaders(r.ReqID, headers) + }, r.ReqID, r.Query.Amount, nil +} + +// handleGetBlockBodies handles a block body request +func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetBlockBodiesPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + bytes int + bodies []rlp.RawValue + ) + bc := backend.BlockChain() + for i, hash := range r.Hashes { + if i != 0 && !waitOrStop() { + return nil + } + if bytes >= softResponseLimit { + break + } + body := bc.GetBodyRLP(hash) + if body == nil { + p.bumpInvalid() + continue + } + bodies = append(bodies, body) + bytes += len(body) + } + return p.replyBlockBodiesRLP(r.ReqID, bodies) + }, r.ReqID, uint64(len(r.Hashes)), nil +} + +// handleGetCode handles a contract code request +func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetCodePacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + bytes int + data [][]byte + ) + bc := backend.BlockChain() + for i, request := range r.Reqs { + if i != 0 && !waitOrStop() { + return nil + } + // Look up the root hash belonging to the request + header := bc.GetHeaderByHash(request.BHash) + if header == nil { + p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash) + p.bumpInvalid() + continue + } + // Refuse to search stale state data in the database since looking for + // a non-exist key is kind of expensive. + local := bc.CurrentHeader().Number.Uint64() + if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local { + p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local) + p.bumpInvalid() + continue + } + triedb := bc.StateCache().TrieDB() + + account, err := getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) + if err != nil { + p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) + p.bumpInvalid() + continue + } + code, err := bc.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash)) + if err != nil { + p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err) + continue + } + // Accumulate the code and abort if enough data was retrieved + data = append(data, code) + if bytes += len(code); bytes >= softResponseLimit { + break + } + } + return p.replyCode(r.ReqID, data) + }, r.ReqID, uint64(len(r.Reqs)), nil +} + +// handleGetReceipts handles a block receipts request +func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetReceiptsPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + bytes int + receipts []rlp.RawValue + ) + bc := backend.BlockChain() + for i, hash := range r.Hashes { + if i != 0 && !waitOrStop() { + return nil + } + if bytes >= softResponseLimit { + break + } + // Retrieve the requested block's receipts, skipping if unknown to us + results := bc.GetReceiptsByHash(hash) + if results == nil { + if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + p.bumpInvalid() + continue + } + } + // If known, encode and queue for response packet + if encoded, err := rlp.EncodeToBytes(results); err != nil { + log.Error("Failed to encode receipt", "err", err) + } else { + receipts = append(receipts, encoded) + bytes += len(encoded) + } + } + return p.replyReceiptsRLP(r.ReqID, receipts) + }, r.ReqID, uint64(len(r.Hashes)), nil +} + +// handleGetProofs handles a proof request +func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetProofsPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + lastBHash common.Hash + root common.Hash + header *types.Header + err error + ) + bc := backend.BlockChain() + nodes := light.NewNodeSet() + + for i, request := range r.Reqs { + if i != 0 && !waitOrStop() { + return nil + } + // Look up the root hash belonging to the request + if request.BHash != lastBHash { + root, lastBHash = common.Hash{}, request.BHash + + if header = bc.GetHeaderByHash(request.BHash); header == nil { + p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash) + p.bumpInvalid() + continue + } + // Refuse to search stale state data in the database since looking for + // a non-exist key is kind of expensive. + local := bc.CurrentHeader().Number.Uint64() + if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local { + p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local) + p.bumpInvalid() + continue + } + root = header.Root + } + // If a header lookup failed (non existent), ignore subsequent requests for the same header + if root == (common.Hash{}) { + p.bumpInvalid() + continue + } + // Open the account or storage trie for the request + statedb := bc.StateCache() + + var trie state.Trie + switch len(request.AccKey) { + case 0: + // No account key specified, open an account trie + trie, err = statedb.OpenTrie(root) + if trie == nil || err != nil { + p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err) + continue + } + default: + // Account key specified, open a storage trie + account, err := getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey)) + if err != nil { + p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) + p.bumpInvalid() + continue + } + trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root) + if trie == nil || err != nil { + p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err) + continue + } + } + // Prove the user's request from the account or stroage trie + if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil { + p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err) + continue + } + if nodes.DataSize() >= softResponseLimit { + break + } + } + return p.replyProofsV2(r.ReqID, nodes.NodeList()) + }, r.ReqID, uint64(len(r.Reqs)), nil +} + +// handleGetHelperTrieProofs handles a helper trie proof request +func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetHelperTrieProofsPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + var ( + lastIdx uint64 + lastType uint + auxTrie *trie.Trie + auxBytes int + auxData [][]byte + ) + bc := backend.BlockChain() + nodes := light.NewNodeSet() + for i, request := range r.Reqs { + if i != 0 && !waitOrStop() { + return nil + } + if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx { + lastType, lastIdx = request.Type, request.TrieIdx + auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx) + } + if auxTrie == nil { + return nil + } + // TODO(rjl493456442) short circuit if the proving is failed. + // The original client side code has a dirty hack to retrieve + // the headers with no valid proof. Keep the compatibility for + // legacy les protocol and drop this hack when the les2/3 are + // not supported. + err := auxTrie.Prove(request.Key, request.FromLevel, nodes) + if p.version >= lpv4 && err != nil { + return nil + } + if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 { + header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key)) + data, err := rlp.EncodeToBytes(header) + if err != nil { + log.Error("Failed to encode header", "err", err) + return nil + } + auxData = append(auxData, data) + auxBytes += len(data) + } + if nodes.DataSize()+auxBytes >= softResponseLimit { + break + } + } + return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) + }, r.ReqID, uint64(len(r.Reqs)), nil +} + +// handleSendTx handles a transaction propagation request +func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r SendTxPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + amount := uint64(len(r.Txs)) + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + stats := make([]light.TxStatus, len(r.Txs)) + for i, tx := range r.Txs { + if i != 0 && !waitOrStop() { + return nil + } + hash := tx.Hash() + stats[i] = txStatus(backend, hash) + if stats[i].Status == core.TxStatusUnknown { + addFn := backend.TxPool().AddRemotes + // Add txs synchronously for testing purpose + if backend.AddTxsSync() { + addFn = backend.TxPool().AddRemotesSync + } + if errs := addFn([]*types.Transaction{tx}); errs[0] != nil { + stats[i].Error = errs[0].Error() + continue + } + stats[i] = txStatus(backend, hash) + } + } + return p.replyTxStatus(r.ReqID, stats) + }, r.ReqID, amount, nil +} + +// handleGetTxStatus handles a transaction status query +func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) { + var r GetTxStatusPacket + if err := msg.Decode(&r); err != nil { + return nil, 0, 0, err + } + return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { + stats := make([]light.TxStatus, len(r.Hashes)) + for i, hash := range r.Hashes { + if i != 0 && !waitOrStop() { + return nil + } + stats[i] = txStatus(backend, hash) + } + return p.replyTxStatus(r.ReqID, stats) + }, r.ReqID, uint64(len(r.Hashes)), nil +} + +// txStatus returns the status of a specified transaction. +func txStatus(b serverBackend, hash common.Hash) light.TxStatus { + var stat light.TxStatus + // Looking the transaction in txpool first. + stat.Status = b.TxPool().Status([]common.Hash{hash})[0] + + // If the transaction is unknown to the pool, try looking it up locally. + if stat.Status == core.TxStatusUnknown { + lookup := b.BlockChain().GetTransactionLookup(hash) + if lookup != nil { + stat.Status = core.TxStatusIncluded + stat.Lookup = lookup + } + } + return stat +} diff --git a/les/test_helper.go b/les/test_helper.go index 1afc9be7d8..6f93c8a48f 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -594,3 +594,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer } return s, c, teardown } + +func NewFuzzerPeer(version int) *clientPeer { + return newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil) +} diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 860ce0952f..ac93a5a467 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -101,6 +101,7 @@ compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi +compile_fuzzer tests/fuzzers/les Fuzz fuzzLes compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul diff --git a/tests/fuzzers/les/debug/main.go b/tests/fuzzers/les/debug/main.go new file mode 100644 index 0000000000..09e087d4c8 --- /dev/null +++ b/tests/fuzzers/les/debug/main.go @@ -0,0 +1,41 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/les" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug \n") + fmt.Fprintf(os.Stderr, "Example\n") + fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + les.Fuzz(data) +} diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go new file mode 100644 index 0000000000..9e896c2c1b --- /dev/null +++ b/tests/fuzzers/les/les-fuzzer.go @@ -0,0 +1,407 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package les + +import ( + "bytes" + "encoding/binary" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + l "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + bankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey) + bankFunds = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)) + + testChainLen = 256 + testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") + + chain *core.BlockChain + addrHashes []common.Hash + txHashes []common.Hash + + chtTrie *trie.Trie + bloomTrie *trie.Trie + chtKeys [][]byte + bloomKeys [][]byte +) + +func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { + db := rawdb.NewMemoryDatabase() + gspec := core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, + GasLimit: 100000000, + } + genesis := gspec.MustCommit(db) + signer := types.HomesteadSigner{} + blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, testChainLen, + func(i int, gen *core.BlockGen) { + var ( + tx *types.Transaction + addr common.Address + ) + nonce := uint64(i) + if i%4 == 0 { + tx, _ = types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, bankKey) + addr = crypto.CreateAddress(bankAddr, nonce) + } else { + addr = common.BigToAddress(big.NewInt(int64(i))) + tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey) + } + gen.AddTx(tx) + addrHashes = append(addrHashes, crypto.Keccak256Hash(addr[:])) + txHashes = append(txHashes, tx.Hash()) + }) + bc, _ = core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + if _, err := bc.InsertChain(blocks); err != nil { + panic(err) + } + return +} + +func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [][]byte) { + chtTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) + bloomTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase())) + for i := 0; i < testChainLen; i++ { + // The element in CHT is -> + key := make([]byte, 8) + binary.BigEndian.PutUint64(key, uint64(i+1)) + chtTrie.Update(key, []byte{0x1, 0xf}) + chtKeys = append(chtKeys, key) + + // The element in Bloom trie is <2 byte bit index> + -> bloom + key2 := make([]byte, 10) + binary.BigEndian.PutUint64(key2[2:], uint64(i+1)) + bloomTrie.Update(key2, []byte{0x2, 0xe}) + bloomKeys = append(bloomKeys, key2) + } + return +} + +func init() { + chain, addrHashes, txHashes = makechain() + chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries() +} + +type fuzzer struct { + chain *core.BlockChain + pool *core.TxPool + + chainLen int + addr, txs []common.Hash + nonce uint64 + + chtKeys [][]byte + bloomKeys [][]byte + chtTrie *trie.Trie + bloomTrie *trie.Trie + + input io.Reader + exhausted bool +} + +func newFuzzer(input []byte) *fuzzer { + return &fuzzer{ + chain: chain, + chainLen: testChainLen, + addr: addrHashes, + txs: txHashes, + chtTrie: chtTrie, + bloomTrie: bloomTrie, + chtKeys: chtKeys, + bloomKeys: bloomKeys, + nonce: uint64(len(txHashes)), + pool: core.NewTxPool(core.DefaultTxPoolConfig, params.TestChainConfig, chain), + input: bytes.NewReader(input), + } +} + +func (f *fuzzer) read(size int) []byte { + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) randomByte() byte { + d := f.read(1) + return d[0] +} + +func (f *fuzzer) randomBool() bool { + d := f.read(1) + return d[0]&1 == 1 +} + +func (f *fuzzer) randomInt(max int) int { + if max == 0 { + return 0 + } + if max <= 256 { + return int(f.randomByte()) % max + } + var a uint16 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + return int(a % uint16(max)) +} + +func (f *fuzzer) randomX(max int) uint64 { + var a uint16 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + if a < 0x8000 { + return uint64(a%uint16(max+1)) - 1 + } + return (uint64(1)<<(a%64+1) - 1) & (uint64(a) * 343897772345826595) +} + +func (f *fuzzer) randomBlockHash() common.Hash { + h := f.chain.GetCanonicalHash(uint64(f.randomInt(3 * f.chainLen))) + if h != (common.Hash{}) { + return h + } + return common.BytesToHash(f.read(common.HashLength)) +} + +func (f *fuzzer) randomAddrHash() []byte { + i := f.randomInt(3 * len(f.addr)) + if i < len(f.addr) { + return f.addr[i].Bytes() + } + return f.read(common.HashLength) +} + +func (f *fuzzer) randomCHTTrieKey() []byte { + i := f.randomInt(3 * len(f.chtKeys)) + if i < len(f.chtKeys) { + return f.chtKeys[i] + } + return f.read(8) +} + +func (f *fuzzer) randomBloomTrieKey() []byte { + i := f.randomInt(3 * len(f.bloomKeys)) + if i < len(f.bloomKeys) { + return f.bloomKeys[i] + } + return f.read(10) +} + +func (f *fuzzer) randomTxHash() common.Hash { + i := f.randomInt(3 * len(f.txs)) + if i < len(f.txs) { + return f.txs[i] + } + return common.BytesToHash(f.read(common.HashLength)) +} + +func (f *fuzzer) BlockChain() *core.BlockChain { + return f.chain +} + +func (f *fuzzer) TxPool() *core.TxPool { + return f.pool +} + +func (f *fuzzer) ArchiveMode() bool { + return false +} + +func (f *fuzzer) AddTxsSync() bool { + return false +} + +func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie { + if typ == 0 { + return f.chtTrie + } else if typ == 1 { + return f.bloomTrie + } + return nil +} + +type dummyMsg struct { + data []byte +} + +func (d dummyMsg) Decode(val interface{}) error { + return rlp.DecodeBytes(d.data, val) +} + +func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) { + version := f.randomInt(3) + 2 // [LES2, LES3, LES4] + peer := l.NewFuzzerPeer(version) + enc, err := rlp.EncodeToBytes(packet) + if err != nil { + panic(err) + } + fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc}) + if err != nil { + panic(err) + } + fn(f, peer, func() bool { return true }) + +} + +func Fuzz(input []byte) int { + // We expect some large inputs + if len(input) < 100 { + return -1 + } + f := newFuzzer(input) + if f.exhausted { + return -1 + } + for !f.exhausted { + switch f.randomInt(8) { + case 0: + req := &l.GetBlockHeadersPacket{ + Query: l.GetBlockHeadersData{ + Amount: f.randomX(l.MaxHeaderFetch + 1), + Skip: f.randomX(10), + Reverse: f.randomBool(), + }, + } + if f.randomBool() { + req.Query.Origin.Hash = f.randomBlockHash() + } else { + req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2)) + } + f.doFuzz(l.GetBlockHeadersMsg, req) + + case 1: + req := &l.GetBlockBodiesPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxBodyFetch+1))} + for i := range req.Hashes { + req.Hashes[i] = f.randomBlockHash() + } + f.doFuzz(l.GetBlockBodiesMsg, req) + + case 2: + req := &l.GetCodePacket{Reqs: make([]l.CodeReq, f.randomInt(l.MaxCodeFetch+1))} + for i := range req.Reqs { + req.Reqs[i] = l.CodeReq{ + BHash: f.randomBlockHash(), + AccKey: f.randomAddrHash(), + } + } + f.doFuzz(l.GetCodeMsg, req) + + case 3: + req := &l.GetReceiptsPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxReceiptFetch+1))} + for i := range req.Hashes { + req.Hashes[i] = f.randomBlockHash() + } + f.doFuzz(l.GetReceiptsMsg, req) + + case 4: + req := &l.GetProofsPacket{Reqs: make([]l.ProofReq, f.randomInt(l.MaxProofsFetch+1))} + for i := range req.Reqs { + if f.randomBool() { + req.Reqs[i] = l.ProofReq{ + BHash: f.randomBlockHash(), + AccKey: f.randomAddrHash(), + Key: f.randomAddrHash(), + FromLevel: uint(f.randomX(3)), + } + } else { + req.Reqs[i] = l.ProofReq{ + BHash: f.randomBlockHash(), + Key: f.randomAddrHash(), + FromLevel: uint(f.randomX(3)), + } + } + } + f.doFuzz(l.GetProofsV2Msg, req) + + case 5: + req := &l.GetHelperTrieProofsPacket{Reqs: make([]l.HelperTrieReq, f.randomInt(l.MaxHelperTrieProofsFetch+1))} + for i := range req.Reqs { + switch f.randomInt(3) { + case 0: + // Canonical hash trie + req.Reqs[i] = l.HelperTrieReq{ + Type: 0, + TrieIdx: f.randomX(3), + Key: f.randomCHTTrieKey(), + FromLevel: uint(f.randomX(3)), + AuxReq: uint(2), + } + case 1: + // Bloom trie + req.Reqs[i] = l.HelperTrieReq{ + Type: 1, + TrieIdx: f.randomX(3), + Key: f.randomBloomTrieKey(), + FromLevel: uint(f.randomX(3)), + AuxReq: 0, + } + default: + // Random trie + req.Reqs[i] = l.HelperTrieReq{ + Type: 2, + TrieIdx: f.randomX(3), + Key: f.randomCHTTrieKey(), + FromLevel: uint(f.randomX(3)), + AuxReq: 0, + } + } + } + f.doFuzz(l.GetHelperTrieProofsMsg, req) + + case 6: + req := &l.SendTxPacket{Txs: make([]*types.Transaction, f.randomInt(l.MaxTxSend+1))} + signer := types.HomesteadSigner{} + for i := range req.Txs { + var nonce uint64 + if f.randomBool() { + nonce = uint64(f.randomByte()) + } else { + nonce = f.nonce + f.nonce += 1 + } + req.Txs[i], _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(10000), params.TxGas, big.NewInt(1000000000*int64(f.randomByte())), nil), signer, bankKey) + } + f.doFuzz(l.SendTxV2Msg, req) + + case 7: + req := &l.GetTxStatusPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxTxStatus+1))} + for i := range req.Hashes { + req.Hashes[i] = f.randomTxHash() + } + f.doFuzz(l.GetTxStatusMsg, req) + } + } + return 0 +} From 3ecfdccd9a0065365a00e7c8b60de7ee2df4e40b Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 22 Feb 2021 21:33:11 +0800 Subject: [PATCH 337/948] les: clean up server handler (#22357) --- les/server_handler.go | 185 +++++++++++++++++++++++------------------ les/server_requests.go | 16 ++-- 2 files changed, 114 insertions(+), 87 deletions(-) diff --git a/les/server_handler.go b/les/server_handler.go index b6e8b050b1..7651d03cab 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -204,6 +204,90 @@ func (h *serverHandler) handle(p *clientPeer) error { } } +// beforeHandle will do a series of prechecks before handling message. +func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, reqCnt uint64, maxCount uint64) (*servingTask, uint64) { + // Ensure that the request sent by client peer is valid + inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) + if reqCnt == 0 || reqCnt > maxCount { + p.fcClient.OneTimeCost(inSizeCost) + return nil, 0 + } + // Ensure that the client peer complies with the flow control + // rules agreed by both sides. + if p.isFrozen() { + p.fcClient.OneTimeCost(inSizeCost) + return nil, 0 + } + maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) + accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) + if !accepted { + p.freeze() + p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) + p.fcClient.OneTimeCost(inSizeCost) + return nil, 0 + } + // Create a multi-stage task, estimate the time it takes for the task to + // execute, and cache it in the request service queue. + factor := h.server.costTracker.globalFactor() + if factor < 0.001 { + factor = 1 + p.Log().Error("Invalid global cost factor", "factor", factor) + } + maxTime := uint64(float64(maxCost) / factor) + task := h.server.servingQueue.newTask(p, maxTime, priority) + if !task.start() { + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) + return nil, 0 + } + return task, maxCost +} + +// Afterhandle will perform a series of operations after message handling, +// such as updating flow control data, sending reply, etc. +func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, maxCost uint64, reqCnt uint64, task *servingTask, reply *reply) { + if reply != nil { + task.done() + } + p.responseLock.Lock() + defer p.responseLock.Unlock() + + // Short circuit if the client is already frozen. + if p.isFrozen() { + realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) + p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + return + } + // Positive correction buffer value with real cost. + var replySize uint32 + if reply != nil { + replySize = reply.size() + } + var realCost uint64 + if h.server.costTracker.testing { + realCost = maxCost // Assign a fake cost for testing purpose + } else { + realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) + if realCost > maxCost { + realCost = maxCost + } + } + bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) + if reply != nil { + // Feed cost tracker request serving statistic. + h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) + // Reduce priority "balance" for the specific peer. + p.balance.RequestServed(realCost) + p.queueSend(func() { + if err := reply.send(bv); err != nil { + select { + case p.errCh <- err: + default: + } + } + }) + } +} + // handleMsg is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { @@ -221,9 +305,8 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { } defer msg.Discard() - p.responseCount++ - responseCount := p.responseCount - + // Lookup the request handler table, ensure it's supported + // message type by the protocol. req, ok := Les3[msg.Code] if !ok { p.Log().Trace("Received invalid message", "code", msg.Code) @@ -232,98 +315,42 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { } p.Log().Trace("Received " + req.Name) + // Decode the p2p message, resolve the concrete handler for it. serve, reqID, reqCnt, err := req.Handle(msg) if err != nil { clientErrorMeter.Mark(1) return errResp(ErrDecode, "%v: %v", msg, err) } - if metrics.EnabledExpensive { req.InPacketsMeter.Mark(1) req.InTrafficMeter.Mark(int64(msg.Size)) } + p.responseCount++ + responseCount := p.responseCount - // Short circuit if the peer is already frozen or the request is invalid. - inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) - if p.isFrozen() || reqCnt == 0 || reqCnt > req.MaxCount { - p.fcClient.OneTimeCost(inSizeCost) - return nil - } - // Prepaid max cost units before request been serving. - maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) - accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) - if !accepted { - p.freeze() - p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) - p.fcClient.OneTimeCost(inSizeCost) + // First check this client message complies all rules before + // handling it and return a processor if all checks are passed. + task, maxCost := h.beforeHandle(p, reqID, responseCount, msg, reqCnt, req.MaxCount) + if task == nil { return nil } - // Create a multi-stage task, estimate the time it takes for the task to - // execute, and cache it in the request service queue. - factor := h.server.costTracker.globalFactor() - if factor < 0.001 { - factor = 1 - p.Log().Error("Invalid global cost factor", "factor", factor) - } - maxTime := uint64(float64(maxCost) / factor) - task := h.server.servingQueue.newTask(p, maxTime, priority) - if task.start() { - wg.Add(1) - go func() { - defer wg.Done() - reply := serve(h, p, task.waitOrStop) - if reply != nil { - task.done() - } + wg.Add(1) + go func() { + defer wg.Done() - p.responseLock.Lock() - defer p.responseLock.Unlock() + reply := serve(h, p, task.waitOrStop) + h.afterHandle(p, reqID, responseCount, msg, maxCost, reqCnt, task, reply) - // Short circuit if the client is already frozen. - if p.isFrozen() { - realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - return - } - // Positive correction buffer value with real cost. - var replySize uint32 + if metrics.EnabledExpensive { + size := uint32(0) if reply != nil { - replySize = reply.size() - } - var realCost uint64 - if h.server.costTracker.testing { - realCost = maxCost // Assign a fake cost for testing purpose - } else { - realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) - if realCost > maxCost { - realCost = maxCost - } + size = reply.size() } - bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - if reply != nil { - // Feed cost tracker request serving statistic. - h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) - // Reduce priority "balance" for the specific peer. - p.balance.RequestServed(realCost) - p.queueSend(func() { - if err := reply.send(bv); err != nil { - select { - case p.errCh <- err: - default: - } - } - }) - if metrics.EnabledExpensive { - req.OutPacketsMeter.Mark(1) - req.OutTrafficMeter.Mark(int64(replySize)) - req.ServingTimeMeter.Update(time.Duration(task.servingTime)) - } - } - }() - } else { - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) - } - + req.OutPacketsMeter.Mark(1) + req.OutTrafficMeter.Mark(int64(size)) + req.ServingTimeMeter.Update(time.Duration(task.servingTime)) + } + }() // If the client has made too much invalid request(e.g. request a non-existent data), // reject them to prevent SPAM attack. if p.getInvalid() > maxRequestErrors { diff --git a/les/server_requests.go b/les/server_requests.go index d4af8006f0..07f30b1b73 100644 --- a/les/server_requests.go +++ b/les/server_requests.go @@ -65,7 +65,7 @@ type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop fun // Les3 contains the request types supported by les/2 and les/3 var Les3 = map[uint64]RequestType{ - GetBlockHeadersMsg: RequestType{ + GetBlockHeadersMsg: { Name: "block header request", MaxCount: MaxHeaderFetch, InPacketsMeter: miscInHeaderPacketsMeter, @@ -75,7 +75,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeHeaderTimer, Handle: handleGetBlockHeaders, }, - GetBlockBodiesMsg: RequestType{ + GetBlockBodiesMsg: { Name: "block bodies request", MaxCount: MaxBodyFetch, InPacketsMeter: miscInBodyPacketsMeter, @@ -85,7 +85,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeBodyTimer, Handle: handleGetBlockBodies, }, - GetCodeMsg: RequestType{ + GetCodeMsg: { Name: "code request", MaxCount: MaxCodeFetch, InPacketsMeter: miscInCodePacketsMeter, @@ -95,7 +95,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeCodeTimer, Handle: handleGetCode, }, - GetReceiptsMsg: RequestType{ + GetReceiptsMsg: { Name: "receipts request", MaxCount: MaxReceiptFetch, InPacketsMeter: miscInReceiptPacketsMeter, @@ -105,7 +105,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeReceiptTimer, Handle: handleGetReceipts, }, - GetProofsV2Msg: RequestType{ + GetProofsV2Msg: { Name: "les/2 proofs request", MaxCount: MaxProofsFetch, InPacketsMeter: miscInTrieProofPacketsMeter, @@ -115,7 +115,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeTrieProofTimer, Handle: handleGetProofs, }, - GetHelperTrieProofsMsg: RequestType{ + GetHelperTrieProofsMsg: { Name: "helper trie proof request", MaxCount: MaxHelperTrieProofsFetch, InPacketsMeter: miscInHelperTriePacketsMeter, @@ -125,7 +125,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeHelperTrieTimer, Handle: handleGetHelperTrieProofs, }, - SendTxV2Msg: RequestType{ + SendTxV2Msg: { Name: "new transactions", MaxCount: MaxTxSend, InPacketsMeter: miscInTxsPacketsMeter, @@ -135,7 +135,7 @@ var Les3 = map[uint64]RequestType{ ServingTimeMeter: miscServingTimeTxTimer, Handle: handleSendTx, }, - GetTxStatusMsg: RequestType{ + GetTxStatusMsg: { Name: "transaction status query request", MaxCount: MaxTxStatus, InPacketsMeter: miscInTxStatusPacketsMeter, From c4a2b682ff3ea2465417671de76c4d1e9a29fef8 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 23 Feb 2021 11:27:32 +0100 Subject: [PATCH 338/948] cmd/geth: add db commands stats, compact, put, get, delete (#22014) This PR introduces: - db.put to put a value into the database - db.get to read a value from the database - db.delete to delete a value from the database - db.stats to check compaction info from the database - db.compact to trigger a db compaction It also moves inspectdb to db.inspect. --- cmd/geth/chaincmd.go | 129 +------------- cmd/geth/dbcmd.go | 341 ++++++++++++++++++++++++++++++++++++++ cmd/geth/main.go | 3 +- cmd/utils/flags.go | 8 +- core/rawdb/database.go | 12 +- ethdb/leveldb/leveldb.go | 56 +++++-- internal/flags/helpers.go | 4 +- 7 files changed, 401 insertions(+), 152 deletions(-) create mode 100644 cmd/geth/dbcmd.go diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 9eec30f813..ba4289ddc3 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -20,7 +20,6 @@ import ( "encoding/json" "fmt" "os" - "path/filepath" "runtime" "strconv" "sync/atomic" @@ -28,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -170,18 +168,6 @@ The export-preimages command export hash preimages to an RLP encoded stream`, Category: "BLOCKCHAIN COMMANDS", Description: ` The first argument must be the directory containing the blockchain to download from`, - } - removedbCommand = cli.Command{ - Action: utils.MigrateFlags(removeDB), - Name: "removedb", - Usage: "Remove blockchain and state databases", - ArgsUsage: " ", - Flags: []cli.Flag{ - utils.DataDirFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - Description: ` -Remove blockchain and state databases`, } dumpCommand = cli.Command{ Action: utils.MigrateFlags(dump), @@ -202,25 +188,6 @@ Remove blockchain and state databases`, The arguments are interpreted as block numbers or hashes. Use "ethereum dump 0" to dump the genesis block.`, } - inspectCommand = cli.Command{ - Action: utils.MigrateFlags(inspect), - Name: "inspect", - Usage: "Inspect the storage size for each type of data in the database", - ArgsUsage: " ", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.AncientFlag, - utils.CacheFlag, - utils.MainnetFlag, - utils.RopstenFlag, - utils.RinkebyFlag, - utils.GoerliFlag, - utils.YoloV3Flag, - utils.LegacyTestnetFlag, - utils.SyncModeFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - } ) // initGenesis will initialise the given JSON format genesis file and writes it as @@ -323,17 +290,7 @@ func importChain(ctx *cli.Context) error { fmt.Printf("Import done in %v.\n\n", time.Since(start)) // Output pre-compaction stats mostly to see the import trashing - stats, err := db.Stat("leveldb.stats") - if err != nil { - utils.Fatalf("Failed to read database stats: %v", err) - } - fmt.Println(stats) - - ioStats, err := db.Stat("leveldb.iostats") - if err != nil { - utils.Fatalf("Failed to read database iostats: %v", err) - } - fmt.Println(ioStats) + showLeveldbStats(db) // Print the memory statistics used by the importing mem := new(runtime.MemStats) @@ -351,22 +308,12 @@ func importChain(ctx *cli.Context) error { // Compact the entire database to more accurately measure disk io and print the stats start = time.Now() fmt.Println("Compacting entire database...") - if err = db.Compact(nil, nil); err != nil { + if err := db.Compact(nil, nil); err != nil { utils.Fatalf("Compaction failed: %v", err) } fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) - stats, err = db.Stat("leveldb.stats") - if err != nil { - utils.Fatalf("Failed to read database stats: %v", err) - } - fmt.Println(stats) - - ioStats, err = db.Stat("leveldb.iostats") - if err != nil { - utils.Fatalf("Failed to read database iostats: %v", err) - } - fmt.Println(ioStats) + showLeveldbStats(db) return importErr } @@ -499,66 +446,6 @@ func copyDb(ctx *cli.Context) error { return nil } -func removeDB(ctx *cli.Context) error { - stack, config := makeConfigNode(ctx) - - // Remove the full node state database - path := stack.ResolvePath("chaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node state database") - } else { - log.Info("Full node state database missing", "path", path) - } - // Remove the full node ancient database - path = config.Eth.DatabaseFreezer - switch { - case path == "": - path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") - case !filepath.IsAbs(path): - path = config.Node.ResolvePath(path) - } - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node ancient database") - } else { - log.Info("Full node ancient database missing", "path", path) - } - // Remove the light node database - path = stack.ResolvePath("lightchaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "light node database") - } else { - log.Info("Light node database missing", "path", path) - } - return nil -} - -// confirmAndRemoveDB prompts the user for a last confirmation and removes the -// folder if accepted. -func confirmAndRemoveDB(database string, kind string) { - confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) - switch { - case err != nil: - utils.Fatalf("%v", err) - case !confirm: - log.Info("Database deletion skipped", "path", database) - default: - start := time.Now() - filepath.Walk(database, func(path string, info os.FileInfo, err error) error { - // If we're at the top level folder, recurse into - if path == database { - return nil - } - // Delete all the files, but not subfolders - if !info.IsDir() { - os.Remove(path) - return nil - } - return filepath.SkipDir - }) - log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) - } -} - func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() @@ -598,16 +485,6 @@ func dump(ctx *cli.Context) error { return nil } -func inspect(ctx *cli.Context) error { - node, _ := makeConfigNode(ctx) - defer node.Close() - - _, chainDb := utils.MakeChain(ctx, node, true) - defer chainDb.Close() - - return rawdb.InspectDatabase(chainDb) -} - // hashish returns true for strings that look like hashes. func hashish(x string) bool { _, err := strconv.Atoi(x) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go new file mode 100644 index 0000000000..48478f613e --- /dev/null +++ b/cmd/geth/dbcmd.go @@ -0,0 +1,341 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/leveldb" + "github.com/ethereum/go-ethereum/log" + "github.com/syndtr/goleveldb/leveldb/opt" + "gopkg.in/urfave/cli.v1" +) + +var ( + removedbCommand = cli.Command{ + Action: utils.MigrateFlags(removeDB), + Name: "removedb", + Usage: "Remove blockchain and state databases", + ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + }, + Category: "DATABASE COMMANDS", + Description: ` +Remove blockchain and state databases`, + } + dbCommand = cli.Command{ + Name: "db", + Usage: "Low level database operations", + ArgsUsage: "", + Category: "DATABASE COMMANDS", + Subcommands: []cli.Command{ + dbInspectCmd, + dbStatCmd, + dbCompactCmd, + dbGetCmd, + dbDeleteCmd, + dbPutCmd, + }, + } + dbInspectCmd = cli.Command{ + Action: utils.MigrateFlags(inspect), + Name: "inspect", + ArgsUsage: " ", + + Usage: "Inspect the storage size for each type of data in the database", + Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, + } + dbStatCmd = cli.Command{ + Action: dbStats, + Name: "stats", + Usage: "Print leveldb statistics", + } + dbCompactCmd = cli.Command{ + Action: dbCompact, + Name: "compact", + Usage: "Compact leveldb database. WARNING: May take a very long time", + Description: `This command performs a database compaction. +WARNING: This operation may take a very long time to finish, and may cause database +corruption if it is aborted during execution'!`, + } + dbGetCmd = cli.Command{ + Action: dbGet, + Name: "get", + Usage: "Show the value of a database key", + ArgsUsage: "", + Description: "This command looks up the specified database key from the database.", + } + dbDeleteCmd = cli.Command{ + Action: dbDelete, + Name: "delete", + Usage: "Delete a database key (WARNING: may corrupt your database)", + ArgsUsage: "", + Description: `This command deletes the specified database key from the database. +WARNING: This is a low-level operation which may cause database corruption!`, + } + dbPutCmd = cli.Command{ + Action: dbPut, + Name: "put", + Usage: "Set the value of a database key (WARNING: may corrupt your database)", + ArgsUsage: " ", + Description: `This command sets a given database key to the given value. +WARNING: This is a low-level operation which may cause database corruption!`, + } +) + +func removeDB(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + + // Remove the full node state database + path := stack.ResolvePath("chaindata") + if common.FileExist(path) { + confirmAndRemoveDB(path, "full node state database") + } else { + log.Info("Full node state database missing", "path", path) + } + // Remove the full node ancient database + path = config.Eth.DatabaseFreezer + switch { + case path == "": + path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") + case !filepath.IsAbs(path): + path = config.Node.ResolvePath(path) + } + if common.FileExist(path) { + confirmAndRemoveDB(path, "full node ancient database") + } else { + log.Info("Full node ancient database missing", "path", path) + } + // Remove the light node database + path = stack.ResolvePath("lightchaindata") + if common.FileExist(path) { + confirmAndRemoveDB(path, "light node database") + } else { + log.Info("Light node database missing", "path", path) + } + return nil +} + +// confirmAndRemoveDB prompts the user for a last confirmation and removes the +// folder if accepted. +func confirmAndRemoveDB(database string, kind string) { + confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) + switch { + case err != nil: + utils.Fatalf("%v", err) + case !confirm: + log.Info("Database deletion skipped", "path", database) + default: + start := time.Now() + filepath.Walk(database, func(path string, info os.FileInfo, err error) error { + // If we're at the top level folder, recurse into + if path == database { + return nil + } + // Delete all the files, but not subfolders + if !info.IsDir() { + os.Remove(path) + return nil + } + return filepath.SkipDir + }) + log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +func inspect(ctx *cli.Context) error { + var ( + prefix []byte + start []byte + ) + if ctx.NArg() > 2 { + return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) + } + if ctx.NArg() >= 1 { + if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { + return fmt.Errorf("failed to hex-decode 'prefix': %v", err) + } else { + prefix = d + } + } + if ctx.NArg() >= 2 { + if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil { + return fmt.Errorf("failed to hex-decode 'start': %v", err) + } else { + start = d + } + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + _, chainDb := utils.MakeChain(ctx, stack, true) + defer chainDb.Close() + + return rawdb.InspectDatabase(chainDb, prefix, start) +} + +func showLeveldbStats(db ethdb.Stater) { + if stats, err := db.Stat("leveldb.stats"); err != nil { + log.Warn("Failed to read database stats", "error", err) + } else { + fmt.Println(stats) + } + if ioStats, err := db.Stat("leveldb.iostats"); err != nil { + log.Warn("Failed to read database iostats", "error", err) + } else { + fmt.Println(ioStats) + } +} + +func dbStats(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := stack.ResolvePath("chaindata") + db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { + options.ReadOnly = true + }) + if err != nil { + return err + } + showLeveldbStats(db) + err = db.Close() + if err != nil { + log.Info("Close err", "error", err) + } + return nil +} + +func dbCompact(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := stack.ResolvePath("chaindata") + cache := ctx.GlobalInt(utils.CacheFlag.Name) * ctx.GlobalInt(utils.CacheDatabaseFlag.Name) / 100 + db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { + options.OpenFilesCacheCapacity = utils.MakeDatabaseHandles() + options.BlockCacheCapacity = cache / 2 * opt.MiB + options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + }) + if err != nil { + return err + } + showLeveldbStats(db) + log.Info("Triggering compaction") + err = db.Compact(nil, nil) + if err != nil { + log.Info("Compact err", "error", err) + } + showLeveldbStats(db) + log.Info("Closing db") + err = db.Close() + if err != nil { + log.Info("Close err", "error", err) + } + log.Info("Exiting") + return err +} + +// dbGet shows the value of a given database key +func dbGet(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := stack.ResolvePath("chaindata") + db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { + options.ReadOnly = true + }) + if err != nil { + return err + } + defer db.Close() + key, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + data, err := db.Get(key) + if err != nil { + log.Info("Get operation failed", "error", err) + return err + } + fmt.Printf("key %#x:\n\t%#x\n", key, data) + return nil +} + +// dbDelete deletes a key from the database +func dbDelete(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + db := utils.MakeChainDatabase(ctx, stack) + defer db.Close() + key, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + if err = db.Delete(key); err != nil { + log.Info("Delete operation returned an error", "error", err) + return err + } + return nil +} + +// dbPut overwrite a value in the database +func dbPut(ctx *cli.Context) error { + if ctx.NArg() != 2 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + db := utils.MakeChainDatabase(ctx, stack) + defer db.Close() + var ( + key []byte + value []byte + data []byte + err error + ) + key, err = hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + value, err = hexutil.Decode(ctx.Args().Get(1)) + if err != nil { + log.Info("Could not decode the value", "error", err) + return err + } + data, err = db.Get(key) + if err == nil { + fmt.Printf("Previous value:\n%#x\n", data) + } + return db.Put(key, value) +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0236ffb7d3..4fa24cc1c2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -238,7 +238,6 @@ func init() { removedbCommand, dumpCommand, dumpGenesisCommand, - inspectCommand, // See accountcmd.go: accountCommand, walletCommand, @@ -254,6 +253,8 @@ func init() { licenseCommand, // See config.go dumpConfigCommand, + // see dbcmd.go + dbCommand, // See cmd/utils/flags_legacy.go utils.ShowDeprecated, // See snapshot.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0d7b0e1bf5..ba643efcc3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1073,9 +1073,9 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { } } -// makeDatabaseHandles raises out the number of allowed file handles per process +// MakeDatabaseHandles raises out the number of allowed file handles per process // for Geth and returns half of the allowance to assign to the database. -func makeDatabaseHandles() int { +func MakeDatabaseHandles() int { limit, err := fdlimit.Maximum() if err != nil { Fatalf("Failed to retrieve file descriptor allowance: %v", err) @@ -1546,7 +1546,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) { cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 } - cfg.DatabaseHandles = makeDatabaseHandles() + cfg.DatabaseHandles = MakeDatabaseHandles() if ctx.GlobalIsSet(AncientFlag.Name) { cfg.DatabaseFreezer = ctx.GlobalString(AncientFlag.Name) } @@ -1821,7 +1821,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 - handles = makeDatabaseHandles() + handles = MakeDatabaseHandles() err error chainDb ethdb.Database diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 1f8c3f4542..91171ef92c 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -270,8 +270,8 @@ func (s *stat) Count() string { // InspectDatabase traverses the entire database and checks the size // of all different categories of data. -func InspectDatabase(db ethdb.Database) error { - it := db.NewIterator(nil, nil) +func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { + it := db.NewIterator(keyPrefix, keyStart) defer it.Release() var ( @@ -307,8 +307,9 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes stat // Meta- and unaccounted data - metadata stat - unaccounted stat + metadata stat + unaccounted stat + shutdownInfo stat // Totals total common.StorageSize @@ -359,6 +360,8 @@ func InspectDatabase(db ethdb.Database) error { bytes.HasPrefix(key, []byte("bltIndex-")) || bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub bloomTrieNodes.Add(size) + case bytes.Equal(key, uncleanShutdownKey): + shutdownInfo.Add(size) default: var accounted bool for _, meta := range [][]byte{ @@ -413,6 +416,7 @@ func InspectDatabase(db ethdb.Database) error { {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, + {"Key-Value store", "Shutdown metadata", shutdownInfo.Size(), shutdownInfo.Count()}, {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 80380db325..70ac7a91ac 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -84,24 +84,36 @@ type Database struct { // New returns a wrapped LevelDB object. The namespace is the prefix that the // metrics reporting should use for surfacing internal stats. func New(file string, cache int, handles int, namespace string) (*Database, error) { - // Ensure we have some minimal caching and file guarantees - if cache < minCache { - cache = minCache - } - if handles < minHandles { - handles = minHandles - } + return NewCustom(file, namespace, func(options *opt.Options) { + // Ensure we have some minimal caching and file guarantees + if cache < minCache { + cache = minCache + } + if handles < minHandles { + handles = minHandles + } + // Set default options + options.OpenFilesCacheCapacity = handles + options.BlockCacheCapacity = cache / 2 * opt.MiB + options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + }) +} + +// NewCustom returns a wrapped LevelDB object. The namespace is the prefix that the +// metrics reporting should use for surfacing internal stats. +// The customize function allows the caller to modify the leveldb options. +func NewCustom(file string, namespace string, customize func(options *opt.Options)) (*Database, error) { + options := configureOptions(customize) logger := log.New("database", file) - logger.Info("Allocated cache and file handles", "cache", common.StorageSize(cache*1024*1024), "handles", handles) + usedCache := options.GetBlockCacheCapacity() + options.GetWriteBuffer()*2 + logCtx := []interface{}{"cache", common.StorageSize(usedCache), "handles", options.GetOpenFilesCacheCapacity()} + if options.ReadOnly { + logCtx = append(logCtx, "readonly", "true") + } + logger.Info("Allocated cache and file handles", logCtx...) // Open the db and recover any potential corruptions - db, err := leveldb.OpenFile(file, &opt.Options{ - OpenFilesCacheCapacity: handles, - BlockCacheCapacity: cache / 2 * opt.MiB, - WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally - Filter: filter.NewBloomFilter(10), - DisableSeeksCompaction: true, - }) + db, err := leveldb.OpenFile(file, options) if _, corrupted := err.(*errors.ErrCorrupted); corrupted { db, err = leveldb.RecoverFile(file, nil) } @@ -133,6 +145,20 @@ func New(file string, cache int, handles int, namespace string) (*Database, erro return ldb, nil } +// configureOptions sets some default options, then runs the provided setter. +func configureOptions(customizeFn func(*opt.Options)) *opt.Options { + // Set default options + options := &opt.Options{ + Filter: filter.NewBloomFilter(10), + DisableSeeksCompaction: true, + } + // Allow caller to make custom modifications to the options + if customizeFn != nil { + customizeFn(options) + } + return options +} + // Close stops the metrics collection, flushes any pending data to disk and closes // all io accesses to the underlying key-value store. func (db *Database) Close() error { diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index eb5f5547b1..43bbcf0201 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -25,7 +25,7 @@ import ( ) var ( - CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} [arguments...] + CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} {{.cmd.ArgsUsage}} {{if .cmd.Description}}{{.cmd.Description}} {{end}}{{if .cmd.Subcommands}} SUBCOMMANDS: @@ -36,7 +36,7 @@ SUBCOMMANDS: {{end}} {{end}}{{end}}` - OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] + OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}} {{if .Description}}{{.Description}} {{end}}{{if .Subcommands}} SUBCOMMANDS: From 142fbcfd6f4fad825e2ce2684f9d5a487ffb3f84 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 23 Feb 2021 13:09:19 +0100 Subject: [PATCH 339/948] internal/ethapi: reject non-replay-protected txs over RPC (#22339) This PR prevents users from submitting transactions without EIP-155 enabled. This behaviour can be overridden by specifying the flag --rpc.allow-unprotected-txs=true. --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 7 +++++++ eth/api_backend.go | 11 ++++++++--- eth/backend.go | 5 ++++- internal/ethapi/api.go | 4 ++++ internal/ethapi/backend.go | 5 +++-- les/api_backend.go | 11 ++++++++--- les/client.go | 2 +- node/config.go | 3 +++ 9 files changed, 39 insertions(+), 10 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 4fa24cc1c2..b0316c5409 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -199,6 +199,7 @@ var ( utils.InsecureUnlockAllowedFlag, utils.RPCGlobalGasCapFlag, utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, } whisperFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ba643efcc3..070b3a1de1 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -593,6 +593,10 @@ var ( Name: "preload", Usage: "Comma separated list of JavaScript files to preload into the console", } + AllowUnprotectedTxs = cli.BoolFlag{ + Name: "rpc.allow-unprotected-txs", + Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC", + } // Network Settings MaxPeersFlag = cli.IntFlag{ @@ -966,6 +970,9 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(HTTPPathPrefixFlag.Name) { cfg.HTTPPathPrefix = ctx.GlobalString(HTTPPathPrefixFlag.Name) } + if ctx.GlobalIsSet(AllowUnprotectedTxs.Name) { + cfg.AllowUnprotectedTxs = ctx.GlobalBool(AllowUnprotectedTxs.Name) + } } // setGraphQL creates the GraphQL listener interface string from the set diff --git a/eth/api_backend.go b/eth/api_backend.go index 17de83a28f..2569972e52 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -41,9 +41,10 @@ import ( // EthAPIBackend implements ethapi.Backend for full nodes type EthAPIBackend struct { - extRPCEnabled bool - eth *Ethereum - gpo *gasprice.Oracle + extRPCEnabled bool + allowUnprotectedTxs bool + eth *Ethereum + gpo *gasprice.Oracle } // ChainConfig returns the active chain configuration. @@ -292,6 +293,10 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +func (b *EthAPIBackend) UnprotectedAllowed() bool { + return b.allowUnprotectedTxs +} + func (b *EthAPIBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } diff --git a/eth/backend.go b/eth/backend.go index 4170ecc94e..044422763b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -222,7 +222,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) - eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth, nil} + eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} + if eth.APIBackend.allowUnprotectedTxs { + log.Info("Unprotected transactions allowed") + } gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d3c007b9bf..52b4f5f506 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1555,6 +1555,10 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil { return common.Hash{}, err } + if !b.UnprotectedAllowed() && !tx.Protected() { + // Ensure only eip155 signed transactions are submitted if EIP155Required is set. + return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC") + } if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index f0a4c0493c..ebb088fef5 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -45,8 +45,9 @@ type Backend interface { ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool - RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection - RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs + RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection + RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs + UnprotectedAllowed() bool // allows only for EIP155 transactions. // Blockchain API SetHead(number uint64) diff --git a/les/api_backend.go b/les/api_backend.go index 0839614901..f5d2354b60 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -40,9 +40,10 @@ import ( ) type LesApiBackend struct { - extRPCEnabled bool - eth *LightEthereum - gpo *gasprice.Oracle + extRPCEnabled bool + allowUnprotectedTxs bool + eth *LightEthereum + gpo *gasprice.Oracle } func (b *LesApiBackend) ChainConfig() *params.ChainConfig { @@ -263,6 +264,10 @@ func (b *LesApiBackend) ExtRPCEnabled() bool { return b.extRPCEnabled } +func (b *LesApiBackend) UnprotectedAllowed() bool { + return b.allowUnprotectedTxs +} + func (b *LesApiBackend) RPCGasCap() uint64 { return b.eth.config.RPCGasCap } diff --git a/les/client.go b/les/client.go index d08c9feba5..faaf6095e8 100644 --- a/les/client.go +++ b/les/client.go @@ -157,7 +157,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } - leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), leth, nil} + leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.Miner.GasPrice diff --git a/node/config.go b/node/config.go index 447a69505c..ef1da15d70 100644 --- a/node/config.go +++ b/node/config.go @@ -191,6 +191,9 @@ type Config struct { staticNodesWarning bool trustedNodesWarning bool oldGethResourceWarning bool + + // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC. + AllowUnprotectedTxs bool `toml:",omitempty"` } // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into From 2d1a0e9b03f636babe8785dc833960a5d11e4403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 18:12:25 +0200 Subject: [PATCH 340/948] accounts/abi/bind: fix up Go mod files for Go 1.16 --- accounts/abi/bind/bind_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 400545fd23..d0958cb62f 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1846,11 +1846,16 @@ func TestGolangBindings(t *testing.T) { t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) } pwd, _ := os.Getwd() - replacer := exec.Command(gocmd, "mod", "edit", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root + replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ethereum/go-ethereum@v0.0.0", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root replacer.Dir = pkg if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } + tidier := exec.Command(gocmd, "mod", "tidy") + tidier.Dir = pkg + if out, err := tidier.CombinedOutput(); err != nil { + t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) + } // Test the entire package and report any failures cmd := exec.Command(gocmd, "test", "-v", "-count", "1") cmd.Dir = pkg From 2743fb042945add8dfe4ca782310e123318c7d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 18:28:24 +0200 Subject: [PATCH 341/948] Dockerfile: bump to Go 1.16 base images --- Dockerfile | 2 +- Dockerfile.alltools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index d86b776611..6e0dea11b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.15-alpine as builder +FROM golang:1.16-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 715213c5de..483afad8c3 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.15-alpine as builder +FROM golang:1.16-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git From c9aa2670499a874a28c44424c29268889b18d027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 19:57:39 +0200 Subject: [PATCH 342/948] travis: bump Android NDK version --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39a0456c3b..2e85e5297d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -145,9 +145,9 @@ jobs: - export GOPATH=$HOME/go script: # Build the Android archive and upload it to Maven Central and Azure - - curl https://dl.google.com/android/repository/android-ndk-r19b-linux-x86_64.zip -o android-ndk-r19b.zip - - unzip -q android-ndk-r19b.zip && rm android-ndk-r19b.zip - - mv android-ndk-r19b $ANDROID_HOME/ndk-bundle + - curl https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip -o android-ndk-r21e.zip + - unzip -q android-ndk-r21e.zip && rm android-ndk-r21e.zip + - mv android-ndk-r21e $ANDROID_HOME/ndk-bundle - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum From 70afe15f680250e69b459d8d9539f594b5fb7491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 23 Feb 2021 20:31:09 +0200 Subject: [PATCH 343/948] travis: bump builders to Bionic --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e85e5297d..122dee3b36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ jobs: # This builder only tests code linters on latest version of Go - stage: lint os: linux - dist: xenial + dist: bionic go: 1.16.x env: - lint @@ -28,7 +28,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic go: 1.16.x env: - ubuntu-ppa @@ -52,7 +52,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic sudo: required go: 1.16.x env: @@ -88,7 +88,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic services: - docker go: 1.16.x @@ -118,7 +118,7 @@ jobs: - stage: build if: type = push os: linux - dist: xenial + dist: bionic addons: apt: packages: @@ -188,7 +188,7 @@ jobs: - stage: build os: linux arch: amd64 - dist: xenial + dist: bionic go: 1.16.x env: - GO111MODULE=on @@ -199,7 +199,7 @@ jobs: if: type = pull_request os: linux arch: arm64 - dist: xenial + dist: bionic go: 1.16.x env: - GO111MODULE=on @@ -208,7 +208,7 @@ jobs: - stage: build os: linux - dist: xenial + dist: bionic go: 1.15.x env: - GO111MODULE=on @@ -219,7 +219,7 @@ jobs: - stage: build if: type = cron os: linux - dist: xenial + dist: bionic go: 1.16.x env: - azure-purge From f54dc4ab3db0592cf81b3b7ca2ed7a5136ea38a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 24 Feb 2021 11:36:08 +0200 Subject: [PATCH 344/948] travis: manually install Android since Travis is stale (#22373) --- .travis.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 122dee3b36..7406f31fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -122,16 +122,7 @@ jobs: addons: apt: packages: - - oracle-java8-installer - - oracle-java8-set-default - language: android - android: - components: - - platform-tools - - tools - - android-15 - - android-19 - - android-24 + - openjdk-8-jdk env: - azure-android - maven-android @@ -139,16 +130,24 @@ jobs: git: submodules: false # avoid cloning ethereum/tests before_install: + # Install Android and it's dependencies manually, Travis is stale + - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 + - curl https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip -o android.zip + - unzip -q android.zip -d $HOME/sdk && rm android.zip + - mv $HOME/sdk/cmdline-tools $HOME/sdk/latest && mkdir $HOME/sdk/cmdline-tools && mv $HOME/sdk/latest $HOME/sdk/cmdline-tools + - export PATH=$PATH:$HOME/sdk/cmdline-tools/latest/bin + - export ANDROID_HOME=$HOME/sdk + + - yes | sdkmanager --licenses >/dev/null + - sdkmanager "platform-tools" "platforms;android-15" "platforms;android-19" "platforms;android-24" "ndk-bundle" + + # Install Go to allow building with - curl https://dl.google.com/go/go1.16.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go script: # Build the Android archive and upload it to Maven Central and Azure - - curl https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip -o android-ndk-r21e.zip - - unzip -q android-ndk-r21e.zip && rm android-ndk-r21e.zip - - mv android-ndk-r21e $ANDROID_HOME/ndk-bundle - - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds From 8e547eecd592fe3306e39a4fea703dc1307b8651 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 24 Feb 2021 14:07:58 +0100 Subject: [PATCH 345/948] cmd/utils: remove deprecated command line flags (#22263) This removes support for all deprecated flags except --rpc*. --- README.md | 8 +-- cmd/clef/main.go | 18 ------ cmd/geth/chaincmd.go | 1 - cmd/geth/consolecmd.go | 2 +- cmd/geth/consolecmd_test.go | 4 +- cmd/geth/main.go | 34 +--------- cmd/geth/run_test.go | 2 +- cmd/geth/snapshot.go | 4 -- cmd/geth/usage.go | 14 +--- cmd/puppeth/module_node.go | 2 +- cmd/utils/flags.go | 116 ++++++++------------------------- cmd/utils/flags_legacy.go | 126 +++--------------------------------- internal/debug/flags.go | 55 +--------------- 13 files changed, 53 insertions(+), 333 deletions(-) diff --git a/README.md b/README.md index 57d431db1e..4a083d117a 100644 --- a/README.md +++ b/README.md @@ -314,13 +314,13 @@ ones either). To start a `geth` instance for mining, run it with all your usual by: ```shell -$ geth --mine --miner.threads=1 --etherbase=0x0000000000000000000000000000000000000000 +$ geth --mine --miner.threads=1 --miner.etherbase=0x0000000000000000000000000000000000000000 ``` Which will start mining blocks and transactions on a single CPU thread, crediting all -proceedings to the account specified by `--etherbase`. You can further tune the mining -by changing the default gas limit blocks converge to (`--targetgaslimit`) and the price -transactions are accepted at (`--gasprice`). +proceedings to the account specified by `--miner.etherbase`. You can further tune the mining +by changing the default gas limit blocks converge to (`--miner.targetgaslimit`) and the price +transactions are accepted at (`--miner.gasprice`). ## Contribution diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 090edd4507..dbbb410fb6 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -107,11 +107,6 @@ var ( Usage: "HTTP-RPC server listening port", Value: node.DefaultHTTPPort + 5, } - legacyRPCPortFlag = cli.IntFlag{ - Name: "rpcport", - Usage: "HTTP-RPC server listening port (Deprecated, please use --http.port).", - Value: node.DefaultHTTPPort + 5, - } signerSecretFlag = cli.StringFlag{ Name: "signersecret", Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", @@ -250,12 +245,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ acceptFlag, }, }, - { - Name: "ALIASED (deprecated)", - Flags: []cli.Flag{ - legacyRPCPortFlag, - }, - }, } func init() { @@ -283,7 +272,6 @@ func init() { testFlag, advancedMode, acceptFlag, - legacyRPCPortFlag, } app.Action = signer app.Commands = []cli.Command{initCommand, @@ -677,12 +665,6 @@ func signer(c *cli.Context) error { // set port port := c.Int(rpcPortFlag.Name) - if c.GlobalIsSet(legacyRPCPortFlag.Name) { - if !c.GlobalIsSet(rpcPortFlag.Name) { - port = c.Int(legacyRPCPortFlag.Name) - } - log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port") - } // start http server httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index ba4289ddc3..ff9581fd88 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -163,7 +163,6 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.TxLookupLimitFlag, utils.GoerliFlag, utils.YoloV3Flag, - utils.LegacyTestnetFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index cc88b9eec4..e26481003d 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -121,7 +121,7 @@ func remoteConsole(ctx *cli.Context) error { path = ctx.GlobalString(utils.DataDirFlag.Name) } if path != "" { - if ctx.GlobalBool(utils.LegacyTestnetFlag.Name) || ctx.GlobalBool(utils.RopstenFlag.Name) { + if ctx.GlobalBool(utils.RopstenFlag.Name) { // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. legacyPath := filepath.Join(path, "testnet") diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 93f9e3d0d9..c3f41b187c 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -53,7 +53,7 @@ func TestConsoleWelcome(t *testing.T) { coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" // Start a geth console, make sure it's cleaned up and terminate the console - geth := runMinimalGeth(t, "--etherbase", coinbase, "console") + geth := runMinimalGeth(t, "--miner.etherbase", coinbase, "console") // Gather all the infos the welcome message needs to contain geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) @@ -100,7 +100,7 @@ func TestAttachWelcome(t *testing.T) { p := trulyRandInt(1024, 65533) // Yeah, sometimes this will fail, sorry :P httpPort = strconv.Itoa(p) wsPort = strconv.Itoa(p + 1) - geth := runMinimalGeth(t, "--etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", + geth := runMinimalGeth(t, "--miner.etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", "--ipcpath", ipc, "--http", "--http.port", httpPort, "--ws", "--ws.port", wsPort) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b0316c5409..d48bfdd42f 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -61,8 +61,6 @@ var ( utils.UnlockedAccountFlag, utils.PasswordFileFlag, utils.BootnodesFlag, - utils.LegacyBootnodesV4Flag, - utils.LegacyBootnodesV5Flag, utils.DataDirFlag, utils.AncientFlag, utils.MinFreeDiskSpaceFlag, @@ -96,11 +94,9 @@ var ( utils.SnapshotFlag, utils.TxLookupLimitFlag, utils.LightServeFlag, - utils.LegacyLightServFlag, utils.LightIngressFlag, utils.LightEgressFlag, utils.LightMaxPeersFlag, - utils.LegacyLightPeersFlag, utils.LightNoPruneFlag, utils.LightKDFFlag, utils.UltraLightServersFlag, @@ -122,17 +118,12 @@ var ( utils.MaxPendingPeersFlag, utils.MiningEnabledFlag, utils.MinerThreadsFlag, - utils.LegacyMinerThreadsFlag, utils.MinerNotifyFlag, utils.MinerGasTargetFlag, - utils.LegacyMinerGasTargetFlag, utils.MinerGasLimitFlag, utils.MinerGasPriceFlag, - utils.LegacyMinerGasPriceFlag, utils.MinerEtherbaseFlag, - utils.LegacyMinerEtherbaseFlag, utils.MinerExtraDataFlag, - utils.LegacyMinerExtraDataFlag, utils.MinerRecommitIntervalFlag, utils.MinerNoVerfiyFlag, utils.NATFlag, @@ -145,7 +136,6 @@ var ( utils.MainnetFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, - utils.LegacyTestnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, @@ -158,9 +148,7 @@ var ( utils.FakePoWFlag, utils.NoCompactionFlag, utils.GpoBlocksFlag, - utils.LegacyGpoBlocksFlag, utils.GpoPercentileFlag, - utils.LegacyGpoPercentileFlag, utils.GpoMaxGasPriceFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, @@ -178,22 +166,18 @@ var ( utils.LegacyRPCPortFlag, utils.LegacyRPCCORSDomainFlag, utils.LegacyRPCVirtualHostsFlag, + utils.LegacyRPCApiFlag, utils.GraphQLEnabledFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, utils.HTTPApiFlag, utils.HTTPPathPrefixFlag, - utils.LegacyRPCApiFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, - utils.LegacyWSListenAddrFlag, utils.WSPortFlag, - utils.LegacyWSPortFlag, utils.WSApiFlag, - utils.LegacyWSApiFlag, utils.WSAllowedOriginsFlag, utils.WSPathPrefixFlag, - utils.LegacyWSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, @@ -267,7 +251,6 @@ func init() { app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, debug.Flags...) - app.Flags = append(app.Flags, debug.DeprecatedFlags...) app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, metricsFlags...) @@ -293,11 +276,6 @@ func main() { func prepare(ctx *cli.Context) { // If we're running a known preset, log it for convenience. switch { - case ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name): - log.Info("Starting Geth on Ropsten testnet...") - log.Warn("The --testnet flag is ambiguous! Please specify one of --goerli, --rinkeby, or --ropsten.") - log.Warn("The generic --testnet flag is deprecated and will be removed in the future!") - case ctx.GlobalIsSet(utils.RopstenFlag.Name): log.Info("Starting Geth on Ropsten testnet...") @@ -316,7 +294,7 @@ func prepare(ctx *cli.Context) { // If we're a full node on mainnet without --cache specified, bump default cache allowance if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) { // Make sure we're not on any supported preconfigured testnet either - if !ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name) && !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { + if !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { // Nope, we're really on mainnet. Bump that cache up! log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096)) @@ -461,19 +439,11 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { if !ok { utils.Fatalf("Ethereum service not running: %v", err) } - // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) - if ctx.GlobalIsSet(utils.LegacyMinerGasPriceFlag.Name) && !ctx.GlobalIsSet(utils.MinerGasPriceFlag.Name) { - gasprice = utils.GlobalBig(ctx, utils.LegacyMinerGasPriceFlag.Name) - } ethBackend.TxPool().SetGasPrice(gasprice) // start mining threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) - if ctx.GlobalIsSet(utils.LegacyMinerThreadsFlag.Name) && !ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { - threads = ctx.GlobalInt(utils.LegacyMinerThreadsFlag.Name) - log.Warn("The flag --minerthreads is deprecated and will be removed in the future, please use --miner.threads") - } if err := ethBackend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index 79b892c59b..527c38a657 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -75,7 +75,7 @@ func runGeth(t *testing.T, args ...string) *testgeth { if i < len(args)-1 { tt.Datadir = args[i+1] } - case "--etherbase": + case "--miner.etherbase": if i < len(args)-1 { tt.Etherbase = args[i+1] } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 6805a42585..b59c530c27 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -60,7 +60,6 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, utils.CacheTrieJournalFlag, utils.BloomFilterSizeFlag, }, @@ -90,7 +89,6 @@ the trie clean cache with default directory will be deleted. utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, }, Description: ` geth snapshot verify-state @@ -110,7 +108,6 @@ In other words, this command does the snapshot to trie conversion. utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, }, Description: ` geth snapshot traverse-state @@ -132,7 +129,6 @@ It's also usable without snapshot enabled. utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.LegacyTestnetFlag, }, Description: ` geth snapshot traverse-rawstate diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 55e934d31b..cae388c1d3 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -161,8 +161,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "NETWORKING", Flags: []cli.Flag{ utils.BootnodesFlag, - utils.LegacyBootnodesV4Flag, - utils.LegacyBootnodesV5Flag, utils.DNSDiscoveryFlag, utils.ListenPortFlag, utils.MaxPeersFlag, @@ -223,7 +221,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ }, { Name: "ALIASED (deprecated)", - Flags: append([]cli.Flag{ + Flags: []cli.Flag{ utils.NoUSBFlag, utils.LegacyRPCEnabledFlag, utils.LegacyRPCListenAddrFlag, @@ -231,15 +229,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.LegacyRPCCORSDomainFlag, utils.LegacyRPCVirtualHostsFlag, utils.LegacyRPCApiFlag, - utils.LegacyWSListenAddrFlag, - utils.LegacyWSPortFlag, - utils.LegacyWSAllowedOriginsFlag, - utils.LegacyWSApiFlag, - utils.LegacyGpoBlocksFlag, - utils.LegacyGpoPercentileFlag, - utils.LegacyGraphQLListenAddrFlag, - utils.LegacyGraphQLPortFlag, - }, debug.DeprecatedFlags...), + }, }, { Name: "MISC", diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go index 5d9ef46523..3ea96870d4 100644 --- a/cmd/puppeth/module_node.go +++ b/cmd/puppeth/module_node.go @@ -94,7 +94,7 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n lightFlag := "" if config.peersLight > 0 { - lightFlag = fmt.Sprintf("--lightpeers=%d --lightserv=50", config.peersLight) + lightFlag = fmt.Sprintf("--light.maxpeers=%d --light.serve=50", config.peersLight) } dockerfile := new(bytes.Buffer) template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0555acfc75..817041c589 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -765,13 +765,9 @@ var ( // then a subdirectory of the specified datadir will be used. func MakeDataDir(ctx *cli.Context) string { if path := ctx.GlobalString(DataDirFlag.Name); path != "" { - if ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name) { + if ctx.GlobalBool(RopstenFlag.Name) { // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. - legacyPath := filepath.Join(path, "testnet") - if _, err := os.Stat(legacyPath); !os.IsNotExist(err) { - return legacyPath - } return filepath.Join(path, "ropsten") } if ctx.GlobalBool(RinkebyFlag.Name) { @@ -827,13 +823,9 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls := params.MainnetBootnodes switch { - case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV4Flag.Name): - if ctx.GlobalIsSet(LegacyBootnodesV4Flag.Name) { - urls = SplitAndTrim(ctx.GlobalString(LegacyBootnodesV4Flag.Name)) - } else { - urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) - } - case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + case ctx.GlobalIsSet(BootnodesFlag.Name): + urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) + case ctx.GlobalBool(RopstenFlag.Name): urls = params.RopstenBootnodes case ctx.GlobalBool(RinkebyFlag.Name): urls = params.RinkebyBootnodes @@ -863,12 +855,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { urls := params.V5Bootnodes switch { - case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name): - if ctx.GlobalIsSet(LegacyBootnodesV5Flag.Name) { - urls = SplitAndTrim(ctx.GlobalString(LegacyBootnodesV5Flag.Name)) - } else { - urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) - } + case ctx.GlobalIsSet(BootnodesFlag.Name): + urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) case cfg.BootstrapNodesV5 != nil: return // already set, don't apply defaults. } @@ -921,11 +909,11 @@ func SplitAndTrim(input string) (ret []string) { // command line flags, returning empty if the HTTP endpoint is disabled. func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalBool(LegacyRPCEnabledFlag.Name) && cfg.HTTPHost == "" { - log.Warn("The flag --rpc is deprecated and will be removed in the future, please use --http") + log.Warn("The flag --rpc is deprecated and will be removed June 2021, please use --http") cfg.HTTPHost = "127.0.0.1" if ctx.GlobalIsSet(LegacyRPCListenAddrFlag.Name) { cfg.HTTPHost = ctx.GlobalString(LegacyRPCListenAddrFlag.Name) - log.Warn("The flag --rpcaddr is deprecated and will be removed in the future, please use --http.addr") + log.Warn("The flag --rpcaddr is deprecated and will be removed June 2021, please use --http.addr") } } if ctx.GlobalBool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" { @@ -937,7 +925,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCPortFlag.Name) { cfg.HTTPPort = ctx.GlobalInt(LegacyRPCPortFlag.Name) - log.Warn("The flag --rpcport is deprecated and will be removed in the future, please use --http.port") + log.Warn("The flag --rpcport is deprecated and will be removed June 2021, please use --http.port") } if ctx.GlobalIsSet(HTTPPortFlag.Name) { cfg.HTTPPort = ctx.GlobalInt(HTTPPortFlag.Name) @@ -945,7 +933,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCCORSDomainFlag.Name) { cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(LegacyRPCCORSDomainFlag.Name)) - log.Warn("The flag --rpccorsdomain is deprecated and will be removed in the future, please use --http.corsdomain") + log.Warn("The flag --rpccorsdomain is deprecated and will be removed June 2021, please use --http.corsdomain") } if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) { cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name)) @@ -953,7 +941,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCApiFlag.Name) { cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(LegacyRPCApiFlag.Name)) - log.Warn("The flag --rpcapi is deprecated and will be removed in the future, please use --http.api") + log.Warn("The flag --rpcapi is deprecated and will be removed June 2021, please use --http.api") } if ctx.GlobalIsSet(HTTPApiFlag.Name) { cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(HTTPApiFlag.Name)) @@ -961,7 +949,7 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(LegacyRPCVirtualHostsFlag.Name) { cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(LegacyRPCVirtualHostsFlag.Name)) - log.Warn("The flag --rpcvhosts is deprecated and will be removed in the future, please use --http.vhosts") + log.Warn("The flag --rpcvhosts is deprecated and will be removed June 2021, please use --http.vhosts") } if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) { cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name)) @@ -991,34 +979,18 @@ func setGraphQL(ctx *cli.Context, cfg *node.Config) { func setWS(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalBool(WSEnabledFlag.Name) && cfg.WSHost == "" { cfg.WSHost = "127.0.0.1" - if ctx.GlobalIsSet(LegacyWSListenAddrFlag.Name) { - cfg.WSHost = ctx.GlobalString(LegacyWSListenAddrFlag.Name) - log.Warn("The flag --wsaddr is deprecated and will be removed in the future, please use --ws.addr") - } if ctx.GlobalIsSet(WSListenAddrFlag.Name) { cfg.WSHost = ctx.GlobalString(WSListenAddrFlag.Name) } } - if ctx.GlobalIsSet(LegacyWSPortFlag.Name) { - cfg.WSPort = ctx.GlobalInt(LegacyWSPortFlag.Name) - log.Warn("The flag --wsport is deprecated and will be removed in the future, please use --ws.port") - } if ctx.GlobalIsSet(WSPortFlag.Name) { cfg.WSPort = ctx.GlobalInt(WSPortFlag.Name) } - if ctx.GlobalIsSet(LegacyWSAllowedOriginsFlag.Name) { - cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(LegacyWSAllowedOriginsFlag.Name)) - log.Warn("The flag --wsorigins is deprecated and will be removed in the future, please use --ws.origins") - } if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) { cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name)) } - if ctx.GlobalIsSet(LegacyWSApiFlag.Name) { - cfg.WSModules = SplitAndTrim(ctx.GlobalString(LegacyWSApiFlag.Name)) - log.Warn("The flag --wsapi is deprecated and will be removed in the future, please use --ws.api") - } if ctx.GlobalIsSet(WSApiFlag.Name) { cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name)) } @@ -1042,10 +1014,6 @@ func setIPC(ctx *cli.Context, cfg *node.Config) { // setLes configures the les server and ultra light client settings from the command line flags. func setLes(ctx *cli.Context, cfg *ethconfig.Config) { - if ctx.GlobalIsSet(LegacyLightServFlag.Name) { - cfg.LightServ = ctx.GlobalInt(LegacyLightServFlag.Name) - log.Warn("The flag --lightserv is deprecated and will be removed in the future, please use --light.serve") - } if ctx.GlobalIsSet(LightServeFlag.Name) { cfg.LightServ = ctx.GlobalInt(LightServeFlag.Name) } @@ -1055,10 +1023,6 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(LightEgressFlag.Name) { cfg.LightEgress = ctx.GlobalInt(LightEgressFlag.Name) } - if ctx.GlobalIsSet(LegacyLightPeersFlag.Name) { - cfg.LightPeers = ctx.GlobalInt(LegacyLightPeersFlag.Name) - log.Warn("The flag --lightpeers is deprecated and will be removed in the future, please use --light.maxpeers") - } if ctx.GlobalIsSet(LightMaxPeersFlag.Name) { cfg.LightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name) } @@ -1122,13 +1086,8 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error // setEtherbase retrieves the etherbase either from the directly specified // command line flags or from the keystore if CLI indexed. func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *ethconfig.Config) { - // Extract the current etherbase, new flag overriding legacy one + // Extract the current etherbase var etherbase string - if ctx.GlobalIsSet(LegacyMinerEtherbaseFlag.Name) { - etherbase = ctx.GlobalString(LegacyMinerEtherbaseFlag.Name) - log.Warn("The flag --etherbase is deprecated and will be removed in the future, please use --miner.etherbase") - - } if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) { etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name) } @@ -1172,27 +1131,24 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { setBootstrapNodesV5(ctx, cfg) lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light" - lightServer := (ctx.GlobalInt(LegacyLightServFlag.Name) != 0 || ctx.GlobalInt(LightServeFlag.Name) != 0) + lightServer := (ctx.GlobalInt(LightServeFlag.Name) != 0) - lightPeers := ctx.GlobalInt(LegacyLightPeersFlag.Name) - if ctx.GlobalIsSet(LightMaxPeersFlag.Name) { - lightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name) - } - if lightClient && !ctx.GlobalIsSet(LegacyLightPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { + lightPeers := ctx.GlobalInt(LightMaxPeersFlag.Name) + if lightClient && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { // dynamic default - for clients we use 1/10th of the default for servers lightPeers /= 10 } if ctx.GlobalIsSet(MaxPeersFlag.Name) { cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name) - if lightServer && !ctx.GlobalIsSet(LegacyLightPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { + if lightServer && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) { cfg.MaxPeers += lightPeers } } else { if lightServer { cfg.MaxPeers += lightPeers } - if lightClient && (ctx.GlobalIsSet(LegacyLightPeersFlag.Name) || ctx.GlobalIsSet(LightMaxPeersFlag.Name)) && cfg.MaxPeers < lightPeers { + if lightClient && ctx.GlobalIsSet(LightMaxPeersFlag.Name) && cfg.MaxPeers < lightPeers { cfg.MaxPeers = lightPeers } } @@ -1297,7 +1253,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = ctx.GlobalString(DataDirFlag.Name) case ctx.GlobalBool(DeveloperFlag.Name): cfg.DataDir = "" // unless explicitly requested, use memory databases - case (ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name)) && cfg.DataDir == node.DefaultDataDir(): + case ctx.GlobalBool(RopstenFlag.Name) && cfg.DataDir == node.DefaultDataDir(): // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. legacyPath := filepath.Join(node.DefaultDataDir(), "testnet") @@ -1307,6 +1263,8 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { } else { cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten") } + + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten") case ctx.GlobalBool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): @@ -1323,17 +1281,9 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { cfg.Blocks = ethconfig.LightClientGPO.Blocks cfg.Percentile = ethconfig.LightClientGPO.Percentile } - if ctx.GlobalIsSet(LegacyGpoBlocksFlag.Name) { - cfg.Blocks = ctx.GlobalInt(LegacyGpoBlocksFlag.Name) - log.Warn("The flag --gpoblocks is deprecated and will be removed in the future, please use --gpo.blocks") - } if ctx.GlobalIsSet(GpoBlocksFlag.Name) { cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name) } - if ctx.GlobalIsSet(LegacyGpoPercentileFlag.Name) { - cfg.Percentile = ctx.GlobalInt(LegacyGpoPercentileFlag.Name) - log.Warn("The flag --gpopercentile is deprecated and will be removed in the future, please use --gpo.percentile") - } if ctx.GlobalIsSet(GpoPercentileFlag.Name) { cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name) } @@ -1416,27 +1366,15 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { if ctx.GlobalIsSet(MinerNotifyFlag.Name) { cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",") } - if ctx.GlobalIsSet(LegacyMinerExtraDataFlag.Name) { - cfg.ExtraData = []byte(ctx.GlobalString(LegacyMinerExtraDataFlag.Name)) - log.Warn("The flag --extradata is deprecated and will be removed in the future, please use --miner.extradata") - } if ctx.GlobalIsSet(MinerExtraDataFlag.Name) { cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name)) } - if ctx.GlobalIsSet(LegacyMinerGasTargetFlag.Name) { - cfg.GasFloor = ctx.GlobalUint64(LegacyMinerGasTargetFlag.Name) - log.Warn("The flag --targetgaslimit is deprecated and will be removed in the future, please use --miner.gastarget") - } if ctx.GlobalIsSet(MinerGasTargetFlag.Name) { cfg.GasFloor = ctx.GlobalUint64(MinerGasTargetFlag.Name) } if ctx.GlobalIsSet(MinerGasLimitFlag.Name) { cfg.GasCeil = ctx.GlobalUint64(MinerGasLimitFlag.Name) } - if ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) { - cfg.GasPrice = GlobalBig(ctx, LegacyMinerGasPriceFlag.Name) - log.Warn("The flag --gasprice is deprecated and will be removed in the future, please use --miner.gasprice") - } if ctx.GlobalIsSet(MinerGasPriceFlag.Name) { cfg.GasPrice = GlobalBig(ctx, MinerGasPriceFlag.Name) } @@ -1525,11 +1463,11 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) - CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) + CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) - if (ctx.GlobalIsSet(LegacyLightServFlag.Name) || ctx.GlobalIsSet(LightServeFlag.Name)) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { + if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") } var ks *keystore.KeyStore @@ -1644,7 +1582,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.Genesis = core.DefaultGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) - case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + case ctx.GlobalBool(RopstenFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 3 } @@ -1710,7 +1648,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } chaindb.Close() } - if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(LegacyMinerGasPriceFlag.Name) { + if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) { cfg.Miner.GasPrice = big.NewInt(1) } default: @@ -1849,7 +1787,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { - case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + case ctx.GlobalBool(RopstenFlag.Name): genesis = core.DefaultRopstenGenesisBlock() case ctx.GlobalBool(RinkebyFlag.Name): genesis = core.DefaultRinkebyGenesisBlock() diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index ff45ab9094..fb5fde6576 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -20,7 +20,6 @@ import ( "fmt" "strings" - "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" ) @@ -34,143 +33,39 @@ var ShowDeprecated = cli.Command{ Description: "Show flags that have been deprecated and will soon be removed", } -var DeprecatedFlags = []cli.Flag{ - LegacyTestnetFlag, - LegacyLightServFlag, - LegacyLightPeersFlag, - LegacyMinerThreadsFlag, - LegacyMinerGasTargetFlag, - LegacyMinerGasPriceFlag, - LegacyMinerEtherbaseFlag, - LegacyMinerExtraDataFlag, -} +var DeprecatedFlags = []cli.Flag{} var ( - // (Deprecated April 2018) - LegacyMinerThreadsFlag = cli.IntFlag{ - Name: "minerthreads", - Usage: "Number of CPU threads to use for mining (deprecated, use --miner.threads)", - Value: 0, - } - LegacyMinerGasTargetFlag = cli.Uint64Flag{ - Name: "targetgaslimit", - Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)", - Value: ethconfig.Defaults.Miner.GasFloor, - } - LegacyMinerGasPriceFlag = BigFlag{ - Name: "gasprice", - Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)", - Value: ethconfig.Defaults.Miner.GasPrice, - } - LegacyMinerEtherbaseFlag = cli.StringFlag{ - Name: "etherbase", - Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)", - Value: "0", - } - LegacyMinerExtraDataFlag = cli.StringFlag{ - Name: "extradata", - Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)", - } - - // (Deprecated June 2019) - LegacyLightServFlag = cli.IntFlag{ - Name: "lightserv", - Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)", - Value: ethconfig.Defaults.LightServ, - } - LegacyLightPeersFlag = cli.IntFlag{ - Name: "lightpeers", - Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated, use --light.maxpeers)", - Value: ethconfig.Defaults.LightPeers, - } - - // (Deprecated April 2020) - LegacyTestnetFlag = cli.BoolFlag{ // TODO(q9f): Remove after Ropsten is discontinued. - Name: "testnet", - Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)", - } - // (Deprecated May 2020, shown in aliased flags section) LegacyRPCEnabledFlag = cli.BoolFlag{ Name: "rpc", - Usage: "Enable the HTTP-RPC server (deprecated, use --http)", + Usage: "Enable the HTTP-RPC server (deprecated and will be removed June 2021, use --http)", } LegacyRPCListenAddrFlag = cli.StringFlag{ Name: "rpcaddr", - Usage: "HTTP-RPC server listening interface (deprecated, use --http.addr)", + Usage: "HTTP-RPC server listening interface (deprecated and will be removed June 2021, use --http.addr)", Value: node.DefaultHTTPHost, } LegacyRPCPortFlag = cli.IntFlag{ Name: "rpcport", - Usage: "HTTP-RPC server listening port (deprecated, use --http.port)", + Usage: "HTTP-RPC server listening port (deprecated and will be removed June 2021, use --http.port)", Value: node.DefaultHTTPPort, } LegacyRPCCORSDomainFlag = cli.StringFlag{ Name: "rpccorsdomain", - Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated, use --http.corsdomain)", + Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated and will be removed June 2021, use --http.corsdomain)", Value: "", } LegacyRPCVirtualHostsFlag = cli.StringFlag{ Name: "rpcvhosts", - Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (deprecated, use --http.vhosts)", + Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (deprecated and will be removed June 2021, use --http.vhosts)", Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","), } LegacyRPCApiFlag = cli.StringFlag{ Name: "rpcapi", - Usage: "API's offered over the HTTP-RPC interface (deprecated, use --http.api)", - Value: "", - } - LegacyWSListenAddrFlag = cli.StringFlag{ - Name: "wsaddr", - Usage: "WS-RPC server listening interface (deprecated, use --ws.addr)", - Value: node.DefaultWSHost, - } - LegacyWSPortFlag = cli.IntFlag{ - Name: "wsport", - Usage: "WS-RPC server listening port (deprecated, use --ws.port)", - Value: node.DefaultWSPort, - } - LegacyWSApiFlag = cli.StringFlag{ - Name: "wsapi", - Usage: "API's offered over the WS-RPC interface (deprecated, use --ws.api)", + Usage: "API's offered over the HTTP-RPC interface (deprecated and will be removed June 2021, use --http.api)", Value: "", } - LegacyWSAllowedOriginsFlag = cli.StringFlag{ - Name: "wsorigins", - Usage: "Origins from which to accept websockets requests (deprecated, use --ws.origins)", - Value: "", - } - LegacyGpoBlocksFlag = cli.IntFlag{ - Name: "gpoblocks", - Usage: "Number of recent blocks to check for gas prices (deprecated, use --gpo.blocks)", - Value: ethconfig.Defaults.GPO.Blocks, - } - LegacyGpoPercentileFlag = cli.IntFlag{ - Name: "gpopercentile", - Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices (deprecated, use --gpo.percentile)", - Value: ethconfig.Defaults.GPO.Percentile, - } - LegacyBootnodesV4Flag = cli.StringFlag{ - Name: "bootnodesv4", - Usage: "Comma separated enode URLs for P2P v4 discovery bootstrap (light server, full nodes) (deprecated, use --bootnodes)", - Value: "", - } - LegacyBootnodesV5Flag = cli.StringFlag{ - Name: "bootnodesv5", - Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes) (deprecated, use --bootnodes)", - Value: "", - } - - // (Deprecated July 2020, shown in aliased flags section) - LegacyGraphQLListenAddrFlag = cli.StringFlag{ - Name: "graphql.addr", - Usage: "GraphQL server listening interface (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)", - } - LegacyGraphQLPortFlag = cli.IntFlag{ - Name: "graphql.port", - Usage: "GraphQL server listening port (deprecated, graphql can only be enabled on the HTTP-RPC server endpoint, use --graphql)", - Value: node.DefaultHTTPPort, - } ) // showDeprecated displays deprecated flags that will be soon removed from the codebase. @@ -179,8 +74,7 @@ func showDeprecated(*cli.Context) { fmt.Println("The following flags are deprecated and will be removed in the future!") fmt.Println("--------------------------------------------------------------------") fmt.Println() - - for _, flag := range DeprecatedFlags { - fmt.Println(flag.String()) - } + // TODO remove when there are newly deprecated flags + fmt.Println("no deprecated flags to show at this time") + fmt.Println() } diff --git a/internal/debug/flags.go b/internal/debug/flags.go index fc4ea9f518..2c92b19de6 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -90,30 +90,6 @@ var ( Name: "trace", Usage: "Write execution trace to the given file", } - // (Deprecated April 2020) - legacyPprofPortFlag = cli.IntFlag{ - Name: "pprofport", - Usage: "pprof HTTP server listening port (deprecated, use --pprof.port)", - Value: 6060, - } - legacyPprofAddrFlag = cli.StringFlag{ - Name: "pprofaddr", - Usage: "pprof HTTP server listening interface (deprecated, use --pprof.addr)", - Value: "127.0.0.1", - } - legacyMemprofilerateFlag = cli.IntFlag{ - Name: "memprofilerate", - Usage: "Turn on memory profiling with the given rate (deprecated, use --pprof.memprofilerate)", - Value: runtime.MemProfileRate, - } - legacyBlockprofilerateFlag = cli.IntFlag{ - Name: "blockprofilerate", - Usage: "Turn on block profiling with the given rate (deprecated, use --pprof.blockprofilerate)", - } - legacyCpuprofileFlag = cli.StringFlag{ - Name: "cpuprofile", - Usage: "Write CPU profile to the given file (deprecated, use --pprof.cpuprofile)", - } ) // Flags holds all command-line flags required for debugging. @@ -123,12 +99,9 @@ var Flags = []cli.Flag{ blockprofilerateFlag, cpuprofileFlag, traceFlag, } -var DeprecatedFlags = []cli.Flag{ - legacyPprofPortFlag, legacyPprofAddrFlag, legacyMemprofilerateFlag, - legacyBlockprofilerateFlag, legacyCpuprofileFlag, -} - -var glogger *log.GlogHandler +var ( + glogger *log.GlogHandler +) func init() { glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) @@ -159,16 +132,8 @@ func Setup(ctx *cli.Context) error { log.Root().SetHandler(glogger) // profiling, tracing - if ctx.GlobalIsSet(legacyMemprofilerateFlag.Name) { - runtime.MemProfileRate = ctx.GlobalInt(legacyMemprofilerateFlag.Name) - log.Warn("The flag --memprofilerate is deprecated and will be removed in the future, please use --pprof.memprofilerate") - } runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) - if ctx.GlobalIsSet(legacyBlockprofilerateFlag.Name) { - Handler.SetBlockProfileRate(ctx.GlobalInt(legacyBlockprofilerateFlag.Name)) - log.Warn("The flag --blockprofilerate is deprecated and will be removed in the future, please use --pprof.blockprofilerate") - } Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name)) if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" { @@ -182,26 +147,12 @@ func Setup(ctx *cli.Context) error { return err } } - if cpuFile := ctx.GlobalString(legacyCpuprofileFlag.Name); cpuFile != "" { - log.Warn("The flag --cpuprofile is deprecated and will be removed in the future, please use --pprof.cpuprofile") - if err := Handler.StartCPUProfile(cpuFile); err != nil { - return err - } - } // pprof server if ctx.GlobalBool(pprofFlag.Name) { listenHost := ctx.GlobalString(pprofAddrFlag.Name) - if ctx.GlobalIsSet(legacyPprofAddrFlag.Name) && !ctx.GlobalIsSet(pprofAddrFlag.Name) { - listenHost = ctx.GlobalString(legacyPprofAddrFlag.Name) - log.Warn("The flag --pprofaddr is deprecated and will be removed in the future, please use --pprof.addr") - } port := ctx.GlobalInt(pprofPortFlag.Name) - if ctx.GlobalIsSet(legacyPprofPortFlag.Name) && !ctx.GlobalIsSet(pprofPortFlag.Name) { - port = ctx.GlobalInt(legacyPprofPortFlag.Name) - log.Warn("The flag --pprofport is deprecated and will be removed in the future, please use --pprof.port") - } address := fmt.Sprintf("%s:%d", listenHost, port) // This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name. From b2b5c82acaa89387960805d53359629e854814bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Feb 2021 12:56:18 +0200 Subject: [PATCH 346/948] eth/protocols/snap: lower abortion and resumption logs to debug --- eth/protocols/snap/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index c31e4a5dae..1cfdef15bd 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -1595,7 +1595,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { // is interrupted and resumed later. However, *do* update the // previous root hash. if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok { - log.Error("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) + log.Debug("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root) for _, subtask := range subtasks { subtask.root = account.Root } @@ -1614,7 +1614,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { // now we have to live with that. for hash := range res.task.SubTasks { if _, ok := resumed[hash]; !ok { - log.Error("Aborting suspended storage retrieval", "account", hash) + log.Debug("Aborting suspended storage retrieval", "account", hash) delete(res.task.SubTasks, hash) } } From 378e961d857e02a1ce032727da08dfebf2d96cac Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 25 Feb 2021 20:55:07 +0800 Subject: [PATCH 347/948] cmd, eth, les: enable serving light clients when non-synced (#22250) This PR adds a more CLI flag, so that the les-server can serve light clients even the local node is not synced yet. This functionality is needed in some testing environments(e.g. hive). After launching the les server, no more blocks will be imported so the node is always marked as "non-synced". --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 7 +++++++ eth/ethconfig/config.go | 1 + eth/ethconfig/gen_config.go | 6 ++++++ les/server.go | 6 +++++- 6 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d48bfdd42f..b4c622ce2e 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -102,6 +102,7 @@ var ( utils.UltraLightServersFlag, utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, + utils.LightNoSyncServeFlag, utils.WhitelistFlag, utils.BloomFilterSizeFlag, utils.CacheFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index cae388c1d3..24215f55a8 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -68,6 +68,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, utils.LightNoPruneFlag, + utils.LightNoSyncServeFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 817041c589..9fff183a14 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -269,6 +269,10 @@ var ( Name: "light.nopruning", Usage: "Disable ancient light chain data pruning", } + LightNoSyncServeFlag = cli.BoolFlag{ + Name: "light.nosyncserve", + Usage: "Enables serving light clients before syncing", + } // Ethash settings EthashCacheDirFlag = DirectoryFlag{ Name: "ethash.cachedir", @@ -1042,6 +1046,9 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.GlobalIsSet(LightNoPruneFlag.Name) { cfg.LightNoPrune = ctx.GlobalBool(LightNoPruneFlag.Name) } + if ctx.GlobalIsSet(LightNoSyncServeFlag.Name) { + cfg.LightNoSyncServe = ctx.GlobalBool(LightNoSyncServeFlag.Name) + } } // MakeDatabaseHandles raises out the number of allowed file handles per process diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index e192e4d333..841dc5e9e1 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -140,6 +140,7 @@ type Config struct { LightEgress int `toml:",omitempty"` // Outgoing bandwidth limit for light servers LightPeers int `toml:",omitempty"` // Maximum number of LES client peers LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning + LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint // Ultra Light client options diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 5814b81b09..ca93b2ad00 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -31,6 +31,7 @@ func (c Config) MarshalTOML() (interface{}, error) { LightEgress int `toml:",omitempty"` LightPeers int `toml:",omitempty"` LightNoPrune bool `toml:",omitempty"` + LightNoSyncServe bool `toml:",omitempty"` SyncFromCheckpoint bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction int `toml:",omitempty"` @@ -74,6 +75,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.LightEgress = c.LightEgress enc.LightPeers = c.LightPeers enc.LightNoPrune = c.LightNoPrune + enc.LightNoSyncServe = c.LightNoSyncServe enc.SyncFromCheckpoint = c.SyncFromCheckpoint enc.UltraLightServers = c.UltraLightServers enc.UltraLightFraction = c.UltraLightFraction @@ -121,6 +123,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { LightEgress *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` LightNoPrune *bool `toml:",omitempty"` + LightNoSyncServe *bool `toml:",omitempty"` SyncFromCheckpoint *bool `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"` UltraLightFraction *int `toml:",omitempty"` @@ -195,6 +198,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.LightNoPrune != nil { c.LightNoPrune = *dec.LightNoPrune } + if dec.LightNoSyncServe != nil { + c.LightNoSyncServe = *dec.LightNoSyncServe + } if dec.SyncFromCheckpoint != nil { c.SyncFromCheckpoint = *dec.SyncFromCheckpoint } diff --git a/les/server.go b/les/server.go index e34647f290..359784cf7d 100644 --- a/les/server.go +++ b/les/server.go @@ -118,7 +118,11 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les threadsIdle: threads, p2pSrv: node.Server(), } - srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced) + issync := e.Synced + if config.LightNoSyncServe { + issync = func() bool { return true } + } + srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config) From 7a3c890009535bc3b87b01d9af19566e654be9da Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 25 Feb 2021 21:24:04 +0800 Subject: [PATCH 348/948] les, light: improve txstatus retrieval (#22349) Transaction unindexing will be enabled by default as of 1.10, which causes tx status retrieval will be broken without this PR. This PR introduces a retry mechanism in TxStatus retrieval. --- les/client.go | 2 +- les/fetcher_test.go | 35 ++++- les/handler_test.go | 218 +++++++++++++++++++++-------- les/odr.go | 101 +++++++++++++- les/odr_requests.go | 5 +- les/odr_test.go | 192 ++++++++++++++++++++++++- les/peer.go | 12 +- les/pruner_test.go | 29 ++-- les/request_test.go | 9 +- les/sync_test.go | 56 ++++++-- les/test_helper.go | 334 ++++++++++++++++++++++++++------------------ les/ulc_test.go | 15 +- light/odr.go | 1 + light/odr_util.go | 9 +- 14 files changed, 776 insertions(+), 242 deletions(-) diff --git a/les/client.go b/les/client.go index faaf6095e8..053118df5a 100644 --- a/les/client.go +++ b/les/client.go @@ -122,7 +122,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout) leth.relay = newLesTxRelay(peers, leth.retriever) - leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever) + leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever) leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune) leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune) leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) diff --git a/les/fetcher_test.go b/les/fetcher_test.go index a9e6e6835e..d3a74d25c2 100644 --- a/les/fetcher_test.go +++ b/les/fetcher_test.go @@ -66,7 +66,12 @@ func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) } func testSequentialAnnouncements(t *testing.T, protocol int) { - s, c, teardown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() // Create connected peer pair. @@ -101,7 +106,12 @@ func TestGappedAnnouncementsLes2(t *testing.T) { testGappedAnnouncements(t, 2) } func TestGappedAnnouncementsLes3(t *testing.T) { testGappedAnnouncements(t, 3) } func testGappedAnnouncements(t *testing.T, protocol int) { - s, c, teardown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() // Create connected peer pair. @@ -183,7 +193,13 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { ids = append(ids, n.String()) } } - _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ids, 60, false, false, true) + netconfig := testnetConfig{ + protocol: protocol, + nopruning: true, + ulcServers: ids, + ulcFraction: 60, + } + _, c, teardown := newClientServerEnv(t, netconfig) defer teardown() defer func() { for i := 0; i < len(teardowns); i++ { @@ -233,8 +249,17 @@ func testTrustedAnnouncement(t *testing.T, protocol int) { check([]uint64{10}, 10, func() { <-newHead }) // Sync the whole chain. } -func TestInvalidAnnounces(t *testing.T) { - s, c, teardown := newClientServerEnv(t, 4, lpv3, nil, nil, 0, false, false, true) +func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) } +func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) } +func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) } + +func testInvalidAnnounces(t *testing.T, protocol int) { + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + s, c, teardown := newClientServerEnv(t, netconfig) defer teardown() // Create connected peer pair. diff --git a/les/handler_test.go b/les/handler_test.go index e251f4503b..d1dbee6bdf 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -52,9 +52,16 @@ func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) } func testGetBlockHeaders(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxHeaderFetch+15, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: downloader.MaxHeaderFetch + 15, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() bc := server.handler.blockchain // Create a "random" unknown hash for testing @@ -169,8 +176,8 @@ func testGetBlockHeaders(t *testing.T, protocol int) { // Send the hash request and verify the response reqID++ - sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, tt.query) - if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { + sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, tt.query) + if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } } @@ -182,9 +189,17 @@ func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) } func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) } func testGetBlockBodies(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: downloader.MaxHeaderFetch + 15, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Create a batch of tests for various scenarios @@ -247,8 +262,8 @@ func testGetBlockBodies(t *testing.T, protocol int) { reqID++ // Send the hash request and verify the response - sendRequest(server.peer.app, GetBlockBodiesMsg, reqID, hashes) - if err := expectResponse(server.peer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { + sendRequest(rawPeer.app, GetBlockBodiesMsg, reqID, hashes) + if err := expectResponse(rawPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { t.Errorf("test %d: bodies mismatch: %v", i, err) } } @@ -261,8 +276,17 @@ func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) } func testGetCode(t *testing.T, protocol int) { // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain var codereqs []*CodeReq @@ -279,8 +303,8 @@ func testGetCode(t *testing.T, protocol int) { } } - sendRequest(server.peer.app, GetCodeMsg, 42, codereqs) - if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, codes); err != nil { + sendRequest(rawPeer.app, GetCodeMsg, 42, codereqs) + if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil { t.Errorf("codes mismatch: %v", err) } } @@ -291,8 +315,17 @@ func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) } func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) } func testGetStaleCode(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: core.TriesInMemory + 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain check := func(number uint64, expected [][]byte) { @@ -300,8 +333,8 @@ func testGetStaleCode(t *testing.T, protocol int) { BHash: bc.GetHeaderByNumber(number).Hash(), AccKey: crypto.Keccak256(testContractAddr[:]), } - sendRequest(server.peer.app, GetCodeMsg, 42, []*CodeReq{req}) - if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, expected); err != nil { + sendRequest(rawPeer.app, GetCodeMsg, 42, []*CodeReq{req}) + if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) } } @@ -317,9 +350,17 @@ func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) } func testGetReceipt(t *testing.T, protocol int) { // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Collect the hashes to request, and the response to expect @@ -332,8 +373,8 @@ func testGetReceipt(t *testing.T, protocol int) { receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64())) } // Send the hash request and verify the response - sendRequest(server.peer.app, GetReceiptsMsg, 42, hashes) - if err := expectResponse(server.peer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { + sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes) + if err := expectResponse(rawPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { t.Errorf("receipts mismatch: %v", err) } } @@ -345,9 +386,17 @@ func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) } func testGetProofs(t *testing.T, protocol int) { // Assemble the test environment - server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain var proofreqs []ProofReq @@ -368,8 +417,8 @@ func testGetProofs(t *testing.T, protocol int) { } } // Send the proof request and verify the response - sendRequest(server.peer.app, GetProofsV2Msg, 42, proofreqs) - if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { + sendRequest(rawPeer.app, GetProofsV2Msg, 42, proofreqs) + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { t.Errorf("proofs mismatch: %v", err) } } @@ -380,8 +429,17 @@ func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) } func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) } func testGetStaleProof(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + blocks: core.TriesInMemory + 4, + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain check := func(number uint64, wantOK bool) { @@ -393,7 +451,7 @@ func testGetStaleProof(t *testing.T, protocol int) { BHash: header.Hash(), Key: account, } - sendRequest(server.peer.app, GetProofsV2Msg, 42, []*ProofReq{req}) + sendRequest(rawPeer.app, GetProofsV2Msg, 42, []*ProofReq{req}) var expected []rlp.RawValue if wantOK { @@ -402,7 +460,7 @@ func testGetStaleProof(t *testing.T, protocol int) { t.Prove(account, 0, proofsV2) expected = proofsV2.NodeList() } - if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) } } @@ -417,20 +475,30 @@ func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) } func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) } func testGetCHTProofs(t *testing.T, protocol int) { - config := light.TestServerIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - if cs >= 1 { - break + var ( + config = light.TestServerIndexerConfig + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + if cs >= 1 { + break + } + time.Sleep(10 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) } - } - server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false, true, 0) + netconfig = testnetConfig{ + blocks: int(config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + ) + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Assemble the proofs from the different protocols @@ -454,8 +522,8 @@ func testGetCHTProofs(t *testing.T, protocol int) { AuxReq: htAuxHeader, }} // Send the proof request and verify the response - sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requestsV2) - if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { + sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requestsV2) + if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { t.Errorf("proofs mismatch: %v", err) } } @@ -466,20 +534,30 @@ func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) } // Tests that bloombits proofs can be correctly retrieved. func testGetBloombitsProofs(t *testing.T, protocol int) { - config := light.TestServerIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - bts, _, _ := btIndexer.Sections() - if bts >= 1 { - break + var ( + config = light.TestServerIndexerConfig + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + bts, _, _ := btIndexer.Sections() + if bts >= 1 { + break + } + time.Sleep(10 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) } - } - server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), protocol, waitIndexers, false, true, 0) + netconfig = testnetConfig{ + blocks: int(config.BloomTrieSize + config.BloomTrieConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + ) + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + bc := server.handler.blockchain // Request and verify each bit of the bloom bits proofs @@ -503,20 +581,28 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { trie.Prove(key, 0, &proofs.Proofs) // Send the proof request and verify the response - sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requests) - if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { + sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requests) + if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { t.Errorf("bit %d: proofs mismatch: %v", bit, err) } } } -func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) } -func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) } -func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, 4) } +func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, lpv2) } +func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, lpv3) } +func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, lpv4) } func testTransactionStatus(t *testing.T, protocol int) { - server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0) + netconfig := testnetConfig{ + protocol: protocol, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() + server.handler.addTxsSync = true chain := server.handler.blockchain @@ -526,11 +612,11 @@ func testTransactionStatus(t *testing.T, protocol int) { test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) { reqID++ if send { - sendRequest(server.peer.app, SendTxV2Msg, reqID, types.Transactions{tx}) + sendRequest(rawPeer.app, SendTxV2Msg, reqID, types.Transactions{tx}) } else { - sendRequest(server.peer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()}) + sendRequest(rawPeer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()}) } - if err := expectResponse(server.peer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil { + if err := expectResponse(rawPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil { t.Errorf("transaction status mismatch") } } @@ -572,7 +658,7 @@ func testTransactionStatus(t *testing.T, protocol int) { t.Fatalf("pending count mismatch: have %d, want 1", pending) } // Discard new block announcement - msg, _ := server.peer.app.ReadMsg() + msg, _ := rawPeer.app.ReadMsg() msg.Discard() // check if their status is included now @@ -597,7 +683,7 @@ func testTransactionStatus(t *testing.T, protocol int) { t.Fatalf("pending count mismatch: have %d, want 3", pending) } // Discard new block announcement - msg, _ = server.peer.app.ReadMsg() + msg, _ = rawPeer.app.ReadMsg() msg.Discard() // check if their status is pending again @@ -605,11 +691,23 @@ func testTransactionStatus(t *testing.T, protocol int) { test(tx2, false, light.TxStatus{Status: core.TxStatusPending}) } -func TestStopResumeLes3(t *testing.T) { - server, tearDown := newServerEnv(t, 0, 3, nil, true, true, testBufLimit/10) +func TestStopResumeLES3(t *testing.T) { testStopResume(t, lpv3) } +func TestStopResumeLES4(t *testing.T) { testStopResume(t, lpv4) } + +func testStopResume(t *testing.T, protocol int) { + netconfig := testnetConfig{ + protocol: protocol, + simClock: true, + nopruning: true, + } + server, _, tearDown := newClientServerEnv(t, netconfig) defer tearDown() server.handler.server.costTracker.testing = true + server.handler.server.costTracker.testCostList = testCostList(testBufLimit / 10) + + rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) + defer closePeer() var ( reqID uint64 @@ -619,14 +717,14 @@ func TestStopResumeLes3(t *testing.T) { header := server.handler.blockchain.CurrentHeader() req := func() { reqID++ - sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) + sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) } for i := 1; i <= 5; i++ { // send requests while we still have enough buffer and expect a response for expBuf >= testCost { req() expBuf -= testCost - if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil { + if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil { t.Errorf("expected response and failed: %v", err) } } @@ -636,7 +734,7 @@ func TestStopResumeLes3(t *testing.T) { req() c-- } - if err := p2p.ExpectMsg(server.peer.app, StopMsg, nil); err != nil { + if err := p2p.ExpectMsg(rawPeer.app, StopMsg, nil); err != nil { t.Errorf("expected StopMsg and failed: %v", err) } // wait until the buffer is recharged by half of the limit @@ -645,7 +743,7 @@ func TestStopResumeLes3(t *testing.T) { // expect a ResumeMsg with the partially recharged buffer value expBuf += testBufRecharge * wait - if err := p2p.ExpectMsg(server.peer.app, ResumeMsg, expBuf); err != nil { + if err := p2p.ExpectMsg(rawPeer.app, ResumeMsg, expBuf); err != nil { t.Errorf("expected ResumeMsg and failed: %v", err) } } diff --git a/les/odr.go b/les/odr.go index 2c36f512de..d45c6a1a5d 100644 --- a/les/odr.go +++ b/les/odr.go @@ -18,6 +18,7 @@ package les import ( "context" + "sort" "time" "github.com/ethereum/go-ethereum/common/mclock" @@ -31,14 +32,16 @@ type LesOdr struct { db ethdb.Database indexerConfig *light.IndexerConfig chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer + peers *serverPeerSet retriever *retrieveManager stop chan struct{} } -func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, retriever *retrieveManager) *LesOdr { +func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr { return &LesOdr{ db: db, indexerConfig: config, + peers: peers, retriever: retriever, stop: make(chan struct{}), } @@ -98,7 +101,101 @@ type Msg struct { Obj interface{} } -// Retrieve tries to fetch an object from the LES network. +// peerByTxHistory is a heap.Interface implementation which can sort +// the peerset by transaction history. +type peerByTxHistory []*serverPeer + +func (h peerByTxHistory) Len() int { return len(h) } +func (h peerByTxHistory) Less(i, j int) bool { + if h[i].txHistory == txIndexUnlimited { + return false + } + if h[j].txHistory == txIndexUnlimited { + return true + } + return h[i].txHistory < h[j].txHistory +} +func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +const ( + maxTxStatusRetry = 3 // The maximum retrys will be made for tx status request. + maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to. +) + +// RetrieveTxStatus retrieves the transaction status from the LES network. +// There is no guarantee in the LES protocol that the mined transaction will +// be retrieved back for sure because of different reasons(the transaction +// is unindexed, the malicous server doesn't reply it deliberately, etc). +// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number +// of retries, thus giving a weak guarantee. +func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error { + // Sort according to the transaction history supported by the peer and + // select the peers with longest history. + var ( + retries int + peers []*serverPeer + missing = len(req.Hashes) + result = make([]light.TxStatus, len(req.Hashes)) + canSend = make(map[string]bool) + ) + for _, peer := range odr.peers.allPeers() { + if peer.txHistory == txIndexDisabled { + continue + } + peers = append(peers, peer) + } + sort.Sort(sort.Reverse(peerByTxHistory(peers))) + for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ { + canSend[peers[i].id] = true + } + // Send out the request and assemble the result. + for { + if retries >= maxTxStatusRetry || len(canSend) == 0 { + break + } + var ( + // Deep copy the request, so that the partial result won't be mixed. + req = &TxStatusRequest{Hashes: req.Hashes} + id = genReqID() + distreq = &distReq{ + getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) }, + canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] }, + request: func(dp distPeer) func() { + p := dp.(*serverPeer) + p.fcServer.QueuedRequest(id, req.GetCost(p)) + delete(canSend, p.id) + return func() { req.Request(id, p) } + }, + } + ) + if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil { + return err + } + // Collect the response and assemble them to the final result. + // All the response is not verifiable, so always pick the first + // one we get. + for index, status := range req.Status { + if result[index].Status != core.TxStatusUnknown { + continue + } + if status.Status == core.TxStatusUnknown { + continue + } + result[index], missing = status, missing-1 + } + // Abort the procedure if all the status are retrieved + if missing == 0 { + break + } + retries += 1 + } + req.Status = result + return nil +} + +// Retrieve tries to fetch an object from the LES network. It's a common API +// for most of the LES requests except for the TxStatusRequest which needs +// the additional retry mechanism. // If the network retrieval was successful, it stores the object in local db. func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) { lreq := LesRequest(req) diff --git a/les/odr_requests.go b/les/odr_requests.go index 711c54b40f..d548fb1ee0 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -487,7 +487,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 { // CanSend tells if a certain peer is suitable for serving the given request func (r *TxStatusRequest) CanSend(peer *serverPeer) bool { - return peer.serveTxLookup + return peer.txHistory != txIndexDisabled } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) @@ -496,13 +496,12 @@ func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error { return peer.requestTxStatus(reqID, r.Hashes) } -// Valid processes an ODR request reply message from the LES network +// Validate processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating transaction status", "count", len(r.Hashes)) - // Ensure we have a correct message with a single block body if msg.MsgType != MsgTxStatus { return errInvalidMessageType } diff --git a/les/odr_test.go b/les/odr_test.go index 2a70a296c8..a43382e05e 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -19,7 +19,10 @@ package les import ( "bytes" "context" + "crypto/rand" + "fmt" "math/big" + "reflect" "testing" "time" @@ -190,7 +193,13 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon // testOdr tests odr requests whose validation guaranteed by block headers. func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + connect: true, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // Ensure the client has synced all necessary data. @@ -246,3 +255,184 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od test(5) } } + +func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) } + +func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { + var ( + blocks = 8 + netconfig = testnetConfig{ + blocks: blocks, + protocol: protocol, + nopruning: true, + } + ) + server, client, tearDown := newClientServerEnv(t, netconfig) + defer tearDown() + + // Iterate the chain, create the tx indexes locally + var ( + testHash common.Hash + testStatus light.TxStatus + + txs = make(map[common.Hash]*types.Transaction) // Transaction objects set + blockNumbers = make(map[common.Hash]uint64) // Transaction hash to block number mappings + blockHashes = make(map[common.Hash]common.Hash) // Transaction hash to block hash mappings + intraIndex = make(map[common.Hash]uint64) // Transaction intra-index in block + ) + for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().NumberU64(); number++ { + block := server.backend.Blockchain().GetBlockByNumber(number) + if block == nil { + t.Fatalf("Failed to retrieve block %d", number) + } + for index, tx := range block.Transactions() { + txs[tx.Hash()] = tx + blockNumbers[tx.Hash()] = number + blockHashes[tx.Hash()] = block.Hash() + intraIndex[tx.Hash()] = uint64(index) + + if testHash == (common.Hash{}) { + testHash = tx.Hash() + testStatus = light.TxStatus{ + Status: core.TxStatusIncluded, + Lookup: &rawdb.LegacyTxLookupEntry{ + BlockHash: block.Hash(), + BlockIndex: block.NumberU64(), + Index: uint64(index), + }, + } + } + } + } + // serveMsg processes incoming GetTxStatusMsg and sends the response back. + serveMsg := func(peer *testPeer, txLookup uint64) error { + msg, err := peer.app.ReadMsg() + if err != nil { + return err + } + if msg.Code != GetTxStatusMsg { + return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg) + } + var r GetTxStatusPacket + if err := msg.Decode(&r); err != nil { + return err + } + stats := make([]light.TxStatus, len(r.Hashes)) + for i, hash := range r.Hashes { + number, exist := blockNumbers[hash] + if !exist { + continue // Filter out unknown transactions + } + min := uint64(blocks) - txLookup + if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) { + continue // Filter out unindexed transactions + } + stats[i].Status = core.TxStatusIncluded + stats[i].Lookup = &rawdb.LegacyTxLookupEntry{ + BlockHash: blockHashes[hash], + BlockIndex: number, + Index: intraIndex[hash], + } + } + data, _ := rlp.EncodeToBytes(stats) + reply := &reply{peer.app, TxStatusMsg, r.ReqID, data} + reply.send(testBufLimit) + return nil + } + + var testspecs = []struct { + peers int + txLookups []uint64 + txs []common.Hash + results []light.TxStatus + }{ + // Retrieve mined transaction from the empty peerset + { + peers: 0, + txLookups: []uint64{}, + txs: []common.Hash{testHash}, + results: []light.TxStatus{{}}, + }, + // Retrieve unknown transaction from the full peers + { + peers: 3, + txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, + txs: []common.Hash{randomHash()}, + results: []light.TxStatus{{}}, + }, + // Retrieve mined transaction from the full peers + { + peers: 3, + txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, + txs: []common.Hash{testHash}, + results: []light.TxStatus{testStatus}, + }, + // Retrieve mixed transactions from the full peers + { + peers: 3, + txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, + txs: []common.Hash{randomHash(), testHash}, + results: []light.TxStatus{{}, testStatus}, + }, + // Retrieve mixed transactions from unindexed peer(but the target is still available) + { + peers: 3, + txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, + txs: []common.Hash{randomHash(), testHash}, + results: []light.TxStatus{{}, testStatus}, + }, + // Retrieve mixed transactions from unindexed peer(but the target is not available) + { + peers: 3, + txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, + txs: []common.Hash{randomHash(), testHash}, + results: []light.TxStatus{{}, {}}, + }, + } + for _, testspec := range testspecs { + // Create a bunch of server peers with different tx history + var ( + serverPeers []*testPeer + closeFns []func() + ) + for i := 0; i < testspec.peers; i++ { + peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i]) + serverPeers = append(serverPeers, peer) + closeFns = append(closeFns, closePeer) + + // Create a one-time routine for serving message + go func(i int, peer *testPeer) { + serveMsg(peer, testspec.txLookups[i]) + }(i, peer) + } + + // Send out the GetTxStatus requests, compare the result with + // expected value. + r := &light.TxStatusRequest{Hashes: testspec.txs} + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + err := client.handler.backend.odr.RetrieveTxStatus(ctx, r) + if err != nil { + t.Errorf("Failed to retrieve tx status %v", err) + } else { + if !reflect.DeepEqual(testspec.results, r.Status) { + t.Errorf("Result mismatch, diff") + } + } + + // Close all connected peers and start the next round + for _, closeFn := range closeFns { + closeFn() + } + } +} + +// randomHash generates a random blob of data and returns it as a hash. +func randomHash() common.Hash { + var hash common.Hash + if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { + panic(err) + } + return hash +} diff --git a/les/peer.go b/les/peer.go index 479b4034bc..0361167ee1 100644 --- a/les/peer.go +++ b/les/peer.go @@ -341,7 +341,7 @@ type serverPeer struct { onlyAnnounce bool // The flag whether the server sends announcement only. chainSince, chainRecent uint64 // The range of chain server peer can serve. stateSince, stateRecent uint64 // The range of state server peer can serve. - serveTxLookup bool // The server peer can serve tx lookups. + txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled // Advertised checkpoint fields checkpointNumber uint64 // The block height which the checkpoint is registered. @@ -634,13 +634,13 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter if err := recv.get("recentTxLookup", &recentTx); err != nil { return err } - // Note: in the current version we only consider the tx index service useful - // if it is unlimited. This can be made configurable in the future. - p.serveTxLookup = recentTx == txIndexUnlimited + p.txHistory = uint64(recentTx) } else { - p.serveTxLookup = true + // The weak assumption is held here that legacy les server(les2,3) + // has unlimited transaction history. The les serving in these legacy + // versions is disabled if the transaction is unindexed. + p.txHistory = txIndexUnlimited } - if p.onlyAnnounce && !p.trusted { return errResp(ErrUselessPeer, "peer cannot serve requests") } diff --git a/les/pruner_test.go b/les/pruner_test.go index 62b4e9a950..c6f198c088 100644 --- a/les/pruner_test.go +++ b/les/pruner_test.go @@ -28,19 +28,26 @@ import ( ) func TestLightPruner(t *testing.T) { - config := light.TestClientIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - bts, _, _ := btIndexer.Sections() - if cs >= 3 && bts >= 3 { - break + var ( + waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { + for { + cs, _, _ := cIndexer.Sections() + bts, _, _ := btIndexer.Sections() + if cs >= 3 && bts >= 3 { + break + } + time.Sleep(10 * time.Millisecond) } - time.Sleep(10 * time.Millisecond) } - } - server, client, tearDown := newClientServerEnv(t, int(3*config.ChtSize+config.ChtConfirms), 2, waitIndexers, nil, 0, false, true, false) + config = light.TestClientIndexerConfig + netconfig = testnetConfig{ + blocks: int(3*config.ChtSize + config.ChtConfirms), + protocol: 3, + indexFn: waitIndexers, + connect: true, + } + ) + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // checkDB iterates the chain with given prefix, resolves the block number diff --git a/les/request_test.go b/les/request_test.go index b054fd88df..c65405e375 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -83,7 +83,14 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq func testAccess(t *testing.T, protocol int, fn accessTestFn) { // Assemble the test environment - server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true, true) + netconfig := testnetConfig{ + blocks: 4, + protocol: protocol, + indexFn: nil, + connect: true, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // Ensure the client has synced all necessary data. diff --git a/les/sync_test.go b/les/sync_test.go index 64e7283663..d3bb90df02 100644 --- a/les/sync_test.go +++ b/les/sync_test.go @@ -31,15 +31,15 @@ import ( ) // Test light syncing which will download all headers from genesis. -func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 0) } +func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 0) } // Test legacy checkpoint syncing which will download tail headers // based on a hardcoded checkpoint. -func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 1) } +func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 1) } // Test checkpoint syncing which will download tail headers based // on a verified checkpoint. -func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 2) } +func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 2) } func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { config := light.TestServerIndexerConfig @@ -55,7 +55,13 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } } // Generate 128+1 blocks (totally 1 CHT section) - server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() expected := config.ChtSize + config.ChtConfirms @@ -78,7 +84,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { // Register the assembled checkpoint into oracle. header := server.backend.Blockchain().CurrentHeader() - data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) + data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) @@ -128,10 +134,10 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { } } -func TestMissOracleBackend(t *testing.T) { testMissOracleBackend(t, true) } -func TestMissOracleBackendNoCheckpoint(t *testing.T) { testMissOracleBackend(t, false) } +func TestMissOracleBackendLES3(t *testing.T) { testMissOracleBackend(t, true, lpv3) } +func TestMissOracleBackendNoCheckpointLES3(t *testing.T) { testMissOracleBackend(t, false, lpv3) } -func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { +func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) { config := light.TestServerIndexerConfig waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { @@ -145,7 +151,13 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } } // Generate 128+1 blocks (totally 1 CHT section) - server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() expected := config.ChtSize + config.ChtConfirms @@ -160,7 +172,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { // Register the assembled checkpoint into oracle. header := server.backend.Blockchain().CurrentHeader() - data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) + data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337)) @@ -220,7 +232,9 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) { } } -func TestSyncFromConfiguredCheckpoint(t *testing.T) { +func TestSyncFromConfiguredCheckpointLES3(t *testing.T) { testSyncFromConfiguredCheckpoint(t, lpv3) } + +func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) { config := light.TestServerIndexerConfig waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { @@ -234,7 +248,13 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) { } } // Generate 256+1 blocks (totally 2 CHT sections) - server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(2*config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() // Configure the local checkpoint(the first section) @@ -296,7 +316,9 @@ func TestSyncFromConfiguredCheckpoint(t *testing.T) { } } -func TestSyncAll(t *testing.T) { +func TestSyncAll(t *testing.T) { testSyncAll(t, lpv3) } + +func testSyncAll(t *testing.T, protocol int) { config := light.TestServerIndexerConfig waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { @@ -310,7 +332,13 @@ func TestSyncAll(t *testing.T) { } } // Generate 256+1 blocks (totally 2 CHT sections) - server, client, tearDown := newClientServerEnv(t, int(2*config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false, true) + netconfig := testnetConfig{ + blocks: int(2*config.ChtSize + config.ChtConfirms), + protocol: protocol, + indexFn: waitIndexers, + nopruning: true, + } + server, client, tearDown := newClientServerEnv(t, netconfig) defer tearDown() client.handler.backend.config.SyncFromCheckpoint = true diff --git a/les/test_helper.go b/les/test_helper.go index 6f93c8a48f..39313b1e3b 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -14,8 +14,9 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// This file contains some shares testing functionality, common to multiple -// different files and modules being tested. +// This file contains some shares testing functionality, common to multiple +// different files and modules being tested. Client based network and Server +// based network can be created easily with available APIs. package les @@ -68,10 +69,10 @@ var ( testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029") - // Checkpoint registrar relative - registrarAddr common.Address - signerKey, _ = crypto.GenerateKey() - signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey) + // Checkpoint oracle relative fields + oracleAddr common.Address + signerKey, _ = crypto.GenerateKey() + signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey) ) var ( @@ -112,14 +113,23 @@ func prepare(n int, backend *backends.SimulatedBackend) { for i := 0; i < n; i++ { switch i { case 0: + // Builtin-block + // number: 1 + // txs: 2 + // deploy checkpoint contract auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337)) - registrarAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + oracleAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1)) + // bankUser transfers some ether to user1 nonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey) backend.SendTransaction(ctx, tx) case 1: + // Builtin-block + // number: 2 + // txs: 4 + bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) @@ -140,6 +150,10 @@ func prepare(n int, backend *backends.SimulatedBackend) { tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1) backend.SendTransaction(ctx, tx4) case 2: + // Builtin-block + // number: 3 + // txs: 2 + // bankUser transfer some ether to signer bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, nil, nil), signer, bankKey) @@ -150,6 +164,10 @@ func prepare(n int, backend *backends.SimulatedBackend) { tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey) backend.SendTransaction(ctx, tx2) case 3: + // Builtin-block + // number: 4 + // txs: 1 + // invoke test contract bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") @@ -310,45 +328,61 @@ type testPeer struct { app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side } -// newTestPeer creates a new peer registered at the given protocol manager. -func newTestPeer(t *testing.T, name string, version int, handler *serverHandler, shake bool, testCost uint64) (*testPeer, <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) +// handshakeWithServer executes the handshake with the remote server peer. +func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID) { + // It only works for the simulated client peer + if p.cpeer == nil { + t.Fatal("handshake for client peer only") + } + var sendList keyValueList + sendList = sendList.add("protocolVersion", uint64(p.cpeer.version)) + sendList = sendList.add("networkId", uint64(NetworkId)) + sendList = sendList.add("headTd", td) + sendList = sendList.add("headHash", head) + sendList = sendList.add("headNum", headNum) + sendList = sendList.add("genesisHash", genesis) + if p.cpeer.version >= lpv4 { + sendList = sendList.add("forkID", &forkID) + } + if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil { + t.Fatalf("status recv: %v", err) + } + if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { + t.Fatalf("status send: %v", err) + } +} - // Start the peer on a new thread - errCh := make(chan error, 1) - go func() { - select { - case <-handler.closeCh: - errCh <- p2p.DiscQuitting - case errCh <- handler.handle(peer): - } - }() - tp := &testPeer{ - app: app, - net: net, - cpeer: peer, +// handshakeWithClient executes the handshake with the remote client peer. +func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList, recentTxLookup uint64) { + // It only works for the simulated client peer + if p.speer == nil { + t.Fatal("handshake for server peer only") } - // Execute any implicitly requested handshakes and return - if shake { - // Customize the cost table if required. - if testCost != 0 { - handler.server.costTracker.testCostList = testCostList(testCost) - } - var ( - genesis = handler.blockchain.Genesis() - head = handler.blockchain.CurrentHeader() - td = handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - forkID := forkid.NewID(handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) - tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(testCost)) + var sendList keyValueList + sendList = sendList.add("protocolVersion", uint64(p.speer.version)) + sendList = sendList.add("networkId", uint64(NetworkId)) + sendList = sendList.add("headTd", td) + sendList = sendList.add("headHash", head) + sendList = sendList.add("headNum", headNum) + sendList = sendList.add("genesisHash", genesis) + sendList = sendList.add("serveHeaders", nil) + sendList = sendList.add("serveChainSince", uint64(0)) + sendList = sendList.add("serveStateSince", uint64(0)) + sendList = sendList.add("serveRecentState", uint64(core.TriesInMemory-4)) + sendList = sendList.add("txRelay", nil) + sendList = sendList.add("flowControl/BL", testBufLimit) + sendList = sendList.add("flowControl/MRR", testBufRecharge) + sendList = sendList.add("flowControl/MRC", costList) + if p.speer.version >= lpv4 { + sendList = sendList.add("forkID", &forkID) + sendList = sendList.add("recentTxLookup", recentTxLookup) + } + if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil { + t.Fatalf("status recv: %v", err) + } + if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { + t.Fatalf("status send: %v", err) } - return tp, errCh } // close terminates the local side of the peer, notifying the remote protocol @@ -402,48 +436,9 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl return &testPeer{cpeer: peer1, net: net, app: app}, &testPeer{speer: peer2, net: app, app: net}, nil } -// handshake simulates a trivial handshake that expects the same state from the -// remote side as we are simulating locally. -func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList) { - var expList keyValueList - expList = expList.add("protocolVersion", uint64(p.cpeer.version)) - expList = expList.add("networkId", uint64(NetworkId)) - expList = expList.add("headTd", td) - expList = expList.add("headHash", head) - expList = expList.add("headNum", headNum) - expList = expList.add("genesisHash", genesis) - if p.cpeer.version >= lpv4 { - expList = expList.add("forkID", &forkID) - } - sendList := make(keyValueList, len(expList)) - copy(sendList, expList) - expList = expList.add("serveHeaders", nil) - expList = expList.add("serveChainSince", uint64(0)) - expList = expList.add("serveStateSince", uint64(0)) - expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4)) - expList = expList.add("txRelay", nil) - if p.cpeer.version >= lpv4 { - expList = expList.add("recentTxLookup", uint64(0)) - } - expList = expList.add("flowControl/BL", testBufLimit) - expList = expList.add("flowControl/MRR", testBufRecharge) - expList = expList.add("flowControl/MRC", costList) - - if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil { - t.Fatalf("status recv: %v", err) - } - if err := p2p.Send(p.app, StatusMsg, sendList); err != nil { - t.Fatalf("status send: %v", err) - } - p.cpeer.fcParams = flowcontrol.ServerParams{ - BufLimit: testBufLimit, - MinRecharge: testBufRecharge, - } -} - type indexerCallback func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer) -// testClient represents a client for testing with necessary auxiliary fields. +// testClient represents a client object for testing with necessary auxiliary fields. type testClient struct { clock mclock.Clock db ethdb.Database @@ -455,7 +450,58 @@ type testClient struct { bloomTrieIndexer *core.ChainIndexer } -// testServer represents a server for testing with necessary auxiliary fields. +// newRawPeer creates a new server peer connects to the server and do the handshake. +func (client *testClient) newRawPeer(t *testing.T, name string, version int, recentTxLookup uint64) (*testPeer, func(), <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() + + // Generate a random id and create the peer + var id enode.ID + rand.Read(id[:]) + peer := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net) + + // Start the peer on a new thread + errCh := make(chan error, 1) + go func() { + select { + case <-client.handler.closeCh: + errCh <- p2p.DiscQuitting + case errCh <- client.handler.handle(peer): + } + }() + tp := &testPeer{ + app: app, + net: net, + speer: peer, + } + var ( + genesis = client.handler.backend.blockchain.Genesis() + head = client.handler.backend.blockchain.CurrentHeader() + td = client.handler.backend.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + ) + forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + tp.handshakeWithClient(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(0), recentTxLookup) // disable flow control by default + + // Ensure the connection is established or exits when any error occurs + for { + select { + case <-errCh: + return nil, nil, nil + default: + } + if atomic.LoadUint32(&peer.serving) == 1 { + break + } + time.Sleep(50 * time.Millisecond) + } + closePeer := func() { + tp.speer.close() + tp.close() + } + return tp, closePeer, errCh +} + +// testServer represents a server object for testing with necessary auxiliary fields. type testServer struct { clock mclock.Clock backend *backends.SimulatedBackend @@ -468,89 +514,109 @@ type testServer struct { bloomTrieIndexer *core.ChainIndexer } -func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64) (*testServer, func()) { - db := rawdb.NewMemoryDatabase() - indexers := testIndexers(db, nil, light.TestServerIndexerConfig, true) +// newRawPeer creates a new client peer connects to the server and do the handshake. +func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*testPeer, func(), <-chan error) { + // Create a message pipe to communicate through + app, net := p2p.MsgPipe() - var clock mclock.Clock = &mclock.System{} - if simClock { - clock = &mclock.Simulated{} - } - handler, b := newTestServerHandler(blocks, indexers, db, clock) + // Generate a random id and create the peer + var id enode.ID + rand.Read(id[:]) + peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) - var peer *testPeer - if newPeer { - peer, _ = newTestPeer(t, "peer", protocol, handler, true, testCost) + // Start the peer on a new thread + errCh := make(chan error, 1) + go func() { + select { + case <-server.handler.closeCh: + errCh <- p2p.DiscQuitting + case errCh <- server.handler.handle(peer): + } + }() + tp := &testPeer{ + app: app, + net: net, + cpeer: peer, } + var ( + genesis = server.handler.blockchain.Genesis() + head = server.handler.blockchain.CurrentHeader() + td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + ) + forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64()) + tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID) - cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2] - cIndexer.Start(handler.blockchain) - bIndexer.Start(handler.blockchain) - - // Wait until indexers generate enough index data. - if callback != nil { - callback(cIndexer, bIndexer, btIndexer) - } - server := &testServer{ - clock: clock, - backend: b, - db: db, - peer: peer, - handler: handler, - chtIndexer: cIndexer, - bloomIndexer: bIndexer, - bloomTrieIndexer: btIndexer, - } - teardown := func() { - if newPeer { - peer.close() - peer.cpeer.close() - b.Close() + // Ensure the connection is established or exits when any error occurs + for { + select { + case <-errCh: + return nil, nil, nil + default: } - cIndexer.Close() - bIndexer.Close() + if atomic.LoadUint32(&peer.serving) == 1 { + break + } + time.Sleep(50 * time.Millisecond) + } + closePeer := func() { + tp.cpeer.close() + tp.close() } - return server, teardown + return tp, closePeer, errCh } -func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool, disablePruning bool) (*testServer, *testClient, func()) { - sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase() - speers := newServerPeerSet() +// testnetConfig wraps all the configurations for testing network. +type testnetConfig struct { + blocks int + protocol int + indexFn indexerCallback + ulcServers []string + ulcFraction int + simClock bool + connect bool + nopruning bool +} +func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testClient, func()) { + var ( + sdb = rawdb.NewMemoryDatabase() + cdb = rawdb.NewMemoryDatabase() + speers = newServerPeerSet() + ) var clock mclock.Clock = &mclock.System{} - if simClock { + if config.simClock { clock = &mclock.Simulated{} } dist := newRequestDistributor(speers, clock) rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 }) - odr := NewLesOdr(cdb, light.TestClientIndexerConfig, rm) + odr := NewLesOdr(cdb, light.TestClientIndexerConfig, speers, rm) sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true) - cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, disablePruning) + cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, config.nopruning) scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2] ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2] odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer) - server, b := newTestServerHandler(blocks, sindexers, sdb, clock) - client := newTestClientHandler(b, odr, cIndexers, cdb, speers, ulcServers, ulcFraction) + server, b := newTestServerHandler(config.blocks, sindexers, sdb, clock) + client := newTestClientHandler(b, odr, cIndexers, cdb, speers, config.ulcServers, config.ulcFraction) scIndexer.Start(server.blockchain) sbIndexer.Start(server.blockchain) ccIndexer.Start(client.backend.blockchain) cbIndexer.Start(client.backend.blockchain) - if callback != nil { - callback(scIndexer, sbIndexer, sbtIndexer) + if config.indexFn != nil { + config.indexFn(scIndexer, sbIndexer, sbtIndexer) } var ( err error speer, cpeer *testPeer ) - if connect { + if config.connect { done := make(chan struct{}) client.syncEnd = func(_ *types.Header) { close(done) } - cpeer, speer, err = newTestPeerPair("peer", protocol, server, client) + cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client) if err != nil { t.Fatalf("Failed to connect testing peers %v", err) } @@ -580,7 +646,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer bloomTrieIndexer: cbtIndexer, } teardown := func() { - if connect { + if config.connect { speer.close() cpeer.close() cpeer.cpeer.close() diff --git a/les/ulc_test.go b/les/ulc_test.go index 657b13db2c..d7308fa593 100644 --- a/les/ulc_test.go +++ b/les/ulc_test.go @@ -126,7 +126,12 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr // newTestServerPeer creates server peer. func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) { - s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0) + netconfig := testnetConfig{ + blocks: blocks, + protocol: protocol, + nopruning: true, + } + s, _, teardown := newClientServerEnv(t, netconfig) key, err := crypto.GenerateKey() if err != nil { t.Fatal("generate key err:", err) @@ -138,6 +143,12 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *en // newTestLightPeer creates node with light sync mode func newTestLightPeer(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) { - _, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcServers, ulcFraction, false, false, true) + netconfig := testnetConfig{ + protocol: protocol, + ulcServers: ulcServers, + ulcFraction: ulcFraction, + nopruning: true, + } + _, c, teardown := newClientServerEnv(t, netconfig) return c, teardown } diff --git a/light/odr.go b/light/odr.go index bb243f9152..9521dd53e8 100644 --- a/light/odr.go +++ b/light/odr.go @@ -42,6 +42,7 @@ type OdrBackend interface { BloomTrieIndexer() *core.ChainIndexer BloomIndexer() *core.ChainIndexer Retrieve(ctx context.Context, req OdrRequest) error + RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error IndexerConfig() *IndexerConfig } diff --git a/light/odr_util.go b/light/odr_util.go index ec2d1e6491..bbbcdbce21 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -269,10 +269,15 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint return result, nil } -// GetTransaction retrieves a canonical transaction by hash and also returns its position in the chain +// GetTransaction retrieves a canonical transaction by hash and also returns +// its position in the chain. There is no guarantee in the LES protocol that +// the mined transaction will be retrieved back for sure because of different +// reasons(the transaction is unindexed, the malicous server doesn't reply it +// deliberately, etc). Therefore, unretrieved transactions will receive a certain +// number of retrys, thus giving a weak guarantee. func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { r := &TxStatusRequest{Hashes: []common.Hash{txHash}} - if err := odr.Retrieve(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded { + if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded { return nil, common.Hash{}, 0, 0, err } pos := r.Status[0].Lookup From bbfb1e4008a359a8b57ec654330c0e674623e52f Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Thu, 25 Feb 2021 07:26:57 -0700 Subject: [PATCH 349/948] all: add support for EIP-2718, EIP-2930 transactions (#21502) This adds support for EIP-2718 typed transactions as well as EIP-2930 access list transactions (tx type 1). These EIPs are scheduled for the Berlin fork. There very few changes to existing APIs in core/types, and several new APIs to deal with access list transactions. In particular, there are two new constructor functions for transactions: types.NewTx and types.SignNewTx. Since the canonical encoding of typed transactions is not RLP-compatible, Transaction now has new methods for encoding and decoding: MarshalBinary and UnmarshalBinary. The existing EIP-155 signer does not support the new transaction types. All code dealing with transaction signatures should be updated to use the newer EIP-2930 signer. To make this easier for future updates, we have added new constructor functions for types.Signer: types.LatestSigner and types.LatestSignerForChainID. This change also adds support for the YoloV3 testnet. Co-authored-by: Martin Holst Swende Co-authored-by: Felix Lange Co-authored-by: Ryan Schneider --- accounts/abi/bind/auth.go | 4 +- accounts/abi/bind/backends/simulated.go | 25 +- accounts/keystore/keystore.go | 17 +- accounts/scwallet/wallet.go | 2 +- accounts/usbwallet/trezor.go | 2 + cmd/clef/main.go | 2 +- cmd/evm/README.md | 17 +- cmd/evm/internal/t8ntool/execution.go | 37 +- cmd/evm/internal/t8ntool/flags.go | 5 + cmd/evm/internal/t8ntool/transition.go | 135 +++++-- cmd/evm/main.go | 1 + cmd/evm/testdata/8/alloc.json | 11 + cmd/evm/testdata/8/env.json | 7 + cmd/evm/testdata/8/readme.md | 63 ++++ cmd/evm/testdata/8/txs.json | 58 +++ cmd/geth/main.go | 7 +- cmd/geth/usage.go | 3 +- cmd/utils/flags.go | 4 +- core/bench_test.go | 2 +- core/blockchain_test.go | 102 +++++- core/error.go | 10 +- core/genesis.go | 6 +- core/genesis_alloc.go | 3 +- core/state/statedb.go | 26 ++ core/state_prefetcher.go | 33 +- core/state_processor.go | 39 +- core/state_transition.go | 18 +- core/tx_pool.go | 12 +- core/types/access_list_tx.go | 115 ++++++ core/types/block.go | 21 +- core/types/block_test.go | 62 +++- core/types/derive_sha.go | 58 --- core/types/gen_access_tuple.go | 43 +++ core/types/gen_receipt_json.go | 6 + core/types/gen_tx_json.go | 101 ----- core/types/hashing.go | 112 ++++++ core/types/hashing_test.go | 212 +++++++++++ core/types/legacy_tx.go | 111 ++++++ core/types/receipt.go | 101 ++++- core/types/receipt_test.go | 76 +++- core/types/transaction.go | 466 ++++++++++++++---------- core/types/transaction_marshalling.go | 187 ++++++++++ core/types/transaction_signing.go | 259 +++++++++++-- core/types/transaction_test.go | 276 ++++++++++++-- core/vm/interface.go | 1 + core/vm/runtime/runtime.go | 18 +- eth/downloader/downloader.go | 2 +- eth/gasprice/gasprice_test.go | 2 +- eth/tracers/api.go | 5 +- eth/tracers/tracer.go | 2 +- ethclient/ethclient.go | 3 +- ethclient/ethclient_test.go | 2 +- ethclient/signer.go | 3 + graphql/graphql.go | 9 +- interfaces.go | 2 + internal/ethapi/api.go | 169 +++++---- internal/guide/guide_test.go | 4 +- les/benchmark.go | 2 +- les/odr_test.go | 4 +- light/odr_test.go | 2 +- light/txpool.go | 10 +- miner/worker.go | 7 +- miner/worker_test.go | 19 +- params/config.go | 6 +- params/protocol_params.go | 31 +- signer/core/api.go | 5 +- tests/state_test_util.go | 18 +- tests/transaction_test_util.go | 2 +- trie/stacktrie_test.go | 170 --------- 69 files changed, 2446 insertions(+), 909 deletions(-) create mode 100644 cmd/evm/testdata/8/alloc.json create mode 100644 cmd/evm/testdata/8/env.json create mode 100644 cmd/evm/testdata/8/readme.md create mode 100644 cmd/evm/testdata/8/txs.json create mode 100644 core/types/access_list_tx.go delete mode 100644 core/types/derive_sha.go create mode 100644 core/types/gen_access_tuple.go delete mode 100644 core/types/gen_tx_json.go create mode 100644 core/types/hashing.go create mode 100644 core/types/hashing_test.go create mode 100644 core/types/legacy_tx.go create mode 100644 core/types/transaction_marshalling.go diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 1190772676..b8065e8488 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -120,7 +120,7 @@ func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accou if chainID == nil { return nil, ErrNoChainID } - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) return &TransactOpts{ From: account.Address, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { @@ -143,7 +143,7 @@ func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*Tr if chainID == nil { return nil, ErrNoChainID } - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) return &TransactOpts{ From: keyAddr, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 8be364d08f..d6d525eae1 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -559,7 +559,10 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa b.mu.Lock() defer b.mu.Unlock() - sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx) + // Check transaction validity. + block := b.blockchain.CurrentBlock() + signer := types.MakeSigner(b.blockchain.Config(), block.Number()) + sender, err := types.Sender(signer, tx) if err != nil { panic(fmt.Errorf("invalid transaction: %v", err)) } @@ -568,7 +571,8 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) } - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + // Include tx in chain. + blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } @@ -707,14 +711,15 @@ type callMsg struct { ethereum.CallMsg } -func (m callMsg) From() common.Address { return m.CallMsg.From } -func (m callMsg) Nonce() uint64 { return 0 } -func (m callMsg) CheckNonce() bool { return false } -func (m callMsg) To() *common.Address { return m.CallMsg.To } -func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } -func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } -func (m callMsg) Value() *big.Int { return m.CallMsg.Value } -func (m callMsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) From() common.Address { return m.CallMsg.From } +func (m callMsg) Nonce() uint64 { return 0 } +func (m callMsg) CheckNonce() bool { return false } +func (m callMsg) To() *common.Address { return m.CallMsg.To } +func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callMsg) Value() *big.Int { return m.CallMsg.Value } +func (m callMsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 9d5e2cf6a2..88dcfbeb69 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -283,11 +283,9 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b if !found { return nil, ErrLocked } - // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil { - return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) - } - return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey) + // Depending on the presence of the chain ID, sign with 2718 or homestead + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, unlockedKey.PrivateKey) } // SignHashWithPassphrase signs hash if the private key matching the given address @@ -310,12 +308,9 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, return nil, err } defer zeroKey(key.PrivateKey) - - // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil { - return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) - } - return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey) + // Depending on the presence of the chain ID, sign with or without replay protection. + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, key.PrivateKey) } // Unlock unlocks the given account indefinitely. diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 6476646d7f..b4d229bc0b 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -699,7 +699,7 @@ func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) hash := signer.Hash(tx) sig, err := w.signHash(account, hash[:]) if err != nil { diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go index 1892097baf..0546458c47 100644 --- a/accounts/usbwallet/trezor.go +++ b/accounts/usbwallet/trezor.go @@ -255,9 +255,11 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction if chainID == nil { signer = new(types.HomesteadSigner) } else { + // Trezor backend does not support typed transactions yet. signer = types.NewEIP155Signer(chainID) signature[64] -= byte(chainID.Uint64()*2 + 35) } + // Inject the final signature into the transaction and sanity check the sender signed, err := tx.WithSignature(signer, signature) if err != nil { diff --git a/cmd/clef/main.go b/cmd/clef/main.go index dbbb410fb6..8befce88dc 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -1106,7 +1106,7 @@ func GenDoc(ctx *cli.Context) { rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") var tx types.Transaction - rlp.DecodeBytes(rlpdata, &tx) + tx.UnmarshalBinary(rlpdata) add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) } diff --git a/cmd/evm/README.md b/cmd/evm/README.md index 8f0848bde8..7742ccbbb7 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -30,7 +30,7 @@ Command line params that has to be supported are --trace.nomemory Disable full memory dump in traces --trace.nostack Disable stack output in traces --trace.noreturndata Disable return data output in traces - --output.basedir value Specifies where output files are placed. Will be created if it does not exist. (default: ".") + --output.basedir value Specifies where output files are placed. Will be created if it does not exist. --output.alloc alloc Determines where to put the alloc of the post-state. `stdout` - into the stdout output `stderr` - into the stderr output @@ -237,10 +237,10 @@ Example where blockhashes are provided: cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 ``` ``` -{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"PUSH1","error":""} -{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"BLOCKHASH","error":""} -{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"returnData":null,"depth":1,"refund":0,"opName":"STOP","error":""} -{"output":"","gasUsed":"0x17","time":112885} +{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"PUSH1","error":""} +{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"BLOCKHASH","error":""} +{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"STOP","error":""} +{"output":"","gasUsed":"0x17","time":142709} ``` In this example, the caller has not provided the required blockhash: @@ -256,9 +256,9 @@ Error code: 4 Another thing that can be done, is to chain invocations: ``` ./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json -INFO [08-03|15:25:15.168] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" -INFO [08-03|15:25:15.169] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" -INFO [08-03|15:25:15.169] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" +INFO [01-21|22:41:22.963] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.966] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.967] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" ``` What happened here, is that we first applied two identical transactions, so the second one was rejected. @@ -267,4 +267,3 @@ the same two transactions: this time, both failed due to too low nonce. In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the actual blocknumber (exposed to the EVM) would not increase. - diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 525723e3cb..c3f1b16efc 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -143,19 +143,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmConfig.Debug = (tracer != nil) statedb.Prepare(tx.Hash(), blockHash, txIndex) txContext := core.NewEVMTxContext(msg) - - evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) - if chainConfig.IsYoloV3(vmContext.BlockNumber) { - statedb.AddAddressToAccessList(msg.From()) - if dst := msg.To(); dst != nil { - statedb.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range evm.ActivePrecompiles() { - statedb.AddAddressToAccessList(addr) - } - } snapshot := statedb.Snapshot() + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) + // (ret []byte, usedGas uint64, failed bool, err error) msgResult, err := core.ApplyMessage(evm, msg, gaspool) if err != nil { @@ -169,7 +159,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, return nil, nil, NewError(ErrorMissingBlockhash, hashError) } gasUsed += msgResult.UsedGas - // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx + + // Receipt: { var root []byte if chainConfig.IsByzantium(vmContext.BlockNumber) { @@ -178,22 +169,32 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() } - receipt := types.NewReceipt(root, msgResult.Failed(), gasUsed) + // Create a new receipt for the transaction, storing the intermediate root and + // gas used by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: gasUsed} + if msgResult.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } receipt.TxHash = tx.Hash() receipt.GasUsed = msgResult.UsedGas - // if the transaction created a contract, store the creation address in the receipt. + + // If the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - // Set the receipt logs and create a bloom for filtering + + // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - // These three are non-consensus fields + // These three are non-consensus fields: //receipt.BlockHash - //receipt.BlockNumber = + //receipt.BlockNumber receipt.TransactionIndex = uint(txIndex) receipts = append(receipts, receipt) } + txIndex++ } statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index 424156ba82..a599462cc6 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -47,6 +47,11 @@ var ( Usage: "Specifies where output files are placed. Will be created if it does not exist.", Value: "", } + OutputBodyFlag = cli.StringFlag{ + Name: "output.body", + Usage: "If set, the RLP of the transactions (block body) will be written to this file.", + Value: "", + } OutputAllocFlag = cli.StringFlag{ Name: "output.alloc", Usage: "Determines where to put the `alloc` of the post-state.\n" + diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 5119ed5fb7..fedcd12435 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -17,6 +17,7 @@ package t8ntool import ( + "crypto/ecdsa" "encoding/json" "fmt" "io/ioutil" @@ -25,12 +26,15 @@ import ( "path" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" "gopkg.in/urfave/cli.v1" ) @@ -64,9 +68,9 @@ func (n *NumberedError) Code() int { } type input struct { - Alloc core.GenesisAlloc `json:"alloc,omitempty"` - Env *stEnv `json:"env,omitempty"` - Txs types.Transactions `json:"txs,omitempty"` + Alloc core.GenesisAlloc `json:"alloc,omitempty"` + Env *stEnv `json:"env,omitempty"` + Txs []*txWithKey `json:"txs,omitempty"` } func Main(ctx *cli.Context) error { @@ -135,7 +139,7 @@ func Main(ctx *cli.Context) error { txStr = ctx.String(InputTxsFlag.Name) inputData = &input{} ) - + // Figure out the prestate alloc if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { decoder := json.NewDecoder(os.Stdin) decoder.Decode(inputData) @@ -151,7 +155,9 @@ func Main(ctx *cli.Context) error { return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err)) } } + prestate.Pre = inputData.Alloc + // Set the block environment if envStr != stdinSelector { inFile, err := os.Open(envStr) if err != nil { @@ -165,26 +171,8 @@ func Main(ctx *cli.Context) error { } inputData.Env = &env } - - if txStr != stdinSelector { - inFile, err := os.Open(txStr) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) - } - defer inFile.Close() - decoder := json.NewDecoder(inFile) - var txs types.Transactions - if err := decoder.Decode(&txs); err != nil { - return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) - } - inputData.Txs = txs - } - - prestate.Pre = inputData.Alloc prestate.Env = *inputData.Env - txs = inputData.Txs - // Iterate over all the tests, run them and aggregate the results vmConfig := vm.Config{ Tracer: tracer, Debug: (tracer != nil), @@ -200,17 +188,105 @@ func Main(ctx *cli.Context) error { // Set the chain id chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + var txsWithKeys []*txWithKey + if txStr != stdinSelector { + inFile, err := os.Open(txStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + if err := decoder.Decode(&txsWithKeys); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) + } + } else { + txsWithKeys = inputData.Txs + } + // We may have to sign the transactions. + signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number))) + + if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed signing transactions: %v", err)) + } + + // Iterate over all the tests, run them and aggregate the results + // Run the test and aggregate the result state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) if err != nil { return err } + body, _ := rlp.EncodeToBytes(txs) // Dump the excution result - //postAlloc := state.DumpGenesisFormat(false, false, false) collector := make(Alloc) state.DumpToCollector(collector, false, false, false, nil, -1) - return dispatchOutput(ctx, baseDir, result, collector) + return dispatchOutput(ctx, baseDir, result, collector, body) + +} +// txWithKey is a helper-struct, to allow us to use the types.Transaction along with +// a `secretKey`-field, for input +type txWithKey struct { + key *ecdsa.PrivateKey + tx *types.Transaction +} + +func (t *txWithKey) UnmarshalJSON(input []byte) error { + // Read the secretKey, if present + type sKey struct { + Key *common.Hash `json:"secretKey"` + } + var key sKey + if err := json.Unmarshal(input, &key); err != nil { + return err + } + if key.Key != nil { + k := key.Key.Hex()[2:] + if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { + return err + } else { + t.key = ecdsaKey + } + } + // Now, read the transaction itself + var tx types.Transaction + if err := json.Unmarshal(input, &tx); err != nil { + return err + } + t.tx = &tx + return nil +} + +// signUnsignedTransactions converts the input txs to canonical transactions. +// +// The transactions can have two forms, either +// 1. unsigned or +// 2. signed +// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set. +// If so, we sign it here and now, with the given `secretKey` +// If the condition above is not met, then it's considered a signed transaction. +// +// To manage this, we read the transactions twice, first trying to read the secretKeys, +// and secondly to read them with the standard tx json format +func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) { + var signedTxs []*types.Transaction + for i, txWithKey := range txs { + tx := txWithKey.tx + key := txWithKey.key + v, r, s := tx.RawSignatureValues() + if key != nil && v.BitLen()+r.BitLen()+s.BitLen() == 0 { + // This transaction needs to be signed + signed, err := types.SignTx(tx, signer, key) + if err != nil { + return nil, NewError(ErrorJson, fmt.Errorf("Tx %d: failed to sign tx: %v", i, err)) + } + signedTxs = append(signedTxs, signed) + } else { + // Already signed + signedTxs = append(signedTxs, tx) + } + } + return signedTxs, nil } type Alloc map[common.Address]core.GenesisAccount @@ -241,15 +317,17 @@ func saveFile(baseDir, filename string, data interface{}) error { if err != nil { return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) } - if err = ioutil.WriteFile(path.Join(baseDir, filename), b, 0644); err != nil { + location := path.Join(baseDir, filename) + if err = ioutil.WriteFile(location, b, 0644); err != nil { return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) } + log.Info("Wrote file", "file", location) return nil } // dispatchOutput writes the output data to either stderr or stdout, or to the specified // files -func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc) error { +func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error { stdOutObject := make(map[string]interface{}) stdErrObject := make(map[string]interface{}) dispatch := func(baseDir, fName, name string, obj interface{}) error { @@ -258,6 +336,8 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a stdOutObject[name] = obj case "stderr": stdErrObject[name] = obj + case "": + // don't save default: // save to file if err := saveFile(baseDir, fName, obj); err != nil { return err @@ -271,6 +351,9 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil { return err } + if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil { + return err + } if len(stdOutObject) > 0 { b, err := json.MarshalIndent(stdOutObject, "", " ") if err != nil { diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 35c672142d..8a3e4e0ea2 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -149,6 +149,7 @@ var stateTransitionCommand = cli.Command{ t8ntool.OutputBasedir, t8ntool.OutputAllocFlag, t8ntool.OutputResultFlag, + t8ntool.OutputBodyFlag, t8ntool.InputAllocFlag, t8ntool.InputEnvFlag, t8ntool.InputTxsFlag, diff --git a/cmd/evm/testdata/8/alloc.json b/cmd/evm/testdata/8/alloc.json new file mode 100644 index 0000000000..1d1b5f86c6 --- /dev/null +++ b/cmd/evm/testdata/8/alloc.json @@ -0,0 +1,11 @@ +{ + "0x000000000000000000000000000000000000aaaa": { + "balance": "0x03", + "code": "0x5854505854", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x100000", + "nonce": "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/env.json b/cmd/evm/testdata/8/env.json new file mode 100644 index 0000000000..8b91934724 --- /dev/null +++ b/cmd/evm/testdata/8/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x1000000000", + "currentNumber": "0x1000000", + "currentTimestamp": "0x04" +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md new file mode 100644 index 0000000000..778fc6151a --- /dev/null +++ b/cmd/evm/testdata/8/readme.md @@ -0,0 +1,63 @@ +## EIP-2930 testing + +This test contains testcases for EIP-2930, which uses transactions with access lists. + +### Prestate + +The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the +following code: `0x5854505854`: `PC ;SLOAD; POP; PC; SLOAD`. + +Essentialy, this contract does `SLOAD(0)` and `SLOAD(3)`. + +The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. + +## Transactions + +There are three transactions, each invokes the contract above. + +1. ACL-transaction, which contains some non-used slots +2. Regular transaction +3. ACL-transaction, which contains the slots `1` and `3` in `0x000000000000000000000000000000000000aaaa` + +## Execution + +Running it yields: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace && cat trace-* | grep SLOAD +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} + +``` + +Simlarly, we can provide the input transactions via `stdin` instead of as file: + +``` +dir=./testdata/8 \ + && cat $dir/txs.json | jq "{txs: .}" \ + | ./evm t8n --state.fork=Berlin \ + --input.alloc=$dir/alloc.json \ + --input.txs=stdin \ + --input.env=$dir/env.json \ + --trace \ + && cat trace-* | grep SLOAD + +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +``` + +If we try to execute it on older rules: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Istanbul --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json +INFO [01-21|23:21:51.265] rejected tx index=0 hash="d2818d…6ab3da" error="tx type not supported" +INFO [01-21|23:21:51.265] rejected tx index=1 hash="26ea00…81c01b" from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" +INFO [01-21|23:21:51.265] rejected tx index=2 hash="698d01…369cee" error="tx type not supported" +``` +Number `1` and `3` are not applicable, and therefore number `2` has wrong nonce. \ No newline at end of file diff --git a/cmd/evm/testdata/8/txs.json b/cmd/evm/testdata/8/txs.json new file mode 100644 index 0000000000..35142ba234 --- /dev/null +++ b/cmd/evm/testdata/8/txs.json @@ -0,0 +1,58 @@ +[ + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x0000000000000000000000000000000000000000", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x2", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x2", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b4c622ce2e..a3af44f6c4 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -140,9 +140,7 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - // YOLOv3 is not yet complete! - // TODO: enable this once 2718/2930 is added - //utils.YoloV3Flag, + utils.YoloV3Flag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, @@ -286,6 +284,9 @@ func prepare(ctx *cli.Context) { case ctx.GlobalIsSet(utils.GoerliFlag.Name): log.Info("Starting Geth on Görli testnet...") + case ctx.GlobalIsSet(utils.YoloV3Flag.Name): + log.Info("Starting Geth on YOLOv3 testnet...") + case ctx.GlobalIsSet(utils.DeveloperFlag.Name): log.Info("Starting Geth in ephemeral dev mode...") diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 24215f55a8..6935adabc7 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -44,8 +44,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, - // TODO: Re-enable this when 2718/2930 is added - //utils.YoloV3Flag, + utils.YoloV3Flag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9fff183a14..57547d0c49 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1277,7 +1277,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") case ctx.GlobalBool(YoloV3Flag.Name) && cfg.DataDir == node.DefaultDataDir(): - cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v2") + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v3") } } @@ -1609,7 +1609,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) case ctx.GlobalBool(YoloV3Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3")).Uint64() // "yolov3" + cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3x")).Uint64() // "yolov3x" } cfg.Genesis = core.DefaultYoloV3GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): diff --git a/core/bench_test.go b/core/bench_test.go index 0f4cabd837..85653ea5db 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { return func(i int, gen *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) - gas, _ := IntrinsicGas(data, false, false, false) + gas, _ := IntrinsicGas(data, nil, false, false, false) tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey) gen.AddTx(tx) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index c33e7321ec..3e4757f8b6 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -600,7 +600,7 @@ func TestFastVsFullChains(t *testing.T) { Alloc: GenesisAlloc{address: {Balance: funds}}, } genesis = gspec.MustCommit(gendb) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) { block.SetCoinbase(common.Address{0x00}) @@ -839,7 +839,7 @@ func TestChainTxReorgs(t *testing.T) { }, } genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) // Create two transactions shared between the chains: @@ -944,7 +944,7 @@ func TestLogReorgs(t *testing.T) { code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -998,7 +998,7 @@ func TestLogRebirth(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker() blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil) ) @@ -1062,7 +1062,7 @@ func TestSideLogRebirth(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) ) @@ -1135,7 +1135,7 @@ func TestReorgSideEvent(t *testing.T) { Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}, } genesis = gspec.MustCommit(db) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -1295,7 +1295,7 @@ func TestEIP155Transition(t *testing.T) { } block.AddTx(tx) - tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID)) + tx, err = basicTx(types.LatestSigner(gspec.Config)) if err != nil { t.Fatal(err) } @@ -1307,7 +1307,7 @@ func TestEIP155Transition(t *testing.T) { } block.AddTx(tx) - tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID)) + tx, err = basicTx(types.LatestSigner(gspec.Config)) if err != nil { t.Fatal(err) } @@ -1345,7 +1345,7 @@ func TestEIP155Transition(t *testing.T) { } ) if i == 0 { - tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2))) + tx, err = basicTx(types.LatestSigner(config)) if err != nil { t.Fatal(err) } @@ -1385,7 +1385,7 @@ func TestEIP161AccountRemoval(t *testing.T) { var ( tx *types.Transaction err error - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) switch i { case 0: @@ -2078,7 +2078,7 @@ func TestTransactionIndices(t *testing.T) { funds = big.NewInt(1000000000) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} genesis = gspec.MustCommit(gendb) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) height := uint64(128) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), func(i int, block *BlockGen) { @@ -2205,7 +2205,7 @@ func TestSkipStaleTxIndicesInFastSync(t *testing.T) { funds = big.NewInt(1000000000) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} genesis = gspec.MustCommit(gendb) - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) height := uint64(128) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), func(i int, block *BlockGen) { @@ -3030,3 +3030,81 @@ func TestInitThenFailCreateContract(t *testing.T) { } } } + +// TestEIP2718Transition tests that an EIP-2718 transaction will be accepted +// after the fork block has passed. This is verified by sending an EIP-2930 +// access list transaction, which specifies a single slot access, and then +// checking that the gas usage of a hot SLOAD and a cold SLOAD are calculated +// correctly. +func TestEIP2718Transition(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &Genesis{ + Config: params.YoloV3ChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + genesis = gspec.MustCommit(db) + ) + + blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + + // One transaction to 0xAAAA + signer := types.LatestSigner(gspec.Config) + tx, _ := types.SignNewTx(key, signer, &types.AccessListTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 30000, + GasPrice: big.NewInt(1), + AccessList: types.AccessList{{ + Address: aa, + StorageKeys: []common.Hash{{0}}, + }}, + }) + b.AddTx(tx) + }) + + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list + expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + vm.GasQuickStep*2 + vm.WarmStorageReadCostEIP2929 + vm.ColdSloadCostEIP2929 + if block.GasUsed() != expected { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed()) + + } +} diff --git a/core/error.go b/core/error.go index 5a28be7e1c..197dd81567 100644 --- a/core/error.go +++ b/core/error.go @@ -16,7 +16,11 @@ package core -import "errors" +import ( + "errors" + + "github.com/ethereum/go-ethereum/core/types" +) var ( // ErrKnownBlock is returned when a block to import is already known locally. @@ -63,4 +67,8 @@ var ( // ErrIntrinsicGas is returned if the transaction is specified to use less gas // than required to start the invocation. ErrIntrinsicGas = errors.New("intrinsic gas too low") + + // ErrTxTypeNotSupported is returned if a transaction is not supported in the + // current network configuration. + ErrTxTypeNotSupported = types.ErrTxTypeNotSupported ) diff --git a/core/genesis.go b/core/genesis.go index 462aec45bd..6bcc50b050 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -377,11 +377,11 @@ func DefaultGoerliGenesisBlock() *Genesis { } func DefaultYoloV3GenesisBlock() *Genesis { - // Full genesis: https://gist.github.com/holiman/b2c32a05ff2e2712e11c0787d987d46f + // Full genesis: https://gist.github.com/holiman/c6ed9269dce28304ad176314caa75e97 return &Genesis{ Config: params.YoloV3ChainConfig, - Timestamp: 0x60117f8b, - ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + Timestamp: 0x6027dd2e, + ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001041afbcb359d5a8dc58c15b2ff51354ff8a217d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), GasLimit: 0x47b760, Difficulty: big.NewInt(1), Alloc: decodePrealloc(yoloV3AllocData), diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 6eecbbf0e8..5b0e933d7a 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -25,5 +25,4 @@ const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908' const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" -const yoloV3AllocData = "\xf9\x05\x01\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - +const yoloV3AllocData = "\xf9\x05o\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x10A\xaf\xbc\xb3Y\u0568\xdcX\xc1[/\xf5\x13T\xff\x8a!}\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xdf\n\x88\xb2\xb6\x8cg7\x13\xa8\xec\x82`\x03go'.5s\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/core/state/statedb.go b/core/state/statedb.go index 8fd066e24f..2e5d6e47c8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -983,6 +983,32 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { return root, err } +// PrepareAccessList handles the preparatory steps for executing a state transition with +// regards to both EIP-2929 and EIP-2930: +// +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. +func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + s.AddAddressToAccessList(sender) + if dst != nil { + s.AddAddressToAccessList(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + s.AddAddressToAccessList(addr) + } + for _, el := range list { + s.AddAddressToAccessList(el.Address) + for _, key := range el.StorageKeys { + s.AddSlotToAccessList(el.Address, key) + } + } +} + // AddAddressToAccessList adds the given address to the access list func (s *StateDB) AddAddressToAccessList(addr common.Address) { if s.accessList.AddAddress(addr) { diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 6fa52c2d9b..05394321f7 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -19,7 +19,6 @@ package core import ( "sync/atomic" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -50,8 +49,11 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse // only goal is to pre-cache transaction signatures and state trie nodes. func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *uint32) { var ( - header = block.Header() - gaspool = new(GasPool).AddGas(block.GasLimit()) + header = block.Header() + gaspool = new(GasPool).AddGas(block.GasLimit()) + blockContext = NewEVMBlockContext(header, p.bc, nil) + evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.config, header.Number) ) // Iterate over and process the individual transactions byzantium := p.config.IsByzantium(block.Number()) @@ -60,9 +62,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c if interrupt != nil && atomic.LoadUint32(interrupt) == 1 { return } - // Block precaching permitted to continue, execute the transaction + // Convert the transaction into an executable message and pre-cache its sender + msg, err := tx.AsMessage(signer) + if err != nil { + return // Also invalid block, bail out + } statedb.Prepare(tx.Hash(), block.Hash(), i) - if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, header, tx, cfg); err != nil { + if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { return // Ugh, something went horribly wrong, bail out } // If we're pre-byzantium, pre-load trie nodes for the intermediate root @@ -79,17 +85,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error { - // Convert the transaction into an executable message and pre-cache its sender - msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) - if err != nil { - return err - } - // Create the EVM and execute the transaction - context := NewEVMBlockContext(header, bc, author) - txContext := NewEVMTxContext(msg) - vm := vm.NewEVM(context, txContext, statedb, config, cfg) - - _, err = ApplyMessage(vm, msg, gaspool) +func precacheTransaction(msg types.Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { + // Update the evm with the new transaction context. + evm.Reset(NewEVMTxContext(msg), statedb) + // Add addresses to access list if applicable + _, err := ApplyMessage(evm, msg, gaspool) return err } diff --git a/core/state_processor.go b/core/state_processor.go index c5b0bb1609..40a953f0d4 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -90,28 +90,17 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { - // Create a new context to be used in the EVM environment + // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) - // Add addresses to access list if applicable - if config.IsYoloV3(header.Number) { - statedb.AddAddressToAccessList(msg.From()) - if dst := msg.To(); dst != nil { - statedb.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range evm.ActivePrecompiles() { - statedb.AddAddressToAccessList(addr) - } - } - - // Update the evm with the new transaction context. evm.Reset(txContext, statedb) - // Apply the transaction to the current state (included in the env) + + // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { return nil, err } - // Update the state with pending changes + + // Update the state with pending changes. var root []byte if config.IsByzantium(header.Number) { statedb.Finalise(true) @@ -120,22 +109,28 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon } *usedGas += result.UsedGas - // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx - // based on the eip phase, we're passing whether the root touch-delete accounts. - receipt := types.NewReceipt(root, result.Failed(), *usedGas) + // Create a new receipt for the transaction, storing the intermediate root and gas used + // by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas - // if the transaction created a contract, store the creation address in the receipt. + + // If the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - // Set the receipt logs and create a bloom for filtering + + // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.BlockHash = statedb.BlockHash() receipt.BlockNumber = header.Number receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, err } diff --git a/core/state_transition.go b/core/state_transition.go index a8d1936c1f..0d589a6119 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" ) @@ -67,6 +68,7 @@ type Message interface { Nonce() uint64 CheckNonce() bool Data() []byte + AccessList() types.AccessList } // ExecutionResult includes all output after executing given evm @@ -105,10 +107,10 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) { // Set the starting gas for the raw transaction var gas uint64 - if contractCreation && isHomestead { + if isContractCreation && isHomestead { gas = params.TxGasContractCreation } else { gas = params.TxGas @@ -138,6 +140,10 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo } gas += z * params.TxDataZeroGas } + if accessList != nil { + gas += uint64(len(accessList)) * params.TxAccessListAddressGas + gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas + } return gas, nil } @@ -238,7 +244,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { contractCreation := msg.To() == nil // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul) + gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul) if err != nil { return nil, err } @@ -251,6 +257,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex()) } + + // Set up the initial access list. + if st.evm.ChainConfig().IsYoloV3(st.evm.Context.BlockNumber) { + st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(), msg.AccessList()) + } + var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err diff --git a/core/tx_pool.go b/core/tx_pool.go index 36546cde75..28ac822131 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -229,6 +229,7 @@ type TxPool struct { mu sync.RWMutex istanbul bool // Fork indicator whether we are in the istanbul stage. + eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. currentState *state.StateDB // Current state in the blockchain head pendingNonces *txNoncer // Pending state tracking virtual nonces @@ -268,7 +269,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block config: config, chainconfig: chainconfig, chain: chain, - signer: types.NewEIP155Signer(chainconfig.ChainID), + signer: types.LatestSigner(chainconfig), pending: make(map[common.Address]*txList), queue: make(map[common.Address]*txList), beats: make(map[common.Address]time.Time), @@ -522,6 +523,10 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { + // Accept only legacy transactions until EIP-2718/2930 activates. + if !pool.eip2718 && tx.Type() != types.LegacyTxType { + return ErrTxTypeNotSupported + } // Reject transactions over defined size to prevent DOS attacks if uint64(tx.Size()) > txMaxSize { return ErrOversizedData @@ -535,7 +540,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if pool.currentMaxGas < tx.Gas() { return ErrGasLimit } - // Make sure the transaction is signed properly + // Make sure the transaction is signed properly. from, err := types.Sender(pool.signer, tx) if err != nil { return ErrInvalidSender @@ -554,7 +559,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { return ErrInsufficientFunds } // Ensure the transaction has more gas than the basic tx fee. - intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul) + intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) if err != nil { return err } @@ -1199,6 +1204,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // Update all fork indicator by next pending block number. next := new(big.Int).Add(newHead.Number, big.NewInt(1)) pool.istanbul = pool.chainconfig.IsIstanbul(next) + pool.eip2718 = pool.chainconfig.IsYoloV3(next) } // promoteExecutables moves transactions that have become processable from the diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go new file mode 100644 index 0000000000..65ee95adf6 --- /dev/null +++ b/core/types/access_list_tx.go @@ -0,0 +1,115 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +//go:generate gencodec -type AccessTuple -out gen_access_tuple.go + +// AccessList is an EIP-2930 access list. +type AccessList []AccessTuple + +// AccessTuple is the element type of an access list. +type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` +} + +// StorageKeys returns the total number of storage keys in the access list. +func (al AccessList) StorageKeys() int { + sum := 0 + for _, tuple := range al { + sum += len(tuple.StorageKeys) + } + return sum +} + +// AccessListTx is the data of EIP-2930 access list transactions. +type AccessListTx struct { + ChainID *big.Int // destination chain ID + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + AccessList AccessList // EIP-2930 access list + V, R, S *big.Int // signature values +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *AccessListTx) copy() TxData { + cpy := &AccessListTx{ + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasPrice: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. + +func (tx *AccessListTx) txType() byte { return AccessListTxType } +func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } +func (tx *AccessListTx) protected() bool { return true } +func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } +func (tx *AccessListTx) data() []byte { return tx.Data } +func (tx *AccessListTx) gas() uint64 { return tx.Gas } +func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) value() *big.Int { return tx.Value } +func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } +func (tx *AccessListTx) to() *common.Address { return tx.To } + +func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} diff --git a/core/types/block.go b/core/types/block.go index 8096ebb755..553db003bb 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -23,15 +23,12 @@ import ( "io" "math/big" "reflect" - "sync" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) var ( @@ -131,22 +128,6 @@ func (h *Header) SanityCheck() error { return nil } -// hasherPool holds LegacyKeccak hashers. -var hasherPool = sync.Pool{ - New: func() interface{} { - return sha3.NewLegacyKeccak256() - }, -} - -func rlpHash(x interface{}) (h common.Hash) { - sha := hasherPool.Get().(crypto.KeccakState) - defer hasherPool.Put(sha) - sha.Reset() - rlp.Encode(sha, x) - sha.Read(h[:]) - return h -} - // EmptyBody returns true if there is no additional 'body' to complete the header // that is: no transactions and no uncles. func (h *Header) EmptyBody() bool { @@ -221,7 +202,7 @@ type storageblock struct { // The values of TxHash, UncleHash, ReceiptHash and Bloom in header // are ignored and set to values derived from the given txs, uncles // and receipts. -func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher Hasher) *Block { +func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block { b := &Block{header: CopyHeader(header), td: new(big.Int)} // TODO: panic if len(txs) != len(receipts) diff --git a/core/types/block_test.go b/core/types/block_test.go index 4dfdcf9545..63904f882c 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -59,6 +59,66 @@ func TestBlockEncoding(t *testing.T) { tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) check("len(Transactions)", len(block.Transactions()), 1) check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + +func TestEIP2718BlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f90319f90211a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(42000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), common.StorageSize(len(blockEnc))) + + // Create legacy tx. + to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + tx1 := NewTx(&LegacyTx{ + Nonce: 0, + To: &to, + Value: big.NewInt(10), + Gas: 50000, + GasPrice: big.NewInt(10), + }) + sig := common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100") + tx1, _ = tx1.WithSignature(HomesteadSigner{}, sig) + + // Create ACL tx. + addr := common.HexToAddress("0x0000000000000000000000000000000000000001") + tx2 := NewTx(&AccessListTx{ + ChainID: big.NewInt(1), + Nonce: 0, + To: &to, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}}, + }) + sig2 := common.Hex2Bytes("3dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef1401") + tx2, _ = tx2.WithSignature(NewEIP2930Signer(big.NewInt(1)), sig2) + + check("len(Transactions)", len(block.Transactions()), 2) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) + check("Transactions[1].Type()", block.Transactions()[1].Type(), uint8(AccessListTxType)) ourBlockEnc, err := rlp.EncodeToBytes(&block) if err != nil { @@ -121,7 +181,7 @@ func makeBenchBlock() *Block { key, _ = crypto.GenerateKey() txs = make([]*Transaction, 70) receipts = make([]*Receipt, len(txs)) - signer = NewEIP155Signer(params.TestChainConfig.ChainID) + signer = LatestSigner(params.TestChainConfig) uncles = make([]*Header, 3) ) header := &Header{ diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go deleted file mode 100644 index 51a10f3f3d..0000000000 --- a/core/types/derive_sha.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package types - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" -) - -type DerivableList interface { - Len() int - GetRlp(i int) []byte -} - -// Hasher is the tool used to calculate the hash of derivable list. -type Hasher interface { - Reset() - Update([]byte, []byte) - Hash() common.Hash -} - -func DeriveSha(list DerivableList, hasher Hasher) common.Hash { - hasher.Reset() - - // StackTrie requires values to be inserted in increasing - // hash order, which is not the order that `list` provides - // hashes in. This insertion sequence ensures that the - // order is correct. - - var buf []byte - for i := 1; i < list.Len() && i <= 0x7f; i++ { - buf = rlp.AppendUint64(buf[:0], uint64(i)) - hasher.Update(buf, list.GetRlp(i)) - } - if list.Len() > 0 { - buf = rlp.AppendUint64(buf[:0], 0) - hasher.Update(buf, list.GetRlp(0)) - } - for i := 0x80; i < list.Len(); i++ { - buf = rlp.AppendUint64(buf[:0], uint64(i)) - hasher.Update(buf, list.GetRlp(i)) - } - return hasher.Hash() -} diff --git a/core/types/gen_access_tuple.go b/core/types/gen_access_tuple.go new file mode 100644 index 0000000000..fc48a84cc0 --- /dev/null +++ b/core/types/gen_access_tuple.go @@ -0,0 +1,43 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package types + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" +) + +// MarshalJSON marshals as JSON. +func (a AccessTuple) MarshalJSON() ([]byte, error) { + type AccessTuple struct { + Address common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` + } + var enc AccessTuple + enc.Address = a.Address + enc.StorageKeys = a.StorageKeys + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *AccessTuple) UnmarshalJSON(input []byte) error { + type AccessTuple struct { + Address *common.Address `json:"address" gencodec:"required"` + StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` + } + var dec AccessTuple + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Address == nil { + return errors.New("missing required field 'address' for AccessTuple") + } + a.Address = *dec.Address + if dec.StorageKeys == nil { + return errors.New("missing required field 'storageKeys' for AccessTuple") + } + a.StorageKeys = dec.StorageKeys + return nil +} diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 790ed65b58..bb892f85be 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -16,6 +16,7 @@ var _ = (*receiptMarshaling)(nil) // MarshalJSON marshals as JSON. func (r Receipt) MarshalJSON() ([]byte, error) { type Receipt struct { + Type hexutil.Uint64 `json:"type,omitempty"` PostState hexutil.Bytes `json:"root"` Status hexutil.Uint64 `json:"status"` CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` @@ -29,6 +30,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { TransactionIndex hexutil.Uint `json:"transactionIndex"` } var enc Receipt + enc.Type = hexutil.Uint64(r.Type) enc.PostState = r.PostState enc.Status = hexutil.Uint64(r.Status) enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) @@ -46,6 +48,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (r *Receipt) UnmarshalJSON(input []byte) error { type Receipt struct { + Type *hexutil.Uint64 `json:"type,omitempty"` PostState *hexutil.Bytes `json:"root"` Status *hexutil.Uint64 `json:"status"` CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` @@ -62,6 +65,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if err := json.Unmarshal(input, &dec); err != nil { return err } + if dec.Type != nil { + r.Type = uint8(*dec.Type) + } if dec.PostState != nil { r.PostState = *dec.PostState } diff --git a/core/types/gen_tx_json.go b/core/types/gen_tx_json.go deleted file mode 100644 index e676058ecc..0000000000 --- a/core/types/gen_tx_json.go +++ /dev/null @@ -1,101 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package types - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -var _ = (*txdataMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (t txdata) MarshalJSON() ([]byte, error) { - type txdata struct { - AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"` - Price *hexutil.Big `json:"gasPrice" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gas" gencodec:"required"` - Recipient *common.Address `json:"to" rlp:"nil"` - Amount *hexutil.Big `json:"value" gencodec:"required"` - Payload hexutil.Bytes `json:"input" gencodec:"required"` - V *hexutil.Big `json:"v" gencodec:"required"` - R *hexutil.Big `json:"r" gencodec:"required"` - S *hexutil.Big `json:"s" gencodec:"required"` - Hash *common.Hash `json:"hash" rlp:"-"` - } - var enc txdata - enc.AccountNonce = hexutil.Uint64(t.AccountNonce) - enc.Price = (*hexutil.Big)(t.Price) - enc.GasLimit = hexutil.Uint64(t.GasLimit) - enc.Recipient = t.Recipient - enc.Amount = (*hexutil.Big)(t.Amount) - enc.Payload = t.Payload - enc.V = (*hexutil.Big)(t.V) - enc.R = (*hexutil.Big)(t.R) - enc.S = (*hexutil.Big)(t.S) - enc.Hash = t.Hash - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (t *txdata) UnmarshalJSON(input []byte) error { - type txdata struct { - AccountNonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` - Price *hexutil.Big `json:"gasPrice" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gas" gencodec:"required"` - Recipient *common.Address `json:"to" rlp:"nil"` - Amount *hexutil.Big `json:"value" gencodec:"required"` - Payload *hexutil.Bytes `json:"input" gencodec:"required"` - V *hexutil.Big `json:"v" gencodec:"required"` - R *hexutil.Big `json:"r" gencodec:"required"` - S *hexutil.Big `json:"s" gencodec:"required"` - Hash *common.Hash `json:"hash" rlp:"-"` - } - var dec txdata - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.AccountNonce == nil { - return errors.New("missing required field 'nonce' for txdata") - } - t.AccountNonce = uint64(*dec.AccountNonce) - if dec.Price == nil { - return errors.New("missing required field 'gasPrice' for txdata") - } - t.Price = (*big.Int)(dec.Price) - if dec.GasLimit == nil { - return errors.New("missing required field 'gas' for txdata") - } - t.GasLimit = uint64(*dec.GasLimit) - if dec.Recipient != nil { - t.Recipient = dec.Recipient - } - if dec.Amount == nil { - return errors.New("missing required field 'value' for txdata") - } - t.Amount = (*big.Int)(dec.Amount) - if dec.Payload == nil { - return errors.New("missing required field 'input' for txdata") - } - t.Payload = *dec.Payload - if dec.V == nil { - return errors.New("missing required field 'v' for txdata") - } - t.V = (*big.Int)(dec.V) - if dec.R == nil { - return errors.New("missing required field 'r' for txdata") - } - t.R = (*big.Int)(dec.R) - if dec.S == nil { - return errors.New("missing required field 's' for txdata") - } - t.S = (*big.Int)(dec.S) - if dec.Hash != nil { - t.Hash = dec.Hash - } - return nil -} diff --git a/core/types/hashing.go b/core/types/hashing.go new file mode 100644 index 0000000000..71efb25a9a --- /dev/null +++ b/core/types/hashing.go @@ -0,0 +1,112 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "bytes" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/sha3" +) + +// hasherPool holds LegacyKeccak256 hashers for rlpHash. +var hasherPool = sync.Pool{ + New: func() interface{} { return sha3.NewLegacyKeccak256() }, +} + +// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding. +var encodeBufferPool = sync.Pool{ + New: func() interface{} { return new(bytes.Buffer) }, +} + +func rlpHash(x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// prefixedRlpHash writes the prefix into the hasher before rlp-encoding the +// given interface. It's used for typed transactions. +func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + sha.Write([]byte{prefix}) + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} + +// TrieHasher is the tool used to calculate the hash of derivable list. +// This is internal, do not use. +type TrieHasher interface { + Reset() + Update([]byte, []byte) + Hash() common.Hash +} + +// DerivableList is the input to DeriveSha. +// It is implemented by the 'Transactions' and 'Receipts' types. +// This is internal, do not use these methods. +type DerivableList interface { + Len() int + EncodeIndex(int, *bytes.Buffer) +} + +func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { + buf.Reset() + list.EncodeIndex(i, buf) + // It's really unfortunate that we need to do perform this copy. + // StackTrie holds onto the values until Hash is called, so the values + // written to it must not alias. + return common.CopyBytes(buf.Bytes()) +} + +// DeriveSha creates the tree hashes of transactions and receipts in a block header. +func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { + hasher.Reset() + + valueBuf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(valueBuf) + + // StackTrie requires values to be inserted in increasing hash order, which is not the + // order that `list` provides hashes in. This insertion sequence ensures that the + // order is correct. + var indexBuf []byte + for i := 1; i < list.Len() && i <= 0x7f; i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + if list.Len() > 0 { + indexBuf = rlp.AppendUint64(indexBuf[:0], 0) + value := encodeForDerive(list, 0, valueBuf) + hasher.Update(indexBuf, value) + } + for i := 0x80; i < list.Len(); i++ { + indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) + value := encodeForDerive(list, i, valueBuf) + hasher.Update(indexBuf, value) + } + return hasher.Hash() +} diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go new file mode 100644 index 0000000000..a948b10ef6 --- /dev/null +++ b/core/types/hashing_test.go @@ -0,0 +1,212 @@ +package types_test + +import ( + "bytes" + "fmt" + "io" + "math/big" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +func TestDeriveSha(t *testing.T) { + txs, err := genTxs(0) + if err != nil { + t.Fatal(err) + } + for len(txs) < 1000 { + exp := types.DeriveSha(txs, new(trie.Trie)) + got := types.DeriveSha(txs, trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) + } + newTxs, err := genTxs(uint64(len(txs) + 1)) + if err != nil { + t.Fatal(err) + } + txs = append(txs, newTxs...) + } +} + +// TestEIP2718DeriveSha tests that the input to the DeriveSha function is correct. +func TestEIP2718DeriveSha(t *testing.T) { + for _, tc := range []struct { + rlpData string + exp string + }{ + { + rlpData: "0xb8a701f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9", + exp: "01 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n80 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n", + }, + } { + d := &hashToHumanReadable{} + var t1, t2 types.Transaction + rlp.DecodeBytes(common.FromHex(tc.rlpData), &t1) + rlp.DecodeBytes(common.FromHex(tc.rlpData), &t2) + txs := types.Transactions{&t1, &t2} + types.DeriveSha(txs, d) + if tc.exp != string(d.data) { + t.Fatalf("Want\n%v\nhave:\n%v", tc.exp, string(d.data)) + } + } +} + +func BenchmarkDeriveSha200(b *testing.B) { + txs, err := genTxs(200) + if err != nil { + b.Fatal(err) + } + var exp common.Hash + var got common.Hash + b.Run("std_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + exp = types.DeriveSha(txs, new(trie.Trie)) + } + }) + + b.Run("stack_trie", func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got = types.DeriveSha(txs, trie.NewStackTrie(nil)) + } + }) + if got != exp { + b.Errorf("got %x exp %x", got, exp) + } +} + +func TestFuzzDeriveSha(t *testing.T) { + // increase this for longer runs -- it's set to quite low for travis + rndSeed := mrand.Int() + for i := 0; i < 10; i++ { + seed := rndSeed + i + exp := types.DeriveSha(newDummy(i), new(trie.Trie)) + got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + printList(newDummy(seed)) + t.Fatalf("seed %d: got %x exp %x", seed, got, exp) + } + } +} + +// TestDerivableList contains testcases found via fuzzing +func TestDerivableList(t *testing.T) { + type tcase []string + tcs := []tcase{ + { + "0xc041", + }, + { + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + "0xf04cf757812428b0763112efb33b6f4fad7deb445e", + }, + { + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", + "0x14abd5c47c0be87b0454596baad2", + "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", + }, + } + for i, tc := range tcs[1:] { + exp := types.DeriveSha(flatList(tc), new(trie.Trie)) + got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) + if !bytes.Equal(got[:], exp[:]) { + t.Fatalf("case %d: got %x exp %x", i, got, exp) + } + } +} + +func genTxs(num uint64) (types.Transactions, error) { + key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + if err != nil { + return nil, err + } + var addr = crypto.PubkeyToAddress(key.PublicKey) + newTx := func(i uint64) (*types.Transaction, error) { + signer := types.NewEIP155Signer(big.NewInt(18)) + utx := types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil) + tx, err := types.SignTx(utx, signer, key) + return tx, err + } + var txs types.Transactions + for i := uint64(0); i < num; i++ { + tx, err := newTx(i) + if err != nil { + return nil, err + } + txs = append(txs, tx) + } + return txs, nil +} + +type dummyDerivableList struct { + len int + seed int +} + +func newDummy(seed int) *dummyDerivableList { + d := &dummyDerivableList{} + src := mrand.NewSource(int64(seed)) + // don't use lists longer than 4K items + d.len = int(src.Int63() & 0x0FFF) + d.seed = seed + return d +} + +func (d *dummyDerivableList) Len() int { + return d.len +} + +func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) { + src := mrand.NewSource(int64(d.seed + i)) + // max item size 256, at least 1 byte per item + size := 1 + src.Int63()&0x00FF + io.CopyN(w, mrand.New(src), size) +} + +func printList(l types.DerivableList) { + fmt.Printf("list length: %d\n", l.Len()) + fmt.Printf("{\n") + for i := 0; i < l.Len(); i++ { + var buf bytes.Buffer + l.EncodeIndex(i, &buf) + fmt.Printf("\"0x%x\",\n", buf.Bytes()) + } + fmt.Printf("},\n") +} + +type flatList []string + +func (f flatList) Len() int { + return len(f) +} +func (f flatList) EncodeIndex(i int, w *bytes.Buffer) { + w.Write(hexutil.MustDecode(f[i])) +} + +type hashToHumanReadable struct { + data []byte +} + +func (d *hashToHumanReadable) Reset() { + d.data = make([]byte, 0) +} + +func (d *hashToHumanReadable) Update(i []byte, i2 []byte) { + l := fmt.Sprintf("%x %x\n", i, i2) + d.data = append(d.data, []byte(l)...) +} + +func (d *hashToHumanReadable) Hash() common.Hash { + return common.Hash{} +} diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go new file mode 100644 index 0000000000..41ad44f379 --- /dev/null +++ b/core/types/legacy_tx.go @@ -0,0 +1,111 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// LegacyTx is the transaction data of regular Ethereum transactions. +type LegacyTx struct { + Nonce uint64 // nonce of sender account + GasPrice *big.Int // wei per gas + Gas uint64 // gas limit + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int // wei amount + Data []byte // contract invocation input data + V, R, S *big.Int // signature values +} + +// NewTransaction creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + To: &to, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// NewContractCreation creates an unsigned legacy transaction. +// Deprecated: use NewTx instead. +func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { + return NewTx(&LegacyTx{ + Nonce: nonce, + Value: amount, + Gas: gasLimit, + GasPrice: gasPrice, + Data: data, + }) +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *LegacyTx) copy() TxData { + cpy := &LegacyTx{ + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are initialized below. + Value: new(big.Int), + GasPrice: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.GasPrice != nil { + cpy.GasPrice.Set(tx.GasPrice) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. + +func (tx *LegacyTx) txType() byte { return LegacyTxType } +func (tx *LegacyTx) chainID() *big.Int { return deriveChainId(tx.V) } +func (tx *LegacyTx) accessList() AccessList { return nil } +func (tx *LegacyTx) data() []byte { return tx.Data } +func (tx *LegacyTx) gas() uint64 { return tx.Gas } +func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) value() *big.Int { return tx.Value } +func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } +func (tx *LegacyTx) to() *common.Address { return tx.To } + +func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.V, tx.R, tx.S = v, r, s +} diff --git a/core/types/receipt.go b/core/types/receipt.go index a96c7525ef..48f4aef06a 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -38,6 +38,8 @@ var ( receiptStatusSuccessfulRLP = []byte{0x01} ) +var errEmptyTypedReceipt = errors.New("empty typed receipt bytes") + const ( // ReceiptStatusFailed is the status code of a transaction if execution failed. ReceiptStatusFailed = uint64(0) @@ -49,6 +51,7 @@ const ( // Receipt represents the results of a transaction. type Receipt struct { // Consensus fields: These fields are defined by the Yellow Paper + Type uint8 `json:"type,omitempty"` PostState []byte `json:"root"` Status uint64 `json:"status"` CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"` @@ -69,6 +72,7 @@ type Receipt struct { } type receiptMarshaling struct { + Type hexutil.Uint64 PostState hexutil.Bytes Status hexutil.Uint64 CumulativeGasUsed hexutil.Uint64 @@ -114,8 +118,13 @@ type v3StoredReceiptRLP struct { } // NewReceipt creates a barebone transaction receipt, copying the init fields. +// Deprecated: create receipts using a struct literal instead. func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { - r := &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: cumulativeGasUsed} + r := &Receipt{ + Type: LegacyTxType, + PostState: common.CopyBytes(root), + CumulativeGasUsed: cumulativeGasUsed, + } if failed { r.Status = ReceiptStatusFailed } else { @@ -127,21 +136,65 @@ func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt // into an RLP stream. If no post state is present, byzantium fork is assumed. func (r *Receipt) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}) + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + if r.Type == LegacyTxType { + return rlp.Encode(w, data) + } + // It's an EIP-2718 typed TX receipt. + if r.Type != AccessListTxType { + return ErrTxTypeNotSupported + } + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + buf.WriteByte(r.Type) + if err := rlp.Encode(buf, data); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) } // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // from an RLP stream. func (r *Receipt) DecodeRLP(s *rlp.Stream) error { - var dec receiptRLP - if err := s.Decode(&dec); err != nil { - return err - } - if err := r.setStatus(dec.PostStateOrStatus); err != nil { + kind, _, err := s.Kind() + switch { + case err != nil: return err + case kind == rlp.List: + // It's a legacy receipt. + var dec receiptRLP + if err := s.Decode(&dec); err != nil { + return err + } + r.Type = LegacyTxType + return r.setFromRLP(dec) + case kind == rlp.String: + // It's an EIP-2718 typed tx receipt. + b, err := s.Bytes() + if err != nil { + return err + } + if len(b) == 0 { + return errEmptyTypedReceipt + } + r.Type = b[0] + if r.Type == AccessListTxType { + var dec receiptRLP + if err := rlp.DecodeBytes(b[1:], &dec); err != nil { + return err + } + return r.setFromRLP(dec) + } + return ErrTxTypeNotSupported + default: + return rlp.ErrExpectedList } - r.CumulativeGasUsed, r.Bloom, r.Logs = dec.CumulativeGasUsed, dec.Bloom, dec.Logs - return nil +} + +func (r *Receipt) setFromRLP(data receiptRLP) error { + r.CumulativeGasUsed, r.Bloom, r.Logs = data.CumulativeGasUsed, data.Bloom, data.Logs + return r.setStatus(data.PostStateOrStatus) } func (r *Receipt) setStatus(postStateOrStatus []byte) error { @@ -172,7 +225,6 @@ func (r *Receipt) statusEncoding() []byte { // to approximate and limit the memory consumption of various caches. func (r *Receipt) Size() common.StorageSize { size := common.StorageSize(unsafe.Sizeof(*r)) + common.StorageSize(len(r.PostState)) - size += common.StorageSize(len(r.Logs)) * common.StorageSize(unsafe.Sizeof(Log{})) for _, log := range r.Logs { size += common.StorageSize(len(log.Topics)*common.HashLength + len(log.Data)) @@ -277,19 +329,27 @@ func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { return nil } -// Receipts is a wrapper around a Receipt array to implement DerivableList. +// Receipts implements DerivableList for receipts. type Receipts []*Receipt // Len returns the number of receipts in this list. -func (r Receipts) Len() int { return len(r) } - -// GetRlp returns the RLP encoding of one receipt from the list. -func (r Receipts) GetRlp(i int) []byte { - bytes, err := rlp.EncodeToBytes(r[i]) - if err != nil { - panic(err) +func (rs Receipts) Len() int { return len(rs) } + +// EncodeIndex encodes the i'th receipt to w. +func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { + r := rs[i] + data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + switch r.Type { + case LegacyTxType: + rlp.Encode(w, data) + case AccessListTxType: + w.WriteByte(AccessListTxType) + rlp.Encode(w, data) + default: + // For unsupported types, write nothing. Since this is for + // DeriveSha, the error will be caught matching the derived hash + // to the block. } - return bytes } // DeriveFields fills the receipts with their computed fields based on consensus @@ -302,7 +362,8 @@ func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, num return errors.New("transaction and receipt count mismatch") } for i := 0; i < len(r); i++ { - // The transaction hash can be retrieved from the transaction itself + // The transaction type and hash can be retrieved from the transaction itself + r[i].Type = txs[i].Type() r[i].TxHash = txs[i].Hash() // block location fields diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 806b3dd2ab..22a316c237 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -29,6 +29,15 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +func TestDecodeEmptyTypedReceipt(t *testing.T) { + input := []byte{0x80} + var r Receipt + err := rlp.DecodeBytes(input, &r) + if err != errEmptyTypedReceipt { + t.Fatal("wrong error:", err) + } +} + func TestLegacyReceiptDecoding(t *testing.T) { tests := []struct { name string @@ -154,9 +163,29 @@ func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) { // Tests that receipt data can be correctly derived from the contextual infos func TestDeriveFields(t *testing.T) { // Create a few transactions to have receipts for + to2 := common.HexToAddress("0x2") + to3 := common.HexToAddress("0x3") txs := Transactions{ - NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil), - NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil), + NewTx(&LegacyTx{ + Nonce: 1, + Value: big.NewInt(1), + Gas: 1, + GasPrice: big.NewInt(1), + }), + NewTx(&LegacyTx{ + To: &to2, + Nonce: 2, + Value: big.NewInt(2), + Gas: 2, + GasPrice: big.NewInt(2), + }), + NewTx(&AccessListTx{ + To: &to3, + Nonce: 3, + Value: big.NewInt(3), + Gas: 3, + GasPrice: big.NewInt(3), + }), } // Create the corresponding receipts receipts := Receipts{ @@ -182,6 +211,18 @@ func TestDeriveFields(t *testing.T) { ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}), GasUsed: 2, }, + &Receipt{ + Type: AccessListTxType, + PostState: common.Hash{3}.Bytes(), + CumulativeGasUsed: 6, + Logs: []*Log{ + {Address: common.BytesToAddress([]byte{0x33})}, + {Address: common.BytesToAddress([]byte{0x03, 0x33})}, + }, + TxHash: txs[2].Hash(), + ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}), + GasUsed: 3, + }, } // Clear all the computed fields and re-derive them number := big.NewInt(1) @@ -196,6 +237,9 @@ func TestDeriveFields(t *testing.T) { logIndex := uint(0) for i := range receipts { + if receipts[i].Type != txs[i].Type() { + t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type()) + } if receipts[i].TxHash != txs[i].Hash() { t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String()) } @@ -243,6 +287,34 @@ func TestDeriveFields(t *testing.T) { } } +// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt +// rlp decoder, which failed due to a shadowing error. +func TestTypedReceiptEncodingDecoding(t *testing.T) { + var payload = common.FromHex("f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0") + check := func(bundle []*Receipt) { + t.Helper() + for i, receipt := range bundle { + if got, want := receipt.Type, uint8(1); got != want { + t.Fatalf("bundle %d: got %x, want %x", i, got, want) + } + } + } + { + var bundle []*Receipt + rlp.DecodeBytes(payload, &bundle) + check(bundle) + } + { + var bundle []*Receipt + r := bytes.NewReader(payload) + s := rlp.NewStream(r, uint64(len(payload))) + if err := s.Decode(&bundle); err != nil { + t.Fatal(err) + } + check(bundle) + } +} + func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) { t.Helper() diff --git a/core/types/transaction.go b/core/types/transaction.go index 177bfbb70b..49127630ae 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -17,6 +17,7 @@ package types import ( + "bytes" "container/heap" "errors" "io" @@ -25,20 +26,28 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) -//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go - var ( - ErrInvalidSig = errors.New("invalid transaction v, r, s values") + ErrInvalidSig = errors.New("invalid transaction v, r, s values") + ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures") + ErrInvalidTxType = errors.New("transaction type not valid in this context") + ErrTxTypeNotSupported = errors.New("transaction type not supported") + errEmptyTypedTx = errors.New("empty typed transaction bytes") +) + +// Transaction types. +const ( + LegacyTxType = iota + AccessListTxType ) +// Transaction is an Ethereum transaction. type Transaction struct { - data txdata // Consensus contents of a transaction - time time.Time // Time first seen locally (spam avoidance) + inner TxData // Consensus contents of a transaction + time time.Time // Time first seen locally (spam avoidance) // caches hash atomic.Value @@ -46,170 +55,266 @@ type Transaction struct { from atomic.Value } -type txdata struct { - AccountNonce uint64 `json:"nonce" gencodec:"required"` - Price *big.Int `json:"gasPrice" gencodec:"required"` - GasLimit uint64 `json:"gas" gencodec:"required"` - Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation - Amount *big.Int `json:"value" gencodec:"required"` - Payload []byte `json:"input" gencodec:"required"` +// NewTx creates a new transaction. +func NewTx(inner TxData) *Transaction { + tx := new(Transaction) + tx.setDecoded(inner.copy(), 0) + return tx +} + +// TxData is the underlying data of a transaction. +// +// This is implemented by LegacyTx and AccessListTx. +type TxData interface { + txType() byte // returns the type ID + copy() TxData // creates a deep copy and initializes all fields - // Signature values - V *big.Int `json:"v" gencodec:"required"` - R *big.Int `json:"r" gencodec:"required"` - S *big.Int `json:"s" gencodec:"required"` + chainID() *big.Int + accessList() AccessList + data() []byte + gas() uint64 + gasPrice() *big.Int + value() *big.Int + nonce() uint64 + to() *common.Address - // This is only used when marshaling to JSON. - Hash *common.Hash `json:"hash" rlp:"-"` + rawSignatureValues() (v, r, s *big.Int) + setSignatureValues(chainID, v, r, s *big.Int) } -type txdataMarshaling struct { - AccountNonce hexutil.Uint64 - Price *hexutil.Big - GasLimit hexutil.Uint64 - Amount *hexutil.Big - Payload hexutil.Bytes - V *hexutil.Big - R *hexutil.Big - S *hexutil.Big +// EncodeRLP implements rlp.Encoder +func (tx *Transaction) EncodeRLP(w io.Writer) error { + if tx.Type() == LegacyTxType { + return rlp.Encode(w, tx.inner) + } + // It's an EIP-2718 typed TX envelope. + buf := encodeBufferPool.Get().(*bytes.Buffer) + defer encodeBufferPool.Put(buf) + buf.Reset() + if err := tx.encodeTyped(buf); err != nil { + return err + } + return rlp.Encode(w, buf.Bytes()) } -func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data) +// encodeTyped writes the canonical encoding of a typed transaction to w. +func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { + w.WriteByte(tx.Type()) + return rlp.Encode(w, tx.inner) } -func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - return newTransaction(nonce, nil, amount, gasLimit, gasPrice, data) +// MarshalBinary returns the canonical encoding of the transaction. +// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed +// transactions, it returns the type and payload. +func (tx *Transaction) MarshalBinary() ([]byte, error) { + if tx.Type() == LegacyTxType { + return rlp.EncodeToBytes(tx.inner) + } + var buf bytes.Buffer + err := tx.encodeTyped(&buf) + return buf.Bytes(), err } -func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { - if len(data) > 0 { - data = common.CopyBytes(data) +// DecodeRLP implements rlp.Decoder +func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { + kind, size, err := s.Kind() + switch { + case err != nil: + return err + case kind == rlp.List: + // It's a legacy transaction. + var inner LegacyTx + err := s.Decode(&inner) + if err == nil { + tx.setDecoded(&inner, int(rlp.ListSize(size))) + } + return err + case kind == rlp.String: + // It's an EIP-2718 typed TX envelope. + var b []byte + if b, err = s.Bytes(); err != nil { + return err + } + inner, err := tx.decodeTyped(b) + if err == nil { + tx.setDecoded(inner, len(b)) + } + return err + default: + return rlp.ErrExpectedList } - d := txdata{ - AccountNonce: nonce, - Recipient: to, - Payload: data, - Amount: new(big.Int), - GasLimit: gasLimit, - Price: new(big.Int), - V: new(big.Int), - R: new(big.Int), - S: new(big.Int), +} + +// UnmarshalBinary decodes the canonical encoding of transactions. +// It supports legacy RLP transactions and EIP2718 typed transactions. +func (tx *Transaction) UnmarshalBinary(b []byte) error { + if len(b) > 0 && b[0] > 0x7f { + // It's a legacy transaction. + var data LegacyTx + err := rlp.DecodeBytes(b, &data) + if err != nil { + return err + } + tx.setDecoded(&data, len(b)) + return nil } - if amount != nil { - d.Amount.Set(amount) + // It's an EIP2718 typed transaction envelope. + inner, err := tx.decodeTyped(b) + if err != nil { + return err } - if gasPrice != nil { - d.Price.Set(gasPrice) + tx.setDecoded(inner, len(b)) + return nil +} + +// decodeTyped decodes a typed transaction from the canonical format. +func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { + if len(b) == 0 { + return nil, errEmptyTypedTx } - return &Transaction{ - data: d, - time: time.Now(), + switch b[0] { + case AccessListTxType: + var inner AccessListTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err + default: + return nil, ErrTxTypeNotSupported } } -// ChainId returns which chain id this transaction was signed for (if at all) -func (tx *Transaction) ChainId() *big.Int { - return deriveChainId(tx.data.V) +// setDecoded sets the inner transaction and size after decoding. +func (tx *Transaction) setDecoded(inner TxData, size int) { + tx.inner = inner + tx.time = time.Now() + if size > 0 { + tx.size.Store(common.StorageSize(size)) + } } -// Protected returns whether the transaction is protected from replay protection. -func (tx *Transaction) Protected() bool { - return isProtectedV(tx.data.V) +func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error { + if isProtectedV(v) && !maybeProtected { + return ErrUnexpectedProtection + } + + var plainV byte + if isProtectedV(v) { + chainID := deriveChainId(v).Uint64() + plainV = byte(v.Uint64() - 35 - 2*chainID) + } else if maybeProtected { + // Only EIP-155 signatures can be optionally protected. Since + // we determined this v value is not protected, it must be a + // raw 27 or 28. + plainV = byte(v.Uint64() - 27) + } else { + // If the signature is not optionally protected, we assume it + // must already be equal to the recovery id. + plainV = byte(v.Uint64()) + } + if !crypto.ValidateSignatureValues(plainV, r, s, false) { + return ErrInvalidSig + } + + return nil } func isProtectedV(V *big.Int) bool { if V.BitLen() <= 8 { v := V.Uint64() - return v != 27 && v != 28 + return v != 27 && v != 28 && v != 1 && v != 0 } // anything not 27 or 28 is considered protected return true } -// EncodeRLP implements rlp.Encoder -func (tx *Transaction) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, &tx.data) -} - -// DecodeRLP implements rlp.Decoder -func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - err := s.Decode(&tx.data) - if err == nil { - tx.size.Store(common.StorageSize(rlp.ListSize(size))) - tx.time = time.Now() +// Protected says whether the transaction is replay-protected. +func (tx *Transaction) Protected() bool { + switch tx := tx.inner.(type) { + case *LegacyTx: + return tx.V != nil && isProtectedV(tx.V) + default: + return true } - return err } -// MarshalJSON encodes the web3 RPC transaction format. -func (tx *Transaction) MarshalJSON() ([]byte, error) { - hash := tx.Hash() - data := tx.data - data.Hash = &hash - return data.MarshalJSON() +// Type returns the transaction type. +func (tx *Transaction) Type() uint8 { + return tx.inner.txType() } -// UnmarshalJSON decodes the web3 RPC transaction format. -func (tx *Transaction) UnmarshalJSON(input []byte) error { - var dec txdata - if err := dec.UnmarshalJSON(input); err != nil { - return err - } - withSignature := dec.V.Sign() != 0 || dec.R.Sign() != 0 || dec.S.Sign() != 0 - if withSignature { - var V byte - if isProtectedV(dec.V) { - chainID := deriveChainId(dec.V).Uint64() - V = byte(dec.V.Uint64() - 35 - 2*chainID) - } else { - V = byte(dec.V.Uint64() - 27) - } - if !crypto.ValidateSignatureValues(V, dec.R, dec.S, false) { - return ErrInvalidSig - } - } - *tx = Transaction{ - data: dec, - time: time.Now(), - } - return nil +// ChainId returns the EIP155 chain ID of the transaction. The return value will always be +// non-nil. For legacy transactions which are not replay-protected, the return value is +// zero. +func (tx *Transaction) ChainId() *big.Int { + return tx.inner.chainID() } -func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } -func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit } -func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } -func (tx *Transaction) GasPriceCmp(other *Transaction) int { - return tx.data.Price.Cmp(other.data.Price) -} -func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { - return tx.data.Price.Cmp(other) -} -func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } -func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } -func (tx *Transaction) CheckNonce() bool { return true } +// Data returns the input data of the transaction. +func (tx *Transaction) Data() []byte { return tx.inner.data() } + +// AccessList returns the access list of the transaction. +func (tx *Transaction) AccessList() AccessList { return tx.inner.accessList() } + +// Gas returns the gas limit of the transaction. +func (tx *Transaction) Gas() uint64 { return tx.inner.gas() } + +// GasPrice returns the gas price of the transaction. +func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) } + +// Value returns the ether amount of the transaction. +func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) } + +// Nonce returns the sender account nonce of the transaction. +func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() } // To returns the recipient address of the transaction. -// It returns nil if the transaction is a contract creation. +// For contract-creation transactions, To returns nil. func (tx *Transaction) To() *common.Address { - if tx.data.Recipient == nil { + // Copy the pointed-to address. + ito := tx.inner.to() + if ito == nil { return nil } - to := *tx.data.Recipient - return &to + cpy := *ito + return &cpy +} + +// Cost returns gas * gasPrice + value. +func (tx *Transaction) Cost() *big.Int { + total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + total.Add(total, tx.Value()) + return total +} + +// RawSignatureValues returns the V, R, S signature values of the transaction. +// The return values should not be modified by the caller. +func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { + return tx.inner.rawSignatureValues() +} + +// GasPriceCmp compares the gas prices of two transactions. +func (tx *Transaction) GasPriceCmp(other *Transaction) int { + return tx.inner.gasPrice().Cmp(other.GasPrice()) } -// Hash hashes the RLP encoding of tx. -// It uniquely identifies the transaction. +// GasPriceIntCmp compares the gas price of the transaction against the given price. +func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { + return tx.inner.gasPrice().Cmp(other) +} + +// Hash returns the transaction hash. func (tx *Transaction) Hash() common.Hash { if hash := tx.hash.Load(); hash != nil { return hash.(common.Hash) } - v := rlpHash(tx) - tx.hash.Store(v) - return v + + var h common.Hash + if tx.Type() == LegacyTxType { + h = rlpHash(tx.inner) + } else { + h = prefixedRlpHash(tx.Type(), tx.inner) + } + tx.hash.Store(h) + return h } // Size returns the true RLP encoded storage size of the transaction, either by @@ -219,32 +324,11 @@ func (tx *Transaction) Size() common.StorageSize { return size.(common.StorageSize) } c := writeCounter(0) - rlp.Encode(&c, &tx.data) + rlp.Encode(&c, &tx.inner) tx.size.Store(common.StorageSize(c)) return common.StorageSize(c) } -// AsMessage returns the transaction as a core.Message. -// -// AsMessage requires a signer to derive the sender. -// -// XXX Rename message to something less arbitrary? -func (tx *Transaction) AsMessage(s Signer) (Message, error) { - msg := Message{ - nonce: tx.data.AccountNonce, - gasLimit: tx.data.GasLimit, - gasPrice: new(big.Int).Set(tx.data.Price), - to: tx.data.Recipient, - amount: tx.data.Amount, - data: tx.data.Payload, - checkNonce: true, - } - - var err error - msg.from, err = Sender(s, tx) - return msg, err -} - // WithSignature returns a new transaction with the given signature. // This signature needs to be in the [R || S || V] format where V is 0 or 1. func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) { @@ -252,40 +336,27 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e if err != nil { return nil, err } - cpy := &Transaction{ - data: tx.data, - time: tx.time, - } - cpy.data.R, cpy.data.S, cpy.data.V = r, s, v - return cpy, nil + cpy := tx.inner.copy() + cpy.setSignatureValues(signer.ChainID(), v, r, s) + return &Transaction{inner: cpy, time: tx.time}, nil } -// Cost returns amount + gasprice * gaslimit. -func (tx *Transaction) Cost() *big.Int { - total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit)) - total.Add(total, tx.data.Amount) - return total -} - -// RawSignatureValues returns the V, R, S signature values of the transaction. -// The return values should not be modified by the caller. -func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { - return tx.data.V, tx.data.R, tx.data.S -} - -// Transactions is a Transaction slice type for basic sorting. +// Transactions implements DerivableList for transactions. type Transactions []*Transaction // Len returns the length of s. func (s Transactions) Len() int { return len(s) } -// Swap swaps the i'th and the j'th element in s. -func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// GetRlp implements Rlpable and returns the i'th element of s in rlp. -func (s Transactions) GetRlp(i int) []byte { - enc, _ := rlp.EncodeToBytes(s[i]) - return enc +// EncodeIndex encodes the i'th transaction to w. Note that this does not check for errors +// because we assume that *Transaction will only ever contain valid txs that were either +// constructed by decoding or via public API in this package. +func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) { + tx := s[i] + if tx.Type() == LegacyTxType { + rlp.Encode(w, tx.inner) + } else { + tx.encodeTyped(w) + } } // TxDifference returns a new set which is the difference between a and b. @@ -312,7 +383,7 @@ func TxDifference(a, b Transactions) Transactions { type TxByNonce Transactions func (s TxByNonce) Len() int { return len(s) } -func (s TxByNonce) Less(i, j int) bool { return s[i].data.AccountNonce < s[j].data.AccountNonce } +func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() } func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // TxByPriceAndTime implements both the sort and the heap interface, making it useful @@ -323,7 +394,7 @@ func (s TxByPriceAndTime) Len() int { return len(s) } func (s TxByPriceAndTime) Less(i, j int) bool { // If the prices are equal, use the time the transaction was first seen for // deterministic sorting - cmp := s[i].data.Price.Cmp(s[j].data.Price) + cmp := s[i].GasPrice().Cmp(s[j].GasPrice()) if cmp == 0 { return s[i].time.Before(s[j].time) } @@ -361,13 +432,13 @@ func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transa // Initialize a price and received time based heap with the head transactions heads := make(TxByPriceAndTime, 0, len(txs)) for from, accTxs := range txs { - heads = append(heads, accTxs[0]) // Ensure the sender address is from the signer - acc, _ := Sender(signer, accTxs[0]) - txs[acc] = accTxs[1:] - if from != acc { + if acc, _ := Sender(signer, accTxs[0]); acc != from { delete(txs, from) + continue } + heads = append(heads, accTxs[0]) + txs[from] = accTxs[1:] } heap.Init(&heads) @@ -416,10 +487,11 @@ type Message struct { gasLimit uint64 gasPrice *big.Int data []byte + accessList AccessList checkNonce bool } -func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message { +func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, accessList AccessList, checkNonce bool) Message { return Message{ from: from, to: to, @@ -428,15 +500,35 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b gasLimit: gasLimit, gasPrice: gasPrice, data: data, + accessList: accessList, checkNonce: checkNonce, } } -func (m Message) From() common.Address { return m.from } -func (m Message) To() *common.Address { return m.to } -func (m Message) GasPrice() *big.Int { return m.gasPrice } -func (m Message) Value() *big.Int { return m.amount } -func (m Message) Gas() uint64 { return m.gasLimit } -func (m Message) Nonce() uint64 { return m.nonce } -func (m Message) Data() []byte { return m.data } -func (m Message) CheckNonce() bool { return m.checkNonce } +// AsMessage returns the transaction as a core.Message. +func (tx *Transaction) AsMessage(s Signer) (Message, error) { + msg := Message{ + nonce: tx.Nonce(), + gasLimit: tx.Gas(), + gasPrice: new(big.Int).Set(tx.GasPrice()), + to: tx.To(), + amount: tx.Value(), + data: tx.Data(), + accessList: tx.AccessList(), + checkNonce: true, + } + + var err error + msg.from, err = Sender(s, tx) + return msg, err +} + +func (m Message) From() common.Address { return m.from } +func (m Message) To() *common.Address { return m.to } +func (m Message) GasPrice() *big.Int { return m.gasPrice } +func (m Message) Value() *big.Int { return m.amount } +func (m Message) Gas() uint64 { return m.gasLimit } +func (m Message) Nonce() uint64 { return m.nonce } +func (m Message) Data() []byte { return m.data } +func (m Message) AccessList() AccessList { return m.accessList } +func (m Message) CheckNonce() bool { return m.checkNonce } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go new file mode 100644 index 0000000000..184a17d5b5 --- /dev/null +++ b/core/types/transaction_marshalling.go @@ -0,0 +1,187 @@ +package types + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// txJSON is the JSON representation of transactions. +type txJSON struct { + Type hexutil.Uint64 `json:"type"` + + // Common transaction fields: + Nonce *hexutil.Uint64 `json:"nonce"` + GasPrice *hexutil.Big `json:"gasPrice"` + Gas *hexutil.Uint64 `json:"gas"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"input"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + To *common.Address `json:"to"` + + // Access list transaction fields: + ChainID *hexutil.Big `json:"chainId,omitempty"` + AccessList *AccessList `json:"accessList,omitempty"` + + // Only used for encoding: + Hash common.Hash `json:"hash"` +} + +// MarshalJSON marshals as JSON with a hash. +func (t *Transaction) MarshalJSON() ([]byte, error) { + var enc txJSON + // These are set for all tx types. + enc.Hash = t.Hash() + enc.Type = hexutil.Uint64(t.Type()) + + // Other fields are set conditionally depending on tx type. + switch tx := t.inner.(type) { + case *LegacyTx: + enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.GasPrice = (*hexutil.Big)(tx.GasPrice) + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.To = t.To() + enc.V = (*hexutil.Big)(tx.V) + enc.R = (*hexutil.Big)(tx.R) + enc.S = (*hexutil.Big)(tx.S) + case *AccessListTx: + enc.ChainID = (*hexutil.Big)(tx.ChainID) + enc.AccessList = &tx.AccessList + enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.GasPrice = (*hexutil.Big)(tx.GasPrice) + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.To = t.To() + enc.V = (*hexutil.Big)(tx.V) + enc.R = (*hexutil.Big)(tx.R) + enc.S = (*hexutil.Big)(tx.S) + } + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (t *Transaction) UnmarshalJSON(input []byte) error { + var dec txJSON + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + // Decode / verify fields according to transaction type. + var inner TxData + switch dec.Type { + case LegacyTxType: + var itx LegacyTx + inner = &itx + if dec.To != nil { + itx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.GasPrice == nil { + return errors.New("missing required field 'gasPrice' in transaction") + } + itx.GasPrice = (*big.Int)(dec.GasPrice) + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil { + return err + } + } + + case AccessListTxType: + var itx AccessListTx + inner = &itx + // Access list is optional for now. + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.To != nil { + itx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.GasPrice == nil { + return errors.New("missing required field 'gasPrice' in transaction") + } + itx.GasPrice = (*big.Int)(dec.GasPrice) + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + + default: + return ErrTxTypeNotSupported + } + + // Now set the inner transaction. + t.setDecoded(inner, 0) + + // TODO: check hash here? + return nil +} diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 842fedbd03..1645369b4f 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -27,9 +27,7 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var ( - ErrInvalidChainId = errors.New("invalid chain id for signer") -) +var ErrInvalidChainId = errors.New("invalid chain id for signer") // sigCache is used to cache the derived sender and contains // the signer used to derive it. @@ -42,6 +40,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer switch { + case config.IsYoloV3(blockNumber): + signer = NewEIP2930Signer(config.ChainID) case config.IsEIP155(blockNumber): signer = NewEIP155Signer(config.ChainID) case config.IsHomestead(blockNumber): @@ -52,7 +52,40 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { return signer } -// SignTx signs the transaction using the given signer and private key +// LatestSigner returns the 'most permissive' Signer available for the given chain +// configuration. Specifically, this enables support of EIP-155 replay protection and +// EIP-2930 access list transactions when their respective forks are scheduled to occur at +// any block number in the chain config. +// +// Use this in transaction-handling code where the current block number is unknown. If you +// have the current block number available, use MakeSigner instead. +func LatestSigner(config *params.ChainConfig) Signer { + if config.ChainID != nil { + if config.YoloV3Block != nil { + return NewEIP2930Signer(config.ChainID) + } + if config.EIP155Block != nil { + return NewEIP155Signer(config.ChainID) + } + } + return HomesteadSigner{} +} + +// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically, +// this enables support for EIP-155 replay protection and all implemented EIP-2718 +// transaction types if chainID is non-nil. +// +// Use this in transaction-handling code where the current block number and fork +// configuration are unknown. If you have a ChainConfig, use LatestSigner instead. +// If you have a ChainConfig and know the current block number, use MakeSigner instead. +func LatestSignerForChainID(chainID *big.Int) Signer { + if chainID == nil { + return HomesteadSigner{} + } + return NewEIP2930Signer(chainID) +} + +// SignTx signs the transaction using the given signer and private key. func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) { h := s.Hash(tx) sig, err := crypto.Sign(h[:], prv) @@ -62,6 +95,27 @@ func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, err return tx.WithSignature(s, sig) } +// SignNewTx creates a transaction and signs it. +func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) { + tx := NewTx(txdata) + h := s.Hash(tx) + sig, err := crypto.Sign(h[:], prv) + if err != nil { + return nil, err + } + return tx.WithSignature(s, sig) +} + +// MustSignNewTx creates a transaction and signs it. +// This panics if the transaction cannot be signed. +func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction { + tx, err := SignNewTx(prv, s, txdata) + if err != nil { + panic(err) + } + return tx +} + // Sender returns the address derived from the signature (V, R, S) using secp256k1 // elliptic curve and an error if it failed deriving or upon an incorrect // signature. @@ -88,21 +142,128 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) { return addr, nil } -// Signer encapsulates transaction signature handling. Note that this interface is not a -// stable API and may change at any time to accommodate new protocol rules. +// Signer encapsulates transaction signature handling. The name of this type is slightly +// misleading because Signers don't actually sign, they're just for validating and +// processing of signatures. +// +// Note that this interface is not a stable API and may change at any time to accommodate +// new protocol rules. type Signer interface { // Sender returns the sender address of the transaction. Sender(tx *Transaction) (common.Address, error) + // SignatureValues returns the raw R, S, V values corresponding to the // given signature. SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) - // Hash returns the hash to be signed. + ChainID() *big.Int + + // Hash returns 'signature hash', i.e. the transaction hash that is signed by the + // private key. This hash does not uniquely identify the transaction. Hash(tx *Transaction) common.Hash + // Equal returns true if the given signer is the same as the receiver. Equal(Signer) bool } -// EIP155Transaction implements Signer using the EIP155 rules. +type eip2930Signer struct{ EIP155Signer } + +// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, +// EIP-155 replay protected transactions, and legacy Homestead transactions. +func NewEIP2930Signer(chainId *big.Int) Signer { + return eip2930Signer{NewEIP155Signer(chainId)} +} + +func (s eip2930Signer) ChainID() *big.Int { + return s.chainId +} + +func (s eip2930Signer) Equal(s2 Signer) bool { + x, ok := s2.(eip2930Signer) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { + V, R, S := tx.RawSignatureValues() + switch tx.Type() { + case LegacyTxType: + if !tx.Protected() { + return HomesteadSigner{}.Sender(tx) + } + V = new(big.Int).Sub(V, s.chainIdMul) + V.Sub(V, big8) + case AccessListTxType: + // ACL txs are defined to use 0 and 1 as their recovery id, add + // 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + default: + return common.Address{}, ErrTxTypeNotSupported + } + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + switch txdata := tx.inner.(type) { + case *LegacyTx: + R, S, V = decodeSignature(sig) + if s.chainId.Sign() != 0 { + V = big.NewInt(int64(sig[64] + 35)) + V.Add(V, s.chainIdMul) + } + case *AccessListTx: + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + return nil, nil, nil, ErrInvalidChainId + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + default: + return nil, nil, nil, ErrTxTypeNotSupported + } + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s eip2930Signer) Hash(tx *Transaction) common.Hash { + switch tx.Type() { + case LegacyTxType: + return rlpHash([]interface{}{ + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + s.chainId, uint(0), uint(0), + }) + case AccessListTxType: + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) + default: + // This _should_ not happen, but in case someone sends in a bad + // json struct via RPC, it's probably more prudent to return an + // empty hash instead of killing the node with a panic + //panic("Unsupported transaction type: %d", tx.typ) + return common.Hash{} + } +} + +// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which +// are replay-protected as well as unprotected homestead transactions. type EIP155Signer struct { chainId, chainIdMul *big.Int } @@ -117,6 +278,10 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer { } } +func (s EIP155Signer) ChainID() *big.Int { + return s.chainId +} + func (s EIP155Signer) Equal(s2 Signer) bool { eip155, ok := s2.(EIP155Signer) return ok && eip155.chainId.Cmp(s.chainId) == 0 @@ -125,24 +290,28 @@ func (s EIP155Signer) Equal(s2 Signer) bool { var big8 = big.NewInt(8) func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } if !tx.Protected() { return HomesteadSigner{}.Sender(tx) } if tx.ChainId().Cmp(s.chainId) != 0 { return common.Address{}, ErrInvalidChainId } - V := new(big.Int).Sub(tx.data.V, s.chainIdMul) + V, R, S := tx.RawSignatureValues() + V = new(big.Int).Sub(V, s.chainIdMul) V.Sub(V, big8) - return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true) + return recoverPlain(s.Hash(tx), R, S, V, true) } // SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { - R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig) - if err != nil { - return nil, nil, nil, err + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported } + R, S, V = decodeSignature(sig) if s.chainId.Sign() != 0 { V = big.NewInt(int64(sig[64] + 35)) V.Add(V, s.chainIdMul) @@ -154,12 +323,12 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // It does not uniquely identify the transaction. func (s EIP155Signer) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ - tx.data.AccountNonce, - tx.data.Price, - tx.data.GasLimit, - tx.data.Recipient, - tx.data.Amount, - tx.data.Payload, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), s.chainId, uint(0), uint(0), }) } @@ -168,6 +337,10 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash { // homestead rules. type HomesteadSigner struct{ FrontierSigner } +func (s HomesteadSigner) ChainID() *big.Int { + return nil +} + func (s HomesteadSigner) Equal(s2 Signer) bool { _, ok := s2.(HomesteadSigner) return ok @@ -180,25 +353,39 @@ func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v } func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) { - return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true) + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(hs.Hash(tx), r, s, v, true) } type FrontierSigner struct{} +func (s FrontierSigner) ChainID() *big.Int { + return nil +} + func (s FrontierSigner) Equal(s2 Signer) bool { _, ok := s2.(FrontierSigner) return ok } +func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != LegacyTxType { + return common.Address{}, ErrTxTypeNotSupported + } + v, r, s := tx.RawSignatureValues() + return recoverPlain(fs.Hash(tx), r, s, v, false) +} + // SignatureValues returns signature values. This signature // needs to be in the [R || S || V] format where V is 0 or 1. func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) { - if len(sig) != crypto.SignatureLength { - panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) + if tx.Type() != LegacyTxType { + return nil, nil, nil, ErrTxTypeNotSupported } - r = new(big.Int).SetBytes(sig[:32]) - s = new(big.Int).SetBytes(sig[32:64]) - v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + r, s, v = decodeSignature(sig) return r, s, v, nil } @@ -206,17 +393,23 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v * // It does not uniquely identify the transaction. func (fs FrontierSigner) Hash(tx *Transaction) common.Hash { return rlpHash([]interface{}{ - tx.data.AccountNonce, - tx.data.Price, - tx.data.GasLimit, - tx.data.Recipient, - tx.data.Amount, - tx.data.Payload, + tx.Nonce(), + tx.GasPrice(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), }) } -func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) { - return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false) +func decodeSignature(sig []byte) (r, s, v *big.Int) { + if len(sig) != crypto.SignatureLength { + panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)) + } + r = new(big.Int).SetBytes(sig[:32]) + s = new(big.Int).SetBytes(sig[32:64]) + v = new(big.Int).SetBytes([]byte{sig[64] + 27}) + return r, s, v } func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) { diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 159cb0c4c4..3cece9c235 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -20,7 +20,9 @@ import ( "bytes" "crypto/ecdsa" "encoding/json" + "fmt" "math/big" + "reflect" "testing" "time" @@ -32,6 +34,8 @@ import ( // The values in those tests are from the Transaction Tests // at github.com/ethereum/tests. var ( + testAddr = common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b") + emptyTx = NewTransaction( 0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), @@ -41,7 +45,7 @@ var ( rightvrsTx, _ = NewTransaction( 3, - common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + testAddr, big.NewInt(10), 2000, big.NewInt(1), @@ -50,8 +54,32 @@ var ( HomesteadSigner{}, common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), ) + + emptyEip2718Tx = NewTx(&AccessListTx{ + ChainID: big.NewInt(1), + Nonce: 3, + To: &testAddr, + Value: big.NewInt(10), + Gas: 25000, + GasPrice: big.NewInt(1), + Data: common.FromHex("5544"), + }) + + signedEip2718Tx, _ = emptyEip2718Tx.WithSignature( + NewEIP2930Signer(big.NewInt(1)), + common.Hex2Bytes("c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101"), + ) ) +func TestDecodeEmptyTypedTx(t *testing.T) { + input := []byte{0x80} + var tx Transaction + err := rlp.DecodeBytes(input, &tx) + if err != errEmptyTypedTx { + t.Fatal("wrong error:", err) + } +} + func TestTransactionSigHash(t *testing.T) { var homestead HomesteadSigner if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { @@ -73,10 +101,121 @@ func TestTransactionEncode(t *testing.T) { } } +func TestEIP2718TransactionSigHash(t *testing.T) { + s := NewEIP2930Signer(big.NewInt(1)) + if s.Hash(emptyEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") { + t.Errorf("empty EIP-2718 transaction hash mismatch, got %x", s.Hash(emptyEip2718Tx)) + } + if s.Hash(signedEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") { + t.Errorf("signed EIP-2718 transaction hash mismatch, got %x", s.Hash(signedEip2718Tx)) + } +} + +// This test checks signature operations on access list transactions. +func TestEIP2930Signer(t *testing.T) { + + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + keyAddr = crypto.PubkeyToAddress(key.PublicKey) + signer1 = NewEIP2930Signer(big.NewInt(1)) + signer2 = NewEIP2930Signer(big.NewInt(2)) + tx0 = NewTx(&AccessListTx{Nonce: 1}) + tx1 = NewTx(&AccessListTx{ChainID: big.NewInt(1), Nonce: 1}) + tx2, _ = SignNewTx(key, signer2, &AccessListTx{ChainID: big.NewInt(2), Nonce: 1}) + ) + + tests := []struct { + tx *Transaction + signer Signer + wantSignerHash common.Hash + wantSenderErr error + wantSignErr error + wantHash common.Hash // after signing + }{ + { + tx: tx0, + signer: signer1, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantSenderErr: ErrInvalidChainId, + wantHash: common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"), + }, + { + tx: tx1, + signer: signer1, + wantSenderErr: ErrInvalidSig, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantHash: common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"), + }, + { + // This checks what happens when trying to sign an unsigned tx for the wrong chain. + tx: tx1, + signer: signer2, + wantSenderErr: ErrInvalidChainId, + wantSignerHash: common.HexToHash("367967247499343401261d718ed5aa4c9486583e4d89251afce47f4a33c33362"), + wantSignErr: ErrInvalidChainId, + }, + { + // This checks what happens when trying to re-sign a signed tx for the wrong chain. + tx: tx2, + signer: signer1, + wantSenderErr: ErrInvalidChainId, + wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"), + wantSignErr: ErrInvalidChainId, + }, + } + + for i, test := range tests { + sigHash := test.signer.Hash(test.tx) + if sigHash != test.wantSignerHash { + t.Errorf("test %d: wrong sig hash: got %x, want %x", i, sigHash, test.wantSignerHash) + } + sender, err := Sender(test.signer, test.tx) + if err != test.wantSenderErr { + t.Errorf("test %d: wrong Sender error %q", i, err) + } + if err == nil && sender != keyAddr { + t.Errorf("test %d: wrong sender address %x", i, sender) + } + signedTx, err := SignTx(test.tx, test.signer, key) + if err != test.wantSignErr { + t.Fatalf("test %d: wrong SignTx error %q", i, err) + } + if signedTx != nil { + if signedTx.Hash() != test.wantHash { + t.Errorf("test %d: wrong tx hash after signing: got %x, want %x", i, signedTx.Hash(), test.wantHash) + } + } + } +} + +func TestEIP2718TransactionEncode(t *testing.T) { + // RLP representation + { + have, err := rlp.EncodeToBytes(signedEip2718Tx) + if err != nil { + t.Fatalf("encode error: %v", err) + } + want := common.FromHex("b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521") + if !bytes.Equal(have, want) { + t.Errorf("encoded RLP mismatch, got %x", have) + } + } + // Binary representation + { + have, err := signedEip2718Tx.MarshalBinary() + if err != nil { + t.Fatalf("encode error: %v", err) + } + want := common.FromHex("01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521") + if !bytes.Equal(have, want) { + t.Errorf("encoded RLP mismatch, got %x", have) + } + } +} + func decodeTx(data []byte) (*Transaction, error) { var tx Transaction t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx) - return t, err } @@ -219,50 +358,125 @@ func TestTransactionTimeSort(t *testing.T) { } } -// TestTransactionJSON tests serializing/de-serializing to/from JSON. -func TestTransactionJSON(t *testing.T) { +// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON. +func TestTransactionCoding(t *testing.T) { key, err := crypto.GenerateKey() if err != nil { t.Fatalf("could not generate key: %v", err) } - signer := NewEIP155Signer(common.Big1) - - transactions := make([]*Transaction, 0, 50) - for i := uint64(0); i < 25; i++ { - var tx *Transaction - switch i % 2 { + var ( + signer = NewEIP2930Signer(common.Big1) + addr = common.HexToAddress("0x0000000000000000000000000000000000000001") + recipient = common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + accesses = AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}} + ) + for i := uint64(0); i < 500; i++ { + var txdata TxData + switch i % 5 { case 0: - tx = NewTransaction(i, common.Address{1}, common.Big0, 1, common.Big2, []byte("abcdef")) + // Legacy tx. + txdata = &LegacyTx{ + Nonce: i, + To: &recipient, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } case 1: - tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef")) + // Legacy tx contract creation. + txdata = &LegacyTx{ + Nonce: i, + Gas: 1, + GasPrice: big.NewInt(2), + Data: []byte("abcdef"), + } + case 2: + // Tx with non-zero access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + Data: []byte("abcdef"), + } + case 3: + // Tx with empty access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + To: &recipient, + Gas: 123457, + GasPrice: big.NewInt(10), + Data: []byte("abcdef"), + } + case 4: + // Contract creation with access list. + txdata = &AccessListTx{ + ChainID: big.NewInt(1), + Nonce: i, + Gas: 123457, + GasPrice: big.NewInt(10), + AccessList: accesses, + } } - transactions = append(transactions, tx) - - signedTx, err := SignTx(tx, signer, key) + tx, err := SignNewTx(key, signer, txdata) if err != nil { t.Fatalf("could not sign transaction: %v", err) } - - transactions = append(transactions, signedTx) - } - - for _, tx := range transactions { - data, err := json.Marshal(tx) + // RLP + parsedTx, err := encodeDecodeBinary(tx) if err != nil { - t.Fatalf("json.Marshal failed: %v", err) + t.Fatal(err) } + assertEqual(parsedTx, tx) - var parsedTx *Transaction - if err := json.Unmarshal(data, &parsedTx); err != nil { - t.Fatalf("json.Unmarshal failed: %v", err) + // JSON + parsedTx, err = encodeDecodeJSON(tx) + if err != nil { + t.Fatal(err) } + assertEqual(parsedTx, tx) + } +} - // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S - if tx.Hash() != parsedTx.Hash() { - t.Errorf("parsed tx differs from original tx, want %v, got %v", tx, parsedTx) - } - if tx.ChainId().Cmp(parsedTx.ChainId()) != 0 { - t.Errorf("invalid chain id, want %d, got %d", tx.ChainId(), parsedTx.ChainId()) +func encodeDecodeJSON(tx *Transaction) (*Transaction, error) { + data, err := json.Marshal(tx) + if err != nil { + return nil, fmt.Errorf("json encoding failed: %v", err) + } + var parsedTx = &Transaction{} + if err := json.Unmarshal(data, &parsedTx); err != nil { + return nil, fmt.Errorf("json decoding failed: %v", err) + } + return parsedTx, nil +} + +func encodeDecodeBinary(tx *Transaction) (*Transaction, error) { + data, err := tx.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("rlp encoding failed: %v", err) + } + var parsedTx = &Transaction{} + if err := parsedTx.UnmarshalBinary(data); err != nil { + return nil, fmt.Errorf("rlp decoding failed: %v", err) + } + return parsedTx, nil +} + +func assertEqual(orig *Transaction, cpy *Transaction) error { + // compare nonce, price, gaslimit, recipient, amount, payload, V, R, S + if want, got := orig.Hash(), cpy.Hash(); want != got { + return fmt.Errorf("parsed tx differs from original tx, want %v, got %v", want, got) + } + if want, got := orig.ChainId(), cpy.ChainId(); want.Cmp(got) != 0 { + return fmt.Errorf("invalid chain id, want %d, got %d", want, got) + } + if orig.AccessList() != nil { + if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) { + return fmt.Errorf("access list wrong!") } } + return nil } diff --git a/core/vm/interface.go b/core/vm/interface.go index fb5bbca48f..ad9b05d666 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -57,6 +57,7 @@ type StateDB interface { // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool + PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) AddressInAccessList(addr common.Address) bool SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 7cdb4ebd22..c97bdc420c 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -114,11 +114,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { sender = vm.AccountRef(cfg.Origin) ) if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { - cfg.State.AddAddressToAccessList(cfg.Origin) - cfg.State.AddAddressToAccessList(address) - for _, addr := range vmenv.ActivePrecompiles() { - cfg.State.AddAddressToAccessList(addr) - } + cfg.State.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. @@ -150,10 +146,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { sender = vm.AccountRef(cfg.Origin) ) if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { - cfg.State.AddAddressToAccessList(cfg.Origin) - for _, addr := range vmenv.ActivePrecompiles() { - cfg.State.AddAddressToAccessList(addr) - } + cfg.State.PrepareAccessList(cfg.Origin, nil, vmenv.ActivePrecompiles(), nil) } // Call the code with the given configuration. @@ -177,12 +170,9 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) + statedb := cfg.State if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { - cfg.State.AddAddressToAccessList(cfg.Origin) - cfg.State.AddAddressToAccessList(address) - for _, addr := range vmenv.ActivePrecompiles() { - cfg.State.AddAddressToAccessList(addr) - } + statedb.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } // Call the code with the given configuration. diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 416a387e31..5ddd2f9848 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -1387,7 +1387,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) case err == nil: peer.log.Trace("Delivered new batch of data", "type", kind, "count", packet.Stats()) default: - peer.log.Trace("Failed to deliver retrieved data", "type", kind, "err", err) + peer.log.Debug("Failed to deliver retrieved data", "type", kind, "err", err) } } // Blocks assembled, try to update the progress diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 89caeeb45b..4fd2df10e2 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -63,7 +63,7 @@ func newTestBackend(t *testing.T) *testBackend { Config: params.TestChainConfig, Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, } - signer = types.NewEIP155Signer(gspec.Config.ChainID) + signer = types.LatestSigner(gspec.Config) ) engine := ethash.NewFaker() db := rawdb.NewMemoryDatabase() diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 1bf59552c9..bd995d08a9 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -753,14 +753,15 @@ func (api *API) traceTx(ctx context.Context, message core.Message, vmctx vm.Bloc default: tracer = vm.NewStructLogger(config.LogConfig) } + // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer}) - result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } - // Depending on the tracer type, format and return the output + + // Depending on the tracer type, format and return the output. switch tracer := tracer.(type) { case *vm.StructLogger: // If the result contains a revert reason, return it. diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index dba7ce87cf..80775caa8e 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -556,7 +556,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost if data, ok := jst.ctx["input"].([]byte); ok { input = data } - intrinsicGas, err := core.IntrinsicGas(input, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) + intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) if err != nil { return err } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 8dc34a835e..a17696356c 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -521,7 +520,7 @@ func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64 // If the transaction was a contract creation use the TransactionReceipt method to get the // contract address after the transaction has been mined. func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return err } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 8b175ee066..9a5a45e34f 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -560,7 +560,7 @@ func sendTransaction(ec *Client) error { } // Create transaction tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) if err != nil { return err diff --git a/ethclient/signer.go b/ethclient/signer.go index 74a93f1e2f..9de020b352 100644 --- a/ethclient/signer.go +++ b/ethclient/signer.go @@ -51,6 +51,9 @@ func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) return s.addr, nil } +func (s *senderFromServer) ChainID() *big.Int { + panic("can't sign with senderFromServer") +} func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { panic("can't sign with senderFromServer") } diff --git a/graphql/graphql.go b/graphql/graphql.go index 5dc0f723d3..2374beb8e1 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -246,12 +245,8 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, if err != nil || tx == nil { return nil, err } - var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } + signer := types.LatestSigner(t.backend.ChainConfig()) from, _ := types.Sender(signer, tx) - return &Account{ backend: t.backend, address: from, @@ -1022,7 +1017,7 @@ func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Has func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hexutil.Bytes }) (common.Hash, error) { tx := new(types.Transaction) - if err := rlp.DecodeBytes(args.Data, tx); err != nil { + if err := tx.UnmarshalBinary(args.Data); err != nil { return common.Hash{}, err } hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx) diff --git a/interfaces.go b/interfaces.go index 1ff31f96b6..afcdc17e58 100644 --- a/interfaces.go +++ b/interfaces.go @@ -119,6 +119,8 @@ type CallMsg struct { GasPrice *big.Int // wei <-> gas exchange ratio Value *big.Int // amount of wei sent along with the call Data []byte // input data, usually an ABI-encoded contract method invocation + + AccessList types.AccessList // EIP-2930 access list. } // A ContractCaller provides contract calls, essentially transactions that are executed by diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 52b4f5f506..622063cf64 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -410,7 +410,7 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) return nil, err } - data, err := rlp.EncodeToBytes(signed) + data, err := signed.MarshalBinary() if err != nil { return nil, err } @@ -748,12 +748,13 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A // CallArgs represents the arguments for a call. type CallArgs struct { - From *common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"data"` + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` + AccessList *types.AccessList `json:"accessList"` } // ToMessage converts CallArgs to the Message type used by the core evm @@ -780,18 +781,20 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() } - value := new(big.Int) if args.Value != nil { value = args.Value.ToInt() } - var data []byte if args.Data != nil { data = *args.Data } + var accessList types.AccessList + if args.AccessList != nil { + accessList = *args.AccessList + } - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, accessList, false) return msg } @@ -869,13 +872,13 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo evm.Cancel() }() - // Setup the gas pool (also for unmetered requests) - // and apply the message. + // Execute the message. gp := new(core.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { return nil, err } + // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) @@ -1200,33 +1203,43 @@ func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Bloc // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` } // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} + // Determine the signer. For replay-protected transactions, use the most permissive + // signer, because we assume that signers are backwards-compatible with old + // transactions. For non-protected transactions, the homestead signer signer is used + // because the return value of ChainId is zero for those transactions. + var signer types.Signer if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) + signer = types.LatestSignerForChainID(tx.ChainId()) + } else { + signer = types.HomesteadSigner{} } + from, _ := types.Sender(signer, tx) v, r, s := tx.RawSignatureValues() - result := &RPCTransaction{ + Type: hexutil.Uint64(tx.Type()), From: from, Gas: hexutil.Uint64(tx.Gas()), GasPrice: (*hexutil.Big)(tx.GasPrice()), @@ -1244,6 +1257,11 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + if tx.Type() == types.AccessListTxType { + al := tx.AccessList() + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + } return result } @@ -1267,7 +1285,7 @@ func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.By if index >= uint64(len(txs)) { return nil } - blob, _ := rlp.EncodeToBytes(txs[index]) + blob, _ := txs[index].MarshalBinary() return blob } @@ -1285,11 +1303,15 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa type PublicTransactionPoolAPI struct { b Backend nonceLock *AddrLocker + signer types.Signer } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI { - return &PublicTransactionPoolAPI{b, nonceLock} + // The signer used by the API should always be the 'latest' known one because we expect + // signers to be backwards-compatible with old transactions. + signer := types.LatestSigner(b.ChainConfig()) + return &PublicTransactionPoolAPI{b, nonceLock, signer} } // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. @@ -1394,7 +1416,7 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, } } // Serialize to RLP and return - return rlp.EncodeToBytes(tx) + return tx.MarshalBinary() } // GetTransactionReceipt returns the transaction receipt for the given transaction hash. @@ -1412,10 +1434,9 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha } receipt := receipts[index] - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } + // Derive the sender. + bigblock := new(big.Int).SetUint64(blockNumber) + signer := types.MakeSigner(s.b.ChainConfig(), bigblock) from, _ := types.Sender(signer, tx) fields := map[string]interface{}{ @@ -1430,6 +1451,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha "contractAddress": nil, "logs": receipt.Logs, "logsBloom": receipt.Bloom, + "type": hexutil.Uint(tx.Type()), } // Assign receipt status or post state. @@ -1473,9 +1495,13 @@ type SendTxArgs struct { // newer name and should be preferred by clients. Data *hexutil.Bytes `json:"data"` Input *hexutil.Bytes `json:"input"` + + // For non-legacy transactions + AccessList *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` } -// setDefaults is a helper function that fills in default values for unspecified tx fields. +// setDefaults fills in default values for unspecified tx fields. func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { if args.GasPrice == nil { price, err := b.SuggestPrice(ctx) @@ -1509,6 +1535,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { return errors.New(`contract creation without any data provided`) } } + // Estimate the gas usage if necessary. if args.Gas == nil { // For backwards-compatibility reason, we try both input and data @@ -1518,11 +1545,12 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { input = args.Data } callArgs := CallArgs{ - From: &args.From, // From shouldn't be nil - To: args.To, - GasPrice: args.GasPrice, - Value: args.Value, - Data: input, + From: &args.From, // From shouldn't be nil + To: args.To, + GasPrice: args.GasPrice, + Value: args.Value, + Data: input, + AccessList: args.AccessList, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) @@ -1532,9 +1560,15 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { args.Gas = &estimated log.Trace("Estimate gas usage automatically", "gas", args.Gas) } + if args.ChainID == nil { + id := (*hexutil.Big)(b.ChainConfig().ChainID) + args.ChainID = id + } return nil } +// toTransaction converts the arguments to a transaction. +// This assumes that setDefaults has been called. func (args *SendTxArgs) toTransaction() *types.Transaction { var input []byte if args.Input != nil { @@ -1542,10 +1576,30 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { } else if args.Data != nil { input = *args.Data } - if args.To == nil { - return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + + var data types.TxData + if args.AccessList == nil { + data = &types.LegacyTx{ + To: args.To, + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: input, + } + } else { + data = &types.AccessListTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: input, + AccessList: *args.AccessList, + } } - return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input) + return types.NewTx(data) } // SubmitTransaction is a helper function that submits tx to txPool and logs a message. @@ -1619,7 +1673,7 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen } // Assemble the transaction and obtain rlp tx := args.toTransaction() - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return nil, err } @@ -1628,9 +1682,9 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen // SendRawTransaction will add the signed transaction to the transaction pool. // The sender is responsible for signing the transaction and using the correct nonce. -func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) { +func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { tx := new(types.Transaction) - if err := rlp.DecodeBytes(encodedTx, tx); err != nil { + if err := tx.UnmarshalBinary(input); err != nil { return common.Hash{}, err } return SubmitTransaction(ctx, s.b, tx) @@ -1691,7 +1745,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if err != nil { return nil, err } - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return nil, err } @@ -1713,11 +1767,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err } transactions := make([]*RPCTransaction, 0, len(pending)) for _, tx := range pending { - var signer types.Signer = types.HomesteadSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) + from, _ := types.Sender(s.signer, tx) if _, exists := accounts[from]; exists { transactions = append(transactions, newRPCPendingTransaction(tx)) } @@ -1754,13 +1804,9 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr return common.Hash{}, err } for _, p := range pending { - var signer types.Signer = types.HomesteadSigner{} - if p.Protected() { - signer = types.NewEIP155Signer(p.ChainId()) - } - wantSigHash := signer.Hash(matchTx) - - if pFrom, err := types.Sender(signer, p); err == nil && pFrom == sendArgs.From && signer.Hash(p) == wantSigHash { + wantSigHash := s.signer.Hash(matchTx) + pFrom, err := types.Sender(s.signer, p) + if err == nil && pFrom == sendArgs.From && s.signer.Hash(p) == wantSigHash { // Match. Re-sign and send the transaction. if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 { sendArgs.GasPrice = gasPrice @@ -1778,7 +1824,6 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr return signedTx.Hash(), nil } } - return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash()) } diff --git a/internal/guide/guide_test.go b/internal/guide/guide_test.go index 9c7ad16d18..abc48e0e4b 100644 --- a/internal/guide/guide_test.go +++ b/internal/guide/guide_test.go @@ -31,6 +31,7 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -75,7 +76,8 @@ func TestAccountManagement(t *testing.T) { if err != nil { t.Fatalf("Failed to create signer account: %v", err) } - tx, chain := new(types.Transaction), big.NewInt(1) + tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil) + chain := big.NewInt(1) // Sign a transaction with a single authorization if _, err := ks.SignTxWithPassphrase(signer, "Signer password", tx, chain); err != nil { diff --git a/les/benchmark.go b/les/benchmark.go index 6255c1049e..757822a6b3 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -171,7 +171,7 @@ type benchmarkTxSend struct { func (b *benchmarkTxSend) init(h *serverHandler, count int) error { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) - signer := types.NewEIP155Signer(big.NewInt(18)) + signer := types.LatestSigner(h.server.chainConfig) b.txs = make(types.Transactions, count) for i := range b.txs { diff --git a/les/odr_test.go b/les/odr_test.go index a43382e05e..0c75014d49 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -135,7 +135,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai from := statedb.GetOrNewStateObject(bankAddr) from.SetBalance(math.MaxBig256) - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)} context := core.NewEVMBlockContext(header, bc, nil) txContext := core.NewEVMTxContext(msg) @@ -150,7 +150,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai header := lc.GetHeaderByHash(bhash) state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(bankAddr, math.MaxBig256) - msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)} context := core.NewEVMBlockContext(header, lc, nil) txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{}) diff --git a/light/odr_test.go b/light/odr_test.go index cb22334fdc..0fc45b8734 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -194,7 +194,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) - msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} + msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, nil, false)} txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, chain, nil) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{}) diff --git a/light/txpool.go b/light/txpool.go index 2831de5a65..bf5f9ff583 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" ) const ( @@ -69,6 +68,7 @@ type TxPool struct { clearIdx uint64 // earliest block nr that can contain mined tx info istanbul bool // Fork indicator whether we are in the istanbul stage. + eip2718 bool // Fork indicator whether we are in the eip2718 stage. } // TxRelayBackend provides an interface to the mechanism that forwards transacions @@ -90,7 +90,7 @@ type TxRelayBackend interface { func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool { pool := &TxPool{ config: config, - signer: types.NewEIP155Signer(config.ChainID), + signer: types.LatestSigner(config), nonce: make(map[common.Address]uint64), pending: make(map[common.Hash]*types.Transaction), mined: make(map[common.Hash][]*types.Transaction), @@ -314,6 +314,7 @@ func (pool *TxPool) setNewHead(head *types.Header) { // Update fork indicator by next pending block number next := new(big.Int).Add(head.Number, big.NewInt(1)) pool.istanbul = pool.config.IsIstanbul(next) + pool.eip2718 = pool.config.IsYoloV3(next) } // Stop stops the light transaction pool @@ -381,7 +382,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error } // Should supply enough intrinsic gas - gas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul) + gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul) if err != nil { return err } @@ -430,8 +431,7 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error { pool.mu.Lock() defer pool.mu.Unlock() - - data, err := rlp.EncodeToBytes(tx) + data, err := tx.MarshalBinary() if err != nil { return err } diff --git a/miner/worker.go b/miner/worker.go index e81d50e46e..2cee6af0c3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -654,7 +654,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { state.StartPrefetcher("miner") env := &environment{ - signer: types.NewEIP155Signer(w.chainConfig.ChainID), + signer: types.MakeSigner(w.chainConfig, header.Number), state: state, ancestors: mapset.NewSet(), family: mapset.NewSet(), @@ -829,6 +829,11 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin w.current.tcount++ txs.Shift() + case errors.Is(err, core.ErrTxTypeNotSupported): + // Pop the unsupported transaction without shifting in the next from the account + log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type()) + txs.Pop() + default: // Strange error, discard the transaction and get the next in line (note, the // nonce-too-high clause will prevent us from executing in vain). diff --git a/miner/worker_test.go b/miner/worker_test.go index a5c558ba5f..0fe62316e1 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -81,10 +81,25 @@ func init() { Period: 10, Epoch: 30000, } - tx1, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) + + signer := types.LatestSigner(params.TestChainConfig) + tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{ + ChainID: params.TestChainConfig.ChainID, + Nonce: 0, + To: &testUserAddress, + Value: big.NewInt(1000), + Gas: params.TxGas, + }) pendingTxs = append(pendingTxs, tx1) - tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) + + tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{ + Nonce: 1, + To: &testUserAddress, + Value: big.NewInt(1000), + Gas: params.TxGas, + }) newTxs = append(newTxs, tx2) + rand.Seed(time.Now().UnixNano()) } diff --git a/params/config.go b/params/config.go index 0dbf592d19..929bdbeb94 100644 --- a/params/config.go +++ b/params/config.go @@ -215,7 +215,7 @@ var ( // YoloV3ChainConfig contains the chain parameters to run a node on the YOLOv3 test network. YoloV3ChainConfig = &ChainConfig{ - ChainID: big.NewInt(133519467574834), + ChainID: new(big.Int).SetBytes([]byte("yolov3x")), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, @@ -246,9 +246,9 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) diff --git a/params/protocol_params.go b/params/protocol_params.go index ffb901ec3d..88f1a06e12 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -60,20 +60,23 @@ const ( JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. - CreateDataGas uint64 = 200 // - CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. - ExpGas uint64 = 10 // Once per EXP instruction - LogGas uint64 = 375 // Per LOG* operation. - CopyGas uint64 = 3 // - StackLimit uint64 = 1024 // Maximum size of VM stack allowed. - TierStepGas uint64 = 0 // Once per operation, for a selection of them. - LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. - CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. - Create2Gas uint64 = 32000 // Once per CREATE2 operation - SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. - MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. - TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. - TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + CreateDataGas uint64 = 200 // + CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. + ExpGas uint64 = 10 // Once per EXP instruction + LogGas uint64 = 375 // Per LOG* operation. + CopyGas uint64 = 3 // + StackLimit uint64 = 1024 // Maximum size of VM stack allowed. + TierStepGas uint64 = 0 // Once per operation, for a selection of them. + LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. + CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. + Create2Gas uint64 = 32000 // Once per CREATE2 operation + SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. + MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. + + TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. + TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) + TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list + TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. diff --git a/signer/core/api.go b/signer/core/api.go index 07e206a74c..968dcfb2ed 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/signer/storage" ) @@ -574,11 +573,11 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth return nil, err } - rlpdata, err := rlp.EncodeToBytes(signedTx) + data, err := signedTx.MarshalBinary() if err != nil { return nil, err } - response := ethapi.SignTransactionResult{Raw: rlpdata, Tx: signedTx} + response := ethapi.SignTransactionResult{Raw: data, Tx: signedTx} // Finally, send the signed tx to the UI api.UI.OnApprovedTx(response) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 53c7d955be..03dc01459c 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -182,27 +182,21 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if err != nil { return nil, nil, common.Hash{}, err } + + // Prepare the EVM. txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) - if config.IsYoloV3(context.BlockNumber) { - statedb.AddAddressToAccessList(msg.From()) - if dst := msg.To(); dst != nil { - statedb.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range evm.ActivePrecompiles() { - statedb.AddAddressToAccessList(addr) - } - } + // Execute the message. + snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) - snapshot := statedb.Snapshot() if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { statedb.RevertToSnapshot(snapshot) } + // Commit block statedb.Commit(config.IsEIP158(block.Number())) // Add 0-value mining reward. This only makes a difference in the cases @@ -300,7 +294,7 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { return nil, fmt.Errorf("invalid tx data %q", dataHex) } - msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, true) + msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, nil, true) return msg, nil } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index aea90535c3..82ee01de15 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -55,7 +55,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { return nil, nil, err } // Intrinsic gas - requiredGas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, isHomestead, isIstanbul) + requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul) if err != nil { return nil, nil, err } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index d4488b4029..29706f2e9d 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -1,16 +1,9 @@ package trie import ( - "bytes" - "fmt" - "math/big" - mrand "math/rand" "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" ) @@ -78,169 +71,6 @@ func TestValLength56(t *testing.T) { } } -func genTxs(num uint64) (types.Transactions, error) { - key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - var addr = crypto.PubkeyToAddress(key.PublicKey) - newTx := func(i uint64) (*types.Transaction, error) { - signer := types.NewEIP155Signer(big.NewInt(18)) - tx, err := types.SignTx(types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil), signer, key) - return tx, err - } - var txs types.Transactions - for i := uint64(0); i < num; i++ { - tx, err := newTx(i) - if err != nil { - return nil, err - } - txs = append(txs, tx) - } - return txs, nil -} - -func TestDeriveSha(t *testing.T) { - txs, err := genTxs(0) - if err != nil { - t.Fatal(err) - } - for len(txs) < 1000 { - exp := types.DeriveSha(txs, newEmpty()) - got := types.DeriveSha(txs, NewStackTrie(nil)) - if !bytes.Equal(got[:], exp[:]) { - t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) - } - newTxs, err := genTxs(uint64(len(txs) + 1)) - if err != nil { - t.Fatal(err) - } - txs = append(txs, newTxs...) - } -} - -func BenchmarkDeriveSha200(b *testing.B) { - txs, err := genTxs(200) - if err != nil { - b.Fatal(err) - } - var exp common.Hash - var got common.Hash - b.Run("std_trie", func(b *testing.B) { - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - exp = types.DeriveSha(txs, newEmpty()) - } - }) - - b.Run("stack_trie", func(b *testing.B) { - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - got = types.DeriveSha(txs, NewStackTrie(nil)) - } - }) - if got != exp { - b.Errorf("got %x exp %x", got, exp) - } -} - -type dummyDerivableList struct { - len int - seed int -} - -func newDummy(seed int) *dummyDerivableList { - d := &dummyDerivableList{} - src := mrand.NewSource(int64(seed)) - // don't use lists longer than 4K items - d.len = int(src.Int63() & 0x0FFF) - d.seed = seed - return d -} - -func (d *dummyDerivableList) Len() int { - return d.len -} - -func (d *dummyDerivableList) GetRlp(i int) []byte { - src := mrand.NewSource(int64(d.seed + i)) - // max item size 256, at least 1 byte per item - size := 1 + src.Int63()&0x00FF - data := make([]byte, size) - _, err := mrand.New(src).Read(data) - if err != nil { - panic(err) - } - return data -} - -func printList(l types.DerivableList) { - fmt.Printf("list length: %d\n", l.Len()) - fmt.Printf("{\n") - for i := 0; i < l.Len(); i++ { - v := l.GetRlp(i) - fmt.Printf("\"0x%x\",\n", v) - } - fmt.Printf("},\n") -} - -func TestFuzzDeriveSha(t *testing.T) { - // increase this for longer runs -- it's set to quite low for travis - rndSeed := mrand.Int() - for i := 0; i < 10; i++ { - seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), newEmpty()) - got := types.DeriveSha(newDummy(i), NewStackTrie(nil)) - if !bytes.Equal(got[:], exp[:]) { - printList(newDummy(seed)) - t.Fatalf("seed %d: got %x exp %x", seed, got, exp) - } - } -} - -type flatList struct { - rlpvals []string -} - -func newFlatList(rlpvals []string) *flatList { - return &flatList{rlpvals} -} -func (f *flatList) Len() int { - return len(f.rlpvals) -} -func (f *flatList) GetRlp(i int) []byte { - return hexutil.MustDecode(f.rlpvals[i]) -} - -// TestDerivableList contains testcases found via fuzzing -func TestDerivableList(t *testing.T) { - type tcase []string - tcs := []tcase{ - { - "0xc041", - }, - { - "0xf04cf757812428b0763112efb33b6f4fad7deb445e", - "0xf04cf757812428b0763112efb33b6f4fad7deb445e", - }, - { - "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", - "0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", - "0x14abd5c47c0be87b0454596baad2", - "0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", - }, - } - for i, tc := range tcs[1:] { - exp := types.DeriveSha(newFlatList(tc), newEmpty()) - got := types.DeriveSha(newFlatList(tc), NewStackTrie(nil)) - if !bytes.Equal(got[:], exp[:]) { - t.Fatalf("case %d: got %x exp %x", i, got, exp) - } - } -} - // TestUpdateSmallNodes tests a case where the leaves are small (both key and value), // which causes a lot of node-within-node. This case was found via fuzzing. func TestUpdateSmallNodes(t *testing.T) { From de9465f991916e183a504ce79988c6cef544f7f1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 25 Feb 2021 18:36:01 +0100 Subject: [PATCH 350/948] cmd/devp2p: add eth66 test suite (#22363) Co-authored-by: Martin Holst Swende --- cmd/devp2p/README.md | 12 +- cmd/devp2p/internal/ethtest/eth66_suite.go | 382 ++++++++++++++++++ .../internal/ethtest/eth66_suiteHelpers.go | 270 +++++++++++++ cmd/devp2p/internal/ethtest/suite.go | 39 +- cmd/devp2p/internal/ethtest/transaction.go | 29 +- cmd/devp2p/internal/ethtest/types.go | 8 +- cmd/devp2p/rlpxcmd.go | 7 +- go.mod | 1 + 8 files changed, 721 insertions(+), 27 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/eth66_suite.go create mode 100644 cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index ca0b852fda..e934ee25c9 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -100,7 +100,17 @@ Then, run the following command, replacing `` with the enode of the geth ``` Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. - + +#### Eth66 Test Suite + +The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically. +To run the eth66 protocol test suite, initialize a geth node as described above and run the following command, +replacing `` with the enode of the geth node: + + ``` + devp2p rlpx eth66-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json +``` + [eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md [dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup [discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go new file mode 100644 index 0000000000..644fed61eb --- /dev/null +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -0,0 +1,382 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" +) + +// TestStatus_66 attempts to connect to the given node and exchange +// a status message with it on the eth66 protocol, and then check to +// make sure the chain head is correct. +func (s *Suite) TestStatus_66(t *utesting.T) { + conn := s.dial66(t) + // get protoHandshake + conn.handshake(t) + // get status + switch msg := conn.statusExchange66(t, s.chain).(type) { + case *Status: + status := *msg + if status.ProtocolVersion != uint32(66) { + t.Fatalf("mismatch in version: wanted 66, got %d", status.ProtocolVersion) + } + t.Logf("got status message: %s", pretty.Sdump(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestGetBlockHeaders_66 tests whether the given node can respond to +// an eth66 `GetBlockHeaders` request and that the response is accurate. +func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { + conn := s.setupConnection66(t) + // get block headers + req := ð.GetBlockHeadersPacket66{ + RequestId: 3, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + // write message + headers := s.getBlockHeaders66(t, conn, req, req.RequestId) + // check for correct headers + headersMatch(t, s.chain, headers) +} + +// TestSimultaneousRequests_66 sends two simultaneous `GetBlockHeader` requests +// with different request IDs and checks to make sure the node responds with the correct +// headers per request. +func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { + // create two connections + conn1, conn2 := s.setupConnection66(t), s.setupConnection66(t) + // create two requests + req1 := ð.GetBlockHeadersPacket66{ + RequestId: 111, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + req2 := ð.GetBlockHeadersPacket66{ + RequestId: 222, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 4, + Skip: 1, + Reverse: false, + }, + } + // wait for headers for first request + headerChan := make(chan BlockHeaders, 1) + go func(headers chan BlockHeaders) { + headers <- s.getBlockHeaders66(t, conn1, req1, req1.RequestId) + }(headerChan) + // check headers of second request + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn2, req2, req2.RequestId)) + // check headers of first request + headersMatch(t, s.chain, <-headerChan) +} + +// TestBroadcast_66 tests whether a block announcement is correctly +// propagated to the given node's peer(s) on the eth66 protocol. +func (s *Suite) TestBroadcast_66(t *utesting.T) { + sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock66(s.chain.Head()); err != nil { + t.Fatal(err) + } +} + +// TestGetBlockBodies_66 tests whether the given node can respond to +// a `GetBlockBodies` request and that the response is accurate over +// the eth66 protocol. +func (s *Suite) TestGetBlockBodies_66(t *utesting.T) { + conn := s.setupConnection66(t) + // create block bodies request + id := uint64(55) + req := ð.GetBlockBodiesPacket66{ + RequestId: id, + GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + }, + } + if err := conn.write66(req, GetBlockBodies{}.Code()); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + reqID, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case BlockBodies: + if reqID != req.RequestId { + t.Fatalf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) + } + t.Logf("received %d block bodies", len(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestLargeAnnounce_66 tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ + { + Block: largeBlock(), + TD: s.fullChain.TD(nextBlock + 1), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), + }, + { + Block: largeBlock(), + TD: largeNumber(2), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + }, + } + + for i, blockAnnouncement := range blocks[0:3] { + t.Logf("Testing malicious announcement: %v\n", i) + sendConn := s.setupConnection66(t) + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // Invalid announcement, check that peer disconnected + switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + } + // Test the last block as a valid block + sendConn := s.setupConnection66(t) + receiveConn := s.setupConnection66(t) + s.testAnnounce66(t, sendConn, receiveConn, blocks[3]) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock66(s.fullChain.blocks[nextBlock]); err != nil { + t.Fatal(err) + } +} + +// TestMaliciousHandshake_66 tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { + conn := s.dial66(t) + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 66}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 66}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + t.Logf("Testing malicious handshake %v\n", i) + // Init the handshake + if err := conn.Write(handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check that the peer disconnected + timeout := 20 * time.Second + // Discard one hello + for i := 0; i < 2; i++ { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Hello's are sent concurrently, so ignore them + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } + // Dial for the next round + conn = s.dial66(t) + } +} + +// TestMaliciousStatus_66 sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus_66(t *utesting.T) { + conn := s.dial66(t) + // get protoHandshake + conn.handshake(t) + status := &Status{ + ProtocolVersion: uint32(66), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + switch msg := conn.statusExchange(t, s.chain, status).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("expected status, got: %#v ", msg) + } + // wait for disconnect + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + return + default: + t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} + +func (s *Suite) TestTransaction_66(t *utesting.T) { + tests := []*types.Transaction{ + getNextTxFromChain(t, s), + unknownTx(t, s), + } + for i, tx := range tests { + t.Logf("Testing tx propagation: %v\n", i) + sendSuccessfulTx66(t, s, tx) + } +} + +func (s *Suite) TestMaliciousTx_66(t *utesting.T) { + tests := []*types.Transaction{ + getOldTxFromChain(t, s), + invalidNonceTx(t, s), + hugeAmount(t, s), + hugeGasPrice(t, s), + hugeData(t, s), + } + for i, tx := range tests { + t.Logf("Testing malicious tx propagation: %v\n", i) + sendFailingTx66(t, s, tx) + } +} + +// TestZeroRequestID_66 checks that a request ID of zero is still handled +// by the node. +func (s *Suite) TestZeroRequestID_66(t *utesting.T) { + conn := s.setupConnection66(t) + req := ð.GetBlockHeadersPacket66{ + RequestId: 0, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 0, + }, + Amount: 2, + }, + } + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req, req.RequestId)) +} + +// TestSameRequestID_66 sends two requests with the same request ID +// concurrently to a single node. +func (s *Suite) TestSameRequestID_66(t *utesting.T) { + conn := s.setupConnection66(t) + // create two separate requests with same ID + reqID := uint64(1234) + req1 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 0, + }, + Amount: 2, + }, + } + req2 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 33, + }, + Amount: 2, + }, + } + // send requests concurrently + go func() { + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req2, reqID)) + }() + // check response from first request + headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req1, reqID)) +} diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go new file mode 100644 index 0000000000..b7fa1dce26 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -0,0 +1,270 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "fmt" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" +) + +func (c *Conn) statusExchange66(t *utesting.T, chain *Chain) Message { + status := &Status{ + ProtocolVersion: uint32(66), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + return c.statusExchange(t, chain, status) +} + +func (s *Suite) dial66(t *utesting.T) *Conn { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + conn.caps = append(conn.caps, p2p.Cap{Name: "eth", Version: 66}) + return conn +} + +func (c *Conn) write66(req eth.Packet, code int) error { + payload, err := rlp.EncodeToBytes(req) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(code), payload) + return err +} + +func (c *Conn) read66() (uint64, Message) { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return 0, errorf("could not read from connection: %v", err) + } + + var msg Message + + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + ethMsg := new(eth.GetBlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket) + case (BlockHeaders{}).Code(): + ethMsg := new(eth.BlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket) + case (GetBlockBodies{}).Code(): + ethMsg := new(eth.GetBlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket) + case (BlockBodies{}).Code(): + ethMsg := new(eth.BlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) + default: + msg = errorf("invalid message code: %d", code) + } + + if msg != nil { + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return 0, msg + } + return 0, errorf("invalid message: %s", string(rawData)) +} + +// ReadAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { + start := time.Now() + for time.Since(start) < timeout { + timeout := time.Now().Add(10 * time.Second) + c.SetReadDeadline(timeout) + + reqID, msg := c.read66() + + switch msg := msg.(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + headers, err := chain.GetHeaders(*msg) + if err != nil { + return 0, errorf("could not get headers for inbound header request: %v", err) + } + + if err := c.Write(headers); err != nil { + return 0, errorf("could not write to connection: %v", err) + } + default: + return reqID, msg + } + } + return 0, errorf("no message received within %v", timeout) +} + +func (s *Suite) setupConnection66(t *utesting.T) *Conn { + // create conn + sendConn := s.dial66(t) + sendConn.handshake(t) + sendConn.statusExchange66(t, s.chain) + return sendConn +} + +func (s *Suite) testAnnounce66(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { + // Announce the block. + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + s.waitAnnounce66(t, receiveConn, blockAnnouncement) +} + +func (s *Suite) waitAnnounce66(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { + timeout := 20 * time.Second + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case *NewBlock: + t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) + assert.Equal(t, + blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement", + ) + assert.Equal(t, + blockAnnouncement.TD, msg.TD, + "wrong TD in announcement", + ) + case *NewBlockHashes: + blockHashes := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(blockHashes)) + assert.Equal(t, blockAnnouncement.Block.Hash(), blockHashes[0].Hash, + "wrong block hash in announcement", + ) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// waitForBlock66 waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock66(block *types.Block) error { + defer c.SetReadDeadline(time.Time{}) + + timeout := time.Now().Add(20 * time.Second) + c.SetReadDeadline(timeout) + for { + req := eth.GetBlockHeadersPacket66{ + RequestId: 54, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: block.Hash(), + }, + Amount: 1, + }, + } + if err := c.write66(req, GetBlockHeaders{}.Code()); err != nil { + return err + } + + reqID, msg := c.read66() + // check message + switch msg := msg.(type) { + case BlockHeaders: + // check request ID + if reqID != req.RequestId { + return fmt.Errorf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) + } + if len(msg) > 0 { + return nil + } + time.Sleep(100 * time.Millisecond) + default: + return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) + } + } +} + +func sendSuccessfulTx66(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn := s.setupConnection66(t) + sendSuccessfulTxWithConn(t, s, tx, sendConn) +} + +func sendFailingTx66(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn, recvConn := s.setupConnection66(t), s.setupConnection66(t) + sendFailingTxWithConns(t, s, tx, sendConn, recvConn) +} + +func (s *Suite) getBlockHeaders66(t *utesting.T, conn *Conn, req eth.Packet, expectedID uint64) BlockHeaders { + if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check block headers response + reqID, msg := conn.readAndServe66(s.chain, timeout) + + switch msg := msg.(type) { + case BlockHeaders: + if reqID != expectedID { + t.Fatalf("request ID mismatch: wanted %d, got %d", expectedID, reqID) + } + return msg + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + return nil + } +} + +func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) { + for _, header := range headers { + num := header.Number.Uint64() + t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) + assert.Equal(t, chain.blocks[int(num)].Header(), header) + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index edf7bb7e31..48010b90dd 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -53,29 +53,47 @@ type Suite struct { // NewSuite creates and returns a new eth-test suite that can // be used to test the given node against the given blockchain // data. -func NewSuite(dest *enode.Node, chainfile string, genesisfile string) *Suite { +func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) { chain, err := loadChain(chainfile, genesisfile) if err != nil { - panic(err) + return nil, err } return &Suite{ Dest: dest, chain: chain.Shorten(1000), fullChain: chain, - } + }, nil } -func (s *Suite) AllTests() []utesting.Test { +func (s *Suite) EthTests() []utesting.Test { return []utesting.Test{ + // status {Name: "Status", Fn: s.TestStatus}, + {Name: "Status_66", Fn: s.TestStatus_66}, + // get block headers {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, - {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "GetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, + {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, + {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, + // get block bodies {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "GetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + // broadcast + {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "Broadcast_66", Fn: s.TestBroadcast_66}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + // malicious handshakes + status {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus}, + // test transactions {Name: "TestTransactions", Fn: s.TestTransaction}, + {Name: "TestTransactions_66", Fn: s.TestTransaction_66}, {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, + {Name: "TestMaliciousTransactions_66", Fn: s.TestMaliciousTx_66}, } } @@ -161,7 +179,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { headers := *msg for _, header := range headers { num := header.Number.Uint64() - t.Logf("received header (%d): %s", num, pretty.Sdump(header)) + t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) assert.Equal(t, s.chain.blocks[int(num)].Header(), header) } default: @@ -386,20 +404,23 @@ func (s *Suite) setupConnection(t *utesting.T) *Conn { // returning the created Conn if successful. func (s *Suite) dial() (*Conn, error) { var conn Conn - + // dial fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) if err != nil { return nil, err } conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey()) - // do encHandshake conn.ourKey, _ = crypto.GenerateKey() _, err = conn.Handshake(conn.ourKey) if err != nil { return nil, err } - + // set default p2p capabilities + conn.caps = []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + } return &conn, nil } diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index effcc3af29..21aa221e8b 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -30,6 +30,10 @@ var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c666 func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn := s.setupConnection(t) + sendSuccessfulTxWithConn(t, s, tx, sendConn) +} + +func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, sendConn *Conn) { t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) // Send the transaction if err := sendConn.Write(&Transactions{tx}); err != nil { @@ -41,20 +45,21 @@ func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { case *Transactions: recTxs := *msg - if len(recTxs) < 1 { - t.Fatalf("received transactions do not match send: %v", recTxs) - } - if tx.Hash() != recTxs[len(recTxs)-1].Hash() { - t.Fatalf("received transactions do not match send: got %v want %v", recTxs, tx) + for _, gotTx := range recTxs { + if gotTx.Hash() == tx.Hash() { + // Ok + return + } } + t.Fatalf("missing transaction: got %v missing %v", recTxs, tx.Hash()) case *NewPooledTransactionHashes: txHashes := *msg - if len(txHashes) < 1 { - t.Fatalf("received transactions do not match send: %v", txHashes) - } - if tx.Hash() != txHashes[len(txHashes)-1] { - t.Fatalf("wrong announcement received, wanted %v got %v", tx, txHashes) + for _, gotHash := range txHashes { + if gotHash == tx.Hash() { + return + } } + t.Fatalf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) default: t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) } @@ -62,6 +67,10 @@ func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) + sendFailingTxWithConns(t, s, tx, sendConn, recvConn) +} + +func sendFailingTxWithConns(t *utesting.T, s *Suite, tx *types.Transaction, sendConn, recvConn *Conn) { // Wait for a transaction announcement switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { case *NewPooledTransactionHashes: diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index b901f50700..734adff366 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -125,6 +125,7 @@ type Conn struct { *rlpx.Conn ourKey *ecdsa.PrivateKey ethProtocolVersion uint + caps []p2p.Cap } func (c *Conn) Read() Message { @@ -221,11 +222,8 @@ func (c *Conn) handshake(t *utesting.T) Message { pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] ourHandshake := &Hello{ Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: pub0, + Caps: c.caps, + ID: pub0, } if err := c.Write(ourHandshake); err != nil { t.Fatalf("could not write to connection: %v", err) diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index d90eb4687c..ac92818aa4 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -94,6 +94,9 @@ func rlpxEthTest(ctx *cli.Context) error { if ctx.NArg() < 3 { exit("missing path to chain.rlp as command-line argument") } - suite := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) - return runTests(ctx, suite.AllTests()) + suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) + if err != nil { + exit(err) + } + return runTests(ctx, suite.EthTests()) } diff --git a/go.mod b/go.mod index cddce4e0d8..a1099c52cc 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 From dc109cce26da8a93f74a998f9dd7fc2ac0ab98d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 25 Feb 2021 21:08:34 +0100 Subject: [PATCH 351/948] les: move server pool to les/vflux/client (#22377) * les: move serverPool to les/vflux/client * les: add metrics * les: moved ValueTracker inside ServerPool * les: protect against node registration before server pool is started * les/vflux/client: fixed tests * les: make peer registration safe --- internal/web3ext/web3ext.go | 14 +- les/client.go | 38 +---- les/client_handler.go | 14 ++ les/peer.go | 9 +- les/vflux/client/queueiterator_test.go | 11 -- les/{ => vflux/client}/serverpool.go | 173 +++++++++++++--------- les/{ => vflux/client}/serverpool_test.go | 64 ++++---- les/vflux/client/valuetracker.go | 77 +++++----- les/vflux/client/valuetracker_test.go | 4 +- 9 files changed, 206 insertions(+), 198 deletions(-) rename les/{ => vflux/client}/serverpool.go (76%) rename les/{ => vflux/client}/serverpool_test.go (86%) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 77954bbbf0..6fcf4b8380 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -33,7 +33,7 @@ var Modules = map[string]string{ "swarmfs": SwarmfsJs, "txpool": TxpoolJs, "les": LESJs, - "lespay": LESPayJs, + "vflux": VfluxJs, } const ChequebookJs = ` @@ -877,24 +877,24 @@ web3._extend({ }); ` -const LESPayJs = ` +const VfluxJs = ` web3._extend({ - property: 'lespay', + property: 'vflux', methods: [ new web3._extend.Method({ name: 'distribution', - call: 'lespay_distribution', + call: 'vflux_distribution', params: 2 }), new web3._extend.Method({ name: 'timeout', - call: 'lespay_timeout', + call: 'vflux_timeout', params: 2 }), new web3._extend.Method({ name: 'value', - call: 'lespay_value', + call: 'vflux_value', params: 2 }), ], @@ -902,7 +902,7 @@ web3._extend({ [ new web3._extend.Property({ name: 'requestStats', - getter: 'lespay_requestStats' + getter: 'vflux_requestStats' }), ] }); diff --git a/les/client.go b/les/client.go index 053118df5a..e20519fd91 100644 --- a/les/client.go +++ b/les/client.go @@ -57,8 +57,7 @@ type LightEthereum struct { handler *clientHandler txPool *light.TxPool blockchain *light.LightChain - serverPool *serverPool - valueTracker *vfc.ValueTracker + serverPool *vfc.ServerPool dialCandidates enode.Iterator pruner *pruner @@ -109,17 +108,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - valueTracker: vfc.NewValueTracker(lesDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, } - peers.subscribe((*vtSubscription)(leth.valueTracker)) - leth.serverPool = newServerPool(lesDb, []byte("serverpool:"), leth.valueTracker, time.Second, nil, &mclock.System{}, config.UltraLightServers) - peers.subscribe(leth.serverPool) - leth.dialCandidates = leth.serverPool.dialIterator + leth.serverPool, leth.dialCandidates = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, nil, &mclock.System{}, config.UltraLightServers, requestList) + leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) - leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout) + leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) leth.relay = newLesTxRelay(peers, leth.retriever) leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever) @@ -193,23 +189,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { return leth, nil } -// vtSubscription implements serverPeerSubscriber -type vtSubscription vfc.ValueTracker - -// registerPeer implements serverPeerSubscriber -func (v *vtSubscription) registerPeer(p *serverPeer) { - vt := (*vfc.ValueTracker)(v) - p.setValueTracker(vt, vt.Register(p.ID())) - p.updateVtParams() -} - -// unregisterPeer implements serverPeerSubscriber -func (v *vtSubscription) unregisterPeer(p *serverPeer) { - vt := (*vfc.ValueTracker)(v) - vt.Unregister(p.ID()) - p.setValueTracker(nil, nil) -} - type LightDummyAPI struct{} // Etherbase is the address that mining rewards will be send to @@ -266,7 +245,7 @@ func (s *LightEthereum) APIs() []rpc.API { }, { Namespace: "vflux", Version: "1.0", - Service: vfc.NewPrivateClientAPI(s.valueTracker), + Service: s.serverPool.API(), Public: false, }, }...) @@ -302,8 +281,8 @@ func (s *LightEthereum) Start() error { if err != nil { return err } - s.serverPool.addSource(discovery) - s.serverPool.start() + s.serverPool.AddSource(discovery) + s.serverPool.Start() // Start bloom request workers. s.wg.Add(bloomServiceThreads) s.startBloomHandlers(params.BloomBitsBlocksClient) @@ -316,8 +295,7 @@ func (s *LightEthereum) Start() error { // Ethereum protocol. func (s *LightEthereum) Stop() error { close(s.closeCh) - s.serverPool.stop() - s.valueTracker.Stop() + s.serverPool.Stop() s.peers.close() s.reqDist.close() s.odr.Stop() diff --git a/les/client_handler.go b/les/client_handler.go index 6cd786cda0..f8e9edc9fe 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -114,11 +114,25 @@ func (h *clientHandler) handle(p *serverPeer) error { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } + // Register peer with the server pool + if h.backend.serverPool != nil { + if nvt, err := h.backend.serverPool.RegisterNode(p.Node()); err == nil { + p.setValueTracker(nvt) + p.updateVtParams() + defer func() { + p.setValueTracker(nil) + h.backend.serverPool.UnregisterNode(p.Node()) + }() + } else { + return err + } + } // Register the peer locally if err := h.backend.peers.register(p); err != nil { p.Log().Error("Light Ethereum peer registration failed", "err", err) return err } + serverConnectionGauge.Update(int64(h.backend.peers.len())) connectedAt := mclock.Now() diff --git a/les/peer.go b/les/peer.go index 0361167ee1..78019b1d87 100644 --- a/les/peer.go +++ b/les/peer.go @@ -349,7 +349,6 @@ type serverPeer struct { fcServer *flowcontrol.ServerNode // Client side mirror token bucket. vtLock sync.Mutex - valueTracker *vfc.ValueTracker nodeValueTracker *vfc.NodeValueTracker sentReqs map[uint64]sentReqEntry @@ -676,9 +675,8 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter // setValueTracker sets the value tracker references for connected servers. Note that the // references should be removed upon disconnection by setValueTracker(nil, nil). -func (p *serverPeer) setValueTracker(vt *vfc.ValueTracker, nvt *vfc.NodeValueTracker) { +func (p *serverPeer) setValueTracker(nvt *vfc.NodeValueTracker) { p.vtLock.Lock() - p.valueTracker = vt p.nodeValueTracker = nvt if nvt != nil { p.sentReqs = make(map[uint64]sentReqEntry) @@ -705,7 +703,7 @@ func (p *serverPeer) updateVtParams() { } } } - p.valueTracker.UpdateCosts(p.nodeValueTracker, reqCosts) + p.nodeValueTracker.UpdateCosts(reqCosts) } // sentReqEntry remembers sent requests and their sending times @@ -732,7 +730,6 @@ func (p *serverPeer) answeredRequest(id uint64) { } e, ok := p.sentReqs[id] delete(p.sentReqs, id) - vt := p.valueTracker nvt := p.nodeValueTracker p.vtLock.Unlock() if !ok { @@ -752,7 +749,7 @@ func (p *serverPeer) answeredRequest(id uint64) { vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} } dt := time.Duration(mclock.Now() - e.at) - vt.Served(nvt, vtReqs[:reqCount], dt) + nvt.Served(vtReqs[:reqCount], dt) } // clientPeer represents each node to which the les server is connected. diff --git a/les/vflux/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go index a74301c7d3..400d978e19 100644 --- a/les/vflux/client/queueiterator_test.go +++ b/les/vflux/client/queueiterator_test.go @@ -26,17 +26,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nodestate" ) -func testNodeID(i int) enode.ID { - return enode.ID{42, byte(i % 256), byte(i / 256)} -} - -func testNodeIndex(id enode.ID) int { - if id[0] != 42 { - return -1 - } - return int(id[1]) + int(id[2])*256 -} - func testNode(i int) *enode.Node { return enode.SignNull(new(enr.Record), testNodeID(i)) } diff --git a/les/serverpool.go b/les/vflux/client/serverpool.go similarity index 76% rename from les/serverpool.go rename to les/vflux/client/serverpool.go index 977579988e..95f7246091 100644 --- a/les/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package client import ( "errors" @@ -27,8 +27,8 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/utils" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" @@ -50,31 +50,34 @@ const ( maxQueryFails = 100 // number of consecutive UDP query failures before we print a warning ) -// serverPool provides a node iterator for dial candidates. The output is a mix of newly discovered +// ServerPool provides a node iterator for dial candidates. The output is a mix of newly discovered // nodes, a weighted random selection of known (previously valuable) nodes and trusted/paid nodes. -type serverPool struct { +type ServerPool struct { clock mclock.Clock unixTime func() int64 db ethdb.KeyValueStore - ns *nodestate.NodeStateMachine - vt *vfc.ValueTracker - mixer *enode.FairMix - mixSources []enode.Iterator - dialIterator enode.Iterator - validSchemes enr.IdentityScheme - trustedURLs []string - fillSet *vfc.FillSet - queryFails uint32 + ns *nodestate.NodeStateMachine + vt *ValueTracker + mixer *enode.FairMix + mixSources []enode.Iterator + dialIterator enode.Iterator + validSchemes enr.IdentityScheme + trustedURLs []string + fillSet *FillSet + started, queryFails uint32 timeoutLock sync.RWMutex timeout time.Duration - timeWeights vfc.ResponseTimeWeights + timeWeights ResponseTimeWeights timeoutRefreshed mclock.AbsTime + + suggestedTimeoutGauge, totalValueGauge metrics.Gauge + sessionValueMeter metrics.Meter } // nodeHistory keeps track of dial costs which determine node weight together with the -// service value calculated by vfc.ValueTracker. +// service value calculated by ValueTracker. type nodeHistory struct { dialCost utils.ExpiredValue redialWaitStart, redialWaitEnd int64 // unix time (seconds) @@ -91,18 +94,18 @@ type nodeHistoryEnc struct { type queryFunc func(*enode.Node) int var ( - serverPoolSetup = &nodestate.Setup{Version: 1} - sfHasValue = serverPoolSetup.NewPersistentFlag("hasValue") - sfQueried = serverPoolSetup.NewFlag("queried") - sfCanDial = serverPoolSetup.NewFlag("canDial") - sfDialing = serverPoolSetup.NewFlag("dialed") - sfWaitDialTimeout = serverPoolSetup.NewFlag("dialTimeout") - sfConnected = serverPoolSetup.NewFlag("connected") - sfRedialWait = serverPoolSetup.NewFlag("redialWait") - sfAlwaysConnect = serverPoolSetup.NewFlag("alwaysConnect") + clientSetup = &nodestate.Setup{Version: 1} + sfHasValue = clientSetup.NewPersistentFlag("hasValue") + sfQueried = clientSetup.NewFlag("queried") + sfCanDial = clientSetup.NewFlag("canDial") + sfDialing = clientSetup.NewFlag("dialed") + sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout") + sfConnected = clientSetup.NewFlag("connected") + sfRedialWait = clientSetup.NewFlag("redialWait") + sfAlwaysConnect = clientSetup.NewFlag("alwaysConnect") sfDisableSelection = nodestate.MergeFlags(sfQueried, sfCanDial, sfDialing, sfConnected, sfRedialWait) - sfiNodeHistory = serverPoolSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}), + sfiNodeHistory = clientSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}), func(field interface{}) ([]byte, error) { if n, ok := field.(nodeHistory); ok { ne := nodeHistoryEnc{ @@ -126,25 +129,25 @@ var ( return n, err }, ) - sfiNodeWeight = serverPoolSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) - sfiConnectedStats = serverPoolSetup.NewField("connectedStats", reflect.TypeOf(vfc.ResponseTimeStats{})) + sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) + sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{})) ) // newServerPool creates a new server pool -func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *vfc.ValueTracker, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string) *serverPool { - s := &serverPool{ +func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { + s := &ServerPool{ db: db, clock: clock, unixTime: func() int64 { return time.Now().Unix() }, validSchemes: enode.ValidSchemes, trustedURLs: trustedURLs, - vt: vt, - ns: nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, serverPoolSetup), + vt: NewValueTracker(db, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), + ns: nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, clientSetup), } s.recalTimeout() s.mixer = enode.NewFairMix(mixTimeout) - knownSelector := vfc.NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) - alwaysConnect := vfc.NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) + knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) + alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) @@ -166,14 +169,30 @@ func newServerPool(db ethdb.KeyValueStore, dbKey []byte, vt *vfc.ValueTracker, m } }) - s.ns.AddLogMetrics(sfHasValue, sfDisableSelection, "selectable", nil, nil, serverSelectableGauge) - s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil) - s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge) - return s + return s, s.dialIterator +} + +// AddMetrics adds metrics to the server pool. Should be called before Start(). +func (s *ServerPool) AddMetrics( + suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge metrics.Gauge, + sessionValueMeter, serverDialedMeter metrics.Meter) { + + s.suggestedTimeoutGauge = suggestedTimeoutGauge + s.totalValueGauge = totalValueGauge + s.sessionValueMeter = sessionValueMeter + if serverSelectableGauge != nil { + s.ns.AddLogMetrics(sfHasValue, sfDisableSelection, "selectable", nil, nil, serverSelectableGauge) + } + if serverDialedMeter != nil { + s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil) + } + if serverConnectedGauge != nil { + s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge) + } } -// addSource adds a node discovery source to the server pool (should be called before start) -func (s *serverPool) addSource(source enode.Iterator) { +// AddSource adds a node discovery source to the server pool (should be called before start) +func (s *ServerPool) AddSource(source enode.Iterator) { if source != nil { s.mixSources = append(s.mixSources, source) } @@ -182,8 +201,8 @@ func (s *serverPool) addSource(source enode.Iterator) { // addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. -func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { - s.fillSet = vfc.NewFillSet(s.ns, input, sfQueried) +func (s *ServerPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { + s.fillSet = NewFillSet(s.ns, input, sfQueried) s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { if newState.Equals(sfQueried) { fails := atomic.LoadUint32(&s.queryFails) @@ -221,7 +240,7 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod }() } }) - return vfc.NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { + return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { if waiting { s.fillSet.SetTarget(preNegLimit) } else { @@ -231,7 +250,7 @@ func (s *serverPool) addPreNegFilter(input enode.Iterator, query queryFunc) enod } // start starts the server pool. Note that NodeStateMachine should be started first. -func (s *serverPool) start() { +func (s *ServerPool) Start() { s.ns.Start() for _, iter := range s.mixSources { // add sources to mixer at startup because the mixer instantly tries to read them @@ -261,10 +280,11 @@ func (s *serverPool) start() { } }) }) + atomic.StoreUint32(&s.started, 1) } // stop stops the server pool -func (s *serverPool) stop() { +func (s *ServerPool) Stop() { s.dialIterator.Close() if s.fillSet != nil { s.fillSet.Close() @@ -276,32 +296,34 @@ func (s *serverPool) stop() { }) }) s.ns.Stop() + s.vt.Stop() } // registerPeer implements serverPeerSubscriber -func (s *serverPool) registerPeer(p *serverPeer) { - s.ns.SetState(p.Node(), sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) - nvt := s.vt.Register(p.ID()) - s.ns.SetField(p.Node(), sfiConnectedStats, nvt.RtStats()) - p.setValueTracker(s.vt, nvt) - p.updateVtParams() +func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) { + if atomic.LoadUint32(&s.started) == 0 { + return nil, errors.New("server pool not started yet") + } + s.ns.SetState(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) + nvt := s.vt.Register(node.ID()) + s.ns.SetField(node, sfiConnectedStats, nvt.RtStats()) + return nvt, nil } // unregisterPeer implements serverPeerSubscriber -func (s *serverPool) unregisterPeer(p *serverPeer) { +func (s *ServerPool) UnregisterNode(node *enode.Node) { s.ns.Operation(func() { - s.setRedialWait(p.Node(), dialCost, dialWaitStep) - s.ns.SetStateSub(p.Node(), nodestate.Flags{}, sfConnected, 0) - s.ns.SetFieldSub(p.Node(), sfiConnectedStats, nil) + s.setRedialWait(node, dialCost, dialWaitStep) + s.ns.SetStateSub(node, nodestate.Flags{}, sfConnected, 0) + s.ns.SetFieldSub(node, sfiConnectedStats, nil) }) - s.vt.Unregister(p.ID()) - p.setValueTracker(nil, nil) + s.vt.Unregister(node.ID()) } // recalTimeout calculates the current recommended timeout. This value is used by // the client as a "soft timeout" value. It also affects the service value calculation // of individual nodes. -func (s *serverPool) recalTimeout() { +func (s *ServerPool) recalTimeout() { // Use cached result if possible, avoid recalculating too frequently. s.timeoutLock.RLock() refreshed := s.timeoutRefreshed @@ -330,17 +352,21 @@ func (s *serverPool) recalTimeout() { s.timeoutLock.Lock() if s.timeout != timeout { s.timeout = timeout - s.timeWeights = vfc.TimeoutWeights(s.timeout) + s.timeWeights = TimeoutWeights(s.timeout) - suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) - totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) + if s.suggestedTimeoutGauge != nil { + s.suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) + } + if s.totalValueGauge != nil { + s.totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) + } } s.timeoutRefreshed = now s.timeoutLock.Unlock() } -// getTimeout returns the recommended request timeout. -func (s *serverPool) getTimeout() time.Duration { +// GetTimeout returns the recommended request timeout. +func (s *ServerPool) GetTimeout() time.Duration { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() @@ -349,7 +375,7 @@ func (s *serverPool) getTimeout() time.Duration { // getTimeoutAndWeight returns the recommended request timeout as well as the // response time weight which is necessary to calculate service value. -func (s *serverPool) getTimeoutAndWeight() (time.Duration, vfc.ResponseTimeWeights) { +func (s *ServerPool) getTimeoutAndWeight() (time.Duration, ResponseTimeWeights) { s.recalTimeout() s.timeoutLock.RLock() defer s.timeoutLock.RUnlock() @@ -358,7 +384,7 @@ func (s *serverPool) getTimeoutAndWeight() (time.Duration, vfc.ResponseTimeWeigh // addDialCost adds the given amount of dial cost to the node history and returns the current // amount of total dial cost -func (s *serverPool) addDialCost(n *nodeHistory, amount int64) uint64 { +func (s *ServerPool) addDialCost(n *nodeHistory, amount int64) uint64 { logOffset := s.vt.StatsExpirer().LogOffset(s.clock.Now()) if amount > 0 { n.dialCost.Add(amount, logOffset) @@ -371,7 +397,7 @@ func (s *serverPool) addDialCost(n *nodeHistory, amount int64) uint64 { } // serviceValue returns the service value accumulated in this session and in total -func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) { +func (s *ServerPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) { nvt := s.vt.GetNode(node.ID()) if nvt == nil { return 0, 0 @@ -381,11 +407,13 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl expFactor := s.vt.StatsExpFactor() totalValue = currentStats.Value(timeWeights, expFactor) - if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(vfc.ResponseTimeStats); ok { + if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(ResponseTimeStats); ok { diff := currentStats diff.SubStats(&connStats) sessionValue = diff.Value(timeWeights, expFactor) - sessionValueMeter.Mark(int64(sessionValue)) + if s.sessionValueMeter != nil { + s.sessionValueMeter.Mark(int64(sessionValue)) + } } return } @@ -393,7 +421,7 @@ func (s *serverPool) serviceValue(node *enode.Node) (sessionValue, totalValue fl // updateWeight calculates the node weight and updates the nodeWeight field and the // hasValue flag. It also saves the node state if necessary. // Note: this function should run inside a NodeStateMachine operation -func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { +func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost)) if weight >= nodeWeightThreshold { s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0) @@ -415,7 +443,7 @@ func (s *serverPool) updateWeight(node *enode.Node, totalValue float64, totalDia // to the minimum. // Note: node weight is also recalculated and updated by this function. // Note 2: this function should run inside a NodeStateMachine operation -func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { +func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) sessionValue, totalValue := s.serviceValue(node) totalDialCost := s.addDialCost(&n, addDialCost) @@ -481,9 +509,14 @@ func (s *serverPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep // This function should be called during startup and shutdown only, otherwise setRedialWait // will keep the weights updated as the underlying statistics are adjusted. // Note: this function should run inside a NodeStateMachine operation -func (s *serverPool) calculateWeight(node *enode.Node) { +func (s *ServerPool) calculateWeight(node *enode.Node) { n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) _, totalValue := s.serviceValue(node) totalDialCost := s.addDialCost(&n, 0) s.updateWeight(node, totalValue, totalDialCost) } + +// API returns the vflux client API +func (s *ServerPool) API() *PrivateClientAPI { + return NewPrivateClientAPI(s.vt) +} diff --git a/les/serverpool_test.go b/les/vflux/client/serverpool_test.go similarity index 86% rename from les/serverpool_test.go rename to les/vflux/client/serverpool_test.go index 5c8ae56f6c..3af3db95bc 100644 --- a/les/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -14,10 +14,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package client import ( "math/rand" + "strconv" "sync/atomic" "testing" "time" @@ -25,8 +26,6 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" ) @@ -50,13 +49,13 @@ func testNodeIndex(id enode.ID) int { return int(id[1]) + int(id[2])*256 } -type serverPoolTest struct { +type ServerPoolTest struct { db ethdb.KeyValueStore clock *mclock.Simulated quit chan struct{} preNeg, preNegFail bool - vt *vfc.ValueTracker - sp *serverPool + vt *ValueTracker + sp *ServerPool input enode.Iterator testNodes []spTestNode trusted []string @@ -71,15 +70,15 @@ type spTestNode struct { connectCycles, waitCycles int nextConnCycle, totalConn int connected, service bool - peer *serverPeer + node *enode.Node } -func newServerPoolTest(preNeg, preNegFail bool) *serverPoolTest { +func newServerPoolTest(preNeg, preNegFail bool) *ServerPoolTest { nodes := make([]*enode.Node, spTestNodes) for i := range nodes { nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i)) } - return &serverPoolTest{ + return &ServerPoolTest{ clock: &mclock.Simulated{}, db: memorydb.New(), input: enode.CycleNodes(nodes), @@ -89,7 +88,7 @@ func newServerPoolTest(preNeg, preNegFail bool) *serverPoolTest { } } -func (s *serverPoolTest) beginWait() { +func (s *ServerPoolTest) beginWait() { // ensure that dialIterator and the maximal number of pre-neg queries are not all stuck in a waiting state for atomic.AddInt32(&s.waitCount, 1) > preNegLimit { atomic.AddInt32(&s.waitCount, -1) @@ -97,16 +96,16 @@ func (s *serverPoolTest) beginWait() { } } -func (s *serverPoolTest) endWait() { +func (s *ServerPoolTest) endWait() { atomic.AddInt32(&s.waitCount, -1) atomic.AddInt32(&s.waitEnded, 1) } -func (s *serverPoolTest) addTrusted(i int) { +func (s *ServerPoolTest) addTrusted(i int) { s.trusted = append(s.trusted, enode.SignNull(&enr.Record{}, testNodeID(i)).String()) } -func (s *serverPoolTest) start() { +func (s *ServerPoolTest) start() { var testQuery queryFunc if s.preNeg { testQuery = func(node *enode.Node) int { @@ -144,13 +143,17 @@ func (s *serverPoolTest) start() { } } - s.vt = vfc.NewValueTracker(s.db, s.clock, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)) - s.sp = newServerPool(s.db, []byte("serverpool:"), s.vt, 0, testQuery, s.clock, s.trusted) - s.sp.addSource(s.input) + requestList := make([]RequestInfo, testReqTypes) + for i := range requestList { + requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} + } + + s.sp, _ = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) + s.sp.AddSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } s.disconnect = make(map[int][]int) - s.sp.start() + s.sp.Start() s.quit = make(chan struct{}) go func() { last := int32(-1) @@ -170,31 +173,30 @@ func (s *serverPoolTest) start() { }() } -func (s *serverPoolTest) stop() { +func (s *ServerPoolTest) stop() { close(s.quit) - s.sp.stop() - s.vt.Stop() + s.sp.Stop() for i := range s.testNodes { n := &s.testNodes[i] if n.connected { n.totalConn += s.cycle } n.connected = false - n.peer = nil + n.node = nil n.nextConnCycle = 0 } s.conn, s.servedConn = 0, 0 } -func (s *serverPoolTest) run() { +func (s *ServerPoolTest) run() { for count := spTestLength; count > 0; count-- { if dcList := s.disconnect[s.cycle]; dcList != nil { for _, idx := range dcList { n := &s.testNodes[idx] - s.sp.unregisterPeer(n.peer) + s.sp.UnregisterNode(n.node) n.totalConn += s.cycle n.connected = false - n.peer = nil + n.node = nil s.conn-- if n.service { s.servedConn-- @@ -221,10 +223,10 @@ func (s *serverPoolTest) run() { n.connected = true dc := s.cycle + n.connectCycles s.disconnect[dc] = append(s.disconnect[dc], idx) - n.peer = &serverPeer{peerCommons: peerCommons{Peer: p2p.NewPeer(id, "", nil)}} - s.sp.registerPeer(n.peer) + n.node = dial + nv, _ := s.sp.RegisterNode(n.node) if n.service { - s.vt.Served(s.vt.GetNode(id), []vfc.ServedRequest{{ReqType: 0, Amount: 100}}, 0) + nv.Served([]ServedRequest{{ReqType: 0, Amount: 100}}, 0) } } } @@ -234,7 +236,7 @@ func (s *serverPoolTest) run() { } } -func (s *serverPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) { +func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) { for ; count > 0; count-- { idx := rand.Intn(spTestNodes) for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected { @@ -253,11 +255,11 @@ func (s *serverPoolTest) setNodes(count, conn, wait int, service, trusted bool) return } -func (s *serverPoolTest) resetNodes() { +func (s *ServerPoolTest) resetNodes() { for i, n := range s.testNodes { if n.connected { n.totalConn += s.cycle - s.sp.unregisterPeer(n.peer) + s.sp.UnregisterNode(n.node) } s.testNodes[i] = spTestNode{totalConn: n.totalConn} } @@ -266,7 +268,7 @@ func (s *serverPoolTest) resetNodes() { s.trusted = nil } -func (s *serverPoolTest) checkNodes(t *testing.T, nodes []int) { +func (s *ServerPoolTest) checkNodes(t *testing.T, nodes []int) { var sum int for _, idx := range nodes { n := &s.testNodes[idx] diff --git a/les/vflux/client/valuetracker.go b/les/vflux/client/valuetracker.go index 4e67b31d96..f5390d0920 100644 --- a/les/vflux/client/valuetracker.go +++ b/les/vflux/client/valuetracker.go @@ -45,6 +45,7 @@ var ( type NodeValueTracker struct { lock sync.Mutex + vt *ValueTracker rtStats, lastRtStats ResponseTimeStats lastTransfer mclock.AbsTime basket serverBasket @@ -52,15 +53,12 @@ type NodeValueTracker struct { reqValues *[]float64 } -// init initializes a NodeValueTracker. -// Note that the contents of the referenced reqValues slice will not change; a new -// reference is passed if the values are updated by ValueTracker. -func (nv *NodeValueTracker) init(now mclock.AbsTime, reqValues *[]float64) { - reqTypeCount := len(*reqValues) - nv.reqCosts = make([]uint64, reqTypeCount) - nv.lastTransfer = now - nv.reqValues = reqValues - nv.basket.init(reqTypeCount) +// UpdateCosts updates the node value tracker's request cost table +func (nv *NodeValueTracker) UpdateCosts(reqCosts []uint64) { + nv.vt.lock.Lock() + defer nv.vt.lock.Unlock() + + nv.updateCosts(reqCosts, &nv.vt.refBasket.reqValues, nv.vt.refBasket.reqValueFactor(reqCosts)) } // updateCosts updates the request cost table of the server. The request value factor @@ -97,6 +95,28 @@ func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats } +type ServedRequest struct { + ReqType, Amount uint32 +} + +// Served adds a served request to the node's statistics. An actual request may be composed +// of one or more request types (service vector indices). +func (nv *NodeValueTracker) Served(reqs []ServedRequest, respTime time.Duration) { + nv.vt.statsExpLock.RLock() + expFactor := nv.vt.statsExpFactor + nv.vt.statsExpLock.RUnlock() + + nv.lock.Lock() + defer nv.lock.Unlock() + + var value float64 + for _, r := range reqs { + nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) + value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) + } + nv.rtStats.Add(respTime, value, expFactor) +} + // RtStats returns the node's own response time distribution statistics func (nv *NodeValueTracker) RtStats() ResponseTimeStats { nv.lock.Lock() @@ -333,7 +353,12 @@ func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { return nil } nv := vt.loadOrNewNode(id) - nv.init(vt.clock.Now(), &vt.refBasket.reqValues) + reqTypeCount := len(vt.refBasket.reqValues) + nv.reqCosts = make([]uint64, reqTypeCount) + nv.lastTransfer = vt.clock.Now() + nv.reqValues = &vt.refBasket.reqValues + nv.basket.init(reqTypeCount) + vt.connected[id] = nv return nv } @@ -364,7 +389,7 @@ func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { if nv, ok := vt.connected[id]; ok { return nv } - nv := &NodeValueTracker{lastTransfer: vt.clock.Now()} + nv := &NodeValueTracker{vt: vt, lastTransfer: vt.clock.Now()} enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) if err != nil { return nv @@ -425,14 +450,6 @@ func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { } } -// UpdateCosts updates the node value tracker's request cost table -func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) { - vt.lock.Lock() - defer vt.lock.Unlock() - - nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts)) -} - // RtStats returns the global response time distribution statistics func (vt *ValueTracker) RtStats() ResponseTimeStats { vt.lock.Lock() @@ -464,28 +481,6 @@ func (vt *ValueTracker) periodicUpdate() { vt.saveToDb() } -type ServedRequest struct { - ReqType, Amount uint32 -} - -// Served adds a served request to the node's statistics. An actual request may be composed -// of one or more request types (service vector indices). -func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) { - vt.statsExpLock.RLock() - expFactor := vt.statsExpFactor - vt.statsExpLock.RUnlock() - - nv.lock.Lock() - defer nv.lock.Unlock() - - var value float64 - for _, r := range reqs { - nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) - value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) - } - nv.rtStats.Add(respTime, value, vt.statsExpFactor) -} - type RequestStatsItem struct { Name string ReqAmount, ReqValue float64 diff --git a/les/vflux/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go index ad398749e9..87a337be8d 100644 --- a/les/vflux/client/valuetracker_test.go +++ b/les/vflux/client/valuetracker_test.go @@ -64,7 +64,7 @@ func TestValueTracker(t *testing.T) { for j := range costList { costList[j] = uint64(baseCost * relPrices[j]) } - vt.UpdateCosts(nodes[i], costList) + nodes[i].UpdateCosts(costList) } for i := range nodes { nodes[i] = vt.Register(enode.ID{byte(i)}) @@ -77,7 +77,7 @@ func TestValueTracker(t *testing.T) { node := rand.Intn(testNodeCount) respTime := time.Duration((rand.Float64() + 1) * float64(time.Second) * float64(node+1) / testNodeCount) totalAmount[reqType] += uint64(reqAmount) - vt.Served(nodes[node], []ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime) + nodes[node].Served([]ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime) clock.Run(time.Second) } } else { From 092856267067dd78b527a773f5b240d5c9f5693a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 25 Feb 2021 09:10:30 +0200 Subject: [PATCH 352/948] all: define Berlin hard fork spec --- cmd/geth/config.go | 5 ++- cmd/geth/main.go | 1 + cmd/puppeth/wizard_genesis.go | 4 ++ cmd/utils/flags.go | 4 ++ core/forkid/forkid_test.go | 60 ++++++++++++++------------ core/genesis.go | 7 ++++ core/state_transition.go | 2 +- core/tx_pool.go | 2 +- core/types/transaction_signing.go | 4 +- core/vm/contracts.go | 12 +++--- core/vm/evm.go | 10 ++--- core/vm/interpreter.go | 4 +- core/vm/jump_table.go | 15 ++++--- core/vm/runtime/runtime.go | 7 ++-- eth/backend.go | 2 +- eth/ethconfig/config.go | 3 ++ eth/tracers/api.go | 4 +- les/client.go | 2 +- light/txpool.go | 2 +- params/config.go | 70 ++++++++++++++++++------------- tests/init.go | 2 +- tests/state_test_util.go | 3 +- 22 files changed, 132 insertions(+), 93 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c71d5eb653..c77c04c252 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -20,6 +20,7 @@ import ( "bufio" "errors" "fmt" + "math/big" "os" "reflect" "unicode" @@ -165,7 +166,9 @@ func checkWhisper(ctx *cli.Context) { // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) - + if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) { + cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name)) + } backend := utils.RegisterEthService(stack, &cfg.Eth) checkWhisper(ctx) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index a3af44f6c4..9afa031584 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -69,6 +69,7 @@ var ( utils.NoUSBFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, + utils.OverrideBerlinFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 78f63e1c7a..4f701fa1c3 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -235,6 +235,10 @@ func (w *wizard) manageGenesis() { fmt.Printf("Which block should Istanbul come into effect? (default = %v)\n", w.conf.Genesis.Config.IstanbulBlock) w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock) + fmt.Println() + fmt.Printf("Which block should Berlin come into effect? (default = %v)\n", w.conf.Genesis.Config.BerlinBlock) + w.conf.Genesis.Config.BerlinBlock = w.readDefaultBigInt(w.conf.Genesis.Config.BerlinBlock) + fmt.Println() fmt.Printf("Which block should YOLOv3 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV3Block) w.conf.Genesis.Config.YoloV3Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV3Block) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 57547d0c49..324a6d6a47 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -230,6 +230,10 @@ var ( Usage: "Megabytes of memory allocated to bloom-filter for pruning", Value: 2048, } + OverrideBerlinFlag = cli.Uint64Flag{ + Name: "override.berlin", + Usage: "Manually specify Berlin fork-block, overriding the bundled setting", + } // Light server and client settings LightServeFlag = cli.IntFlag{ Name: "light.serve", diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 888b553475..a20598fa9d 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -43,24 +43,26 @@ func TestCreation(t *testing.T) { params.MainnetChainConfig, params.MainnetGenesisHash, []testcase{ - {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced - {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block - {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block - {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block - {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block - {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block - {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block - {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block - {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block - {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block - {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block - {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block - {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block - {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block - {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block - {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // First Muir Glacier block - {10000000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // Future Muir Glacier block + {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block + {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block + {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // First Berlin block + {20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // Future Berlin block }, }, // Ropsten test cases @@ -80,8 +82,10 @@ func TestCreation(t *testing.T) { {6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block {6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block {7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block - {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // First Muir Glacier block - {7500000, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // Future + {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block + {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block + {9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // First Berlin block + {10000000, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // Future Berlin block }, }, // Rinkeby test cases @@ -100,8 +104,10 @@ func TestCreation(t *testing.T) { {4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block {4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block {5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block - {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // First Istanbul block - {6000000, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // Future Istanbul block + {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block + {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block + {8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // First Berlin block + {10000000, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // Future Berlin block }, }, // Goerli test cases @@ -111,8 +117,10 @@ func TestCreation(t *testing.T) { []testcase{ {0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block - {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // First Istanbul block - {2000000, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // Future Istanbul block + {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block + {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block + {4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // First Berlin block + {5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // Future Berlin block }, }, } @@ -185,11 +193,11 @@ func TestValidation(t *testing.T) { // Local is mainnet Petersburg, remote is Rinkeby Petersburg. {7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Muir Glacier, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). - {88888888, ID{Hash: checksumToBytes(0xe029e991), Next: 88888888}, ErrLocalIncompatibleOrStale}, + {88888888, ID{Hash: checksumToBytes(0x0eb440f6), Next: 88888888}, ErrLocalIncompatibleOrStale}, // Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing // fork) at block 7279999, before Petersburg. Local is incompatible. diff --git a/core/genesis.go b/core/genesis.go index 6bcc50b050..e05e27fe17 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -153,6 +153,10 @@ func (e *GenesisMismatchError) Error() string { // // The returned chain configuration is never nil. func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) { + return SetupGenesisBlockWithOverride(db, genesis, nil) +} + +func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideBerlin *big.Int) (*params.ChainConfig, common.Hash, error) { if genesis != nil && genesis.Config == nil { return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } @@ -198,6 +202,9 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) + if overrideBerlin != nil { + newcfg.BerlinBlock = overrideBerlin + } if err := newcfg.CheckConfigForkOrder(); err != nil { return newcfg, common.Hash{}, err } diff --git a/core/state_transition.go b/core/state_transition.go index 0d589a6119..d511e40bd6 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -259,7 +259,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Set up the initial access list. - if st.evm.ChainConfig().IsYoloV3(st.evm.Context.BlockNumber) { + if st.evm.ChainConfig().IsBerlin(st.evm.Context.BlockNumber) { st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(), msg.AccessList()) } diff --git a/core/tx_pool.go b/core/tx_pool.go index 28ac822131..4c1bd809fd 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1204,7 +1204,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // Update all fork indicator by next pending block number. next := new(big.Int).Add(newHead.Number, big.NewInt(1)) pool.istanbul = pool.chainconfig.IsIstanbul(next) - pool.eip2718 = pool.chainconfig.IsYoloV3(next) + pool.eip2718 = pool.chainconfig.IsBerlin(next) } // promoteExecutables moves transactions that have become processable from the diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 1645369b4f..b4594cb90b 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -40,7 +40,7 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer switch { - case config.IsYoloV3(blockNumber): + case config.IsBerlin(blockNumber): signer = NewEIP2930Signer(config.ChainID) case config.IsEIP155(blockNumber): signer = NewEIP155Signer(config.ChainID) @@ -61,7 +61,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.ChainConfig) Signer { if config.ChainID != nil { - if config.YoloV3Block != nil { + if config.BerlinBlock != nil || config.YoloV3Block != nil { return NewEIP2930Signer(config.ChainID) } if config.EIP155Block != nil { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 9ea19d38a7..4e99a51618 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -78,9 +78,9 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } -// PrecompiledContractsYoloV3 contains the default set of pre-compiled Ethereum -// contracts used in the Yolo v3 test release. -var PrecompiledContractsYoloV3 = map[common.Address]PrecompiledContract{ +// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum +// contracts used in the Berlin release. +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -107,7 +107,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ } var ( - PrecompiledAddressesYoloV3 []common.Address + PrecompiledAddressesBerlin []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address PrecompiledAddressesHomestead []common.Address @@ -123,8 +123,8 @@ func init() { for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) } - for k := range PrecompiledContractsYoloV3 { - PrecompiledAddressesYoloV3 = append(PrecompiledAddressesYoloV3, k) + for k := range PrecompiledContractsBerlin { + PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) } } diff --git a/core/vm/evm.go b/core/vm/evm.go index 3617d77b12..7346d76e5b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -46,8 +46,8 @@ type ( // configuration func (evm *EVM) ActivePrecompiles() []common.Address { switch { - case evm.chainRules.IsYoloV3: - return PrecompiledAddressesYoloV3 + case evm.chainRules.IsBerlin: + return PrecompiledAddressesBerlin case evm.chainRules.IsIstanbul: return PrecompiledAddressesIstanbul case evm.chainRules.IsByzantium: @@ -60,8 +60,8 @@ func (evm *EVM) ActivePrecompiles() []common.Address { func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { - case evm.chainRules.IsYoloV3: - precompiles = PrecompiledContractsYoloV3 + case evm.chainRules.IsBerlin: + precompiles = PrecompiledContractsBerlin case evm.chainRules.IsIstanbul: precompiles = PrecompiledContractsIstanbul case evm.chainRules.IsByzantium: @@ -446,7 +446,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back - if evm.chainRules.IsYoloV3 { + if evm.chainRules.IsBerlin { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 967db32780..0084b7d071 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { - case evm.chainRules.IsYoloV3: - jt = yoloV3InstructionSet + case evm.chainRules.IsBerlin: + jt = berlinInstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet case evm.chainRules.IsConstantinople: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 954f657a94..d831f9300f 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,24 +56,23 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() - yoloV3InstructionSet = newYoloV3InstructionSet() + berlinInstructionSet = newBerlinInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation -// newYoloV3InstructionSet creates an instructionset containing -// - "EIP-2315: Simple Subroutines" -// - "EIP-2929: Gas cost increases for state access opcodes" -func newYoloV3InstructionSet() JumpTable { +// newBerlinInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg and berlin instructions. +func newBerlinInstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 return instructionSet } -// newIstanbulInstructionSet returns the frontier, homestead -// byzantium, contantinople and petersburg instructions. +// newIstanbulInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul and petersburg instructions. func newIstanbulInstructionSet() JumpTable { instructionSet := newConstantinopleInstructionSet() @@ -84,7 +83,7 @@ func newIstanbulInstructionSet() JumpTable { return instructionSet } -// newConstantinopleInstructionSet returns the frontier, homestead +// newConstantinopleInstructionSet returns the frontier, homestead, // byzantium and contantinople instructions. func newConstantinopleInstructionSet() JumpTable { instructionSet := newByzantiumInstructionSet() diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index c97bdc420c..9cb69e1c76 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -65,6 +65,7 @@ func setDefaults(cfg *Config) { PetersburgBlock: new(big.Int), IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), + BerlinBlock: new(big.Int), YoloV3Block: nil, } } @@ -113,7 +114,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { cfg.State.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } cfg.State.CreateAccount(address) @@ -145,7 +146,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { cfg.State.PrepareAccessList(cfg.Origin, nil, vmenv.ActivePrecompiles(), nil) } @@ -171,7 +172,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er sender := cfg.State.GetOrNewStateObject(cfg.Origin) statedb := cfg.State - if cfg.ChainConfig.IsYoloV3(vmenv.Context.BlockNumber) { + if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { statedb.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) } diff --git a/eth/backend.go b/eth/backend.go index 044422763b..76ce5137f4 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -126,7 +126,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 841dc5e9e1..5d0eece067 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -198,6 +198,9 @@ type Config struct { // CheckpointOracle is the configuration for checkpoint oracle. CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` + + // Berlin block override (TODO: remove after the fork) + OverrideBerlin *big.Int `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain configuration. diff --git a/eth/tracers/api.go b/eth/tracers/api.go index bd995d08a9..61fe055689 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -581,8 +581,8 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block chainConfigCopy := new(params.ChainConfig) *chainConfigCopy = *chainConfig chainConfig = chainConfigCopy - if yolov3 := config.LogConfig.Overrides.YoloV3Block; yolov3 != nil { - chainConfig.YoloV3Block = yolov3 + if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil { + chainConfig.BerlinBlock = berlin canon = false } } diff --git a/les/client.go b/les/client.go index e20519fd91..4d07f844f8 100644 --- a/les/client.go +++ b/les/client.go @@ -84,7 +84,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr } diff --git a/light/txpool.go b/light/txpool.go index bf5f9ff583..1296389e3b 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -314,7 +314,7 @@ func (pool *TxPool) setNewHead(head *types.Header) { // Update fork indicator by next pending block number next := new(big.Int).Add(head.Number, big.NewInt(1)) pool.istanbul = pool.config.IsIstanbul(next) - pool.eip2718 = pool.config.IsYoloV3(next) + pool.eip2718 = pool.config.IsBerlin(next) } // Stop stops the light transaction pool diff --git a/params/config.go b/params/config.go index 929bdbeb94..8cbfffc03c 100644 --- a/params/config.go +++ b/params/config.go @@ -56,18 +56,19 @@ var ( // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(1150000), - DAOForkBlock: big.NewInt(1920000), + HomesteadBlock: big.NewInt(1_150_000), + DAOForkBlock: big.NewInt(1_920_000), DAOForkSupport: true, - EIP150Block: big.NewInt(2463000), + EIP150Block: big.NewInt(2_463_000), EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(2675000), - EIP158Block: big.NewInt(2675000), - ByzantiumBlock: big.NewInt(4370000), - ConstantinopleBlock: big.NewInt(7280000), - PetersburgBlock: big.NewInt(7280000), - IstanbulBlock: big.NewInt(9069000), - MuirGlacierBlock: big.NewInt(9200000), + EIP155Block: big.NewInt(2_675_000), + EIP158Block: big.NewInt(2_675_000), + ByzantiumBlock: big.NewInt(4_370_000), + ConstantinopleBlock: big.NewInt(7_280_000), + PetersburgBlock: big.NewInt(7_280_000), + IstanbulBlock: big.NewInt(9_069_000), + MuirGlacierBlock: big.NewInt(9_200_000), + BerlinBlock: big.NewInt(12_244_000), Ethash: new(EthashConfig), } @@ -102,11 +103,12 @@ var ( EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"), EIP155Block: big.NewInt(10), EIP158Block: big.NewInt(10), - ByzantiumBlock: big.NewInt(1700000), - ConstantinopleBlock: big.NewInt(4230000), - PetersburgBlock: big.NewInt(4939394), - IstanbulBlock: big.NewInt(6485846), - MuirGlacierBlock: big.NewInt(7117117), + ByzantiumBlock: big.NewInt(1_700_000), + ConstantinopleBlock: big.NewInt(4_230_000), + PetersburgBlock: big.NewInt(4_939_394), + IstanbulBlock: big.NewInt(6_485_846), + MuirGlacierBlock: big.NewInt(7_117_117), + BerlinBlock: big.NewInt(9_812_189), Ethash: new(EthashConfig), } @@ -141,11 +143,12 @@ var ( EIP150Hash: common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"), EIP155Block: big.NewInt(3), EIP158Block: big.NewInt(3), - ByzantiumBlock: big.NewInt(1035301), - ConstantinopleBlock: big.NewInt(3660663), - PetersburgBlock: big.NewInt(4321234), - IstanbulBlock: big.NewInt(5435345), + ByzantiumBlock: big.NewInt(1_035_301), + ConstantinopleBlock: big.NewInt(3_660_663), + PetersburgBlock: big.NewInt(4_321_234), + IstanbulBlock: big.NewInt(5_435_345), MuirGlacierBlock: nil, + BerlinBlock: big.NewInt(8_290_928), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -184,8 +187,9 @@ var ( ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(1561651), + IstanbulBlock: big.NewInt(1_561_651), MuirGlacierBlock: nil, + BerlinBlock: big.NewInt(4_460_644), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -227,6 +231,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, + BerlinBlock: nil, // Don't enable Berlin directly, we're YOLOing it YoloV3Block: big.NewInt(0), Clique: &CliqueConfig{ Period: 15, @@ -239,16 +244,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, big.NewInt(0), nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -319,6 +324,7 @@ type ChainConfig struct { PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) + BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) @@ -358,7 +364,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v3: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, YOLO v3: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -371,6 +377,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, + c.BerlinBlock, c.YoloV3Block, engine, ) @@ -428,9 +435,9 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { return isForked(c.IstanbulBlock, num) } -// IsYoloV3 returns whether num is either equal to the YoloV3 fork block or greater. -func (c *ChainConfig) IsYoloV3(num *big.Int) bool { - return isForked(c.YoloV3Block, num) +// IsBerlin returns whether num is either equal to the Berlin fork block or greater. +func (c *ChainConfig) IsBerlin(num *big.Int) bool { + return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num) } // IsEWASM returns whether num represents a block number after the EWASM fork @@ -476,7 +483,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "petersburgBlock", block: c.PetersburgBlock}, {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, - {name: "yoloV3Block", block: c.YoloV3Block}, + {name: "berlinBlock", block: c.BerlinBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -540,6 +547,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } + if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { + return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) + } if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) } @@ -613,7 +623,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsYoloV3 bool + IsBerlin bool } // Rules ensures c's ChainID is not nil. @@ -632,6 +642,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), - IsYoloV3: c.IsYoloV3(num), + IsBerlin: c.IsBerlin(num), } } diff --git a/tests/init.go b/tests/init.go index a2ef040786..67f706eb50 100644 --- a/tests/init.go +++ b/tests/init.go @@ -165,7 +165,7 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV3Block: big.NewInt(0), + BerlinBlock: big.NewInt(0), }, } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 03dc01459c..99681baaf1 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -24,14 +24,13 @@ import ( "strconv" "strings" - "github.com/ethereum/go-ethereum/core/state/snapshot" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" From 27b31371d46bc932853cce36078b28a53088b2b2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 26 Feb 2021 13:40:35 +0100 Subject: [PATCH 353/948] rpc: add separate size limit for websocket (#22385) This makes the WebSocket message size limit independent of the limit used for HTTP requests. The new limit for WebSocket messages is 15MB. --- rpc/http_test.go | 25 +++++++++++++++++++++++++ rpc/testservice_test.go | 10 ++++++++++ rpc/websocket.go | 3 ++- rpc/websocket_test.go | 27 +++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/rpc/http_test.go b/rpc/http_test.go index fc939ae48f..b75af67c52 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -98,3 +98,28 @@ func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body func TestHTTPResponseWithEmptyGet(t *testing.T) { confirmHTTPRequestYieldsStatusCode(t, http.MethodGet, "", "", http.StatusOK) } + +// This checks that maxRequestContentLength is not applied to the response of a request. +func TestHTTPRespBodyUnlimited(t *testing.T) { + const respLength = maxRequestContentLength * 3 + + s := NewServer() + defer s.Stop() + s.RegisterName("test", largeRespService{respLength}) + ts := httptest.NewServer(s) + defer ts.Close() + + c, err := DialHTTP(ts.URL) + if err != nil { + t.Fatal(err) + } + defer c.Close() + + var r string + if err := c.Call(&r, "test_largeResp"); err != nil { + t.Fatal(err) + } + if len(r) != respLength { + t.Fatalf("response has wrong length %d, want %d", len(r), respLength) + } +} diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index 6f948a1bac..62afc1df44 100644 --- a/rpc/testservice_test.go +++ b/rpc/testservice_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/binary" "errors" + "strings" "sync" "time" ) @@ -194,3 +195,12 @@ func (s *notificationTestService) HangSubscription(ctx context.Context, val int) }() return subscription, nil } + +// largeRespService generates arbitrary-size JSON responses. +type largeRespService struct { + length int +} + +func (x largeRespService) LargeResp() string { + return strings.Repeat("x", x.length) +} diff --git a/rpc/websocket.go b/rpc/websocket.go index cd60eeb613..ab55ae69c1 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -37,6 +37,7 @@ const ( wsWriteBuffer = 1024 wsPingInterval = 60 * time.Second wsPingWriteTimeout = 5 * time.Second + wsMessageSizeLimit = 15 * 1024 * 1024 ) var wsBufferPool = new(sync.Pool) @@ -239,7 +240,7 @@ type websocketCodec struct { } func newWebsocketCodec(conn *websocket.Conn) ServerCodec { - conn.SetReadLimit(maxRequestContentLength) + conn.SetReadLimit(wsMessageSizeLimit) wc := &websocketCodec{ jsonCodec: NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON).(*jsonCodec), conn: conn, diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index f54fc3cd54..37ed19476f 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -157,6 +157,33 @@ func TestClientWebsocketPing(t *testing.T) { } } +// This checks that the websocket transport can deal with large messages. +func TestClientWebsocketLargeMessage(t *testing.T) { + var ( + srv = NewServer() + httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) + wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") + ) + defer srv.Stop() + defer httpsrv.Close() + + respLength := wsMessageSizeLimit - 50 + srv.RegisterName("test", largeRespService{respLength}) + + c, err := DialWebsocket(context.Background(), wsURL, "") + if err != nil { + t.Fatal(err) + } + + var r string + if err := c.Call(&r, "test_largeResp"); err != nil { + t.Fatal("call failed:", err) + } + if len(r) != respLength { + t.Fatalf("response has wrong length %d, want %d", len(r), respLength) + } +} + // wsPingTestServer runs a WebSocket server which accepts a single subscription request. // When a value arrives on sendPing, the server sends a ping frame, waits for a matching // pong and finally delivers a single subscription result. From 3822b09904edcd92bc203b5739115208daa38765 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 26 Feb 2021 15:28:34 +0100 Subject: [PATCH 354/948] accounts/keystore: use github.com/google/uuid (#22217) This replaces the github.com/pborman/uuid dependency with github.com/google/uuid because the former is only a wrapper for the latter (since v1.0.0). Co-authored-by: Felix Lange --- accounts/keystore/key.go | 12 +++++++++--- accounts/keystore/passphrase.go | 21 ++++++++++++++++----- accounts/keystore/presale.go | 9 ++++++--- cmd/ethkey/generate.go | 9 ++++++--- go.mod | 3 +-- go.sum | 20 ++------------------ 6 files changed, 40 insertions(+), 34 deletions(-) diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 84d8df0c5a..2b815ce0f9 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" ) const ( @@ -110,7 +110,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } u := new(uuid.UUID) - *u = uuid.Parse(keyJSON.Id) + *u, err = uuid.Parse(keyJSON.Id) + if err != nil { + return err + } k.Id = *u addr, err := hex.DecodeString(keyJSON.Address) if err != nil { @@ -128,7 +131,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { - id := uuid.NewRandom() + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf("Could not create random uuid: %v", err)) + } key := &Key{ Id: id, Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index dd4d7764e4..3b3e631888 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -42,7 +42,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" ) @@ -228,9 +228,12 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { return nil, err } key := crypto.ToECDSAUnsafe(keyBytes) - + id, err := uuid.FromBytes(keyId) + if err != nil { + return nil, err + } return &Key{ - Id: keyId, + Id: id, Address: crypto.PubkeyToAddress(key.PublicKey), PrivateKey: key, }, nil @@ -276,7 +279,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt if keyProtected.Version != version { return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) } - keyId = uuid.Parse(keyProtected.Id) + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] plainText, err := DecryptDataV3(keyProtected.Crypto, auth) if err != nil { return nil, nil, err @@ -285,7 +292,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt } func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { - keyId = uuid.Parse(keyProtected.Id) + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] mac, err := hex.DecodeString(keyProtected.Crypto.MAC) if err != nil { return nil, nil, err diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go index 03055245f5..0664dc2cdd 100644 --- a/accounts/keystore/presale.go +++ b/accounts/keystore/presale.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" ) @@ -37,7 +37,10 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou if err != nil { return accounts.Account{}, nil, err } - key.Id = uuid.NewRandom() + key.Id, err = uuid.NewRandom() + if err != nil { + return accounts.Account{}, nil, err + } a := accounts.Account{ Address: key.Address, URL: accounts.URL{ @@ -86,7 +89,7 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error ecKey := crypto.ToECDSAUnsafe(ethPriv) key = &Key{ - Id: nil, + Id: uuid.UUID{}, Address: crypto.PubkeyToAddress(ecKey.PublicKey), PrivateKey: ecKey, } diff --git a/cmd/ethkey/generate.go b/cmd/ethkey/generate.go index c2aa1c6fb4..629d23da5b 100644 --- a/cmd/ethkey/generate.go +++ b/cmd/ethkey/generate.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "gopkg.in/urfave/cli.v1" ) @@ -86,9 +86,12 @@ If you want to encrypt an existing private key, it can be specified by setting } // Create the keyfile object with a random UUID. - id := uuid.NewRandom() + UUID, err := uuid.NewRandom() + if err != nil { + utils.Fatalf("Failed to generate random uuid: %v", err) + } key := &keystore.Key{ - Id: id, + Id: UUID, Address: crypto.PubkeyToAddress(privateKey.PublicKey), PrivateKey: privateKey, } diff --git a/go.mod b/go.mod index a1099c52cc..96a217ed06 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa + github.com/google/uuid v1.1.5 github.com/gorilla/websocket v1.4.2 github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d @@ -37,7 +38,6 @@ require ( github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c - github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 @@ -48,7 +48,6 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.3 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 diff --git a/go.sum b/go.sum index 4b788824c7..af76759c51 100644 --- a/go.sum +++ b/go.sum @@ -48,7 +48,6 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIO github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= -github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -72,19 +71,12 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -109,7 +101,6 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -137,7 +128,6 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= -github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -145,7 +135,6 @@ github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -158,7 +147,6 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -190,12 +178,10 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg= github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -204,7 +190,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -215,6 +200,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -255,7 +242,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= @@ -264,7 +250,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -372,7 +357,6 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= From 498458b4102c0d32d7453035a115e6b9df5e485d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 26 Feb 2021 16:33:37 +0100 Subject: [PATCH 355/948] core/state: fix eta calculation on pruning (#22386) --- core/state/pruner/pruner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 1fbfa55b6a..530a348540 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -155,7 +155,7 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c if done := binary.BigEndian.Uint64(key[:8]); done > 0 { var ( left = math.MaxUint64 - binary.BigEndian.Uint64(key[:8]) - speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + speed = done/uint64(time.Since(pstart)/time.Millisecond+1) + 1 // +1s to avoid division by zero ) eta = time.Duration(left/speed) * time.Millisecond } From d96870428f116494d5190a8e595189e283dd144b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 1 Mar 2021 10:24:20 +0100 Subject: [PATCH 356/948] les: UDP pre-negotiation of available server capacity (#22183) This PR implements the first one of the "lespay" UDP queries which is already useful in itself: the capacity query. The server pool is making use of this query by doing a cheap UDP query to determine whether it is worth starting the more expensive TCP connection process. --- common/prque/lazyqueue.go | 5 +- common/prque/lazyqueue_test.go | 2 +- les/client.go | 85 +++++++++-- les/clientpool.go | 55 +++++++ les/clientpool_test.go | 6 +- les/enr_entry.go | 3 +- les/server.go | 29 +++- les/vflux/client/serverpool.go | 105 +++++++++++-- les/vflux/client/serverpool_test.go | 8 +- les/vflux/requests.go | 180 +++++++++++++++++++++++ les/vflux/server/balance.go | 58 +++++--- les/vflux/server/balance_test.go | 4 +- les/vflux/server/prioritypool.go | 202 +++++++++++++++++++++++--- les/vflux/server/prioritypool_test.go | 126 +++++++++++++++- les/vflux/server/service.go | 122 ++++++++++++++++ p2p/discover/v5_udp.go | 11 +- p2p/discover/v5_udp_test.go | 2 +- p2p/nodestate/nodestate.go | 1 + 18 files changed, 915 insertions(+), 89 deletions(-) create mode 100644 les/vflux/requests.go create mode 100644 les/vflux/server/service.go diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go index 52403df464..c74faab7e6 100644 --- a/common/prque/lazyqueue.go +++ b/common/prque/lazyqueue.go @@ -48,7 +48,7 @@ type LazyQueue struct { } type ( - PriorityCallback func(data interface{}, now mclock.AbsTime) int64 // actual priority callback + PriorityCallback func(data interface{}) int64 // actual priority callback MaxPriorityCallback func(data interface{}, until mclock.AbsTime) int64 // estimated maximum priority callback ) @@ -139,11 +139,10 @@ func (q *LazyQueue) peekIndex() int { // Pop multiple times. Popped items are passed to the callback. MultiPop returns // when the callback returns false or there are no more items to pop. func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) bool) { - now := q.clock.Now() nextIndex := q.peekIndex() for nextIndex != -1 { data := heap.Pop(q.queue[nextIndex]).(*item).value - heap.Push(q.popQueue, &item{data, q.priority(data, now)}) + heap.Push(q.popQueue, &item{data, q.priority(data)}) nextIndex = q.peekIndex() for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) { i := heap.Pop(q.popQueue).(*item) diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go index be9491e24e..9a831d628b 100644 --- a/common/prque/lazyqueue_test.go +++ b/common/prque/lazyqueue_test.go @@ -40,7 +40,7 @@ type lazyItem struct { index int } -func testPriority(a interface{}, now mclock.AbsTime) int64 { +func testPriority(a interface{}) int64 { return a.(*lazyItem).p } diff --git a/les/client.go b/les/client.go index 4d07f844f8..ecabfdf503 100644 --- a/les/client.go +++ b/les/client.go @@ -36,30 +36,33 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/les/vflux" vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) type LightEthereum struct { lesCommons - peers *serverPeerSet - reqDist *requestDistributor - retriever *retrieveManager - odr *LesOdr - relay *lesTxRelay - handler *clientHandler - txPool *light.TxPool - blockchain *light.LightChain - serverPool *vfc.ServerPool - dialCandidates enode.Iterator - pruner *pruner + peers *serverPeerSet + reqDist *requestDistributor + retriever *retrieveManager + odr *LesOdr + relay *lesTxRelay + handler *clientHandler + txPool *light.TxPool + blockchain *light.LightChain + serverPool *vfc.ServerPool + serverPoolIterator enode.Iterator + pruner *pruner bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -112,7 +115,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { p2pConfig: &stack.Config().P2P, } - leth.serverPool, leth.dialCandidates = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, nil, &mclock.System{}, config.UltraLightServers, requestList) + leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, leth.prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) @@ -189,6 +192,62 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { return leth, nil } +// VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses +func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies { + reqsEnc, _ := rlp.EncodeToBytes(&reqs) + repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc) + var replies vflux.Replies + if len(repliesEnc) == 0 || rlp.DecodeBytes(repliesEnc, &replies) != nil { + return nil + } + return replies +} + +// vfxVersion returns the version number of the "les" service subdomain of the vflux UDP +// service, as advertised in the ENR record +func (s *LightEthereum) vfxVersion(n *enode.Node) uint { + if n.Seq() == 0 { + var err error + if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { + s.serverPool.Persist(n) + } else { + return 0 + } + } + + var les []rlp.RawValue + if err := n.Load(enr.WithEntry("les", &les)); err != nil || len(les) < 1 { + return 0 + } + var version uint + rlp.DecodeBytes(les[0], &version) // Ignore additional fields (for forward compatibility). + return version +} + +// prenegQuery sends a capacity query to the given server node to determine whether +// a connection slot is immediately available +func (s *LightEthereum) prenegQuery(n *enode.Node) int { + if s.vfxVersion(n) < 1 { + // UDP query not supported, always try TCP connection + return 1 + } + + var requests vflux.Requests + requests.Add("les", vflux.CapacityQueryName, vflux.CapacityQueryReq{ + Bias: 180, + AddTokens: []vflux.IntOrInf{{}}, + }) + replies := s.VfluxRequest(n, requests) + var cqr vflux.CapacityQueryReply + if replies.Get(0, &cqr) != nil || len(cqr) != 1 { // Note: Get returns an error if replies is nil + return -1 + } + if cqr[0] > 0 { + return 1 + } + return 0 +} + type LightDummyAPI struct{} // Etherbase is the address that mining rewards will be send to @@ -269,7 +328,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { return p.Info() } return nil - }, s.dialCandidates) + }, s.serverPoolIterator) } // Start implements node.Lifecycle, starting all internal goroutines needed by the diff --git a/les/clientpool.go b/les/clientpool.go index 4e1499bf5d..1aa63a281e 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -24,11 +24,13 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/les/vflux" vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" + "github.com/ethereum/go-ethereum/rlp" ) const ( @@ -382,3 +384,56 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { } } } + +// serveCapQuery serves a vflux capacity query. It receives multiple token amount values +// and a bias time value. For each given token amount it calculates the maximum achievable +// capacity in case the amount is added to the balance. +func (f *clientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte { + var req vflux.CapacityQueryReq + if rlp.DecodeBytes(data, &req) != nil { + return nil + } + if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { + return nil + } + node := f.ns.GetNode(id) + if node == nil { + node = enode.SignNull(&enr.Record{}, id) + } + c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) + if c == nil { + c = &clientInfo{node: node} + f.ns.SetField(node, clientInfoField, c) + f.ns.SetField(node, connAddressField, freeID) + defer func() { + f.ns.SetField(node, connAddressField, nil) + f.ns.SetField(node, clientInfoField, nil) + }() + if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { + log.Error("BalanceField is missing", "node", node.ID()) + return nil + } + } + // use vfs.CapacityCurve to answer request for multiple newly bought token amounts + curve := f.pp.GetCapacityCurve().Exclude(id) + result := make(vflux.CapacityQueryReply, len(req.AddTokens)) + bias := time.Second * time.Duration(req.Bias) + if f.connectedBias > bias { + bias = f.connectedBias + } + pb, _ := c.balance.GetBalance() + for i, addTokens := range req.AddTokens { + add := addTokens.Int64() + result[i] = curve.MaxCapacity(func(capacity uint64) int64 { + return c.balance.EstimatePriority(capacity, add, 0, bias, false) / int64(capacity) + }) + if add <= 0 && uint64(-add) >= pb && result[i] > f.minCap { + result[i] = f.minCap + } + if result[i] < f.minCap { + result[i] = 0 + } + } + reply, _ := rlp.EncodeToBytes(&result) + return reply +} diff --git a/les/clientpool_test.go b/les/clientpool_test.go index 5cff010409..345b373b0f 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -508,8 +508,10 @@ func TestNegativeBalanceCalculation(t *testing.T) { for i := 0; i < 10; i++ { pool.disconnect(newPoolTestPeer(i, nil)) _, nb := getBalance(pool, newPoolTestPeer(i, nil)) - if checkDiff(nb, uint64(time.Minute)/1000) { - t.Fatalf("Negative balance mismatch, want %v, got %v", uint64(time.Minute)/1000, nb) + exp := uint64(time.Minute) / 1000 + exp -= exp / 120 // correct for negative balance expiration + if checkDiff(nb, exp) { + t.Fatalf("Negative balance mismatch, want %v, got %v", exp, nb) } } } diff --git a/les/enr_entry.go b/les/enr_entry.go index 1e56c1f175..8be4a7a00e 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -27,7 +27,8 @@ import ( // lesEntry is the "les" ENR entry. This is set for LES servers only. type lesEntry struct { // Ignore additional fields (for forward compatibility). - _ []rlp.RawValue `rlp:"tail"` + VfxVersion uint + Rest []rlp.RawValue `rlp:"tail"` } func (lesEntry) ENRKey() string { return "les" } diff --git a/les/server.go b/les/server.go index 359784cf7d..63feaf892c 100644 --- a/les/server.go +++ b/les/server.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" + "github.com/ethereum/go-ethereum/les/vflux" vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" @@ -68,6 +69,7 @@ type LesServer struct { archiveMode bool // Flag whether the ethereum node runs in archive mode. handler *serverHandler broadcaster *broadcaster + vfluxServer *vfs.Server privateKey *ecdsa.PrivateKey // Flow control and capacity management @@ -112,12 +114,14 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les ns: ns, archiveMode: e.ArchiveMode(), broadcaster: newBroadcaster(ns), + vfluxServer: vfs.NewServer(time.Millisecond * 10), fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), threadsBusy: config.LightServ/100 + 1, threadsIdle: threads, p2pSrv: node.Server(), } + srv.vfluxServer.Register(srv) issync := e.Synced if config.LightNoSyncServe { issync = func() bool { return true } @@ -201,7 +205,9 @@ func (s *LesServer) Protocols() []p2p.Protocol { }, nil) // Add "les" ENR entries. for i := range ps { - ps[i].Attributes = []enr.Entry{&lesEntry{}} + ps[i].Attributes = []enr.Entry{&lesEntry{ + VfxVersion: 1, + }} } return ps } @@ -211,10 +217,11 @@ func (s *LesServer) Start() error { s.privateKey = s.p2pSrv.PrivateKey s.broadcaster.setSignerKey(s.privateKey) s.handler.start() - s.wg.Add(1) go s.capacityManagement() - + if s.p2pSrv.DiscV5 != nil { + s.p2pSrv.DiscV5.RegisterTalkHandler("vfx", s.vfluxServer.ServeEncoded) + } return nil } @@ -228,6 +235,7 @@ func (s *LesServer) Stop() error { s.costTracker.stop() s.handler.stop() s.servingQueue.stop() + s.vfluxServer.Stop() // Note, bloom trie indexer is closed by parent bloombits indexer. s.chtIndexer.Close() @@ -311,3 +319,18 @@ func (s *LesServer) dropClient(id enode.ID) { p.Peer.Disconnect(p2p.DiscRequested) } } + +// ServiceInfo implements vfs.Service +func (s *LesServer) ServiceInfo() (string, string) { + return "les", "Ethereum light client service" +} + +// Handle implements vfs.Service +func (s *LesServer) Handle(id enode.ID, address string, name string, data []byte) []byte { + switch name { + case vflux.CapacityQueryName: + return s.clientPool.serveCapQuery(id, address, data) + default: + return nil + } +} diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go index 95f7246091..47ec4fee74 100644 --- a/les/vflux/client/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -94,7 +94,7 @@ type nodeHistoryEnc struct { type queryFunc func(*enode.Node) int var ( - clientSetup = &nodestate.Setup{Version: 1} + clientSetup = &nodestate.Setup{Version: 2} sfHasValue = clientSetup.NewPersistentFlag("hasValue") sfQueried = clientSetup.NewFlag("queried") sfCanDial = clientSetup.NewFlag("canDial") @@ -131,9 +131,25 @@ var ( ) sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{})) + sfiLocalAddress = clientSetup.NewPersistentField("localAddress", reflect.TypeOf(&enr.Record{}), + func(field interface{}) ([]byte, error) { + if enr, ok := field.(*enr.Record); ok { + enc, err := rlp.EncodeToBytes(enr) + return enc, err + } + return nil, errors.New("invalid field type") + }, + func(enc []byte) (interface{}, error) { + var enr enr.Record + if err := rlp.DecodeBytes(enc, &enr); err != nil { + return nil, err + } + return &enr, nil + }, + ) ) -// newServerPool creates a new server pool +// NewServerPool creates a new server pool func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { s := &ServerPool{ db: db, @@ -151,15 +167,10 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) - iter := enode.Iterator(s.mixer) + s.dialIterator = s.mixer if query != nil { - iter = s.addPreNegFilter(iter, query) + s.dialIterator = s.addPreNegFilter(s.dialIterator, query) } - s.dialIterator = enode.Filter(iter, func(node *enode.Node) bool { - s.ns.SetState(node, sfDialing, sfCanDial, 0) - s.ns.SetState(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10) - return true - }) s.ns.SubscribeState(nodestate.MergeFlags(sfWaitDialTimeout, sfConnected), func(n *enode.Node, oldState, newState nodestate.Flags) { if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() { @@ -169,7 +180,41 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio } }) - return s, s.dialIterator + return s, &serverPoolIterator{ + dialIterator: s.dialIterator, + nextFn: func(node *enode.Node) { + s.ns.Operation(func() { + s.ns.SetStateSub(node, sfDialing, sfCanDial, 0) + s.ns.SetStateSub(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10) + }) + }, + nodeFn: s.DialNode, + } +} + +type serverPoolIterator struct { + dialIterator enode.Iterator + nextFn func(*enode.Node) + nodeFn func(*enode.Node) *enode.Node +} + +// Next implements enode.Iterator +func (s *serverPoolIterator) Next() bool { + if s.dialIterator.Next() { + s.nextFn(s.dialIterator.Node()) + return true + } + return false +} + +// Node implements enode.Iterator +func (s *serverPoolIterator) Node() *enode.Node { + return s.nodeFn(s.dialIterator.Node()) +} + +// Close implements enode.Iterator +func (s *serverPoolIterator) Close() { + s.dialIterator.Close() } // AddMetrics adds metrics to the server pool. Should be called before Start(). @@ -285,7 +330,6 @@ func (s *ServerPool) Start() { // stop stops the server pool func (s *ServerPool) Stop() { - s.dialIterator.Close() if s.fillSet != nil { s.fillSet.Close() } @@ -299,18 +343,23 @@ func (s *ServerPool) Stop() { s.vt.Stop() } -// registerPeer implements serverPeerSubscriber +// RegisterNode implements serverPeerSubscriber func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) { if atomic.LoadUint32(&s.started) == 0 { return nil, errors.New("server pool not started yet") } - s.ns.SetState(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) nvt := s.vt.Register(node.ID()) - s.ns.SetField(node, sfiConnectedStats, nvt.RtStats()) + s.ns.Operation(func() { + s.ns.SetStateSub(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) + s.ns.SetFieldSub(node, sfiConnectedStats, nvt.RtStats()) + if node.IP().IsLoopback() { + s.ns.SetFieldSub(node, sfiLocalAddress, node.Record()) + } + }) return nvt, nil } -// unregisterPeer implements serverPeerSubscriber +// UnregisterNode implements serverPeerSubscriber func (s *ServerPool) UnregisterNode(node *enode.Node) { s.ns.Operation(func() { s.setRedialWait(node, dialCost, dialWaitStep) @@ -430,6 +479,7 @@ func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDia s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) s.ns.SetFieldSub(node, sfiNodeWeight, nil) s.ns.SetFieldSub(node, sfiNodeHistory, nil) + s.ns.SetFieldSub(node, sfiLocalAddress, nil) } s.ns.Persist(node) // saved if node history or hasValue changed } @@ -520,3 +570,28 @@ func (s *ServerPool) calculateWeight(node *enode.Node) { func (s *ServerPool) API() *PrivateClientAPI { return NewPrivateClientAPI(s.vt) } + +type dummyIdentity enode.ID + +func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil } +func (id dummyIdentity) NodeAddr(r *enr.Record) []byte { return id[:] } + +// DialNode replaces the given enode with a locally generated one containing the ENR +// stored in the sfiLocalAddress field if present. This workaround ensures that nodes +// on the local network can be dialed at the local address if a connection has been +// successfully established previously. +// Note that NodeStateMachine always remembers the enode with the latest version of +// the remote signed ENR. ENR filtering should be performed on that version while +// dialNode should be used for dialing the node over TCP or UDP. +func (s *ServerPool) DialNode(n *enode.Node) *enode.Node { + if enr, ok := s.ns.GetField(n, sfiLocalAddress).(*enr.Record); ok { + n, _ := enode.New(dummyIdentity(n.ID()), enr) + return n + } + return n +} + +// Persist immediately stores the state of a node in the node database +func (s *ServerPool) Persist(n *enode.Node) { + s.ns.Persist(n) +} diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go index 3af3db95bc..ee299618c6 100644 --- a/les/vflux/client/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -56,6 +56,7 @@ type ServerPoolTest struct { preNeg, preNegFail bool vt *ValueTracker sp *ServerPool + spi enode.Iterator input enode.Iterator testNodes []spTestNode trusted []string @@ -148,7 +149,7 @@ func (s *ServerPoolTest) start() { requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} } - s.sp, _ = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) + s.sp, s.spi = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) s.sp.AddSource(s.input) s.sp.validSchemes = enode.ValidSchemesForTesting s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } @@ -176,6 +177,7 @@ func (s *ServerPoolTest) start() { func (s *ServerPoolTest) stop() { close(s.quit) s.sp.Stop() + s.spi.Close() for i := range s.testNodes { n := &s.testNodes[i] if n.connected { @@ -208,9 +210,9 @@ func (s *ServerPoolTest) run() { if s.conn < spTestTarget { s.dialCount++ s.beginWait() - s.sp.dialIterator.Next() + s.spi.Next() s.endWait() - dial := s.sp.dialIterator.Node() + dial := s.spi.Node() id := dial.ID() idx := testNodeIndex(id) n := &s.testNodes[idx] diff --git a/les/vflux/requests.go b/les/vflux/requests.go new file mode 100644 index 0000000000..11255607e8 --- /dev/null +++ b/les/vflux/requests.go @@ -0,0 +1,180 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vflux + +import ( + "errors" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/rlp" +) + +var ErrNoReply = errors.New("no reply for given request") + +const ( + MaxRequestLength = 16 // max number of individual requests in a batch + CapacityQueryName = "cq" + CapacityQueryMaxLen = 16 +) + +type ( + // Request describes a single vflux request inside a batch. Service and request + // type are identified by strings, parameters are RLP encoded. + Request struct { + Service, Name string + Params []byte + } + // Requests are a batch of vflux requests + Requests []Request + + // Replies are the replies to a batch of requests + Replies [][]byte + + // CapacityQueryReq is the encoding format of the capacity query + CapacityQueryReq struct { + Bias uint64 // seconds + AddTokens []IntOrInf + } + // CapacityQueryReq is the encoding format of the response to the capacity query + CapacityQueryReply []uint64 +) + +// Add encodes and adds a new request to the batch +func (r *Requests) Add(service, name string, val interface{}) (int, error) { + enc, err := rlp.EncodeToBytes(val) + if err != nil { + return -1, err + } + *r = append(*r, Request{ + Service: service, + Name: name, + Params: enc, + }) + return len(*r) - 1, nil +} + +// Get decodes the reply to the i-th request in the batch +func (r Replies) Get(i int, val interface{}) error { + if i < 0 || i >= len(r) { + return ErrNoReply + } + return rlp.DecodeBytes(r[i], val) +} + +const ( + IntNonNegative = iota + IntNegative + IntPlusInf + IntMinusInf +) + +// IntOrInf is the encoding format for arbitrary length signed integers that can also +// hold the values of +Inf or -Inf +type IntOrInf struct { + Type uint8 + Value big.Int +} + +// BigInt returns the value as a big.Int or panics if the value is infinity +func (i *IntOrInf) BigInt() *big.Int { + switch i.Type { + case IntNonNegative: + return new(big.Int).Set(&i.Value) + case IntNegative: + return new(big.Int).Neg(&i.Value) + case IntPlusInf: + panic(nil) // caller should check Inf() before trying to convert to big.Int + case IntMinusInf: + panic(nil) + } + return &big.Int{} // invalid type decodes to 0 value +} + +// Inf returns 1 if the value is +Inf, -1 if it is -Inf, 0 otherwise +func (i *IntOrInf) Inf() int { + switch i.Type { + case IntPlusInf: + return 1 + case IntMinusInf: + return -1 + } + return 0 // invalid type decodes to 0 value +} + +// Int64 limits the value between MinInt64 and MaxInt64 (even if it is +-Inf) and returns an int64 type +func (i *IntOrInf) Int64() int64 { + switch i.Type { + case IntNonNegative: + if i.Value.IsInt64() { + return i.Value.Int64() + } else { + return math.MaxInt64 + } + case IntNegative: + if i.Value.IsInt64() { + return -i.Value.Int64() + } else { + return math.MinInt64 + } + case IntPlusInf: + return math.MaxInt64 + case IntMinusInf: + return math.MinInt64 + } + return 0 // invalid type decodes to 0 value +} + +// SetBigInt sets the value to the given big.Int +func (i *IntOrInf) SetBigInt(v *big.Int) { + if v.Sign() >= 0 { + i.Type = IntNonNegative + i.Value.Set(v) + } else { + i.Type = IntNegative + i.Value.Neg(v) + } +} + +// SetInt64 sets the value to the given int64. Note that MaxInt64 translates to +Inf +// while MinInt64 translates to -Inf. +func (i *IntOrInf) SetInt64(v int64) { + if v >= 0 { + if v == math.MaxInt64 { + i.Type = IntPlusInf + } else { + i.Type = IntNonNegative + i.Value.SetInt64(v) + } + } else { + if v == math.MinInt64 { + i.Type = IntMinusInf + } else { + i.Type = IntNegative + i.Value.SetInt64(-v) + } + } +} + +// SetInf sets the value to +Inf or -Inf +func (i *IntOrInf) SetInf(sign int) { + if sign == 1 { + i.Type = IntPlusInf + } else { + i.Type = IntMinusInf + } +} diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go index f5073d0db1..db12a5c573 100644 --- a/les/vflux/server/balance.go +++ b/les/vflux/server/balance.go @@ -243,11 +243,11 @@ func (n *NodeBalance) RequestServed(cost uint64) uint64 { } // Priority returns the actual priority based on the current balance -func (n *NodeBalance) Priority(now mclock.AbsTime, capacity uint64) int64 { +func (n *NodeBalance) Priority(capacity uint64) int64 { n.lock.Lock() defer n.lock.Unlock() - n.updateBalance(now) + n.updateBalance(n.bt.clock.Now()) return n.balanceToPriority(n.balance, capacity) } @@ -256,16 +256,35 @@ func (n *NodeBalance) Priority(now mclock.AbsTime, capacity uint64) int64 { // in the current session. // If update is true then a priority callback is added that turns UpdateFlag on and off // in case the priority goes below the estimated minimum. -func (n *NodeBalance) EstMinPriority(at mclock.AbsTime, capacity uint64, update bool) int64 { +func (n *NodeBalance) EstimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 { n.lock.Lock() defer n.lock.Unlock() - var avgReqCost float64 - dt := time.Duration(n.lastUpdate - n.initTime) - if dt > time.Second { - avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) + now := n.bt.clock.Now() + n.updateBalance(now) + b := n.balance + if addBalance != 0 { + offset := n.bt.posExp.LogOffset(now) + old := n.balance.pos.Value(offset) + if addBalance > 0 && (addBalance > maxBalance || old > maxBalance-uint64(addBalance)) { + b.pos = utils.ExpiredValue{} + b.pos.Add(maxBalance, offset) + } else { + b.pos.Add(addBalance, offset) + } } - pri := n.balanceToPriority(n.reducedBalance(at, capacity, avgReqCost), capacity) + if future > 0 { + var avgReqCost float64 + dt := time.Duration(n.lastUpdate - n.initTime) + if dt > time.Second { + avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) + } + b = n.reducedBalance(b, now, future, capacity, avgReqCost) + } + if bias > 0 { + b = n.reducedBalance(b, now+mclock.AbsTime(future), bias, capacity, 0) + } + pri := n.balanceToPriority(b, capacity) if update { n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) } @@ -366,7 +385,7 @@ func (n *NodeBalance) deactivate() { // updateBalance updates balance based on the time factor func (n *NodeBalance) updateBalance(now mclock.AbsTime) { if n.active && now > n.lastUpdate { - n.balance = n.reducedBalance(now, n.capacity, 0) + n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0) n.lastUpdate = now } } @@ -546,23 +565,25 @@ func (n *NodeBalance) balanceToPriority(b balance, capacity uint64) int64 { } // reducedBalance estimates the reduced balance at a given time in the fututre based -// on the current balance, the time factor and an estimated average request cost per time ratio -func (n *NodeBalance) reducedBalance(at mclock.AbsTime, capacity uint64, avgReqCost float64) balance { - dt := float64(at - n.lastUpdate) - b := n.balance +// on the given balance, the time factor and an estimated average request cost per time ratio +func (n *NodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance { + // since the costs are applied continuously during the dt time period we calculate + // the expiration offset at the middle of the period + at := start + mclock.AbsTime(dt/2) + dtf := float64(dt) if !b.pos.IsZero() { factor := n.posFactor.timePrice(capacity) + n.posFactor.RequestFactor*avgReqCost - diff := -int64(dt * factor) + diff := -int64(dtf * factor) dd := b.pos.Add(diff, n.bt.posExp.LogOffset(at)) if dd == diff { - dt = 0 + dtf = 0 } else { - dt += float64(dd) / factor + dtf += float64(dd) / factor } } if dt > 0 { factor := n.negFactor.timePrice(capacity) + n.negFactor.RequestFactor*avgReqCost - b.neg.Add(int64(dt*factor), n.bt.negExp.LogOffset(at)) + b.neg.Add(int64(dtf*factor), n.bt.negExp.LogOffset(at)) } return b } @@ -588,8 +609,9 @@ func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { } dt = float64(posBalance-newBalance) / timePrice return time.Duration(dt), true + } else { + dt = float64(posBalance) / timePrice } - dt = float64(posBalance) / timePrice } else { if priority > 0 { return 0, false diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index 6c817aa26c..e22074db2d 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -231,7 +231,7 @@ func TestBalanceToPriority(t *testing.T) { } for _, i := range inputs { node.SetBalance(i.pos, i.neg) - priority := node.Priority(b.clock.Now(), 1000) + priority := node.Priority(1000) if priority != i.priority { t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority) } @@ -272,7 +272,7 @@ func TestEstimatedPriority(t *testing.T) { for _, i := range inputs { b.clock.Run(i.runTime) node.RequestServed(i.reqCost) - priority := node.EstMinPriority(b.clock.Now()+mclock.AbsTime(i.futureTime), 1000000000, false) + priority := node.EstimatePriority(1000000000, 0, i.futureTime, 0, false) if priority != i.priority { t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) } diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go index e3327aba75..e940ac7c65 100644 --- a/les/vflux/server/prioritypool.go +++ b/les/vflux/server/prioritypool.go @@ -101,17 +101,21 @@ type PriorityPool struct { minCap uint64 activeBias time.Duration capacityStepDiv uint64 + + cachedCurve *CapacityCurve + ccUpdatedAt mclock.AbsTime + ccUpdateForced bool } // nodePriority interface provides current and estimated future priorities on demand type nodePriority interface { // Priority should return the current priority of the node (higher is better) - Priority(now mclock.AbsTime, cap uint64) int64 + Priority(cap uint64) int64 // EstMinPriority should return a lower estimate for the minimum of the node priority // value starting from the current moment until the given time. If the priority goes // under the returned estimate before the specified moment then it is the caller's // responsibility to signal with updateFlag. - EstMinPriority(until mclock.AbsTime, cap uint64, update bool) int64 + EstimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 } // ppNodeInfo is the internal node descriptor of PriorityPool @@ -131,12 +135,12 @@ func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, cl ns: ns, PriorityPoolSetup: setup, clock: clock, - activeQueue: prque.NewLazyQueue(activeSetIndex, activePriority, activeMaxPriority, clock, lazyQueueRefresh), inactiveQueue: prque.New(inactiveSetIndex), minCap: minCap, activeBias: activeBias, capacityStepDiv: capacityStepDiv, } + pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh) ns.SubscribeField(pp.priorityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { if newValue != nil { @@ -197,6 +201,9 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias if targetCap < pp.minCap { targetCap = pp.minCap } + if bias < pp.activeBias { + bias = pp.activeBias + } c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo) if c == nil { log.Error("RequestCapacity called for unknown node", "id", node.ID()) @@ -204,9 +211,9 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias } var priority int64 if targetCap > c.capacity { - priority = c.nodePriority.EstMinPriority(pp.clock.Now()+mclock.AbsTime(bias), targetCap, false) + priority = c.nodePriority.EstimatePriority(targetCap, 0, 0, bias, false) } else { - priority = c.nodePriority.Priority(pp.clock.Now(), targetCap) + priority = c.nodePriority.Priority(targetCap) } pp.markForChange(c) pp.setCapacity(c, targetCap) @@ -214,7 +221,7 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias pp.activeQueue.Remove(c.activeIndex) pp.inactiveQueue.Remove(c.inactiveIndex) pp.activeQueue.Push(c) - minPriority = pp.enforceLimits() + _, minPriority = pp.enforceLimits() // if capacity update is possible now then minPriority == math.MinInt64 // if it is not possible at all then minPriority == math.MaxInt64 allowed = priority > minPriority @@ -281,29 +288,34 @@ func invertPriority(p int64) int64 { } // activePriority callback returns actual priority of ppNodeInfo item in activeQueue -func activePriority(a interface{}, now mclock.AbsTime) int64 { +func activePriority(a interface{}) int64 { c := a.(*ppNodeInfo) if c.forced { return math.MinInt64 } if c.bias == 0 { - return invertPriority(c.nodePriority.Priority(now, c.capacity)) + return invertPriority(c.nodePriority.Priority(c.capacity)) + } else { + return invertPriority(c.nodePriority.EstimatePriority(c.capacity, 0, 0, c.bias, true)) } - return invertPriority(c.nodePriority.EstMinPriority(now+mclock.AbsTime(c.bias), c.capacity, true)) } // activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue -func activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { +func (pp *PriorityPool) activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { c := a.(*ppNodeInfo) if c.forced { return math.MinInt64 } - return invertPriority(c.nodePriority.EstMinPriority(until+mclock.AbsTime(c.bias), c.capacity, false)) + future := time.Duration(until - pp.clock.Now()) + if future < 0 { + future = 0 + } + return invertPriority(c.nodePriority.EstimatePriority(c.capacity, 0, future, c.bias, false)) } // inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue func (pp *PriorityPool) inactivePriority(p *ppNodeInfo) int64 { - return p.nodePriority.Priority(pp.clock.Now(), pp.minCap) + return p.nodePriority.Priority(pp.minCap) } // connectedNode is called when a new node has been added to the pool (InactiveFlag set) @@ -379,16 +391,19 @@ func (pp *PriorityPool) setCapacity(n *ppNodeInfo, cap uint64) { // enforceLimits enforces active node count and total capacity limits. It returns the // lowest active node priority. Note that this function is performed on the temporary // internal state. -func (pp *PriorityPool) enforceLimits() int64 { +func (pp *PriorityPool) enforceLimits() (*ppNodeInfo, int64) { if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount { - return math.MinInt64 + return nil, math.MinInt64 } - var maxActivePriority int64 + var ( + c *ppNodeInfo + maxActivePriority int64 + ) pp.activeQueue.MultiPop(func(data interface{}, priority int64) bool { - c := data.(*ppNodeInfo) + c = data.(*ppNodeInfo) pp.markForChange(c) maxActivePriority = priority - if c.capacity == pp.minCap { + if c.capacity == pp.minCap || pp.activeCount > pp.maxCount { pp.setCapacity(c, 0) } else { sub := c.capacity / pp.capacityStepDiv @@ -400,7 +415,7 @@ func (pp *PriorityPool) enforceLimits() int64 { } return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount }) - return invertPriority(maxActivePriority) + return c, invertPriority(maxActivePriority) } // finalizeChanges either commits or reverts temporary changes. The necessary capacity @@ -430,6 +445,9 @@ func (pp *PriorityPool) finalizeChanges(commit bool) (updates []capUpdate) { c.origCap = 0 } pp.changed = nil + if commit { + pp.ccUpdateForced = true + } return } @@ -472,6 +490,7 @@ func (pp *PriorityPool) tryActivate() []capUpdate { break } } + pp.ccUpdateForced = true return pp.finalizeChanges(commit) } @@ -500,3 +519,150 @@ func (pp *PriorityPool) updatePriority(node *enode.Node) { } updates = pp.tryActivate() } + +// CapacityCurve is a snapshot of the priority pool contents in a format that can efficiently +// estimate how much capacity could be granted to a given node at a given priority level. +type CapacityCurve struct { + points []curvePoint // curve points sorted in descending order of priority + index map[enode.ID][]int // curve point indexes belonging to each node + exclude []int // curve point indexes of excluded node + excludeFirst bool // true if activeCount == maxCount +} + +type curvePoint struct { + freeCap uint64 // available capacity and node count at the current priority level + nextPri int64 // next priority level where more capacity will be available +} + +// GetCapacityCurve returns a new or recently cached CapacityCurve based on the contents of the pool +func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { + pp.lock.Lock() + defer pp.lock.Unlock() + + now := pp.clock.Now() + dt := time.Duration(now - pp.ccUpdatedAt) + if !pp.ccUpdateForced && pp.cachedCurve != nil && dt < time.Second*10 { + return pp.cachedCurve + } + + pp.ccUpdateForced = false + pp.ccUpdatedAt = now + curve := &CapacityCurve{ + index: make(map[enode.ID][]int), + } + pp.cachedCurve = curve + + var excludeID enode.ID + excludeFirst := pp.maxCount == pp.activeCount + // reduce node capacities or remove nodes until nothing is left in the queue; + // record the available capacity and the necessary priority after each step + for pp.activeCap > 0 { + cp := curvePoint{} + if pp.activeCap > pp.maxCap { + log.Error("Active capacity is greater than allowed maximum", "active", pp.activeCap, "maximum", pp.maxCap) + } else { + cp.freeCap = pp.maxCap - pp.activeCap + } + // temporarily increase activeCap to enforce reducing or removing a node capacity + tempCap := cp.freeCap + 1 + pp.activeCap += tempCap + var next *ppNodeInfo + // enforceLimits removes the lowest priority node if it has minimal capacity, + // otherwise reduces its capacity + next, cp.nextPri = pp.enforceLimits() + pp.activeCap -= tempCap + if next == nil { + log.Error("GetCapacityCurve: cannot remove next element from the priority queue") + break + } + id := next.node.ID() + if excludeFirst { + // if the node count limit is already reached then mark the node with the + // lowest priority for exclusion + curve.excludeFirst = true + excludeID = id + excludeFirst = false + } + // multiple curve points and therefore multiple indexes may belong to a node + // if it was removed in multiple steps (if its capacity was more than the minimum) + curve.index[id] = append(curve.index[id], len(curve.points)) + curve.points = append(curve.points, cp) + } + // restore original state of the queue + pp.finalizeChanges(false) + curve.points = append(curve.points, curvePoint{ + freeCap: pp.maxCap, + nextPri: math.MaxInt64, + }) + if curve.excludeFirst { + curve.exclude = curve.index[excludeID] + } + return curve +} + +// Exclude returns a CapacityCurve with the given node excluded from the original curve +func (cc *CapacityCurve) Exclude(id enode.ID) *CapacityCurve { + if exclude, ok := cc.index[id]; ok { + // return a new version of the curve (only one excluded node can be selected) + // Note: if the first node was excluded by default (excludeFirst == true) then + // we can forget about that and exclude the node with the given id instead. + return &CapacityCurve{ + points: cc.points, + index: cc.index, + exclude: exclude, + } + } + return cc +} + +func (cc *CapacityCurve) getPoint(i int) curvePoint { + cp := cc.points[i] + if i == 0 && cc.excludeFirst { + cp.freeCap = 0 + return cp + } + for ii := len(cc.exclude) - 1; ii >= 0; ii-- { + ei := cc.exclude[ii] + if ei < i { + break + } + e1, e2 := cc.points[ei], cc.points[ei+1] + cp.freeCap += e2.freeCap - e1.freeCap + } + return cp +} + +// MaxCapacity calculates the maximum capacity available for a node with a given +// (monotonically decreasing) priority vs. capacity function. Note that if the requesting +// node is already in the pool then it should be excluded from the curve in order to get +// the correct result. +func (cc *CapacityCurve) MaxCapacity(priority func(cap uint64) int64) uint64 { + min, max := 0, len(cc.points)-1 // the curve always has at least one point + for min < max { + mid := (min + max) / 2 + cp := cc.getPoint(mid) + if cp.freeCap == 0 || priority(cp.freeCap) > cp.nextPri { + min = mid + 1 + } else { + max = mid + } + } + cp2 := cc.getPoint(min) + if cp2.freeCap == 0 || min == 0 { + return cp2.freeCap + } + cp1 := cc.getPoint(min - 1) + if priority(cp2.freeCap) > cp1.nextPri { + return cp2.freeCap + } + minc, maxc := cp1.freeCap, cp2.freeCap-1 + for minc < maxc { + midc := (minc + maxc + 1) / 2 + if midc == 0 || priority(midc) > cp1.nextPri { + minc = midc + } else { + maxc = midc - 1 + } + } + return maxc +} diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go index cbb3f5b372..d83ddc1767 100644 --- a/les/vflux/server/prioritypool_test.go +++ b/les/vflux/server/prioritypool_test.go @@ -20,6 +20,7 @@ import ( "math/rand" "reflect" "testing" + "time" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/p2p/enode" @@ -42,6 +43,7 @@ func init() { const ( testCapacityStepDiv = 100 testCapacityToleranceDiv = 10 + testMinCap = 100 ) type ppTestClient struct { @@ -49,11 +51,11 @@ type ppTestClient struct { balance, cap uint64 } -func (c *ppTestClient) Priority(now mclock.AbsTime, cap uint64) int64 { +func (c *ppTestClient) Priority(cap uint64) int64 { return int64(c.balance / cap) } -func (c *ppTestClient) EstMinPriority(until mclock.AbsTime, cap uint64, update bool) int64 { +func (c *ppTestClient) EstimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 { return int64(c.balance / cap) } @@ -67,7 +69,7 @@ func TestPriorityPool(t *testing.T) { c.cap = newValue.(uint64) } }) - pp := NewPriorityPool(ns, ppTestSetup, clock, 100, 0, testCapacityStepDiv) + pp := NewPriorityPool(ns, ppTestSetup, clock, testMinCap, 0, testCapacityStepDiv) ns.Start() pp.SetLimits(100, 1000000) clients := make([]*ppTestClient, 100) @@ -94,7 +96,7 @@ func TestPriorityPool(t *testing.T) { for i := range clients { c := &ppTestClient{ node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), - balance: 1000000000, + balance: 100000000000, cap: 1000, } sumBalance += c.balance @@ -109,7 +111,7 @@ func TestPriorityPool(t *testing.T) { for count := 0; count < 100; count++ { c := clients[rand.Intn(len(clients))] oldBalance := c.balance - c.balance = uint64(rand.Int63n(1000000000) + 1000000000) + c.balance = uint64(rand.Int63n(100000000000) + 100000000000) sumBalance += c.balance - oldBalance pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) @@ -120,10 +122,124 @@ func TestPriorityPool(t *testing.T) { raise(c) } } + // check whether capacities are proportional to balances for _, c := range clients { check(c) } + if count%10 == 0 { + // test available capacity calculation with capacity curve + c = clients[rand.Intn(len(clients))] + curve := pp.GetCapacityCurve().Exclude(c.node.ID()) + + add := uint64(rand.Int63n(10000000000000)) + c.balance += add + sumBalance += add + expCap := curve.MaxCapacity(func(cap uint64) int64 { + return int64(c.balance / cap) + }) + //fmt.Println(expCap, c.balance, sumBalance) + /*for i, cp := range curve.points { + fmt.Println("cp", i, cp, "ex", curve.getPoint(i)) + }*/ + var ok bool + expFail := expCap + 1 + if expFail < testMinCap { + expFail = testMinCap + } + ns.Operation(func() { + _, ok = pp.RequestCapacity(c.node, expFail, 0, true) + }) + if ok { + t.Errorf("Request for more than expected available capacity succeeded") + } + if expCap >= testMinCap { + ns.Operation(func() { + _, ok = pp.RequestCapacity(c.node, expCap, 0, true) + }) + if !ok { + t.Errorf("Request for expected available capacity failed") + } + } + c.balance -= add + sumBalance -= add + pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) + pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) + for _, c := range clients { + raise(c) + } + } } ns.Stop() } + +func TestCapacityCurve(t *testing.T) { + clock := &mclock.Simulated{} + ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + pp := NewPriorityPool(ns, ppTestSetup, clock, 400000, 0, 2) + ns.Start() + pp.SetLimits(10, 10000000) + clients := make([]*ppTestClient, 10) + + for i := range clients { + c := &ppTestClient{ + node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), + balance: 100000000000 * uint64(i+1), + cap: 1000000, + } + clients[i] = c + ns.SetState(c.node, ppTestClientFlag, nodestate.Flags{}, 0) + ns.SetField(c.node, ppTestSetup.priorityField, c) + ns.SetState(c.node, ppTestSetup.InactiveFlag, nodestate.Flags{}, 0) + ns.Operation(func() { + pp.RequestCapacity(c.node, c.cap, 0, true) + }) + } + + curve := pp.GetCapacityCurve() + check := func(balance, expCap uint64) { + cap := curve.MaxCapacity(func(cap uint64) int64 { + return int64(balance / cap) + }) + var fail bool + if cap == 0 || expCap == 0 { + fail = cap != expCap + } else { + pri := balance / cap + expPri := balance / expCap + fail = pri != expPri && pri != expPri+1 + } + if fail { + t.Errorf("Incorrect capacity for %d balance (got %d, expected %d)", balance, cap, expCap) + } + } + + check(0, 0) + check(10000000000, 100000) + check(50000000000, 500000) + check(100000000000, 1000000) + check(200000000000, 1000000) + check(300000000000, 1500000) + check(450000000000, 1500000) + check(600000000000, 2000000) + check(800000000000, 2000000) + check(1000000000000, 2500000) + + pp.SetLimits(11, 10000000) + curve = pp.GetCapacityCurve() + + check(0, 0) + check(10000000000, 100000) + check(50000000000, 500000) + check(150000000000, 750000) + check(200000000000, 1000000) + check(220000000000, 1100000) + check(275000000000, 1100000) + check(375000000000, 1500000) + check(450000000000, 1500000) + check(600000000000, 2000000) + check(800000000000, 2000000) + check(1000000000000, 2500000) + + ns.Stop() +} diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go new file mode 100644 index 0000000000..ab759ae441 --- /dev/null +++ b/les/vflux/server/service.go @@ -0,0 +1,122 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "net" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/les/vflux" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +type ( + // Server serves vflux requests + Server struct { + limiter *utils.Limiter + lock sync.Mutex + services map[string]*serviceEntry + delayPerRequest time.Duration + } + + // Service is a service registered at the Server and identified by a string id + Service interface { + ServiceInfo() (id, desc string) // only called during registration + Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently + } + + serviceEntry struct { + id, desc string + backend Service + } +) + +// NewServer creates a new Server +func NewServer(delayPerRequest time.Duration) *Server { + return &Server{ + limiter: utils.NewLimiter(1000), + delayPerRequest: delayPerRequest, + services: make(map[string]*serviceEntry), + } +} + +// Register registers a Service +func (s *Server) Register(b Service) { + srv := &serviceEntry{backend: b} + srv.id, srv.desc = b.ServiceInfo() + if strings.Contains(srv.id, ":") { + // srv.id + ":" will be used as a service database prefix + log.Error("Service ID contains ':'", "id", srv.id) + return + } + s.lock.Lock() + s.services[srv.id] = srv + s.lock.Unlock() +} + +// Serve serves a vflux request batch +// Note: requests are served by the Handle functions of the registered services. Serve +// may be called concurrently but the Handle functions are called sequentially and +// therefore thread safety is guaranteed. +func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vflux.Replies { + reqLen := uint(len(requests)) + if reqLen == 0 || reqLen > vflux.MaxRequestLength { + return nil + } + // Note: the value parameter will be supplied by the token sale module (total amount paid) + ch := <-s.limiter.Add(id, address, 0, reqLen) + if ch == nil { + return nil + } + // Note: the limiter ensures that the following section is not running concurrently, + // the lock only protects against contention caused by new service registration + s.lock.Lock() + results := make(vflux.Replies, len(requests)) + for i, req := range requests { + if service := s.services[req.Service]; service != nil { + results[i] = service.backend.Handle(id, address, req.Name, req.Params) + } + } + s.lock.Unlock() + time.Sleep(s.delayPerRequest * time.Duration(reqLen)) + close(ch) + return results +} + +// ServeEncoded serves an encoded vflux request batch and returns the encoded replies +func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte { + var requests vflux.Requests + if err := rlp.DecodeBytes(req, &requests); err != nil { + return nil + } + results := s.Serve(id, addr.String(), requests) + if results == nil { + return nil + } + res, _ := rlp.EncodeToBytes(&results) + return res +} + +// Stop shuts down the server +func (s *Server) Stop() { + s.limiter.Stop() +} diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index 9dd2b31733..eb01d95e93 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -74,7 +74,7 @@ type UDPv5 struct { // talkreq handler registry trlock sync.Mutex - trhandlers map[string]func([]byte) []byte + trhandlers map[string]TalkRequestHandler // channels into dispatch packetInCh chan ReadPacket @@ -96,6 +96,9 @@ type UDPv5 struct { wg sync.WaitGroup } +// TalkRequestHandler callback processes a talk request and optionally returns a reply +type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte + // callV5 represents a remote procedure call against another node. type callV5 struct { node *enode.Node @@ -145,7 +148,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { log: cfg.Log, validSchemes: cfg.ValidSchemes, clock: cfg.Clock, - trhandlers: make(map[string]func([]byte) []byte), + trhandlers: make(map[string]TalkRequestHandler), // channels into dispatch packetInCh: make(chan ReadPacket, 1), readNextCh: make(chan struct{}, 1), @@ -233,7 +236,7 @@ func (t *UDPv5) LocalNode() *enode.LocalNode { // RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called // whenever a request for the given protocol is received and should return the response // data or nil. -func (t *UDPv5) RegisterTalkHandler(protocol string, handler func([]byte) []byte) { +func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) { t.trlock.Lock() defer t.trlock.Unlock() t.trhandlers[protocol] = handler @@ -841,7 +844,7 @@ func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAd var response []byte if handler != nil { - response = handler(p.Message) + response = handler(fromID, fromAddr, p.Message) } resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response} t.sendResponse(fromID, fromAddr, resp) diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index d91a2097db..292785bd51 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -435,7 +435,7 @@ func TestUDPv5_talkHandling(t *testing.T) { defer test.close() var recvMessage []byte - test.udp.RegisterTalkHandler("test", func(message []byte) []byte { + test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte { recvMessage = message return []byte("test response") }) diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index def93bac43..d3166f1d87 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -599,6 +599,7 @@ func (ns *NodeStateMachine) updateEnode(n *enode.Node) (enode.ID, *nodeInfo) { node := ns.nodes[id] if node != nil && n.Seq() > node.node.Seq() { node.node = n + node.dirty = true } return id, node } From 19d7a37abb9f3b9bf1a94baf6bd8c7d5042e54f8 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 1 Mar 2021 17:26:10 +0800 Subject: [PATCH 357/948] core/rawdb: fix the transaction indexer (#22395) --- core/rawdb/chain_iterator.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 393b72c26c..862a549540 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -243,13 +243,13 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan } } } - // If there exists uncommitted data, flush them. - if batch.ValueSize() > 0 { - WriteTxIndexTail(batch, lastNum) // Also write the tail there - if err := batch.Write(); err != nil { - log.Crit("Failed writing batch to db", "error", err) - return - } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to index, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, lastNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return } select { case <-interrupt: @@ -334,13 +334,13 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch } } } - // Commit the last batch if there exists uncommitted data - if batch.ValueSize() > 0 { - WriteTxIndexTail(batch, nextNum) - if err := batch.Write(); err != nil { - log.Crit("Failed writing batch to db", "error", err) - return - } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to unindex, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return } select { case <-interrupt: From 7834e4a278038e57b741ee826b3a46ff9d809fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Mar 2021 23:40:57 +0200 Subject: [PATCH 358/948] core, eth: unship EIP 2315 --- core/vm/contract.go | 13 ------------- core/vm/eips.go | 29 ----------------------------- core/vm/gen_structlog.go | 13 ------------- core/vm/instructions.go | 32 -------------------------------- core/vm/instructions_test.go | 33 ++++++++++++++++----------------- core/vm/interpreter.go | 16 ++++++---------- core/vm/jump_table.go | 1 - core/vm/logger.go | 35 +++++++---------------------------- core/vm/logger_json.go | 5 ++--- core/vm/logger_test.go | 3 +-- core/vm/opcodes.go | 34 ++++++++++++---------------------- core/vm/stack.go | 31 ------------------------------- eth/tracers/tracer.go | 4 ++-- eth/tracers/tracer_test.go | 4 ++-- 14 files changed, 48 insertions(+), 205 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index 915193d137..61dbd5007a 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -96,19 +96,6 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool { return c.isCode(udest) } -func (c *Contract) validJumpSubdest(udest uint64) bool { - // PC cannot go beyond len(code) and certainly can't be bigger than 63 bits. - // Don't bother checking for BEGINSUB in that case. - if int64(udest) < 0 || udest >= uint64(len(c.Code)) { - return false - } - // Only BEGINSUBs allowed for destinations - if OpCode(c.Code[udest]) != BEGINSUB { - return false - } - return c.isCode(udest) -} - // isCode returns true if the provided PC location is an actual opcode, as // opposed to a data-segment following a PUSHN operation. func (c *Contract) isCode(udest uint64) bool { diff --git a/core/vm/eips.go b/core/vm/eips.go index 962c0f14b1..0c8bf1792e 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -29,7 +29,6 @@ var activators = map[int]func(*JumpTable){ 2200: enable2200, 1884: enable1884, 1344: enable1344, - 2315: enable2315, } // EnableEIP enables the given EIP on the config. @@ -108,34 +107,6 @@ func enable2200(jt *JumpTable) { jt[SSTORE].dynamicGas = gasSStoreEIP2200 } -// enable2315 applies EIP-2315 (Simple Subroutines) -// - Adds opcodes that jump to and return from subroutines -func enable2315(jt *JumpTable) { - // New opcode - jt[BEGINSUB] = &operation{ - execute: opBeginSub, - constantGas: GasQuickStep, - minStack: minStack(0, 0), - maxStack: maxStack(0, 0), - } - // New opcode - jt[JUMPSUB] = &operation{ - execute: opJumpSub, - constantGas: GasSlowStep, - minStack: minStack(1, 0), - maxStack: maxStack(1, 0), - jumps: true, - } - // New opcode - jt[RETURNSUB] = &operation{ - execute: opReturnSub, - constantGas: GasFastStep, - minStack: minStack(0, 0), - maxStack: maxStack(0, 0), - jumps: true, - } -} - // enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" // https://eips.ethereum.org/EIPS/eip-2929 func enable2929(jt *JumpTable) { diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index 44da014de9..ac04afe8b7 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -45,12 +45,6 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.Stack[k] = (*math.HexOrDecimal256)(v) } } - if s.ReturnStack != nil { - enc.ReturnStack = make([]math.HexOrDecimal64, len(s.ReturnStack)) - for k, v := range s.ReturnStack { - enc.ReturnStack[k] = math.HexOrDecimal64(v) - } - } enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth @@ -71,7 +65,6 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Memory *hexutil.Bytes `json:"memory"` MemorySize *int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` - ReturnStack []math.HexOrDecimal64 `json:"returnStack"` ReturnData *hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` @@ -106,12 +99,6 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { s.Stack[k] = (*big.Int)(v) } } - if dec.ReturnStack != nil { - s.ReturnStack = make([]uint32, len(dec.ReturnStack)) - for k, v := range dec.ReturnStack { - s.ReturnStack[k] = uint32(v) - } - } if dec.ReturnData != nil { s.ReturnData = *dec.ReturnData } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 1137505292..f4ca0603ed 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -547,38 +547,6 @@ func opJumpdest(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( return nil, nil } -func opBeginSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - return nil, ErrInvalidSubroutineEntry -} - -func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - if len(callContext.rstack.data) >= 1023 { - return nil, ErrReturnStackExceeded - } - pos := callContext.stack.pop() - if !pos.IsUint64() { - return nil, ErrInvalidJump - } - posU64 := pos.Uint64() - if !callContext.contract.validJumpSubdest(posU64) { - return nil, ErrInvalidJump - } - callContext.rstack.push(uint32(*pc)) - *pc = posU64 + 1 - return nil, nil -} - -func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - if len(callContext.rstack.data) == 0 { - return nil, ErrInvalidRetsub - } - // Other than the check that the return stack is not empty, there is no - // need to validate the pc from 'returns', since we only ever push valid - //values onto it via jumpsub. - *pc = uint64(callContext.rstack.pop()) + 1 - return nil, nil -} - func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { callContext.stack.push(new(uint256.Int).SetUint64(*pc)) return nil, nil diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 985d5a5156..55d876581c 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -94,7 +94,6 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() - rstack = newReturnStack() pc = uint64(0) evmInterpreter = env.interpreter.(*EVMInterpreter) ) @@ -105,7 +104,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, &callCtx{nil, stack, rstack, nil}) + opFn(&pc, evmInterpreter, &callCtx{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -220,7 +219,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evmInterpreter, &callCtx{nil, stack, nil, nil}) + opAddmod(&pc, evmInterpreter, &callCtx{nil, stack, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -231,10 +230,10 @@ func TestAddMod(t *testing.T) { // getResult is a convenience function to generate the expected values func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( - env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack, rstack = newstack(), newReturnStack() - pc = uint64(0) - interpreter = env.interpreter.(*EVMInterpreter) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + interpreter = env.interpreter.(*EVMInterpreter) ) result := make([]TwoOperandTestcase, len(args)) for i, param := range args { @@ -242,7 +241,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, &callCtx{nil, stack, rstack, nil}) + opFn(&pc, interpreter, &callCtx{nil, stack, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -282,7 +281,7 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack, rstack = newstack(), newReturnStack() + stack = newstack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -300,7 +299,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { a.SetBytes(arg) stack.push(a) } - op(&pc, evmInterpreter, &callCtx{nil, stack, rstack, nil}) + op(&pc, evmInterpreter, &callCtx{nil, stack, nil}) stack.pop() } } @@ -516,7 +515,7 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack, rstack = newstack(), newReturnStack() + stack = newstack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -526,12 +525,12 @@ func TestOpMstore(t *testing.T) { pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int)) - opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int)) - opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -540,7 +539,7 @@ func TestOpMstore(t *testing.T) { func BenchmarkOpMstore(bench *testing.B) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack, rstack = newstack(), newReturnStack() + stack = newstack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -554,14 +553,14 @@ func BenchmarkOpMstore(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.pushN(*value, *memStart) - opMstore(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) } } func BenchmarkOpSHA3(bench *testing.B) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) - stack, rstack = newstack(), newReturnStack() + stack = newstack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -573,7 +572,7 @@ func BenchmarkOpSHA3(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.pushN(*uint256.NewInt().SetUint64(32), *start) - opSha3(&pc, evmInterpreter, &callCtx{mem, stack, rstack, nil}) + opSha3(&pc, evmInterpreter, &callCtx{mem, stack, nil}) } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 0084b7d071..06a3b962b2 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -67,7 +67,6 @@ type Interpreter interface { type callCtx struct { memory *Memory stack *Stack - rstack *ReturnStack contract *Contract } @@ -161,14 +160,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } var ( - op OpCode // current opcode - mem = NewMemory() // bound memory - stack = newstack() // local stack - returns = newReturnStack() // local returns stack + op OpCode // current opcode + mem = NewMemory() // bound memory + stack = newstack() // local stack callContext = &callCtx{ memory: mem, stack: stack, - rstack: returns, contract: contract, } // For optimisation reason we're using uint64 as the program counter. @@ -187,7 +184,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // they are returned to the pools defer func() { returnStack(stack) - returnRStack(returns) }() contract.Input = input @@ -195,9 +191,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( defer func() { if err != nil { if !logged { - in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, in.returnData, contract, in.evm.depth, err) } else { - in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) } } }() @@ -279,7 +275,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } if in.cfg.Debug { - in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, in.returnData, contract, in.evm.depth, err) logged = true } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index d831f9300f..7b61762456 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -66,7 +66,6 @@ type JumpTable [256]*operation // contantinople, istanbul, petersburg and berlin instructions. func newBerlinInstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() - enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 return instructionSet } diff --git a/core/vm/logger.go b/core/vm/logger.go index 962be6ec8e..41ce00ed01 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -70,7 +70,6 @@ type StructLog struct { Memory []byte `json:"memory"` MemorySize int `json:"memSize"` Stack []*big.Int `json:"stack"` - ReturnStack []uint32 `json:"returnStack"` ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` @@ -81,7 +80,6 @@ type StructLog struct { // overrides for gencodec type structLogMarshaling struct { Stack []*math.HexOrDecimal256 - ReturnStack []math.HexOrDecimal64 Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 Memory hexutil.Bytes @@ -110,8 +108,8 @@ func (s *StructLog) ErrorString() string { // if you need to retain them beyond the current call. type Tracer interface { CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error - CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error - CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error + CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error + CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error } @@ -148,7 +146,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea // CaptureState logs a new structured log message and pushes it out to the environment // // CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { return errTraceLimitReached @@ -167,11 +165,6 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui stck[i] = new(big.Int).Set(item.ToBig()) } } - var rstack []uint32 - if !l.cfg.DisableStack && rStack != nil { - rstck := make([]uint32, len(rStack.data)) - copy(rstck, rStack.data) - } // Copy a snapshot of the current storage to a new container var storage Storage if !l.cfg.DisableStorage { @@ -204,14 +197,14 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui copy(rdata, rData) } // create a new snapshot of the EVM. - log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err} + log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) return nil } // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { return nil } @@ -252,12 +245,6 @@ func WriteTrace(writer io.Writer, logs []StructLog) { fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) } } - if len(log.ReturnStack) > 0 { - fmt.Fprintln(writer, "ReturnStack:") - for i := len(log.Stack) - 1; i >= 0; i-- { - fmt.Fprintf(writer, "%08d 0x%x (%d)\n", len(log.Stack)-i-1, log.ReturnStack[i], log.ReturnStack[i]) - } - } if len(log.Memory) > 0 { fmt.Fprintln(writer, "Memory:") fmt.Fprint(writer, hex.Dump(log.Memory)) @@ -323,7 +310,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b return nil } -func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { +func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) if !t.cfg.DisableStack { @@ -334,14 +321,6 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 } b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) - - // format return stack - a = a[:0] - for _, elem := range rStack.data { - a = append(a, fmt.Sprintf("%2d", elem)) - } - b = fmt.Sprintf("[%v]", strings.Join(a, ",")) - fmt.Fprintf(t.out, "%10v |", b) } fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund()) fmt.Fprintln(t.out, "") @@ -351,7 +330,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 return nil } -func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index 5f3f2c42f7..a27c261ed8 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create } // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { log := StructLog{ Pc: pc, Op: op, @@ -68,7 +68,6 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint logstack[i] = item.ToBig() } log.Stack = logstack - log.ReturnStack = rStack.data } if !l.cfg.DisableReturnData { log.ReturnData = rData @@ -77,7 +76,7 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint } // CaptureFault outputs state information on the logger. -func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { return nil } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index bf7d5358f8..5a5f42fd34 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -55,13 +55,12 @@ func TestStoreCapture(t *testing.T) { logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() - rstack = newReturnStack() contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0) ) stack.push(uint256.NewInt().SetUint64(1)) stack.push(uint256.NewInt()) var index common.Hash - logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, nil, contract, 0, nil) + logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, nil, contract, 0, nil) if len(logger.storage[contract.Address()]) == 0 { t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index da7b2ee4aa..b0adf37d0c 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -107,21 +107,18 @@ const ( // 0x50 range - 'storage' and execution. const ( - POP OpCode = 0x50 - MLOAD OpCode = 0x51 - MSTORE OpCode = 0x52 - MSTORE8 OpCode = 0x53 - SLOAD OpCode = 0x54 - SSTORE OpCode = 0x55 - JUMP OpCode = 0x56 - JUMPI OpCode = 0x57 - PC OpCode = 0x58 - MSIZE OpCode = 0x59 - GAS OpCode = 0x5a - JUMPDEST OpCode = 0x5b - BEGINSUB OpCode = 0x5c - RETURNSUB OpCode = 0x5d - JUMPSUB OpCode = 0x5e + POP OpCode = 0x50 + MLOAD OpCode = 0x51 + MSTORE OpCode = 0x52 + MSTORE8 OpCode = 0x53 + SLOAD OpCode = 0x54 + SSTORE OpCode = 0x55 + JUMP OpCode = 0x56 + JUMPI OpCode = 0x57 + PC OpCode = 0x58 + MSIZE OpCode = 0x59 + GAS OpCode = 0x5a + JUMPDEST OpCode = 0x5b ) // 0x60 range. @@ -300,10 +297,6 @@ var opCodeToString = map[OpCode]string{ GAS: "GAS", JUMPDEST: "JUMPDEST", - BEGINSUB: "BEGINSUB", - JUMPSUB: "JUMPSUB", - RETURNSUB: "RETURNSUB", - // 0x60 range - push. PUSH1: "PUSH1", PUSH2: "PUSH2", @@ -468,9 +461,6 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, - "BEGINSUB": BEGINSUB, - "RETURNSUB": RETURNSUB, - "JUMPSUB": JUMPSUB, "PUSH1": PUSH1, "PUSH2": PUSH2, "PUSH3": PUSH3, diff --git a/core/vm/stack.go b/core/vm/stack.go index af27d6552c..c71d2653a5 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -98,34 +98,3 @@ func (st *Stack) Print() { } fmt.Println("#############") } - -var rStackPool = sync.Pool{ - New: func() interface{} { - return &ReturnStack{data: make([]uint32, 0, 10)} - }, -} - -// ReturnStack is an object for basic return stack operations. -type ReturnStack struct { - data []uint32 -} - -func newReturnStack() *ReturnStack { - return rStackPool.Get().(*ReturnStack) -} - -func returnRStack(rs *ReturnStack) { - rs.data = rs.data[:0] - rStackPool.Put(rs) -} - -func (st *ReturnStack) push(d uint32) { - st.data = append(st.data, d) -} - -// A uint32 is sufficient as for code below 4.2G -func (st *ReturnStack) pop() (ret uint32) { - ret = st.data[len(st.data)-1] - st.data = st.data[:len(st.data)-1] - return -} diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 80775caa8e..4c398edf34 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -544,7 +544,7 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rdata []byte, contract *vm.Contract, depth int, err error) error { +func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rdata []byte, contract *vm.Contract, depth int, err error) error { if jst.err == nil { // Initialize the context if it wasn't done yet if !jst.inited { @@ -595,7 +595,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { +func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { if jst.err == nil { // Apart from the error, everything matches the previous invocation jst.errorValue = new(string) diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index f28e14864b..d96030385c 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -152,10 +152,10 @@ func TestHaltBetweenSteps(t *testing.T) { env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) + tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) + tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) if _, err := tracer.GetResult(); err.Error() != timeout.Error() { t.Errorf("Expected timeout error, got %v", err) From 430f69e01eb33a1b2f4f3c9372da14d01aaba62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 2 Mar 2021 23:51:03 +0200 Subject: [PATCH 359/948] core/vm/runtime: more unshipping --- core/vm/runtime/runtime_test.go | 225 +------------------------------- 1 file changed, 2 insertions(+), 223 deletions(-) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index af69e3333f..6e0434c2ca 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -330,14 +330,14 @@ func (s *stepCounter) CaptureStart(from common.Address, to common.Address, creat return nil } -func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rData []byte, contract *vm.Contract, depth int, err error) error { +func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rData []byte, contract *vm.Contract, depth int, err error) error { s.steps++ // Enable this for more output //s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err) return nil } -func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { +func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { return nil } @@ -345,227 +345,6 @@ func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, return nil } -func TestJumpSub1024Limit(t *testing.T) { - state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - address := common.HexToAddress("0x0a") - // Code is - // 0 beginsub - // 1 push 0 - // 3 jumpsub - // - // The code recursively calls itself. It should error when the returns-stack - // grows above 1023 - state.SetCode(address, []byte{ - byte(vm.PUSH1), 3, - byte(vm.JUMPSUB), - byte(vm.BEGINSUB), - byte(vm.PUSH1), 3, - byte(vm.JUMPSUB), - }) - tracer := stepCounter{inner: vm.NewJSONLogger(nil, os.Stdout)} - // Enable 2315 - _, _, err := Call(address, nil, &Config{State: state, - GasLimit: 20000, - ChainConfig: params.AllEthashProtocolChanges, - EVMConfig: vm.Config{ - ExtraEips: []int{2315}, - Debug: true, - //Tracer: vm.NewJSONLogger(nil, os.Stdout), - Tracer: &tracer, - }}) - exp := "return stack limit reached" - if err.Error() != exp { - t.Fatalf("expected %v, got %v", exp, err) - } - if exp, got := 2048, tracer.steps; exp != got { - t.Fatalf("expected %d steps, got %d", exp, got) - } -} - -func TestReturnSubShallow(t *testing.T) { - state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - address := common.HexToAddress("0x0a") - // The code does returnsub without having anything on the returnstack. - // It should not panic, but just fail after one step - state.SetCode(address, []byte{ - byte(vm.PUSH1), 5, - byte(vm.JUMPSUB), - byte(vm.RETURNSUB), - byte(vm.PC), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - byte(vm.PC), - }) - tracer := stepCounter{} - - // Enable 2315 - _, _, err := Call(address, nil, &Config{State: state, - GasLimit: 10000, - ChainConfig: params.AllEthashProtocolChanges, - EVMConfig: vm.Config{ - ExtraEips: []int{2315}, - Debug: true, - Tracer: &tracer, - }}) - - exp := "invalid retsub" - if err.Error() != exp { - t.Fatalf("expected %v, got %v", exp, err) - } - if exp, got := 4, tracer.steps; exp != got { - t.Fatalf("expected %d steps, got %d", exp, got) - } -} - -// disabled -- only used for generating markdown -func DisabledTestReturnCases(t *testing.T) { - cfg := &Config{ - EVMConfig: vm.Config{ - Debug: true, - Tracer: vm.NewMarkdownLogger(nil, os.Stdout), - ExtraEips: []int{2315}, - }, - } - // This should fail at first opcode - Execute([]byte{ - byte(vm.RETURNSUB), - byte(vm.PC), - byte(vm.PC), - }, nil, cfg) - - // Should also fail - Execute([]byte{ - byte(vm.PUSH1), 5, - byte(vm.JUMPSUB), - byte(vm.RETURNSUB), - byte(vm.PC), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - byte(vm.PC), - }, nil, cfg) - - // This should complete - Execute([]byte{ - byte(vm.PUSH1), 0x4, - byte(vm.JUMPSUB), - byte(vm.STOP), - byte(vm.BEGINSUB), - byte(vm.PUSH1), 0x9, - byte(vm.JUMPSUB), - byte(vm.RETURNSUB), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - }, nil, cfg) -} - -// DisabledTestEipExampleCases contains various testcases that are used for the -// EIP examples -// This test is disabled, as it's only used for generating markdown -func DisabledTestEipExampleCases(t *testing.T) { - cfg := &Config{ - EVMConfig: vm.Config{ - Debug: true, - Tracer: vm.NewMarkdownLogger(nil, os.Stdout), - ExtraEips: []int{2315}, - }, - } - prettyPrint := func(comment string, code []byte) { - instrs := make([]string, 0) - it := asm.NewInstructionIterator(code) - for it.Next() { - if it.Arg() != nil && 0 < len(it.Arg()) { - instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg())) - } else { - instrs = append(instrs, fmt.Sprintf("%v", it.Op())) - } - } - ops := strings.Join(instrs, ", ") - - fmt.Printf("%v\nBytecode: `0x%x` (`%v`)\n", - comment, - code, ops) - Execute(code, nil, cfg) - } - - { // First eip testcase - code := []byte{ - byte(vm.PUSH1), 4, - byte(vm.JUMPSUB), - byte(vm.STOP), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - } - prettyPrint("This should jump into a subroutine, back out and stop.", code) - } - - { - code := []byte{ - byte(vm.PUSH9), 0x00, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 4 + 8, - byte(vm.JUMPSUB), - byte(vm.STOP), - byte(vm.BEGINSUB), - byte(vm.PUSH1), 8 + 9, - byte(vm.JUMPSUB), - byte(vm.RETURNSUB), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - } - prettyPrint("This should execute fine, going into one two depths of subroutines", code) - } - // TODO(@holiman) move this test into an actual test, which not only prints - // out the trace. - { - code := []byte{ - byte(vm.PUSH9), 0x01, 0x00, 0x00, 0x00, 0x0, 0x00, 0x00, 0x00, 4 + 8, - byte(vm.JUMPSUB), - byte(vm.STOP), - byte(vm.BEGINSUB), - byte(vm.PUSH1), 8 + 9, - byte(vm.JUMPSUB), - byte(vm.RETURNSUB), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - } - prettyPrint("This should fail, since the given location is outside of the "+ - "code-range. The code is the same as previous example, except that the "+ - "pushed location is `0x01000000000000000c` instead of `0x0c`.", code) - } - { - // This should fail at first opcode - code := []byte{ - byte(vm.RETURNSUB), - byte(vm.PC), - byte(vm.PC), - } - prettyPrint("This should fail at first opcode, due to shallow `return_stack`", code) - - } - { - code := []byte{ - byte(vm.PUSH1), 5, // Jump past the subroutine - byte(vm.JUMP), - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - byte(vm.JUMPDEST), - byte(vm.PUSH1), 3, // Now invoke the subroutine - byte(vm.JUMPSUB), - } - prettyPrint("In this example. the JUMPSUB is on the last byte of code. When the "+ - "subroutine returns, it should hit the 'virtual stop' _after_ the bytecode, "+ - "and not exit with error", code) - } - - { - code := []byte{ - byte(vm.BEGINSUB), - byte(vm.RETURNSUB), - byte(vm.STOP), - } - prettyPrint("In this example, the code 'walks' into a subroutine, which is not "+ - "allowed, and causes an error", code) - } -} - // benchmarkNonModifyingCode benchmarks code, but if the code modifies the // state, this should not be used, since it does not reset the state between runs. func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) { From 0540d3c6f60d1cba6a3dd384790f5d1fa0d799bd Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Mar 2021 08:42:59 +0100 Subject: [PATCH 360/948] cmd/geth: put allowUnsecureTx flag in RPC section (#22412) --- cmd/geth/usage.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 6935adabc7..2c3cdf1945 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -152,6 +152,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.GraphQLVirtualHostsFlag, utils.RPCGlobalGasCapFlag, utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, From c539a052bd5a31dfaeabf65d789b691f5d03f300 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 3 Mar 2021 18:04:25 +0800 Subject: [PATCH 361/948] params: update chts (#22418) --- params/config.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/params/config.go b/params/config.go index 8cbfffc03c..8f8b36b57d 100644 --- a/params/config.go +++ b/params/config.go @@ -74,10 +74,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 345, - SectionHead: common.HexToHash("0x5453bab878704adebc934b41fd214a07ea7a72b8572ff088dca7f7956cd0ef28"), - CHTRoot: common.HexToHash("0x7693d432595846c094f47cb37f5c868b0b7b1968fc6b0fc411ded1345fdaffab"), - BloomRoot: common.HexToHash("0x8b0e7895bc39840d8dac857e26bdf3d0a07684b0b962b252546659e0337a9f70"), + SectionIndex: 364, + SectionHead: common.HexToHash("0x3fd20ff221f5e962bb66f57a61973bfc2ba959879a6509384a80a45d208b5afc"), + CHTRoot: common.HexToHash("0xe35b3b807f4e9427fb4e2929961c78a9dc10f503a538319031cc7d00946a0591"), + BloomRoot: common.HexToHash("0x340553b378b2db214b898be15c80ac5be7caffc2e6448fd6f7aff23290d89296"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -157,10 +157,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 232, - SectionHead: common.HexToHash("0x8170fca4039b11a008c11f9996ff112151cbb17411437bb2f86288e11158b2f0"), - CHTRoot: common.HexToHash("0x4526560d92ae1b3a6d3ee780c3ad289ba2bbf1b5da58d9ea107f2f26412b631f"), - BloomRoot: common.HexToHash("0x82a889098a35d6a21ea8894d35a1db69b94bad61b988bbe5ae4601437320e331"), + SectionIndex: 248, + SectionHead: common.HexToHash("0x26874cf023695778cc3175d1bec19894204d8d0b756b587e81e35f300dc5b33c"), + CHTRoot: common.HexToHash("0xc129d1ed6673c5d3e1068e9d97244e72952b7ca08acbd7b3bfa58bc3085c442c"), + BloomRoot: common.HexToHash("0x1dafe79dcd7d348782aa834a4a4397890d9ad90643736791132ed5c16879a037"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -198,10 +198,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 116, - SectionHead: common.HexToHash("0xf2d200f636f213c9c7bb4e747ff564813da7708253037103aef3d8be5203c5e1"), - CHTRoot: common.HexToHash("0xb0ac83e2ccf6c2776945e099c4e3df50fe6200499c8b2045c34cafdf57d15087"), - BloomRoot: common.HexToHash("0xfb580ad1c611230a4bfc56534f58bcb156d028bc6ce70e35403dc019c7c02d90"), + SectionIndex: 132, + SectionHead: common.HexToHash("0x29fa240c97b47ecbfef3fea8b3cff035d93154d1d48b25e3333cf2f7067c5324"), + CHTRoot: common.HexToHash("0x85e5c59e5b202284291405dadc40dc36ab6417bd189fb18be24f6dcab6b80511"), + BloomRoot: common.HexToHash("0x0b7afdd200477f46e982e2cabc822ac454424986fa50d899685dfaeede1f882d"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From 07e907c7d4ce01fff663aa7b5a378f647518996f Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 3 Mar 2021 18:04:50 +0800 Subject: [PATCH 362/948] cmd/utils: fix txlookuplimit for archive node (#22419) * cmd/utils: fix exclusive check for archive node * cmd/utils: set the txlookuplimit to 0 --- cmd/utils/flags.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 324a6d6a47..7bbca42654 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1477,8 +1477,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer - CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) - if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalIsSet(TxLookupLimitFlag.Name) { + if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { + ctx.GlobalSet(TxLookupLimitFlag.Name, "0") + log.Warn("Disable transaction unindexing for archive node") + } + if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") } var ks *keystore.KeyStore From ba999105ef89473cfe39e5e53354f7099e67a290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 3 Mar 2021 12:05:27 +0200 Subject: [PATCH 363/948] core/forkid, params: unset Berlin fork number (#22413) --- core/forkid/forkid_test.go | 58 ++++++++++++++++---------------------- params/config.go | 4 --- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index a20598fa9d..87d64ed67f 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -43,26 +43,24 @@ func TestCreation(t *testing.T) { params.MainnetChainConfig, params.MainnetGenesisHash, []testcase{ - {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced - {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block - {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block - {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block - {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block - {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block - {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block - {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block - {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block - {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block - {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block - {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block - {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block - {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block - {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block - {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block - {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block - {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // First Berlin block - {20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // Future Berlin block + {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // First Muir Glacier block + {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // Future Muir Glacier block }, }, // Ropsten test cases @@ -82,10 +80,8 @@ func TestCreation(t *testing.T) { {6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block {6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block {7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block - {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block - {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block - {9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // First Berlin block - {10000000, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // Future Berlin block + {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // First Muir Glacier block + {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // Future Muir Glacier block }, }, // Rinkeby test cases @@ -104,10 +100,8 @@ func TestCreation(t *testing.T) { {4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block {4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block {5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block - {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block - {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block - {8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // First Berlin block - {10000000, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // Future Berlin block + {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // First Istanbul block + {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // Future Istanbul block }, }, // Goerli test cases @@ -117,10 +111,8 @@ func TestCreation(t *testing.T) { []testcase{ {0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block - {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block - {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block - {4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // First Berlin block - {5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // Future Berlin block + {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // First Istanbul block + {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // Future Istanbul block }, }, } @@ -193,7 +185,7 @@ func TestValidation(t *testing.T) { // Local is mainnet Petersburg, remote is Rinkeby Petersburg. {7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet Istanbul, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). diff --git a/params/config.go b/params/config.go index 8f8b36b57d..9b8c5cf4ed 100644 --- a/params/config.go +++ b/params/config.go @@ -68,7 +68,6 @@ var ( PetersburgBlock: big.NewInt(7_280_000), IstanbulBlock: big.NewInt(9_069_000), MuirGlacierBlock: big.NewInt(9_200_000), - BerlinBlock: big.NewInt(12_244_000), Ethash: new(EthashConfig), } @@ -108,7 +107,6 @@ var ( PetersburgBlock: big.NewInt(4_939_394), IstanbulBlock: big.NewInt(6_485_846), MuirGlacierBlock: big.NewInt(7_117_117), - BerlinBlock: big.NewInt(9_812_189), Ethash: new(EthashConfig), } @@ -148,7 +146,6 @@ var ( PetersburgBlock: big.NewInt(4_321_234), IstanbulBlock: big.NewInt(5_435_345), MuirGlacierBlock: nil, - BerlinBlock: big.NewInt(8_290_928), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -189,7 +186,6 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(1_561_651), MuirGlacierBlock: nil, - BerlinBlock: big.NewInt(4_460_644), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, From b24804d88cdbd38edc85ee9f2afaa9e6cb7a767e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Wed, 3 Mar 2021 15:05:24 +0100 Subject: [PATCH 364/948] les: fix nodiscover option on the client side (#22422) --- les/client.go | 12 +++++++++++- les/vflux/client/serverpool.go | 6 +++--- les/vflux/client/serverpool_test.go | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/les/client.go b/les/client.go index ecabfdf503..605c4d03ca 100644 --- a/les/client.go +++ b/les/client.go @@ -115,7 +115,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { p2pConfig: &stack.Config().P2P, } - leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, leth.prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) + var prenegQuery vfc.QueryFunc + if leth.p2pServer.DiscV5 != nil { + prenegQuery = leth.prenegQuery + } + leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) @@ -194,6 +198,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { // VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies { + if s.p2pServer.DiscV5 == nil { + return nil + } reqsEnc, _ := rlp.EncodeToBytes(&reqs) repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc) var replies vflux.Replies @@ -208,6 +215,9 @@ func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.R func (s *LightEthereum) vfxVersion(n *enode.Node) uint { if n.Seq() == 0 { var err error + if s.p2pServer.DiscV5 == nil { + return 0 + } if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { s.serverPool.Persist(n) } else { diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go index 47ec4fee74..e73b277ced 100644 --- a/les/vflux/client/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -91,7 +91,7 @@ type nodeHistoryEnc struct { // queryFunc sends a pre-negotiation query and blocks until a response arrives or timeout occurs. // It returns 1 if the remote node has confirmed that connection is possible, 0 if not // possible and -1 if no response arrived (timeout). -type queryFunc func(*enode.Node) int +type QueryFunc func(*enode.Node) int var ( clientSetup = &nodestate.Setup{Version: 2} @@ -150,7 +150,7 @@ var ( ) // NewServerPool creates a new server pool -func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query queryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { +func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query QueryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { s := &ServerPool{ db: db, clock: clock, @@ -246,7 +246,7 @@ func (s *ServerPool) AddSource(source enode.Iterator) { // addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. -func (s *ServerPool) addPreNegFilter(input enode.Iterator, query queryFunc) enode.Iterator { +func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enode.Iterator { s.fillSet = NewFillSet(s.ns, input, sfQueried) s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { if newState.Equals(sfQueried) { diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go index ee299618c6..c777d6c16d 100644 --- a/les/vflux/client/serverpool_test.go +++ b/les/vflux/client/serverpool_test.go @@ -107,7 +107,7 @@ func (s *ServerPoolTest) addTrusted(i int) { } func (s *ServerPoolTest) start() { - var testQuery queryFunc + var testQuery QueryFunc if s.preNeg { testQuery = func(node *enode.Node) int { idx := testNodeIndex(node.ID()) From 5a81dd97d5f3347457e640631564fa5b893720c2 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 3 Mar 2021 22:08:14 +0800 Subject: [PATCH 365/948] cmd: retire whisper flags (#22421) * cmd: retire whisper flags * cmd/geth: remove whisper configs --- cmd/geth/config.go | 31 ++----------------------------- cmd/geth/consolecmd.go | 2 +- cmd/geth/main.go | 8 -------- cmd/geth/usage.go | 4 ---- cmd/utils/flags.go | 28 ---------------------------- 5 files changed, 3 insertions(+), 70 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c77c04c252..6fc75363c6 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -43,7 +42,7 @@ var ( Name: "dumpconfig", Usage: "Show configuration values", ArgsUsage: "", - Flags: append(append(nodeFlags, rpcFlags...), whisperFlags...), + Flags: append(nodeFlags, rpcFlags...), Category: "MISCELLANEOUS COMMANDS", Description: `The dumpconfig command shows configuration values.`, } @@ -75,19 +74,8 @@ type ethstatsConfig struct { URL string `toml:",omitempty"` } -// whisper has been deprecated, but clients out there might still have [Shh] -// in their config, which will crash. Cut them some slack by keeping the -// config, and displaying a message that those config switches are ineffectual. -// To be removed circa Q1 2021 -- @gballet. -type whisperDeprecatedConfig struct { - MaxMessageSize uint32 `toml:",omitempty"` - MinimumAcceptedPOW float64 `toml:",omitempty"` - RestrictConnectionBetweenLightClients bool `toml:",omitempty"` -} - type gethConfig struct { Eth ethconfig.Config - Shh whisperDeprecatedConfig Node node.Config Ethstats ethstatsConfig Metrics metrics.Config @@ -132,11 +120,8 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if err := loadConfig(file, &cfg); err != nil { utils.Fatalf("%v", err) } - - if cfg.Shh != (whisperDeprecatedConfig{}) { - log.Warn("Deprecated whisper config detected. Whisper has been moved to github.com/ethereum/whisper") - } } + // Apply flags. utils.SetNodeConfig(ctx, &cfg.Node) stack, err := node.New(&cfg.Node) @@ -147,22 +132,11 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } - utils.SetShhConfig(ctx, stack) - applyMetricConfig(ctx, &cfg) return stack, cfg } -// enableWhisper returns true in case one of the whisper flags is set. -func checkWhisper(ctx *cli.Context) { - for _, flag := range whisperFlags { - if ctx.GlobalIsSet(flag.GetName()) { - log.Warn("deprecated whisper flag detected. Whisper has been moved to github.com/ethereum/whisper") - } - } -} - // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) @@ -171,7 +145,6 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { } backend := utils.RegisterEthService(stack, &cfg.Eth) - checkWhisper(ctx) // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { utils.RegisterGraphQLService(stack, backend, cfg.Node) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index e26481003d..9d8794eb15 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -36,7 +36,7 @@ var ( Action: utils.MigrateFlags(localConsole), Name: "console", Usage: "Start an interactive JavaScript environment", - Flags: append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...), + Flags: append(append(nodeFlags, rpcFlags...), consoleFlags...), Category: "CONSOLE COMMANDS", Description: ` The Geth console is an interactive shell for the JavaScript runtime environment diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 9afa031584..207c93c0d8 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -186,13 +186,6 @@ var ( utils.AllowUnprotectedTxs, } - whisperFlags = []cli.Flag{ - utils.WhisperEnabledFlag, - utils.WhisperMaxMessageSizeFlag, - utils.WhisperMinPOWFlag, - utils.WhisperRestrictConnectionBetweenLightClientsFlag, - } - metricsFlags = []cli.Flag{ utils.MetricsEnabledFlag, utils.MetricsEnabledExpensiveFlag, @@ -251,7 +244,6 @@ func init() { app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, debug.Flags...) - app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, metricsFlags...) app.Before = func(ctx *cli.Context) error { diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 2c3cdf1945..daea0afc4e 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -216,10 +216,6 @@ var AppHelpFlagGroups = []flags.FlagGroup{ Name: "METRICS AND STATS", Flags: metricsFlags, }, - { - Name: "WHISPER (deprecated)", - Flags: whisperFlags, - }, { Name: "ALIASED (deprecated)", Flags: []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7bbca42654..fc479f3987 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -680,24 +680,6 @@ var ( Usage: "Maximum gas price will be recommended by gpo", Value: ethconfig.Defaults.GPO.MaxPrice.Int64(), } - WhisperEnabledFlag = cli.BoolFlag{ - Name: "shh", - Usage: "Enable Whisper", - } - WhisperMaxMessageSizeFlag = cli.IntFlag{ - Name: "shh.maxmessagesize", - Usage: "Max message size accepted", - Value: 1024 * 1024, - } - WhisperMinPOWFlag = cli.Float64Flag{ - Name: "shh.pow", - Usage: "Minimum POW accepted", - Value: 0.2, - } - WhisperRestrictConnectionBetweenLightClientsFlag = cli.BoolFlag{ - Name: "shh.restrict-light", - Usage: "Restrict connection between two whisper light clients", - } // Metrics flags MetricsEnabledFlag = cli.BoolFlag{ @@ -1461,16 +1443,6 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { } } -// SetShhConfig applies shh-related command line flags to the config. -func SetShhConfig(ctx *cli.Context, stack *node.Node) { - if ctx.GlobalIsSet(WhisperEnabledFlag.Name) || - ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) || - ctx.GlobalIsSet(WhisperMinPOWFlag.Name) || - ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) { - log.Warn("Whisper support has been deprecated and the code has been moved to github.com/ethereum/whisper") - } -} - // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags From cd316d7c7158f1ffc99910c8c07b951ec05ed067 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 3 Mar 2021 15:50:07 +0100 Subject: [PATCH 366/948] tests: update to latest tests (#22290) This updates the consensus tests to commit 31d6630 and adds support for access list transactions in the test runner. Co-authored-by: Martin Holst Swende --- tests/block_test.go | 14 +++++--------- tests/gen_stenv.go | 2 ++ tests/gen_sttransaction.go | 37 +++++++++++++++++++++++-------------- tests/gen_vmexec.go | 2 ++ tests/state_test.go | 8 -------- tests/state_test_util.go | 22 +++++++++++++--------- tests/testdata | 2 +- 7 files changed, 46 insertions(+), 41 deletions(-) diff --git a/tests/block_test.go b/tests/block_test.go index 84618fd0be..2649bae85a 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -25,7 +25,11 @@ func TestBlockchain(t *testing.T) { bt := new(testMatcher) // General state tests are 'exported' as blockchain tests, but we can run them natively. - bt.skipLoad(`^GeneralStateTests/`) + // For speedier CI-runs, the line below can be uncommented, so those are skipped. + // For now, in hardfork-times (Berlin), we run the tests both as StateTests and + // as blockchain tests, since the latter also covers things like receipt root + //bt.skipLoad(`^GeneralStateTests/`) + // Skip random failures due to selfish mining test bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) @@ -43,15 +47,7 @@ func TestBlockchain(t *testing.T) { // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, // using 4.6 TGas bt.skipLoad(`.*randomStatetest94.json.*`) - bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - if test.json.Network == "Berlin" { - // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them - // fail when berlin is defined as YOLOv3. We skip those, until they've been - // regenerated and re-imported - // TODO (@holiman) - return - } if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) } diff --git a/tests/gen_stenv.go b/tests/gen_stenv.go index 1d4baf2fd7..bfecc145b4 100644 --- a/tests/gen_stenv.go +++ b/tests/gen_stenv.go @@ -13,6 +13,7 @@ import ( var _ = (*stEnvMarshaling)(nil) +// MarshalJSON marshals as JSON. func (s stEnv) MarshalJSON() ([]byte, error) { type stEnv struct { Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` @@ -30,6 +31,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (s *stEnv) UnmarshalJSON(input []byte) error { type stEnv struct { Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index 451ffcbf43..2670f4f9c8 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -8,25 +8,29 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" ) var _ = (*stTransactionMarshaling)(nil) +// MarshalJSON marshals as JSON. func (s stTransaction) MarshalJSON() ([]byte, error) { type stTransaction struct { - GasPrice *math.HexOrDecimal256 `json:"gasPrice"` - Nonce math.HexOrDecimal64 `json:"nonce"` - To string `json:"to"` - Data []string `json:"data"` - GasLimit []math.HexOrDecimal64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey hexutil.Bytes `json:"secretKey"` + GasPrice *math.HexOrDecimal256 `json:"gasPrice"` + Nonce math.HexOrDecimal64 `json:"nonce"` + To string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []math.HexOrDecimal64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey hexutil.Bytes `json:"secretKey"` } var enc stTransaction enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) enc.Nonce = math.HexOrDecimal64(s.Nonce) enc.To = s.To enc.Data = s.Data + enc.AccessLists = s.AccessLists if s.GasLimit != nil { enc.GasLimit = make([]math.HexOrDecimal64, len(s.GasLimit)) for k, v := range s.GasLimit { @@ -38,15 +42,17 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (s *stTransaction) UnmarshalJSON(input []byte) error { type stTransaction struct { - GasPrice *math.HexOrDecimal256 `json:"gasPrice"` - Nonce *math.HexOrDecimal64 `json:"nonce"` - To *string `json:"to"` - Data []string `json:"data"` - GasLimit []math.HexOrDecimal64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey *hexutil.Bytes `json:"secretKey"` + GasPrice *math.HexOrDecimal256 `json:"gasPrice"` + Nonce *math.HexOrDecimal64 `json:"nonce"` + To *string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []math.HexOrDecimal64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey *hexutil.Bytes `json:"secretKey"` } var dec stTransaction if err := json.Unmarshal(input, &dec); err != nil { @@ -64,6 +70,9 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { if dec.Data != nil { s.Data = dec.Data } + if dec.AccessLists != nil { + s.AccessLists = dec.AccessLists + } if dec.GasLimit != nil { s.GasLimit = make([]uint64, len(dec.GasLimit)) for k, v := range dec.GasLimit { diff --git a/tests/gen_vmexec.go b/tests/gen_vmexec.go index a5f01cf456..2fe155152d 100644 --- a/tests/gen_vmexec.go +++ b/tests/gen_vmexec.go @@ -14,6 +14,7 @@ import ( var _ = (*vmExecMarshaling)(nil) +// MarshalJSON marshals as JSON. func (v vmExec) MarshalJSON() ([]byte, error) { type vmExec struct { Address common.UnprefixedAddress `json:"address" gencodec:"required"` @@ -37,6 +38,7 @@ func (v vmExec) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } +// UnmarshalJSON unmarshals from JSON. func (v *vmExec) UnmarshalJSON(input []byte) error { type vmExec struct { Address *common.UnprefixedAddress `json:"address" gencodec:"required"` diff --git a/tests/state_test.go b/tests/state_test.go index 0f2bd38532..b77a898c21 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -62,14 +62,6 @@ func TestState(t *testing.T) { } { st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { - if subtest.Fork == "Berlin" { - // Our current berlin-tests were generated using YOLOv2 rules, hence a lot of them - // fail when berlin is defined as YOLOv3. We skip those, until they've been - // regenerated and re-imported - // TODO (@holiman) - continue - } - subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) name := name + "/" + key diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 99681baaf1..46834de6da 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -95,13 +95,14 @@ type stEnvMarshaling struct { //go:generate gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go type stTransaction struct { - GasPrice *big.Int `json:"gasPrice"` - Nonce uint64 `json:"nonce"` - To string `json:"to"` - Data []string `json:"data"` - GasLimit []uint64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey []byte `json:"secretKey"` + GasPrice *big.Int `json:"gasPrice"` + Nonce uint64 `json:"nonce"` + To string `json:"to"` + Data []string `json:"data"` + AccessLists []*types.AccessList `json:"accessLists,omitempty"` + GasLimit []uint64 `json:"gasLimit"` + Value []string `json:"value"` + PrivateKey []byte `json:"secretKey"` } type stTransactionMarshaling struct { @@ -292,8 +293,11 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { if err != nil { return nil, fmt.Errorf("invalid tx data %q", dataHex) } - - msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, nil, true) + var accessList types.AccessList + if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil { + accessList = *tx.AccessLists[ps.Indexes.Data] + } + msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, accessList, true) return msg, nil } diff --git a/tests/testdata b/tests/testdata index 6c863f03be..31d663076b 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 6c863f03bee8d7a66bb7a028a9f880a86a5f4975 +Subproject commit 31d663076b6678df18983d6da912d7cad4ad3416 From 56dec25ae26bf749b93c3ea69538fabea60c5768 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 3 Mar 2021 17:44:17 +0100 Subject: [PATCH 367/948] params: release geth 1.10.0 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 3447f0f283..de5eac3af9 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 63385374ec0e0a513ff30b9360471769e937244a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 3 Mar 2021 18:01:31 +0100 Subject: [PATCH 368/948] params: begin v1.10.1 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index de5eac3af9..b974c3abda 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 1 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From 9230ca4924ef3a75f1653d828a2cd845d54977e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 4 Mar 2021 10:44:37 +0200 Subject: [PATCH 369/948] Revert "core/forkid, params: unset Berlin fork number (#22413)" This reverts commit ba999105ef89473cfe39e5e53354f7099e67a290. --- core/forkid/forkid_test.go | 58 ++++++++++++++++++++++---------------- params/config.go | 4 +++ 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 87d64ed67f..a20598fa9d 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -43,24 +43,26 @@ func TestCreation(t *testing.T) { params.MainnetChainConfig, params.MainnetGenesisHash, []testcase{ - {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced - {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block - {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block - {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block - {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block - {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block - {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block - {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block - {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block - {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block - {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block - {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block - {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block - {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block - {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block - {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block - {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // First Muir Glacier block - {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 0}}, // Future Muir Glacier block + {0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced + {1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block + {1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block + {1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block + {1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block + {2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block + {2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block + {2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block + {2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block + {4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block + {4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block + {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block + {7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block + {9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block + {9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block + {9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block + {9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block + {12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block + {12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // First Berlin block + {20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}}, // Future Berlin block }, }, // Ropsten test cases @@ -80,8 +82,10 @@ func TestCreation(t *testing.T) { {6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block {6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block {7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block - {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // First Muir Glacier block - {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}}, // Future Muir Glacier block + {7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block + {9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block + {9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // First Berlin block + {10000000, ID{Hash: checksumToBytes(0xa157d377), Next: 0}}, // Future Berlin block }, }, // Rinkeby test cases @@ -100,8 +104,10 @@ func TestCreation(t *testing.T) { {4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block {4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block {5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block - {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // First Istanbul block - {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}}, // Future Istanbul block + {5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block + {8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block + {8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // First Berlin block + {10000000, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}}, // Future Berlin block }, }, // Goerli test cases @@ -111,8 +117,10 @@ func TestCreation(t *testing.T) { []testcase{ {0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block - {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // First Istanbul block - {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}}, // Future Istanbul block + {1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block + {4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block + {4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // First Berlin block + {5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}}, // Future Berlin block }, }, } @@ -185,7 +193,7 @@ func TestValidation(t *testing.T) { // Local is mainnet Petersburg, remote is Rinkeby Petersburg. {7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Istanbul, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork) // at some future block 88888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). diff --git a/params/config.go b/params/config.go index 9b8c5cf4ed..8f8b36b57d 100644 --- a/params/config.go +++ b/params/config.go @@ -68,6 +68,7 @@ var ( PetersburgBlock: big.NewInt(7_280_000), IstanbulBlock: big.NewInt(9_069_000), MuirGlacierBlock: big.NewInt(9_200_000), + BerlinBlock: big.NewInt(12_244_000), Ethash: new(EthashConfig), } @@ -107,6 +108,7 @@ var ( PetersburgBlock: big.NewInt(4_939_394), IstanbulBlock: big.NewInt(6_485_846), MuirGlacierBlock: big.NewInt(7_117_117), + BerlinBlock: big.NewInt(9_812_189), Ethash: new(EthashConfig), } @@ -146,6 +148,7 @@ var ( PetersburgBlock: big.NewInt(4_321_234), IstanbulBlock: big.NewInt(5_435_345), MuirGlacierBlock: nil, + BerlinBlock: big.NewInt(8_290_928), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -186,6 +189,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(1_561_651), MuirGlacierBlock: nil, + BerlinBlock: big.NewInt(4_460_644), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, From de61da99c49146200b0f0db07dd2be6afe1232ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 4 Mar 2021 13:04:50 +0200 Subject: [PATCH 370/948] build: fix PPA failure due to updated debsrc --- build/ci.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build/ci.go b/build/ci.go index 756fba8399..b5c67b2a80 100644 --- a/build/ci.go +++ b/build/ci.go @@ -564,16 +564,17 @@ func doDebianSource(cmdline []string) { build.MustRun(debuild) var ( - basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) - source = filepath.Join(*workdir, basename+".tar.xz") - dsc = filepath.Join(*workdir, basename+".dsc") - changes = filepath.Join(*workdir, basename+"_source.changes") + basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) + source = filepath.Join(*workdir, basename+".tar.xz") + dsc = filepath.Join(*workdir, basename+".dsc") + changes = filepath.Join(*workdir, basename+"_source.changes") + buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo") ) if *signer != "" { build.MustRunCommand("debsign", changes) } if *upload != "" { - ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes}) + ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo}) } } } From 5b95453ef2a52820147332af2c0d98432f470c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 4 Mar 2021 13:57:02 +0200 Subject: [PATCH 371/948] build: add support for Ubuntu Hirsute Hippo --- build/ci.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build/ci.go b/build/ci.go index b5c67b2a80..fb06c0f420 100644 --- a/build/ci.go +++ b/build/ci.go @@ -115,7 +115,6 @@ var ( } // A debian package is created for all executables listed here. - debEthereum = debPackage{ Name: "ethereum", Version: params.Version, @@ -137,11 +136,12 @@ var ( // Note: disco is unsupported because it was officially deprecated on Launchpad. // Note: eoan is unsupported because it was officially deprecated on Launchpad. debDistroGoBoots = map[string]string{ - "trusty": "golang-1.11", - "xenial": "golang-go", - "bionic": "golang-go", - "focal": "golang-go", - "groovy": "golang-go", + "trusty": "golang-1.11", + "xenial": "golang-go", + "bionic": "golang-go", + "focal": "golang-go", + "groovy": "golang-go", + "hirsute": "golang-go", } debGoBootPaths = map[string]string{ From 72b8cacf132cb6deed7ae3352ccbf32b9d429417 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 7 Mar 2021 20:55:01 +0100 Subject: [PATCH 372/948] tests: update reference tests with 2315 removed from Berlin --- tests/testdata | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdata b/tests/testdata index 31d663076b..c600d7795a 160000 --- a/tests/testdata +++ b/tests/testdata @@ -1 +1 @@ -Subproject commit 31d663076b6678df18983d6da912d7cad4ad3416 +Subproject commit c600d7795aa2ea57a9c856fc79f72fc05b542124 From c2d2f4ed8f232bb11663a1b01a2e578aa22f24bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Mar 2021 11:32:20 +0200 Subject: [PATCH 373/948] params: release Geth v1.10.1 --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index b974c3abda..e92ae13a1f 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 1 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 182670849eafdde729d009ec99d12358f996fbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 8 Mar 2021 11:34:08 +0200 Subject: [PATCH 374/948] params: begin v1.10.2 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index e92ae13a1f..d88b4f8b88 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 2 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From be87f769f676cbe3bf028e856160396ed08c64fc Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 8 Mar 2021 14:23:28 +0100 Subject: [PATCH 375/948] core/types: reduce allocations in GasPriceCmp (#22456) --- core/types/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index 49127630ae..a35e07a5a3 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -293,7 +293,7 @@ func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { // GasPriceCmp compares the gas prices of two transactions. func (tx *Transaction) GasPriceCmp(other *Transaction) int { - return tx.inner.gasPrice().Cmp(other.GasPrice()) + return tx.inner.gasPrice().Cmp(other.inner.gasPrice()) } // GasPriceIntCmp compares the gas price of the transaction against the given price. From 3d299b746893e39f580dfcca3cef90604d05f4b6 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 9 Mar 2021 09:04:03 +0100 Subject: [PATCH 376/948] les: fix errors in metric namespace (#22459) * les: add trailing slash to metric namespace * les: omit '.' in metric namespace --- les/client.go | 2 +- les/server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/les/client.go b/les/client.go index 605c4d03ca..a557e7ccb3 100644 --- a/les/client.go +++ b/les/client.go @@ -83,7 +83,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/les.client") + lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/") if err != nil { return nil, err } diff --git a/les/server.go b/les/server.go index 63feaf892c..9627d65afa 100644 --- a/les/server.go +++ b/les/server.go @@ -87,7 +87,7 @@ type LesServer struct { } func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { - lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/les.server") + lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/lesserver/") if err != nil { return nil, err } From 22082f9e565665f7da045b9ff708e4698f383c77 Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 9 Mar 2021 17:50:25 +0800 Subject: [PATCH 377/948] cmd: extend dumpgenesis to support network flags on the cmd (#22406) --- cmd/geth/chaincmd.go | 7 ++++++- cmd/utils/flags.go | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index ff9581fd88..1834dab6a9 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -62,7 +62,11 @@ It expects the genesis file as argument.`, Usage: "Dumps genesis block JSON configuration to stdout", ArgsUsage: "", Flags: []cli.Flag{ - utils.DataDirFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -227,6 +231,7 @@ func initGenesis(ctx *cli.Context) error { } func dumpGenesis(ctx *cli.Context) error { + // TODO(rjl493456442) support loading from the custom datadir genesis := utils.MakeGenesis(ctx) if genesis == nil { genesis = core.DefaultGenesisBlock() diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index fc479f3987..5c800a7fe9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1773,6 +1773,8 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { + case ctx.GlobalBool(MainnetFlag.Name): + genesis = core.DefaultGenesisBlock() case ctx.GlobalBool(RopstenFlag.Name): genesis = core.DefaultRopstenGenesisBlock() case ctx.GlobalBool(RinkebyFlag.Name): From aae7660410f0ef90279e14afaaf2f429fdc2a186 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 11 Mar 2021 15:09:25 +0100 Subject: [PATCH 378/948] p2p/enr: fix decoding of incomplete lists (#22484) Given a list of less than two elements DecodeRLP returned rlp.EOL, leading to issues in outer decoders. --- p2p/enr/enr.go | 7 +++++++ p2p/enr/enr_test.go | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index c36ae9e3ed..05e43fd805 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -50,6 +50,7 @@ var ( errNotSorted = errors.New("record key/value pairs are not sorted by key") errDuplicateKey = errors.New("record contains duplicate key") errIncompletePair = errors.New("record contains incomplete k/v pair") + errIncompleteList = errors.New("record contains less than two list elements") errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit) errEncodeUnsigned = errors.New("can't encode unsigned record") errNotFound = errors.New("no such key in record") @@ -209,9 +210,15 @@ func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) { return dec, raw, err } if err = s.Decode(&dec.signature); err != nil { + if err == rlp.EOL { + err = errIncompleteList + } return dec, raw, err } if err = s.Decode(&dec.seq); err != nil { + if err == rlp.EOL { + err = errIncompleteList + } return dec, raw, err } // The rest of the record contains sorted k/v pairs. diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go index 96a9ced5cf..bf3f104744 100644 --- a/p2p/enr/enr_test.go +++ b/p2p/enr/enr_test.go @@ -231,6 +231,29 @@ func TestRecordTooBig(t *testing.T) { require.NoError(t, signTest([]byte{5}, &r)) } +// This checks that incomplete RLP inputs are handled correctly. +func TestDecodeIncomplete(t *testing.T) { + type decTest struct { + input []byte + err error + } + tests := []decTest{ + {[]byte{0xC0}, errIncompleteList}, + {[]byte{0xC1, 0x1}, errIncompleteList}, + {[]byte{0xC2, 0x1, 0x2}, nil}, + {[]byte{0xC3, 0x1, 0x2, 0x3}, errIncompletePair}, + {[]byte{0xC4, 0x1, 0x2, 0x3, 0x4}, nil}, + {[]byte{0xC5, 0x1, 0x2, 0x3, 0x4, 0x5}, errIncompletePair}, + } + for _, test := range tests { + var r Record + err := rlp.DecodeBytes(test.input, &r) + if err != test.err { + t.Errorf("wrong error for %X: %v", test.input, err) + } + } +} + // TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs. func TestSignEncodeAndDecodeRandom(t *testing.T) { var r Record From 6387c520b724525861539c43b8da98372fc42534 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Fri, 12 Mar 2021 10:16:19 +0100 Subject: [PATCH 379/948] cmd/geth: add ancient datadir flag to snapshot subcommands (#22486) --- cmd/geth/snapshot.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index b59c530c27..068dea0b92 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -57,6 +57,7 @@ var ( Category: "MISCELLANEOUS COMMANDS", Flags: []cli.Flag{ utils.DataDirFlag, + utils.AncientFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, @@ -86,6 +87,7 @@ the trie clean cache with default directory will be deleted. Category: "MISCELLANEOUS COMMANDS", Flags: []cli.Flag{ utils.DataDirFlag, + utils.AncientFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, @@ -105,6 +107,7 @@ In other words, this command does the snapshot to trie conversion. Category: "MISCELLANEOUS COMMANDS", Flags: []cli.Flag{ utils.DataDirFlag, + utils.AncientFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, @@ -126,6 +129,7 @@ It's also usable without snapshot enabled. Category: "MISCELLANEOUS COMMANDS", Flags: []cli.Flag{ utils.DataDirFlag, + utils.AncientFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, From 3f74c8e0e504fe7606bc970dff71cf1266a4a9ed Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 13 Mar 2021 21:33:30 +0100 Subject: [PATCH 380/948] cmd/devp2p: better testcase failure output for ethtests (#22482) --- cmd/devp2p/internal/ethtest/types.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 734adff366..96012b3156 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -273,14 +273,15 @@ loop: for { switch msg := c.Read().(type) { case *Status: - if msg.Head != chain.blocks[chain.Len()-1].Hash() { - t.Fatalf("wrong head block in status: %s", msg.Head.String()) + if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { + t.Fatalf("wrong head block in status, want: %#x (block %d) have %#x", + want, chain.blocks[chain.Len()-1].NumberU64(), have) } - if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { - t.Fatalf("wrong TD in status: %v", msg.TD) + if have, want := msg.TD.Cmp(chain.TD(chain.Len())), 0; have != want { + t.Fatalf("wrong TD in status: have %v want %v", have, want) } - if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) { - t.Fatalf("wrong fork ID in status: %v", msg.ForkID) + if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { + t.Fatalf("wrong fork ID in status: have %v, want %v", have, want) } message = msg break loop From c6d45009f1ab46f61ebaa66d71d08d48097cc1a9 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 14 Mar 2021 16:13:25 +0100 Subject: [PATCH 381/948] eth, les: properly init statedb accesslist during tracing (#22480) * eth/state, les/state: properly init statedb accesslist when tracing, fixes #22475 * eth: review comments * eth/tracers: fix compilation err * eth/tracers: apply @karalabe's suggested fix --- eth/state_accessor.go | 1 + eth/tracers/api.go | 45 ++++++++++++++++++++++++++++++++++--------- les/state_accessor.go | 1 + 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 869b3d7636..cbbd9a8202 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -217,6 +217,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) + statedb.Prepare(tx.Hash(), block.Hash(), idx) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { release() return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 61fe055689..75ab403471 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -169,6 +169,13 @@ type StdTraceConfig struct { TxHash common.Hash } +// txTraceContext is the contextual infos about a transaction before it gets run. +type txTraceContext struct { + index int // Index of the transaction within the block + hash common.Hash // Hash of the transaction + block common.Hash // Hash of the block containing the transaction +} + // txTraceResult is the result of a single transaction trace. type txTraceResult struct { Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer @@ -266,7 +273,12 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) - res, err := api.traceTx(ctx, msg, blockCtx, task.statedb, config) + txctx := &txTraceContext{ + index: i, + hash: tx.Hash(), + block: task.block.Hash(), + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -478,6 +490,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac threads = len(txs) } blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + blockHash := block.Hash() for th := 0; th < threads; th++ { pend.Add(1) go func() { @@ -485,7 +498,12 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := txs[task.index].AsMessage(signer) - res, err := api.traceTx(ctx, msg, blockCtx, task.statedb, config) + txctx := &txTraceContext{ + index: task.index, + hash: txs[task.index].Hash(), + block: blockHash, + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue @@ -502,9 +520,8 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - - vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.backend.ChainConfig(), vm.Config{}) + statedb.Prepare(tx.Hash(), block.Hash(), i) + vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -619,6 +636,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block } // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) + statedb.Prepare(tx.Hash(), block.Hash(), i) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -678,7 +696,12 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * } defer release() - return api.traceTx(ctx, msg, vmctx, statedb, config) + txctx := &txTraceContext{ + index: int(index), + hash: hash, + block: blockHash, + } + return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } // TraceCall lets you trace a given eth_call. It collects the structured logs @@ -713,13 +736,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa // Execute the trace msg := args.ToMessage(api.backend.RPCGasCap()) vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) - return api.traceTx(ctx, msg, vmctx, statedb, config) + + return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, config) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *API) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTraceContext, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.Tracer @@ -753,9 +777,12 @@ func (api *API) traceTx(ctx context.Context, message core.Message, vmctx vm.Bloc default: tracer = vm.NewStructLogger(config.LogConfig) } - // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer}) + + // Call Prepare to clear out the statedb access list + statedb.Prepare(txctx.hash, txctx.block, txctx.index) + result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) diff --git a/les/state_accessor.go b/les/state_accessor.go index 3c9143c875..2f49bb921e 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -72,6 +72,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. msg, _ := tx.AsMessage(signer) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) + statedb.Prepare(tx.Hash(), block.Hash(), idx) if idx == txIndex { return msg, context, statedb, func() {}, nil } From faacc8e0faa89f5b3a7055e86924a95b6a9be944 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 15 Mar 2021 15:25:45 +0100 Subject: [PATCH 382/948] cmd/geth, eth/downloader: remove copydb command (#22501) * cmd/geth: remove copydb command * eth/downloader: remove fakepeer --- cmd/geth/chaincmd.go | 81 ------------------- cmd/geth/main.go | 1 - eth/downloader/fakepeer.go | 161 ------------------------------------- 3 files changed, 243 deletions(-) delete mode 100644 eth/downloader/fakepeer.go diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 1834dab6a9..2010ae4b30 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -28,14 +28,10 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/trie" "gopkg.in/urfave/cli.v1" ) @@ -150,27 +146,6 @@ be gzipped.`, Category: "BLOCKCHAIN COMMANDS", Description: ` The export-preimages command export hash preimages to an RLP encoded stream`, - } - copydbCommand = cli.Command{ - Action: utils.MigrateFlags(copyDb), - Name: "copydb", - Usage: "Create a local chain from a target chaindata folder", - ArgsUsage: "", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.CacheFlag, - utils.SyncModeFlag, - utils.FakePoWFlag, - utils.MainnetFlag, - utils.RopstenFlag, - utils.RinkebyFlag, - utils.TxLookupLimitFlag, - utils.GoerliFlag, - utils.YoloV3Flag, - }, - Category: "BLOCKCHAIN COMMANDS", - Description: ` -The first argument must be the directory containing the blockchain to download from`, } dumpCommand = cli.Command{ Action: utils.MigrateFlags(dump), @@ -394,62 +369,6 @@ func exportPreimages(ctx *cli.Context) error { return nil } -func copyDb(ctx *cli.Context) error { - // Ensure we have a source chain directory to copy - if len(ctx.Args()) < 1 { - utils.Fatalf("Source chaindata directory path argument missing") - } - if len(ctx.Args()) < 2 { - utils.Fatalf("Source ancient chain directory path argument missing") - } - // Initialize a new chain for the running node to sync into - stack, _ := makeConfigNode(ctx) - defer stack.Close() - - chain, chainDb := utils.MakeChain(ctx, stack, false) - syncMode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode) - - var syncBloom *trie.SyncBloom - if syncMode == downloader.FastSync { - syncBloom = trie.NewSyncBloom(uint64(ctx.GlobalInt(utils.CacheFlag.Name)/2), chainDb) - } - dl := downloader.New(0, chainDb, syncBloom, new(event.TypeMux), chain, nil, nil) - - // Create a source peer to satisfy downloader requests from - db, err := rawdb.NewLevelDBDatabaseWithFreezer(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name)/2, 256, ctx.Args().Get(1), "") - if err != nil { - return err - } - hc, err := core.NewHeaderChain(db, chain.Config(), chain.Engine(), func() bool { return false }) - if err != nil { - return err - } - peer := downloader.NewFakePeer("local", db, hc, dl) - if err = dl.RegisterPeer("local", 63, peer); err != nil { - return err - } - // Synchronise with the simulated peer - start := time.Now() - - currentHeader := hc.CurrentHeader() - if err = dl.Synchronise("local", currentHeader.Hash(), hc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()), syncMode); err != nil { - return err - } - for dl.Synchronising() { - time.Sleep(10 * time.Millisecond) - } - fmt.Printf("Database copy done in %v\n", time.Since(start)) - - // Compact the entire database to remove any sync overhead - start = time.Now() - fmt.Println("Compacting entire database...") - if err = db.Compact(nil, nil); err != nil { - utils.Fatalf("Compaction failed: %v", err) - } - fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) - return nil -} - func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 207c93c0d8..5559835499 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -212,7 +212,6 @@ func init() { exportCommand, importPreimagesCommand, exportPreimagesCommand, - copydbCommand, removedbCommand, dumpCommand, dumpGenesisCommand, diff --git a/eth/downloader/fakepeer.go b/eth/downloader/fakepeer.go deleted file mode 100644 index 3ec90bc9ef..0000000000 --- a/eth/downloader/fakepeer.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" -) - -// FakePeer is a mock downloader peer that operates on a local database instance -// instead of being an actual live node. It's useful for testing and to implement -// sync commands from an existing local database. -type FakePeer struct { - id string - db ethdb.Database - hc *core.HeaderChain - dl *Downloader -} - -// NewFakePeer creates a new mock downloader peer with the given data sources. -func NewFakePeer(id string, db ethdb.Database, hc *core.HeaderChain, dl *Downloader) *FakePeer { - return &FakePeer{id: id, db: db, hc: hc, dl: dl} -} - -// Head implements downloader.Peer, returning the current head hash and number -// of the best known header. -func (p *FakePeer) Head() (common.Hash, *big.Int) { - header := p.hc.CurrentHeader() - return header.Hash(), header.Number -} - -// RequestHeadersByHash implements downloader.Peer, returning a batch of headers -// defined by the origin hash and the associated query parameters. -func (p *FakePeer) RequestHeadersByHash(hash common.Hash, amount int, skip int, reverse bool) error { - var ( - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < amount { - origin := p.hc.GetHeaderByHash(hash) - if origin == nil { - break - } - number := origin.Number.Uint64() - headers = append(headers, origin) - if reverse { - for i := 0; i <= skip; i++ { - if header := p.hc.GetHeader(hash, number); header != nil { - hash = header.ParentHash - number-- - } else { - unknown = true - break - } - } - } else { - var ( - current = origin.Number.Uint64() - next = current + uint64(skip) + 1 - ) - if header := p.hc.GetHeaderByNumber(next); header != nil { - if p.hc.GetBlockHashesFromHash(header.Hash(), uint64(skip+1))[skip] == hash { - hash = header.Hash() - } else { - unknown = true - } - } else { - unknown = true - } - } - } - p.dl.DeliverHeaders(p.id, headers) - return nil -} - -// RequestHeadersByNumber implements downloader.Peer, returning a batch of headers -// defined by the origin number and the associated query parameters. -func (p *FakePeer) RequestHeadersByNumber(number uint64, amount int, skip int, reverse bool) error { - var ( - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < amount { - origin := p.hc.GetHeaderByNumber(number) - if origin == nil { - break - } - if reverse { - if number >= uint64(skip+1) { - number -= uint64(skip + 1) - } else { - unknown = true - } - } else { - number += uint64(skip + 1) - } - headers = append(headers, origin) - } - p.dl.DeliverHeaders(p.id, headers) - return nil -} - -// RequestBodies implements downloader.Peer, returning a batch of block bodies -// corresponding to the specified block hashes. -func (p *FakePeer) RequestBodies(hashes []common.Hash) error { - var ( - txs [][]*types.Transaction - uncles [][]*types.Header - ) - for _, hash := range hashes { - block := rawdb.ReadBlock(p.db, hash, *p.hc.GetBlockNumber(hash)) - - txs = append(txs, block.Transactions()) - uncles = append(uncles, block.Uncles()) - } - p.dl.DeliverBodies(p.id, txs, uncles) - return nil -} - -// RequestReceipts implements downloader.Peer, returning a batch of transaction -// receipts corresponding to the specified block hashes. -func (p *FakePeer) RequestReceipts(hashes []common.Hash) error { - var receipts [][]*types.Receipt - for _, hash := range hashes { - receipts = append(receipts, rawdb.ReadRawReceipts(p.db, hash, *p.hc.GetBlockNumber(hash))) - } - p.dl.DeliverReceipts(p.id, receipts) - return nil -} - -// RequestNodeData implements downloader.Peer, returning a batch of state trie -// nodes corresponding to the specified trie hashes. -func (p *FakePeer) RequestNodeData(hashes []common.Hash) error { - var data [][]byte - for _, hash := range hashes { - if entry, err := p.db.Get(hash.Bytes()); err == nil { - data = append(data, entry) - } - } - p.dl.DeliverNodeData(p.id, data) - return nil -} From bc47993692a446437b0d91cae758fb6be664050e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 16 Mar 2021 09:43:33 +0100 Subject: [PATCH 383/948] tests/fuzzers: fix goroutine leak in les fuzzer (#22455) The oss-fuzz fuzzer has been reporting some failing testcases for les. They're all spurious, and cannot reliably be reproduced. However, running them showed that there was a goroutine leak: the tests created a lot of new clients, which started an exec queue that was never torn down. This PR fixes the goroutine leak, and also a log message which was erroneously formatted. --- les/server_requests.go | 4 ++-- les/test_helper.go | 8 ++++++-- tests/fuzzers/les/les-fuzzer.go | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/les/server_requests.go b/les/server_requests.go index 07f30b1b73..bab5f733d5 100644 --- a/les/server_requests.go +++ b/les/server_requests.go @@ -206,8 +206,8 @@ func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) next = current + r.Query.Skip + 1 ) if next <= current { - infos, _ := json.MarshalIndent(p.Peer.Info(), "", " ") - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", infos) + infos, _ := json.Marshal(p.Peer.Info()) + p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(infos)) unknown = true } else { if header := bc.GetHeaderByNumber(next); header != nil { diff --git a/les/test_helper.go b/les/test_helper.go index 39313b1e3b..e1d3beb6a1 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -661,6 +661,10 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC return s, c, teardown } -func NewFuzzerPeer(version int) *clientPeer { - return newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil) +// NewFuzzerPeer creates a client peer for test purposes, and also returns +// a function to close the peer: this is needed to avoid goroutine leaks in the +// exec queue. +func NewFuzzerPeer(version int) (p *clientPeer, closer func()) { + p = newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil) + return p, func() { p.peerCommons.close() } } diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go index 9e896c2c1b..3e10171873 100644 --- a/tests/fuzzers/les/les-fuzzer.go +++ b/tests/fuzzers/les/les-fuzzer.go @@ -261,18 +261,18 @@ func (d dummyMsg) Decode(val interface{}) error { } func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) { - version := f.randomInt(3) + 2 // [LES2, LES3, LES4] - peer := l.NewFuzzerPeer(version) enc, err := rlp.EncodeToBytes(packet) if err != nil { panic(err) } + version := f.randomInt(3) + 2 // [LES2, LES3, LES4] + peer, closeFn := l.NewFuzzerPeer(version) + defer closeFn() fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc}) if err != nil { panic(err) } fn(f, peer, func() bool { return true }) - } func Fuzz(input []byte) int { From 99830720f681e5120fc131daaf0ad74d329bb074 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Tue, 16 Mar 2021 02:48:54 -0600 Subject: [PATCH 384/948] core/types: improve comments in new EIP-2718 code (#22402) Responding to these comments: https://github.com/ethereum/go-ethereum/pull/21502/files#r579010962 https://github.com/ethereum/go-ethereum/pull/21502/files#r579021565 https://github.com/ethereum/go-ethereum/pull/21502/files#r579023510 https://github.com/ethereum/go-ethereum/pull/21502/files#r578983734 --- core/types/hashing.go | 5 +++-- core/types/hashing_test.go | 16 ++++++++++++++++ core/types/receipt.go | 1 + core/types/transaction_signing.go | 6 +----- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/core/types/hashing.go b/core/types/hashing.go index 71efb25a9a..3227cf8a72 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -36,6 +36,7 @@ var encodeBufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } +// rlpHash encodes x and hashes the encoded bytes. func rlpHash(x interface{}) (h common.Hash) { sha := hasherPool.Get().(crypto.KeccakState) defer hasherPool.Put(sha) @@ -45,8 +46,8 @@ func rlpHash(x interface{}) (h common.Hash) { return h } -// prefixedRlpHash writes the prefix into the hasher before rlp-encoding the -// given interface. It's used for typed transactions. +// prefixedRlpHash writes the prefix into the hasher before rlp-encoding x. +// It's used for typed transactions. func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { sha := hasherPool.Get().(crypto.KeccakState) defer hasherPool.Put(sha) diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index a948b10ef6..6d1ebf897c 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -1,3 +1,19 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package types_test import ( diff --git a/core/types/receipt.go b/core/types/receipt.go index 48f4aef06a..e04259b9d8 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -38,6 +38,7 @@ var ( receiptStatusSuccessfulRLP = []byte{0x01} ) +// This error is returned when a typed receipt is decoded, but the string is empty. var errEmptyTypedReceipt = errors.New("empty typed receipt bytes") const ( diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index b4594cb90b..126748efeb 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -207,11 +207,7 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { switch txdata := tx.inner.(type) { case *LegacyTx: - R, S, V = decodeSignature(sig) - if s.chainId.Sign() != 0 { - V = big.NewInt(int64(sig[64] + 35)) - V.Add(V, s.chainIdMul) - } + return s.EIP155Signer.SignatureValues(tx, sig) case *AccessListTx: // Check that chain ID of tx matches the signer. We also accept ID zero here, // because it indicates that the chain ID was not specified in the tx. From 7076e8e42ff528e0ff058da3e357343dfb6fd71d Mon Sep 17 00:00:00 2001 From: ligi Date: Tue, 16 Mar 2021 09:53:43 +0100 Subject: [PATCH 385/948] cmd/clef: docs - link to ethereum org repo (#22400) --- cmd/clef/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 700f0a144b..27c62817f9 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -919,4 +919,4 @@ There are a couple of implementation for a UI. We'll try to keep this list up to | QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)| | GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: | | Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: | -| Clef UI| https://github.com/kyokan/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)| +| Clef UI| https://github.com/ethereum/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)| From 7cbf1d70a7428fdd247882a840c416c80c30454f Mon Sep 17 00:00:00 2001 From: ligi Date: Tue, 16 Mar 2021 09:55:03 +0100 Subject: [PATCH 386/948] cmd/clef (docs): fix image background (#22399) Flatten the image so we do not have dark text on dark background --- cmd/clef/sign_flow.png | Bin 20753 -> 20537 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/cmd/clef/sign_flow.png b/cmd/clef/sign_flow.png index 93ef81a32e8ee72d68f59dbad9db6b03ac7fb782..e7010ab43f3a6f54d69958055b93b611f7853951 100644 GIT binary patch literal 20537 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 v0&LujY-|E-YI*tr_m0VFN1@TBCCLBRh3B+C&T From 94ab4ea341c34ee692b707928a6da285fab85ea3 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 16 Mar 2021 11:15:14 +0100 Subject: [PATCH 387/948] core/rawdb: fix transaction indexing/unindexing hashing error (#22457) * core/rawdb: more verbose error logs + better hashing * core/rawdb: add failing testcase * core/rawdb: properly hash transactions while indexing/unindexing * core/rawdb: exit on error + better log msg --- core/rawdb/chain_iterator.go | 29 +++--------- core/rawdb/chain_iterator_test.go | 73 ++++++++++++++++++++++++------- rlp/iterator.go | 1 + 3 files changed, 65 insertions(+), 38 deletions(-) diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 862a549540..ad222005be 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -23,10 +23,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) // InitDatabaseFromFreezer reinitializes an empty database from a previous batch @@ -135,32 +135,15 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool close(hashesCh) } }() - - var hasher = sha3.NewLegacyKeccak256() for data := range rlpCh { - it, err := rlp.NewListIterator(data.rlp) - if err != nil { - log.Warn("tx iteration error", "error", err) - return - } - it.Next() - txs := it.Value() - txIt, err := rlp.NewListIterator(txs) - if err != nil { - log.Warn("tx iteration error", "error", err) + var body types.Body + if err := rlp.DecodeBytes(data.rlp, &body); err != nil { + log.Warn("Failed to decode block body", "block", data.number, "error", err) return } var hashes []common.Hash - for txIt.Next() { - if err := txIt.Err(); err != nil { - log.Warn("tx iteration error", "error", err) - return - } - var txHash common.Hash - hasher.Reset() - hasher.Write(txIt.Value()) - hasher.Sum(txHash[:0]) - hashes = append(hashes, txHash) + for _, tx := range body.Transactions { + hashes = append(hashes, tx.Hash()) } result := &blockTxHashes{ hashes: hashes, diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index 90b2639d38..45cc6323e0 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -33,14 +33,34 @@ func TestChainIterator(t *testing.T) { var block *types.Block var txs []*types.Transaction - for i := uint64(0); i <= 10; i++ { - if i == 0 { - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil, newHasher()) // Empty genesis block + to := common.BytesToAddress([]byte{0x11}) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + for i := uint64(1); i <= 10; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = types.NewTx(&types.LegacyTx{ + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) } else { - tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) - txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + tx = types.NewTx(&types.AccessListTx{ + ChainID: big.NewInt(1337), + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) } + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } @@ -66,7 +86,7 @@ func TestChainIterator(t *testing.T) { numbers = append(numbers, int(h.number)) if len(h.hashes) > 0 { if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp { - t.Fatalf("hash wrong, got %x exp %x", got, exp) + t.Fatalf("block %d: hash wrong, got %x exp %x", h.number, got, exp) } } } @@ -88,14 +108,37 @@ func TestIndexTransactions(t *testing.T) { var block *types.Block var txs []*types.Transaction - for i := uint64(0); i <= 10; i++ { - if i == 0 { - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil, newHasher()) // Empty genesis block + to := common.BytesToAddress([]byte{0x11}) + + // Write empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + + for i := uint64(1); i <= 10; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = types.NewTx(&types.LegacyTx{ + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) } else { - tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) - txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + tx = types.NewTx(&types.AccessListTx{ + ChainID: big.NewInt(1337), + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) } + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } @@ -108,10 +151,10 @@ func TestIndexTransactions(t *testing.T) { } number := ReadTxLookupEntry(chainDb, txs[i-1].Hash()) if exist && number == nil { - t.Fatalf("Transaction indice missing") + t.Fatalf("Transaction index %d missing", i) } if !exist && number != nil { - t.Fatalf("Transaction indice is not deleted") + t.Fatalf("Transaction index %d is not deleted", i) } } number := ReadTxIndexTail(chainDb) diff --git a/rlp/iterator.go b/rlp/iterator.go index c28866dbc1..559e03a868 100644 --- a/rlp/iterator.go +++ b/rlp/iterator.go @@ -23,6 +23,7 @@ type listIterator struct { } // NewListIterator creates an iterator for the (list) represented by data +// TODO: Consider removing this implementation, as it is no longer used. func NewListIterator(data RawValue) (*listIterator, error) { k, t, c, err := readKind(data) if err != nil { From 62d8022b513772666d92c3ba45312b61c403fea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 16 Mar 2021 12:53:54 +0100 Subject: [PATCH 388/948] les: fix UDP connection query (#22451) This PR fixes multiple issues with the UDP connection pre-negotiation feature: - the enable condition was wrong (it checked the existence of the DiscV5 struct where it wasn't initialized yet, disabling the feature even if discv5 was enabled) - the server pool queried already connected nodes when the discovery iterators returned them again - servers responded positively before they were synced and really willing to accept connections Metrics are also added on the server side that count the positive and negative replies to served connection queries. --- les/client.go | 18 ++++-- les/clientpool.go | 18 +++++- les/clientpool_test.go | 24 ++++---- les/enr_entry.go | 5 +- les/metrics.go | 10 ++-- les/server.go | 2 +- les/test_helper.go | 6 +- les/vflux/client/serverpool.go | 103 ++++++++++++++++++--------------- 8 files changed, 110 insertions(+), 76 deletions(-) diff --git a/les/client.go b/les/client.go index a557e7ccb3..99f79bb20c 100644 --- a/les/client.go +++ b/les/client.go @@ -73,8 +73,9 @@ type LightEthereum struct { accountManager *accounts.Manager netRPCService *ethapi.PublicNetAPI - p2pServer *p2p.Server - p2pConfig *p2p.Config + p2pServer *p2p.Server + p2pConfig *p2p.Config + udpEnabled bool } // New creates an instance of the light client. @@ -113,10 +114,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), p2pServer: stack.Server(), p2pConfig: &stack.Config().P2P, + udpEnabled: stack.Config().P2P.DiscoveryV5, } var prenegQuery vfc.QueryFunc - if leth.p2pServer.DiscV5 != nil { + if leth.udpEnabled { prenegQuery = leth.prenegQuery } leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) @@ -198,7 +200,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { // VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies { - if s.p2pServer.DiscV5 == nil { + if !s.udpEnabled { return nil } reqsEnc, _ := rlp.EncodeToBytes(&reqs) @@ -215,7 +217,7 @@ func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.R func (s *LightEthereum) vfxVersion(n *enode.Node) uint { if n.Seq() == 0 { var err error - if s.p2pServer.DiscV5 == nil { + if !s.udpEnabled { return 0 } if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { @@ -346,7 +348,11 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { func (s *LightEthereum) Start() error { log.Warn("Light client mode is an experimental feature") - discovery, err := s.setupDiscovery(s.p2pConfig) + if s.udpEnabled && s.p2pServer.DiscV5 == nil { + s.udpEnabled = false + log.Error("Discovery v5 is not initialized") + } + discovery, err := s.setupDiscovery() if err != nil { return err } diff --git a/les/clientpool.go b/les/clientpool.go index 1aa63a281e..3965d54508 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -72,6 +72,7 @@ type clientPool struct { clock mclock.Clock closed bool removePeer func(enode.ID) + synced func() bool ns *nodestate.NodeStateMachine pp *vfs.PriorityPool bt *vfs.BalanceTracker @@ -107,7 +108,7 @@ type clientInfo struct { } // newClientPool creates a new client pool -func newClientPool(ns *nodestate.NodeStateMachine, lesDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { +func newClientPool(ns *nodestate.NodeStateMachine, lesDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID), synced func() bool) *clientPool { pool := &clientPool{ ns: ns, BalanceTrackerSetup: balanceTrackerSetup, @@ -116,6 +117,7 @@ func newClientPool(ns *nodestate.NodeStateMachine, lesDb ethdb.Database, minCap minCap: minCap, connectedBias: connectedBias, removePeer: removePeer, + synced: synced, } pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lesDb, clock, &utils.Expirer{}, &utils.Expirer{}) pool.pp = vfs.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) @@ -396,6 +398,13 @@ func (f *clientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []by if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { return nil } + result := make(vflux.CapacityQueryReply, len(req.AddTokens)) + if !f.synced() { + capacityQueryZeroMeter.Mark(1) + reply, _ := rlp.EncodeToBytes(&result) + return reply + } + node := f.ns.GetNode(id) if node == nil { node = enode.SignNull(&enr.Record{}, id) @@ -416,7 +425,6 @@ func (f *clientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []by } // use vfs.CapacityCurve to answer request for multiple newly bought token amounts curve := f.pp.GetCapacityCurve().Exclude(id) - result := make(vflux.CapacityQueryReply, len(req.AddTokens)) bias := time.Second * time.Duration(req.Bias) if f.connectedBias > bias { bias = f.connectedBias @@ -434,6 +442,12 @@ func (f *clientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []by result[i] = 0 } } + // add first result to metrics (don't care about priority client multi-queries yet) + if result[0] == 0 { + capacityQueryZeroMeter.Mark(1) + } else { + capacityQueryNonZeroMeter.Mark(1) + } reply, _ := rlp.EncodeToBytes(&result) return reply } diff --git a/les/clientpool_test.go b/les/clientpool_test.go index 345b373b0f..2aee444545 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -133,7 +133,7 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando disconnFn = func(id enode.ID) { disconnCh <- int(id[0]) + int(id[1])<<8 } - pool = newClientPool(testStateMachine(), db, 1, 0, &clock, disconnFn) + pool = newClientPool(testStateMachine(), db, 1, 0, &clock, disconnFn, alwaysTrueFn) ) pool.ns.Start() @@ -239,7 +239,7 @@ func TestConnectPaidClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) @@ -255,7 +255,7 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -274,7 +274,7 @@ func TestConnectPaidClientToFullPool(t *testing.T) { db = rawdb.NewMemoryDatabase() ) removeFn := func(enode.ID) {} // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -304,7 +304,7 @@ func TestPaidClientKickedOut(t *testing.T) { removeFn := func(id enode.ID) { kickedCh <- int(id[0]) } - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) pool.ns.Start() pool.bt.SetExpirationTCs(0, 0) defer pool.stop() @@ -335,7 +335,7 @@ func TestConnectFreeClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) @@ -352,7 +352,7 @@ func TestConnectFreeClientToFullPool(t *testing.T) { db = rawdb.NewMemoryDatabase() ) removeFn := func(enode.ID) {} // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -382,7 +382,7 @@ func TestFreeClientKickedOut(t *testing.T) { kicked = make(chan int, 100) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -424,7 +424,7 @@ func TestPositiveBalanceCalculation(t *testing.T) { kicked = make(chan int, 10) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -448,7 +448,7 @@ func TestDowngradePriorityClient(t *testing.T) { kicked = make(chan int, 10) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -483,7 +483,7 @@ func TestNegativeBalanceCalculation(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -521,7 +521,7 @@ func TestInactiveClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) pool.ns.Start() defer pool.stop() pool.setLimits(2, uint64(2)) diff --git a/les/enr_entry.go b/les/enr_entry.go index 8be4a7a00e..892c3530d3 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -18,7 +18,6 @@ package les import ( "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" @@ -42,7 +41,7 @@ type ethEntry struct { func (ethEntry) ENRKey() string { return "eth" } // setupDiscovery creates the node discovery source for the eth protocol. -func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { +func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { it := enode.NewFairMix(0) // Enable DNS discovery. @@ -56,7 +55,7 @@ func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error } // Enable DHT. - if cfg.DiscoveryV5 && eth.p2pServer.DiscV5 != nil { + if eth.udpEnabled { it.AddSource(eth.p2pServer.DiscV5.RandomNodes()) } diff --git a/les/metrics.go b/les/metrics.go index 9a79fd1bbd..5a8d4bbe02 100644 --- a/les/metrics.go +++ b/les/metrics.go @@ -73,10 +73,12 @@ var ( serverConnectionGauge = metrics.NewRegisteredGauge("les/connection/server", nil) clientConnectionGauge = metrics.NewRegisteredGauge("les/connection/client", nil) - totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil) - totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil) - totalConnectedGauge = metrics.NewRegisteredGauge("les/server/totalConnected", nil) - blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil) + totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil) + totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil) + totalConnectedGauge = metrics.NewRegisteredGauge("les/server/totalConnected", nil) + blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil) + capacityQueryZeroMeter = metrics.NewRegisteredMeter("les/server/capQueryZero", nil) + capacityQueryNonZeroMeter = metrics.NewRegisteredMeter("les/server/capQueryNonZero", nil) requestServedMeter = metrics.NewRegisteredMeter("les/server/req/avgServedTime", nil) requestServedTimer = metrics.NewRegisteredTimer("les/server/req/servedTime", nil) diff --git a/les/server.go b/les/server.go index 9627d65afa..0351bdd801 100644 --- a/les/server.go +++ b/les/server.go @@ -149,7 +149,7 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les srv.maxCapacity = totalRecharge } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) - srv.clientPool = newClientPool(ns, lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) + srv.clientPool = newClientPool(ns, lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient, issync) srv.clientPool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() diff --git a/les/test_helper.go b/les/test_helper.go index e1d3beb6a1..e49bfc8738 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -307,7 +307,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } server.costTracker, server.minCapacity = newCostTracker(db, server.config) server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism. - server.clientPool = newClientPool(ns, db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {}) + server.clientPool = newClientPool(ns, db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {}, alwaysTrueFn) server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) if server.oracle != nil { @@ -319,6 +319,10 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da return server.handler, simulation } +func alwaysTrueFn() bool { + return true +} + // testPeer is a simulated peer to allow testing direct network calls. type testPeer struct { cpeer *clientPeer diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go index e73b277ced..cc0254c127 100644 --- a/les/vflux/client/serverpool.go +++ b/les/vflux/client/serverpool.go @@ -47,7 +47,8 @@ const ( nodeWeightThreshold = 100 // minimum weight for keeping a node in the the known (valuable) set minRedialWait = 10 // minimum redial wait time in seconds preNegLimit = 5 // maximum number of simultaneous pre-negotiation queries - maxQueryFails = 100 // number of consecutive UDP query failures before we print a warning + warnQueryFails = 20 // number of consecutive UDP query failures before we print a warning + maxQueryFails = 100 // number of consecutive UDP query failures when then chance of skipping a query reaches 50% ) // ServerPool provides a node iterator for dial candidates. The output is a mix of newly discovered @@ -94,16 +95,16 @@ type nodeHistoryEnc struct { type QueryFunc func(*enode.Node) int var ( - clientSetup = &nodestate.Setup{Version: 2} - sfHasValue = clientSetup.NewPersistentFlag("hasValue") - sfQueried = clientSetup.NewFlag("queried") - sfCanDial = clientSetup.NewFlag("canDial") - sfDialing = clientSetup.NewFlag("dialed") - sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout") - sfConnected = clientSetup.NewFlag("connected") - sfRedialWait = clientSetup.NewFlag("redialWait") - sfAlwaysConnect = clientSetup.NewFlag("alwaysConnect") - sfDisableSelection = nodestate.MergeFlags(sfQueried, sfCanDial, sfDialing, sfConnected, sfRedialWait) + clientSetup = &nodestate.Setup{Version: 2} + sfHasValue = clientSetup.NewPersistentFlag("hasValue") + sfQuery = clientSetup.NewFlag("query") + sfCanDial = clientSetup.NewFlag("canDial") + sfDialing = clientSetup.NewFlag("dialed") + sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout") + sfConnected = clientSetup.NewFlag("connected") + sfRedialWait = clientSetup.NewFlag("redialWait") + sfAlwaysConnect = clientSetup.NewFlag("alwaysConnect") + sfDialProcess = nodestate.MergeFlags(sfQuery, sfCanDial, sfDialing, sfConnected, sfRedialWait) sfiNodeHistory = clientSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}), func(field interface{}) ([]byte, error) { @@ -162,8 +163,8 @@ func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duratio } s.recalTimeout() s.mixer = enode.NewFairMix(mixTimeout) - knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDisableSelection, sfiNodeWeight) - alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDisableSelection, true, nil) + knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDialProcess, sfiNodeWeight) + alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDialProcess, true, nil) s.mixSources = append(s.mixSources, knownSelector) s.mixSources = append(s.mixSources, alwaysConnect) @@ -226,7 +227,7 @@ func (s *ServerPool) AddMetrics( s.totalValueGauge = totalValueGauge s.sessionValueMeter = sessionValueMeter if serverSelectableGauge != nil { - s.ns.AddLogMetrics(sfHasValue, sfDisableSelection, "selectable", nil, nil, serverSelectableGauge) + s.ns.AddLogMetrics(sfHasValue, sfDialProcess, "selectable", nil, nil, serverSelectableGauge) } if serverDialedMeter != nil { s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil) @@ -247,43 +248,51 @@ func (s *ServerPool) AddSource(source enode.Iterator) { // Nodes that are filtered out and does not appear on the output iterator are put back // into redialWait state. func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enode.Iterator { - s.fillSet = NewFillSet(s.ns, input, sfQueried) - s.ns.SubscribeState(sfQueried, func(n *enode.Node, oldState, newState nodestate.Flags) { - if newState.Equals(sfQueried) { - fails := atomic.LoadUint32(&s.queryFails) - if fails == maxQueryFails { - log.Warn("UDP pre-negotiation query does not seem to work") + s.fillSet = NewFillSet(s.ns, input, sfQuery) + s.ns.SubscribeState(sfDialProcess, func(n *enode.Node, oldState, newState nodestate.Flags) { + if !newState.Equals(sfQuery) { + if newState.HasAll(sfQuery) { + // remove query flag if the node is already somewhere in the dial process + s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) } - if fails > maxQueryFails { - fails = maxQueryFails - } - if rand.Intn(maxQueryFails*2) < int(fails) { - // skip pre-negotiation with increasing chance, max 50% - // this ensures that the client can operate even if UDP is not working at all - s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) - // set canDial before resetting queried so that FillSet will not read more - // candidates unnecessarily - s.ns.SetStateSub(n, nodestate.Flags{}, sfQueried, 0) - return + return + } + fails := atomic.LoadUint32(&s.queryFails) + failMax := fails + if failMax > maxQueryFails { + failMax = maxQueryFails + } + if rand.Intn(maxQueryFails*2) < int(failMax) { + // skip pre-negotiation with increasing chance, max 50% + // this ensures that the client can operate even if UDP is not working at all + s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) + // set canDial before resetting queried so that FillSet will not read more + // candidates unnecessarily + s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) + return + } + go func() { + q := query(n) + if q == -1 { + atomic.AddUint32(&s.queryFails, 1) + fails++ + if fails%warnQueryFails == 0 { + // warn if a large number of consecutive queries have failed + log.Warn("UDP connection queries failed", "count", fails) + } + } else { + atomic.StoreUint32(&s.queryFails, 0) } - go func() { - q := query(n) - if q == -1 { - atomic.AddUint32(&s.queryFails, 1) + s.ns.Operation(func() { + // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait + if q == 1 { + s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) } else { - atomic.StoreUint32(&s.queryFails, 0) + s.setRedialWait(n, queryCost, queryWaitStep) } - s.ns.Operation(func() { - // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait - if q == 1 { - s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) - } else { - s.setRedialWait(n, queryCost, queryWaitStep) - } - s.ns.SetStateSub(n, nodestate.Flags{}, sfQueried, 0) - }) - }() - } + s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) + }) + }() }) return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { if waiting { From 91726e8aadeebc4bda8f136aebf4b5c7da8b9d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 16 Mar 2021 12:55:43 +0100 Subject: [PATCH 390/948] les: allow either full enode strings or raw hex ids in the API (#22423) --- les/api.go | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/les/api.go b/les/api.go index 6491c4dcc4..a930524516 100644 --- a/les/api.go +++ b/les/api.go @@ -49,6 +49,18 @@ func NewPrivateLightServerAPI(server *LesServer) *PrivateLightServerAPI { } } +// parseNode parses either an enode address a raw hex node id +func parseNode(node string) (enode.ID, error) { + if id, err := enode.ParseID(node); err == nil { + return id, nil + } + if node, err := enode.Parse(enode.ValidSchemes, node); err == nil { + return node.ID(), nil + } else { + return enode.ID{}, err + } +} + // ServerInfo returns global server parameters func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} { res := make(map[string]interface{}) @@ -59,7 +71,14 @@ func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} { } // ClientInfo returns information about clients listed in the ids list or matching the given tags -func (api *PrivateLightServerAPI) ClientInfo(ids []enode.ID) map[enode.ID]map[string]interface{} { +func (api *PrivateLightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]interface{} { + var ids []enode.ID + for _, node := range nodes { + if id, err := parseNode(node); err == nil { + ids = append(ids, id) + } + } + res := make(map[enode.ID]map[string]interface{}) api.server.clientPool.forClients(ids, func(client *clientInfo) { res[client.node.ID()] = api.clientInfo(client) @@ -159,8 +178,18 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien // SetClientParams sets client parameters for all clients listed in the ids list // or all connected clients if the list is empty -func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[string]interface{}) error { - var err error +func (api *PrivateLightServerAPI) SetClientParams(nodes []string, params map[string]interface{}) error { + var ( + ids []enode.ID + err error + ) + for _, node := range nodes { + if id, err := parseNode(node); err != nil { + return err + } else { + ids = append(ids, id) + } + } api.server.clientPool.forClients(ids, func(client *clientInfo) { if client.connected { posFactors, negFactors := client.balance.GetPriceFactors() @@ -201,7 +230,11 @@ func (api *PrivateLightServerAPI) SetConnectedBias(bias time.Duration) error { // AddBalance adds the given amount to the balance of a client if possible and returns // the balance before and after the operation -func (api *PrivateLightServerAPI) AddBalance(id enode.ID, amount int64) (balance [2]uint64, err error) { +func (api *PrivateLightServerAPI) AddBalance(node string, amount int64) (balance [2]uint64, err error) { + var id enode.ID + if id, err = parseNode(node); err != nil { + return + } api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { balance[0], balance[1], err = c.balance.AddBalance(amount) }) @@ -297,8 +330,14 @@ func NewPrivateDebugAPI(server *LesServer) *PrivateDebugAPI { } // FreezeClient forces a temporary client freeze which normally happens when the server is overloaded -func (api *PrivateDebugAPI) FreezeClient(id enode.ID) error { - var err error +func (api *PrivateDebugAPI) FreezeClient(node string) error { + var ( + id enode.ID + err error + ) + if id, err = parseNode(node); err != nil { + return err + } api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { if c.connected { c.peer.freeze() From 410089afea1907d4abbf1dc4d1b42bda1094a5e5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 17 Mar 2021 09:36:34 +0100 Subject: [PATCH 391/948] eth/protocols/snap, eth/downloader: don't use bloom filter in snap sync --- eth/downloader/downloader.go | 2 +- eth/handler.go | 6 +++++- eth/protocols/snap/sync.go | 13 ++++--------- eth/protocols/snap/sync_test.go | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 5ddd2f9848..a5ed3761b1 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -240,7 +240,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, headerProcCh: make(chan []*types.Header, 1), quitCh: make(chan struct{}), stateCh: make(chan dataPack), - SnapSyncer: snap.NewSyncer(stateDb, stateBloom), + SnapSyncer: snap.NewSyncer(stateDb), stateSyncStart: make(chan *stateSync), syncStatsState: stateSyncStats{ processed: rawdb.ReadFastTrieProgress(stateDb), diff --git a/eth/handler.go b/eth/handler.go index 13fa701935..11c8565de1 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -177,7 +177,11 @@ func newHandler(config *handlerConfig) (*handler, error) { // Construct the downloader (long sync) and its backing state bloom if fast // sync is requested. The downloader is responsible for deallocating the state // bloom when it's done. - if atomic.LoadUint32(&h.fastSync) == 1 { + // Note: we don't enable it if snap-sync is performed, since it's very heavy + // and the heal-portion of the snap sync is much lighter than fast. What we particularly + // want to avoid, is a 90%-finished (but restarted) snap-sync to begin + // indexing the entire trie + if atomic.LoadUint32(&h.fastSync) == 1 && atomic.LoadUint32(&h.snapSync) == 0 { h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database) } h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 1cfdef15bd..0303e65eda 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -376,8 +376,7 @@ type SyncPeer interface { // - The peer delivers a stale response after a previous timeout // - The peer delivers a refusal to serve the requested state type Syncer struct { - db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) - bloom *trie.SyncBloom // Bloom filter to deduplicate nodes for state fixup + db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup) root common.Hash // Current state trie root being synced tasks []*accountTask // Current account task set being synced @@ -446,10 +445,9 @@ type Syncer struct { // NewSyncer creates a new snapshot syncer to download the Ethereum state over the // snap protocol. -func NewSyncer(db ethdb.KeyValueStore, bloom *trie.SyncBloom) *Syncer { +func NewSyncer(db ethdb.KeyValueStore) *Syncer { return &Syncer{ - db: db, - bloom: bloom, + db: db, peers: make(map[string]SyncPeer), peerJoin: new(event.Feed), @@ -546,7 +544,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { s.lock.Lock() s.root = root s.healer = &healTask{ - scheduler: state.NewStateSync(root, s.db, s.bloom), + scheduler: state.NewStateSync(root, s.db, nil), trieTasks: make(map[common.Hash]trie.SyncPath), codeTasks: make(map[common.Hash]struct{}), } @@ -1660,7 +1658,6 @@ func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { bytes += common.StorageSize(len(code)) rawdb.WriteCode(batch, hash, code) - s.bloom.Add(hash[:]) } if err := batch.Write(); err != nil { log.Crit("Failed to persist bytecodes", "err", err) @@ -1796,7 +1793,6 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { } // Node is not a boundary, persist to disk batch.Put(it.Key(), it.Value()) - s.bloom.Add(it.Key()) bytes += common.StorageSize(common.HashLength + len(it.Value())) nodes++ @@ -1953,7 +1949,6 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { } // Node is neither a boundary, not an incomplete account, persist to disk batch.Put(it.Key(), it.Value()) - s.bloom.Add(it.Key()) bytes += common.StorageSize(common.HashLength + len(it.Value())) nodes++ diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 0b048786e8..49dff7bb3a 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -525,7 +525,7 @@ func TestSyncBloatedProof(t *testing.T) { func setupSyncer(peers ...*testPeer) *Syncer { stateDb := rawdb.NewMemoryDatabase() - syncer := NewSyncer(stateDb, trie.NewSyncBloom(1, stateDb)) + syncer := NewSyncer(stateDb) for _, peer := range peers { syncer.Register(peer) peer.remote = syncer From 117fa7d4a1ac0211b6a59aac5f3c5f7e5890e053 Mon Sep 17 00:00:00 2001 From: wuff1996 <33193253+wuff1996@users.noreply.github.com> Date: Fri, 19 Mar 2021 15:49:24 +0800 Subject: [PATCH 392/948] eth/protocols/snap: fix typo (#22530) --- eth/protocols/snap/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 24c8599552..b9515b8a39 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -115,7 +115,7 @@ func handle(backend Backend, peer *Peer) error { } // handleMessage is invoked whenever an inbound message is received from a -// remote peer on the `spap` protocol. The remote connection is torn down upon +// remote peer on the `snap` protocol. The remote connection is torn down upon // returning any error. func handleMessage(backend Backend, peer *Peer) error { // Read the next message from the remote peer, and ensure it's fully consumed From 6a528fce3357176e2da2fe07b379842f596bb566 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 19 Mar 2021 09:57:23 +0000 Subject: [PATCH 393/948] cmd/devp2p/internal/ethtest: return request ID in BlockHeaders response (#22508) This PR fixes an issue with the eth66 test suite where, during a readAndServe when the test is manually responding to GetBlockHeader requests, it now responds with a BlockHeaders eth66 packet that includes the inbound request ID. --- cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go index b7fa1dce26..a3f1aaa4f8 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -141,8 +141,11 @@ func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Mess if err != nil { return 0, errorf("could not get headers for inbound header request: %v", err) } - - if err := c.Write(headers); err != nil { + resp := ð.BlockHeadersPacket66{ + RequestId: reqID, + BlockHeadersPacket: eth.BlockHeadersPacket(headers), + } + if err := c.write66(resp, BlockHeaders{}.Code()); err != nil { return 0, errorf("could not write to connection: %v", err) } default: From aa8b2189c6ad5148377076b3fab7ca5e0d8c0b62 Mon Sep 17 00:00:00 2001 From: meowsbits Date: Fri, 19 Mar 2021 05:14:23 -0500 Subject: [PATCH 394/948] ethclient: fix error handling for header test (#22514) The wantErr field was disused, and the error returned by HeaderByNumber was not properly tested. This simplifies the error checking using errors.Is and asserts that getting an expected missing header returns ethereum.NotFound. Also adds a nil check condition for header.Number before using big.Int's Sign method. --- ethclient/ethclient_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 9a5a45e34f..9fa5bf87a4 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -288,8 +288,9 @@ func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { want: chain[1].Header(), }, "future_block": { - block: big.NewInt(1000000000), - want: nil, + block: big.NewInt(1000000000), + want: nil, + wantErr: ethereum.NotFound, }, } for name, tt := range tests { @@ -299,10 +300,10 @@ func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { defer cancel() got, err := ec.HeaderByNumber(ctx, tt.block) - if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) { + if !errors.Is(err, tt.wantErr) { t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr) } - if got != nil && got.Number.Sign() == 0 { + if got != nil && got.Number != nil && got.Number.Sign() == 0 { got.Number = big.NewInt(0) // hack to make DeepEqual work } if !reflect.DeepEqual(got, tt.want) { From 38ea7f2cf460fb12a5769f8c0767726334111b99 Mon Sep 17 00:00:00 2001 From: Martin Redmond <21436+reds@users.noreply.github.com> Date: Fri, 19 Mar 2021 06:56:10 -0400 Subject: [PATCH 395/948] accounts/abi/bind: add NoSend transact option (#22446) This adds a new option to avoid sending the transaction which is created by calling a bound contract method. --- accounts/abi/bind/base.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index f5a6fe22fc..55aca31a1a 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -54,6 +54,8 @@ type TransactOpts struct { GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) + + NoSend bool // Do all transact steps but do not send the transaction } // FilterOpts is the collection of options to fine tune filtering for events @@ -260,6 +262,9 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if err != nil { return nil, err } + if opts.NoSend { + return signedTx, nil + } if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { return nil, err } From a90861ae0c2a5d294fb4abb46eac65181d560264 Mon Sep 17 00:00:00 2001 From: ucwong Date: Fri, 19 Mar 2021 18:58:12 +0800 Subject: [PATCH 396/948] go.mod: upgrade goleveldb to commit 64b5b1c (#22436) This pulls in a fix for a corruption issue when the process crashes while a new manifest file is being added. --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 96a217ed06..48259118ac 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/shirou/gopsutil v2.20.5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/stretchr/testify v1.7.0 - github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca + github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c diff --git a/go.sum b/go.sum index af76759c51..7dac5e874e 100644 --- a/go.sum +++ b/go.sum @@ -431,8 +431,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= +github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= From 345890a558257a76acb92a3ae88fbfb0dfea4520 Mon Sep 17 00:00:00 2001 From: ucwong Date: Fri, 19 Mar 2021 19:03:33 +0800 Subject: [PATCH 397/948] go.mod: upgrade goupnp to commit 0ca76305 (#22479) This pulls in a fix to skip the broadcast on interfaces which are down. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 48259118ac..3addd54005 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 github.com/holiman/uint256 v1.1.1 - github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 + github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e diff --git a/go.sum b/go.sum index 7dac5e874e..82990d1a9e 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,8 @@ github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031 h1:HarGZ5h9HD9LgEg1yRVMXyfiw4wlXiLiYM2oMjeA/SE= -github.com/huin/goupnp v1.0.1-0.20200620063722-49508fba0031/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= +github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 h1:bcAj8KroPf552TScjFPIakjH2/tdIrIH8F+cc4v4SRo= +github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= From d50e9d24be6ae410af7b5975f453456367a7b28c Mon Sep 17 00:00:00 2001 From: jacksoom Date: Fri, 19 Mar 2021 19:04:15 +0800 Subject: [PATCH 398/948] consensus/ethash: remove unnecessary variable definition (#22512) --- consensus/ethash/ethash.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 550d99893d..1afdc9381a 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -537,7 +537,6 @@ func NewShared() *Ethash { // Close closes the exit channel to notify all backend threads exiting. func (ethash *Ethash) Close() error { - var err error ethash.closeOnce.Do(func() { // Short circuit if the exit channel is not allocated. if ethash.remote == nil { @@ -546,7 +545,7 @@ func (ethash *Ethash) Close() error { close(ethash.remote.requestExit) <-ethash.remote.exitCh }) - return err + return nil } // cache tries to retrieve a verification cache for the specified block number From e3a3f7cd6472f692f4fad97a302d09fb60f53430 Mon Sep 17 00:00:00 2001 From: Quest Henkart Date: Fri, 19 Mar 2021 06:15:57 -0600 Subject: [PATCH 399/948] cmd/devp2p: use AWS-SDK v2 (#22360) This updates the DNS deployer to use AWS SDK v2. Migration is relatively seamless, although there were two locations that required a slightly different approach to achieve the same results. In particular, waiting for DNS change propagation is very different with SDK v2. This change also optimizes DNS updates by publishing all changes before waiting for propagation. --- cmd/devp2p/dns_route53.go | 136 +++++++++++++++++++++------------ cmd/devp2p/dns_route53_test.go | 72 ++++++++--------- go.mod | 7 +- go.sum | 25 ++++++ 4 files changed, 153 insertions(+), 87 deletions(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index c5f99529b8..5f534ff9f7 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -17,16 +17,19 @@ package main import ( + "context" "errors" "fmt" "sort" "strconv" "strings" + "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "gopkg.in/urfave/cli.v1" @@ -38,6 +41,7 @@ const ( // https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets route53ChangeSizeLimit = 32000 route53ChangeCountLimit = 1000 + maxRetryLimit = 60 ) var ( @@ -58,7 +62,7 @@ var ( ) type route53Client struct { - api *route53.Route53 + api *route53.Client zoneID string } @@ -74,13 +78,13 @@ func newRoute53Client(ctx *cli.Context) *route53Client { if akey == "" || asec == "" { exit(fmt.Errorf("need Route53 Access Key ID and secret proceed")) } - config := &aws.Config{Credentials: credentials.NewStaticCredentials(akey, asec, "")} - session, err := session.NewSession(config) + creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, "")) + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds)) if err != nil { - exit(fmt.Errorf("can't create AWS session: %v", err)) + exit(fmt.Errorf("can't initialize AWS configuration: %v", err)) } return &route53Client{ - api: route53.New(session), + api: route53.NewFromConfig(cfg), zoneID: ctx.String(route53ZoneIDFlag.Name), } } @@ -105,25 +109,43 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { return nil } - // Submit change batches. + // Submit all change batches. batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) + changesToCheck := make([]*route53.ChangeResourceRecordSetsOutput, len(batches)) for i, changes := range batches { log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) - batch := new(route53.ChangeBatch) - batch.SetChanges(changes) - batch.SetComment(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq())) + batch := &types.ChangeBatch{ + Changes: changes, + Comment: aws.String(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq())), + } req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch} - resp, err := c.api.ChangeResourceRecordSets(req) + changesToCheck[i], err = c.api.ChangeResourceRecordSets(context.TODO(), req) if err != nil { return err } + } - log.Info(fmt.Sprintf("Waiting for change request %s", *resp.ChangeInfo.Id)) - wreq := &route53.GetChangeInput{Id: resp.ChangeInfo.Id} - if err := c.api.WaitUntilResourceRecordSetsChanged(wreq); err != nil { - return err + // wait for all change batches to propagate + for _, change := range changesToCheck { + log.Info(fmt.Sprintf("Waiting for change request %s", *change.ChangeInfo.Id)) + wreq := &route53.GetChangeInput{Id: change.ChangeInfo.Id} + var count int + for { + wresp, err := c.api.GetChange(context.TODO(), wreq) + if err != nil { + return err + } + + count++ + + if wresp.ChangeInfo.Status == types.ChangeStatusInsync || count >= maxRetryLimit { + break + } + + time.Sleep(30 * time.Second) } } + return nil } @@ -140,7 +162,7 @@ func (c *route53Client) findZoneID(name string) (string, error) { log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name)) var req route53.ListHostedZonesByNameInput for { - resp, err := c.api.ListHostedZonesByName(&req) + resp, err := c.api.ListHostedZonesByName(context.TODO(), &req) if err != nil { return "", err } @@ -149,7 +171,7 @@ func (c *route53Client) findZoneID(name string) (string, error) { return *zone.Id, nil } } - if !*resp.IsTruncated { + if !resp.IsTruncated { break } req.DNSName = resp.NextDNSName @@ -159,7 +181,7 @@ func (c *route53Client) findZoneID(name string) (string, error) { } // computeChanges creates DNS changes for the given record. -func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []*route53.Change { +func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []types.Change { // Convert all names to lowercase. lrecords := make(map[string]string, len(records)) for name, r := range records { @@ -167,7 +189,7 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e } records = lrecords - var changes []*route53.Change + var changes []types.Change for path, val := range records { ttl := int64(rootTTL) if path != name { @@ -204,21 +226,21 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e } // sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order. -func sortChanges(changes []*route53.Change) { +func sortChanges(changes []types.Change) { score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3} sort.Slice(changes, func(i, j int) bool { - if *changes[i].Action == *changes[j].Action { + if changes[i].Action == changes[j].Action { return *changes[i].ResourceRecordSet.Name < *changes[j].ResourceRecordSet.Name } - return score[*changes[i].Action] < score[*changes[j].Action] + return score[string(changes[i].Action)] < score[string(changes[j].Action)] }) } // splitChanges splits up DNS changes such that each change batch // is smaller than the given RDATA limit. -func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*route53.Change { +func splitChanges(changes []types.Change, sizeLimit, countLimit int) [][]types.Change { var ( - batches [][]*route53.Change + batches [][]types.Change batchSize int batchCount int ) @@ -241,7 +263,7 @@ func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*rou } // changeSize returns the RDATA size of a DNS change. -func changeSize(ch *route53.Change) int { +func changeSize(ch types.Change) int { size := 0 for _, rr := range ch.ResourceRecordSet.ResourceRecords { if rr.Value != nil { @@ -251,8 +273,8 @@ func changeSize(ch *route53.Change) int { return size } -func changeCount(ch *route53.Change) int { - if *ch.Action == "UPSERT" { +func changeCount(ch types.Change) int { + if ch.Action == types.ChangeActionUpsert { return 2 } return 1 @@ -262,13 +284,19 @@ func changeCount(ch *route53.Change) int { func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) { log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID)) var req route53.ListResourceRecordSetsInput - req.SetHostedZoneId(c.zoneID) + req.HostedZoneId = &c.zoneID existing := make(map[string]recordSet) - err := c.api.ListResourceRecordSetsPages(&req, func(resp *route53.ListResourceRecordSetsOutput, last bool) bool { + for { + resp, err := c.api.ListResourceRecordSets(context.TODO(), &req) + if err != nil { + return existing, err + } + for _, set := range resp.ResourceRecordSets { - if !isSubdomain(*set.Name, name) || *set.Type != "TXT" { + if !isSubdomain(*set.Name, name) || set.Type != types.RRTypeTxt { continue } + s := recordSet{ttl: *set.TTL} for _, rec := range set.ResourceRecords { s.values = append(s.values, *rec.Value) @@ -276,28 +304,38 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error name := strings.TrimSuffix(*set.Name, ".") existing[name] = s } - return true - }) - return existing, err + + if !resp.IsTruncated { + break + } + + // sets the cursor to the next batch + req.StartRecordIdentifier = resp.NextRecordIdentifier + } + + return existing, nil } // newTXTChange creates a change to a TXT record. -func newTXTChange(action, name string, ttl int64, values ...string) *route53.Change { - var c route53.Change - var r route53.ResourceRecordSet - var rrs []*route53.ResourceRecord +func newTXTChange(action, name string, ttl int64, values ...string) types.Change { + r := types.ResourceRecordSet{ + Type: types.RRTypeTxt, + Name: &name, + TTL: &ttl, + } + var rrs []types.ResourceRecord for _, val := range values { - rr := new(route53.ResourceRecord) - rr.SetValue(val) + var rr types.ResourceRecord + rr.Value = aws.String(val) rrs = append(rrs, rr) } - r.SetType("TXT") - r.SetName(name) - r.SetTTL(ttl) - r.SetResourceRecords(rrs) - c.SetAction(action) - c.SetResourceRecordSet(&r) - return &c + + r.ResourceRecords = rrs + + return types.Change{ + Action: types.ChangeAction(action), + ResourceRecordSet: &r, + } } // isSubdomain returns true if name is a subdomain of domain. diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go index a2ef3791f6..600c281a28 100644 --- a/cmd/devp2p/dns_route53_test.go +++ b/cmd/devp2p/dns_route53_test.go @@ -20,7 +20,7 @@ import ( "reflect" "testing" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" ) // This test checks that computeChanges/splitChanges create DNS changes in @@ -43,93 +43,93 @@ func TestRoute53ChangeSort(t *testing.T) { "MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", } - wantChanges := []*route53.Change{ + wantChanges := []types.Change{ { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("2xs2367yhaxjfglzhvawlqd4zy.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("c7hrfpf3blgf3yr4dy5kx3smbe.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("h4fht4b454p6uxfd7jcyq5pwdy.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("jwxydbpxywg6fx3gmdibfa6cj4.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("mhtdo6tmubria2xwg5ludack24.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("UPSERT"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "UPSERT", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`), }}, TTL: ip(rootTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("DELETE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "DELETE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("2kfjogvxdqtxxugbh7gs7naaai.n"), - ResourceRecords: []*route53.ResourceRecord{ + ResourceRecords: []types.ResourceRecord{ {Value: sp(`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`)}, }, TTL: ip(3333), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("DELETE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "DELETE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("fdxn3sn67na5dka4j2gok7bvqi.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree-branch:"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, } @@ -141,7 +141,7 @@ func TestRoute53ChangeSort(t *testing.T) { } // Check splitting according to size. - wantSplit := [][]*route53.Change{ + wantSplit := [][]types.Change{ wantChanges[:4], wantChanges[4:6], wantChanges[6:], @@ -152,7 +152,7 @@ func TestRoute53ChangeSort(t *testing.T) { } // Check splitting according to count. - wantSplit = [][]*route53.Change{ + wantSplit = [][]types.Change{ wantChanges[:5], wantChanges[5:], } diff --git a/go.mod b/go.mod index 3addd54005..dc020f9a65 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,14 @@ module github.com/ethereum/go-ethereum -go 1.13 +go 1.15 require ( github.com/Azure/azure-storage-blob-go v0.7.0 github.com/VictoriaMetrics/fastcache v1.5.7 - github.com/aws/aws-sdk-go v1.25.48 + github.com/aws/aws-sdk-go-v2 v1.2.0 + github.com/aws/aws-sdk-go-v2/config v1.1.1 + github.com/aws/aws-sdk-go-v2/credentials v1.1.1 + github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 diff --git a/go.sum b/go.sum index 82990d1a9e..813aa8e27d 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,24 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v1.2.0 h1:BS+UYpbsElC82gB+2E2jiCBg36i8HlubTB/dO/moQ9c= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2/config v1.1.1 h1:ZAoq32boMzcaTW9bcUacBswAmHTbvlvDJICgHFZuECo= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1 h1:NbvWIM1Mx6sNPTxowHgS2ewXCRp+NGTzUYb/96FZJbY= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2 h1:EtEU7WRaWliitZh2nmuxEXrN0Cb8EgPUFGIoTMeqbzI= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2 h1:4AH9fFjUlVktQMznF+YN33aWNXaR4VgDXyP28qokJC0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 h1:cKr6St+CtC3/dl/rEBJvlk7A/IN5D5F02GNkGzfbtVU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 h1:37QubsarExl5ZuCBlnRP+7l1tNwZPBSTqpTBrPH98RU= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 h1:TJoIfnIFubCX0ACVeJ0w46HEH5MwjwYN4iFhuYIhfIY= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0 h1:D6CSsM3gdxaGaqXnPgOBCeL6Mophqzu7KJOu7zW78sU= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -193,6 +211,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -265,6 +286,10 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= From 5bc0343ed3de7dd491edf42f9714ad6b93930408 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 19 Mar 2021 13:20:27 +0100 Subject: [PATCH 400/948] p2p/dnsdisc: fix flaw in dns size calculation (#22533) This fixes the calculation of the tree branch factor. With the new formula, we now creat at most 13 children instead of 30, ensuring the TXT record size will be below 370 bytes. --- p2p/dnsdisc/tree.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go index 82a935ca41..410ec3b854 100644 --- a/p2p/dnsdisc/tree.go +++ b/p2p/dnsdisc/tree.go @@ -113,10 +113,41 @@ func (t *Tree) Nodes() []*enode.Node { return nodes } +/* +We want to keep the UDP size below 512 bytes. The UDP size is roughly: +UDP length = 8 + UDP payload length ( 229 ) +UPD Payload length: + - dns.id 2 + - dns.flags 2 + - dns.count.queries 2 + - dns.count.answers 2 + - dns.count.auth_rr 2 + - dns.count.add_rr 2 + - queries (query-size + 6) + - answers : + - dns.resp.name 2 + - dns.resp.type 2 + - dns.resp.class 2 + - dns.resp.ttl 4 + - dns.resp.len 2 + - dns.txt.length 1 + - dns.txt resp_data_size + +So the total size is roughly a fixed overhead of `39`, and the size of the +query (domain name) and response. +The query size is, for example, FVY6INQ6LZ33WLCHO3BPR3FH6Y.snap.mainnet.ethdisco.net (52) + +We also have some static data in the response, such as `enrtree-branch:`, and potentially +splitting the response up with `" "`, leaving us with a size of roughly `400` that we need +to stay below. + +The number `370` is used to have some margin for extra overhead (for example, the dns query +may be larger - more subdomains). +*/ const ( - hashAbbrev = 16 - maxChildren = 300 / hashAbbrev * (13 / 8) - minHashLength = 12 + hashAbbrevSize = 1 + 16*13/8 // Size of an encoded hash (plus comma) + maxChildren = 370 / hashAbbrevSize // 13 children + minHashLength = 12 ) // MakeTree creates a tree containing the given nodes and links. From c454717fa67df71b4a3c2f16cd0f6bc186148a04 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:32:57 +0100 Subject: [PATCH 401/948] core: fix potential race in chainIndexerTest (#22346) --- core/chain_indexer_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index b76203dc8f..f099609015 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -18,6 +18,7 @@ package core import ( "context" + "errors" "fmt" "math/big" "math/rand" @@ -224,7 +225,10 @@ func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Heade //t.processCh <- header.Number.Uint64() select { case <-time.After(10 * time.Second): - b.t.Fatal("Unexpected call to Process") + b.t.Error("Unexpected call to Process") + // Can't use Fatal since this is not the test's goroutine. + // Returning error stops the chainIndexer's updateLoop + return errors.New("Unexpected call to Process") case b.processCh <- header.Number.Uint64(): } return nil From d3040a80d7a0721297cfac93b82e0e9428ddd3d1 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 19 Mar 2021 14:15:39 +0000 Subject: [PATCH 402/948] cmd/devp2p/internal/ethtest: skip eth/66 tests when v66 not supported (#22460) --- cmd/devp2p/internal/ethtest/chain_test.go | 58 +++++++++++++++++-- cmd/devp2p/internal/ethtest/eth66_suite.go | 10 ++++ .../internal/ethtest/eth66_suiteHelpers.go | 1 + cmd/devp2p/internal/ethtest/suite.go | 37 +++++++++++- cmd/devp2p/internal/ethtest/types.go | 17 +++--- cmd/devp2p/rlpxcmd.go | 8 ++- 6 files changed, 116 insertions(+), 15 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 5e4289d80a..ec98833ab5 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -35,7 +35,31 @@ func TestEthProtocolNegotiation(t *testing.T) { expected uint32 }{ { - conn: &Conn{}, + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, caps: []p2p.Cap{ {Name: "eth", Version: 63}, {Name: "eth", Version: 64}, @@ -44,7 +68,20 @@ func TestEthProtocolNegotiation(t *testing.T) { expected: uint32(65), }, { - conn: &Conn{}, + conn: &Conn{ + ourHighestProtoVersion: 64, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: 64, + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, caps: []p2p.Cap{ {Name: "eth", Version: 0}, {Name: "eth", Version: 89}, @@ -53,7 +90,20 @@ func TestEthProtocolNegotiation(t *testing.T) { expected: uint32(65), }, { - conn: &Conn{}, + conn: &Conn{ + ourHighestProtoVersion: 64, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, caps: []p2p.Cap{ {Name: "eth", Version: 63}, {Name: "eth", Version: 64}, @@ -66,7 +116,7 @@ func TestEthProtocolNegotiation(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { tt.conn.negotiateEthProtocol(tt.caps) - assert.Equal(t, tt.expected, uint32(tt.conn.ethProtocolVersion)) + assert.Equal(t, tt.expected, uint32(tt.conn.negotiatedProtoVersion)) }) } } diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 644fed61eb..63fb1af597 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -26,6 +26,16 @@ import ( "github.com/ethereum/go-ethereum/p2p" ) +// Is_66 checks if the node supports the eth66 protocol version, +// and if not, exists the test suite +func (s *Suite) Is_66(t *utesting.T) { + conn := s.dial66(t) + conn.handshake(t) + if conn.negotiatedProtoVersion < 66 { + t.Fail() + } +} + // TestStatus_66 attempts to connect to the given node and exchange // a status message with it on the eth66 protocol, and then check to // make sure the chain head is correct. diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go index a3f1aaa4f8..4ef349740f 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -46,6 +46,7 @@ func (s *Suite) dial66(t *utesting.T) *Conn { t.Fatalf("could not dial: %v", err) } conn.caps = append(conn.caps, p2p.Cap{Name: "eth", Version: 66}) + conn.ourHighestProtoVersion = 66 return conn } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 48010b90dd..7b2a22db70 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -65,7 +65,7 @@ func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, e }, nil } -func (s *Suite) EthTests() []utesting.Test { +func (s *Suite) AllEthTests() []utesting.Test { return []utesting.Test{ // status {Name: "Status", Fn: s.TestStatus}, @@ -97,6 +97,38 @@ func (s *Suite) EthTests() []utesting.Test { } } +func (s *Suite) EthTests() []utesting.Test { + return []utesting.Test{ + {Name: "Status", Fn: s.TestStatus}, + {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus}, + {Name: "TestTransactions", Fn: s.TestTransaction}, + {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, + } +} + +func (s *Suite) Eth66Tests() []utesting.Test { + return []utesting.Test{ + // only proceed with eth66 test suite if node supports eth 66 protocol + {Name: "Status_66", Fn: s.TestStatus_66}, + {Name: "GetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, + {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, + {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, + {Name: "GetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + {Name: "Broadcast_66", Fn: s.TestBroadcast_66}, + {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, + {Name: "TestTransactions_66", Fn: s.TestTransaction_66}, + {Name: "TestMaliciousTransactions_66", Fn: s.TestMaliciousTx_66}, + } +} + // TestStatus attempts to connect to the given node and exchange // a status message with it, and then check to make sure // the chain head is correct. @@ -125,7 +157,7 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) { // get protoHandshake conn.handshake(t) status := &Status{ - ProtocolVersion: uint32(conn.ethProtocolVersion), + ProtocolVersion: uint32(conn.negotiatedProtoVersion), NetworkID: s.chain.chainConfig.ChainID.Uint64(), TD: largeNumber(2), Head: s.chain.blocks[s.chain.Len()-1].Hash(), @@ -421,6 +453,7 @@ func (s *Suite) dial() (*Conn, error) { {Name: "eth", Version: 64}, {Name: "eth", Version: 65}, } + conn.ourHighestProtoVersion = 65 return &conn, nil } diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 96012b3156..1e2ae77965 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -123,9 +123,10 @@ func (nb NewPooledTransactionHashes) Code() int { return 24 } // Conn represents an individual connection with a peer type Conn struct { *rlpx.Conn - ourKey *ecdsa.PrivateKey - ethProtocolVersion uint - caps []p2p.Cap + ourKey *ecdsa.PrivateKey + negotiatedProtoVersion uint + ourHighestProtoVersion uint + caps []p2p.Cap } func (c *Conn) Read() Message { @@ -236,7 +237,7 @@ func (c *Conn) handshake(t *utesting.T) Message { c.SetSnappy(true) } c.negotiateEthProtocol(msg.Caps) - if c.ethProtocolVersion == 0 { + if c.negotiatedProtoVersion == 0 { t.Fatalf("unexpected eth protocol version") } return msg @@ -254,11 +255,11 @@ func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { if capability.Name != "eth" { continue } - if capability.Version > highestEthVersion && capability.Version <= 65 { + if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { highestEthVersion = capability.Version } } - c.ethProtocolVersion = highestEthVersion + c.negotiatedProtoVersion = highestEthVersion } // statusExchange performs a `Status` message exchange with the given @@ -295,13 +296,13 @@ loop: } } // make sure eth protocol version is set for negotiation - if c.ethProtocolVersion == 0 { + if c.negotiatedProtoVersion == 0 { t.Fatalf("eth protocol version must be set in Conn") } if status == nil { // write status message to client status = &Status{ - ProtocolVersion: uint32(c.ethProtocolVersion), + ProtocolVersion: uint32(c.negotiatedProtoVersion), NetworkID: chain.chainConfig.ChainID.Uint64(), TD: chain.TD(chain.Len()), Head: chain.blocks[chain.Len()-1].Hash(), diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index ac92818aa4..24a16f0b3c 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" @@ -98,5 +99,10 @@ func rlpxEthTest(ctx *cli.Context) error { if err != nil { exit(err) } - return runTests(ctx, suite.EthTests()) + // check if given node supports eth66, and if so, run eth66 protocol tests as well + is66Failed, _ := utesting.Run(utesting.Test{Name: "Is_66", Fn: suite.Is_66}) + if is66Failed { + return runTests(ctx, suite.EthTests()) + } + return runTests(ctx, suite.AllEthTests()) } From 9429ab14727f55d3d0231a86a3cf37039ea79c9a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 20 Mar 2021 00:22:24 +0100 Subject: [PATCH 403/948] cmd/devp2p: add flag for AWS region (#22537) --- cmd/devp2p/dns_route53.go | 8 +++++++- cmd/devp2p/dnscmd.go | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index 5f534ff9f7..b32a885350 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -59,6 +59,11 @@ var ( Name: "zone-id", Usage: "Route53 Zone ID", } + route53RegionFlag = cli.StringFlag{ + Name: "aws-region", + Usage: "AWS Region", + Value: "eu-central-1", + } ) type route53Client struct { @@ -76,13 +81,14 @@ func newRoute53Client(ctx *cli.Context) *route53Client { akey := ctx.String(route53AccessKeyFlag.Name) asec := ctx.String(route53AccessSecretFlag.Name) if akey == "" || asec == "" { - exit(fmt.Errorf("need Route53 Access Key ID and secret proceed")) + exit(fmt.Errorf("need Route53 Access Key ID and secret to proceed")) } creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, "")) cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds)) if err != nil { exit(fmt.Errorf("can't initialize AWS configuration: %v", err)) } + cfg.Region = ctx.String(route53RegionFlag.Name) return &route53Client{ api: route53.NewFromConfig(cfg), zoneID: ctx.String(route53ZoneIDFlag.Name), diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index f56f0f34e4..50ab7bf983 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -77,7 +77,12 @@ var ( Usage: "Deploy DNS TXT records to Amazon Route53", ArgsUsage: "", Action: dnsToRoute53, - Flags: []cli.Flag{route53AccessKeyFlag, route53AccessSecretFlag, route53ZoneIDFlag}, + Flags: []cli.Flag{ + route53AccessKeyFlag, + route53AccessSecretFlag, + route53ZoneIDFlag, + route53RegionFlag, + }, } ) From 5bf6612a2e19693b7faa032187f02a019cbea700 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sat, 20 Mar 2021 10:35:22 +0100 Subject: [PATCH 404/948] cmd/devp2p: fix error in updating the cursor when collecting records for route53 (#22538) This PR fixes a regression introduced in #22360, when we updated to the v2 of the AWS sdk, which causes current crawler to just get the same first 100 results over and over, and get stuck in a loop. --- cmd/devp2p/dns_route53.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index b32a885350..010913060a 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -315,8 +315,16 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error break } - // sets the cursor to the next batch + // Set the cursor to the next batc. From the AWS docs: + // + // To display the next page of results, get the values of NextRecordName, + // NextRecordType, and NextRecordIdentifier (if any) from the response. Then submit + // another ListResourceRecordSets request, and specify those values for + // StartRecordName, StartRecordType, and StartRecordIdentifier. + req.StartRecordIdentifier = resp.NextRecordIdentifier + req.StartRecordName = resp.NextRecordName + req.StartRecordType = resp.NextRecordType } return existing, nil From 36b51b8156a4079952d646aac2800423dd8df163 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Sat, 20 Mar 2021 18:50:44 +0000 Subject: [PATCH 405/948] cmd/devp2p: add old block announcement test to eth test suite (#22474) Add old block announcement test to eth test suite, checks to make sure old block announcement isn't propagated --- cmd/devp2p/internal/ethtest/eth66_suite.go | 4 +++ cmd/devp2p/internal/ethtest/suite.go | 33 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 63fb1af597..0995dcb3e4 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -215,6 +215,10 @@ func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { } } +func (s *Suite) TestOldAnnounce_66(t *utesting.T) { + s.oldAnnounce(t, s.setupConnection66(t), s.setupConnection66(t)) +} + // TestMaliciousHandshake_66 tries to send malicious data during the handshake. func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { conn := s.dial66(t) diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 7b2a22db70..66fb8026a0 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -19,6 +19,7 @@ package ethtest import ( "fmt" "net" + "strings" "time" "github.com/davecgh/go-spew/spew" @@ -84,6 +85,8 @@ func (s *Suite) AllEthTests() []utesting.Test { {Name: "Broadcast_66", Fn: s.TestBroadcast_66}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, + {Name: "TestOldAnnounce_66", Fn: s.TestOldAnnounce_66}, // malicious handshakes + status {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, @@ -389,6 +392,36 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { } } +func (s *Suite) TestOldAnnounce(t *utesting.T) { + s.oldAnnounce(t, s.setupConnection(t), s.setupConnection(t)) +} + +func (s *Suite) oldAnnounce(t *utesting.T, sendConn, receiveConn *Conn) { + oldBlockAnnounce := &NewBlock{ + Block: s.chain.blocks[len(s.chain.blocks)/2], + TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), + } + + if err := sendConn.Write(oldBlockAnnounce); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := receiveConn.ReadAndServe(s.chain, timeout*2).(type) { + case *NewBlock: + t.Fatalf("unexpected: block propagated: %s", pretty.Sdump(msg)) + case *NewBlockHashes: + t.Fatalf("unexpected: block announced: %s", pretty.Sdump(msg)) + case *Error: + errMsg := *msg + // check to make sure error is timeout (propagation didn't come through == test successful) + if !strings.Contains(errMsg.String(), "timeout") { + t.Fatalf("unexpected error: %v", pretty.Sdump(msg)) + } + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { // Announce the block. if err := sendConn.Write(blockAnnouncement); err != nil { From ec73ec092d149e20e13cdb0f4b9dbefa9d21a19e Mon Sep 17 00:00:00 2001 From: Tobias Hildebrandt <79341166+tobias-hildebrandt@users.noreply.github.com> Date: Sat, 20 Mar 2021 18:54:17 +0000 Subject: [PATCH 406/948] cmd/utils: fix compilation issue on openbsd (#22511) --- cmd/utils/diskusage.go | 2 +- cmd/utils/diskusage_openbsd.go | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 cmd/utils/diskusage_openbsd.go diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go index da696de6bf..c3d51765f8 100644 --- a/cmd/utils/diskusage.go +++ b/cmd/utils/diskusage.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build !windows +// +build !windows,!openbsd package utils diff --git a/cmd/utils/diskusage_openbsd.go b/cmd/utils/diskusage_openbsd.go new file mode 100644 index 0000000000..54f759d291 --- /dev/null +++ b/cmd/utils/diskusage_openbsd.go @@ -0,0 +1,43 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build openbsd + +package utils + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func getFreeDiskSpace(path string) (uint64, error) { + var stat unix.Statfs_t + if err := unix.Statfs(path, &stat); err != nil { + return 0, fmt.Errorf("failed to call Statfs: %v", err) + } + + // Available blocks * size per block = available space in bytes + var bavail = stat.F_bavail + // Not sure if the following check is necessary for OpenBSD + if stat.F_bavail < 0 { + // FreeBSD can have a negative number of blocks available + // because of the grace limit. + bavail = 0 + } + //nolint:unconvert + return uint64(bavail) * uint64(stat.F_bsize), nil +} From eaccdba4ab310e3fb98edbc4b340b5e7c4d767fd Mon Sep 17 00:00:00 2001 From: Derek Chiang Date: Mon, 22 Mar 2021 00:10:51 -0700 Subject: [PATCH 407/948] core: fix method comment for `txpool.requestReset` (#22543) --- core/tx_pool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 4c1bd809fd..5db1d3df32 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -949,7 +949,7 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { } } -// requestPromoteExecutables requests a pool reset to the new head block. +// requestReset requests a pool reset to the new head block. // The returned channel is closed when the reset has occurred. func (pool *TxPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} { select { From aab35600bcfbf1c7cfff39c4b7b8b594d19ce358 Mon Sep 17 00:00:00 2001 From: MrChico Date: Mon, 22 Mar 2021 09:29:32 +0100 Subject: [PATCH 408/948] accounts: eip-712 signing for ledger (#22378) * accounts: eip-712 signing for ledger * address review comments --- accounts/usbwallet/ledger.go | 82 ++++++++++++++++++++++++++++++++++++ accounts/usbwallet/trezor.go | 4 ++ accounts/usbwallet/wallet.go | 43 ++++++++++++++++++- 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index 71f0f9392f..3de3b4091c 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -52,8 +52,10 @@ const ( ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration + ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet + ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address @@ -170,6 +172,24 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio return w.ledgerSign(path, tx, chainID) } +// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and +// waiting for the user to sign or deny the transaction. +// +// Note: this was introduced in the ledger 1.5.0 firmware +func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { + // If the Ethereum app doesn't run, abort + if w.offline() { + return nil, accounts.ErrWalletClosed + } + // Ensure the wallet is capable of signing the given transaction + if w.version[0] < 1 && w.version[1] < 5 { + //lint:ignore ST1005 brand name displayed on the console + return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2]) + } + // All infos gathered and metadata checks out, request signing + return w.ledgerSignTypedMessage(path, domainHash, messageHash) +} + // ledgerVersion retrieves the current version of the Ethereum wallet app running // on the Ledger wallet. // @@ -367,6 +387,68 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction return sender, signed, nil } +// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user +// to confirm or deny the transaction. +// +// The signing protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+-----------------------------+-----+--- +// E0 | 0C | 00 | implementation version : 00 | variable | variable +// +// Where the input is: +// +// Description | Length +// -------------------------------------------------+---------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// domain hash | 32 bytes +// message hash | 32 bytes +// +// +// +// And the output data is: +// +// Description | Length +// ------------+--------- +// signature V | 1 byte +// signature R | 32 bytes +// signature S | 32 bytes +func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Create the 712 message + payload := append(path, domainHash...) + payload = append(payload, messageHash...) + + // Send the request and wait for the response + var ( + op = ledgerP1InitTypedMessageData + reply []byte + err error + ) + + // Send the message over, ensuring it's processed correctly + reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload) + + if err != nil { + return nil, err + } + + // Extract the Ethereum signature and do a sanity validation + if len(reply) != crypto.SignatureLength { + return nil, errors.New("reply lacks signature") + } + signature := append(reply[1:], reply[0]) + return signature, nil +} + // ledgerExchange performs a data exchange with the Ledger wallet, sending it a // message and retrieving the response. // diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go index 0546458c47..c2182b88d0 100644 --- a/accounts/usbwallet/trezor.go +++ b/accounts/usbwallet/trezor.go @@ -185,6 +185,10 @@ func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio return w.trezorSign(path, tx, chainID) } +func (w *trezorDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { + return nil, accounts.ErrNotSupported +} + // trezorDerive sends a derivation request to the Trezor device and returns the // Ethereum address located on that path. func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) { diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 9f74e5554f..b6f1814488 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -67,6 +67,8 @@ type driver interface { // SignTx sends the transaction to the USB device and waits for the user to confirm // or deny the transaction. SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) + + SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error) } // wallet represents the common functionality shared by all USB hardware @@ -524,7 +526,46 @@ func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { - return w.signHash(account, crypto.Keccak256(data)) + + // Unless we are doing 712 signing, simply dispatch to signHash + if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) { + return w.signHash(account, crypto.Keccak256(data)) + } + + // dispatch to 712 signing if the mimetype is TypedData and the format matches + w.stateLock.RLock() // Comms have own mutex, this is for the state fields + defer w.stateLock.RUnlock() + + // If the wallet is closed, abort + if w.device == nil { + return nil, accounts.ErrWalletClosed + } + // Make sure the requested account is contained within + path, ok := w.paths[account.Address] + if !ok { + return nil, accounts.ErrUnknownAccount + } + // All infos gathered and metadata checks out, request signing + <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() + + // Ensure the device isn't screwed with while user confirmation is pending + // TODO(karalabe): remove if hotplug lands on Windows + w.hub.commsLock.Lock() + w.hub.commsPend++ + w.hub.commsLock.Unlock() + + defer func() { + w.hub.commsLock.Lock() + w.hub.commsPend-- + w.hub.commsLock.Unlock() + }() + // Sign the transaction + signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66]) + if err != nil { + return nil, err + } + return signature, nil } // SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given From 0c70b83e0012ce0f9c7b76e61eef7dde5a360838 Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 23 Mar 2021 02:06:30 +0800 Subject: [PATCH 409/948] all: add read-only option to database (#22407) * all: add read-only option to database * all: fixes tests * cmd/geth: migrate flags * cmd/geth: fix the compact * cmd/geth: fix the format * cmd/geth: fix log * cmd: add chain-readonly * core: add readonly notion to freezer * core/rawdb: add log * core/rawdb: fix freezer close * cmd: fix * cmd, core: construct db * core: update tests --- cmd/geth/chaincmd.go | 33 +++--- cmd/geth/dao_test.go | 2 +- cmd/geth/dbcmd.go | 151 ++++++++++++++++---------- cmd/geth/snapshot.go | 57 +++++----- cmd/utils/flags.go | 22 ++-- core/bench_test.go | 8 +- core/blockchain_repair_test.go | 6 +- core/blockchain_sethead_test.go | 4 +- core/blockchain_snapshot_test.go | 4 +- core/blockchain_test.go | 18 +-- core/rawdb/accessors_chain.go | 26 +++++ core/rawdb/accessors_chain_test.go | 2 +- core/rawdb/database.go | 25 +++-- core/rawdb/freezer.go | 18 ++- core/state/pruner/pruner.go | 36 ++---- core/state/snapshot/disklayer_test.go | 2 +- eth/backend.go | 2 +- eth/filters/bench_test.go | 6 +- eth/filters/filter_test.go | 4 +- eth/protocols/eth/protocol_test.go | 2 +- ethdb/leveldb/leveldb.go | 5 +- les/client.go | 4 +- les/server.go | 2 +- node/node.go | 8 +- node/node_test.go | 6 +- trie/trie_test.go | 2 +- 26 files changed, 265 insertions(+), 190 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 2010ae4b30..61401dd59f 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -191,7 +192,7 @@ func initGenesis(ctx *cli.Context) error { defer stack.Close() for _, name := range []string{"chaindata", "lightchaindata"} { - chaindb, err := stack.OpenDatabase(name, 0, 0, "") + chaindb, err := stack.OpenDatabase(name, 0, 0, "", false) if err != nil { utils.Fatalf("Failed to open database: %v", err) } @@ -229,7 +230,7 @@ func importChain(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, db := utils.MakeChain(ctx, stack, false) + chain, db := utils.MakeChain(ctx, stack) defer db.Close() // Start periodically gathering memory profiles @@ -304,7 +305,7 @@ func exportChain(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, _ := utils.MakeChain(ctx, stack, true) + chain, _ := utils.MakeChain(ctx, stack) start := time.Now() var err error @@ -340,7 +341,7 @@ func importPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack) + db := utils.MakeChainDatabase(ctx, stack, false) start := time.Now() if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { @@ -359,7 +360,7 @@ func exportPreimages(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack) + db := utils.MakeChainDatabase(ctx, stack, true) start := time.Now() if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { @@ -373,21 +374,27 @@ func dump(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, chainDb := utils.MakeChain(ctx, stack, true) - defer chainDb.Close() + db := utils.MakeChainDatabase(ctx, stack, true) for _, arg := range ctx.Args() { - var block *types.Block + var header *types.Header if hashish(arg) { - block = chain.GetBlockByHash(common.HexToHash(arg)) + hash := common.HexToHash(arg) + number := rawdb.ReadHeaderNumber(db, hash) + if number != nil { + header = rawdb.ReadHeader(db, hash, *number) + } } else { - num, _ := strconv.Atoi(arg) - block = chain.GetBlockByNumber(uint64(num)) + number, _ := strconv.Atoi(arg) + hash := rawdb.ReadCanonicalHash(db, uint64(number)) + if hash != (common.Hash{}) { + header = rawdb.ReadHeader(db, hash, uint64(number)) + } } - if block == nil { + if header == nil { fmt.Println("{}") utils.Fatalf("block not found") } else { - state, err := state.New(block.Root(), state.NewDatabase(chainDb), nil) + state, err := state.New(header.Root, state.NewDatabase(db), nil) if err != nil { utils.Fatalf("could not create new state: %v", err) } diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index 29b1a7f474..b7f26b3652 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -123,7 +123,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc } // Retrieve the DAO config flag from the database path := filepath.Join(datadir, "geth", "chaindata") - db, err := rawdb.NewLevelDBDatabase(path, 0, 0, "") + db, err := rawdb.NewLevelDBDatabase(path, 0, 0, "", false) if err != nil { t.Fatalf("test %d: failed to open test database: %v", test, err) } diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 48478f613e..078cad53b4 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -28,9 +28,7 @@ import ( "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/log" - "github.com/syndtr/goleveldb/leveldb/opt" "gopkg.in/urfave/cli.v1" ) @@ -65,43 +63,98 @@ Remove blockchain and state databases`, Action: utils.MigrateFlags(inspect), Name: "inspect", ArgsUsage: " ", - + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, Usage: "Inspect the storage size for each type of data in the database", Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, } dbStatCmd = cli.Command{ - Action: dbStats, + Action: utils.MigrateFlags(dbStats), Name: "stats", Usage: "Print leveldb statistics", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, } dbCompactCmd = cli.Command{ - Action: dbCompact, + Action: utils.MigrateFlags(dbCompact), Name: "compact", Usage: "Compact leveldb database. WARNING: May take a very long time", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + utils.CacheFlag, + utils.CacheDatabaseFlag, + }, Description: `This command performs a database compaction. WARNING: This operation may take a very long time to finish, and may cause database corruption if it is aborted during execution'!`, } dbGetCmd = cli.Command{ - Action: dbGet, - Name: "get", - Usage: "Show the value of a database key", - ArgsUsage: "", + Action: utils.MigrateFlags(dbGet), + Name: "get", + Usage: "Show the value of a database key", + ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, Description: "This command looks up the specified database key from the database.", } dbDeleteCmd = cli.Command{ - Action: dbDelete, + Action: utils.MigrateFlags(dbDelete), Name: "delete", Usage: "Delete a database key (WARNING: may corrupt your database)", ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, Description: `This command deletes the specified database key from the database. WARNING: This is a low-level operation which may cause database corruption!`, } dbPutCmd = cli.Command{ - Action: dbPut, + Action: utils.MigrateFlags(dbPut), Name: "put", Usage: "Set the value of a database key (WARNING: may corrupt your database)", ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, Description: `This command sets a given database key to the given value. WARNING: This is a low-level operation which may cause database corruption!`, } @@ -192,10 +245,10 @@ func inspect(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - _, chainDb := utils.MakeChain(ctx, stack, true) - defer chainDb.Close() + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() - return rawdb.InspectDatabase(chainDb, prefix, start) + return rawdb.InspectDatabase(db, prefix, start) } func showLeveldbStats(db ethdb.Stater) { @@ -214,48 +267,32 @@ func showLeveldbStats(db ethdb.Stater) { func dbStats(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - path := stack.ResolvePath("chaindata") - db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { - options.ReadOnly = true - }) - if err != nil { - return err - } + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + showLeveldbStats(db) - err = db.Close() - if err != nil { - log.Info("Close err", "error", err) - } return nil } func dbCompact(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - path := stack.ResolvePath("chaindata") - cache := ctx.GlobalInt(utils.CacheFlag.Name) * ctx.GlobalInt(utils.CacheDatabaseFlag.Name) / 100 - db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { - options.OpenFilesCacheCapacity = utils.MakeDatabaseHandles() - options.BlockCacheCapacity = cache / 2 * opt.MiB - options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally - }) - if err != nil { - return err - } + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + log.Info("Stats before compaction") showLeveldbStats(db) + log.Info("Triggering compaction") - err = db.Compact(nil, nil) - if err != nil { + if err := db.Compact(nil, nil); err != nil { log.Info("Compact err", "error", err) + return err } + log.Info("Stats after compaction") showLeveldbStats(db) - log.Info("Closing db") - err = db.Close() - if err != nil { - log.Info("Close err", "error", err) - } - log.Info("Exiting") - return err + return nil } // dbGet shows the value of a given database key @@ -265,14 +302,10 @@ func dbGet(ctx *cli.Context) error { } stack, _ := makeConfigNode(ctx) defer stack.Close() - path := stack.ResolvePath("chaindata") - db, err := leveldb.NewCustom(path, "", func(options *opt.Options) { - options.ReadOnly = true - }) - if err != nil { - return err - } + + db := utils.MakeChainDatabase(ctx, stack, true) defer db.Close() + key, err := hexutil.Decode(ctx.Args().Get(0)) if err != nil { log.Info("Could not decode the key", "error", err) @@ -283,7 +316,7 @@ func dbGet(ctx *cli.Context) error { log.Info("Get operation failed", "error", err) return err } - fmt.Printf("key %#x:\n\t%#x\n", key, data) + fmt.Printf("key %#x: %#x\n", key, data) return nil } @@ -294,13 +327,19 @@ func dbDelete(ctx *cli.Context) error { } stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack) + + db := utils.MakeChainDatabase(ctx, stack, false) defer db.Close() + key, err := hexutil.Decode(ctx.Args().Get(0)) if err != nil { log.Info("Could not decode the key", "error", err) return err } + data, err := db.Get(key) + if err == nil { + fmt.Printf("Previous value: %#x\n", data) + } if err = db.Delete(key); err != nil { log.Info("Delete operation returned an error", "error", err) return err @@ -315,8 +354,10 @@ func dbPut(ctx *cli.Context) error { } stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack) + + db := utils.MakeChainDatabase(ctx, stack, false) defer db.Close() + var ( key []byte value []byte @@ -335,7 +376,7 @@ func dbPut(ctx *cli.Context) error { } data, err = db.Get(key) if err == nil { - fmt.Printf("Previous value:\n%#x\n", data) + fmt.Printf("Previous value: %#x\n", data) } return db.Put(key, value) } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 068dea0b92..e8f6a35438 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -152,10 +152,8 @@ func pruneState(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) defer stack.Close() - chain, chaindb := utils.MakeChain(ctx, stack, true) - defer chaindb.Close() - - pruner, err := pruner.NewPruner(chaindb, chain.CurrentBlock().Header(), stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) + chaindb := utils.MakeChainDatabase(ctx, stack, false) + pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) if err != nil { log.Error("Failed to open snapshot tree", "error", err) return err @@ -183,10 +181,13 @@ func verifyState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, chaindb := utils.MakeChain(ctx, stack, true) - defer chaindb.Close() - - snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, chain.CurrentBlock().Root(), false, false, false) + chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false) if err != nil { log.Error("Failed to open snapshot tree", "error", err) return err @@ -195,7 +196,7 @@ func verifyState(ctx *cli.Context) error { log.Error("Too many arguments given") return errors.New("too many arguments") } - var root = chain.CurrentBlock().Root() + var root = headBlock.Root() if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args()[0]) if err != nil { @@ -218,19 +219,16 @@ func traverseState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, chaindb := utils.MakeChain(ctx, stack, true) - defer chaindb.Close() - + chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } if ctx.NArg() > 1 { log.Error("Too many arguments given") return errors.New("too many arguments") } - // Use the HEAD root as the default - head := chain.CurrentBlock() - if head == nil { - log.Error("Head block is missing") - return errors.New("head block is missing") - } var ( root common.Hash err error @@ -243,8 +241,8 @@ func traverseState(ctx *cli.Context) error { } log.Info("Start traversing the state", "root", root) } else { - root = head.Root() - log.Info("Start traversing the state", "root", root, "number", head.NumberU64()) + root = headBlock.Root() + log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } triedb := trie.NewDatabase(chaindb) t, err := trie.NewSecure(root, triedb) @@ -311,19 +309,16 @@ func traverseRawState(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, chaindb := utils.MakeChain(ctx, stack, true) - defer chaindb.Close() - + chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } if ctx.NArg() > 1 { log.Error("Too many arguments given") return errors.New("too many arguments") } - // Use the HEAD root as the default - head := chain.CurrentBlock() - if head == nil { - log.Error("Head block is missing") - return errors.New("head block is missing") - } var ( root common.Hash err error @@ -336,8 +331,8 @@ func traverseRawState(ctx *cli.Context) error { } log.Info("Start traversing the state", "root", root) } else { - root = head.Root() - log.Info("Start traversing the state", "root", root, "number", head.NumberU64()) + root = headBlock.Root() + log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } triedb := trie.NewDatabase(chaindb) t, err := trie.NewSecure(root, triedb) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5c800a7fe9..00b28bddf6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1628,7 +1628,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DataDirFlag.Name) { // Check if we have an already initialized chain and fall back to // that if so. Otherwise we need to generate a new genesis spec. - chaindb := MakeChainDatabase(ctx, stack) + chaindb := MakeChainDatabase(ctx, stack, true) if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } @@ -1749,7 +1749,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string { } // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. -func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { +func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.Database { var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles() @@ -1759,10 +1759,10 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { ) if ctx.GlobalString(SyncModeFlag.Name) == "light" { name := "lightchaindata" - chainDb, err = stack.OpenDatabase(name, cache, handles, "") + chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly) } else { name := "chaindata" - chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "") + chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly) } if err != nil { Fatalf("Could not open database: %v", err) @@ -1790,9 +1790,9 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { } // MakeChain creates a chain manager from set command line flags. -func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.BlockChain, chainDb ethdb.Database) { +func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chainDb ethdb.Database) { var err error - chainDb = MakeChainDatabase(ctx, stack) + chainDb = MakeChainDatabase(ctx, stack, false) // TODO(rjl493456442) support read-only database config, _, err := core.SetupGenesisBlock(chainDb, MakeGenesis(ctx)) if err != nil { Fatalf("%v", err) @@ -1841,12 +1841,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B cache.TrieDirtyLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 } vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name)} - var limit *uint64 - if ctx.GlobalIsSet(TxLookupLimitFlag.Name) && !readOnly { - l := ctx.GlobalUint64(TxLookupLimitFlag.Name) - limit = &l - } - chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil, limit) + + // TODO(rjl493456442) disable snapshot generation/wiping if the chain is read only. + // Disable transaction indexing/unindexing by default. + chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil, nil) if err != nil { Fatalf("Can't create BlockChain: %v", err) } diff --git a/core/bench_test.go b/core/bench_test.go index 85653ea5db..0c49907e64 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -157,7 +157,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { b.Fatalf("cannot create temporary directory: %v", err) } defer os.RemoveAll(dir) - db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "") + db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false) if err != nil { b.Fatalf("cannot create temporary database: %v", err) } @@ -255,7 +255,7 @@ func benchWriteChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("cannot create temporary directory: %v", err) } - db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "") + db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } @@ -272,7 +272,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) { } defer os.RemoveAll(dir) - db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "") + db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } @@ -283,7 +283,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) { b.ResetTimer() for i := 0; i < b.N; i++ { - db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "") + db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index b5cd232a9c..8bb39d2607 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1762,7 +1762,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -1817,7 +1817,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { } // Force run a freeze cycle type freezer interface { - Freeze(threshold uint64) + Freeze(threshold uint64) error Ancients() (uint64, error) } db.(freezer).Freeze(tt.freezeThreshold) @@ -1830,7 +1830,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { db.Close() // Start a new blockchain back up and see where the repait leads us - db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index 45c4073eb4..e99b09cf8c 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1961,7 +1961,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -2023,7 +2023,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { } // Force run a freeze cycle type freezer interface { - Freeze(threshold uint64) + Freeze(threshold uint64) error Ancients() (uint64, error) } db.(freezer).Freeze(tt.freezeThreshold) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 96a5c7a8d4..70ae31f7d8 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -65,7 +65,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } os.RemoveAll(datadir) - db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "") + db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -261,7 +261,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { db.Close() // Start a new blockchain back up and see where the repair leads us - newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "") + newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 3e4757f8b6..fd7f1aea1b 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -651,7 +651,7 @@ func TestFastVsFullChains(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -725,7 +725,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(dir) - db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "") + db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1592,7 +1592,7 @@ func TestBlockchainRecovery(t *testing.T) { } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1649,7 +1649,7 @@ func TestIncompleteAncientReceiptChainInsertion(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -1848,7 +1848,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(dir) - chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "") + chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2128,7 +2128,7 @@ func TestTransactionIndices(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2156,7 +2156,7 @@ func TestTransactionIndices(t *testing.T) { // Init block chain with external ancients, check all needed indices has been indexed. limit := []uint64{0, 32, 64, 128} for _, l := range limit { - ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2176,7 +2176,7 @@ func TestTransactionIndices(t *testing.T) { } // Reconstruct a block chain which only reserves HEAD-64 tx indices - ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } @@ -2255,7 +2255,7 @@ func TestSkipStaleTxIndicesInFastSync(t *testing.T) { t.Fatalf("failed to create temp freezer dir: %v", err) } defer os.Remove(frdir) - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "") + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create temp freezer db: %v", err) } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 461e1cbb17..92450313b4 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -825,3 +825,29 @@ func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { } return a } + +// ReadHeadHeader returns the current canonical head header. +func ReadHeadHeader(db ethdb.Reader) *types.Header { + headHeaderHash := ReadHeadHeaderHash(db) + if headHeaderHash == (common.Hash{}) { + return nil + } + headHeaderNumber := ReadHeaderNumber(db, headHeaderHash) + if headHeaderNumber == nil { + return nil + } + return ReadHeader(db, headHeaderHash, *headHeaderNumber) +} + +// ReadHeadHeader returns the current canonical head block. +func ReadHeadBlock(db ethdb.Reader) *types.Block { + headBlockHash := ReadHeadBlockHash(db) + if headBlockHash == (common.Hash{}) { + return nil + } + headBlockNumber := ReadHeaderNumber(db, headBlockHash) + if headBlockNumber == nil { + return nil + } + return ReadBlock(db, headBlockHash, *headBlockNumber) +} diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index a5804cd309..ea9dc436cf 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -440,7 +440,7 @@ func TestAncientStorage(t *testing.T) { } defer os.Remove(frdir) - db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "") + db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create database with ancient backend") } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 91171ef92c..94759eb984 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -57,7 +57,10 @@ func (frdb *freezerdb) Close() error { // Freeze is a helper method used for external testing to trigger and block until // a freeze cycle completes, without having to sleep for a minute to trigger the // automatic background run. -func (frdb *freezerdb) Freeze(threshold uint64) { +func (frdb *freezerdb) Freeze(threshold uint64) error { + if frdb.AncientStore.(*freezer).readonly { + return errReadOnly + } // Set the freezer threshold to a temporary value defer func(old uint64) { atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, old) @@ -68,6 +71,7 @@ func (frdb *freezerdb) Freeze(threshold uint64) { trigger := make(chan struct{}, 1) frdb.AncientStore.(*freezer).trigger <- trigger <-trigger + return nil } // nofreezedb is a database wrapper that disables freezer data retrievals. @@ -121,9 +125,9 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database { // NewDatabaseWithFreezer creates a high level database on top of a given key- // value data store with a freezer moving immutable chain segments into cold // storage. -func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string) (ethdb.Database, error) { +func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) { // Create the idle freezer instance - frdb, err := newFreezer(freezer, namespace) + frdb, err := newFreezer(freezer, namespace, readonly) if err != nil { return nil, err } @@ -192,8 +196,9 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } } // Freezer is consistent with the key-value database, permit combining the two - go frdb.freeze(db) - + if !frdb.readonly { + go frdb.freeze(db) + } return &freezerdb{ KeyValueStore: db, AncientStore: frdb, @@ -215,8 +220,8 @@ func NewMemoryDatabaseWithCap(size int) ethdb.Database { // NewLevelDBDatabase creates a persistent key-value database without a freezer // moving immutable chain segments into cold storage. -func NewLevelDBDatabase(file string, cache int, handles int, namespace string) (ethdb.Database, error) { - db, err := leveldb.New(file, cache, handles, namespace) +func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { + db, err := leveldb.New(file, cache, handles, namespace, readonly) if err != nil { return nil, err } @@ -225,12 +230,12 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string) ( // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a // freezer moving immutable chain segments into cold storage. -func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) { - kvdb, err := leveldb.New(file, cache, handles, namespace) +func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) { + kvdb, err := leveldb.New(file, cache, handles, namespace, readonly) if err != nil { return nil, err } - frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace) + frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly) if err != nil { kvdb.Close() return nil, err diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 5744b0cbb3..4e5ae4284e 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -35,6 +35,10 @@ import ( ) var ( + // errReadOnly is returned if the freezer is opened in read only mode. All the + // mutations are disallowed. + errReadOnly = errors.New("read only") + // errUnknownTable is returned if the user attempts to read from a table that is // not tracked by the freezer. errUnknownTable = errors.New("unknown table") @@ -73,6 +77,7 @@ type freezer struct { frozen uint64 // Number of blocks already frozen threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests) + readonly bool tables map[string]*freezerTable // Data tables for storing everything instanceLock fileutil.Releaser // File-system lock to prevent double opens @@ -84,7 +89,7 @@ type freezer struct { // newFreezer creates a chain freezer that moves ancient chain data into // append-only flat file containers. -func newFreezer(datadir string, namespace string) (*freezer, error) { +func newFreezer(datadir string, namespace string, readonly bool) (*freezer, error) { // Create the initial freezer object var ( readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) @@ -106,6 +111,7 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { } // Open all the supported data tables freezer := &freezer{ + readonly: readonly, threshold: params.FullImmutabilityThreshold, tables: make(map[string]*freezerTable), instanceLock: lock, @@ -130,7 +136,7 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { lock.Release() return nil, err } - log.Info("Opened ancient database", "database", datadir) + log.Info("Opened ancient database", "database", datadir, "readonly", readonly) return freezer, nil } @@ -138,7 +144,7 @@ func newFreezer(datadir string, namespace string) (*freezer, error) { func (f *freezer) Close() error { var errs []error f.closeOnce.Do(func() { - f.quit <- struct{}{} + close(f.quit) for _, table := range f.tables { if err := table.Close(); err != nil { errs = append(errs, err) @@ -191,6 +197,9 @@ func (f *freezer) AncientSize(kind string) (uint64, error) { // injection will be rejected. But if two injections with same number happen at // the same time, we can get into the trouble. func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) { + if f.readonly { + return errReadOnly + } // Ensure the binary blobs we are appending is continuous with freezer. if atomic.LoadUint64(&f.frozen) != number { return errOutOrderInsertion @@ -233,6 +242,9 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td // TruncateAncients discards any recent data above the provided threshold number. func (f *freezer) TruncateAncients(items uint64) error { + if f.readonly { + return errReadOnly + } if atomic.LoadUint64(&f.frozen) <= items { return nil } diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 530a348540..62cc7b0120 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -85,8 +85,12 @@ type Pruner struct { } // NewPruner creates the pruner instance. -func NewPruner(db ethdb.Database, headHeader *types.Header, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) { - snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, false) +func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) { + headBlock := rawdb.ReadHeadBlock(db) + if headBlock == nil { + return nil, errors.New("Failed to load head block") + } + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false) if err != nil { return nil, err // The relevant snapshot(s) might not exist } @@ -104,7 +108,7 @@ func NewPruner(db ethdb.Database, headHeader *types.Header, datadir, trieCachePa stateBloom: stateBloom, datadir: datadir, trieCachePath: trieCachePath, - headHeader: headHeader, + headHeader: headBlock.Header(), snaptree: snaptree, }, nil } @@ -350,9 +354,9 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err if stateBloomPath == "" { return nil // nothing to recover } - headHeader, err := getHeadHeader(db) - if err != nil { - return err + headBlock := rawdb.ReadHeadBlock(db) + if headBlock == nil { + return errors.New("Failed to load head block") } // Initialize the snapshot tree in recovery mode to handle this special case: // - Users run the `prune-state` command multiple times @@ -362,7 +366,7 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err // - The state HEAD is rewound already because of multiple incomplete `prune-state` // In this case, even the state HEAD is not exactly matched with snapshot, it // still feasible to recover the pruning correctly. - snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headHeader.Root, false, false, true) + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true) if err != nil { return err // The relevant snapshot(s) might not exist } @@ -382,7 +386,7 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err // otherwise the dangling state will be left. var ( found bool - layers = snaptree.Snapshots(headHeader.Root, 128, true) + layers = snaptree.Snapshots(headBlock.Root(), 128, true) middleRoots = make(map[common.Hash]struct{}) ) for _, layer := range layers { @@ -506,22 +510,6 @@ func findBloomFilter(datadir string) (string, common.Hash, error) { return stateBloomPath, stateBloomRoot, nil } -func getHeadHeader(db ethdb.Database) (*types.Header, error) { - headHeaderHash := rawdb.ReadHeadBlockHash(db) - if headHeaderHash == (common.Hash{}) { - return nil, errors.New("empty head block hash") - } - headHeaderNumber := rawdb.ReadHeaderNumber(db, headHeaderHash) - if headHeaderNumber == nil { - return nil, errors.New("empty head block number") - } - headHeader := rawdb.ReadHeader(db, headHeaderHash, *headHeaderNumber) - if headHeader == nil { - return nil, errors.New("empty head header") - } - return headHeader, nil -} - const warningLog = ` WARNING! diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index 6beb944e07..ccde2fc094 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -524,7 +524,7 @@ func TestDiskSeek(t *testing.T) { t.Fatal(err) } else { defer os.RemoveAll(dir) - diskdb, err := leveldb.New(dir, 256, 0, "") + diskdb, err := leveldb.New(dir, 256, 0, "", false) if err != nil { t.Fatal(err) } diff --git a/eth/backend.go b/eth/backend.go index 76ce5137f4..6e45d27501 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -122,7 +122,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) // Assemble the Ethereum object - chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/") + chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) if err != nil { return nil, err } diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index 4f70d6d04a..020db070e5 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -65,7 +65,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { benchDataDir := node.DefaultDataDir() + "/geth/chaindata" b.Log("Running bloombits benchmark section size:", sectionSize) - db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "") + db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", benchDataDir, err) } @@ -126,7 +126,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { for i := 0; i < benchFilterCnt; i++ { if i%20 == 0 { db.Close() - db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "") + db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) backend = &testBackend{db: db, sections: cnt} } var addr common.Address @@ -157,7 +157,7 @@ func clearBloomBits(db ethdb.Database) { func BenchmarkNoBloomBits(b *testing.B) { benchDataDir := node.DefaultDataDir() + "/geth/chaindata" b.Log("Running benchmark without bloombits") - db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "") + db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) if err != nil { b.Fatalf("error opening database at %v: %v", benchDataDir, err) } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index f45720d5a9..3fc77bbc4d 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -49,7 +49,7 @@ func BenchmarkFilters(b *testing.B) { defer os.RemoveAll(dir) var ( - db, _ = rawdb.NewLevelDBDatabase(dir, 0, 0, "") + db, _ = rawdb.NewLevelDBDatabase(dir, 0, 0, "", false) backend = &testBackend{db: db} key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -103,7 +103,7 @@ func TestFilters(t *testing.T) { defer os.RemoveAll(dir) var ( - db, _ = rawdb.NewLevelDBDatabase(dir, 0, 0, "") + db, _ = rawdb.NewLevelDBDatabase(dir, 0, 0, "", false) backend = &testBackend{db: db} key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key1.PublicKey) diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go index d92f3ea837..7910c9b735 100644 --- a/eth/protocols/eth/protocol_test.go +++ b/eth/protocols/eth/protocol_test.go @@ -178,7 +178,7 @@ func TestEth66Messages(t *testing.T) { // init the receipts { receipts = []*types.Receipt{ - &types.Receipt{ + { Status: types.ReceiptStatusFailed, CumulativeGasUsed: 1, Logs: []*types.Log{ diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 70ac7a91ac..d3011212aa 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -83,7 +83,7 @@ type Database struct { // New returns a wrapped LevelDB object. The namespace is the prefix that the // metrics reporting should use for surfacing internal stats. -func New(file string, cache int, handles int, namespace string) (*Database, error) { +func New(file string, cache int, handles int, namespace string, readonly bool) (*Database, error) { return NewCustom(file, namespace, func(options *opt.Options) { // Ensure we have some minimal caching and file guarantees if cache < minCache { @@ -96,6 +96,9 @@ func New(file string, cache int, handles int, namespace string) (*Database, erro options.OpenFilesCacheCapacity = handles options.BlockCacheCapacity = cache / 2 * opt.MiB options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally + if readonly { + options.ReadOnly = true + } }) } diff --git a/les/client.go b/les/client.go index 99f79bb20c..7534eb3ea0 100644 --- a/les/client.go +++ b/les/client.go @@ -80,11 +80,11 @@ type LightEthereum struct { // New creates an instance of the light client. func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { - chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/") + chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false) if err != nil { return nil, err } - lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/") + lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/", false) if err != nil { return nil, err } diff --git a/les/server.go b/les/server.go index 0351bdd801..be64dfe190 100644 --- a/les/server.go +++ b/les/server.go @@ -87,7 +87,7 @@ type LesServer struct { } func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { - lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/lesserver/") + lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/lesserver/", false) if err != nil { return nil, err } diff --git a/node/node.go b/node/node.go index 2ed4c31f60..1e65fff1c2 100644 --- a/node/node.go +++ b/node/node.go @@ -547,7 +547,7 @@ func (n *Node) EventMux() *event.TypeMux { // OpenDatabase opens an existing database with the given name (or creates one if no // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. -func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) { +func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -559,7 +559,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( if n.config.DataDir == "" { db = rawdb.NewMemoryDatabase() } else { - db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace) + db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace, readonly) } if err == nil { @@ -573,7 +573,7 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) ( // also attaching a chain freezer to it that moves ancient chain data from the // database to immutable append-only files. If the node is an ephemeral one, a // memory database is returned. -func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) { +func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly bool) (ethdb.Database, error) { n.lock.Lock() defer n.lock.Unlock() if n.state == closedState { @@ -592,7 +592,7 @@ func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, case !filepath.IsAbs(freezer): freezer = n.ResolvePath(freezer) } - db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace) + db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly) } if err == nil { diff --git a/node/node_test.go b/node/node_test.go index 6731dbac1f..e104630600 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -158,7 +158,7 @@ func TestNodeCloseClosesDB(t *testing.T) { stack, _ := New(testNodeConfig()) defer stack.Close() - db, err := stack.OpenDatabase("mydb", 0, 0, "") + db, err := stack.OpenDatabase("mydb", 0, 0, "", false) if err != nil { t.Fatal("can't open DB:", err) } @@ -181,7 +181,7 @@ func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) { var err error stack.RegisterLifecycle(&InstrumentedService{ startHook: func() { - db, err = stack.OpenDatabase("mydb", 0, 0, "") + db, err = stack.OpenDatabase("mydb", 0, 0, "", false) if err != nil { t.Fatal("can't open DB:", err) } @@ -202,7 +202,7 @@ func TestNodeOpenDatabaseFromLifecycleStop(t *testing.T) { stack.RegisterLifecycle(&InstrumentedService{ stopHook: func() { - db, err := stack.OpenDatabase("mydb", 0, 0, "") + db, err := stack.OpenDatabase("mydb", 0, 0, "", false) if err != nil { t.Fatal("can't open DB:", err) } diff --git a/trie/trie_test.go b/trie/trie_test.go index 87bce9abca..3aa4098d14 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -1060,7 +1060,7 @@ func tempDB() (string, *Database) { if err != nil { panic(fmt.Sprintf("can't create temporary directory: %v", err)) } - diskdb, err := leveldb.New(dir, 256, 0, "") + diskdb, err := leveldb.New(dir, 256, 0, "", false) if err != nil { panic(fmt.Sprintf("can't create temporary database: %v", err)) } From 8d6cc1674252371c7441279fd9d482d9416dc8aa Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:11:10 +0100 Subject: [PATCH 410/948] cmd/geth: check block range against chain head in export cmd (#22387) Check the input parameters against the actual head block, exit on error --- cmd/geth/chaincmd.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 61401dd59f..3cae32aa9e 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -322,6 +322,9 @@ func exportChain(ctx *cli.Context) error { if first < 0 || last < 0 { utils.Fatalf("Export error: block number must be greater than 0\n") } + if head := chain.CurrentFastBlock(); uint64(last) > head.NumberU64() { + utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.NumberU64()) + } err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last)) } From a31f6d54dfd49e277b78017c3c5e34d544687f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 22 Mar 2021 22:41:28 +0200 Subject: [PATCH 411/948] core/state/snapshot: fix panic on missing parent --- core/state/snapshot/snapshot.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index aa5f5900b0..810f1354e9 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -283,11 +283,11 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m return errSnapshotCycle } // Generate a new snapshot on top of the parent - parent := t.Snapshot(parentRoot).(snapshot) + parent := t.Snapshot(parentRoot) if parent == nil { return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) } - snap := parent.Update(blockRoot, destructs, accounts, storage) + snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage) // Save the new snapshot for later t.lock.Lock() From e862cbff95ef76daf49779cff94aecaa54538aa4 Mon Sep 17 00:00:00 2001 From: meowsbits Date: Tue, 23 Mar 2021 04:41:23 -0500 Subject: [PATCH 412/948] internal/web3ext, node: migrate node admin API (Start|Stop)RPC->HTTP (#22461) * internal/web3ext,node: migrate node admin API (Start|Stop)RPC->HTTP Corresponding CLI flags --rpc have been moved to --http. This moves the admin module HTTP RPC start/stop methods to an equivalent namespace. Rel https://github.com/ethereum/go-ethereum/pull/22263 Date: 2021-03-08 08:00:11-06:00 Signed-off-by: meows * internal/web3ext: fix startRPC/HTTP param count (4->5) Date: 2021-03-16 06:13:23-05:00 Signed-off-by: meows --- internal/web3ext/web3ext.go | 16 ++++++++++++++-- node/api.go | 23 +++++++++++++++++++---- node/api_test.go | 20 ++++++++++---------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 6fcf4b8380..aba262699c 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -186,12 +186,24 @@ web3._extend({ call: 'admin_sleepBlocks', params: 2 }), + new web3._extend.Method({ + name: 'startHTTP', + call: 'admin_startHTTP', + params: 5, + inputFormatter: [null, null, null, null, null] + }), + new web3._extend.Method({ + name: 'stopHTTP', + call: 'admin_stopHTTP' + }), + // This method is deprecated. new web3._extend.Method({ name: 'startRPC', call: 'admin_startRPC', - params: 4, - inputFormatter: [null, null, null, null] + params: 5, + inputFormatter: [null, null, null, null, null] }), + // This method is deprecated. new web3._extend.Method({ name: 'stopRPC', call: 'admin_stopRPC' diff --git a/node/api.go b/node/api.go index 083784f4e4..be20b89d95 100644 --- a/node/api.go +++ b/node/api.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rpc" @@ -162,8 +163,8 @@ func (api *privateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, return rpcSub, nil } -// StartRPC starts the HTTP RPC API server. -func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { +// StartHTTP starts the HTTP RPC API server. +func (api *privateAdminAPI) StartHTTP(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() @@ -216,12 +217,26 @@ func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis return true, nil } -// StopRPC shuts down the HTTP server. -func (api *privateAdminAPI) StopRPC() (bool, error) { +// StartRPC starts the HTTP RPC API server. +// This method is deprecated. Use StartHTTP instead. +func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) { + log.Warn("Deprecation warning", "method", "admin.StartRPC", "use-instead", "admin.StartHTTP") + return api.StartHTTP(host, port, cors, apis, vhosts) +} + +// StopHTTP shuts down the HTTP server. +func (api *privateAdminAPI) StopHTTP() (bool, error) { api.node.http.stop() return true, nil } +// StopRPC shuts down the HTTP server. +// This method is deprecated. Use StopHTTP instead. +func (api *privateAdminAPI) StopRPC() (bool, error) { + log.Warn("Deprecation warning", "method", "admin.StopRPC", "use-instead", "admin.StopHTTP") + return api.StopHTTP() +} + // StartWS starts the websocket RPC API server. func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) { api.node.lock.Lock() diff --git a/node/api_test.go b/node/api_test.go index 9c3fa3a31d..9549adf9c2 100644 --- a/node/api_test.go +++ b/node/api_test.go @@ -69,7 +69,7 @@ func TestStartRPC(t *testing.T) { name: "rpc enabled through API", cfg: Config{}, fn: func(t *testing.T, n *Node, api *privateAdminAPI) { - _, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil) + _, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil) assert.NoError(t, err) }, wantReachable: true, @@ -90,14 +90,14 @@ func TestStartRPC(t *testing.T) { port := listener.Addr().(*net.TCPAddr).Port // Now try to start RPC on that port. This should fail. - _, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil) + _, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil) if err == nil { - t.Fatal("StartRPC should have failed on port", port) + t.Fatal("StartHTTP should have failed on port", port) } // Try again after unblocking the port. It should work this time. listener.Close() - _, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil) + _, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil) assert.NoError(t, err) }, wantReachable: true, @@ -109,7 +109,7 @@ func TestStartRPC(t *testing.T) { name: "rpc stopped through API", cfg: Config{HTTPHost: "127.0.0.1"}, fn: func(t *testing.T, n *Node, api *privateAdminAPI) { - _, err := api.StopRPC() + _, err := api.StopHTTP() assert.NoError(t, err) }, wantReachable: false, @@ -121,10 +121,10 @@ func TestStartRPC(t *testing.T) { name: "rpc stopped twice", cfg: Config{HTTPHost: "127.0.0.1"}, fn: func(t *testing.T, n *Node, api *privateAdminAPI) { - _, err := api.StopRPC() + _, err := api.StopHTTP() assert.NoError(t, err) - _, err = api.StopRPC() + _, err = api.StopHTTP() assert.NoError(t, err) }, wantReachable: false, @@ -211,14 +211,14 @@ func TestStartRPC(t *testing.T) { { name: "rpc stopped with ws enabled", fn: func(t *testing.T, n *Node, api *privateAdminAPI) { - _, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil) + _, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil) assert.NoError(t, err) wsport := n.http.port _, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) assert.NoError(t, err) - _, err = api.StopRPC() + _, err = api.StopHTTP() assert.NoError(t, err) }, wantReachable: false, @@ -233,7 +233,7 @@ func TestStartRPC(t *testing.T) { assert.NoError(t, err) wsport := n.http.port - _, err = api.StartRPC(sp("127.0.0.1"), ip(wsport), nil, nil, nil) + _, err = api.StartHTTP(sp("127.0.0.1"), ip(wsport), nil, nil, nil) assert.NoError(t, err) }, wantReachable: true, From 5129cdc4f04889d3f21cadba5a246d62391301a6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 24 Mar 2021 12:32:39 +0100 Subject: [PATCH 413/948] cmd/devp2p: skip ENR field tails properly in nodeset filter (#22565) In Geth v1.10, we changed the structure of the "les" ENR entry. As a result, the DHT crawler that creates the DNS lists no longer recognizes the les nodes, which is fixed in this commit. * cmd/devp2p: skip ENR field tails properly in nodeset filter * cmd/devp2p: fix tail decoder for snap as well * les: fix tail decoding in "eth" ENR entry --- cmd/devp2p/nodesetcmd.go | 6 +++--- les/enr_entry.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index ba97405abc..33de1fdf31 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -173,7 +173,7 @@ func ethFilter(args []string) (nodeFilter, error) { f := func(n nodeJSON) bool { var eth struct { ForkID forkid.ID - _ []rlp.RawValue `rlp:"tail"` + Tail []rlp.RawValue `rlp:"tail"` } if n.N.Load(enr.WithEntry("eth", ð)) != nil { return false @@ -186,7 +186,7 @@ func ethFilter(args []string) (nodeFilter, error) { func lesFilter(args []string) (nodeFilter, error) { f := func(n nodeJSON) bool { var les struct { - _ []rlp.RawValue `rlp:"tail"` + Tail []rlp.RawValue `rlp:"tail"` } return n.N.Load(enr.WithEntry("les", &les)) == nil } @@ -196,7 +196,7 @@ func lesFilter(args []string) (nodeFilter, error) { func snapFilter(args []string) (nodeFilter, error) { f := func(n nodeJSON) bool { var snap struct { - _ []rlp.RawValue `rlp:"tail"` + Tail []rlp.RawValue `rlp:"tail"` } return n.N.Load(enr.WithEntry("snap", &snap)) == nil } diff --git a/les/enr_entry.go b/les/enr_entry.go index 892c3530d3..307313fb10 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -35,7 +35,7 @@ func (lesEntry) ENRKey() string { return "les" } // ethEntry is the "eth" ENR entry. This is redeclared here to avoid depending on package eth. type ethEntry struct { ForkID forkid.ID - _ []rlp.RawValue `rlp:"tail"` + Tail []rlp.RawValue `rlp:"tail"` } func (ethEntry) ENRKey() string { return "eth" } From 15e6c27f8bcc6a30b47b956701d70f5eb14c1b7c Mon Sep 17 00:00:00 2001 From: Chen Quan Date: Wed, 24 Mar 2021 20:18:29 +0800 Subject: [PATCH 414/948] p2p: fix minor typo and remove fd parameter in checkInboundConn (#22547) --- p2p/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index fc71548554..f70ebf7216 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -876,8 +876,8 @@ func (srv *Server) listenLoop() { } remoteIP := netutil.AddrIP(fd.RemoteAddr()) - if err := srv.checkInboundConn(fd, remoteIP); err != nil { - srv.log.Debug("Rejected inbound connnection", "addr", fd.RemoteAddr(), "err", err) + if err := srv.checkInboundConn(remoteIP); err != nil { + srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err) fd.Close() slots <- struct{}{} continue @@ -897,7 +897,7 @@ func (srv *Server) listenLoop() { } } -func (srv *Server) checkInboundConn(fd net.Conn, remoteIP net.IP) error { +func (srv *Server) checkInboundConn(remoteIP net.IP) error { if remoteIP == nil { return nil } From ab8fd4d005bd8d3fdf677f12c3b59f636c06f1a8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 24 Mar 2021 13:37:20 +0100 Subject: [PATCH 415/948] p2p/dnsdisc: rate limit resolving before checking cache (#22566) This makes the rate limit apply regardless of whether the node is already cached. --- p2p/dnsdisc/client.go | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index d3e8111ab5..3e4b50aadd 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -37,9 +37,10 @@ import ( // Client discovers nodes by querying DNS servers. type Client struct { - cfg Config - clock mclock.Clock - entries *lru.Cache + cfg Config + clock mclock.Clock + entries *lru.Cache + ratelimit *rate.Limiter } // Config holds configuration options for the client. @@ -97,8 +98,12 @@ func NewClient(cfg Config) *Client { panic(err) } rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10) - cfg.Resolver = &rateLimitResolver{cfg.Resolver, rlimit} - return &Client{cfg: cfg, entries: cache, clock: mclock.System{}} + return &Client{ + cfg: cfg, + entries: cache, + clock: mclock.System{}, + ratelimit: rlimit, + } } // SyncTree downloads the entire node tree at the given URL. @@ -157,6 +162,13 @@ func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) { // resolveEntry retrieves an entry from the cache or fetches it from the network // if it isn't cached. func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) { + // The rate limit always applies, even when the result might be cached. This is + // important because it avoids hot-spinning in consumers of node iterators created on + // this client. + if err := c.ratelimit.Wait(ctx); err != nil { + return nil, err + } + cacheKey := truncateHash(hash) if e, ok := c.entries.Get(cacheKey); ok { return e.(entry), nil @@ -196,19 +208,6 @@ func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry return nil, nameError{name, errNoEntry} } -// rateLimitResolver applies a rate limit to a Resolver. -type rateLimitResolver struct { - r Resolver - limiter *rate.Limiter -} - -func (r *rateLimitResolver) LookupTXT(ctx context.Context, domain string) ([]string, error) { - if err := r.limiter.Wait(ctx); err != nil { - return nil, err - } - return r.r.LookupTXT(ctx, domain) -} - // randomIterator traverses a set of trees and returns nodes found in them. type randomIterator struct { cur *enode.Node From c5df05b9a9529cb17b4416e23cd24583335c6a9d Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 24 Mar 2021 22:33:34 +0800 Subject: [PATCH 416/948] eth/protocols/snap: fix the flaws in the snap sync (#22553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: fix snap sync * eth/protocols/snap: fix tests * eth: fix tiny * eth: update tests * eth: update tests * core/state/snapshot: testcase for #22534 * eth/protocols/snap: fix boundary loss on full-but-proven range * core/state/snapshot: lintfix * eth: address comment * eth: fix handler Co-authored-by: Martin Holst Swende Co-authored-by: Péter Szilágyi --- eth/protocols/snap/handler.go | 9 +- eth/protocols/snap/sync.go | 24 +- eth/protocols/snap/sync_test.go | 845 +++++++++++++++++++++++++------- 3 files changed, 695 insertions(+), 183 deletions(-) diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index b9515b8a39..37e84839ab 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -256,8 +256,13 @@ func handleMessage(backend Backend, peer *Peer) error { var ( storage []*StorageData last common.Hash + abort bool ) - for it.Next() && size < hardLimit { + for it.Next() { + if size >= hardLimit { + abort = true + break + } hash, slot := it.Hash(), common.CopyBytes(it.Slot()) // Track the returned interval for the Merkle proofs @@ -280,7 +285,7 @@ func handleMessage(backend Backend, peer *Peer) error { // Generate the Merkle proofs for the first and last storage slot, but // only if the response was capped. If the entire storage trie included // in the response, no need for any proofs. - if origin != (common.Hash{}) || size >= hardLimit { + if origin != (common.Hash{}) || abort { // Request started at a non-zero hash or was capped prematurely, add // the endpoint Merkle proofs accTrie, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB()) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 0303e65eda..fa2dc16c39 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -1551,7 +1551,14 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { // Ensure that the response doesn't overflow into the subsequent task last := res.task.Last.Big() for i, hash := range res.hashes { - if hash.Big().Cmp(last) > 0 { + // Mark the range complete if the last is already included. + // Keep iteration to delete the extra states if exists. + cmp := hash.Big().Cmp(last) + if cmp == 0 { + res.cont = false + continue + } + if cmp > 0 { // Chunk overflown, cut off excess, but also update the boundary nodes for j := i; j < len(res.hashes); j++ { if err := res.trie.Prove(res.hashes[j][:], 0, res.overflow); err != nil { @@ -1758,7 +1765,14 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { // Ensure the response doesn't overflow into the subsequent task last := res.subTask.Last.Big() for k, hash := range res.hashes[i] { - if hash.Big().Cmp(last) > 0 { + // Mark the range complete if the last is already included. + // Keep iteration to delete the extra states if exists. + cmp := hash.Big().Cmp(last) + if cmp == 0 { + res.cont = false + continue + } + if cmp > 0 { // Chunk overflown, cut off excess, but also update the boundary for l := k; l < len(res.hashes[i]); l++ { if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { @@ -1785,11 +1799,15 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { it := res.nodes[i].NewIterator(nil, nil) for it.Next() { // Boundary nodes are not written for the last result, since they are incomplete - if i == len(res.hashes)-1 { + if i == len(res.hashes)-1 && res.subTask != nil { if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { skipped++ continue } + if _, err := res.overflow.Get(it.Key()); err == nil { + skipped++ + continue + } } // Node is not a boundary, persist to disk batch.Put(it.Key(), it.Value()) diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 49dff7bb3a..3e9778dbc7 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -23,6 +23,7 @@ import ( "fmt" "math/big" "sort" + "sync" "testing" "time" @@ -30,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -111,10 +113,12 @@ func BenchmarkHashing(b *testing.B) { }) } -type storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error -type accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error -type trieHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error -type codeHandlerFunc func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error +type ( + accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error + storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error + trieHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error + codeHandlerFunc func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error +) type testPeer struct { id string @@ -130,10 +134,10 @@ type testPeer struct { storageRequestHandler storageHandlerFunc trieRequestHandler trieHandlerFunc codeRequestHandler codeHandlerFunc - cancelCh chan struct{} + term func() } -func newTestPeer(id string, t *testing.T, cancelCh chan struct{}) *testPeer { +func newTestPeer(id string, t *testing.T, term func()) *testPeer { peer := &testPeer{ id: id, test: t, @@ -142,12 +146,11 @@ func newTestPeer(id string, t *testing.T, cancelCh chan struct{}) *testPeer { trieRequestHandler: defaultTrieRequestHandler, storageRequestHandler: defaultStorageRequestHandler, codeRequestHandler: defaultCodeRequestHandler, - cancelCh: cancelCh, + term: term, } //stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) //peer.logger.SetHandler(stderrHandler) return peer - } func (t *testPeer) ID() string { return t.id } @@ -155,7 +158,7 @@ func (t *testPeer) Log() log.Logger { return t.logger } func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error { t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) - go t.accountRequestHandler(t, id, root, origin, bytes) + go t.accountRequestHandler(t, id, root, origin, limit, bytes) return nil } @@ -211,20 +214,21 @@ func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, } // defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests -func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, cap uint64) error { - keys, vals, proofs := createAccountRequestResponse(t, root, origin, cap) +func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + keys, vals, proofs := createAccountRequestResponse(t, root, origin, limit, cap) if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil { - t.logger.Error("remote error on delivery", "error", err) t.test.Errorf("Remote side rejected our delivery: %v", err) - t.remote.Unregister(t.id) - close(t.cancelCh) + t.term() return err } return nil } -func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) { +func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) { var size uint64 + if limit == (common.Hash{}) { + limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } for _, entry := range t.accountValues { if size > cap { break @@ -234,20 +238,22 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H vals = append(vals, entry.v) size += uint64(32 + len(entry.v)) } + // If we've exceeded the request threshold, abort + if bytes.Compare(entry.k, limit[:]) >= 0 { + break + } } // Unless we send the entire trie, we need to supply proofs - // Actually, we need to supply proofs either way! This seems tob be an implementation + // Actually, we need to supply proofs either way! This seems to be an implementation // quirk in go-ethereum proof := light.NewNodeSet() if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { - t.logger.Error("Could not prove inexistence of origin", "origin", origin, - "error", err) + t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err) } if len(keys) > 0 { lastK := (keys[len(keys)-1])[:] if err := t.accountTrie.Prove(lastK, 0, proof); err != nil { - t.logger.Error("Could not prove last item", - "error", err) + t.logger.Error("Could not prove last item", "error", err) } } for _, blob := range proof.NodeList() { @@ -260,9 +266,8 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error { hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max) if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { - t.logger.Error("remote error on delivery", "error", err) t.test.Errorf("Remote side rejected our delivery: %v", err) - close(t.cancelCh) + t.term() } return nil } @@ -270,58 +275,112 @@ func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Has func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { var bytecodes [][]byte for _, h := range hashes { - bytecodes = append(bytecodes, getCode(h)) + bytecodes = append(bytecodes, getCodeByHash(h)) } if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { - t.logger.Error("remote error on delivery", "error", err) t.test.Errorf("Remote side rejected our delivery: %v", err) - close(t.cancelCh) + t.term() } return nil } -func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { - var ( - size uint64 - limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - ) - if len(bLimit) > 0 { - limit = common.BytesToHash(bLimit) +func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { + var size uint64 + for _, account := range accounts { + // The first account might start from a different origin and end sooner + var originHash common.Hash + if len(origin) > 0 { + originHash = common.BytesToHash(origin) + } + var limitHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + if len(limit) > 0 { + limitHash = common.BytesToHash(limit) + } + var ( + keys []common.Hash + vals [][]byte + abort bool + ) + for _, entry := range t.storageValues[account] { + if size >= max { + abort = true + break + } + if bytes.Compare(entry.k, originHash[:]) < 0 { + continue + } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) + size += uint64(32 + len(entry.v)) + if bytes.Compare(entry.k, limitHash[:]) >= 0 { + break + } + } + hashes = append(hashes, keys) + slots = append(slots, vals) + + // Generate the Merkle proofs for the first and last storage slot, but + // only if the response was capped. If the entire storage trie included + // in the response, no need for any proofs. + if originHash != (common.Hash{}) || abort { + // If we're aborting, we need to prove the first and last item + // This terminates the response (and thus the loop) + proof := light.NewNodeSet() + stTrie := t.storageTries[account] + + // Here's a potential gotcha: when constructing the proof, we cannot + // use the 'origin' slice directly, but must use the full 32-byte + // hash form. + if err := stTrie.Prove(originHash[:], 0, proof); err != nil { + t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "error", err) + } + if len(keys) > 0 { + lastK := (keys[len(keys)-1])[:] + if err := stTrie.Prove(lastK, 0, proof); err != nil { + t.logger.Error("Could not prove last item", "error", err) + } + } + for _, blob := range proof.NodeList() { + proofs = append(proofs, blob) + } + break + } } + return hashes, slots, proofs +} + +// the createStorageRequestResponseAlwaysProve tests a cornercase, where it always +// supplies the proof for the last account, even if it is 'complete'.h +func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { + var size uint64 + max = max * 3 / 4 + var origin common.Hash if len(bOrigin) > 0 { origin = common.BytesToHash(bOrigin) } - - var limitExceeded bool - var incomplete bool - for _, account := range accounts { - + var exit bool + for i, account := range accounts { var keys []common.Hash var vals [][]byte for _, entry := range t.storageValues[account] { - if limitExceeded { - incomplete = true - break - } if bytes.Compare(entry.k, origin[:]) < 0 { - incomplete = true - continue + exit = true } keys = append(keys, common.BytesToHash(entry.k)) vals = append(vals, entry.v) size += uint64(32 + len(entry.v)) - if bytes.Compare(entry.k, limit[:]) >= 0 { - limitExceeded = true - } if size > max { - limitExceeded = true + exit = true } } + if i == len(accounts)-1 { + exit = true + } hashes = append(hashes, keys) slots = append(slots, vals) - if incomplete { + if exit { // If we're aborting, we need to prove the first and last item // This terminates the response (and thus the loop) proof := light.NewNodeSet() @@ -350,21 +409,17 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm } // emptyRequestAccountRangeFn is a rejects AccountRangeRequests -func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { - var proofs [][]byte - var keys []common.Hash - var vals [][]byte - t.remote.OnAccounts(t, requestId, keys, vals, proofs) +func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + t.remote.OnAccounts(t, requestId, nil, nil, nil) return nil } -func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { +func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { return nil } func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error { - var nodes [][]byte - t.remote.OnTrieNodes(t, requestId, nodes) + t.remote.OnTrieNodes(t, requestId, nil) return nil } @@ -373,10 +428,7 @@ func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common. } func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { - var hashes [][]common.Hash - var slots [][][]byte - var proofs [][]byte - t.remote.OnStorage(t, requestId, hashes, slots, proofs) + t.remote.OnStorage(t, requestId, nil, nil, nil) return nil } @@ -384,6 +436,15 @@ func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root comm return nil } +func proofHappyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + hashes, slots, proofs := createStorageRequestResponseAlwaysProve(t, root, accounts, origin, limit, max) + if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil { + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() + } + return nil +} + //func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { // var bytecodes [][]byte // t.remote.OnByteCodes(t, id, bytecodes) @@ -397,7 +458,7 @@ func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max bytecodes = append(bytecodes, h[:]) } if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { - t.logger.Error("remote error on delivery", "error", err) + t.logger.Info("remote error on delivery (as expected)", "error", err) // Mimic the real-life handler, which drops a peer on errors t.remote.Unregister(t.id) } @@ -407,12 +468,12 @@ func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error { var bytecodes [][]byte for _, h := range hashes[:1] { - bytecodes = append(bytecodes, getCode(h)) + bytecodes = append(bytecodes, getCodeByHash(h)) } + // Missing bytecode can be retrieved again, no error expected if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil { - t.logger.Error("remote error on delivery", "error", err) - // Mimic the real-life handler, which drops a peer on errors - t.remote.Unregister(t.id) + t.test.Errorf("Remote side rejected our delivery: %v", err) + t.term() } return nil } @@ -422,16 +483,16 @@ func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Ha return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500) } -func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { - return defaultAccountRequestHandler(t, requestId, root, origin, 500) +func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + return defaultAccountRequestHandler(t, requestId, root, origin, limit, 500) } //func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { // return defaultAccountRequestHandler(t, requestId-1, root, origin, 500) //} -func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { - hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, cap) +func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, limit, cap) if len(proofs) > 0 { proofs = proofs[1:] } @@ -473,23 +534,36 @@ func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Has func TestSyncBloatedProof(t *testing.T) { t.Parallel() + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(100) - cancel := make(chan struct{}) - source := newTestPeer("source", t, cancel) + source := newTestPeer("source", t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems - source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error { - var proofs [][]byte - var keys []common.Hash - var vals [][]byte - + source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error { + var ( + proofs [][]byte + keys []common.Hash + vals [][]byte + ) // The values for _, entry := range t.accountValues { - if bytes.Compare(origin[:], entry.k) <= 0 { - keys = append(keys, common.BytesToHash(entry.k)) - vals = append(vals, entry.v) + if bytes.Compare(entry.k, origin[:]) < 0 { + continue + } + if bytes.Compare(entry.k, limit[:]) > 0 { + continue } + keys = append(keys, common.BytesToHash(entry.k)) + vals = append(vals, entry.v) } // The proofs proof := light.NewNodeSet() @@ -511,9 +585,9 @@ func TestSyncBloatedProof(t *testing.T) { proofs = append(proofs, blob) } if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil { - t.logger.Info("remote error on delivery", "error", err) + t.logger.Info("remote error on delivery (as expected)", "error", err) + t.term() // This is actually correct, signal to exit the test successfully - close(t.cancelCh) } return nil } @@ -537,20 +611,28 @@ func setupSyncer(peers ...*testPeer) *Syncer { func TestSync(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(100) mkSource := func(name string) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems return source } - - syncer := setupSyncer(mkSource("sourceA")) + syncer := setupSyncer(mkSource("source")) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a @@ -558,56 +640,79 @@ func TestSync(t *testing.T) { func TestSyncTinyTriePanic(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(1) mkSource := func(name string) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems return source } - - syncer := setupSyncer( - mkSource("nice-a"), - ) - done := checkStall(t, cancel) + syncer := setupSyncer(mkSource("source")) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSync tests a basic sync with multiple peers func TestMultiSync(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(100) mkSource := func(name string) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems return source } - syncer := setupSyncer(mkSource("sourceA"), mkSource("sourceB")) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorage tests basic sync using accounts + storage + code func TestSyncWithStorage(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false) mkSource := func(name string) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries @@ -615,33 +720,43 @@ func TestSyncWithStorage(t *testing.T) { return source } syncer := setupSyncer(mkSource("sourceA")) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all func TestMultiSyncManyUseless(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) - mkSource := func(name string, a, b, c bool) *testPeer { - source := newTestPeer(name, t, cancel) + mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries source.storageValues = storageElems - if !a { + if !noAccount { source.accountRequestHandler = emptyRequestAccountRangeFn } - if !b { + if !noStorage { source.storageRequestHandler = emptyStorageRequestHandler } - if !c { + if !noTrieNode { source.trieRequestHandler = emptyTrieRequestHandler } return source @@ -653,9 +768,12 @@ func TestMultiSyncManyUseless(t *testing.T) { mkSource("noStorage", true, false, true), mkSource("noTrie", true, true, false), ) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all @@ -666,24 +784,31 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { defer func(old time.Duration) { requestTimeout = old }(requestTimeout) requestTimeout = time.Millisecond - cancel := make(chan struct{}) - - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) - mkSource := func(name string, a, b, c bool) *testPeer { - source := newTestPeer(name, t, cancel) + mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries source.storageValues = storageElems - if !a { + if !noAccount { source.accountRequestHandler = emptyRequestAccountRangeFn } - if !b { + if !noStorage { source.storageRequestHandler = emptyStorageRequestHandler } - if !c { + if !noTrieNode { source.trieRequestHandler = emptyTrieRequestHandler } return source @@ -695,9 +820,12 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { mkSource("noStorage", true, false, true), mkSource("noTrie", true, true, false), ) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all @@ -706,24 +834,31 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { defer func(old time.Duration) { requestTimeout = old }(requestTimeout) requestTimeout = time.Millisecond - cancel := make(chan struct{}) - - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) - mkSource := func(name string, a, b, c bool) *testPeer { - source := newTestPeer(name, t, cancel) + mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries source.storageValues = storageElems - if !a { + if !noAccount { source.accountRequestHandler = nonResponsiveRequestAccountRangeFn } - if !b { + if !noStorage { source.storageRequestHandler = nonResponsiveStorageRequestHandler } - if !c { + if !noTrieNode { source.trieRequestHandler = nonResponsiveTrieRequestHandler } return source @@ -735,18 +870,21 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { mkSource("noStorage", true, false, true), mkSource("noTrie", true, true, false), ) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } -func checkStall(t *testing.T, cancel chan struct{}) chan struct{} { +func checkStall(t *testing.T, term func()) chan struct{} { testDone := make(chan struct{}) go func() { select { case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much t.Log("Sync stalled") - close(cancel) + term() case <-testDone: return } @@ -754,17 +892,58 @@ func checkStall(t *testing.T, cancel chan struct{}) chan struct{} { return testDone } +// TestSyncBoundaryAccountTrie tests sync against a few normal peers, but the +// account trie has a few boundary elements. +func TestSyncBoundaryAccountTrie(t *testing.T) { + t.Parallel() + + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems := makeBoundaryAccountTrie(3000) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + syncer := setupSyncer( + mkSource("peer-a"), + mkSource("peer-b"), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) +} + // TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is // consistently returning very small results func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, slow bool) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems @@ -780,11 +959,12 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { mkSource("nice-c", false), mkSource("capped", true), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver @@ -792,12 +972,19 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.codeRequestHandler = codeFn @@ -811,22 +998,30 @@ func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { mkSource("capped", cappedCodeRequestHandler), mkSource("corrupt", corruptCodeRequestHandler), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, accFn accountHandlerFunc) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.accountRequestHandler = accFn @@ -840,11 +1035,12 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { mkSource("capped", defaultAccountRequestHandler), mkSource("corrupt", corruptAccountRequestHandler), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes @@ -852,12 +1048,19 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.codeRequestHandler = codeFn @@ -872,7 +1075,7 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { return cappedCodeRequestHandler(t, id, hashes, max) }), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } @@ -885,6 +1088,43 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { if threshold := 100; counter > threshold { t.Fatalf("Error, expected < %d invocations, got %d", threshold, counter) } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncBoundaryStorageTrie tests sync against a few normal peers, but the +// storage trie has a few boundary elements. +func TestSyncBoundaryStorageTrie(t *testing.T) { + t.Parallel() + + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + return source + } + syncer := setupSyncer( + mkSource("peer-a"), + mkSource("peer-b"), + ) + done := checkStall(t, term) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is @@ -892,12 +1132,19 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false) mkSource := func(name string, slow bool) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries @@ -913,11 +1160,12 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { mkSource("nice-a", false), mkSource("slow", true), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is @@ -925,12 +1173,19 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { func TestSyncWithStorageAndCorruptPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries @@ -945,22 +1200,30 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { mkSource("nice-c", defaultStorageRequestHandler), mkSource("corrupt", corruptStorageRequestHandler), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { t.Parallel() - cancel := make(chan struct{}) - - sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true) + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { - source := newTestPeer(name, t, cancel) + source := newTestPeer(name, t, term) source.accountTrie = sourceAccountTrie source.accountValues = elems source.storageTries = storageTries @@ -968,23 +1231,55 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { source.storageRequestHandler = handler return source } - syncer := setupSyncer( mkSource("nice-a", defaultStorageRequestHandler), mkSource("nice-b", defaultStorageRequestHandler), mkSource("nice-c", defaultStorageRequestHandler), mkSource("corrupt", noProofStorageRequestHandler), ) - done := checkStall(t, cancel) + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } close(done) + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) +} + +// TestSyncWithStorage tests basic sync using accounts + storage + code, against +// a peer who insists on delivering full storage sets _and_ proofs. This triggered +// an error, where the recipient erroneously clipped the boundary nodes, but +// did not mark the account for healing. +func TestSyncWithStorageMisbehavingProve(t *testing.T) { + t.Parallel() + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(10, 30, false) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + source.storageTries = storageTries + source.storageValues = storageElems + source.storageRequestHandler = proofHappyStorageRequestHandler + return source + } + syncer := setupSyncer(mkSource("sourceA")) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) } type kv struct { k, v []byte - t bool } // Some helpers for sorting @@ -1013,14 +1308,14 @@ var ( } ) -// getACodeHash returns a pseudo-random code hash -func getACodeHash(i uint64) []byte { +// getCodeHash returns a pseudo-random code hash +func getCodeHash(i uint64) []byte { h := codehashes[int(i)%len(codehashes)] return common.CopyBytes(h[:]) } -// convenience function to lookup the code from the code hash -func getCode(hash common.Hash) []byte { +// getCodeByHash convenience function to lookup the code from the code hash +func getCodeByHash(hash common.Hash) []byte { if hash == emptyCode { return nil } @@ -1042,23 +1337,77 @@ func makeAccountTrieNoStorage(n int) (*trie.Trie, entrySlice) { Nonce: i, Balance: big.NewInt(int64(i)), Root: emptyRoot, - CodeHash: getACodeHash(i), + CodeHash: getCodeHash(i), }) key := key32(i) - elem := &kv{key, value, false} + elem := &kv{key, value} accTrie.Update(elem.k, elem.v) entries = append(entries, elem) } sort.Sort(entries) - // Push to disk layer accTrie.Commit(nil) return accTrie, entries } -// makeAccountTrieWithStorage spits out a trie, along with the leafs -func makeAccountTrieWithStorage(accounts, slots int, code bool) (*trie.Trie, entrySlice, - map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { +// makeBoundaryAccountTrie constructs an account trie. Instead of filling +// accounts normally, this function will fill a few accounts which have +// boundary hash. +func makeBoundaryAccountTrie(n int) (*trie.Trie, entrySlice) { + var ( + entries entrySlice + boundaries []common.Hash + db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + trie, _ = trie.New(common.Hash{}, db) + ) + // Initialize boundaries + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(accountConcurrency), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if i == accountConcurrency-1 { + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + boundaries = append(boundaries, last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + // Fill boundary accounts + for i := 0; i < len(boundaries); i++ { + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: uint64(0), + Balance: big.NewInt(int64(i)), + Root: emptyRoot, + CodeHash: getCodeHash(uint64(i)), + }) + elem := &kv{boundaries[i].Bytes(), value} + trie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + // Fill other accounts if required + for i := uint64(1); i <= uint64(n); i++ { + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: i, + Balance: big.NewInt(int64(i)), + Root: emptyRoot, + CodeHash: getCodeHash(i), + }) + elem := &kv{key32(i), value} + trie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + sort.Sort(entries) + trie.Commit(nil) + return trie, entries +} + +// makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts +// has a unique storage set. +func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { var ( db = trie.NewDatabase(rawdb.NewMemoryDatabase()) accTrie, _ = trie.New(common.Hash{}, db) @@ -1066,16 +1415,63 @@ func makeAccountTrieWithStorage(accounts, slots int, code bool) (*trie.Trie, ent storageTries = make(map[common.Hash]*trie.Trie) storageEntries = make(map[common.Hash]entrySlice) ) + // Create n accounts in the trie + for i := uint64(1); i <= uint64(accounts); i++ { + key := key32(i) + codehash := emptyCode[:] + if code { + codehash = getCodeHash(i) + } + // Create a storage trie + stTrie, stEntries := makeStorageTrieWithSeed(uint64(slots), i, db) + stRoot := stTrie.Hash() + stTrie.Commit(nil) + value, _ := rlp.EncodeToBytes(state.Account{ + Nonce: i, + Balance: big.NewInt(int64(i)), + Root: stRoot, + CodeHash: codehash, + }) + elem := &kv{key, value} + accTrie.Update(elem.k, elem.v) + entries = append(entries, elem) + + storageTries[common.BytesToHash(key)] = stTrie + storageEntries[common.BytesToHash(key)] = stEntries + } + sort.Sort(entries) + + accTrie.Commit(nil) + return accTrie, entries, storageTries, storageEntries +} +// makeAccountTrieWithStorage spits out a trie, along with the leafs +func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { + var ( + db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + accTrie, _ = trie.New(common.Hash{}, db) + entries entrySlice + storageTries = make(map[common.Hash]*trie.Trie) + storageEntries = make(map[common.Hash]entrySlice) + ) // Make a storage trie which we reuse for the whole lot - stTrie, stEntries := makeStorageTrie(slots, db) + var ( + stTrie *trie.Trie + stEntries entrySlice + ) + if boundary { + stTrie, stEntries = makeBoundaryStorageTrie(slots, db) + } else { + stTrie, stEntries = makeStorageTrieWithSeed(uint64(slots), 0, db) + } stRoot := stTrie.Hash() + // Create n accounts in the trie for i := uint64(1); i <= uint64(accounts); i++ { key := key32(i) codehash := emptyCode[:] if code { - codehash = getACodeHash(i) + codehash = getCodeHash(i) } value, _ := rlp.EncodeToBytes(state.Account{ Nonce: i, @@ -1083,7 +1479,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code bool) (*trie.Trie, ent Root: stRoot, CodeHash: codehash, }) - elem := &kv{key, value, false} + elem := &kv{key, value} accTrie.Update(elem.k, elem.v) entries = append(entries, elem) // we reuse the same one for all accounts @@ -1096,23 +1492,116 @@ func makeAccountTrieWithStorage(accounts, slots int, code bool) (*trie.Trie, ent return accTrie, entries, storageTries, storageEntries } -// makeStorageTrie fills a storage trie with n items, returning the -// not-yet-committed trie and the sorted entries -func makeStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice) { +// makeStorageTrieWithSeed fills a storage trie with n items, returning the +// not-yet-committed trie and the sorted entries. The seeds can be used to ensure +// that tries are unique. +func makeStorageTrieWithSeed(n, seed uint64, db *trie.Database) (*trie.Trie, entrySlice) { trie, _ := trie.New(common.Hash{}, db) var entries entrySlice - for i := uint64(1); i <= uint64(n); i++ { - // store 'i' at slot 'i' - slotValue := key32(i) + for i := uint64(1); i <= n; i++ { + // store 'x' at slot 'x' + slotValue := key32(i + seed) rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:])) slotKey := key32(i) key := crypto.Keccak256Hash(slotKey[:]) - elem := &kv{key[:], rlpSlotValue, false} + elem := &kv{key[:], rlpSlotValue} + trie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + sort.Sort(entries) + trie.Commit(nil) + return trie, entries +} + +// makeBoundaryStorageTrie constructs a storage trie. Instead of filling +// storage slots normally, this function will fill a few slots which have +// boundary hash. +func makeBoundaryStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice) { + var ( + entries entrySlice + boundaries []common.Hash + trie, _ = trie.New(common.Hash{}, db) + ) + // Initialize boundaries + var next common.Hash + step := new(big.Int).Sub( + new(big.Int).Div( + new(big.Int).Exp(common.Big2, common.Big256, nil), + big.NewInt(accountConcurrency), + ), common.Big1, + ) + for i := 0; i < accountConcurrency; i++ { + last := common.BigToHash(new(big.Int).Add(next.Big(), step)) + if i == accountConcurrency-1 { + last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + boundaries = append(boundaries, last) + next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + // Fill boundary slots + for i := 0; i < len(boundaries); i++ { + key := boundaries[i] + val := []byte{0xde, 0xad, 0xbe, 0xef} + + elem := &kv{key[:], val} + trie.Update(elem.k, elem.v) + entries = append(entries, elem) + } + // Fill other slots if required + for i := uint64(1); i <= uint64(n); i++ { + slotKey := key32(i) + key := crypto.Keccak256Hash(slotKey[:]) + + slotValue := key32(i) + rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:])) + + elem := &kv{key[:], rlpSlotValue} trie.Update(elem.k, elem.v) entries = append(entries, elem) } sort.Sort(entries) + trie.Commit(nil) return trie, entries } + +func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { + t.Helper() + triedb := trie.NewDatabase(db) + accTrie, err := trie.New(root, triedb) + if err != nil { + t.Fatal(err) + } + accounts, slots := 0, 0 + accIt := trie.NewIterator(accTrie.NodeIterator(nil)) + for accIt.Next() { + var acc struct { + Nonce uint64 + Balance *big.Int + Root common.Hash + CodeHash []byte + } + if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { + log.Crit("Invalid account encountered during snapshot creation", "err", err) + } + accounts++ + if acc.Root != emptyRoot { + storeTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + t.Fatal(err) + } + storeIt := trie.NewIterator(storeTrie.NodeIterator(nil)) + for storeIt.Next() { + slots++ + } + if err := storeIt.Err; err != nil { + t.Fatal(err) + } + } + } + if err := accIt.Err; err != nil { + t.Fatal(err) + } + t.Logf("accounts: %d, slots: %d", accounts, slots) +} From 0fda25e471aa0e061396050c3d5e59fbaaf1e7b0 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 25 Mar 2021 10:13:14 +0100 Subject: [PATCH 417/948] eth/tracers, core: use scopecontext in tracers, provide statedb in capturestart (#22333) Fixes the CaptureStart api to include the EVM, thus being able to set the statedb early on. This pr also exposes the struct we used internally in the interpreter to encapsulate the contract, mem, stack, rstack, so we pass it as a single struct to the tracer, and removes the error returns on the capture methods. --- core/vm/eips.go | 10 +- core/vm/evm.go | 6 +- core/vm/instructions.go | 424 ++++++++++++++++---------------- core/vm/instructions_test.go | 16 +- core/vm/interpreter.go | 24 +- core/vm/jump_table.go | 2 +- core/vm/logger.go | 44 ++-- core/vm/logger_json.go | 23 +- core/vm/logger_test.go | 16 +- core/vm/runtime/runtime_test.go | 19 +- eth/tracers/tracer.go | 113 ++++----- eth/tracers/tracer_test.go | 52 +++- 12 files changed, 384 insertions(+), 365 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 0c8bf1792e..6bb941d5f9 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -76,9 +76,9 @@ func enable1884(jt *JumpTable) { } } -func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) - callContext.stack.push(balance) +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(scope.Contract.Address())) + scope.Stack.push(balance) return nil, nil } @@ -95,9 +95,9 @@ func enable1344(jt *JumpTable) { } // opChainID implements CHAINID opcode -func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID) - callContext.stack.push(chainId) + scope.Stack.push(chainId) return nil, nil } diff --git a/core/vm/evm.go b/core/vm/evm.go index 7346d76e5b..6fac50f721 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -239,7 +239,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { - evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) + evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) } return nil, gas, nil @@ -250,7 +250,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { - evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) + evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) }(gas, time.Now()) @@ -472,7 +472,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } if evm.vmConfig.Debug && evm.depth == 0 { - evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value) + evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) } start := time.Now() diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f4ca0603ed..3277674ee8 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -24,68 +24,68 @@ import ( "golang.org/x/crypto/sha3" ) -func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Add(&x, y) return nil, nil } -func opSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Sub(&x, y) return nil, nil } -func opMul(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Mul(&x, y) return nil, nil } -func opDiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opDiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Div(&x, y) return nil, nil } -func opSdiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opSdiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.SDiv(&x, y) return nil, nil } -func opMod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opMod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Mod(&x, y) return nil, nil } -func opSmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opSmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.SMod(&x, y) return nil, nil } -func opExp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - base, exponent := callContext.stack.pop(), callContext.stack.peek() +func opExp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + base, exponent := scope.Stack.pop(), scope.Stack.peek() exponent.Exp(&base, exponent) return nil, nil } -func opSignExtend(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - back, num := callContext.stack.pop(), callContext.stack.peek() +func opSignExtend(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + back, num := scope.Stack.pop(), scope.Stack.peek() num.ExtendSign(num, &back) return nil, nil } -func opNot(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x := callContext.stack.peek() +func opNot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() x.Not(x) return nil, nil } -func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opLt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() if x.Lt(y) { y.SetOne() } else { @@ -94,8 +94,8 @@ func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte return nil, nil } -func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opGt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() if x.Gt(y) { y.SetOne() } else { @@ -104,8 +104,8 @@ func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte return nil, nil } -func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opSlt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() if x.Slt(y) { y.SetOne() } else { @@ -114,8 +114,8 @@ func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt return nil, nil } -func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opSgt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() if x.Sgt(y) { y.SetOne() } else { @@ -124,8 +124,8 @@ func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt return nil, nil } -func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opEq(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() if x.Eq(y) { y.SetOne() } else { @@ -134,8 +134,8 @@ func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte return nil, nil } -func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x := callContext.stack.peek() +func opIszero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() if x.IsZero() { x.SetOne() } else { @@ -144,32 +144,32 @@ func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] return nil, nil } -func opAnd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opAnd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.And(&x, y) return nil, nil } -func opOr(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opOr(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Or(&x, y) return nil, nil } -func opXor(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y := callContext.stack.pop(), callContext.stack.peek() +func opXor(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y := scope.Stack.pop(), scope.Stack.peek() y.Xor(&x, y) return nil, nil } -func opByte(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - th, val := callContext.stack.pop(), callContext.stack.peek() +func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + th, val := scope.Stack.pop(), scope.Stack.peek() val.Byte(&th) return nil, nil } -func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() +func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() if z.IsZero() { z.Clear() } else { @@ -178,8 +178,8 @@ func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] return nil, nil } -func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.peek() +func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek() z.MulMod(&x, &y, z) return nil, nil } @@ -187,9 +187,9 @@ func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] // opSHL implements Shift Left // The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the left by arg1 number of bits. -func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opSHL(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := callContext.stack.pop(), callContext.stack.peek() + shift, value := scope.Stack.pop(), scope.Stack.peek() if shift.LtUint64(256) { value.Lsh(value, uint(shift.Uint64())) } else { @@ -201,9 +201,9 @@ func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt // opSHR implements Logical Shift Right // The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill. -func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opSHR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := callContext.stack.pop(), callContext.stack.peek() + shift, value := scope.Stack.pop(), scope.Stack.peek() if shift.LtUint64(256) { value.Rsh(value, uint(shift.Uint64())) } else { @@ -215,8 +215,8 @@ func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt // opSAR implements Arithmetic Shift Right // The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension. -func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - shift, value := callContext.stack.pop(), callContext.stack.peek() +func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + shift, value := scope.Stack.pop(), scope.Stack.peek() if shift.GtUint64(256) { if value.Sign() >= 0 { value.Clear() @@ -231,9 +231,9 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byt return nil, nil } -func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - offset, size := callContext.stack.pop(), callContext.stack.peek() - data := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) +func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.peek() + data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) if interpreter.hasher == nil { interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState) @@ -251,37 +251,37 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by size.SetBytes(interpreter.hasherBuf[:]) return nil, nil } -func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Address().Bytes())) +func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes())) return nil, nil } -func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - slot := callContext.stack.peek() +func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) return nil, nil } -func opOrigin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) +func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil } -func opCaller(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetBytes(callContext.contract.Caller().Bytes())) +func opCaller(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes())) return nil, nil } -func opCallValue(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(callContext.contract.value) - callContext.stack.push(v) +func opCallValue(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v, _ := uint256.FromBig(scope.Contract.value) + scope.Stack.push(v) return nil, nil } -func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - x := callContext.stack.peek() +func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + x := scope.Stack.peek() if offset, overflow := x.Uint64WithOverflow(); !overflow { - data := getData(callContext.contract.Input, offset, 32) + data := getData(scope.Contract.Input, offset, 32) x.SetBytes(data) } else { x.Clear() @@ -289,16 +289,16 @@ func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCt return nil, nil } -func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(callContext.contract.Input)))) +func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input)))) return nil, nil } -func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = callContext.stack.pop() - dataOffset = callContext.stack.pop() - length = callContext.stack.pop() + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + length = scope.Stack.pop() ) dataOffset64, overflow := dataOffset.Uint64WithOverflow() if overflow { @@ -307,21 +307,21 @@ func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCt // These values are checked for overflow during gas cost calculation memOffset64 := memOffset.Uint64() length64 := length.Uint64() - callContext.memory.Set(memOffset64, length64, getData(callContext.contract.Input, dataOffset64, length64)) + scope.Memory.Set(memOffset64, length64, getData(scope.Contract.Input, dataOffset64, length64)) return nil, nil } -func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) +func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData)))) return nil, nil } -func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = callContext.stack.pop() - dataOffset = callContext.stack.pop() - length = callContext.stack.pop() + memOffset = scope.Stack.pop() + dataOffset = scope.Stack.pop() + length = scope.Stack.pop() ) offset64, overflow := dataOffset.Uint64WithOverflow() @@ -335,42 +335,42 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call if overflow || uint64(len(interpreter.returnData)) < end64 { return nil, ErrReturnDataOutOfBounds } - callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) return nil, nil } -func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - slot := callContext.stack.peek() +func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) return nil, nil } -func opCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { l := new(uint256.Int) - l.SetUint64(uint64(len(callContext.contract.Code))) - callContext.stack.push(l) + l.SetUint64(uint64(len(scope.Contract.Code))) + scope.Stack.push(l) return nil, nil } -func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - memOffset = callContext.stack.pop() - codeOffset = callContext.stack.pop() - length = callContext.stack.pop() + memOffset = scope.Stack.pop() + codeOffset = scope.Stack.pop() + length = scope.Stack.pop() ) uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() if overflow { uint64CodeOffset = 0xffffffffffffffff } - codeCopy := getData(callContext.contract.Code, uint64CodeOffset, length.Uint64()) - callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } -func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - stack = callContext.stack + stack = scope.Stack a = stack.pop() memOffset = stack.pop() codeOffset = stack.pop() @@ -382,7 +382,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx } addr := common.Address(a.Bytes20()) codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) - callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil } @@ -413,8 +413,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx // // (6) Caller tries to get the code hash for an account which is marked as deleted, // this account should be regarded as a non-existent account and zero should be returned. -func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - slot := callContext.stack.peek() +func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) if interpreter.evm.StateDB.Empty(address) { slot.Clear() @@ -424,14 +424,14 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx return nil, nil } -func opGasprice(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.GasPrice) - callContext.stack.push(v) + scope.Stack.push(v) return nil, nil } -func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - num := callContext.stack.peek() +func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + num := scope.Stack.peek() num64, overflow := num.Uint64WithOverflow() if overflow { num.Clear() @@ -452,88 +452,88 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) return nil, nil } -func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) +func opCoinbase(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) return nil, nil } -func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opTimestamp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.Context.Time) - callContext.stack.push(v) + scope.Stack.push(v) return nil, nil } -func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) - callContext.stack.push(v) + scope.Stack.push(v) return nil, nil } -func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) - callContext.stack.push(v) + scope.Stack.push(v) return nil, nil } -func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) +func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) return nil, nil } -func opPop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.pop() +func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.pop() return nil, nil } -func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v := callContext.stack.peek() +func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + v := scope.Stack.peek() offset := int64(v.Uint64()) - v.SetBytes(callContext.memory.GetPtr(offset, 32)) + v.SetBytes(scope.Memory.GetPtr(offset, 32)) return nil, nil } -func opMstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // pop value of the stack - mStart, val := callContext.stack.pop(), callContext.stack.pop() - callContext.memory.Set32(mStart.Uint64(), &val) + mStart, val := scope.Stack.pop(), scope.Stack.pop() + scope.Memory.Set32(mStart.Uint64(), &val) return nil, nil } -func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - off, val := callContext.stack.pop(), callContext.stack.pop() - callContext.memory.store[off.Uint64()] = byte(val.Uint64()) +func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + off, val := scope.Stack.pop(), scope.Stack.pop() + scope.Memory.store[off.Uint64()] = byte(val.Uint64()) return nil, nil } -func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - loc := callContext.stack.peek() +func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.peek() hash := common.Hash(loc.Bytes32()) - val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), hash) + val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) loc.SetBytes(val.Bytes()) return nil, nil } -func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - loc := callContext.stack.pop() - val := callContext.stack.pop() - interpreter.evm.StateDB.SetState(callContext.contract.Address(), +func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.pop() + val := scope.Stack.pop() + interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) return nil, nil } -func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - pos := callContext.stack.pop() - if !callContext.contract.validJumpdest(&pos) { +func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + pos := scope.Stack.pop() + if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump } *pc = pos.Uint64() return nil, nil } -func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - pos, cond := callContext.stack.pop(), callContext.stack.pop() +func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + pos, cond := scope.Stack.pop(), scope.Stack.pop() if !cond.IsZero() { - if !callContext.contract.validJumpdest(&pos) { + if !scope.Contract.validJumpdest(&pos) { return nil, ErrInvalidJump } *pc = pos.Uint64() @@ -543,31 +543,31 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]b return nil, nil } -func opJumpdest(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { return nil, nil } -func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(*pc)) +func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(*pc)) return nil, nil } -func opMsize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(uint64(callContext.memory.Len()))) +func opMsize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len()))) return nil, nil } -func opGas(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(callContext.contract.Gas)) +func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas)) return nil, nil } -func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - value = callContext.stack.pop() - offset, size = callContext.stack.pop(), callContext.stack.pop() - input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - gas = callContext.contract.Gas + value = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas ) if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 @@ -575,14 +575,14 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] // reuse size int for stackvalue stackvalue := size - callContext.contract.UseGas(gas) + scope.Contract.UseGas(gas) //TODO: use uint256.Int instead of converting with toBig() var bigVal = big0 if !value.IsZero() { bigVal = value.ToBig() } - res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal) + res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -594,8 +594,8 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] } else { stackvalue.SetBytes(addr.Bytes()) } - callContext.stack.push(&stackvalue) - callContext.contract.Gas += returnGas + scope.Stack.push(&stackvalue) + scope.Contract.Gas += returnGas if suberr == ErrExecutionReverted { return res, nil @@ -603,18 +603,18 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] return nil, nil } -func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - endowment = callContext.stack.pop() - offset, size = callContext.stack.pop(), callContext.stack.pop() - salt = callContext.stack.pop() - input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) - gas = callContext.contract.Gas + endowment = scope.Stack.pop() + offset, size = scope.Stack.pop(), scope.Stack.pop() + salt = scope.Stack.pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + gas = scope.Contract.Gas ) // Apply EIP150 gas -= gas / 64 - callContext.contract.UseGas(gas) + scope.Contract.UseGas(gas) // reuse size int for stackvalue stackvalue := size //TODO: use uint256.Int instead of converting with toBig() @@ -622,7 +622,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ if !endowment.IsZero() { bigEndowment = endowment.ToBig() } - res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, + res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas, bigEndowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { @@ -630,8 +630,8 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ } else { stackvalue.SetBytes(addr.Bytes()) } - callContext.stack.push(&stackvalue) - callContext.contract.Gas += returnGas + scope.Stack.push(&stackvalue) + scope.Contract.Gas += returnGas if suberr == ErrExecutionReverted { return res, nil @@ -639,8 +639,8 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ return nil, nil } -func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - stack := callContext.stack +func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. // We can use this as a temporary value temp := stack.pop() @@ -649,7 +649,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.Address(addr.Bytes20()) // Get the arguments from the memory. - args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() @@ -660,7 +660,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal) + ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal) if err != nil { temp.Clear() @@ -669,16 +669,16 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by } stack.push(&temp) if err == nil || err == ErrExecutionReverted { - callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - callContext.contract.Gas += returnGas + scope.Contract.Gas += returnGas return ret, nil } -func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - stack := callContext.stack + stack := scope.Stack // We use it as a temporary value temp := stack.pop() gas := interpreter.evm.callGasTemp @@ -686,7 +686,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) //TODO: use uint256.Int instead of converting with toBig() var bigVal = big0 @@ -695,7 +695,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal) + ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, bigVal) if err != nil { temp.Clear() } else { @@ -703,15 +703,15 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( } stack.push(&temp) if err == nil || err == ErrExecutionReverted { - callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - callContext.contract.Gas += returnGas + scope.Contract.Gas += returnGas return ret, nil } -func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - stack := callContext.stack +func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack // Pop gas. The actual gas is in interpreter.evm.callGasTemp. // We use it as a temporary value temp := stack.pop() @@ -720,9 +720,9 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCt addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - ret, returnGas, err := interpreter.evm.DelegateCall(callContext.contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas) if err != nil { temp.Clear() } else { @@ -730,16 +730,16 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCt } stack.push(&temp) if err == nil || err == ErrExecutionReverted { - callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - callContext.contract.Gas += returnGas + scope.Contract.Gas += returnGas return ret, nil } -func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - stack := callContext.stack + stack := scope.Stack // We use it as a temporary value temp := stack.pop() gas := interpreter.evm.callGasTemp @@ -747,9 +747,9 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.Address(addr.Bytes20()) // Get arguments from the memory. - args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - ret, returnGas, err := interpreter.evm.StaticCall(callContext.contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas) if err != nil { temp.Clear() } else { @@ -757,36 +757,36 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) } stack.push(&temp) if err == nil || err == ErrExecutionReverted { - callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - callContext.contract.Gas += returnGas + scope.Contract.Gas += returnGas return ret, nil } -func opReturn(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - offset, size := callContext.stack.pop(), callContext.stack.pop() - ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) +func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.pop() + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) return ret, nil } -func opRevert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - offset, size := callContext.stack.pop(), callContext.stack.pop() - ret := callContext.memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) +func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + offset, size := scope.Stack.pop(), scope.Stack.pop() + ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64())) return ret, nil } -func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { return nil, nil } -func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - beneficiary := callContext.stack.pop() - balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) +func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + beneficiary := scope.Stack.pop() + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) - interpreter.evm.StateDB.Suicide(callContext.contract.Address()) + interpreter.evm.StateDB.Suicide(scope.Contract.Address()) return nil, nil } @@ -794,18 +794,18 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ // make log instruction function func makeLog(size int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { topics := make([]common.Hash, size) - stack := callContext.stack + stack := scope.Stack mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { addr := stack.pop() topics[i] = addr.Bytes32() } - d := callContext.memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) + d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) interpreter.evm.StateDB.AddLog(&types.Log{ - Address: callContext.contract.Address(), + Address: scope.Contract.Address(), Topics: topics, Data: d, // This is a non-consensus field, but assigned here because @@ -818,24 +818,24 @@ func makeLog(size int) executionFunc { } // opPush1 is a specialized version of pushN -func opPush1(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { +func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - codeLen = uint64(len(callContext.contract.Code)) + codeLen = uint64(len(scope.Contract.Code)) integer = new(uint256.Int) ) *pc += 1 if *pc < codeLen { - callContext.stack.push(integer.SetUint64(uint64(callContext.contract.Code[*pc]))) + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) } else { - callContext.stack.push(integer.Clear()) + scope.Stack.push(integer.Clear()) } return nil, nil } // make push instruction function func makePush(size uint64, pushByteSize int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - codeLen := len(callContext.contract.Code) + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + codeLen := len(scope.Contract.Code) startMin := codeLen if int(*pc+1) < startMin { @@ -848,8 +848,8 @@ func makePush(size uint64, pushByteSize int) executionFunc { } integer := new(uint256.Int) - callContext.stack.push(integer.SetBytes(common.RightPadBytes( - callContext.contract.Code[startMin:endMin], pushByteSize))) + scope.Stack.push(integer.SetBytes(common.RightPadBytes( + scope.Contract.Code[startMin:endMin], pushByteSize))) *pc += size return nil, nil @@ -858,8 +858,8 @@ func makePush(size uint64, pushByteSize int) executionFunc { // make dup instruction function func makeDup(size int64) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.dup(int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.dup(int(size)) return nil, nil } } @@ -868,8 +868,8 @@ func makeDup(size int64) executionFunc { func makeSwap(size int64) executionFunc { // switch n + 1 otherwise n would be swapped with n size++ - return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.swap(int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.swap(int(size)) return nil, nil } } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 55d876581c..14f9e181f9 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -104,7 +104,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, &callCtx{nil, stack, nil}) + opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -219,7 +219,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evmInterpreter, &callCtx{nil, stack, nil}) + opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -241,7 +241,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, &callCtx{nil, stack, nil}) + opFn(&pc, interpreter, &ScopeContext{nil, stack, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -299,7 +299,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { a.SetBytes(arg) stack.push(a) } - op(&pc, evmInterpreter, &callCtx{nil, stack, nil}) + op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) stack.pop() } } @@ -525,12 +525,12 @@ func TestOpMstore(t *testing.T) { pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int)) - opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int)) - opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -553,7 +553,7 @@ func BenchmarkOpMstore(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.pushN(*value, *memStart) - opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) + opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) } } @@ -572,7 +572,7 @@ func BenchmarkOpSHA3(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.pushN(*uint256.NewInt().SetUint64(32), *start) - opSha3(&pc, evmInterpreter, &callCtx{mem, stack, nil}) + opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 06a3b962b2..3b67ad6dea 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -62,12 +62,12 @@ type Interpreter interface { CanRun([]byte) bool } -// callCtx contains the things that are per-call, such as stack and memory, +// ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas -type callCtx struct { - memory *Memory - stack *Stack - contract *Contract +type ScopeContext struct { + Memory *Memory + Stack *Stack + Contract *Contract } // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports @@ -163,10 +163,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( op OpCode // current opcode mem = NewMemory() // bound memory stack = newstack() // local stack - callContext = &callCtx{ - memory: mem, - stack: stack, - contract: contract, + callContext = &ScopeContext{ + Memory: mem, + Stack: stack, + Contract: contract, } // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC @@ -191,9 +191,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( defer func() { if err != nil { if !logged { - in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, in.returnData, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) } else { - in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err) } } }() @@ -275,7 +275,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } if in.cfg.Debug { - in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, in.returnData, contract, in.evm.depth, err) + in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) logged = true } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 7b61762456..bb1800ea91 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -21,7 +21,7 @@ import ( ) type ( - executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) diff --git a/core/vm/logger.go b/core/vm/logger.go index 41ce00ed01..9ccaafc772 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,7 +18,6 @@ package vm import ( "encoding/hex" - "errors" "fmt" "io" "math/big" @@ -32,8 +31,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var errTraceLimitReached = errors.New("the number of logs reached the specified limit") - // Storage represents a contract's storage. type Storage map[common.Hash]common.Hash @@ -107,10 +104,10 @@ func (s *StructLog) ErrorString() string { // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type Tracer interface { - CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error - CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error - CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error - CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error + CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) + CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) + CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) + CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) } // StructLogger is an EVM state logger and implements Tracer. @@ -139,17 +136,19 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { - return nil +func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } // CaptureState logs a new structured log message and pushes it out to the environment // // CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { +func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { + memory := scope.Memory + stack := scope.Stack + contract := scope.Contract // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { - return errTraceLimitReached + return } // Copy a snapshot of the current memory state to a new buffer var mem []byte @@ -199,17 +198,15 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui // create a new snapshot of the EVM. log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) - return nil } // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { - return nil +func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { } // CaptureEnd is called after the call finishes to finalize the tracing. -func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { +func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { l.output = output l.err = err if l.cfg.Debug { @@ -218,7 +215,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration fmt.Printf(" error: %v\n", err) } } - return nil } // StructLogs returns the captured log entries. @@ -292,7 +288,7 @@ func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { return l } -func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { +func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { if !create { fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", from.String(), to.String(), @@ -307,10 +303,11 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b | Pc | Op | Cost | Stack | RStack | Refund | |-------|-------------|------|-----------|-----------|---------| `) - return nil } -func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { +// CaptureState also tracks SLOAD/SSTORE ops to track storage change. +func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) if !t.cfg.DisableStack { @@ -327,18 +324,13 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 if err != nil { fmt.Fprintf(t.out, "Error: %v\n", err) } - return nil } -func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { - +func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) - - return nil } -func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error { +func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) { fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", output, gasUsed, err) - return nil } diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index a27c261ed8..e54be08596 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -41,12 +41,16 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { return l } -func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { - return nil +func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } +func (l *JSONLogger) CaptureFault(*EVM, uint64, OpCode, uint64, uint64, *ScopeContext, int, error) {} + // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rData []byte, contract *Contract, depth int, err error) error { +func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { + memory := scope.Memory + stack := scope.Stack + log := StructLog{ Pc: pc, Op: op, @@ -72,16 +76,11 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint if !l.cfg.DisableReturnData { log.ReturnData = rData } - return l.encoder.Encode(log) -} - -// CaptureFault outputs state information on the logger. -func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { - return nil + l.encoder.Encode(log) } // CaptureEnd is triggered at end of execution. -func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { +func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { type endLog struct { Output string `json:"output"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` @@ -89,7 +88,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, Err string `json:"error,omitempty"` } if err != nil { - return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()}) + l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()}) } - return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""}) + l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""}) } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 5a5f42fd34..9c936af36a 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -53,16 +53,20 @@ func TestStoreCapture(t *testing.T) { var ( env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) - mem = NewMemory() - stack = newstack() contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0) + scope = &ScopeContext{ + Memory: NewMemory(), + Stack: newstack(), + Contract: contract, + } ) - stack.push(uint256.NewInt().SetUint64(1)) - stack.push(uint256.NewInt()) + scope.Stack.push(uint256.NewInt().SetUint64(1)) + scope.Stack.push(uint256.NewInt()) var index common.Hash - logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, nil, contract, 0, nil) + logger.CaptureState(env, 0, SSTORE, 0, 0, scope, nil, 0, nil) if len(logger.storage[contract.Address()]) == 0 { - t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) + t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), + len(logger.storage[contract.Address()])) } exp := common.BigToHash(big.NewInt(1)) if logger.storage[contract.Address()][index] != exp { diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 6e0434c2ca..2692755324 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -326,23 +326,18 @@ type stepCounter struct { steps int } -func (s *stepCounter) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { - return nil +func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } -func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rData []byte, contract *vm.Contract, depth int, err error) error { - s.steps++ - // Enable this for more output - //s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err) - return nil +func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { } -func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { - return nil -} +func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {} -func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { - return nil +func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + s.steps++ + // Enable this for more output + //s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err) } // benchmarkNonModifyingCode benchmarks code, but if the code modifies the diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 4c398edf34..ba65925373 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -287,8 +287,6 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) { // Tracer provides an implementation of Tracer that evaluates a Javascript // function for each VM execution step. type Tracer struct { - inited bool // Flag whether the context was already inited from the EVM - vm *duktape.Context // Javascript VM instance tracerObject int // Stack index of the tracer JavaScript object @@ -529,7 +527,7 @@ func wrapError(context string, err error) error { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { +func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { jst.ctx["type"] = "CALL" if create { jst.ctx["type"] = "CREATE" @@ -540,77 +538,67 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b jst.ctx["gas"] = gas jst.ctx["value"] = value - return nil + // Initialize the context + jst.ctx["block"] = env.Context.BlockNumber.Uint64() + jst.dbWrapper.db = env.StateDB + // Compute intrinsic gas + isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) + isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) + intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) + if err != nil { + return + } + jst.ctx["intrinsicGas"] = intrinsicGas } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rdata []byte, contract *vm.Contract, depth int, err error) error { - if jst.err == nil { - // Initialize the context if it wasn't done yet - if !jst.inited { - jst.ctx["block"] = env.Context.BlockNumber.Uint64() - // Compute intrinsic gas - isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber) - isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber) - var input []byte - if data, ok := jst.ctx["input"].([]byte); ok { - input = data - } - intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul) - if err != nil { - return err - } - jst.ctx["intrinsicGas"] = intrinsicGas - jst.inited = true - } - // If tracing was interrupted, set the error and stop - if atomic.LoadUint32(&jst.interrupt) > 0 { - jst.err = jst.reason - return nil - } - jst.opWrapper.op = op - jst.stackWrapper.stack = stack - jst.memoryWrapper.memory = memory - jst.contractWrapper.contract = contract - jst.dbWrapper.db = env.StateDB - - *jst.pcValue = uint(pc) - *jst.gasValue = uint(gas) - *jst.costValue = uint(cost) - *jst.depthValue = uint(depth) - *jst.refundValue = uint(env.StateDB.GetRefund()) - - jst.errorValue = nil - if err != nil { - jst.errorValue = new(string) - *jst.errorValue = err.Error() - } - _, err := jst.call("step", "log", "db") - if err != nil { - jst.err = wrapError("step", err) - } +func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + if jst.err != nil { + return + } + // If tracing was interrupted, set the error and stop + if atomic.LoadUint32(&jst.interrupt) > 0 { + jst.err = jst.reason + return + } + jst.opWrapper.op = op + jst.stackWrapper.stack = scope.Stack + jst.memoryWrapper.memory = scope.Memory + jst.contractWrapper.contract = scope.Contract + + *jst.pcValue = uint(pc) + *jst.gasValue = uint(gas) + *jst.costValue = uint(cost) + *jst.depthValue = uint(depth) + *jst.refundValue = uint(env.StateDB.GetRefund()) + + jst.errorValue = nil + if err != nil { + jst.errorValue = new(string) + *jst.errorValue = err.Error() + } + + if _, err := jst.call("step", "log", "db"); err != nil { + jst.err = wrapError("step", err) } - return nil } // CaptureFault implements the Tracer interface to trace an execution fault -// while running an opcode. -func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { - if jst.err == nil { - // Apart from the error, everything matches the previous invocation - jst.errorValue = new(string) - *jst.errorValue = err.Error() +func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { + if jst.err != nil { + return + } + // Apart from the error, everything matches the previous invocation + jst.errorValue = new(string) + *jst.errorValue = err.Error() - _, err := jst.call("fault", "log", "db") - if err != nil { - jst.err = wrapError("fault", err) - } + if _, err := jst.call("fault", "log", "db"); err != nil { + jst.err = wrapError("fault", err) } - return nil } // CaptureEnd is called after the call finishes to finalize the tracing. -func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { +func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { jst.ctx["output"] = output jst.ctx["time"] = t.String() jst.ctx["gasUsed"] = gasUsed @@ -618,7 +606,6 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er if err != nil { jst.ctx["error"] = err.Error() } - return nil } // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index d96030385c..7cda2e5330 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -47,7 +47,8 @@ type dummyStatedb struct { state.StateDB } -func (*dummyStatedb) GetRefund() uint64 { return 1337 } +func (*dummyStatedb) GetRefund() uint64 { return 1337 } +func (*dummyStatedb) GetBalance(addr common.Address) *big.Int { return new(big.Int) } type vmContext struct { blockCtx vm.BlockContext @@ -67,7 +68,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { contract := vm.NewContract(account{}, account{}, value, startGas) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) + tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value) ret, err := env.Interpreter().Run(contract, []byte{}, false) tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err) if err != nil { @@ -150,14 +151,55 @@ func TestHaltBetweenSteps(t *testing.T) { t.Fatal(err) } env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) - contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) + scope := &vm.ScopeContext{ + Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), + } - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) + tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil) timeout := errors.New("stahp") tracer.Stop(timeout) - tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) + tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil) if _, err := tracer.GetResult(); err.Error() != timeout.Error() { t.Errorf("Expected timeout error, got %v", err) } } + +// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb +// in 'result' +func TestNoStepExec(t *testing.T) { + runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { + env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + startGas := uint64(10000) + contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas) + tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, big.NewInt(0)) + tracer.CaptureEnd(nil, startGas-contract.Gas, 1, nil) + return tracer.GetResult() + } + execTracer := func(code string) []byte { + t.Helper() + ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} + tracer, err := New(code, ctx.txCtx) + if err != nil { + t.Fatal(err) + } + ret, err := runEmptyTrace(tracer, ctx) + if err != nil { + t.Fatal(err) + } + return ret + } + for i, tt := range []struct { + code string + want string + }{ + { // tests that we don't panic on accessing the db methods + code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx, db){ return db.getBalance(ctx.to)} }", + want: `"0"`, + }, + } { + if have := execTracer(tt.code); tt.want != string(have) { + t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) + } + } +} From 497448bf90226eb088a41d1d102ffbb6cb2d1d6f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 25 Mar 2021 11:50:14 +0100 Subject: [PATCH 418/948] core: fix condition on header verification --- core/headerchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/headerchain.go b/core/headerchain.go index dcd3644cd1..9aee660eba 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -323,7 +323,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) seals := make([]bool, len(chain)) if checkFreq != 0 { // In case of checkFreq == 0 all seals are left false. - for i := 0; i < len(seals)/checkFreq; i++ { + for i := 0; i <= len(seals)/checkFreq; i++ { index := i*checkFreq + hc.rand.Intn(checkFreq) if index >= len(seals) { index = len(seals) - 1 From bed74b38d985e48d1e8b4b8c132e97a8987e07ac Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 25 Mar 2021 12:32:32 +0100 Subject: [PATCH 419/948] cmd/devp2p: fix comparison of TXT record value (#22572) * cmd/devp2p: fix comparison of TXT record value The AWS API returns quoted DNS strings, so we must encode the new value before comparing it against the existing record content. * cmd/devp2p: add test * cmd/devp2p: fix typo and rename val -> newValue --- cmd/devp2p/dns_route53.go | 35 +++++++++++++++++----------------- cmd/devp2p/dns_route53_test.go | 24 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index 010913060a..2b6a30e60f 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -131,7 +131,7 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { } } - // wait for all change batches to propagate + // Wait for all change batches to propagate. for _, change := range changesToCheck { log.Info(fmt.Sprintf("Waiting for change request %s", *change.ChangeInfo.Id)) wreq := &route53.GetChangeInput{Id: change.ChangeInfo.Id} @@ -196,24 +196,29 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e records = lrecords var changes []types.Change - for path, val := range records { + for path, newValue := range records { + prevRecords, exists := existing[path] + prevValue := strings.Join(prevRecords.values, "") + + // prevValue contains quoted strings, encode newValue to compare. + newValue = splitTXT(newValue) + + // Assign TTL. ttl := int64(rootTTL) if path != name { ttl = int64(treeNodeTTL) } - prevRecords, exists := existing[path] - prevValue := strings.Join(prevRecords.values, "") if !exists { // Entry is unknown, push a new one - log.Info(fmt.Sprintf("Creating %s = %q", path, val)) - changes = append(changes, newTXTChange("CREATE", path, ttl, splitTXT(val))) - } else if prevValue != val || prevRecords.ttl != ttl { + log.Info(fmt.Sprintf("Creating %s = %s", path, newValue)) + changes = append(changes, newTXTChange("CREATE", path, ttl, newValue)) + } else if prevValue != newValue || prevRecords.ttl != ttl { // Entry already exists, only change its content. - log.Info(fmt.Sprintf("Updating %s from %q to %q", path, prevValue, val)) - changes = append(changes, newTXTChange("UPSERT", path, ttl, splitTXT(val))) + log.Info(fmt.Sprintf("Updating %s from %s to %s", path, prevValue, newValue)) + changes = append(changes, newTXTChange("UPSERT", path, ttl, newValue)) } else { - log.Info(fmt.Sprintf("Skipping %s = %q", path, val)) + log.Debug(fmt.Sprintf("Skipping %s = %s", path, newValue)) } } @@ -288,21 +293,19 @@ func changeCount(ch types.Change) int { // collectRecords collects all TXT records below the given name. func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) { - log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID)) var req route53.ListResourceRecordSetsInput req.HostedZoneId = &c.zoneID existing := make(map[string]recordSet) - for { + for page := 0; ; page++ { + log.Info("Loading existing TXT records", "name", name, "zone", c.zoneID, "page", page) resp, err := c.api.ListResourceRecordSets(context.TODO(), &req) if err != nil { return existing, err } - for _, set := range resp.ResourceRecordSets { if !isSubdomain(*set.Name, name) || set.Type != types.RRTypeTxt { continue } - s := recordSet{ttl: *set.TTL} for _, rec := range set.ResourceRecords { s.values = append(s.values, *rec.Value) @@ -314,14 +317,12 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error if !resp.IsTruncated { break } - - // Set the cursor to the next batc. From the AWS docs: + // Set the cursor to the next batch. From the AWS docs: // // To display the next page of results, get the values of NextRecordName, // NextRecordType, and NextRecordIdentifier (if any) from the response. Then submit // another ListResourceRecordSets request, and specify those values for // StartRecordName, StartRecordType, and StartRecordIdentifier. - req.StartRecordIdentifier = resp.NextRecordIdentifier req.StartRecordName = resp.NextRecordName req.StartRecordType = resp.NextRecordType diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go index 600c281a28..e6eb516e6b 100644 --- a/cmd/devp2p/dns_route53_test.go +++ b/cmd/devp2p/dns_route53_test.go @@ -162,5 +162,29 @@ func TestRoute53ChangeSort(t *testing.T) { } } +// This test checks that computeChanges compares the quoted value of the records correctly. +func TestRoute53NoChange(t *testing.T) { + // Existing record set. + testTree0 := map[string]recordSet{ + "n": {ttl: rootTTL, values: []string{ + `"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`, + }}, + "2xs2367yhaxjfglzhvawlqd4zy.n": {ttl: treeNodeTTL, values: []string{ + `"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`, + }}, + } + // New set. + testTree1 := map[string]string{ + "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", + "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", + } + + var client route53Client + changes := client.computeChanges("n", testTree1, testTree0) + if len(changes) > 0 { + t.Fatalf("wrong changes (got %d, want 0)", len(changes)) + } +} + func sp(s string) *string { return &s } func ip(i int64) *int64 { return &i } From 54c0d573d75ab9baa239db3f071d6cb4d1ec6aad Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 25 Mar 2021 15:37:51 +0100 Subject: [PATCH 420/948] eth: dump rpc gas cap and tx fee cap (#22574) --- eth/ethconfig/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 5d0eece067..6be767aaf4 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -187,11 +187,11 @@ type Config struct { EVMInterpreter string // RPCGasCap is the global gas cap for eth-call variants. - RPCGasCap uint64 `toml:",omitempty"` + RPCGasCap uint64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for // send-transction variants. The unit is ether. - RPCTxFeeCap float64 `toml:",omitempty"` + RPCTxFeeCap float64 // Checkpoint is a hardcoded checkpoint which can be nil. Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` From 6d7ff6acea32b19b7c23c411748925a24873de9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 26 Mar 2021 14:00:06 +0200 Subject: [PATCH 421/948] eth/protocols, metrics, p2p: add handler performance metrics --- eth/protocols/eth/handler.go | 10 +++++++++- eth/protocols/snap/handler.go | 10 ++++++++++ metrics/histogram.go | 9 +++++++++ p2p/metrics.go | 11 ++++++++++- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 64648ed419..de5a38dcce 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -241,7 +242,14 @@ func handleMessage(backend Backend, peer *Peer) error { } else if peer.Version() >= ETH66 { handlers = eth66 } - + // Track the emount of time it takes to serve the request and run the handler + if metrics.Enabled { + h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) + defer func(start time.Time) { + sampler := func() metrics.Sample { return metrics.NewExpDecaySample(1028, 0.015) } + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) + }(time.Now()) + } if handler := handlers[msg.Code]; handler != nil { return handler(backend, msg, peer) } diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 37e84839ab..6622cd8718 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -19,12 +19,14 @@ package snap import ( "bytes" "fmt" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -128,6 +130,14 @@ func handleMessage(backend Backend, peer *Peer) error { } defer msg.Discard() + // Track the emount of time it takes to serve the request and run the handler + if metrics.Enabled { + h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) + defer func(start time.Time) { + sampler := func() metrics.Sample { return metrics.NewExpDecaySample(1028, 0.015) } + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) + }(time.Now()) + } // Handle the message depending on its contents switch { case msg.Code == GetAccountRangeMsg: diff --git a/metrics/histogram.go b/metrics/histogram.go index 46f3bbd2f1..2c54ce8b40 100644 --- a/metrics/histogram.go +++ b/metrics/histogram.go @@ -26,6 +26,15 @@ func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram { return r.GetOrRegister(name, func() Histogram { return NewHistogram(s) }).(Histogram) } +// GetOrRegisterHistogramLazy returns an existing Histogram or constructs and +// registers a new StandardHistogram. +func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram { + if nil == r { + r = DefaultRegistry + } + return r.GetOrRegister(name, func() Histogram { return NewHistogram(s()) }).(Histogram) +} + // NewHistogram constructs a new StandardHistogram from a Sample. func NewHistogram(s Sample) Histogram { if !Enabled { diff --git a/p2p/metrics.go b/p2p/metrics.go index 44946473fa..be0d2f495e 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -25,8 +25,17 @@ import ( ) const ( + // ingressMeterName is the prefix of the per-packet inbound metrics. ingressMeterName = "p2p/ingress" - egressMeterName = "p2p/egress" + + // egressMeterName is the prefix of the per-packet outbound metrics. + egressMeterName = "p2p/egress" + + // HandleHistName is the prefix of the per-packet serving time histograms. + HandleHistName = "p2p/handle" + + // WaitHistName is the prefix of the per-packet (req only) waiting time histograms. + WaitHistName = "p2p/wait" ) var ( From 2550e46269b05aa89127b06c1e18d51ebadd9da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 26 Mar 2021 16:14:12 +0200 Subject: [PATCH 422/948] eth/protocols, metrics: use resetting histograms for rare packets --- eth/protocols/eth/handler.go | 6 +++++- eth/protocols/snap/handler.go | 6 +++++- metrics/resetting_sample.go | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 metrics/resetting_sample.go diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index de5a38dcce..0dc3de9898 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -246,7 +246,11 @@ func handleMessage(backend Backend, peer *Peer) error { if metrics.Enabled { h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) defer func(start time.Time) { - sampler := func() metrics.Sample { return metrics.NewExpDecaySample(1028, 0.015) } + sampler := func() metrics.Sample { + return metrics.ResettingSample( + metrics.NewExpDecaySample(1028, 0.015), + ) + } metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) }(time.Now()) } diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 6622cd8718..4169306c25 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -134,7 +134,11 @@ func handleMessage(backend Backend, peer *Peer) error { if metrics.Enabled { h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) defer func(start time.Time) { - sampler := func() metrics.Sample { return metrics.NewExpDecaySample(1028, 0.015) } + sampler := func() metrics.Sample { + return metrics.ResettingSample( + metrics.NewExpDecaySample(1028, 0.015), + ) + } metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) }(time.Now()) } diff --git a/metrics/resetting_sample.go b/metrics/resetting_sample.go new file mode 100644 index 0000000000..43c1129cd0 --- /dev/null +++ b/metrics/resetting_sample.go @@ -0,0 +1,24 @@ +package metrics + +// ResettingSample converts an ordinary sample into one that resets whenever its +// snapshot is retrieved. This will break for multi-monitor systems, but when only +// a single metric is being pushed out, this ensure that low-frequency events don't +// skew th charts indefinitely. +func ResettingSample(sample Sample) Sample { + return &resettingSample{ + Sample: sample, + } +} + +// resettingSample is a simple wrapper around a sample that resets it upon the +// snapshot retrieval. +type resettingSample struct { + Sample +} + +// Snapshot returns a read-only copy of the sample with the original reset. +func (rs *resettingSample) Snapshot() Sample { + s := rs.Sample.Snapshot() + rs.Sample.Clear() + return s +} From 955727181be3d4f6d919440f9709e3bf736ff8a4 Mon Sep 17 00:00:00 2001 From: Zou Guangxian Date: Sat, 27 Mar 2021 01:06:25 +0800 Subject: [PATCH 423/948] eth: fix corner case in sync head determination (#21695) This avoids synchronisation failures when the local header is ahead of the local full block. --- eth/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/sync.go b/eth/sync.go index dc72e88388..4520ec6879 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -289,8 +289,8 @@ func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) { } } // Nope, we're really full syncing - head := cs.handler.chain.CurrentHeader() - td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64()) + head := cs.handler.chain.CurrentBlock() + td := cs.handler.chain.GetTd(head.Hash(), head.NumberU64()) return downloader.FullSync, td } From cae6b5527e34befb05d21e2c3d635d82d8c32582 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 26 Mar 2021 18:30:10 +0100 Subject: [PATCH 424/948] cmd/geth, consensus/ethash: add support for --miner.notify.full flag (#22558) The PR implements the --miner.notify.full flag that enables full pending block notifications. When this flag is used, the block notifications sent to mining endpoints contain the complete block header JSON instead of a work package array. Co-authored-by: AlexSSD7 Co-authored-by: Martin Holst Swende --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 5 ++ consensus/ethash/algorithm_test.go | 8 ++- consensus/ethash/ethash.go | 28 +++++---- consensus/ethash/ethash_test.go | 9 ++- consensus/ethash/sealer.go | 11 +++- consensus/ethash/sealer_test.go | 94 ++++++++++++++++++++++++++++++ eth/backend.go | 6 +- eth/ethconfig/config.go | 30 +++++----- miner/miner.go | 17 +++--- 11 files changed, 171 insertions(+), 39 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 5559835499..556ad0dbf3 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -152,6 +152,7 @@ var ( utils.GpoMaxGasPriceFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, + utils.MinerNotifyFullFlag, configFileFlag, } diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index daea0afc4e..d7f8fd7ab9 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -180,6 +180,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MiningEnabledFlag, utils.MinerThreadsFlag, utils.MinerNotifyFlag, + utils.MinerNotifyFullFlag, utils.MinerGasPriceFlag, utils.MinerGasTargetFlag, utils.MinerGasLimitFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 00b28bddf6..a602d5a35e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -427,6 +427,10 @@ var ( Name: "miner.notify", Usage: "Comma separated HTTP URL list to notify of new work packages", } + MinerNotifyFullFlag = cli.BoolFlag{ + Name: "miner.notify.full", + Usage: "Notify with pending block headers instead of work packages", + } MinerGasTargetFlag = cli.Uint64Flag{ Name: "miner.gastarget", Usage: "Target gas floor for mined blocks", @@ -1359,6 +1363,7 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { if ctx.GlobalIsSet(MinerNotifyFlag.Name) { cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",") } + cfg.NotifyFull = ctx.GlobalBool(MinerNotifyFullFlag.Name) if ctx.GlobalIsSet(MinerExtraDataFlag.Name) { cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name)) } diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go index 663687b81c..9cc9d535d4 100644 --- a/consensus/ethash/algorithm_test.go +++ b/consensus/ethash/algorithm_test.go @@ -726,10 +726,14 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { for i := 0; i < 3; i++ { pend.Add(1) - go func(idx int) { defer pend.Done() - ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false) + + config := Config{ + CacheDir: cachedir, + CachesOnDisk: 1, + } + ethash := New(config, nil, false) defer ethash.Close() if err := ethash.verifySeal(nil, block.Header(), false); err != nil { t.Errorf("proc %d: block verification failed: %v", idx, err) diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 1afdc9381a..d922be7773 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -48,7 +48,7 @@ var ( two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // sharedEthash is a full instance that can be shared between multiple users. - sharedEthash = New(Config{"", 3, 0, false, "", 1, 0, false, ModeNormal, nil}, nil, false) + sharedEthash *Ethash // algorithmRevision is the data structure version used for file naming. algorithmRevision = 23 @@ -57,6 +57,15 @@ var ( dumpMagic = []uint32{0xbaddcafe, 0xfee1dead} ) +func init() { + sharedConfig := Config{ + PowMode: ModeNormal, + CachesInMem: 3, + DatasetsInMem: 1, + } + sharedEthash = New(sharedConfig, nil, false) +} + // isLittleEndian returns whether the local system is running in little or big // endian byte order. func isLittleEndian() bool { @@ -411,6 +420,10 @@ type Config struct { DatasetsLockMmap bool PowMode Mode + // When set, notifications sent by the remote sealer will + // be block header JSON objects instead of work package arrays. + NotifyFull bool + Log log.Logger `toml:"-"` } @@ -462,6 +475,9 @@ func New(config Config, notify []string, noverify bool) *Ethash { update: make(chan struct{}), hashrate: metrics.NewMeterForced(), } + if config.PowMode == ModeShared { + ethash.shared = sharedEthash + } ethash.remote = startRemoteSealer(ethash, notify, noverify) return ethash } @@ -469,15 +485,7 @@ func New(config Config, notify []string, noverify bool) *Ethash { // NewTester creates a small sized ethash PoW scheme useful only for testing // purposes. func NewTester(notify []string, noverify bool) *Ethash { - ethash := &Ethash{ - config: Config{PowMode: ModeTest, Log: log.Root()}, - caches: newlru("cache", 1, newCache), - datasets: newlru("dataset", 1, newDataset), - update: make(chan struct{}), - hashrate: metrics.NewMeterForced(), - } - ethash.remote = startRemoteSealer(ethash, notify, noverify) - return ethash + return New(Config{PowMode: ModeTest}, notify, noverify) } // NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index 2639707eb2..eb3850b3b0 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -62,7 +62,14 @@ func TestCacheFileEvict(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tmpdir) - e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest}, nil, false) + + config := Config{ + CachesInMem: 3, + CachesOnDisk: 10, + CacheDir: tmpdir, + PowMode: ModeTest, + } + e := New(config, nil, false) defer e.Close() workers := 8 diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go index 2053534028..1830e672b1 100644 --- a/consensus/ethash/sealer.go +++ b/consensus/ethash/sealer.go @@ -358,7 +358,16 @@ func (s *remoteSealer) makeWork(block *types.Block) { // new work to be processed. func (s *remoteSealer) notifyWork() { work := s.currentWork - blob, _ := json.Marshal(work) + + // Encode the JSON payload of the notification. When NotifyFull is set, + // this is the complete block header, otherwise it is a JSON array. + var blob []byte + if s.ethash.config.NotifyFull { + blob, _ = json.Marshal(s.currentBlock.Header()) + } else { + blob, _ = json.Marshal(work) + } + s.reqWG.Add(len(s.notifyURLs)) for _, url := range s.notifyURLs { go s.sendNotification(s.notifyCtx, url, blob, work) diff --git a/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go index 20ed2a4184..c34e76aec2 100644 --- a/consensus/ethash/sealer_test.go +++ b/consensus/ethash/sealer_test.go @@ -22,6 +22,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "strconv" "testing" "time" @@ -74,6 +75,50 @@ func TestRemoteNotify(t *testing.T) { } } +// Tests whether remote HTTP servers are correctly notified of new work. (Full pending block body / --miner.notify.full) +func TestRemoteNotifyFull(t *testing.T) { + // Start a simple web server to capture notifications. + sink := make(chan map[string]interface{}) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + blob, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("failed to read miner notification: %v", err) + } + var work map[string]interface{} + if err := json.Unmarshal(blob, &work); err != nil { + t.Errorf("failed to unmarshal miner notification: %v", err) + } + sink <- work + })) + defer server.Close() + + // Create the custom ethash engine. + config := Config{ + PowMode: ModeTest, + NotifyFull: true, + Log: testlog.Logger(t, log.LvlWarn), + } + ethash := New(config, []string{server.URL}, false) + defer ethash.Close() + + // Stream a work task and ensure the notification bubbles out. + header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} + block := types.NewBlockWithHeader(header) + + ethash.Seal(nil, block, nil, nil) + select { + case work := <-sink: + if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want { + t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want) + } + if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want { + t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want) + } + case <-time.After(3 * time.Second): + t.Fatalf("notification timed out") + } +} + // Tests that pushing work packages fast to the miner doesn't cause any data race // issues in the notifications. func TestRemoteMultiNotify(t *testing.T) { @@ -119,6 +164,55 @@ func TestRemoteMultiNotify(t *testing.T) { } } +// Tests that pushing work packages fast to the miner doesn't cause any data race +// issues in the notifications. Full pending block body / --miner.notify.full) +func TestRemoteMultiNotifyFull(t *testing.T) { + // Start a simple web server to capture notifications. + sink := make(chan map[string]interface{}, 64) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + blob, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("failed to read miner notification: %v", err) + } + var work map[string]interface{} + if err := json.Unmarshal(blob, &work); err != nil { + t.Errorf("failed to unmarshal miner notification: %v", err) + } + sink <- work + })) + defer server.Close() + + // Create the custom ethash engine. + config := Config{ + PowMode: ModeTest, + NotifyFull: true, + Log: testlog.Logger(t, log.LvlWarn), + } + ethash := New(config, []string{server.URL}, false) + defer ethash.Close() + + // Provide a results reader. + // Otherwise the unread results will be logged asynchronously + // and this can happen after the test is finished, causing a panic. + results := make(chan *types.Block, cap(sink)) + + // Stream a lot of work task and ensure all the notifications bubble out. + for i := 0; i < cap(sink); i++ { + header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)} + block := types.NewBlockWithHeader(header) + ethash.Seal(nil, block, results, nil) + } + + for i := 0; i < cap(sink); i++ { + select { + case <-sink: + <-results + case <-time.After(10 * time.Second): + t.Fatalf("notification %d timed out", i) + } + } +} + // Tests whether stale solutions are correctly processed. func TestStaleSubmission(t *testing.T) { ethash := NewTester(nil, true) diff --git a/eth/backend.go b/eth/backend.go index 6e45d27501..9cf8b85663 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -121,6 +121,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) + // Transfer mining-related config to the ethash config. + ethashConfig := config.Ethash + ethashConfig.NotifyFull = config.Miner.NotifyFull + // Assemble the Ethereum object chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) if err != nil { @@ -140,7 +144,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { chainDb: chainDb, eventMux: stack.EventMux(), accountManager: stack.AccountManager(), - engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, ðashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 6be767aaf4..0c6eb0bdd7 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -213,25 +213,23 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co switch config.PowMode { case ethash.ModeFake: log.Warn("Ethash used in fake mode") - return ethash.NewFaker() case ethash.ModeTest: log.Warn("Ethash used in test mode") - return ethash.NewTester(nil, noverify) case ethash.ModeShared: log.Warn("Ethash used in shared mode") - return ethash.NewShared() - default: - engine := ethash.New(ethash.Config{ - CacheDir: stack.ResolvePath(config.CacheDir), - CachesInMem: config.CachesInMem, - CachesOnDisk: config.CachesOnDisk, - CachesLockMmap: config.CachesLockMmap, - DatasetDir: config.DatasetDir, - DatasetsInMem: config.DatasetsInMem, - DatasetsOnDisk: config.DatasetsOnDisk, - DatasetsLockMmap: config.DatasetsLockMmap, - }, notify, noverify) - engine.SetThreads(-1) // Disable CPU mining - return engine } + engine := ethash.New(ethash.Config{ + PowMode: config.PowMode, + CacheDir: stack.ResolvePath(config.CacheDir), + CachesInMem: config.CachesInMem, + CachesOnDisk: config.CachesOnDisk, + CachesLockMmap: config.CachesLockMmap, + DatasetDir: config.DatasetDir, + DatasetsInMem: config.DatasetsInMem, + DatasetsOnDisk: config.DatasetsOnDisk, + DatasetsLockMmap: config.DatasetsLockMmap, + NotifyFull: config.NotifyFull, + }, notify, noverify) + engine.SetThreads(-1) // Disable CPU mining + return engine } diff --git a/miner/miner.go b/miner/miner.go index 20169f5007..4d71e307a6 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -42,14 +42,15 @@ type Backend interface { // Config is the configuration parameters of mining. type Config struct { - Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account) - Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages(only useful in ethash). - ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner - GasFloor uint64 // Target gas floor for mined blocks. - GasCeil uint64 // Target gas ceiling for mined blocks. - GasPrice *big.Int // Minimum gas price for mining a transaction - Recommit time.Duration // The time interval for miner to re-create mining work. - Noverify bool // Disable remote mining solution verification(only useful in ethash). + Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account) + Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash). + NotifyFull bool `toml:",omitempty"` // Notify with pending block headers instead of work packages + ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner + GasFloor uint64 // Target gas floor for mined blocks. + GasCeil uint64 // Target gas ceiling for mined blocks. + GasPrice *big.Int // Minimum gas price for mining a transaction + Recommit time.Duration // The time interval for miner to re-create mining work. + Noverify bool // Disable remote mining solution verification(only useful in ethash). } // Miner creates blocks and searches for proof-of-work values. From 62379f02c61f459020a1fb8d7fdea4935a1d8701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 26 Mar 2021 21:13:52 +0200 Subject: [PATCH 425/948] metrics/influxdb: don't push empty histograms, no measurement != 0 --- metrics/influxdb/influxdb.go | 43 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/metrics/influxdb/influxdb.go b/metrics/influxdb/influxdb.go index d4fc478e7b..52d0091034 100644 --- a/metrics/influxdb/influxdb.go +++ b/metrics/influxdb/influxdb.go @@ -162,26 +162,29 @@ func (r *reporter) send() error { }) case metrics.Histogram: ms := metric.Snapshot() - ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) - pts = append(pts, client.Point{ - Measurement: fmt.Sprintf("%s%s.histogram", namespace, name), - Tags: r.tags, - Fields: map[string]interface{}{ - "count": ms.Count(), - "max": ms.Max(), - "mean": ms.Mean(), - "min": ms.Min(), - "stddev": ms.StdDev(), - "variance": ms.Variance(), - "p50": ps[0], - "p75": ps[1], - "p95": ps[2], - "p99": ps[3], - "p999": ps[4], - "p9999": ps[5], - }, - Time: now, - }) + + if ms.Count() > 0 { + ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) + pts = append(pts, client.Point{ + Measurement: fmt.Sprintf("%s%s.histogram", namespace, name), + Tags: r.tags, + Fields: map[string]interface{}{ + "count": ms.Count(), + "max": ms.Max(), + "mean": ms.Mean(), + "min": ms.Min(), + "stddev": ms.StdDev(), + "variance": ms.Variance(), + "p50": ps[0], + "p75": ps[1], + "p95": ps[2], + "p99": ps[3], + "p999": ps[4], + "p9999": ps[5], + }, + Time: now, + }) + } case metrics.Meter: ms := metric.Snapshot() pts = append(pts, client.Point{ From 099be0410048c65f885b9112f09238523fe19b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 26 Mar 2021 22:29:22 +0200 Subject: [PATCH 426/948] eth/protocols/snap: add peer id and req id to the timeout logs --- eth/protocols/snap/sync.go | 40 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index fa2dc16c39..af581df07c 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -811,6 +811,8 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { if idle == "" { return } + peer := s.peers[idle] + // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 for { @@ -834,14 +836,14 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { task: task, } req.timeout = time.AfterFunc(requestTimeout, func() { - log.Debug("Account range request timed out") + peer.Log().Debug("Account range request timed out", "reqid", reqid) s.scheduleRevertAccountRequest(req) }) s.accountReqs[reqid] = req delete(s.accountIdlers, idle) s.pend.Add(1) - go func(peer SyncPeer, root common.Hash) { + go func(root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -849,7 +851,7 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { peer.Log().Debug("Failed to request account range", "err", err) s.scheduleRevertAccountRequest(req) } - }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + }(s.root) // Inject the request into the task to block further assignments task.req = req @@ -891,6 +893,8 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { if idle == "" { return } + peer := s.peers[idle] + // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 for { @@ -921,14 +925,14 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { task: task, } req.timeout = time.AfterFunc(requestTimeout, func() { - log.Debug("Bytecode request timed out") + peer.Log().Debug("Bytecode request timed out", "reqid", reqid) s.scheduleRevertBytecodeRequest(req) }) s.bytecodeReqs[reqid] = req delete(s.bytecodeIdlers, idle) s.pend.Add(1) - go func(peer SyncPeer) { + go func() { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -936,7 +940,7 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { log.Debug("Failed to request bytecodes", "err", err) s.scheduleRevertBytecodeRequest(req) } - }(s.peers[idle]) // We're in the lock, peers[id] surely exists + }() } } @@ -976,6 +980,8 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { if idle == "" { return } + peer := s.peers[idle] + // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 for { @@ -1045,14 +1051,14 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { req.limit = subtask.Last } req.timeout = time.AfterFunc(requestTimeout, func() { - log.Debug("Storage request timed out") + peer.Log().Debug("Storage request timed out", "reqid", reqid) s.scheduleRevertStorageRequest(req) }) s.storageReqs[reqid] = req delete(s.storageIdlers, idle) s.pend.Add(1) - go func(peer SyncPeer, root common.Hash) { + go func(root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1064,7 +1070,7 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { log.Debug("Failed to request storage", "err", err) s.scheduleRevertStorageRequest(req) } - }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + }(s.root) // Inject the request into the subtask to block further assignments if subtask != nil { @@ -1121,6 +1127,8 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { if idle == "" { return } + peer := s.peers[idle] + // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 for { @@ -1160,14 +1168,14 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { task: s.healer, } req.timeout = time.AfterFunc(requestTimeout, func() { - log.Debug("Trienode heal request timed out") + peer.Log().Debug("Trienode heal request timed out", "reqid", reqid) s.scheduleRevertTrienodeHealRequest(req) }) s.trienodeHealReqs[reqid] = req delete(s.trienodeHealIdlers, idle) s.pend.Add(1) - go func(peer SyncPeer, root common.Hash) { + go func(root common.Hash) { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1175,7 +1183,7 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { log.Debug("Failed to request trienode healers", "err", err) s.scheduleRevertTrienodeHealRequest(req) } - }(s.peers[idle], s.root) // We're in the lock, peers[id] surely exists + }(s.root) } } @@ -1227,6 +1235,8 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { if idle == "" { return } + peer := s.peers[idle] + // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 for { @@ -1258,14 +1268,14 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { task: s.healer, } req.timeout = time.AfterFunc(requestTimeout, func() { - log.Debug("Bytecode heal request timed out") + peer.Log().Debug("Bytecode heal request timed out", "reqid", reqid) s.scheduleRevertBytecodeHealRequest(req) }) s.bytecodeHealReqs[reqid] = req delete(s.bytecodeHealIdlers, idle) s.pend.Add(1) - go func(peer SyncPeer) { + go func() { defer s.pend.Done() // Attempt to send the remote request and revert if it fails @@ -1273,7 +1283,7 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { log.Debug("Failed to request bytecode healers", "err", err) s.scheduleRevertBytecodeHealRequest(req) } - }(s.peers[idle]) // We're in the lock, peers[id] surely exists + }() } } From 27056f62e57e55ae91cfac95d7af24d1f707fb99 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 26 Mar 2021 22:15:20 +0100 Subject: [PATCH 427/948] cmd/devp2p: update to newer cloudflare API client (#22588) This upgrades the cloudflare client dependency to v0.14.0. The new version changes the API because all methods now require a context parameter. This change also reduces the log level of the 'Skipping...' message to debug, following a similar change in the AWS deployer. --- cmd/devp2p/dns_cloudflare.go | 14 ++++++++------ go.mod | 8 ++++---- go.sum | 30 ++++++++++++++++++------------ 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/cmd/devp2p/dns_cloudflare.go b/cmd/devp2p/dns_cloudflare.go index a4d10dcfdd..596254df91 100644 --- a/cmd/devp2p/dns_cloudflare.go +++ b/cmd/devp2p/dns_cloudflare.go @@ -17,6 +17,7 @@ package main import ( + "context" "fmt" "strings" @@ -79,7 +80,7 @@ func (c *cloudflareClient) checkZone(name string) error { c.zoneID = id } log.Info(fmt.Sprintf("Checking Permissions on zone %s", c.zoneID)) - zone, err := c.ZoneDetails(c.zoneID) + zone, err := c.ZoneDetails(context.Background(), c.zoneID) if err != nil { return err } @@ -112,7 +113,7 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) records = lrecords log.Info(fmt.Sprintf("Retrieving existing TXT records on %s", name)) - entries, err := c.DNSRecords(c.zoneID, cloudflare.DNSRecord{Type: "TXT"}) + entries, err := c.DNSRecords(context.Background(), c.zoneID, cloudflare.DNSRecord{Type: "TXT"}) if err != nil { return err } @@ -134,14 +135,15 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) if path != name { ttl = treeNodeTTL // Max TTL permitted by Cloudflare } - _, err = c.CreateDNSRecord(c.zoneID, cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl}) + record := cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl} + _, err = c.CreateDNSRecord(context.Background(), c.zoneID, record) } else if old.Content != val { // Entry already exists, only change its content. log.Info(fmt.Sprintf("Updating %s from %q to %q", path, old.Content, val)) old.Content = val - err = c.UpdateDNSRecord(c.zoneID, old.ID, old) + err = c.UpdateDNSRecord(context.Background(), c.zoneID, old.ID, old) } else { - log.Info(fmt.Sprintf("Skipping %s = %q", path, val)) + log.Debug(fmt.Sprintf("Skipping %s = %q", path, val)) } if err != nil { return fmt.Errorf("failed to publish %s: %v", path, err) @@ -155,7 +157,7 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) } // Stale entry, nuke it. log.Info(fmt.Sprintf("Deleting %s = %q", path, entry.Content)) - if err := c.DeleteDNSRecord(c.zoneID, entry.ID); err != nil { + if err := c.DeleteDNSRecord(context.Background(), c.zoneID, entry.ID); err != nil { return fmt.Errorf("failed to delete %s: %v", path, err) } } diff --git a/go.mod b/go.mod index dc020f9a65..0b0e8bf373 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 - github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 + github.com/cloudflare/cloudflare-go v0.14.0 github.com/consensys/gurvy v0.3.8 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea @@ -40,7 +40,7 @@ require ( github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 - github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c + github.com/olekukonko/tablewriter v0.0.5 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 @@ -52,8 +52,8 @@ require ( github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c - golang.org/x/text v0.3.3 - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 + golang.org/x/text v0.3.4 + golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 diff --git a/go.sum b/go.sum index 813aa8e27d..1b7212e420 100644 --- a/go.sum +++ b/go.sum @@ -60,7 +60,6 @@ github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1: github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v1.2.0 h1:BS+UYpbsElC82gB+2E2jiCBg36i8HlubTB/dO/moQ9c= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= @@ -108,8 +107,9 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk= github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu2VvzMZlOFA= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc= github.com/consensys/gurvy v0.3.8 h1:H2hvjvT2OFMgdMn5ZbhXqHt+F8DJ2clZW7Vmc0kFFxc= @@ -209,7 +209,6 @@ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= @@ -284,11 +283,8 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -339,8 +335,9 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -365,8 +362,9 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk= github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -390,8 +388,9 @@ github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssy github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -464,6 +463,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -537,8 +537,9 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -578,17 +579,21 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -686,6 +691,7 @@ gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHO gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= From 76700ac892f6fe3d7865fbe2f724626c72a04227 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 29 Mar 2021 17:09:29 +0800 Subject: [PATCH 428/948] core/state/pruner: move the compaction out of the pruning procedure (#22579) The main idea behind it is: the range compaction is very expensive which can take a few hours to finish. During this long procedure, a lot of exceptions can occur, e.g. - Geth is killed manually - Geth is killed because of machine crash - etc In order to minimize the effect of the exceptions, the compaction is moved out of the pruning. So that even the compaction is not finished, the pruning is regarded as done. --- core/state/pruner/pruner.go | 68 ++++++++++++------------------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 62cc7b0120..4d6e415511 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -113,7 +113,7 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint6 }, nil } -func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[common.Hash]struct{}, start time.Time) error { +func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, stateBloom *stateBloom, bloomPath string, middleStateRoots map[common.Hash]struct{}, start time.Time) error { // Delete all stale trie nodes in the disk. With the help of state bloom // the trie nodes(and codes) belong to the active state will be filtered // out. A very small part of stale tries will also be filtered because of @@ -186,6 +186,25 @@ func prune(maindb ethdb.Database, stateBloom *stateBloom, middleStateRoots map[c iter.Release() log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart))) + // Pruning is done, now drop the "useless" layers from the snapshot. + // Firstly, flushing the target layer into the disk. After that all + // diff layers below the target will all be merged into the disk. + if err := snaptree.Cap(root, 0); err != nil { + return err + } + // Secondly, flushing the snapshot journal into the disk. All diff + // layers upon are dropped silently. Eventually the entire snapshot + // tree is converted into a single disk layer with the pruning target + // as the root. + if _, err := snaptree.Journal(root); err != nil { + return err + } + // Delete the state bloom, it marks the entire pruning procedure is + // finished. If any crashes or manual exit happens before this, + // `RecoverPruning` will pick it up in the next restarts to redo all + // the things. + os.RemoveAll(bloomPath) + // Start compactions, will remove the deleted data from the disk immediately. // Note for small pruning, the compaction is skipped. if count >= rangeCompactionThreshold { @@ -314,29 +333,7 @@ func (p *Pruner) Prune(root common.Hash) error { return err } log.Info("State bloom filter committed", "name", filterName) - - if err := prune(p.db, p.stateBloom, middleRoots, start); err != nil { - return err - } - // Pruning is done, now drop the "useless" layers from the snapshot. - // Firstly, flushing the target layer into the disk. After that all - // diff layers below the target will all be merged into the disk. - if err := p.snaptree.Cap(root, 0); err != nil { - return err - } - // Secondly, flushing the snapshot journal into the disk. All diff - // layers upon the target layer are dropped silently. Eventually the - // entire snapshot tree is converted into a single disk layer with - // the pruning target as the root. - if _, err := p.snaptree.Journal(root); err != nil { - return err - } - // Delete the state bloom, it marks the entire pruning procedure is - // finished. If any crashes or manual exit happens before this, - // `RecoverPruning` will pick it up in the next restarts to redo all - // the things. - os.RemoveAll(filterName) - return nil + return prune(p.snaptree, root, p.db, p.stateBloom, filterName, middleRoots, start) } // RecoverPruning will resume the pruning procedure during the system restart. @@ -400,28 +397,7 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err log.Error("Pruning target state is not existent") return errors.New("non-existent target state") } - if err := prune(db, stateBloom, middleRoots, time.Now()); err != nil { - return err - } - // Pruning is done, now drop the "useless" layers from the snapshot. - // Firstly, flushing the target layer into the disk. After that all - // diff layers below the target will all be merged into the disk. - if err := snaptree.Cap(stateBloomRoot, 0); err != nil { - return err - } - // Secondly, flushing the snapshot journal into the disk. All diff - // layers upon are dropped silently. Eventually the entire snapshot - // tree is converted into a single disk layer with the pruning target - // as the root. - if _, err := snaptree.Journal(stateBloomRoot); err != nil { - return err - } - // Delete the state bloom, it marks the entire pruning procedure is - // finished. If any crashes or manual exit happens before this, - // `RecoverPruning` will pick it up in the next restarts to redo all - // the things. - os.RemoveAll(stateBloomPath) - return nil + return prune(snaptree, stateBloomRoot, db, stateBloom, stateBloomPath, middleRoots, time.Now()) } // extractGenesis loads the genesis state and commits all the state entries From 7644795950481432f6ba230421d41349b69774e8 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 29 Mar 2021 14:17:35 +0200 Subject: [PATCH 429/948] eth/protocols/snap: try to prevent requests timing out --- eth/protocols/snap/handler.go | 13 +++++++++---- eth/protocols/snap/sync.go | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 4169306c25..f7939964f3 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -50,6 +50,11 @@ const ( // maxTrieNodeLookups is the maximum number of state trie nodes to serve. This // number is there to limit the number of disk lookups. maxTrieNodeLookups = 1024 + + // maxTrieNodeTimeSpent is the maximum time we should spend on looking up trie nodes. + // If we spend too much time, then it's a fairly high chance of timing out + // at the remote side, which means all the work is in vain. + maxTrieNodeTimeSpent = 5 * time.Second ) // Handler is a callback to invoke from an outside runner after the boilerplate @@ -129,7 +134,7 @@ func handleMessage(backend Backend, peer *Peer) error { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } defer msg.Discard() - + start := time.Now() // Track the emount of time it takes to serve the request and run the handler if metrics.Enabled { h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) @@ -140,7 +145,7 @@ func handleMessage(backend Backend, peer *Peer) error { ) } metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds()) - }(time.Now()) + }(start) } // Handle the message depending on its contents switch { @@ -470,13 +475,13 @@ func handleMessage(backend Backend, peer *Peer) error { bytes += uint64(len(blob)) // Sanity check limits to avoid DoS on the store trie loads - if bytes > req.Bytes || loads > maxTrieNodeLookups { + if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent { break } } } // Abort request processing if we've exceeded our limits - if bytes > req.Bytes || loads > maxTrieNodeLookups { + if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent { break } } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index af581df07c..2924fa0802 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -85,7 +85,7 @@ const ( var ( // requestTimeout is the maximum time a peer is allowed to spend on serving // a single network request. - requestTimeout = 10 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? + requestTimeout = 15 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? ) // ErrCancelled is returned from snap syncing if the operation was prematurely From b6912c10476bf24c25b780b91a892a71fba268b6 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Mon, 29 Mar 2021 19:54:49 +0100 Subject: [PATCH 430/948] core: add BlockGen.GetBalance method (#22589) --- core/chain_makers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/chain_makers.go b/core/chain_makers.go index 33f253d9e8..e058e5a78e 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -111,6 +111,11 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { b.receipts = append(b.receipts, receipt) } +// GetBalance returns the balance of the given address at the generated block. +func (b *BlockGen) GetBalance(addr common.Address) *big.Int { + return b.statedb.GetBalance(addr) +} + // AddUncheckedTx forcefully adds a transaction to the block without any // validation. // From 24588bacfd81bb991f9832f6557a34ae5078b06e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 29 Mar 2021 20:58:58 +0200 Subject: [PATCH 431/948] cmd/puppeth: specify working directory for nodejs 15 (#22549) --- cmd/puppeth/module_dashboard.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go index be8b6ec600..a76ee19a06 100644 --- a/cmd/puppeth/module_dashboard.go +++ b/cmd/puppeth/module_dashboard.go @@ -518,6 +518,8 @@ var dashboardMascot = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01s\x var dashboardDockerfile = ` FROM mhart/alpine-node:latest +WORKDIR /usr/app + RUN \ npm install connect serve-static && \ \ From 59ac3c9fd30eb4134da60d0549cfd84b27a3a3f7 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 30 Mar 2021 13:57:21 +0200 Subject: [PATCH 432/948] cmd/geth: add db dumptrie command (#22563) Adds the command "geth db dumptrie ", to better help investigate the trie data --- cmd/geth/dbcmd.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 078cad53b4..db1fb0b801 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "time" "github.com/ethereum/go-ethereum/cmd/utils" @@ -29,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" "gopkg.in/urfave/cli.v1" ) @@ -57,6 +59,7 @@ Remove blockchain and state databases`, dbGetCmd, dbDeleteCmd, dbPutCmd, + dbGetSlotsCmd, }, } dbInspectCmd = cli.Command{ @@ -158,6 +161,22 @@ WARNING: This is a low-level operation which may cause database corruption!`, Description: `This command sets a given database key to the given value. WARNING: This is a low-level operation which may cause database corruption!`, } + dbGetSlotsCmd = cli.Command{ + Action: utils.MigrateFlags(dbDumpTrie), + Name: "dumptrie", + Usage: "Show the storage key/values of a given storage trie", + ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: "This command looks up the specified database key from the database.", + } ) func removeDB(ctx *cli.Context) error { @@ -380,3 +399,53 @@ func dbPut(ctx *cli.Context) error { } return db.Put(key, value) } + +// dbDumpTrie shows the key-value slots of a given storage trie +func dbDumpTrie(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + var ( + root []byte + start []byte + max = int64(-1) + err error + ) + if root, err = hexutil.Decode(ctx.Args().Get(0)); err != nil { + log.Info("Could not decode the root", "error", err) + return err + } + stRoot := common.BytesToHash(root) + if ctx.NArg() >= 2 { + if start, err = hexutil.Decode(ctx.Args().Get(1)); err != nil { + log.Info("Could not decode the seek position", "error", err) + return err + } + } + if ctx.NArg() >= 3 { + if max, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil { + log.Info("Could not decode the max count", "error", err) + return err + } + } + theTrie, err := trie.New(stRoot, trie.NewDatabase(db)) + if err != nil { + return err + } + var count int64 + it := trie.NewIterator(theTrie.NodeIterator(start)) + for it.Next() { + if max > 0 && count == max { + fmt.Printf("Exiting after %d values\n", count) + break + } + fmt.Printf(" %d. key %#x: %#x\n", count, it.Key, it.Value) + count++ + } + return it.Err +} From 44fe466999ce7f8b02de7e83de955f8847342309 Mon Sep 17 00:00:00 2001 From: nebojsa94 Date: Tue, 30 Mar 2021 15:38:53 +0200 Subject: [PATCH 433/948] core/vm: fix Byzantium address list (#22603) --- core/vm/contracts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 4e99a51618..a3ceece0e9 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -118,7 +118,7 @@ func init() { PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k) } for k := range PrecompiledContractsByzantium { - PrecompiledAddressesHomestead = append(PrecompiledAddressesByzantium, k) + PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k) } for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) From 3faae5defc0066a95c234da84462535aed1db463 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 30 Mar 2021 15:52:03 +0200 Subject: [PATCH 434/948] ethstats: avoid creating subscriptions on background goroutine (#22587) This fixes an issue where the ethstats service could crash if geth was started and then immediately stopped due to an internal error. The cause of the crash was a nil subscription being returned by the backend, because the background goroutine creating them was scheduled after the backend had already shut down. Moving the creation of subscriptions into the Start method, which runs synchronously during startup of the node, means the returned subscriptions can never be 'nil'. Co-authored-by: Felix Lange Co-authored-by: Martin Holst Swende --- ethstats/ethstats.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index e0f4f95ff3..0f386c02fd 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -95,6 +95,8 @@ type Service struct { pongCh chan struct{} // Pong notifications are fed into this channel histCh chan []uint64 // History request block numbers are fed into this channel + headSub event.Subscription + txSub event.Subscription } // connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the @@ -167,7 +169,12 @@ func New(node *node.Node, backend backend, engine consensus.Engine, url string) // Start implements node.Lifecycle, starting up the monitoring and reporting daemon. func (s *Service) Start() error { - go s.loop() + // Subscribe to chain events to execute updates on + chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) + s.headSub = s.backend.SubscribeChainHeadEvent(chainHeadCh) + txEventCh := make(chan core.NewTxsEvent, txChanSize) + s.txSub = s.backend.SubscribeNewTxsEvent(txEventCh) + go s.loop(chainHeadCh, txEventCh) log.Info("Stats daemon started") return nil @@ -175,22 +182,15 @@ func (s *Service) Start() error { // Stop implements node.Lifecycle, terminating the monitoring and reporting daemon. func (s *Service) Stop() error { + s.headSub.Unsubscribe() + s.txSub.Unsubscribe() log.Info("Stats daemon stopped") return nil } // loop keeps trying to connect to the netstats server, reporting chain events // until termination. -func (s *Service) loop() { - // Subscribe to chain events to execute updates on - chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) - headSub := s.backend.SubscribeChainHeadEvent(chainHeadCh) - defer headSub.Unsubscribe() - - txEventCh := make(chan core.NewTxsEvent, txChanSize) - txSub := s.backend.SubscribeNewTxsEvent(txEventCh) - defer txSub.Unsubscribe() - +func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core.NewTxsEvent) { // Start a goroutine that exhausts the subscriptions to avoid events piling up var ( quitCh = make(chan struct{}) @@ -223,9 +223,9 @@ func (s *Service) loop() { } // node stopped - case <-txSub.Err(): + case <-s.txSub.Err(): break HandleLoop - case <-headSub.Err(): + case <-s.headSub.Err(): break HandleLoop } } From 61ff3e86b2d33f32566cae4f9bd1be973d84d757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 30 Mar 2021 19:04:22 +0300 Subject: [PATCH 435/948] core/state/snapshot, ethdb: track deletions more accurately (#22582) * core/state/snapshot, ethdb: track deletions more accurately * core/state/snapshot: don't reset the iterator, leveldb's screwy * ethdb: don't mess with the insert batches for now --- core/state/snapshot/snapshot.go | 21 ++++++++++++++++++++- ethdb/leveldb/leveldb.go | 2 +- ethdb/memorydb/memorydb.go | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 810f1354e9..eccf377264 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -484,8 +484,17 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator batch.Delete(key) base.cache.Del(key[1:]) - snapshotFlushStorageItemMeter.Mark(1) + + // Ensure we don't delete too much data blindly (contract can be + // huge). It's ok to flush, the root will go missing in case of a + // crash and we'll detect and regenerate the snapshot. + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage deletions", "err", err) + } + batch.Reset() + } } } it.Release() @@ -503,6 +512,16 @@ func diffToDisk(bottom *diffLayer) *diskLayer { snapshotFlushAccountItemMeter.Mark(1) snapshotFlushAccountSizeMeter.Mark(int64(len(data))) + + // Ensure we don't write too much data blindly. It's ok to flush, the + // root will go missing in case of a crash and we'll detect and regen + // the snapshot. + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage deletions", "err", err) + } + batch.Reset() + } } // Push all the storage slots into the database for accountHash, storage := range bottom.storageData { diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index d3011212aa..5d19cc3577 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -461,7 +461,7 @@ func (b *batch) Put(key, value []byte) error { // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { b.b.Delete(key) - b.size++ + b.size += len(key) return nil } diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 4c5e1a84de..fedc9e326c 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -211,7 +211,7 @@ func (b *batch) Put(key, value []byte) error { // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true}) - b.size += 1 + b.size += len(key) return nil } From 4a37ae510eb02bb7daf1fa5982f326e92a401c60 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 30 Mar 2021 20:09:30 +0200 Subject: [PATCH 436/948] rpc: tighter shutdown synchronization in client subscription (#22597) This fixes a rare issue where the client subscription forwarding loop would attempt send on the subscription's channel after Unsubscribe has returned, leading to a panic if the subscription channel was already closed by the user. Example: sub, _ := client.Subscribe(..., channel, ...) sub.Unsubscribe() close(channel) The race occurred because Unsubscribe called quitWithServer to tell the forwarding loop to stop sending on sub.channel, but did not wait for the loop to actually come down. This is fixed by adding an additional channel to track the shutdown, on which Unsubscribe now waits. Fixes #22322 --- rpc/client_test.go | 88 ++++++++++++++++++++++++++++++ rpc/handler.go | 4 +- rpc/subscription.go | 121 +++++++++++++++++++++++++++++------------- rpc/websocket_test.go | 4 ++ 4 files changed, 177 insertions(+), 40 deletions(-) diff --git a/rpc/client_test.go b/rpc/client_test.go index 5d301a07a7..224eb0c5c8 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -18,6 +18,7 @@ package rpc import ( "context" + "encoding/json" "fmt" "math/rand" "net" @@ -376,6 +377,93 @@ func TestClientCloseUnsubscribeRace(t *testing.T) { } } +// unsubscribeRecorder collects the subscription IDs of *_unsubscribe calls. +type unsubscribeRecorder struct { + ServerCodec + unsubscribes map[string]bool +} + +func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) { + if r.unsubscribes == nil { + r.unsubscribes = make(map[string]bool) + } + + msgs, batch, err := r.ServerCodec.readBatch() + for _, msg := range msgs { + if msg.isUnsubscribe() { + var params []string + if err := json.Unmarshal(msg.Params, ¶ms); err != nil { + panic("unsubscribe decode error: " + err.Error()) + } + r.unsubscribes[params[0]] = true + } + } + return msgs, batch, err +} + +// This checks that Client calls the _unsubscribe method on the server when Unsubscribe is +// called on a subscription. +func TestClientSubscriptionUnsubscribeServer(t *testing.T) { + t.Parallel() + + // Create the server. + srv := NewServer() + srv.RegisterName("nftest", new(notificationTestService)) + p1, p2 := net.Pipe() + recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)} + go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions) + defer srv.Stop() + + // Create the client on the other end of the pipe. + client, _ := newClient(context.Background(), func(context.Context) (ServerCodec, error) { + return NewCodec(p2), nil + }) + defer client.Close() + + // Create the subscription. + ch := make(chan int) + sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1) + if err != nil { + t.Fatal(err) + } + + // Unsubscribe and check that unsubscribe was called. + sub.Unsubscribe() + if !recorder.unsubscribes[sub.subid] { + t.Fatal("client did not call unsubscribe method") + } + if _, open := <-sub.Err(); open { + t.Fatal("subscription error channel not closed after unsubscribe") + } +} + +// This checks that the subscribed channel can be closed after Unsubscribe. +// It is the reproducer for https://github.com/ethereum/go-ethereum/issues/22322 +func TestClientSubscriptionChannelClose(t *testing.T) { + t.Parallel() + + var ( + srv = NewServer() + httpsrv = httptest.NewServer(srv.WebsocketHandler(nil)) + wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") + ) + defer srv.Stop() + defer httpsrv.Close() + + srv.RegisterName("nftest", new(notificationTestService)) + client, _ := Dial(wsURL) + + for i := 0; i < 100; i++ { + ch := make(chan int, 100) + sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1) + if err != nil { + t.Fatal(err) + } + sub.Unsubscribe() + close(ch) + } +} + // This test checks that Client doesn't lock up when a single subscriber // doesn't read subscription events. func TestClientNotificationStorm(t *testing.T) { diff --git a/rpc/handler.go b/rpc/handler.go index 23023eaca1..85f774a1b7 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -189,7 +189,7 @@ func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) { } for id, sub := range h.clientSubs { delete(h.clientSubs, id) - sub.quitWithError(false, err) + sub.close(err) } } @@ -281,7 +281,7 @@ func (h *handler) handleResponse(msg *jsonrpcMessage) { return } if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil { - go op.sub.start() + go op.sub.run() h.clientSubs[op.sub.subid] = op.sub } } diff --git a/rpc/subscription.go b/rpc/subscription.go index 233215d792..942e764e5d 100644 --- a/rpc/subscription.go +++ b/rpc/subscription.go @@ -208,23 +208,37 @@ type ClientSubscription struct { channel reflect.Value namespace string subid string - in chan json.RawMessage - quitOnce sync.Once // ensures quit is closed once - quit chan struct{} // quit is closed when the subscription exits - errOnce sync.Once // ensures err is closed once - err chan error + // The in channel receives notification values from client dispatcher. + in chan json.RawMessage + + // The error channel receives the error from the forwarding loop. + // It is closed by Unsubscribe. + err chan error + errOnce sync.Once + + // Closing of the subscription is requested by sending on 'quit'. This is handled by + // the forwarding loop, which closes 'forwardDone' when it has stopped sending to + // sub.channel. Finally, 'unsubDone' is closed after unsubscribing on the server side. + quit chan error + forwardDone chan struct{} + unsubDone chan struct{} } +// This is the sentinel value sent on sub.quit when Unsubscribe is called. +var errUnsubscribed = errors.New("unsubscribed") + func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription { sub := &ClientSubscription{ - client: c, - namespace: namespace, - etype: channel.Type().Elem(), - channel: channel, - quit: make(chan struct{}), - err: make(chan error, 1), - in: make(chan json.RawMessage), + client: c, + namespace: namespace, + etype: channel.Type().Elem(), + channel: channel, + in: make(chan json.RawMessage), + quit: make(chan error), + forwardDone: make(chan struct{}), + unsubDone: make(chan struct{}), + err: make(chan error, 1), } return sub } @@ -232,9 +246,9 @@ func newClientSubscription(c *Client, namespace string, channel reflect.Value) * // Err returns the subscription error channel. The intended use of Err is to schedule // resubscription when the client connection is closed unexpectedly. // -// The error channel receives a value when the subscription has ended due -// to an error. The received error is nil if Close has been called -// on the underlying client and no other error has occurred. +// The error channel receives a value when the subscription has ended due to an error. The +// received error is nil if Close has been called on the underlying client and no other +// error has occurred. // // The error channel is closed when Unsubscribe is called on the subscription. func (sub *ClientSubscription) Err() <-chan error { @@ -244,41 +258,63 @@ func (sub *ClientSubscription) Err() <-chan error { // Unsubscribe unsubscribes the notification and closes the error channel. // It can safely be called more than once. func (sub *ClientSubscription) Unsubscribe() { - sub.quitWithError(true, nil) - sub.errOnce.Do(func() { close(sub.err) }) -} - -func (sub *ClientSubscription) quitWithError(unsubscribeServer bool, err error) { - sub.quitOnce.Do(func() { - // The dispatch loop won't be able to execute the unsubscribe call - // if it is blocked on deliver. Close sub.quit first because it - // unblocks deliver. - close(sub.quit) - if unsubscribeServer { - sub.requestUnsubscribe() - } - if err != nil { - if err == ErrClientQuit { - err = nil // Adhere to subscription semantics. - } - sub.err <- err + sub.errOnce.Do(func() { + select { + case sub.quit <- errUnsubscribed: + <-sub.unsubDone + case <-sub.unsubDone: } + close(sub.err) }) } +// deliver is called by the client's message dispatcher to send a notification value. func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) { select { case sub.in <- result: return true - case <-sub.quit: + case <-sub.forwardDone: return false } } -func (sub *ClientSubscription) start() { - sub.quitWithError(sub.forward()) +// close is called by the client's message dispatcher when the connection is closed. +func (sub *ClientSubscription) close(err error) { + select { + case sub.quit <- err: + case <-sub.forwardDone: + } +} + +// run is the forwarding loop of the subscription. It runs in its own goroutine and +// is launched by the client's handler after the subscription has been created. +func (sub *ClientSubscription) run() { + defer close(sub.unsubDone) + + unsubscribe, err := sub.forward() + + // The client's dispatch loop won't be able to execute the unsubscribe call if it is + // blocked in sub.deliver() or sub.close(). Closing forwardDone unblocks them. + close(sub.forwardDone) + + // Call the unsubscribe method on the server. + if unsubscribe { + sub.requestUnsubscribe() + } + + // Send the error. + if err != nil { + if err == ErrClientQuit { + // ErrClientQuit gets here when Client.Close is called. This is reported as a + // nil error because it's not an error, but we can't close sub.err here. + err = nil + } + sub.err <- err + } } +// forward is the forwarding loop. It takes in RPC notifications and sends them +// on the subscription channel. func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { cases := []reflect.SelectCase{ {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)}, @@ -286,7 +322,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { {Dir: reflect.SelectSend, Chan: sub.channel}, } buffer := list.New() - defer buffer.Init() + for { var chosen int var recv reflect.Value @@ -301,7 +337,15 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { switch chosen { case 0: // <-sub.quit - return false, nil + if !recv.IsNil() { + err = recv.Interface().(error) + } + if err == errUnsubscribed { + // Exiting because Unsubscribe was called, unsubscribe on server. + return true, nil + } + return false, err + case 1: // <-sub.in val, err := sub.unmarshal(recv.Interface().(json.RawMessage)) if err != nil { @@ -311,6 +355,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) { return true, ErrSubscriptionQueueOverflow } buffer.PushBack(val) + case 2: // sub.channel<- cases[2].Send = reflect.Value{} // Don't hold onto the value. buffer.Remove(buffer.Front()) diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index 37ed19476f..4976853baf 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -129,11 +129,15 @@ func TestClientWebsocketPing(t *testing.T) { if err != nil { t.Fatalf("client dial error: %v", err) } + defer client.Close() + resultChan := make(chan int) sub, err := client.EthSubscribe(ctx, resultChan, "foo") if err != nil { t.Fatalf("client subscribe error: %v", err) } + // Note: Unsubscribe is not called on this subscription because the mockup + // server can't handle the request. // Wait for the context's deadline to be reached before proceeding. // This is important for reproducing https://github.com/ethereum/go-ethereum/issues/19798 From 55300d4fdbfda751a7a3d4baadd9db65c50b22b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 31 Mar 2021 10:56:51 +0300 Subject: [PATCH 437/948] all: fix miner hashRate -> hashrate on API calls --- consensus/ethash/api.go | 2 +- consensus/ethash/ethash_test.go | 6 +++--- eth/api.go | 10 ---------- ethstats/ethstats.go | 2 +- internal/web3ext/web3ext.go | 4 ++-- miner/miner.go | 2 +- 6 files changed, 8 insertions(+), 18 deletions(-) diff --git a/consensus/ethash/api.go b/consensus/ethash/api.go index 68b3a84b09..f4d3802e0b 100644 --- a/consensus/ethash/api.go +++ b/consensus/ethash/api.go @@ -89,7 +89,7 @@ func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest common.Hash) boo // // It accepts the miner hash rate and an identifier which must be unique // between nodes. -func (api *API) SubmitHashRate(rate hexutil.Uint64, id common.Hash) bool { +func (api *API) SubmitHashrate(rate hexutil.Uint64, id common.Hash) bool { if api.ethash.remote == nil { return false } diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go index eb3850b3b0..382eefeecf 100644 --- a/consensus/ethash/ethash_test.go +++ b/consensus/ethash/ethash_test.go @@ -135,7 +135,7 @@ func TestRemoteSealer(t *testing.T) { } } -func TestHashRate(t *testing.T) { +func TestHashrate(t *testing.T) { var ( hashrate = []hexutil.Uint64{100, 200, 300} expect uint64 @@ -150,7 +150,7 @@ func TestHashRate(t *testing.T) { api := &API{ethash} for i := 0; i < len(hashrate); i += 1 { - if res := api.SubmitHashRate(hashrate[i], ids[i]); !res { + if res := api.SubmitHashrate(hashrate[i], ids[i]); !res { t.Error("remote miner submit hashrate failed") } expect += uint64(hashrate[i]) @@ -170,7 +170,7 @@ func TestClosedRemoteSealer(t *testing.T) { t.Error("expect to return an error to indicate ethash is stopped") } - if res := api.SubmitHashRate(hexutil.Uint64(100), common.HexToHash("a")); res { + if res := api.SubmitHashrate(hexutil.Uint64(100), common.HexToHash("a")); res { t.Error("expect to return false when submit hashrate to a stopped ethash") } } diff --git a/eth/api.go b/eth/api.go index 53ef91392b..c2f72a1897 100644 --- a/eth/api.go +++ b/eth/api.go @@ -61,11 +61,6 @@ func (api *PublicEthereumAPI) Coinbase() (common.Address, error) { return api.Etherbase() } -// Hashrate returns the POW hashrate -func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { - return hexutil.Uint64(api.e.Miner().HashRate()) -} - // ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. func (api *PublicEthereumAPI) ChainId() (hexutil.Uint64, error) { // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config @@ -149,11 +144,6 @@ func (api *PrivateMinerAPI) SetRecommitInterval(interval int) { api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond) } -// GetHashrate returns the current hashrate of the miner. -func (api *PrivateMinerAPI) GetHashrate() uint64 { - return api.e.miner.HashRate() -} - // PrivateAdminAPI is the collection of Ethereum full node-related APIs // exposed over the private admin endpoint. type PrivateAdminAPI struct { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index 0f386c02fd..c7acb9481c 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -754,7 +754,7 @@ func (s *Service) reportStats(conn *connWrapper) error { fullBackend, ok := s.backend.(fullNodeBackend) if ok { mining = fullBackend.Miner().Mining() - hashrate = int(fullBackend.Miner().HashRate()) + hashrate = int(fullBackend.Miner().Hashrate()) sync := fullBackend.Downloader().Progress() syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index aba262699c..e1f20ad72a 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -138,8 +138,8 @@ web3._extend({ params: 3, }), new web3._extend.Method({ - name: 'submitHashRate', - call: 'ethash_submitHashRate', + name: 'submitHashrate', + call: 'ethash_submitHashrate', params: 2, }), ] diff --git a/miner/miner.go b/miner/miner.go index 4d71e307a6..00c3d0cb5c 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -160,7 +160,7 @@ func (miner *Miner) Mining() bool { return miner.worker.isRunning() } -func (miner *Miner) HashRate() uint64 { +func (miner *Miner) Hashrate() uint64 { if pow, ok := miner.engine.(consensus.PoW); ok { return uint64(pow.Hashrate()) } From c79fc209cdcdd3ec28e4643b12c954fdbeeba452 Mon Sep 17 00:00:00 2001 From: Edgar Aroutiounian Date: Tue, 6 Apr 2021 04:57:00 -0400 Subject: [PATCH 438/948] core/state/snapshot: fix data race in diff layer (#22540) --- core/state/snapshot/difflayer.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 9c86a679d1..ee88938b77 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -296,13 +296,17 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { if !hit { hit = dl.diffed.Contains(destructBloomHasher(hash)) } + var origin *diskLayer + if !hit { + origin = dl.origin // extract origin while holding the lock + } dl.lock.RUnlock() // If the bloom filter misses, don't even bother with traversing the memory // diff layers, reach straight into the bottom persistent disk layer - if !hit { + if origin != nil { snapshotBloomAccountMissMeter.Mark(1) - return dl.origin.AccountRLP(hash) + return origin.AccountRLP(hash) } // The bloom filter hit, start poking in the internal maps return dl.accountRLP(hash, 0) @@ -358,13 +362,17 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro if !hit { hit = dl.diffed.Contains(destructBloomHasher(accountHash)) } + var origin *diskLayer + if !hit { + origin = dl.origin // extract origin while holding the lock + } dl.lock.RUnlock() // If the bloom filter misses, don't even bother with traversing the memory // diff layers, reach straight into the bottom persistent disk layer - if !hit { + if origin != nil { snapshotBloomStorageMissMeter.Mark(1) - return dl.origin.Storage(accountHash, storageHash) + return origin.Storage(accountHash, storageHash) } // The bloom filter hit, start poking in the internal maps return dl.storage(accountHash, storageHash, 0) From 706683ea72c1b711bd3b56327fe6c08e70bd37e3 Mon Sep 17 00:00:00 2001 From: piersy Date: Tue, 6 Apr 2021 11:24:39 +0100 Subject: [PATCH 439/948] internal/ethapi: fix eth_chainId method (#22243) This removes the duplicated definition of eth_chainID in package eth and updates the definition in internal/ethapi to treat chain ID as a bigint. Co-authored-by: Felix Lange --- eth/api.go | 9 --------- internal/ethapi/api.go | 10 +++++++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/eth/api.go b/eth/api.go index c2f72a1897..fb51e78c1a 100644 --- a/eth/api.go +++ b/eth/api.go @@ -61,15 +61,6 @@ func (api *PublicEthereumAPI) Coinbase() (common.Address, error) { return api.Etherbase() } -// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. -func (api *PublicEthereumAPI) ChainId() (hexutil.Uint64, error) { - // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config - if config := api.e.blockchain.Config(); config.IsEIP155(api.e.blockchain.CurrentBlock().Number()) { - return (hexutil.Uint64)(config.ChainID.Uint64()), nil - } - return hexutil.Uint64(0), fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") -} - // PublicMinerAPI provides an API to control the miner. // It offers only methods that operate on data that pose no security risk when it is publicly accessible. type PublicMinerAPI struct { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 622063cf64..861d427851 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -529,9 +529,13 @@ func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI { return &PublicBlockChainAPI{b} } -// ChainId returns the chainID value for transaction replay protection. -func (s *PublicBlockChainAPI) ChainId() *hexutil.Big { - return (*hexutil.Big)(s.b.ChainConfig().ChainID) +// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. +func (api *PublicBlockChainAPI) ChainId() (*hexutil.Big, error) { + // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config + if config := api.b.ChainConfig(); config.IsEIP155(api.b.CurrentBlock().Number()) { + return (*hexutil.Big)(config.ChainID), nil + } + return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") } // BlockNumber returns the block number of the chain head. From adf09aeab1fc091e4ab56c04852b18b4b924eb50 Mon Sep 17 00:00:00 2001 From: AmitBRD <60668103+AmitBRD@users.noreply.github.com> Date: Tue, 6 Apr 2021 08:58:36 -0500 Subject: [PATCH 440/948] graphql: add support for tx types and tx access lists (#22491) This adds support for EIP-2718 access list transactions in the GraphQL API. Co-authored-by: Amit Shah Co-authored-by: Felix Lange --- graphql/graphql.go | 39 ++++++++++++ graphql/graphql_test.go | 136 ++++++++++++++++++++++++++++++++++++++-- graphql/schema.go | 9 +++ 3 files changed, 180 insertions(+), 4 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 2374beb8e1..b1af7675bd 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -151,6 +151,20 @@ func (l *Log) Data(ctx context.Context) hexutil.Bytes { return l.log.Data } +// AccessTuple represents EIP-2930 +type AccessTuple struct { + address common.Address + storageKeys *[]common.Hash +} + +func (at *AccessTuple) Address(ctx context.Context) common.Address { + return at.address +} + +func (at *AccessTuple) StorageKeys(ctx context.Context) *[]common.Hash { + return at.storageKeys +} + // Transaction represents an Ethereum transaction. // backend and hash are mandatory; all others will be fetched when required. type Transaction struct { @@ -342,6 +356,31 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { return &ret, nil } +func (t *Transaction) Type(ctx context.Context) (*int32, error) { + tx, err := t.resolve(ctx) + if err != nil { + return nil, err + } + txType := int32(tx.Type()) + return &txType, nil +} + +func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return nil, err + } + accessList := tx.AccessList() + ret := make([]*AccessTuple, 0, len(accessList)) + for _, al := range accessList { + ret = append(ret, &AccessTuple{ + address: al.Address, + storageKeys: &al.StorageKeys, + }) + } + return &ret, nil +} + func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) { tx, err := t.resolve(ctx) if err != nil || tx == nil { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 2f3b230329..2404ff45fa 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -25,8 +25,12 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/node" @@ -55,7 +59,7 @@ func TestBuildSchema(t *testing.T) { // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint func TestGraphQLBlockSerialization(t *testing.T) { - stack := createNode(t, true) + stack := createNode(t, true, false) defer stack.Close() // start node if err := stack.Start(); err != nil { @@ -157,9 +161,45 @@ func TestGraphQLBlockSerialization(t *testing.T) { } } +func TestGraphQLBlockSerializationEIP2718(t *testing.T) { + stack := createNode(t, true, true) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + for i, tt := range []struct { + body string + want string + code int + }{ + { + body: `{"query": "{block {number transactions { from { address } to { address } value hash type accessList { address storageKeys } index}}}"}`, + want: `{"data":{"block":{"number":1,"transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0x4f7b8d718145233dcf7f29e34a969c63dd4de8715c054ea2af022b66c4f4633e","type":0,"accessList":[],"index":0},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x9c6c2c045b618fe87add0e49ba3ca00659076ecae00fd51de3ba5d4ccf9dbf40","type":1,"accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":1}]}}}`, + code: 200, + }, + } { + resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) + if err != nil { + t.Fatalf("could not post: %v", err) + } + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + if have := string(bodyBytes); have != tt.want { + t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want) + } + if tt.code != resp.StatusCode { + t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code) + } + } +} + // Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { - stack := createNode(t, false) + stack := createNode(t, false, false) defer stack.Close() if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) @@ -173,7 +213,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, http.StatusNotFound, resp.StatusCode) } -func createNode(t *testing.T, gqlEnabled bool) *node.Node { +func createNode(t *testing.T, gqlEnabled bool, txEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", HTTPPort: 0, @@ -186,7 +226,11 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { if !gqlEnabled { return stack } - createGQLService(t, stack) + if !txEnabled { + createGQLService(t, stack) + } else { + createGQLServiceWithTransactions(t, stack) + } return stack } @@ -226,3 +270,87 @@ func createGQLService(t *testing.T, stack *node.Node) { t.Fatalf("could not create graphql service: %v", err) } } + +func createGQLServiceWithTransactions(t *testing.T, stack *node.Node) { + // create backend + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address := crypto.PubkeyToAddress(key.PublicKey) + funds := big.NewInt(1000000000) + dad := common.HexToAddress("0x0000000000000000000000000000000000000dad") + + ethConf := ðconfig.Config{ + Genesis: &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: big.NewInt(1048576), + Alloc: core.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xdad sloads 0x00 and 0x01 + dad: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + }, + Ethash: ethash.Config{ + PowMode: ethash.ModeFake, + }, + NetworkId: 1337, + TrieCleanCache: 5, + TrieCleanCacheJournal: "triecache", + TrieCleanCacheRejournal: 60 * time.Minute, + TrieDirtyCache: 5, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 5, + } + + ethBackend, err := eth.New(stack, ethConf) + if err != nil { + t.Fatalf("could not create eth backend: %v", err) + } + signer := types.LatestSigner(ethConf.Genesis.Config) + + legacyTx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: uint64(0), + To: &dad, + Value: big.NewInt(100), + Gas: 50000, + GasPrice: big.NewInt(1), + }) + envelopTx, _ := types.SignNewTx(key, signer, &types.AccessListTx{ + ChainID: ethConf.Genesis.Config.ChainID, + Nonce: uint64(1), + To: &dad, + Gas: 30000, + GasPrice: big.NewInt(1), + Value: big.NewInt(50), + AccessList: types.AccessList{{ + Address: dad, + StorageKeys: []common.Hash{{0}}, + }}, + }) + + // Create some blocks and import them + chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(), + ethash.NewFaker(), ethBackend.ChainDb(), 1, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + b.AddTx(legacyTx) + b.AddTx(envelopTx) + }) + + _, err = ethBackend.BlockChain().InsertChain(chain) + if err != nil { + t.Fatalf("could not create import blocks: %v", err) + } + // create gql service + err = New(stack, ethBackend.APIBackend, []string{}, []string{}) + if err != nil { + t.Fatalf("could not create graphql service: %v", err) + } +} diff --git a/graphql/schema.go b/graphql/schema.go index 6ea63db636..d792bb9153 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -69,6 +69,12 @@ const schema string = ` transaction: Transaction! } + #EIP-2718 + type AccessTuple{ + address: Address! + storageKeys : [Bytes32!] + } + # Transaction is an Ethereum transaction. type Transaction { # Hash is the hash of this transaction. @@ -118,6 +124,9 @@ const schema string = ` r: BigInt! s: BigInt! v: BigInt! + #Envelope transaction support + type: Int + accessList: [AccessTuple!] } # BlockFilterCriteria encapsulates log filter criteria for a filter applied From 5338ce44475191eb8708509a44d92f1219a8a798 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:39:51 +0430 Subject: [PATCH 441/948] internal/debug: add JSON log format and rename logging flags (#22341) This change adds support for logging JSON records when the --log.json flag is given. The --debug and --backtrace flags are deprecated and replaced by --log.debug and --log.backtrace. While changing this, it was noticed that the --memprofilerate and --blockprofilerate were ineffective (they were always overridden even if --pprof.memprofilerate was not set). This is also fixed. Co-authored-by: Felix Lange --- internal/debug/flags.go | 124 ++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 2c92b19de6..126ee09a7c 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -41,22 +41,22 @@ var ( Usage: "Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail", Value: 3, } - logjsonFlag = cli.BoolFlag{ - Name: "log.json", - Usage: "Format logs with JSON", - } vmoduleFlag = cli.StringFlag{ Name: "vmodule", Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4)", Value: "", } + logjsonFlag = cli.BoolFlag{ + Name: "log.json", + Usage: "Format logs with JSON", + } backtraceAtFlag = cli.StringFlag{ - Name: "backtrace", + Name: "log.backtrace", Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")", Value: "", } debugFlag = cli.BoolFlag{ - Name: "debug", + Name: "log.debug", Usage: "Prepends log messages with call-site location (file and line number)", } pprofFlag = cli.BoolFlag{ @@ -90,18 +90,69 @@ var ( Name: "trace", Usage: "Write execution trace to the given file", } + // (Deprecated April 2020) + legacyPprofPortFlag = cli.IntFlag{ + Name: "pprofport", + Usage: "pprof HTTP server listening port (deprecated, use --pprof.port)", + Value: 6060, + } + legacyPprofAddrFlag = cli.StringFlag{ + Name: "pprofaddr", + Usage: "pprof HTTP server listening interface (deprecated, use --pprof.addr)", + Value: "127.0.0.1", + } + legacyMemprofilerateFlag = cli.IntFlag{ + Name: "memprofilerate", + Usage: "Turn on memory profiling with the given rate (deprecated, use --pprof.memprofilerate)", + Value: runtime.MemProfileRate, + } + legacyBlockprofilerateFlag = cli.IntFlag{ + Name: "blockprofilerate", + Usage: "Turn on block profiling with the given rate (deprecated, use --pprof.blockprofilerate)", + } + legacyCpuprofileFlag = cli.StringFlag{ + Name: "cpuprofile", + Usage: "Write CPU profile to the given file (deprecated, use --pprof.cpuprofile)", + } + legacyBacktraceAtFlag = cli.StringFlag{ + Name: "backtrace", + Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\") (deprecated, use --log.backtrace)", + Value: "", + } + legacyDebugFlag = cli.BoolFlag{ + Name: "debug", + Usage: "Prepends log messages with call-site location (file and line number) (deprecated, use --log.debug)", + } ) // Flags holds all command-line flags required for debugging. var Flags = []cli.Flag{ - verbosityFlag, logjsonFlag, vmoduleFlag, backtraceAtFlag, debugFlag, - pprofFlag, pprofAddrFlag, pprofPortFlag, memprofilerateFlag, - blockprofilerateFlag, cpuprofileFlag, traceFlag, + verbosityFlag, + vmoduleFlag, + logjsonFlag, + backtraceAtFlag, + debugFlag, + pprofFlag, + pprofAddrFlag, + pprofPortFlag, + memprofilerateFlag, + blockprofilerateFlag, + cpuprofileFlag, + traceFlag, } -var ( - glogger *log.GlogHandler -) +// This is the list of deprecated debugging flags. +var DeprecatedFlags = []cli.Flag{ + legacyPprofPortFlag, + legacyPprofAddrFlag, + legacyMemprofilerateFlag, + legacyBlockprofilerateFlag, + legacyCpuprofileFlag, + legacyBacktraceAtFlag, + legacyDebugFlag, +} + +var glogger *log.GlogHandler func init() { glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) @@ -124,17 +175,54 @@ func Setup(ctx *cli.Context) error { ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) } glogger.SetHandler(ostream) + // logging - log.PrintOrigins(ctx.GlobalBool(debugFlag.Name)) - glogger.Verbosity(log.Lvl(ctx.GlobalInt(verbosityFlag.Name))) - glogger.Vmodule(ctx.GlobalString(vmoduleFlag.Name)) - glogger.BacktraceAt(ctx.GlobalString(backtraceAtFlag.Name)) + verbosity := ctx.GlobalInt(verbosityFlag.Name) + glogger.Verbosity(log.Lvl(verbosity)) + vmodule := ctx.GlobalString(vmoduleFlag.Name) + glogger.Vmodule(vmodule) + + debug := ctx.GlobalBool(debugFlag.Name) + if ctx.GlobalIsSet(legacyDebugFlag.Name) { + debug = ctx.GlobalBool(legacyDebugFlag.Name) + log.Warn("The flag --debug is deprecated and will be removed in the future, please use --log.debug") + } + if ctx.GlobalIsSet(debugFlag.Name) { + debug = ctx.GlobalBool(debugFlag.Name) + } + log.PrintOrigins(debug) + + backtrace := ctx.GlobalString(backtraceAtFlag.Name) + if b := ctx.GlobalString(legacyBacktraceAtFlag.Name); b != "" { + backtrace = b + log.Warn("The flag --backtrace is deprecated and will be removed in the future, please use --log.backtrace") + } + if b := ctx.GlobalString(backtraceAtFlag.Name); b != "" { + backtrace = b + } + glogger.BacktraceAt(backtrace) + log.Root().SetHandler(glogger) // profiling, tracing - runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) + runtime.MemProfileRate = memprofilerateFlag.Value + if ctx.GlobalIsSet(legacyMemprofilerateFlag.Name) { + runtime.MemProfileRate = ctx.GlobalInt(legacyMemprofilerateFlag.Name) + log.Warn("The flag --memprofilerate is deprecated and will be removed in the future, please use --pprof.memprofilerate") + } + if ctx.GlobalIsSet(memprofilerateFlag.Name) { + runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name) + } - Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name)) + blockProfileRate := blockprofilerateFlag.Value + if ctx.GlobalIsSet(legacyBlockprofilerateFlag.Name) { + blockProfileRate = ctx.GlobalInt(legacyBlockprofilerateFlag.Name) + log.Warn("The flag --blockprofilerate is deprecated and will be removed in the future, please use --pprof.blockprofilerate") + } + if ctx.GlobalIsSet(blockprofilerateFlag.Name) { + blockProfileRate = ctx.GlobalInt(blockprofilerateFlag.Name) + } + Handler.SetBlockProfileRate(blockProfileRate) if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" { if err := Handler.StartGoTrace(traceFile); err != nil { From 95219ae62dd157414e4ab9b3a63d2c80cf10efab Mon Sep 17 00:00:00 2001 From: Peter Simard Date: Tue, 6 Apr 2021 10:23:35 -0400 Subject: [PATCH 442/948] cmd/utils: move cache sanity check to SetEthConfig (#22510) Move the cache sanity check to the SetEthConfig function to allow the config file to load. --- cmd/geth/main.go | 22 ---------------------- cmd/utils/flags.go | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 556ad0dbf3..12a5ae9bfc 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -19,9 +19,7 @@ package main import ( "fmt" - "math" "os" - godebug "runtime/debug" "sort" "strconv" "strings" @@ -41,7 +39,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" - gopsutil "github.com/shirou/gopsutil/mem" "gopkg.in/urfave/cli.v1" ) @@ -300,25 +297,6 @@ func prepare(ctx *cli.Context) { log.Info("Dropping default light client cache", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 128) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128)) } - // Cap the cache allowance and tune the garbage collector - mem, err := gopsutil.VirtualMemory() - if err == nil { - if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 { - log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024) - mem.Total = 2 * 1024 * 1024 * 1024 - } - allowance := int(mem.Total / 1024 / 1024 / 3) - if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance { - log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) - ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance)) - } - } - // Ensure Go's GC ignores the database cache for trigger percentage - cache := ctx.GlobalInt(utils.CacheFlag.Name) - gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) - - log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) - godebug.SetGCPercent(int(gogc)) // Start metrics export if enabled utils.SetupMetrics(ctx) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a602d5a35e..d631b8e332 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -22,9 +22,11 @@ import ( "fmt" "io" "io/ioutil" + "math" "math/big" "os" "path/filepath" + godebug "runtime/debug" "strconv" "strings" "text/tabwriter" @@ -65,6 +67,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" pcsclite "github.com/gballet/go-libpcsclite" + gopsutil "github.com/shirou/gopsutil/mem" "gopkg.in/urfave/cli.v1" ) @@ -1473,6 +1476,26 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { setWhitelist(ctx, cfg) setLes(ctx, cfg) + // Cap the cache allowance and tune the garbage collector + mem, err := gopsutil.VirtualMemory() + if err == nil { + if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 { + log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024) + mem.Total = 2 * 1024 * 1024 * 1024 + } + allowance := int(mem.Total / 1024 / 1024 / 3) + if cache := ctx.GlobalInt(CacheFlag.Name); cache > allowance { + log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) + ctx.GlobalSet(CacheFlag.Name, strconv.Itoa(allowance)) + } + } + // Ensure Go's GC ignores the database cache for trigger percentage + cache := ctx.GlobalInt(CacheFlag.Name) + gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) + + log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) + godebug.SetGCPercent(int(gogc)) + if ctx.GlobalIsSet(SyncModeFlag.Name) { cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode) } From e275b1a293bdbc8170a01c1612c1cdf8e82c1ca1 Mon Sep 17 00:00:00 2001 From: Evolution404 <35091674+Evolution404@users.noreply.github.com> Date: Wed, 7 Apr 2021 02:02:52 +0800 Subject: [PATCH 443/948] consensus/ethash: replace a magic number with it's constant (#22618) --- consensus/ethash/algorithm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go index 80379597e2..065e60b90b 100644 --- a/consensus/ethash/algorithm.go +++ b/consensus/ethash/algorithm.go @@ -174,7 +174,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) { case <-done: return case <-time.After(3 * time.Second): - logger.Info("Generating ethash verification cache", "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/4, "elapsed", common.PrettyDuration(time.Since(start))) + logger.Info("Generating ethash verification cache", "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/(cacheRounds+1), "elapsed", common.PrettyDuration(time.Since(start))) } } }() From 2d89fe0883f0ebb5f0145763d46f5335febab3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Tue, 6 Apr 2021 20:42:50 +0200 Subject: [PATCH 444/948] les: move client pool to les/vflux/server (#22495) * les: move client pool to les/vflux/server * les/vflux/server: un-expose NodeBalance, remove unused fn, fix bugs * tests/fuzzers/vflux: add ClientPool fuzzer * les/vflux/server: fixed balance tests * les: rebase fix * les/vflux/server: fixed more bugs * les/vflux/server: unexported NodeStateMachine fields and flags * les/vflux/server: unexport all internal components and functions * les/vflux/server: fixed priorityPool test * les/vflux/server: polish balance * les/vflux/server: fixed mutex locking error * les/vflux/server: priorityPool bug fixed * common/prque: make Prque wrap-around priority handling optional * les/vflux/server: rename funcs, small optimizations * les/vflux/server: fixed timeUntil * les/vflux/server: separated balance.posValue and negValue * les/vflux/server: polish setup * les/vflux/server: enforce capacity curve monotonicity * les/vflux/server: simplified requestCapacity * les/vflux/server: requestCapacity with target range, no iterations in SetCapacity * les/vflux/server: minor changes * les/vflux/server: moved default factors to balanceTracker * les/vflux/server: set inactiveFlag in priorityPool * les/vflux/server: moved related metrics to vfs package * les/vflux/client: make priorityPool temp state logic cleaner * les/vflux/server: changed log.Crit to log.Error * add vflux fuzzer to oss-fuzz Co-authored-by: rjl493456442 --- common/prque/lazyqueue.go | 6 +- common/prque/prque.go | 7 +- common/prque/sstack.go | 20 +- common/prque/sstack_test.go | 6 +- les/api.go | 103 ++--- les/clientpool.go | 453 -------------------- les/flowcontrol/manager.go | 2 +- les/metrics.go | 17 +- les/peer.go | 268 +++++++++++- les/server.go | 85 +--- les/server_handler.go | 127 +----- les/servingqueue.go | 4 +- les/test_helper.go | 12 +- les/vflux/server/balance.go | 489 ++++++++++++---------- les/vflux/server/balance_test.go | 222 +++++----- les/vflux/server/balance_tracker.go | 165 ++++---- les/vflux/server/clientpool.go | 335 +++++++++++++++ les/{ => vflux/server}/clientpool_test.go | 271 ++++++------ les/vflux/server/metrics.go | 33 ++ les/vflux/server/prioritypool.go | 416 +++++++++--------- les/vflux/server/prioritypool_test.go | 76 ++-- les/vflux/server/service.go | 6 +- les/vflux/server/status.go | 59 +++ oss-fuzz.sh | 1 + p2p/nodestate/nodestate.go | 17 + tests/fuzzers/vflux/clientpool-fuzzer.go | 289 +++++++++++++ tests/fuzzers/vflux/debug/main.go | 41 ++ 27 files changed, 1986 insertions(+), 1544 deletions(-) delete mode 100644 les/clientpool.go create mode 100644 les/vflux/server/clientpool.go rename les/{ => vflux/server}/clientpool_test.go (61%) create mode 100644 les/vflux/server/metrics.go create mode 100644 les/vflux/server/status.go create mode 100644 tests/fuzzers/vflux/clientpool-fuzzer.go create mode 100644 tests/fuzzers/vflux/debug/main.go diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go index c74faab7e6..37c2f3bd42 100644 --- a/common/prque/lazyqueue.go +++ b/common/prque/lazyqueue.go @@ -55,7 +55,7 @@ type ( // NewLazyQueue creates a new lazy queue func NewLazyQueue(setIndex SetIndexCallback, priority PriorityCallback, maxPriority MaxPriorityCallback, clock mclock.Clock, refreshPeriod time.Duration) *LazyQueue { q := &LazyQueue{ - popQueue: newSstack(nil), + popQueue: newSstack(nil, false), setIndex: setIndex, priority: priority, maxPriority: maxPriority, @@ -71,8 +71,8 @@ func NewLazyQueue(setIndex SetIndexCallback, priority PriorityCallback, maxPrior // Reset clears the contents of the queue func (q *LazyQueue) Reset() { - q.queue[0] = newSstack(q.setIndex0) - q.queue[1] = newSstack(q.setIndex1) + q.queue[0] = newSstack(q.setIndex0, false) + q.queue[1] = newSstack(q.setIndex1, false) } // Refresh performs queue re-evaluation if necessary diff --git a/common/prque/prque.go b/common/prque/prque.go index 3cc5a1adaf..54c78b5fc2 100755 --- a/common/prque/prque.go +++ b/common/prque/prque.go @@ -28,7 +28,12 @@ type Prque struct { // New creates a new priority queue. func New(setIndex SetIndexCallback) *Prque { - return &Prque{newSstack(setIndex)} + return &Prque{newSstack(setIndex, false)} +} + +// NewWrapAround creates a new priority queue with wrap-around priority handling. +func NewWrapAround(setIndex SetIndexCallback) *Prque { + return &Prque{newSstack(setIndex, true)} } // Pushes a value with a given priority into the queue, expanding if necessary. diff --git a/common/prque/sstack.go b/common/prque/sstack.go index 8518af54ff..b06a95413d 100755 --- a/common/prque/sstack.go +++ b/common/prque/sstack.go @@ -31,22 +31,24 @@ type SetIndexCallback func(data interface{}, index int) // the stack (heap) functionality and the Len, Less and Swap methods for the // sortability requirements of the heaps. type sstack struct { - setIndex SetIndexCallback - size int - capacity int - offset int + setIndex SetIndexCallback + size int + capacity int + offset int + wrapAround bool blocks [][]*item active []*item } // Creates a new, empty stack. -func newSstack(setIndex SetIndexCallback) *sstack { +func newSstack(setIndex SetIndexCallback, wrapAround bool) *sstack { result := new(sstack) result.setIndex = setIndex result.active = make([]*item, blockSize) result.blocks = [][]*item{result.active} result.capacity = blockSize + result.wrapAround = wrapAround return result } @@ -94,7 +96,11 @@ func (s *sstack) Len() int { // Compares the priority of two elements of the stack (higher is first). // Required by sort.Interface. func (s *sstack) Less(i, j int) bool { - return (s.blocks[i/blockSize][i%blockSize].priority - s.blocks[j/blockSize][j%blockSize].priority) > 0 + a, b := s.blocks[i/blockSize][i%blockSize].priority, s.blocks[j/blockSize][j%blockSize].priority + if s.wrapAround { + return a-b > 0 + } + return a > b } // Swaps two elements in the stack. Required by sort.Interface. @@ -110,5 +116,5 @@ func (s *sstack) Swap(i, j int) { // Resets the stack, effectively clearing its contents. func (s *sstack) Reset() { - *s = *newSstack(s.setIndex) + *s = *newSstack(s.setIndex, false) } diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go index 2ff093579d..bc6298979c 100644 --- a/common/prque/sstack_test.go +++ b/common/prque/sstack_test.go @@ -21,7 +21,7 @@ func TestSstack(t *testing.T) { for i := 0; i < size; i++ { data[i] = &item{rand.Int(), rand.Int63()} } - stack := newSstack(nil) + stack := newSstack(nil, false) for rep := 0; rep < 2; rep++ { // Push all the data into the stack, pop out every second secs := []*item{} @@ -55,7 +55,7 @@ func TestSstackSort(t *testing.T) { data[i] = &item{rand.Int(), int64(i)} } // Push all the data into the stack - stack := newSstack(nil) + stack := newSstack(nil, false) for _, val := range data { stack.Push(val) } @@ -76,7 +76,7 @@ func TestSstackReset(t *testing.T) { for i := 0; i < size; i++ { data[i] = &item{rand.Int(), rand.Int63()} } - stack := newSstack(nil) + stack := newSstack(nil, false) for rep := 0; rep < 2; rep++ { // Push all the data into the stack, pop out every second secs := []*item{} diff --git a/les/api.go b/les/api.go index a930524516..782bb31ef2 100644 --- a/les/api.go +++ b/les/api.go @@ -31,7 +31,6 @@ var ( errNoCheckpoint = errors.New("no local checkpoint provided") errNotActivated = errors.New("checkpoint registrar is not activated") errUnknownBenchmarkType = errors.New("unknown benchmark type") - errNoPriority = errors.New("priority too low to raise capacity") ) // PrivateLightServerAPI provides an API to access the LES light server. @@ -44,8 +43,8 @@ type PrivateLightServerAPI struct { func NewPrivateLightServerAPI(server *LesServer) *PrivateLightServerAPI { return &PrivateLightServerAPI{ server: server, - defaultPosFactors: server.clientPool.defaultPosFactors, - defaultNegFactors: server.clientPool.defaultNegFactors, + defaultPosFactors: defaultPosFactors, + defaultNegFactors: defaultNegFactors, } } @@ -66,7 +65,9 @@ func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} { res := make(map[string]interface{}) res["minimumCapacity"] = api.server.minCapacity res["maximumCapacity"] = api.server.maxCapacity - res["totalCapacity"], res["totalConnectedCapacity"], res["priorityConnectedCapacity"] = api.server.clientPool.capacityInfo() + _, res["totalCapacity"] = api.server.clientPool.Limits() + _, res["totalConnectedCapacity"] = api.server.clientPool.Active() + res["priorityConnectedCapacity"] = 0 //TODO connect when token sale module is added return res } @@ -80,9 +81,18 @@ func (api *PrivateLightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[st } res := make(map[enode.ID]map[string]interface{}) - api.server.clientPool.forClients(ids, func(client *clientInfo) { - res[client.node.ID()] = api.clientInfo(client) - }) + if len(ids) == 0 { + ids = api.server.peers.ids() + } + for _, id := range ids { + if peer := api.server.peers.peer(id); peer != nil { + res[id] = api.clientInfo(peer, peer.balance) + } else { + api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) { + res[id] = api.clientInfo(nil, balance) + }) + } + } return res } @@ -94,31 +104,35 @@ func (api *PrivateLightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[st // assigned to it. func (api *PrivateLightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} { res := make(map[enode.ID]map[string]interface{}) - ids := api.server.clientPool.bt.GetPosBalanceIDs(start, stop, maxCount+1) + ids := api.server.clientPool.GetPosBalanceIDs(start, stop, maxCount+1) if len(ids) > maxCount { res[ids[maxCount]] = make(map[string]interface{}) ids = ids[:maxCount] } - if len(ids) != 0 { - api.server.clientPool.forClients(ids, func(client *clientInfo) { - res[client.node.ID()] = api.clientInfo(client) - }) + for _, id := range ids { + if peer := api.server.peers.peer(id); peer != nil { + res[id] = api.clientInfo(peer, peer.balance) + } else { + api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) { + res[id] = api.clientInfo(nil, balance) + }) + } } return res } // clientInfo creates a client info data structure -func (api *PrivateLightServerAPI) clientInfo(c *clientInfo) map[string]interface{} { +func (api *PrivateLightServerAPI) clientInfo(peer *clientPeer, balance vfs.ReadOnlyBalance) map[string]interface{} { info := make(map[string]interface{}) - pb, nb := c.balance.GetBalance() - info["isConnected"] = c.connected + pb, nb := balance.GetBalance() + info["isConnected"] = peer != nil info["pricing/balance"] = pb info["priority"] = pb != 0 // cb := api.server.clientPool.ndb.getCurrencyBalance(id) // info["pricing/currency"] = cb.amount - if c.connected { - info["connectionTime"] = float64(mclock.Now()-c.connectedAt) / float64(time.Second) - info["capacity"], _ = api.server.clientPool.ns.GetField(c.node, priorityPoolSetup.CapacityField).(uint64) + if peer != nil { + info["connectionTime"] = float64(mclock.Now()-peer.connectedAt) / float64(time.Second) + info["capacity"] = peer.getCapacity() info["pricing/negBalance"] = nb } return info @@ -126,7 +140,7 @@ func (api *PrivateLightServerAPI) clientInfo(c *clientInfo) map[string]interface // setParams either sets the given parameters for a single connected client (if specified) // or the default parameters applicable to clients connected in the future -func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) { +func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientPeer, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) { defParams := client == nil for name, value := range params { errValue := func() error { @@ -156,9 +170,8 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien setFactor(&negFactors.RequestFactor) case !defParams && name == "capacity": if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity { - _, err = api.server.clientPool.setCapacity(client.node, client.address, uint64(capacity), 0, true) - // Don't have to call factor update explicitly. It's already done - // in setCapacity function. + _, err = api.server.clientPool.SetCapacity(client.Node(), uint64(capacity), 0, false) + // time factor recalculation is performed automatically by the balance tracker } else { err = errValue() } @@ -179,31 +192,25 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien // SetClientParams sets client parameters for all clients listed in the ids list // or all connected clients if the list is empty func (api *PrivateLightServerAPI) SetClientParams(nodes []string, params map[string]interface{}) error { - var ( - ids []enode.ID - err error - ) + var err error for _, node := range nodes { - if id, err := parseNode(node); err != nil { + var id enode.ID + if id, err = parseNode(node); err != nil { return err - } else { - ids = append(ids, id) } - } - api.server.clientPool.forClients(ids, func(client *clientInfo) { - if client.connected { - posFactors, negFactors := client.balance.GetPriceFactors() - update, e := api.setParams(params, client, &posFactors, &negFactors) + if peer := api.server.peers.peer(id); peer != nil { + posFactors, negFactors := peer.balance.GetPriceFactors() + update, e := api.setParams(params, peer, &posFactors, &negFactors) if update { - client.balance.SetPriceFactors(posFactors, negFactors) + peer.balance.SetPriceFactors(posFactors, negFactors) } if e != nil { err = e } } else { - err = fmt.Errorf("client %064x is not connected", client.node.ID()) + err = fmt.Errorf("client %064x is not connected", id) } - }) + } return err } @@ -211,7 +218,7 @@ func (api *PrivateLightServerAPI) SetClientParams(nodes []string, params map[str func (api *PrivateLightServerAPI) SetDefaultParams(params map[string]interface{}) error { update, err := api.setParams(params, nil, &api.defaultPosFactors, &api.defaultNegFactors) if update { - api.server.clientPool.setDefaultFactors(api.defaultPosFactors, api.defaultNegFactors) + api.server.clientPool.SetDefaultFactors(api.defaultPosFactors, api.defaultNegFactors) } return err } @@ -224,7 +231,7 @@ func (api *PrivateLightServerAPI) SetConnectedBias(bias time.Duration) error { if bias < time.Duration(0) { return fmt.Errorf("bias illegal: %v less than 0", bias) } - api.server.clientPool.setConnectedBias(bias) + api.server.clientPool.SetConnectedBias(bias) return nil } @@ -235,8 +242,8 @@ func (api *PrivateLightServerAPI) AddBalance(node string, amount int64) (balance if id, err = parseNode(node); err != nil { return } - api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { - balance[0], balance[1], err = c.balance.AddBalance(amount) + api.server.clientPool.BalanceOperation(id, "", func(nb vfs.AtomicBalanceOperator) { + balance[0], balance[1], err = nb.AddBalance(amount) }) return } @@ -338,14 +345,12 @@ func (api *PrivateDebugAPI) FreezeClient(node string) error { if id, err = parseNode(node); err != nil { return err } - api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo) { - if c.connected { - c.peer.freeze() - } else { - err = fmt.Errorf("client %064x is not connected", id[:]) - } - }) - return err + if peer := api.server.peers.peer(id); peer != nil { + peer.freeze() + return nil + } else { + return fmt.Errorf("client %064x is not connected", id[:]) + } } // PrivateLightAPI provides an API to access the LES light server or light client. diff --git a/les/clientpool.go b/les/clientpool.go deleted file mode 100644 index 3965d54508..0000000000 --- a/les/clientpool.go +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "fmt" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/les/vflux" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - defaultNegExpTC = 3600 // default time constant (in seconds) for exponentially reducing negative balance - - // defaultConnectedBias is applied to already connected clients So that - // already connected client won't be kicked out very soon and we - // can ensure all connected clients can have enough time to request - // or sync some data. - // - // todo(rjl493456442) make it configurable. It can be the option of - // free trial time! - defaultConnectedBias = time.Minute * 3 - inactiveTimeout = time.Second * 10 -) - -// clientPool implements a client database that assigns a priority to each client -// based on a positive and negative balance. Positive balance is externally assigned -// to prioritized clients and is decreased with connection time and processed -// requests (unless the price factors are zero). If the positive balance is zero -// then negative balance is accumulated. -// -// Balance tracking and priority calculation for connected clients is done by -// balanceTracker. activeQueue ensures that clients with the lowest positive or -// highest negative balance get evicted when the total capacity allowance is full -// and new clients with a better balance want to connect. -// -// Already connected nodes receive a small bias in their favor in order to avoid -// accepting and instantly kicking out clients. In theory, we try to ensure that -// each client can have several minutes of connection time. -// -// Balances of disconnected clients are stored in nodeDB including positive balance -// and negative banalce. Boeth positive balance and negative balance will decrease -// exponentially. If the balance is low enough, then the record will be dropped. -type clientPool struct { - vfs.BalanceTrackerSetup - vfs.PriorityPoolSetup - lock sync.Mutex - clock mclock.Clock - closed bool - removePeer func(enode.ID) - synced func() bool - ns *nodestate.NodeStateMachine - pp *vfs.PriorityPool - bt *vfs.BalanceTracker - - defaultPosFactors, defaultNegFactors vfs.PriceFactors - posExpTC, negExpTC uint64 - minCap uint64 // The minimal capacity value allowed for any client - connectedBias time.Duration - capLimit uint64 -} - -// clientPoolPeer represents a client peer in the pool. -// Positive balances are assigned to node key while negative balances are assigned -// to freeClientId. Currently network IP address without port is used because -// clients have a limited access to IP addresses while new node keys can be easily -// generated so it would be useless to assign a negative value to them. -type clientPoolPeer interface { - Node() *enode.Node - freeClientId() string - updateCapacity(uint64) - freeze() - allowInactive() bool -} - -// clientInfo defines all information required by clientpool. -type clientInfo struct { - node *enode.Node - address string - peer clientPoolPeer - connected, priority bool - connectedAt mclock.AbsTime - balance *vfs.NodeBalance -} - -// newClientPool creates a new client pool -func newClientPool(ns *nodestate.NodeStateMachine, lesDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID), synced func() bool) *clientPool { - pool := &clientPool{ - ns: ns, - BalanceTrackerSetup: balanceTrackerSetup, - PriorityPoolSetup: priorityPoolSetup, - clock: clock, - minCap: minCap, - connectedBias: connectedBias, - removePeer: removePeer, - synced: synced, - } - pool.bt = vfs.NewBalanceTracker(ns, balanceTrackerSetup, lesDb, clock, &utils.Expirer{}, &utils.Expirer{}) - pool.pp = vfs.NewPriorityPool(ns, priorityPoolSetup, clock, minCap, connectedBias, 4) - - // set default expiration constants used by tests - // Note: server overwrites this if token sale is active - pool.bt.SetExpirationTCs(0, defaultNegExpTC) - - ns.SubscribeState(pool.InactiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - if newState.Equals(pool.InactiveFlag) { - ns.AddTimeout(node, pool.InactiveFlag, inactiveTimeout) - } - if oldState.Equals(pool.InactiveFlag) && newState.Equals(pool.InactiveFlag.Or(pool.PriorityFlag)) { - ns.SetStateSub(node, pool.InactiveFlag, nodestate.Flags{}, 0) // remove timeout - } - }) - - ns.SubscribeState(pool.ActiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - c, _ := ns.GetField(node, clientInfoField).(*clientInfo) - if c == nil { - return - } - c.priority = newState.HasAll(pool.PriorityFlag) - if newState.Equals(pool.ActiveFlag) { - cap, _ := ns.GetField(node, pool.CapacityField).(uint64) - if cap > minCap { - pool.pp.RequestCapacity(node, minCap, 0, true) - } - } - }) - - ns.SubscribeState(pool.InactiveFlag.Or(pool.ActiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - if oldState.IsEmpty() { - clientConnectedMeter.Mark(1) - log.Debug("Client connected", "id", node.ID()) - } - if oldState.Equals(pool.InactiveFlag) && newState.Equals(pool.ActiveFlag) { - clientActivatedMeter.Mark(1) - log.Debug("Client activated", "id", node.ID()) - } - if oldState.Equals(pool.ActiveFlag) && newState.Equals(pool.InactiveFlag) { - clientDeactivatedMeter.Mark(1) - log.Debug("Client deactivated", "id", node.ID()) - c, _ := ns.GetField(node, clientInfoField).(*clientInfo) - if c == nil || !c.peer.allowInactive() { - pool.removePeer(node.ID()) - } - } - if newState.IsEmpty() { - clientDisconnectedMeter.Mark(1) - log.Debug("Client disconnected", "id", node.ID()) - pool.removePeer(node.ID()) - } - }) - - var totalConnected uint64 - ns.SubscribeField(pool.CapacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - oldCap, _ := oldValue.(uint64) - newCap, _ := newValue.(uint64) - totalConnected += newCap - oldCap - totalConnectedGauge.Update(int64(totalConnected)) - c, _ := ns.GetField(node, clientInfoField).(*clientInfo) - if c != nil { - c.peer.updateCapacity(newCap) - } - }) - return pool -} - -// stop shuts the client pool down -func (f *clientPool) stop() { - f.lock.Lock() - f.closed = true - f.lock.Unlock() - f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - // enforces saving all balances in BalanceTracker - f.disconnectNode(node) - }) - f.bt.Stop() -} - -// connect should be called after a successful handshake. If the connection was -// rejected, there is no need to call disconnect. -func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) { - f.lock.Lock() - defer f.lock.Unlock() - - // Short circuit if clientPool is already closed. - if f.closed { - return 0, fmt.Errorf("Client pool is already closed") - } - // Dedup connected peers. - node, freeID := peer.Node(), peer.freeClientId() - if f.ns.GetField(node, clientInfoField) != nil { - log.Debug("Client already connected", "address", freeID, "id", node.ID().String()) - return 0, fmt.Errorf("Client already connected address=%s id=%s", freeID, node.ID().String()) - } - now := f.clock.Now() - c := &clientInfo{ - node: node, - address: freeID, - peer: peer, - connected: true, - connectedAt: now, - } - f.ns.SetField(node, clientInfoField, c) - f.ns.SetField(node, connAddressField, freeID) - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { - f.disconnect(peer) - return 0, nil - } - c.balance.SetPriceFactors(f.defaultPosFactors, f.defaultNegFactors) - - f.ns.SetState(node, f.InactiveFlag, nodestate.Flags{}, 0) - var allowed bool - f.ns.Operation(func() { - _, allowed = f.pp.RequestCapacity(node, f.minCap, f.connectedBias, true) - }) - if allowed { - return f.minCap, nil - } - if !peer.allowInactive() { - f.disconnect(peer) - } - return 0, nil -} - -// setConnectedBias sets the connection bias, which is applied to already connected clients -// So that already connected client won't be kicked out very soon and we can ensure all -// connected clients can have enough time to request or sync some data. -func (f *clientPool) setConnectedBias(bias time.Duration) { - f.lock.Lock() - defer f.lock.Unlock() - - f.connectedBias = bias - f.pp.SetActiveBias(bias) -} - -// disconnect should be called when a connection is terminated. If the disconnection -// was initiated by the pool itself using disconnectFn then calling disconnect is -// not necessary but permitted. -func (f *clientPool) disconnect(p clientPoolPeer) { - f.disconnectNode(p.Node()) -} - -// disconnectNode removes node fields and flags related to connected status -func (f *clientPool) disconnectNode(node *enode.Node) { - f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientInfoField, nil) -} - -// setDefaultFactors sets the default price factors applied to subsequently connected clients -func (f *clientPool) setDefaultFactors(posFactors, negFactors vfs.PriceFactors) { - f.lock.Lock() - defer f.lock.Unlock() - - f.defaultPosFactors = posFactors - f.defaultNegFactors = negFactors -} - -// capacityInfo returns the total capacity allowance, the total capacity of connected -// clients and the total capacity of connected and prioritized clients -func (f *clientPool) capacityInfo() (uint64, uint64, uint64) { - f.lock.Lock() - defer f.lock.Unlock() - - // total priority active cap will be supported when the token issuer module is added - _, activeCap := f.pp.Active() - return f.capLimit, activeCap, 0 -} - -// setLimits sets the maximum number and total capacity of connected clients, -// dropping some of them if necessary. -func (f *clientPool) setLimits(totalConn int, totalCap uint64) { - f.lock.Lock() - defer f.lock.Unlock() - - f.capLimit = totalCap - f.pp.SetLimits(uint64(totalConn), totalCap) -} - -// setCapacity sets the assigned capacity of a connected client -func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint64, bias time.Duration, setCap bool) (uint64, error) { - c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) - if c == nil { - if setCap { - return 0, fmt.Errorf("client %064x is not connected", node.ID()) - } - c = &clientInfo{node: node} - f.ns.SetField(node, clientInfoField, c) - f.ns.SetField(node, connAddressField, freeID) - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { - log.Error("BalanceField is missing", "node", node.ID()) - return 0, fmt.Errorf("BalanceField of %064x is missing", node.ID()) - } - defer func() { - f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientInfoField, nil) - }() - } - var ( - minPriority int64 - allowed bool - ) - f.ns.Operation(func() { - if !setCap || c.priority { - // check clientInfo.priority inside Operation to ensure thread safety - minPriority, allowed = f.pp.RequestCapacity(node, capacity, bias, setCap) - } - }) - if allowed { - return 0, nil - } - missing := c.balance.PosBalanceMissing(minPriority, capacity, bias) - if missing < 1 { - // ensure that we never return 0 missing and insufficient priority error - missing = 1 - } - return missing, errNoPriority -} - -// setCapacityLocked is the equivalent of setCapacity used when f.lock is already locked -func (f *clientPool) setCapacityLocked(node *enode.Node, freeID string, capacity uint64, minConnTime time.Duration, setCap bool) (uint64, error) { - f.lock.Lock() - defer f.lock.Unlock() - - return f.setCapacity(node, freeID, capacity, minConnTime, setCap) -} - -// forClients calls the supplied callback for either the listed node IDs or all connected -// nodes. It passes a valid clientInfo to the callback and ensures that the necessary -// fields and flags are set in order for BalanceTracker and PriorityPool to work even if -// the node is not connected. -func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { - f.lock.Lock() - defer f.lock.Unlock() - - if len(ids) == 0 { - f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) - if c != nil { - cb(c) - } - }) - } else { - for _, id := range ids { - node := f.ns.GetNode(id) - if node == nil { - node = enode.SignNull(&enr.Record{}, id) - } - c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) - if c != nil { - cb(c) - } else { - c = &clientInfo{node: node} - f.ns.SetField(node, clientInfoField, c) - f.ns.SetField(node, connAddressField, "") - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance != nil { - cb(c) - } else { - log.Error("BalanceField is missing") - } - f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientInfoField, nil) - } - } - } -} - -// serveCapQuery serves a vflux capacity query. It receives multiple token amount values -// and a bias time value. For each given token amount it calculates the maximum achievable -// capacity in case the amount is added to the balance. -func (f *clientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte { - var req vflux.CapacityQueryReq - if rlp.DecodeBytes(data, &req) != nil { - return nil - } - if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { - return nil - } - result := make(vflux.CapacityQueryReply, len(req.AddTokens)) - if !f.synced() { - capacityQueryZeroMeter.Mark(1) - reply, _ := rlp.EncodeToBytes(&result) - return reply - } - - node := f.ns.GetNode(id) - if node == nil { - node = enode.SignNull(&enr.Record{}, id) - } - c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) - if c == nil { - c = &clientInfo{node: node} - f.ns.SetField(node, clientInfoField, c) - f.ns.SetField(node, connAddressField, freeID) - defer func() { - f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientInfoField, nil) - }() - if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*vfs.NodeBalance); c.balance == nil { - log.Error("BalanceField is missing", "node", node.ID()) - return nil - } - } - // use vfs.CapacityCurve to answer request for multiple newly bought token amounts - curve := f.pp.GetCapacityCurve().Exclude(id) - bias := time.Second * time.Duration(req.Bias) - if f.connectedBias > bias { - bias = f.connectedBias - } - pb, _ := c.balance.GetBalance() - for i, addTokens := range req.AddTokens { - add := addTokens.Int64() - result[i] = curve.MaxCapacity(func(capacity uint64) int64 { - return c.balance.EstimatePriority(capacity, add, 0, bias, false) / int64(capacity) - }) - if add <= 0 && uint64(-add) >= pb && result[i] > f.minCap { - result[i] = f.minCap - } - if result[i] < f.minCap { - result[i] = 0 - } - } - // add first result to metrics (don't care about priority client multi-queries yet) - if result[0] == 0 { - capacityQueryZeroMeter.Mark(1) - } else { - capacityQueryNonZeroMeter.Mark(1) - } - reply, _ := rlp.EncodeToBytes(&result) - return reply -} diff --git a/les/flowcontrol/manager.go b/les/flowcontrol/manager.go index d6d0b1adde..c9e681c144 100644 --- a/les/flowcontrol/manager.go +++ b/les/flowcontrol/manager.go @@ -108,7 +108,7 @@ type ClientManager struct { func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager { cm := &ClientManager{ clock: clock, - rcQueue: prque.New(func(a interface{}, i int) { a.(*ClientNode).queueIndex = i }), + rcQueue: prque.NewWrapAround(func(a interface{}, i int) { a.(*ClientNode).queueIndex = i }), capLastUpdate: clock.Now(), stop: make(chan chan struct{}), } diff --git a/les/metrics.go b/les/metrics.go index 5a8d4bbe02..d356326b76 100644 --- a/les/metrics.go +++ b/les/metrics.go @@ -73,12 +73,9 @@ var ( serverConnectionGauge = metrics.NewRegisteredGauge("les/connection/server", nil) clientConnectionGauge = metrics.NewRegisteredGauge("les/connection/client", nil) - totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil) - totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil) - totalConnectedGauge = metrics.NewRegisteredGauge("les/server/totalConnected", nil) - blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil) - capacityQueryZeroMeter = metrics.NewRegisteredMeter("les/server/capQueryZero", nil) - capacityQueryNonZeroMeter = metrics.NewRegisteredMeter("les/server/capQueryNonZero", nil) + totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil) + totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil) + blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil) requestServedMeter = metrics.NewRegisteredMeter("les/server/req/avgServedTime", nil) requestServedTimer = metrics.NewRegisteredTimer("les/server/req/servedTime", nil) @@ -100,12 +97,8 @@ var ( sqServedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/served", nil) sqQueuedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/queued", nil) - clientConnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/connected", nil) - clientActivatedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/activated", nil) - clientDeactivatedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/deactivated", nil) - clientDisconnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/disconnected", nil) - clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil) - clientErrorMeter = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil) + clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil) + clientErrorMeter = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil) requestRTT = metrics.NewRegisteredTimer("les/client/req/rtt", nil) requestSendDelay = metrics.NewRegisteredTimer("les/client/req/sendDelay", nil) diff --git a/les/peer.go b/les/peer.go index 78019b1d87..f6cc94dfad 100644 --- a/les/peer.go +++ b/les/peer.go @@ -17,6 +17,7 @@ package les import ( + "crypto/ecdsa" "errors" "fmt" "math/big" @@ -37,6 +38,7 @@ import ( vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" ) @@ -762,15 +764,22 @@ type clientPeer struct { responseLock sync.Mutex responseCount uint64 // Counter to generate an unique id for request processing. - balance *vfs.NodeBalance + balance vfs.ConnectedBalance // invalidLock is used for protecting invalidCount. invalidLock sync.RWMutex invalidCount utils.LinearExpiredValue // Counter the invalid request the client peer has made. - server bool - errCh chan error - fcClient *flowcontrol.ClientNode // Server side mirror token bucket. + capacity uint64 + // lastAnnounce is the last broadcast created by the server; may be newer than the last head + // sent to the specific client (stored in headInfo) if capacity is zero. In this case the + // latest head is sent when the client gains non-zero capacity. + lastAnnounce announceData + + connectedAt mclock.AbsTime + server bool + errCh chan error + fcClient *flowcontrol.ClientNode // Server side mirror token bucket. } func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *clientPeer { @@ -789,9 +798,9 @@ func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWrite } } -// freeClientId returns a string identifier for the peer. Multiple peers with +// FreeClientId returns a string identifier for the peer. Multiple peers with // the same identifier can not be connected in free mode simultaneously. -func (p *clientPeer) freeClientId() string { +func (p *clientPeer) FreeClientId() string { if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok { if addr.IP.IsLoopback() { // using peer id instead of loopback ip address allows multiple free @@ -921,25 +930,69 @@ func (p *clientPeer) sendAnnounce(request announceData) error { return p2p.Send(p.rw, AnnounceMsg, request) } -// allowInactive implements clientPoolPeer -func (p *clientPeer) allowInactive() bool { - return false +// InactiveAllowance implements vfs.clientPeer +func (p *clientPeer) InactiveAllowance() time.Duration { + return 0 // will return more than zero for les/5 clients +} + +// getCapacity returns the current capacity of the peer +func (p *clientPeer) getCapacity() uint64 { + p.lock.RLock() + defer p.lock.RUnlock() + + return p.capacity } -// updateCapacity updates the request serving capacity assigned to a given client -// and also sends an announcement about the updated flow control parameters -func (p *clientPeer) updateCapacity(cap uint64) { +// UpdateCapacity updates the request serving capacity assigned to a given client +// and also sends an announcement about the updated flow control parameters. +// Note: UpdateCapacity implements vfs.clientPeer and should not block. The requested +// parameter is true if the callback was initiated by ClientPool.SetCapacity on the given peer. +func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { p.lock.Lock() defer p.lock.Unlock() - if cap != p.fcParams.MinRecharge { - p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio} + if newCap != p.fcParams.MinRecharge { + p.fcParams = flowcontrol.ServerParams{MinRecharge: newCap, BufLimit: newCap * bufLimitRatio} p.fcClient.UpdateParams(p.fcParams) var kvList keyValueList - kvList = kvList.add("flowControl/MRR", cap) - kvList = kvList.add("flowControl/BL", cap*bufLimitRatio) + kvList = kvList.add("flowControl/MRR", newCap) + kvList = kvList.add("flowControl/BL", newCap*bufLimitRatio) p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) } + + if p.capacity == 0 && newCap != 0 { + p.sendLastAnnounce() + } + p.capacity = newCap +} + +// announceOrStore sends the given head announcement to the client if the client is +// active (capacity != 0) and the same announcement hasn't been sent before. If the +// client is inactive the announcement is stored and sent later if the client is +// activated again. +func (p *clientPeer) announceOrStore(announce announceData) { + p.lock.Lock() + defer p.lock.Unlock() + + p.lastAnnounce = announce + if p.capacity != 0 { + p.sendLastAnnounce() + } +} + +// announce sends the given head announcement to the client if it hasn't been sent before +func (p *clientPeer) sendLastAnnounce() { + if p.lastAnnounce.Td == nil { + return + } + if p.headInfo.Td == nil || p.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 { + if !p.queueSend(func() { p.sendAnnounce(p.lastAnnounce) }) { + p.Log().Debug("Dropped announcement because queue is full", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash) + } else { + p.Log().Debug("Sent announcement", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash) + } + p.headInfo = blockInfo{Hash: p.lastAnnounce.Hash, Number: p.lastAnnounce.Number, Td: p.lastAnnounce.Td} + } } // freezeClient temporarily puts the client in a frozen state which means all @@ -1064,6 +1117,11 @@ func (p *clientPeer) getInvalid() uint64 { return p.invalidCount.Value(mclock.Now()) } +// Disconnect implements vfs.clientPeer +func (p *clientPeer) Disconnect() { + p.Peer.Disconnect(p2p.DiscRequested) +} + // serverPeerSubscriber is an interface to notify services about added or // removed server peers type serverPeerSubscriber interface { @@ -1221,3 +1279,181 @@ func (ps *serverPeerSet) close() { } ps.closed = true } + +// clientPeerSet represents the set of active client peers currently +// participating in the Light Ethereum sub-protocol. +type clientPeerSet struct { + peers map[enode.ID]*clientPeer + lock sync.RWMutex + closed bool + + privateKey *ecdsa.PrivateKey + lastAnnounce, signedAnnounce announceData +} + +// newClientPeerSet creates a new peer set to track the client peers. +func newClientPeerSet() *clientPeerSet { + return &clientPeerSet{peers: make(map[enode.ID]*clientPeer)} +} + +// register adds a new peer into the peer set, or returns an error if the +// peer is already known. +func (ps *clientPeerSet) register(peer *clientPeer) error { + ps.lock.Lock() + defer ps.lock.Unlock() + + if ps.closed { + return errClosed + } + if _, exist := ps.peers[peer.ID()]; exist { + return errAlreadyRegistered + } + ps.peers[peer.ID()] = peer + ps.announceOrStore(peer) + return nil +} + +// unregister removes a remote peer from the peer set, disabling any further +// actions to/from that particular entity. It also initiates disconnection +// at the networking layer. +func (ps *clientPeerSet) unregister(id enode.ID) error { + ps.lock.Lock() + defer ps.lock.Unlock() + + p, ok := ps.peers[id] + if !ok { + return errNotRegistered + } + delete(ps.peers, id) + p.Peer.Disconnect(p2p.DiscRequested) + return nil +} + +// ids returns a list of all registered peer IDs +func (ps *clientPeerSet) ids() []enode.ID { + ps.lock.RLock() + defer ps.lock.RUnlock() + + var ids []enode.ID + for id := range ps.peers { + ids = append(ids, id) + } + return ids +} + +// peer retrieves the registered peer with the given id. +func (ps *clientPeerSet) peer(id enode.ID) *clientPeer { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return ps.peers[id] +} + +// len returns if the current number of peers in the set. +func (ps *clientPeerSet) len() int { + ps.lock.RLock() + defer ps.lock.RUnlock() + + return len(ps.peers) +} + +// setSignerKey sets the signer key for signed announcements. Should be called before +// starting the protocol handler. +func (ps *clientPeerSet) setSignerKey(privateKey *ecdsa.PrivateKey) { + ps.privateKey = privateKey +} + +// broadcast sends the given announcements to all active peers +func (ps *clientPeerSet) broadcast(announce announceData) { + ps.lock.Lock() + defer ps.lock.Unlock() + + ps.lastAnnounce = announce + for _, peer := range ps.peers { + ps.announceOrStore(peer) + } +} + +// announceOrStore sends the requested type of announcement to the given peer or stores +// it for later if the peer is inactive (capacity == 0). +func (ps *clientPeerSet) announceOrStore(p *clientPeer) { + if ps.lastAnnounce.Td == nil { + return + } + switch p.announceType { + case announceTypeSimple: + p.announceOrStore(ps.lastAnnounce) + case announceTypeSigned: + if ps.signedAnnounce.Hash != ps.lastAnnounce.Hash { + ps.signedAnnounce = ps.lastAnnounce + ps.signedAnnounce.sign(ps.privateKey) + } + p.announceOrStore(ps.signedAnnounce) + } +} + +// close disconnects all peers. No new peers can be registered +// after close has returned. +func (ps *clientPeerSet) close() { + ps.lock.Lock() + defer ps.lock.Unlock() + + for _, p := range ps.peers { + p.Peer.Disconnect(p2p.DiscQuitting) + } + ps.closed = true +} + +// serverSet is a special set which contains all connected les servers. +// Les servers will also be discovered by discovery protocol because they +// also run the LES protocol. We can't drop them although they are useless +// for us(server) but for other protocols(e.g. ETH) upon the devp2p they +// may be useful. +type serverSet struct { + lock sync.Mutex + set map[string]*clientPeer + closed bool +} + +func newServerSet() *serverSet { + return &serverSet{set: make(map[string]*clientPeer)} +} + +func (s *serverSet) register(peer *clientPeer) error { + s.lock.Lock() + defer s.lock.Unlock() + + if s.closed { + return errClosed + } + if _, exist := s.set[peer.id]; exist { + return errAlreadyRegistered + } + s.set[peer.id] = peer + return nil +} + +func (s *serverSet) unregister(peer *clientPeer) error { + s.lock.Lock() + defer s.lock.Unlock() + + if s.closed { + return errClosed + } + if _, exist := s.set[peer.id]; !exist { + return errNotRegistered + } + delete(s.set, peer.id) + peer.Peer.Disconnect(p2p.DiscQuitting) + return nil +} + +func (s *serverSet) close() { + s.lock.Lock() + defer s.lock.Unlock() + + for _, p := range s.set { + p.Peer.Disconnect(p2p.DiscQuitting) + } + s.closed = true +} diff --git a/les/server.go b/les/server.go index be64dfe190..d44b1b57d4 100644 --- a/les/server.go +++ b/les/server.go @@ -18,7 +18,6 @@ package les import ( "crypto/ecdsa" - "reflect" "time" "github.com/ethereum/go-ethereum/common/mclock" @@ -26,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/flowcontrol" - "github.com/ethereum/go-ethereum/les/vflux" vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" @@ -34,24 +32,16 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) var ( - serverSetup = &nodestate.Setup{} - clientPeerField = serverSetup.NewField("clientPeer", reflect.TypeOf(&clientPeer{})) - clientInfoField = serverSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{})) - connAddressField = serverSetup.NewField("connAddr", reflect.TypeOf("")) - balanceTrackerSetup = vfs.NewBalanceTrackerSetup(serverSetup) - priorityPoolSetup = vfs.NewPriorityPoolSetup(serverSetup) + defaultPosFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1} + defaultNegFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1} ) -func init() { - balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField) - priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority -} +const defaultConnectedBias = time.Minute * 3 type ethBackend interface { ArchiveMode() bool @@ -65,10 +55,10 @@ type ethBackend interface { type LesServer struct { lesCommons - ns *nodestate.NodeStateMachine archiveMode bool // Flag whether the ethereum node runs in archive mode. handler *serverHandler - broadcaster *broadcaster + peers *clientPeerSet + serverset *serverSet vfluxServer *vfs.Server privateKey *ecdsa.PrivateKey @@ -77,7 +67,7 @@ type LesServer struct { costTracker *costTracker defParams flowcontrol.ServerParams servingQueue *servingQueue - clientPool *clientPool + clientPool *vfs.ClientPool minCapacity, maxCapacity uint64 threadsIdle int // Request serving threads count when system is idle. @@ -91,7 +81,6 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les if err != nil { return nil, err } - ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) // Calculate the number of threads used to service the light client // requests based on the user-specified value. threads := config.LightServ * 4 / 100 @@ -111,9 +100,9 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true), closeCh: make(chan struct{}), }, - ns: ns, archiveMode: e.ArchiveMode(), - broadcaster: newBroadcaster(ns), + peers: newClientPeerSet(), + serverset: newServerSet(), vfluxServer: vfs.NewServer(time.Millisecond * 10), fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), @@ -121,7 +110,6 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les threadsIdle: threads, p2pSrv: node.Server(), } - srv.vfluxServer.Register(srv) issync := e.Synced if config.LightNoSyncServe { issync = func() bool { return true } @@ -149,8 +137,10 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les srv.maxCapacity = totalRecharge } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) - srv.clientPool = newClientPool(ns, lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient, issync) - srv.clientPool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) + srv.clientPool = vfs.NewClientPool(lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, issync) + srv.clientPool.Start() + srv.clientPool.SetDefaultFactors(defaultPosFactors, defaultNegFactors) + srv.vfluxServer.Register(srv.clientPool, "les", "Ethereum light client service") checkpoint := srv.latestLocalCheckpoint() if !checkpoint.Empty() { @@ -162,14 +152,6 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les node.RegisterProtocols(srv.Protocols()) node.RegisterAPIs(srv.APIs()) node.RegisterLifecycle(srv) - - // disconnect all peers at nsm shutdown - ns.SubscribeField(clientPeerField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - if state.Equals(serverSetup.OfflineFlag()) && oldValue != nil { - oldValue.(*clientPeer).Peer.Disconnect(p2p.DiscRequested) - } - }) - ns.Start() return srv, nil } @@ -198,7 +180,7 @@ func (s *LesServer) APIs() []rpc.API { func (s *LesServer) Protocols() []p2p.Protocol { ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} { - if p := s.getClient(id); p != nil { + if p := s.peers.peer(id); p != nil { return p.Info() } return nil @@ -215,7 +197,7 @@ func (s *LesServer) Protocols() []p2p.Protocol { // Start starts the LES server func (s *LesServer) Start() error { s.privateKey = s.p2pSrv.PrivateKey - s.broadcaster.setSignerKey(s.privateKey) + s.peers.setSignerKey(s.privateKey) s.handler.start() s.wg.Add(1) go s.capacityManagement() @@ -229,8 +211,9 @@ func (s *LesServer) Start() error { func (s *LesServer) Stop() error { close(s.closeCh) - s.clientPool.stop() - s.ns.Stop() + s.clientPool.Stop() + s.serverset.close() + s.peers.close() s.fcManager.Stop() s.costTracker.stop() s.handler.stop() @@ -261,7 +244,7 @@ func (s *LesServer) capacityManagement() { totalCapacityCh := make(chan uint64, 100) totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh) - s.clientPool.setLimits(s.config.LightPeers, totalCapacity) + s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity) var ( busy bool @@ -298,39 +281,9 @@ func (s *LesServer) capacityManagement() { log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers) } freePeers = newFreePeers - s.clientPool.setLimits(s.config.LightPeers, totalCapacity) + s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity) case <-s.closeCh: return } } } - -func (s *LesServer) getClient(id enode.ID) *clientPeer { - if node := s.ns.GetNode(id); node != nil { - if p, ok := s.ns.GetField(node, clientPeerField).(*clientPeer); ok { - return p - } - } - return nil -} - -func (s *LesServer) dropClient(id enode.ID) { - if p := s.getClient(id); p != nil { - p.Peer.Disconnect(p2p.DiscRequested) - } -} - -// ServiceInfo implements vfs.Service -func (s *LesServer) ServiceInfo() (string, string) { - return "les", "Ethereum light client service" -} - -// Handle implements vfs.Service -func (s *LesServer) Handle(id enode.ID, address string, name string, data []byte) []byte { - switch name { - case vflux.CapacityQueryName: - return s.clientPool.serveCapQuery(id, address, data) - default: - return nil - } -} diff --git a/les/server_handler.go b/les/server_handler.go index 7651d03cab..5e12136d90 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -17,7 +17,6 @@ package les import ( - "crypto/ecdsa" "errors" "sync" "sync/atomic" @@ -31,13 +30,10 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -59,7 +55,6 @@ const ( var ( errTooManyInvalidRequest = errors.New("too many invalid requests made") - errFullClientPool = errors.New("client pool is full") ) // serverHandler is responsible for serving light client and process @@ -128,32 +123,18 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } - // Reject the duplicated peer, otherwise register it to peerset. - var registered bool - if err := h.server.ns.Operation(func() { - if h.server.ns.GetField(p.Node(), clientPeerField) != nil { - registered = true - } else { - h.server.ns.SetFieldSub(p.Node(), clientPeerField, p) - } - }); err != nil { - return err - } - if registered { - return errAlreadyRegistered - } - defer func() { - h.server.ns.SetField(p.Node(), clientPeerField, nil) - if p.fcClient != nil { // is nil when connecting another server - p.fcClient.Disconnect() - } - }() if p.server { + if err := h.server.serverset.register(p); err != nil { + return err + } // connected to another server, no messages expected, just wait for disconnection _, err := p.rw.ReadMsg() + h.server.serverset.unregister(p) return err } + defer p.fcClient.Disconnect() // set by handshake if it's not another server + // Reject light clients if server is not synced. // // Put this checking here, so that "non-synced" les-server peers are still allowed @@ -162,30 +143,31 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light server not synced, rejecting peer") return p2p.DiscRequested } - // Disconnect the inbound peer if it's rejected by clientPool - if cap, err := h.server.clientPool.connect(p); cap != p.fcParams.MinRecharge || err != nil { - p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool) - return errFullClientPool + if err := h.server.peers.register(p); err != nil { + return err } - p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*vfs.NodeBalance) - if p.balance == nil { + if p.balance = h.server.clientPool.Register(p); p.balance == nil { + h.server.peers.unregister(p.ID()) + p.Log().Debug("Client pool already closed") return p2p.DiscRequested } - activeCount, _ := h.server.clientPool.pp.Active() + activeCount, _ := h.server.clientPool.Active() clientConnectionGauge.Update(int64(activeCount)) + p.connectedAt = mclock.Now() var wg sync.WaitGroup // Wait group used to track all in-flight task routines. - connectedAt := mclock.Now() defer func() { wg.Wait() // Ensure all background task routines have exited. - h.server.clientPool.disconnect(p) + h.server.clientPool.Unregister(p) + h.server.peers.unregister(p.ID()) p.balance = nil - activeCount, _ := h.server.clientPool.pp.Active() + activeCount, _ := h.server.clientPool.Active() clientConnectionGauge.Update(int64(activeCount)) - connectionTimer.Update(time.Duration(mclock.Now() - connectedAt)) + connectionTimer.Update(time.Duration(mclock.Now() - p.connectedAt)) }() - // Mark the peer starts to be served. + + // Mark the peer as being served. atomic.StoreUint32(&p.serving, 1) defer atomic.StoreUint32(&p.serving, 0) @@ -448,78 +430,9 @@ func (h *serverHandler) broadcastLoop() { } lastHead, lastTd = header, td log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg) - h.server.broadcaster.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}) + h.server.peers.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}) case <-h.closeCh: return } } } - -// broadcaster sends new header announcements to active client peers -type broadcaster struct { - ns *nodestate.NodeStateMachine - privateKey *ecdsa.PrivateKey - lastAnnounce, signedAnnounce announceData -} - -// newBroadcaster creates a new broadcaster -func newBroadcaster(ns *nodestate.NodeStateMachine) *broadcaster { - b := &broadcaster{ns: ns} - ns.SubscribeState(priorityPoolSetup.ActiveFlag, func(node *enode.Node, oldState, newState nodestate.Flags) { - if newState.Equals(priorityPoolSetup.ActiveFlag) { - // send last announcement to activated peers - b.sendTo(node) - } - }) - return b -} - -// setSignerKey sets the signer key for signed announcements. Should be called before -// starting the protocol handler. -func (b *broadcaster) setSignerKey(privateKey *ecdsa.PrivateKey) { - b.privateKey = privateKey -} - -// broadcast sends the given announcements to all active peers -func (b *broadcaster) broadcast(announce announceData) { - b.ns.Operation(func() { - // iterate in an Operation to ensure that the active set does not change while iterating - b.lastAnnounce = announce - b.ns.ForEach(priorityPoolSetup.ActiveFlag, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - b.sendTo(node) - }) - }) -} - -// sendTo sends the most recent announcement to the given node unless the same or higher Td -// announcement has already been sent. -func (b *broadcaster) sendTo(node *enode.Node) { - if b.lastAnnounce.Td == nil { - return - } - if p, _ := b.ns.GetField(node, clientPeerField).(*clientPeer); p != nil { - if p.headInfo.Td == nil || b.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 { - announce := b.lastAnnounce - switch p.announceType { - case announceTypeSimple: - if !p.queueSend(func() { p.sendAnnounce(announce) }) { - log.Debug("Drop announcement because queue is full", "number", announce.Number, "hash", announce.Hash) - } else { - log.Debug("Sent announcement", "number", announce.Number, "hash", announce.Hash) - } - case announceTypeSigned: - if b.signedAnnounce.Hash != b.lastAnnounce.Hash { - b.signedAnnounce = b.lastAnnounce - b.signedAnnounce.sign(b.privateKey) - } - announce := b.signedAnnounce - if !p.queueSend(func() { p.sendAnnounce(announce) }) { - log.Debug("Drop announcement because queue is full", "number", announce.Number, "hash", announce.Hash) - } else { - log.Debug("Sent announcement", "number", announce.Number, "hash", announce.Hash) - } - } - p.headInfo = blockInfo{b.lastAnnounce.Hash, b.lastAnnounce.Number, b.lastAnnounce.Td} - } - } -} diff --git a/les/servingqueue.go b/les/servingqueue.go index 9db84e6159..16e064cb3f 100644 --- a/les/servingqueue.go +++ b/les/servingqueue.go @@ -123,7 +123,7 @@ func (t *servingTask) waitOrStop() bool { // newServingQueue returns a new servingQueue func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue { sq := &servingQueue{ - queue: prque.New(nil), + queue: prque.NewWrapAround(nil), suspendBias: suspendBias, queueAddCh: make(chan *servingTask, 100), queueBestCh: make(chan *servingTask), @@ -279,7 +279,7 @@ func (sq *servingQueue) updateRecentTime() { func (sq *servingQueue) addTask(task *servingTask) { if sq.best == nil { sq.best = task - } else if task.priority > sq.best.priority { + } else if task.priority-sq.best.priority > 0 { sq.queue.Push(sq.best, sq.best.priority) sq.best = task } else { diff --git a/les/test_helper.go b/les/test_helper.go index e49bfc8738..ee2da2f8eb 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -45,10 +45,10 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/les/checkpointoracle" "github.com/ethereum/go-ethereum/les/flowcontrol" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" "github.com/ethereum/go-ethereum/params" ) @@ -284,7 +284,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } oracle = checkpointoracle.New(checkpointConfig, getLocal) } - ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) server := &LesServer{ lesCommons: lesCommons{ genesis: genesis.Hash(), @@ -296,8 +295,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da oracle: oracle, closeCh: make(chan struct{}), }, - ns: ns, - broadcaster: newBroadcaster(ns), + peers: newClientPeerSet(), servingQueue: newServingQueue(int64(time.Millisecond*10), 1), defParams: flowcontrol.ServerParams{ BufLimit: testBufLimit, @@ -307,14 +305,14 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } server.costTracker, server.minCapacity = newCostTracker(db, server.config) server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism. - server.clientPool = newClientPool(ns, db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {}, alwaysTrueFn) - server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool + server.clientPool = vfs.NewClientPool(db, testBufRecharge, defaultConnectedBias, clock, alwaysTrueFn) + server.clientPool.Start() + server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) if server.oracle != nil { server.oracle.Start(simulation) } server.servingQueue.setThreads(4) - ns.Start() server.handler.start() return server.handler, simulation } diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go index db12a5c573..01e645a16a 100644 --- a/les/vflux/server/balance.go +++ b/les/vflux/server/balance.go @@ -47,21 +47,57 @@ type PriceFactors struct { TimeFactor, CapacityFactor, RequestFactor float64 } -// timePrice returns the price of connection per nanosecond at the given capacity -func (p PriceFactors) timePrice(cap uint64) float64 { - return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 +// connectionPrice returns the price of connection per nanosecond at the given capacity +// and the estimated average request cost. +func (p PriceFactors) connectionPrice(cap uint64, avgReqCost float64) float64 { + return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 + p.RequestFactor*avgReqCost } -// NodeBalance keeps track of the positive and negative balances of a connected +type ( + // nodePriority interface provides current and estimated future priorities on demand + nodePriority interface { + // priority should return the current priority of the node (higher is better) + priority(cap uint64) int64 + // estimatePriority should return a lower estimate for the minimum of the node priority + // value starting from the current moment until the given time. If the priority goes + // under the returned estimate before the specified moment then it is the caller's + // responsibility to signal with updateFlag. + estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 + } + + // ReadOnlyBalance provides read-only operations on the node balance + ReadOnlyBalance interface { + nodePriority + GetBalance() (uint64, uint64) + GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) + GetPriceFactors() (posFactor, negFactor PriceFactors) + } + + // ConnectedBalance provides operations permitted on connected nodes (non-read-only + // operations are not permitted inside a BalanceOperation) + ConnectedBalance interface { + ReadOnlyBalance + SetPriceFactors(posFactor, negFactor PriceFactors) + RequestServed(cost uint64) uint64 + } + + // AtomicBalanceOperator provides operations permitted in an atomic BalanceOperation + AtomicBalanceOperator interface { + ReadOnlyBalance + AddBalance(amount int64) (uint64, uint64, error) + SetBalance(pos, neg uint64) error + } +) + +// nodeBalance keeps track of the positive and negative balances of a connected // client and calculates actual and projected future priority values. // Implements nodePriority interface. -type NodeBalance struct { - bt *BalanceTracker +type nodeBalance struct { + bt *balanceTracker lock sync.RWMutex node *enode.Node connAddress string - active bool - priority bool + active, hasPriority, setFlags bool capacity uint64 balance balance posFactor, negFactor PriceFactors @@ -78,7 +114,62 @@ type NodeBalance struct { // balance represents a pair of positive and negative balances type balance struct { - pos, neg utils.ExpiredValue + pos, neg utils.ExpiredValue + posExp, negExp utils.ValueExpirer +} + +// posValue returns the value of positive balance at a given timestamp. +func (b balance) posValue(now mclock.AbsTime) uint64 { + return b.pos.Value(b.posExp.LogOffset(now)) +} + +// negValue returns the value of negative balance at a given timestamp. +func (b balance) negValue(now mclock.AbsTime) uint64 { + return b.neg.Value(b.negExp.LogOffset(now)) +} + +// addValue adds the value of a given amount to the balance. The original value and +// updated value will also be returned if the addition is successful. +// Returns the error if the given value is too large and the value overflows. +func (b *balance) addValue(now mclock.AbsTime, amount int64, pos bool, force bool) (uint64, uint64, int64, error) { + var ( + val utils.ExpiredValue + offset utils.Fixed64 + ) + if pos { + offset, val = b.posExp.LogOffset(now), b.pos + } else { + offset, val = b.negExp.LogOffset(now), b.neg + } + old := val.Value(offset) + if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) { + if !force { + return old, 0, 0, errBalanceOverflow + } + val = utils.ExpiredValue{} + amount = maxBalance + } + net := val.Add(amount, offset) + if pos { + b.pos = val + } else { + b.neg = val + } + return old, val.Value(offset), net, nil +} + +// setValue sets the internal balance amount to the given values. Returns the +// error if the given value is too large. +func (b *balance) setValue(now mclock.AbsTime, pos uint64, neg uint64) error { + if pos > maxBalance || neg > maxBalance { + return errBalanceOverflow + } + var pb, nb utils.ExpiredValue + pb.Add(int64(pos), b.posExp.LogOffset(now)) + nb.Add(int64(neg), b.negExp.LogOffset(now)) + b.pos = pb + b.neg = nb + return nil } // balanceCallback represents a single callback that is activated when client priority @@ -90,18 +181,18 @@ type balanceCallback struct { } // GetBalance returns the current positive and negative balance. -func (n *NodeBalance) GetBalance() (uint64, uint64) { +func (n *nodeBalance) GetBalance() (uint64, uint64) { n.lock.Lock() defer n.lock.Unlock() now := n.bt.clock.Now() n.updateBalance(now) - return n.balance.pos.Value(n.bt.posExp.LogOffset(now)), n.balance.neg.Value(n.bt.negExp.LogOffset(now)) + return n.balance.posValue(now), n.balance.negValue(now) } // GetRawBalance returns the current positive and negative balance // but in the raw(expired value) format. -func (n *NodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { +func (n *nodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { n.lock.Lock() defer n.lock.Unlock() @@ -114,164 +205,147 @@ func (n *NodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { // before and after the operation. Exceeding maxBalance results in an error (balance is // unchanged) while adding a negative amount higher than the current balance results in // zero balance. -func (n *NodeBalance) AddBalance(amount int64) (uint64, uint64, error) { +// Note: this function should run inside a NodeStateMachine operation +func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) { var ( - err error - old, new uint64 + err error + old, new uint64 + now = n.bt.clock.Now() + callbacks []func() + setPriority bool ) - n.bt.ns.Operation(func() { - var ( - callbacks []func() - setPriority bool - ) - n.bt.updateTotalBalance(n, func() bool { - now := n.bt.clock.Now() - n.updateBalance(now) - - // Ensure the given amount is valid to apply. - offset := n.bt.posExp.LogOffset(now) - old = n.balance.pos.Value(offset) - if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) { - err = errBalanceOverflow - return false - } - - // Update the total positive balance counter. - n.balance.pos.Add(amount, offset) - callbacks = n.checkCallbacks(now) - setPriority = n.checkPriorityStatus() - new = n.balance.pos.Value(offset) - n.storeBalance(true, false) - return true - }) - for _, cb := range callbacks { - cb() - } - if setPriority { - n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) + // Operation with holding the lock + n.bt.updateTotalBalance(n, func() bool { + n.updateBalance(now) + if old, new, _, err = n.balance.addValue(now, amount, true, false); err != nil { + return false } - n.signalPriorityUpdate() + callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus() + n.storeBalance(true, false) + return true }) if err != nil { return old, old, err } - + // Operation without holding the lock + for _, cb := range callbacks { + cb() + } + if n.setFlags { + if setPriority { + n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) + } + // Note: priority flag is automatically removed by the zero priority callback if necessary + n.signalPriorityUpdate() + } return old, new, nil } // SetBalance sets the positive and negative balance to the given values -func (n *NodeBalance) SetBalance(pos, neg uint64) error { - if pos > maxBalance || neg > maxBalance { - return errBalanceOverflow - } - n.bt.ns.Operation(func() { - var ( - callbacks []func() - setPriority bool - ) - n.bt.updateTotalBalance(n, func() bool { - now := n.bt.clock.Now() - n.updateBalance(now) - - var pb, nb utils.ExpiredValue - pb.Add(int64(pos), n.bt.posExp.LogOffset(now)) - nb.Add(int64(neg), n.bt.negExp.LogOffset(now)) - n.balance.pos = pb - n.balance.neg = nb - callbacks = n.checkCallbacks(now) - setPriority = n.checkPriorityStatus() - n.storeBalance(true, true) - return true - }) - for _, cb := range callbacks { - cb() +// Note: this function should run inside a NodeStateMachine operation +func (n *nodeBalance) SetBalance(pos, neg uint64) error { + var ( + now = n.bt.clock.Now() + callbacks []func() + setPriority bool + ) + // Operation with holding the lock + n.bt.updateTotalBalance(n, func() bool { + n.updateBalance(now) + if err := n.balance.setValue(now, pos, neg); err != nil { + return false } + callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus() + n.storeBalance(true, true) + return true + }) + // Operation without holding the lock + for _, cb := range callbacks { + cb() + } + if n.setFlags { if setPriority { - n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) + n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) } + // Note: priority flag is automatically removed by the zero priority callback if necessary n.signalPriorityUpdate() - }) + } return nil } // RequestServed should be called after serving a request for the given peer -func (n *NodeBalance) RequestServed(cost uint64) uint64 { +func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) { n.lock.Lock() - var callbacks []func() - defer func() { - n.lock.Unlock() - if callbacks != nil { - n.bt.ns.Operation(func() { - for _, cb := range callbacks { - cb() - } - }) - } - }() - now := n.bt.clock.Now() + var ( + check bool + fcost = float64(cost) + now = n.bt.clock.Now() + ) n.updateBalance(now) - fcost := float64(cost) - - posExp := n.bt.posExp.LogOffset(now) - var check bool if !n.balance.pos.IsZero() { - if n.posFactor.RequestFactor != 0 { - c := -int64(fcost * n.posFactor.RequestFactor) - cc := n.balance.pos.Add(c, posExp) - if c == cc { + posCost := -int64(fcost * n.posFactor.RequestFactor) + if posCost == 0 { + fcost = 0 + newBalance = n.balance.posValue(now) + } else { + var net int64 + _, newBalance, net, _ = n.balance.addValue(now, posCost, true, false) + if posCost == net { fcost = 0 } else { - fcost *= 1 - float64(cc)/float64(c) + fcost *= 1 - float64(net)/float64(posCost) } check = true - } else { - fcost = 0 } } - if fcost > 0 { - if n.negFactor.RequestFactor != 0 { - n.balance.neg.Add(int64(fcost*n.negFactor.RequestFactor), n.bt.negExp.LogOffset(now)) - check = true - } + if fcost > 0 && n.negFactor.RequestFactor != 0 { + n.balance.addValue(now, int64(fcost*n.negFactor.RequestFactor), false, false) + check = true } + n.sumReqCost += cost + + var callbacks []func() if check { callbacks = n.checkCallbacks(now) } - n.sumReqCost += cost - return n.balance.pos.Value(posExp) + n.lock.Unlock() + + if callbacks != nil { + n.bt.ns.Operation(func() { + for _, cb := range callbacks { + cb() + } + }) + } + return } -// Priority returns the actual priority based on the current balance -func (n *NodeBalance) Priority(capacity uint64) int64 { +// priority returns the actual priority based on the current balance +func (n *nodeBalance) priority(capacity uint64) int64 { n.lock.Lock() defer n.lock.Unlock() - n.updateBalance(n.bt.clock.Now()) - return n.balanceToPriority(n.balance, capacity) + now := n.bt.clock.Now() + n.updateBalance(now) + return n.balanceToPriority(now, n.balance, capacity) } // EstMinPriority gives a lower estimate for the priority at a given time in the future. // An average request cost per time is assumed that is twice the average cost per time // in the current session. -// If update is true then a priority callback is added that turns UpdateFlag on and off +// If update is true then a priority callback is added that turns updateFlag on and off // in case the priority goes below the estimated minimum. -func (n *NodeBalance) EstimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 { +func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 { n.lock.Lock() defer n.lock.Unlock() now := n.bt.clock.Now() n.updateBalance(now) - b := n.balance + + b := n.balance // copy the balance if addBalance != 0 { - offset := n.bt.posExp.LogOffset(now) - old := n.balance.pos.Value(offset) - if addBalance > 0 && (addBalance > maxBalance || old > maxBalance-uint64(addBalance)) { - b.pos = utils.ExpiredValue{} - b.pos.Add(maxBalance, offset) - } else { - b.pos.Add(addBalance, offset) - } + b.addValue(now, addBalance, true, true) } if future > 0 { var avgReqCost float64 @@ -284,52 +358,20 @@ func (n *NodeBalance) EstimatePriority(capacity uint64, addBalance int64, future if bias > 0 { b = n.reducedBalance(b, now+mclock.AbsTime(future), bias, capacity, 0) } - pri := n.balanceToPriority(b, capacity) + // Note: we subtract one from the estimated priority in order to ensure that biased + // estimates are always lower than actual priorities, even if the bias is very small. + // This ensures that two nodes will not ping-pong update signals forever if both of + // them have zero estimated priority drop in the projected future. + pri := n.balanceToPriority(now, b, capacity) - 1 if update { n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) } return pri } -// PosBalanceMissing calculates the missing amount of positive balance in order to -// connect at targetCapacity, stay connected for the given amount of time and then -// still have a priority of targetPriority -func (n *NodeBalance) PosBalanceMissing(targetPriority int64, targetCapacity uint64, after time.Duration) uint64 { - n.lock.Lock() - defer n.lock.Unlock() - - now := n.bt.clock.Now() - if targetPriority < 0 { - timePrice := n.negFactor.timePrice(targetCapacity) - timeCost := uint64(float64(after) * timePrice) - negBalance := n.balance.neg.Value(n.bt.negExp.LogOffset(now)) - if timeCost+negBalance < uint64(-targetPriority) { - return 0 - } - if uint64(-targetPriority) > negBalance && timePrice > 1e-100 { - if negTime := time.Duration(float64(uint64(-targetPriority)-negBalance) / timePrice); negTime < after { - after -= negTime - } else { - after = 0 - } - } - targetPriority = 0 - } - timePrice := n.posFactor.timePrice(targetCapacity) - posRequired := uint64(float64(targetPriority)*float64(targetCapacity)+float64(after)*timePrice) + 1 - if posRequired >= maxBalance { - return math.MaxUint64 // target not reachable - } - posBalance := n.balance.pos.Value(n.bt.posExp.LogOffset(now)) - if posRequired > posBalance { - return posRequired - posBalance - } - return 0 -} - // SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of // connection while RequestFactor is the price of a request cost unit. -func (n *NodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { +func (n *nodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { n.lock.Lock() now := n.bt.clock.Now() n.updateBalance(now) @@ -346,7 +388,7 @@ func (n *NodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { } // GetPriceFactors returns the price factors -func (n *NodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) { +func (n *nodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) { n.lock.Lock() defer n.lock.Unlock() @@ -354,7 +396,7 @@ func (n *NodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) { } // activate starts time/capacity cost deduction. -func (n *NodeBalance) activate() { +func (n *nodeBalance) activate() { n.bt.updateTotalBalance(n, func() bool { if n.active { return false @@ -366,7 +408,7 @@ func (n *NodeBalance) activate() { } // deactivate stops time/capacity cost deduction and saves the balances in the database -func (n *NodeBalance) deactivate() { +func (n *nodeBalance) deactivate() { n.bt.updateTotalBalance(n, func() bool { if !n.active { return false @@ -383,7 +425,7 @@ func (n *NodeBalance) deactivate() { } // updateBalance updates balance based on the time factor -func (n *NodeBalance) updateBalance(now mclock.AbsTime) { +func (n *nodeBalance) updateBalance(now mclock.AbsTime) { if n.active && now > n.lastUpdate { n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0) n.lastUpdate = now @@ -391,7 +433,7 @@ func (n *NodeBalance) updateBalance(now mclock.AbsTime) { } // storeBalance stores the positive and/or negative balance of the node in the database -func (n *NodeBalance) storeBalance(pos, neg bool) { +func (n *nodeBalance) storeBalance(pos, neg bool) { if pos { n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos) } @@ -405,7 +447,7 @@ func (n *NodeBalance) storeBalance(pos, neg bool) { // immediately. // Note: should be called while n.lock is held // Note 2: the callback function runs inside a NodeStateMachine operation -func (n *NodeBalance) addCallback(id int, threshold int64, callback func()) { +func (n *nodeBalance) addCallback(id int, threshold int64, callback func()) { n.removeCallback(id) idx := 0 for idx < n.callbackCount && threshold > n.callbacks[idx].threshold { @@ -425,7 +467,7 @@ func (n *NodeBalance) addCallback(id int, threshold int64, callback func()) { // removeCallback removes the given callback and returns true if it was active // Note: should be called while n.lock is held -func (n *NodeBalance) removeCallback(id int) bool { +func (n *nodeBalance) removeCallback(id int) bool { idx := n.callbackIndex[id] if idx == -1 { return false @@ -442,11 +484,11 @@ func (n *NodeBalance) removeCallback(id int) bool { // checkCallbacks checks whether the threshold of any of the active callbacks // have been reached and returns triggered callbacks. // Note: checkCallbacks assumes that the balance has been recently updated. -func (n *NodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { +func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { if n.callbackCount == 0 || n.capacity == 0 { return } - pri := n.balanceToPriority(n.balance, n.capacity) + pri := n.balanceToPriority(now, n.balance, n.capacity) for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri { n.callbackCount-- n.callbackIndex[n.callbacks[n.callbackCount].id] = -1 @@ -458,7 +500,7 @@ func (n *NodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { // scheduleCheck sets up or updates a scheduled event to ensure that it will be called // again just after the next threshold has been reached. -func (n *NodeBalance) scheduleCheck(now mclock.AbsTime) { +func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) { if n.callbackCount != 0 { d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold) if !ok { @@ -484,7 +526,7 @@ func (n *NodeBalance) scheduleCheck(now mclock.AbsTime) { } // updateAfter schedules a balance update and callback check in the future -func (n *NodeBalance) updateAfter(dt time.Duration) { +func (n *nodeBalance) updateAfter(dt time.Duration) { if n.updateEvent == nil || n.updateEvent.Stop() { if dt == 0 { n.updateEvent = nil @@ -512,20 +554,22 @@ func (n *NodeBalance) updateAfter(dt time.Duration) { // balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative) // Note: this function should run inside a NodeStateMachine operation -func (n *NodeBalance) balanceExhausted() { +func (n *nodeBalance) balanceExhausted() { n.lock.Lock() n.storeBalance(true, false) - n.priority = false + n.hasPriority = false n.lock.Unlock() - n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.PriorityFlag, 0) + if n.setFlags { + n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.priorityFlag, 0) + } } // checkPriorityStatus checks whether the node has gained priority status and sets the priority // callback and flag if necessary. It assumes that the balance has been recently updated. // Note that the priority flag has to be set by the caller after the mutex has been released. -func (n *NodeBalance) checkPriorityStatus() bool { - if !n.priority && !n.balance.pos.IsZero() { - n.priority = true +func (n *nodeBalance) checkPriorityStatus() bool { + if !n.hasPriority && !n.balance.pos.IsZero() { + n.hasPriority = true n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() }) return true } @@ -534,15 +578,15 @@ func (n *NodeBalance) checkPriorityStatus() bool { // signalPriorityUpdate signals that the priority fell below the previous minimum estimate // Note: this function should run inside a NodeStateMachine operation -func (n *NodeBalance) signalPriorityUpdate() { - n.bt.ns.SetStateSub(n.node, n.bt.UpdateFlag, nodestate.Flags{}, 0) - n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.UpdateFlag, 0) +func (n *nodeBalance) signalPriorityUpdate() { + n.bt.ns.SetStateSub(n.node, n.bt.setup.updateFlag, nodestate.Flags{}, 0) + n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.updateFlag, 0) } // setCapacity updates the capacity value used for priority calculation // Note: capacity should never be zero // Note 2: this function should run inside a NodeStateMachine operation -func (n *NodeBalance) setCapacity(capacity uint64) { +func (n *nodeBalance) setCapacity(capacity uint64) { n.lock.Lock() now := n.bt.clock.Now() n.updateBalance(now) @@ -557,74 +601,89 @@ func (n *NodeBalance) setCapacity(capacity uint64) { // balanceToPriority converts a balance to a priority value. Lower priority means // first to disconnect. Positive balance translates to positive priority. If positive // balance is zero then negative balance translates to a negative priority. -func (n *NodeBalance) balanceToPriority(b balance, capacity uint64) int64 { - if !b.pos.IsZero() { - return int64(b.pos.Value(n.bt.posExp.LogOffset(n.bt.clock.Now())) / capacity) +func (n *nodeBalance) balanceToPriority(now mclock.AbsTime, b balance, capacity uint64) int64 { + pos := b.posValue(now) + if pos > 0 { + return int64(pos / capacity) + } + return -int64(b.negValue(now)) +} + +// priorityToBalance converts a target priority to a requested balance value. +// If the priority is negative, then minimal negative balance is returned; +// otherwise the minimal positive balance is returned. +func (n *nodeBalance) priorityToBalance(priority int64, capacity uint64) (uint64, uint64) { + if priority > 0 { + return uint64(priority) * n.capacity, 0 } - return -int64(b.neg.Value(n.bt.negExp.LogOffset(n.bt.clock.Now()))) + return 0, uint64(-priority) } // reducedBalance estimates the reduced balance at a given time in the fututre based // on the given balance, the time factor and an estimated average request cost per time ratio -func (n *NodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance { +func (n *nodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance { // since the costs are applied continuously during the dt time period we calculate // the expiration offset at the middle of the period - at := start + mclock.AbsTime(dt/2) - dtf := float64(dt) + var ( + at = start + mclock.AbsTime(dt/2) + dtf = float64(dt) + ) if !b.pos.IsZero() { - factor := n.posFactor.timePrice(capacity) + n.posFactor.RequestFactor*avgReqCost + factor := n.posFactor.connectionPrice(capacity, avgReqCost) diff := -int64(dtf * factor) - dd := b.pos.Add(diff, n.bt.posExp.LogOffset(at)) - if dd == diff { + _, _, net, _ := b.addValue(at, diff, true, false) + if net == diff { dtf = 0 } else { - dtf += float64(dd) / factor + dtf += float64(net) / factor } } - if dt > 0 { - factor := n.negFactor.timePrice(capacity) + n.negFactor.RequestFactor*avgReqCost - b.neg.Add(int64(dtf*factor), n.bt.negExp.LogOffset(at)) + if dtf > 0 { + factor := n.negFactor.connectionPrice(capacity, avgReqCost) + b.addValue(at, int64(dtf*factor), false, false) } return b } // timeUntil calculates the remaining time needed to reach a given priority level // assuming that no requests are processed until then. If the given level is never -// reached then (0, false) is returned. +// reached then (0, false) is returned. If it has already been reached then (0, true) +// is returned. // Note: the function assumes that the balance has been recently updated and // calculates the time starting from the last update. -func (n *NodeBalance) timeUntil(priority int64) (time.Duration, bool) { - now := n.bt.clock.Now() - var dt float64 - if !n.balance.pos.IsZero() { - posBalance := n.balance.pos.Value(n.bt.posExp.LogOffset(now)) - timePrice := n.posFactor.timePrice(n.capacity) +func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) { + var ( + now = n.bt.clock.Now() + pos = n.balance.posValue(now) + targetPos, targetNeg = n.priorityToBalance(priority, n.capacity) + diffTime float64 + ) + if pos > 0 { + timePrice := n.posFactor.connectionPrice(n.capacity, 0) if timePrice < 1e-100 { return 0, false } - if priority > 0 { - newBalance := uint64(priority) * n.capacity - if newBalance > posBalance { - return 0, false + if targetPos > 0 { + if targetPos > pos { + return 0, true } - dt = float64(posBalance-newBalance) / timePrice - return time.Duration(dt), true + diffTime = float64(pos-targetPos) / timePrice + return time.Duration(diffTime), true } else { - dt = float64(posBalance) / timePrice + diffTime = float64(pos) / timePrice } } else { - if priority > 0 { - return 0, false + if targetPos > 0 { + return 0, true } } - // if we have a positive balance then dt equals the time needed to get it to zero - negBalance := n.balance.neg.Value(n.bt.negExp.LogOffset(now)) - timePrice := n.negFactor.timePrice(n.capacity) - if uint64(-priority) > negBalance { + neg := n.balance.negValue(now) + if targetNeg > neg { + timePrice := n.negFactor.connectionPrice(n.capacity, 0) if timePrice < 1e-100 { return 0, false } - dt += float64(uint64(-priority)-negBalance) / timePrice + diffTime += float64(targetNeg-neg) / timePrice } - return time.Duration(dt), true + return time.Duration(diffTime), true } diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index e22074db2d..66f0d1f301 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/p2p/enode" @@ -31,59 +32,82 @@ import ( "github.com/ethereum/go-ethereum/p2p/nodestate" ) -var ( - testFlag = testSetup.NewFlag("testFlag") - connAddrFlag = testSetup.NewField("connAddr", reflect.TypeOf("")) - btTestSetup = NewBalanceTrackerSetup(testSetup) -) - -func init() { - btTestSetup.Connect(connAddrFlag, ppTestSetup.CapacityField) -} - type zeroExpirer struct{} func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64) {} func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {} func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64 { return 0 } +type balanceTestClient struct{} + +func (client balanceTestClient) FreeClientId() string { return "" } + type balanceTestSetup struct { clock *mclock.Simulated + db ethdb.KeyValueStore ns *nodestate.NodeStateMachine - bt *BalanceTracker + setup *serverSetup + bt *balanceTracker } -func newBalanceTestSetup() *balanceTestSetup { +func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpirer) *balanceTestSetup { + // Initialize and customize the setup for the balance testing clock := &mclock.Simulated{} - ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) - db := memorydb.New() - bt := NewBalanceTracker(ns, btTestSetup, db, clock, zeroExpirer{}, zeroExpirer{}) + setup := newServerSetup() + setup.clientField = setup.setup.NewField("balancTestClient", reflect.TypeOf(balanceTestClient{})) + + ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) + if posExp == nil { + posExp = zeroExpirer{} + } + if negExp == nil { + negExp = zeroExpirer{} + } + if db == nil { + db = memorydb.New() + } + bt := newBalanceTracker(ns, setup, db, clock, posExp, negExp) ns.Start() return &balanceTestSetup{ clock: clock, + db: db, ns: ns, + setup: setup, bt: bt, } } -func (b *balanceTestSetup) newNode(capacity uint64) *NodeBalance { +func (b *balanceTestSetup) newNode(capacity uint64) *nodeBalance { node := enode.SignNull(&enr.Record{}, enode.ID{}) - b.ns.SetState(node, testFlag, nodestate.Flags{}, 0) - b.ns.SetField(node, btTestSetup.connAddressField, "") + b.ns.SetField(node, b.setup.clientField, balanceTestClient{}) if capacity != 0 { - b.ns.SetField(node, ppTestSetup.CapacityField, capacity) + b.ns.SetField(node, b.setup.capacityField, capacity) } - n, _ := b.ns.GetField(node, btTestSetup.BalanceField).(*NodeBalance) + n, _ := b.ns.GetField(node, b.setup.balanceField).(*nodeBalance) return n } +func (b *balanceTestSetup) setBalance(node *nodeBalance, pos, neg uint64) (err error) { + b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { + err = balance.SetBalance(pos, neg) + }) + return +} + +func (b *balanceTestSetup) addBalance(node *nodeBalance, add int64) (old, new uint64, err error) { + b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { + old, new, err = balance.AddBalance(add) + }) + return +} + func (b *balanceTestSetup) stop() { - b.bt.Stop() + b.bt.stop() b.ns.Stop() } func TestAddBalance(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000) @@ -100,7 +124,7 @@ func TestAddBalance(t *testing.T) { {maxBalance, [2]uint64{0, 0}, 0, true}, } for _, i := range inputs { - old, new, err := node.AddBalance(i.delta) + old, new, err := b.addBalance(node, i.delta) if i.expectErr { if err == nil { t.Fatalf("Expect get error but nil") @@ -119,7 +143,7 @@ func TestAddBalance(t *testing.T) { } func TestSetBalance(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000) @@ -130,9 +154,8 @@ func TestSetBalance(t *testing.T) { {0, 1000}, {1000, 1000}, } - for _, i := range inputs { - node.SetBalance(i.pos, i.neg) + b.setBalance(node, i.pos, i.neg) pos, neg := node.GetBalance() if pos != i.pos { t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) @@ -144,13 +167,12 @@ func TestSetBalance(t *testing.T) { } func TestBalanceTimeCost(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000) - b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - node.SetBalance(uint64(time.Minute), 0) // 1 minute time allowance + b.setBalance(node, uint64(time.Minute), 0) // 1 minute time allowance var inputs = []struct { runTime time.Duration @@ -172,7 +194,7 @@ func TestBalanceTimeCost(t *testing.T) { } } - node.SetBalance(uint64(time.Minute), 0) // Refill 1 minute time allowance + b.setBalance(node, uint64(time.Minute), 0) // Refill 1 minute time allowance for _, i := range inputs { b.clock.Run(i.runTime) if pos, _ := node.GetBalance(); pos != i.expPos { @@ -185,13 +207,12 @@ func TestBalanceTimeCost(t *testing.T) { } func TestBalanceReqCost(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) - node.SetBalance(uint64(time.Minute), 0) // 1 minute time serving time allowance + b.setBalance(node, uint64(time.Minute), 0) // 1 minute time serving time allowance var inputs = []struct { reqCost uint64 expPos uint64 @@ -214,7 +235,7 @@ func TestBalanceReqCost(t *testing.T) { } func TestBalanceToPriority(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) @@ -230,22 +251,20 @@ func TestBalanceToPriority(t *testing.T) { {0, 1000, -1000}, } for _, i := range inputs { - node.SetBalance(i.pos, i.neg) - priority := node.Priority(1000) + b.setBalance(node, i.pos, i.neg) + priority := node.priority(1000) if priority != i.priority { - t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority) + t.Fatalf("priority mismatch, want %v, got %v", i.priority, priority) } } } func TestEstimatedPriority(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000000000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - - b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) - node.SetBalance(uint64(time.Minute), 0) + b.setBalance(node, uint64(time.Minute), 0) var inputs = []struct { runTime time.Duration // time cost futureTime time.Duration // diff of future time @@ -272,47 +291,18 @@ func TestEstimatedPriority(t *testing.T) { for _, i := range inputs { b.clock.Run(i.runTime) node.RequestServed(i.reqCost) - priority := node.EstimatePriority(1000000000, 0, i.futureTime, 0, false) - if priority != i.priority { - t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) - } - } -} - -func TestPosBalanceMissing(t *testing.T) { - b := newBalanceTestSetup() - defer b.stop() - node := b.newNode(1000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - - b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) - var inputs = []struct { - pos, neg uint64 - priority int64 - cap uint64 - after time.Duration - expect uint64 - }{ - {uint64(time.Second * 2), 0, 0, 1, time.Second, 0}, - {uint64(time.Second * 2), 0, 0, 1, 2 * time.Second, 1}, - {uint64(time.Second * 2), 0, int64(time.Second), 1, 2 * time.Second, uint64(time.Second) + 1}, - {0, 0, int64(time.Second), 1, time.Second, uint64(2*time.Second) + 1}, - {0, 0, -int64(time.Second), 1, time.Second, 1}, - } - for _, i := range inputs { - node.SetBalance(i.pos, i.neg) - got := node.PosBalanceMissing(i.priority, i.cap, i.after) - if got != i.expect { - t.Fatalf("Missing budget mismatch, want %v, got %v", i.expect, got) + priority := node.estimatePriority(1000000000, 0, i.futureTime, 0, false) + if priority != i.priority-1 { + t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority-1, priority) } } } func TestPostiveBalanceCounting(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() - var nodes []*NodeBalance + var nodes []*nodeBalance for i := 0; i < 100; i += 1 { node := b.newNode(1000000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) @@ -323,7 +313,7 @@ func TestPostiveBalanceCounting(t *testing.T) { var sum uint64 for i := 0; i < 100; i += 1 { amount := int64(rand.Intn(100) + 100) - nodes[i].AddBalance(amount) + b.addBalance(nodes[i], amount) sum += uint64(amount) } if b.bt.TotalTokenAmount() != sum { @@ -333,7 +323,7 @@ func TestPostiveBalanceCounting(t *testing.T) { // Change client status for i := 0; i < 100; i += 1 { if rand.Intn(2) == 0 { - b.ns.SetField(nodes[i].node, ppTestSetup.CapacityField, uint64(1)) + b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) } } if b.bt.TotalTokenAmount() != sum { @@ -341,7 +331,7 @@ func TestPostiveBalanceCounting(t *testing.T) { } for i := 0; i < 100; i += 1 { if rand.Intn(2) == 0 { - b.ns.SetField(nodes[i].node, ppTestSetup.CapacityField, uint64(1)) + b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) } } if b.bt.TotalTokenAmount() != sum { @@ -350,7 +340,7 @@ func TestPostiveBalanceCounting(t *testing.T) { } func TestCallbackChecking(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) @@ -363,7 +353,7 @@ func TestCallbackChecking(t *testing.T) { {0, time.Second}, {-int64(time.Second), 2 * time.Second}, } - node.SetBalance(uint64(time.Second), 0) + b.setBalance(node, uint64(time.Second), 0) for _, i := range inputs { diff, _ := node.timeUntil(i.priority) if diff != i.expDiff { @@ -373,14 +363,13 @@ func TestCallbackChecking(t *testing.T) { } func TestCallback(t *testing.T) { - b := newBalanceTestSetup() + b := newBalanceTestSetup(nil, nil, nil) defer b.stop() node := b.newNode(1000) node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - b.ns.SetField(node.node, ppTestSetup.CapacityField, uint64(1)) callCh := make(chan struct{}, 1) - node.SetBalance(uint64(time.Minute), 0) + b.setBalance(node, uint64(time.Minute), 0) node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) b.clock.Run(time.Minute) @@ -390,7 +379,7 @@ func TestCallback(t *testing.T) { t.Fatalf("Callback hasn't been called yet") } - node.SetBalance(uint64(time.Minute), 0) + b.setBalance(node, uint64(time.Minute), 0) node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) node.removeCallback(balanceCallbackZero) @@ -403,23 +392,14 @@ func TestCallback(t *testing.T) { } func TestBalancePersistence(t *testing.T) { - clock := &mclock.Simulated{} - ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) - db := memorydb.New() posExp := &utils.Expirer{} negExp := &utils.Expirer{} - posExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour*2)) // halves every two hours - negExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour)) // halves every hour - bt := NewBalanceTracker(ns, btTestSetup, db, clock, posExp, negExp) - ns.Start() - bts := &balanceTestSetup{ - clock: clock, - ns: ns, - bt: bt, - } - var nb *NodeBalance - exp := func(expPos, expNeg uint64) { - pos, neg := nb.GetBalance() + posExp.SetRate(0, math.Log(2)/float64(time.Hour*2)) // halves every two hours + negExp.SetRate(0, math.Log(2)/float64(time.Hour)) // halves every hour + setup := newBalanceTestSetup(nil, posExp, negExp) + + exp := func(balance *nodeBalance, expPos, expNeg uint64) { + pos, neg := balance.GetBalance() if pos != expPos { t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) } @@ -428,44 +408,32 @@ func TestBalancePersistence(t *testing.T) { } } expTotal := func(expTotal uint64) { - total := bt.TotalTokenAmount() + total := setup.bt.TotalTokenAmount() if total != expTotal { t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total) } } expTotal(0) - nb = bts.newNode(0) + balance := setup.newNode(0) expTotal(0) - nb.SetBalance(16000000000, 16000000000) - exp(16000000000, 16000000000) + setup.setBalance(balance, 16000000000, 16000000000) + exp(balance, 16000000000, 16000000000) expTotal(16000000000) - clock.Run(time.Hour * 2) - exp(8000000000, 4000000000) + + setup.clock.Run(time.Hour * 2) + exp(balance, 8000000000, 4000000000) expTotal(8000000000) - bt.Stop() - ns.Stop() - - clock = &mclock.Simulated{} - ns = nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) - posExp = &utils.Expirer{} - negExp = &utils.Expirer{} - posExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour*2)) // halves every two hours - negExp.SetRate(clock.Now(), math.Log(2)/float64(time.Hour)) // halves every hour - bt = NewBalanceTracker(ns, btTestSetup, db, clock, posExp, negExp) - ns.Start() - bts = &balanceTestSetup{ - clock: clock, - ns: ns, - bt: bt, - } + setup.stop() + + // Test the functionalities after restart + setup = newBalanceTestSetup(setup.db, posExp, negExp) expTotal(8000000000) - nb = bts.newNode(0) - exp(8000000000, 4000000000) + balance = setup.newNode(0) + exp(balance, 8000000000, 4000000000) expTotal(8000000000) - clock.Run(time.Hour * 2) - exp(4000000000, 1000000000) + setup.clock.Run(time.Hour * 2) + exp(balance, 4000000000, 1000000000) expTotal(4000000000) - bt.Stop() - ns.Stop() + setup.stop() } diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go index 1708019de4..746697a8c7 100644 --- a/les/vflux/server/balance_tracker.go +++ b/les/vflux/server/balance_tracker.go @@ -17,7 +17,6 @@ package server import ( - "reflect" "sync" "time" @@ -25,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" ) @@ -34,82 +34,56 @@ const ( persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence ) -// BalanceTrackerSetup contains node state flags and fields used by BalanceTracker -type BalanceTrackerSetup struct { - // controlled by PriorityPool - PriorityFlag, UpdateFlag nodestate.Flags - BalanceField nodestate.Field - // external connections - connAddressField, capacityField nodestate.Field -} - -// NewBalanceTrackerSetup creates a new BalanceTrackerSetup and initializes the fields -// and flags controlled by BalanceTracker -func NewBalanceTrackerSetup(setup *nodestate.Setup) BalanceTrackerSetup { - return BalanceTrackerSetup{ - // PriorityFlag is set if the node has a positive balance - PriorityFlag: setup.NewFlag("priorityNode"), - // UpdateFlag set and then immediately reset if the balance has been updated and - // therefore priority is suddenly changed - UpdateFlag: setup.NewFlag("balanceUpdate"), - // BalanceField contains the NodeBalance struct which implements nodePriority, - // allowing on-demand priority calculation and future priority estimation - BalanceField: setup.NewField("balance", reflect.TypeOf(&NodeBalance{})), - } -} - -// Connect sets the fields used by BalanceTracker as an input -func (bts *BalanceTrackerSetup) Connect(connAddressField, capacityField nodestate.Field) { - bts.connAddressField = connAddressField - bts.capacityField = capacityField -} - -// BalanceTracker tracks positive and negative balances for connected nodes. -// After connAddressField is set externally, a NodeBalance is created and previous +// balanceTracker tracks positive and negative balances for connected nodes. +// After clientField is set externally, a nodeBalance is created and previous // balance values are loaded from the database. Both balances are exponentially expired // values. Costs are deducted from the positive balance if present, otherwise added to // the negative balance. If the capacity is non-zero then a time cost is applied // continuously while individual request costs are applied immediately. // The two balances are translated into a single priority value that also depends // on the actual capacity. -type BalanceTracker struct { - BalanceTrackerSetup - clock mclock.Clock - lock sync.Mutex - ns *nodestate.NodeStateMachine - ndb *nodeDB - posExp, negExp utils.ValueExpirer - posExpTC, negExpTC uint64 +type balanceTracker struct { + setup *serverSetup + clock mclock.Clock + lock sync.Mutex + ns *nodestate.NodeStateMachine + ndb *nodeDB + posExp, negExp utils.ValueExpirer + + posExpTC, negExpTC uint64 + defaultPosFactors, defaultNegFactors PriceFactors active, inactive utils.ExpiredValue balanceTimer *utils.UpdateTimer quit chan struct{} } -// NewBalanceTracker creates a new BalanceTracker -func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *BalanceTracker { +// newBalanceTracker creates a new balanceTracker +func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *balanceTracker { ndb := newNodeDB(db, clock) - bt := &BalanceTracker{ - ns: ns, - BalanceTrackerSetup: setup, - ndb: ndb, - clock: clock, - posExp: posExp, - negExp: negExp, - balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), - quit: make(chan struct{}), + bt := &balanceTracker{ + ns: ns, + setup: setup, + ndb: ndb, + clock: clock, + posExp: posExp, + negExp: negExp, + balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), + quit: make(chan struct{}), } posOffset, negOffset := bt.ndb.getExpiration() posExp.SetLogOffset(clock.Now(), posOffset) negExp.SetLogOffset(clock.Now(), negOffset) + // Load all persisted balance entries of priority nodes, + // calculate the total number of issued service tokens. bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool { bt.inactive.AddExp(balance) return true }) - ns.SubscribeField(bt.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - n, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance) + ns.SubscribeField(bt.setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + n, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance) if n == nil { return } @@ -126,15 +100,22 @@ func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup n.deactivate() } }) - ns.SubscribeField(bt.connAddressField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + ns.SubscribeField(bt.setup.clientField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + type peer interface { + FreeClientId() string + } if newValue != nil { - ns.SetFieldSub(node, bt.BalanceField, bt.newNodeBalance(node, newValue.(string))) + n := bt.newNodeBalance(node, newValue.(peer).FreeClientId(), true) + bt.lock.Lock() + n.SetPriceFactors(bt.defaultPosFactors, bt.defaultNegFactors) + bt.lock.Unlock() + ns.SetFieldSub(node, bt.setup.balanceField, n) } else { - ns.SetStateSub(node, nodestate.Flags{}, bt.PriorityFlag, 0) - if b, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance); b != nil { + ns.SetStateSub(node, nodestate.Flags{}, bt.setup.priorityFlag, 0) + if b, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance); b != nil { b.deactivate() } - ns.SetFieldSub(node, bt.BalanceField, nil) + ns.SetFieldSub(node, bt.setup.balanceField, nil) } }) @@ -157,31 +138,31 @@ func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup return bt } -// Stop saves expiration offset and unsaved node balances and shuts BalanceTracker down -func (bt *BalanceTracker) Stop() { +// Stop saves expiration offset and unsaved node balances and shuts balanceTracker down +func (bt *balanceTracker) stop() { now := bt.clock.Now() bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now)) close(bt.quit) bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok { + if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok { n.lock.Lock() n.storeBalance(true, true) n.lock.Unlock() - bt.ns.SetField(node, bt.BalanceField, nil) + bt.ns.SetField(node, bt.setup.balanceField, nil) } }) bt.ndb.close() } // TotalTokenAmount returns the current total amount of service tokens in existence -func (bt *BalanceTracker) TotalTokenAmount() uint64 { +func (bt *balanceTracker) TotalTokenAmount() uint64 { bt.lock.Lock() defer bt.lock.Unlock() bt.balanceTimer.Update(func(_ time.Duration) bool { bt.active = utils.ExpiredValue{} bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok && n.active { + if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok && n.active { pos, _ := n.GetRawBalance() bt.active.AddExp(pos) } @@ -194,13 +175,21 @@ func (bt *BalanceTracker) TotalTokenAmount() uint64 { } // GetPosBalanceIDs lists node IDs with an associated positive balance -func (bt *BalanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { +func (bt *balanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { return bt.ndb.getPosBalanceIDs(start, stop, maxCount) } +// SetDefaultFactors sets the default price factors applied to subsequently connected clients +func (bt *balanceTracker) SetDefaultFactors(posFactors, negFactors PriceFactors) { + bt.lock.Lock() + bt.defaultPosFactors = posFactors + bt.defaultNegFactors = negFactors + bt.lock.Unlock() +} + // SetExpirationTCs sets positive and negative token expiration time constants. // Specified in seconds, 0 means infinite (no expiration). -func (bt *BalanceTracker) SetExpirationTCs(pos, neg uint64) { +func (bt *balanceTracker) SetExpirationTCs(pos, neg uint64) { bt.lock.Lock() defer bt.lock.Unlock() @@ -220,39 +209,55 @@ func (bt *BalanceTracker) SetExpirationTCs(pos, neg uint64) { // GetExpirationTCs returns the current positive and negative token expiration // time constants -func (bt *BalanceTracker) GetExpirationTCs() (pos, neg uint64) { +func (bt *balanceTracker) GetExpirationTCs() (pos, neg uint64) { bt.lock.Lock() defer bt.lock.Unlock() return bt.posExpTC, bt.negExpTC } -// newNodeBalance loads balances from the database and creates a NodeBalance instance -// for the given node. It also sets the PriorityFlag and adds balanceCallbackZero if +// BalanceOperation allows atomic operations on the balance of a node regardless of whether +// it is currently connected or not +func (bt *balanceTracker) BalanceOperation(id enode.ID, connAddress string, cb func(AtomicBalanceOperator)) { + bt.ns.Operation(func() { + var nb *nodeBalance + if node := bt.ns.GetNode(id); node != nil { + nb, _ = bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance) + } else { + node = enode.SignNull(&enr.Record{}, id) + nb = bt.newNodeBalance(node, connAddress, false) + } + cb(nb) + }) +} + +// newNodeBalance loads balances from the database and creates a nodeBalance instance +// for the given node. It also sets the priorityFlag and adds balanceCallbackZero if // the node has a positive balance. // Note: this function should run inside a NodeStateMachine operation -func (bt *BalanceTracker) newNodeBalance(node *enode.Node, negBalanceKey string) *NodeBalance { +func (bt *balanceTracker) newNodeBalance(node *enode.Node, connAddress string, setFlags bool) *nodeBalance { pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false) - nb := bt.ndb.getOrNewBalance([]byte(negBalanceKey), true) - n := &NodeBalance{ + nb := bt.ndb.getOrNewBalance([]byte(connAddress), true) + n := &nodeBalance{ bt: bt, node: node, - connAddress: negBalanceKey, - balance: balance{pos: pb, neg: nb}, + setFlags: setFlags, + connAddress: connAddress, + balance: balance{pos: pb, neg: nb, posExp: bt.posExp, negExp: bt.negExp}, initTime: bt.clock.Now(), lastUpdate: bt.clock.Now(), } for i := range n.callbackIndex { n.callbackIndex[i] = -1 } - if n.checkPriorityStatus() { - n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0) + if setFlags && n.checkPriorityStatus() { + n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) } return n } // storeBalance stores either a positive or a negative balance in the database -func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) { +func (bt *balanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) { if bt.canDropBalance(bt.clock.Now(), neg, value) { bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly. } else { @@ -262,7 +267,7 @@ func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredV // canDropBalance tells whether a positive or negative balance is below the threshold // and therefore can be dropped from the database -func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { +func (bt *balanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { if neg { return b.Value(bt.negExp.LogOffset(now)) <= negThreshold } @@ -270,7 +275,7 @@ func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.E } // updateTotalBalance adjusts the total balance after executing given callback. -func (bt *BalanceTracker) updateTotalBalance(n *NodeBalance, callback func() bool) { +func (bt *balanceTracker) updateTotalBalance(n *nodeBalance, callback func() bool) { bt.lock.Lock() defer bt.lock.Unlock() diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go new file mode 100644 index 0000000000..2e5fdd0ee7 --- /dev/null +++ b/les/vflux/server/clientpool.go @@ -0,0 +1,335 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "errors" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/les/vflux" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nodestate" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + ErrNotConnected = errors.New("client not connected") + ErrNoPriority = errors.New("priority too low to raise capacity") + ErrCantFindMaximum = errors.New("Unable to find maximum allowed capacity") +) + +// ClientPool implements a client database that assigns a priority to each client +// based on a positive and negative balance. Positive balance is externally assigned +// to prioritized clients and is decreased with connection time and processed +// requests (unless the price factors are zero). If the positive balance is zero +// then negative balance is accumulated. +// +// Balance tracking and priority calculation for connected clients is done by +// balanceTracker. PriorityQueue ensures that clients with the lowest positive or +// highest negative balance get evicted when the total capacity allowance is full +// and new clients with a better balance want to connect. +// +// Already connected nodes receive a small bias in their favor in order to avoid +// accepting and instantly kicking out clients. In theory, we try to ensure that +// each client can have several minutes of connection time. +// +// Balances of disconnected clients are stored in nodeDB including positive balance +// and negative banalce. Boeth positive balance and negative balance will decrease +// exponentially. If the balance is low enough, then the record will be dropped. +type ClientPool struct { + *priorityPool + *balanceTracker + + setup *serverSetup + clock mclock.Clock + closed bool + ns *nodestate.NodeStateMachine + synced func() bool + + lock sync.RWMutex + connectedBias time.Duration + + minCap uint64 // the minimal capacity value allowed for any client + capReqNode *enode.Node // node that is requesting capacity change; only used inside NSM operation +} + +// clientPeer represents a peer in the client pool. None of the callbacks should block. +type clientPeer interface { + Node() *enode.Node + FreeClientId() string // unique id for non-priority clients (typically a prefix of the network address) + InactiveAllowance() time.Duration // disconnection timeout for inactive non-priority peers + UpdateCapacity(newCap uint64, requested bool) // signals a capacity update (requested is true if it is a result of a SetCapacity call on the given peer + Disconnect() // initiates disconnection (Unregister should always be called) +} + +// NewClientPool creates a new client pool +func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias time.Duration, clock mclock.Clock, synced func() bool) *ClientPool { + setup := newServerSetup() + ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) + cp := &ClientPool{ + priorityPool: newPriorityPool(ns, setup, clock, minCap, connectedBias, 4, 100), + balanceTracker: newBalanceTracker(ns, setup, balanceDb, clock, &utils.Expirer{}, &utils.Expirer{}), + setup: setup, + ns: ns, + clock: clock, + minCap: minCap, + connectedBias: connectedBias, + synced: synced, + } + + ns.SubscribeState(nodestate.MergeFlags(setup.activeFlag, setup.inactiveFlag, setup.priorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + if newState.Equals(setup.inactiveFlag) { + // set timeout for non-priority inactive client + var timeout time.Duration + if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { + timeout = c.InactiveAllowance() + } + if timeout > 0 { + ns.AddTimeout(node, setup.inactiveFlag, timeout) + } else { + // Note: if capacity is immediately available then priorityPool will set the active + // flag simultaneously with removing the inactive flag and therefore this will not + // initiate disconnection + ns.SetStateSub(node, nodestate.Flags{}, setup.inactiveFlag, 0) + } + } + if oldState.Equals(setup.inactiveFlag) && newState.Equals(setup.inactiveFlag.Or(setup.priorityFlag)) { + ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) // priority gained; remove timeout + } + if newState.Equals(setup.activeFlag) { + // active with no priority; limit capacity to minCap + cap, _ := ns.GetField(node, setup.capacityField).(uint64) + if cap > minCap { + cp.requestCapacity(node, minCap, minCap, 0) + } + } + if newState.Equals(nodestate.Flags{}) { + if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { + c.Disconnect() + } + } + }) + + ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { + newCap, _ := newValue.(uint64) + c.UpdateCapacity(newCap, node == cp.capReqNode) + } + }) + + // add metrics + cp.ns.SubscribeState(nodestate.MergeFlags(cp.setup.activeFlag, cp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + if oldState.IsEmpty() && !newState.IsEmpty() { + clientConnectedMeter.Mark(1) + } + if !oldState.IsEmpty() && newState.IsEmpty() { + clientDisconnectedMeter.Mark(1) + } + if oldState.HasNone(cp.setup.activeFlag) && oldState.HasAll(cp.setup.activeFlag) { + clientActivatedMeter.Mark(1) + } + if oldState.HasAll(cp.setup.activeFlag) && oldState.HasNone(cp.setup.activeFlag) { + clientDeactivatedMeter.Mark(1) + } + _, connected := cp.Active() + totalConnectedGauge.Update(int64(connected)) + }) + return cp +} + +// Start starts the client pool. Should be called before Register/Unregister. +func (cp *ClientPool) Start() { + cp.ns.Start() +} + +// Stop shuts the client pool down. The clientPeer interface callbacks will not be called +// after Stop. Register calls will return nil. +func (cp *ClientPool) Stop() { + cp.balanceTracker.stop() + cp.ns.Stop() +} + +// Register registers the peer into the client pool. If the peer has insufficient +// priority and remains inactive for longer than the allowed timeout then it will be +// disconnected by calling the Disconnect function of the clientPeer interface. +func (cp *ClientPool) Register(peer clientPeer) ConnectedBalance { + cp.ns.SetField(peer.Node(), cp.setup.clientField, peerWrapper{peer}) + balance, _ := cp.ns.GetField(peer.Node(), cp.setup.balanceField).(*nodeBalance) + return balance +} + +// Unregister removes the peer from the client pool +func (cp *ClientPool) Unregister(peer clientPeer) { + cp.ns.SetField(peer.Node(), cp.setup.clientField, nil) +} + +// setConnectedBias sets the connection bias, which is applied to already connected clients +// So that already connected client won't be kicked out very soon and we can ensure all +// connected clients can have enough time to request or sync some data. +func (cp *ClientPool) SetConnectedBias(bias time.Duration) { + cp.lock.Lock() + cp.connectedBias = bias + cp.setActiveBias(bias) + cp.lock.Unlock() +} + +// SetCapacity sets the assigned capacity of a connected client +func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Duration, requested bool) (capacity uint64, err error) { + cp.lock.RLock() + if cp.connectedBias > bias { + bias = cp.connectedBias + } + cp.lock.RUnlock() + + cp.ns.Operation(func() { + balance, _ := cp.ns.GetField(node, cp.setup.balanceField).(*nodeBalance) + if balance == nil { + err = ErrNotConnected + return + } + capacity, _ = cp.ns.GetField(node, cp.setup.capacityField).(uint64) + if capacity == 0 { + // if the client is inactive then it has insufficient priority for the minimal capacity + // (will be activated automatically with minCap when possible) + return + } + if reqCap < cp.minCap { + // can't request less than minCap; switching between 0 (inactive state) and minCap is + // performed by the server automatically as soon as necessary/possible + reqCap = cp.minCap + } + if reqCap > cp.minCap && cp.ns.GetState(node).HasNone(cp.setup.priorityFlag) { + err = ErrNoPriority + return + } + if reqCap == capacity { + return + } + if requested { + // mark the requested node so that the UpdateCapacity callback can signal + // whether the update is the direct result of a SetCapacity call on the given node + cp.capReqNode = node + defer func() { + cp.capReqNode = nil + }() + } + + var minTarget, maxTarget uint64 + if reqCap > capacity { + // Estimate maximum available capacity at the current priority level and request + // the estimated amount. + // Note: requestCapacity could find the highest available capacity between the + // current and the requested capacity but it could cost a lot of iterations with + // fine step adjustment if the requested capacity is very high. By doing a quick + // estimation of the maximum available capacity based on the capacity curve we + // can limit the number of required iterations. + curve := cp.getCapacityCurve().exclude(node.ID()) + maxTarget = curve.maxCapacity(func(capacity uint64) int64 { + return balance.estimatePriority(capacity, 0, 0, bias, false) + }) + if maxTarget <= capacity { + return + } + if maxTarget > reqCap { + maxTarget = reqCap + } + // Specify a narrow target range that allows a limited number of fine step + // iterations + minTarget = maxTarget - maxTarget/20 + if minTarget < capacity { + minTarget = capacity + } + } else { + minTarget, maxTarget = reqCap, reqCap + } + if newCap := cp.requestCapacity(node, minTarget, maxTarget, bias); newCap >= minTarget && newCap <= maxTarget { + capacity = newCap + return + } + // we should be able to find the maximum allowed capacity in a few iterations + log.Error("Unable to find maximum allowed capacity") + err = ErrCantFindMaximum + }) + return +} + +// serveCapQuery serves a vflux capacity query. It receives multiple token amount values +// and a bias time value. For each given token amount it calculates the maximum achievable +// capacity in case the amount is added to the balance. +func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte { + var req vflux.CapacityQueryReq + if rlp.DecodeBytes(data, &req) != nil { + return nil + } + if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { + return nil + } + result := make(vflux.CapacityQueryReply, len(req.AddTokens)) + if !cp.synced() { + capacityQueryZeroMeter.Mark(1) + reply, _ := rlp.EncodeToBytes(&result) + return reply + } + + bias := time.Second * time.Duration(req.Bias) + cp.lock.RLock() + if cp.connectedBias > bias { + bias = cp.connectedBias + } + cp.lock.RUnlock() + + // use capacityCurve to answer request for multiple newly bought token amounts + curve := cp.getCapacityCurve().exclude(id) + cp.BalanceOperation(id, freeID, func(balance AtomicBalanceOperator) { + pb, _ := balance.GetBalance() + for i, addTokens := range req.AddTokens { + add := addTokens.Int64() + result[i] = curve.maxCapacity(func(capacity uint64) int64 { + return balance.estimatePriority(capacity, add, 0, bias, false) / int64(capacity) + }) + if add <= 0 && uint64(-add) >= pb && result[i] > cp.minCap { + result[i] = cp.minCap + } + if result[i] < cp.minCap { + result[i] = 0 + } + } + }) + // add first result to metrics (don't care about priority client multi-queries yet) + if result[0] == 0 { + capacityQueryZeroMeter.Mark(1) + } else { + capacityQueryNonZeroMeter.Mark(1) + } + reply, _ := rlp.EncodeToBytes(&result) + return reply +} + +// Handle implements Service +func (cp *ClientPool) Handle(id enode.ID, address string, name string, data []byte) []byte { + switch name { + case vflux.CapacityQueryName: + return cp.serveCapQuery(id, address, data) + default: + return nil + } +} diff --git a/les/clientpool_test.go b/les/vflux/server/clientpool_test.go similarity index 61% rename from les/clientpool_test.go rename to les/vflux/server/clientpool_test.go index 2aee444545..9503121697 100644 --- a/les/clientpool_test.go +++ b/les/vflux/server/clientpool_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package server import ( "fmt" @@ -24,12 +24,13 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/rawdb" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nodestate" ) +const defaultConnectedBias = time.Minute * 3 + func TestClientPoolL10C100Free(t *testing.T) { testClientPool(t, 10, 100, 0, true) } @@ -64,11 +65,6 @@ type poolTestPeer struct { inactiveAllowed bool } -func testStateMachine() *nodestate.NodeStateMachine { - return nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) - -} - func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer { return &poolTestPeer{ index: i, @@ -81,36 +77,39 @@ func (i *poolTestPeer) Node() *enode.Node { return i.node } -func (i *poolTestPeer) freeClientId() string { +func (i *poolTestPeer) FreeClientId() string { return fmt.Sprintf("addr #%d", i.index) } -func (i *poolTestPeer) updateCapacity(cap uint64) { - i.cap = cap +func (i *poolTestPeer) InactiveAllowance() time.Duration { + if i.inactiveAllowed { + return time.Second * 10 + } + return 0 } -func (i *poolTestPeer) freeze() {} - -func (i *poolTestPeer) allowInactive() bool { - return i.inactiveAllowed +func (i *poolTestPeer) UpdateCapacity(capacity uint64, requested bool) { + i.cap = capacity } -func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) { - temp := pool.ns.GetField(p.node, clientInfoField) == nil - if temp { - pool.ns.SetField(p.node, connAddressField, p.freeClientId()) - } - n, _ := pool.ns.GetField(p.node, pool.BalanceField).(*vfs.NodeBalance) - pos, neg = n.GetBalance() - if temp { - pool.ns.SetField(p.node, connAddressField, nil) +func (i *poolTestPeer) Disconnect() { + if i.disconnCh == nil { + return } + id := i.node.ID() + i.disconnCh <- int(id[0]) + int(id[1])<<8 +} + +func getBalance(pool *ClientPool, p *poolTestPeer) (pos, neg uint64) { + pool.BalanceOperation(p.node.ID(), p.FreeClientId(), func(nb AtomicBalanceOperator) { + pos, neg = nb.GetBalance() + }) return } -func addBalance(pool *clientPool, id enode.ID, amount int64) { - pool.forClients([]enode.ID{id}, func(c *clientInfo) { - c.balance.AddBalance(amount) +func addBalance(pool *ClientPool, id enode.ID, amount int64) { + pool.BalanceOperation(id, "", func(nb AtomicBalanceOperator) { + nb.AddBalance(amount) }) } @@ -122,6 +121,19 @@ func checkDiff(a, b uint64) bool { return a > b+maxDiff || b > a+maxDiff } +func connect(pool *ClientPool, peer *poolTestPeer) uint64 { + pool.Register(peer) + return peer.cap +} + +func disconnect(pool *ClientPool, peer *poolTestPeer) { + pool.Unregister(peer) +} + +func alwaysTrueFn() bool { + return true +} + func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, randomDisconnect bool) { rand.Seed(time.Now().UnixNano()) var ( @@ -130,19 +142,17 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando connected = make([]bool, clientCount) connTicks = make([]int, clientCount) disconnCh = make(chan int, clientCount) - disconnFn = func(id enode.ID) { - disconnCh <- int(id[0]) + int(id[1])<<8 - } - pool = newClientPool(testStateMachine(), db, 1, 0, &clock, disconnFn, alwaysTrueFn) + pool = NewClientPool(db, 1, 0, &clock, alwaysTrueFn) ) - pool.ns.Start() + pool.Start() + pool.SetExpirationTCs(0, 1000) - pool.setLimits(activeLimit, uint64(activeLimit)) - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool.SetLimits(uint64(activeLimit), uint64(activeLimit)) + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // pool should accept new peers up to its connected limit for i := 0; i < activeLimit; i++ { - if cap, _ := pool.connect(newPoolTestPeer(i, disconnCh)); cap != 0 { + if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 { connected[i] = true } else { t.Fatalf("Test peer #%d rejected", i) @@ -163,23 +173,23 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando i := rand.Intn(clientCount) if connected[i] { if randomDisconnect { - pool.disconnect(newPoolTestPeer(i, disconnCh)) + disconnect(pool, newPoolTestPeer(i, disconnCh)) connected[i] = false connTicks[i] += tickCounter } } else { - if cap, _ := pool.connect(newPoolTestPeer(i, disconnCh)); cap != 0 { + if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 { connected[i] = true connTicks[i] -= tickCounter } else { - pool.disconnect(newPoolTestPeer(i, disconnCh)) + disconnect(pool, newPoolTestPeer(i, disconnCh)) } } pollDisconnects: for { select { case i := <-disconnCh: - pool.disconnect(newPoolTestPeer(i, disconnCh)) + disconnect(pool, newPoolTestPeer(i, disconnCh)) if connected[i] { connTicks[i] += tickCounter connected[i] = false @@ -211,18 +221,18 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max) } } - pool.stop() + pool.Stop() } -func testPriorityConnect(t *testing.T, pool *clientPool, p *poolTestPeer, cap uint64, expSuccess bool) { - if cap, _ := pool.connect(p); cap == 0 { +func testPriorityConnect(t *testing.T, pool *ClientPool, p *poolTestPeer, cap uint64, expSuccess bool) { + if cap := connect(pool, p); cap == 0 { if expSuccess { t.Fatalf("Failed to connect paid client") } else { return } } - if _, err := pool.setCapacity(p.node, "", cap, defaultConnectedBias, true); err != nil { + if newCap, _ := pool.SetCapacity(p.node, cap, defaultConnectedBias, true); newCap != cap { if expSuccess { t.Fatalf("Failed to raise capacity of paid client") } else { @@ -239,11 +249,11 @@ func TestConnectPaidClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) @@ -255,16 +265,16 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) // Add balance for an external client and mark it as paid client addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) - // Connect a fat paid client to pool, should reject it. + // connect a fat paid client to pool, should reject it. testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 100, false) } @@ -273,24 +283,23 @@ func TestConnectPaidClientToFullPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - removeFn := func(enode.ID) {} // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20)) - pool.connect(newPoolTestPeer(i, nil)) + connect(pool, newPoolTestPeer(i, nil)) } addBalance(pool, newPoolTestPeer(11, nil).node.ID(), int64(time.Second*2)) // Add low balance to new paid client - if cap, _ := pool.connect(newPoolTestPeer(11, nil)); cap != 0 { + if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 { t.Fatalf("Low balance paid client should be rejected") } clock.Run(time.Second) addBalance(pool, newPoolTestPeer(12, nil).node.ID(), int64(time.Minute*5)) // Add high balance to new paid client - if cap, _ := pool.connect(newPoolTestPeer(12, nil)); cap == 0 { + if cap := connect(pool, newPoolTestPeer(12, nil)); cap == 0 { t.Fatalf("High balance paid client should be accepted") } } @@ -301,23 +310,20 @@ func TestPaidClientKickedOut(t *testing.T) { db = rawdb.NewMemoryDatabase() kickedCh = make(chan int, 100) ) - removeFn := func(id enode.ID) { - kickedCh <- int(id[0]) - } - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) - pool.ns.Start() - pool.bt.SetExpirationTCs(0, 0) - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + pool.SetExpirationTCs(0, 0) + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance - pool.connect(newPoolTestPeer(i, kickedCh)) + connect(pool, newPoolTestPeer(i, kickedCh)) clock.Run(time.Millisecond) } clock.Run(defaultConnectedBias + time.Second*11) - if cap, _ := pool.connect(newPoolTestPeer(11, kickedCh)); cap == 0 { + if cap := connect(pool, newPoolTestPeer(11, kickedCh)); cap == 0 { t.Fatalf("Free client should be accepted") } select { @@ -335,12 +341,12 @@ func TestConnectFreeClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - if cap, _ := pool.connect(newPoolTestPeer(0, nil)); cap == 0 { + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + if cap := connect(pool, newPoolTestPeer(0, nil)); cap == 0 { t.Fatalf("Failed to connect free client") } testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 2, false) @@ -351,26 +357,25 @@ func TestConnectFreeClientToFullPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - removeFn := func(enode.ID) {} // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.connect(newPoolTestPeer(i, nil)) + connect(pool, newPoolTestPeer(i, nil)) } - if cap, _ := pool.connect(newPoolTestPeer(11, nil)); cap != 0 { + if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 { t.Fatalf("New free client should be rejected") } clock.Run(time.Minute) - if cap, _ := pool.connect(newPoolTestPeer(12, nil)); cap != 0 { + if cap := connect(pool, newPoolTestPeer(12, nil)); cap != 0 { t.Fatalf("New free client should be rejected") } clock.Run(time.Millisecond) clock.Run(4 * time.Minute) - if cap, _ := pool.connect(newPoolTestPeer(13, nil)); cap == 0 { + if cap := connect(pool, newPoolTestPeer(13, nil)); cap == 0 { t.Fatalf("Old client connects more than 5min should be kicked") } } @@ -381,18 +386,17 @@ func TestFreeClientKickedOut(t *testing.T) { db = rawdb.NewMemoryDatabase() kicked = make(chan int, 100) ) - removeFn := func(id enode.ID) { kicked <- int(id[0]) } - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.connect(newPoolTestPeer(i, kicked)) + connect(pool, newPoolTestPeer(i, kicked)) clock.Run(time.Millisecond) } - if cap, _ := pool.connect(newPoolTestPeer(10, kicked)); cap != 0 { + if cap := connect(pool, newPoolTestPeer(10, kicked)); cap != 0 { t.Fatalf("New free client should be rejected") } select { @@ -400,10 +404,10 @@ func TestFreeClientKickedOut(t *testing.T) { case <-time.NewTimer(time.Second).C: t.Fatalf("timeout") } - pool.disconnect(newPoolTestPeer(10, kicked)) + disconnect(pool, newPoolTestPeer(10, kicked)) clock.Run(5 * time.Minute) for i := 0; i < 10; i++ { - pool.connect(newPoolTestPeer(i+10, kicked)) + connect(pool, newPoolTestPeer(i+10, kicked)) } for i := 0; i < 10; i++ { select { @@ -423,18 +427,17 @@ func TestPositiveBalanceCalculation(t *testing.T) { db = rawdb.NewMemoryDatabase() kicked = make(chan int, 10) ) - removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3)) testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true) clock.Run(time.Minute) - pool.disconnect(newPoolTestPeer(0, kicked)) + disconnect(pool, newPoolTestPeer(0, kicked)) pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) if checkDiff(pb, uint64(time.Minute*2)) { t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb) @@ -447,12 +450,11 @@ func TestDowngradePriorityClient(t *testing.T) { db = rawdb.NewMemoryDatabase() kicked = make(chan int, 10) ) - removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) p := newPoolTestPeer(0, kicked) addBalance(pool, p.node.ID(), int64(time.Minute)) @@ -483,30 +485,31 @@ func TestNegativeBalanceCalculation(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(10, uint64(10)) // Total capacity limit is 10 - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, vfs.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetExpirationTCs(0, 3600) + pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) for i := 0; i < 10; i++ { - pool.connect(newPoolTestPeer(i, nil)) + connect(pool, newPoolTestPeer(i, nil)) } clock.Run(time.Second) for i := 0; i < 10; i++ { - pool.disconnect(newPoolTestPeer(i, nil)) + disconnect(pool, newPoolTestPeer(i, nil)) _, nb := getBalance(pool, newPoolTestPeer(i, nil)) if nb != 0 { t.Fatalf("Short connection shouldn't be recorded") } } for i := 0; i < 10; i++ { - pool.connect(newPoolTestPeer(i, nil)) + connect(pool, newPoolTestPeer(i, nil)) } clock.Run(time.Minute) for i := 0; i < 10; i++ { - pool.disconnect(newPoolTestPeer(i, nil)) + disconnect(pool, newPoolTestPeer(i, nil)) _, nb := getBalance(pool, newPoolTestPeer(i, nil)) exp := uint64(time.Minute) / 1000 exp -= exp / 120 // correct for negative balance expiration @@ -521,10 +524,10 @@ func TestInactiveClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}, alwaysTrueFn) - pool.ns.Start() - defer pool.stop() - pool.setLimits(2, uint64(2)) + pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) + pool.Start() + defer pool.Stop() + pool.SetLimits(2, uint64(2)) p1 := newPoolTestPeer(1, nil) p1.inactiveAllowed = true @@ -535,15 +538,15 @@ func TestInactiveClient(t *testing.T) { addBalance(pool, p1.node.ID(), 1000*int64(time.Second)) addBalance(pool, p3.node.ID(), 2000*int64(time.Second)) // p1: 1000 p2: 0 p3: 2000 - p1.cap, _ = pool.connect(p1) + p1.cap = connect(pool, p1) if p1.cap != 1 { t.Fatalf("Failed to connect peer #1") } - p2.cap, _ = pool.connect(p2) + p2.cap = connect(pool, p2) if p2.cap != 1 { t.Fatalf("Failed to connect peer #2") } - p3.cap, _ = pool.connect(p3) + p3.cap = connect(pool, p3) if p3.cap != 1 { t.Fatalf("Failed to connect peer #3") } @@ -566,11 +569,11 @@ func TestInactiveClient(t *testing.T) { if p2.cap != 0 { t.Fatalf("Failed to deactivate peer #2") } - pool.setDefaultFactors(vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, vfs.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) + pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) p4 := newPoolTestPeer(4, nil) addBalance(pool, p4.node.ID(), 1500*int64(time.Second)) // p1: 1000 p2: 500 p3: 2000 p4: 1500 - p4.cap, _ = pool.connect(p4) + p4.cap = connect(pool, p4) if p4.cap != 1 { t.Fatalf("Failed to activate peer #4") } @@ -579,8 +582,8 @@ func TestInactiveClient(t *testing.T) { } clock.Run(time.Second * 600) // manually trigger a check to avoid a long real-time wait - pool.ns.SetState(p1.node, pool.UpdateFlag, nodestate.Flags{}, 0) - pool.ns.SetState(p1.node, nodestate.Flags{}, pool.UpdateFlag, 0) + pool.ns.SetState(p1.node, pool.setup.updateFlag, nodestate.Flags{}, 0) + pool.ns.SetState(p1.node, nodestate.Flags{}, pool.setup.updateFlag, 0) // p1: 1000 p2: 500 p3: 2000 p4: 900 if p1.cap != 1 { t.Fatalf("Failed to activate peer #1") @@ -588,8 +591,8 @@ func TestInactiveClient(t *testing.T) { if p4.cap != 0 { t.Fatalf("Failed to deactivate peer #4") } - pool.disconnect(p2) - pool.disconnect(p4) + disconnect(pool, p2) + disconnect(pool, p4) addBalance(pool, p1.node.ID(), -1000*int64(time.Second)) if p1.cap != 1 { t.Fatalf("Should not deactivate peer #1") diff --git a/les/vflux/server/metrics.go b/les/vflux/server/metrics.go new file mode 100644 index 0000000000..307b8347af --- /dev/null +++ b/les/vflux/server/metrics.go @@ -0,0 +1,33 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + totalConnectedGauge = metrics.NewRegisteredGauge("vflux/server/totalConnected", nil) + + clientConnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/connected", nil) + clientActivatedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/activated", nil) + clientDeactivatedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/deactivated", nil) + clientDisconnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/disconnected", nil) + + capacityQueryZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryZero", nil) + capacityQueryNonZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryNonZero", nil) +) diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go index e940ac7c65..573a3570a4 100644 --- a/les/vflux/server/prioritypool.go +++ b/les/vflux/server/prioritypool.go @@ -18,7 +18,6 @@ package server import ( "math" - "reflect" "sync" "time" @@ -33,36 +32,7 @@ const ( lazyQueueRefresh = time.Second * 10 // refresh period of the active queue ) -// PriorityPoolSetup contains node state flags and fields used by PriorityPool -// Note: ActiveFlag and InactiveFlag can be controlled both externally and by the pool, -// see PriorityPool description for details. -type PriorityPoolSetup struct { - // controlled by PriorityPool - ActiveFlag, InactiveFlag nodestate.Flags - CapacityField, ppNodeInfoField nodestate.Field - // external connections - updateFlag nodestate.Flags - priorityField nodestate.Field -} - -// NewPriorityPoolSetup creates a new PriorityPoolSetup and initializes the fields -// and flags controlled by PriorityPool -func NewPriorityPoolSetup(setup *nodestate.Setup) PriorityPoolSetup { - return PriorityPoolSetup{ - ActiveFlag: setup.NewFlag("active"), - InactiveFlag: setup.NewFlag("inactive"), - CapacityField: setup.NewField("capacity", reflect.TypeOf(uint64(0))), - ppNodeInfoField: setup.NewField("ppNodeInfo", reflect.TypeOf(&ppNodeInfo{})), - } -} - -// Connect sets the fields and flags used by PriorityPool as an input -func (pps *PriorityPoolSetup) Connect(priorityField nodestate.Field, updateFlag nodestate.Flags) { - pps.priorityField = priorityField // should implement nodePriority - pps.updateFlag = updateFlag // triggers an immediate priority update -} - -// PriorityPool handles a set of nodes where each node has a capacity (a scalar value) +// priorityPool handles a set of nodes where each node has a capacity (a scalar value) // and a priority (which can change over time and can also depend on the capacity). // A node is active if it has at least the necessary minimal amount of capacity while // inactive nodes have 0 capacity (values between 0 and the minimum are not allowed). @@ -79,70 +49,70 @@ func (pps *PriorityPoolSetup) Connect(priorityField nodestate.Field, updateFlag // This time bias can be interpreted as minimum expected active time at the given // capacity (if the threshold priority stays the same). // -// Nodes in the pool always have either InactiveFlag or ActiveFlag set. A new node is -// added to the pool by externally setting InactiveFlag. PriorityPool can switch a node -// between InactiveFlag and ActiveFlag at any time. Nodes can be removed from the pool -// by externally resetting both flags. ActiveFlag should not be set externally. +// Nodes in the pool always have either inactiveFlag or activeFlag set. A new node is +// added to the pool by externally setting inactiveFlag. priorityPool can switch a node +// between inactiveFlag and activeFlag at any time. Nodes can be removed from the pool +// by externally resetting both flags. activeFlag should not be set externally. // // The highest priority nodes in "inactive" state are moved to "active" state as soon as // the minimum capacity can be granted for them. The capacity of lower priority active // nodes is reduced or they are demoted to "inactive" state if their priority is // insufficient even at minimal capacity. -type PriorityPool struct { - PriorityPoolSetup - ns *nodestate.NodeStateMachine - clock mclock.Clock - lock sync.Mutex - activeQueue *prque.LazyQueue - inactiveQueue *prque.Prque - changed []*ppNodeInfo - activeCount, activeCap uint64 - maxCount, maxCap uint64 - minCap uint64 - activeBias time.Duration - capacityStepDiv uint64 - - cachedCurve *CapacityCurve +type priorityPool struct { + setup *serverSetup + ns *nodestate.NodeStateMachine + clock mclock.Clock + lock sync.Mutex + inactiveQueue *prque.Prque + maxCount, maxCap uint64 + minCap uint64 + activeBias time.Duration + capacityStepDiv, fineStepDiv uint64 + + cachedCurve *capacityCurve ccUpdatedAt mclock.AbsTime ccUpdateForced bool -} -// nodePriority interface provides current and estimated future priorities on demand -type nodePriority interface { - // Priority should return the current priority of the node (higher is better) - Priority(cap uint64) int64 - // EstMinPriority should return a lower estimate for the minimum of the node priority - // value starting from the current moment until the given time. If the priority goes - // under the returned estimate before the specified moment then it is the caller's - // responsibility to signal with updateFlag. - EstimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 + tempState []*ppNodeInfo // nodes currently in temporary state + // the following fields represent the temporary state if tempState is not empty + activeCount, activeCap uint64 + activeQueue *prque.LazyQueue } -// ppNodeInfo is the internal node descriptor of PriorityPool +// ppNodeInfo is the internal node descriptor of priorityPool type ppNodeInfo struct { nodePriority nodePriority node *enode.Node connected bool - capacity, origCap uint64 - bias time.Duration - forced, changed bool + capacity uint64 // only changed when temporary state is committed activeIndex, inactiveIndex int -} -// NewPriorityPool creates a new PriorityPool -func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, clock mclock.Clock, minCap uint64, activeBias time.Duration, capacityStepDiv uint64) *PriorityPool { - pp := &PriorityPool{ - ns: ns, - PriorityPoolSetup: setup, - clock: clock, - inactiveQueue: prque.New(inactiveSetIndex), - minCap: minCap, - activeBias: activeBias, - capacityStepDiv: capacityStepDiv, + tempState bool // should only be true while the priorityPool lock is held + tempCapacity uint64 // equals capacity when tempState is false + // the following fields only affect the temporary state and they are set to their + // default value when entering the temp state + minTarget, stepDiv uint64 + bias time.Duration +} + +// newPriorityPool creates a new priorityPool +func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock mclock.Clock, minCap uint64, activeBias time.Duration, capacityStepDiv, fineStepDiv uint64) *priorityPool { + pp := &priorityPool{ + setup: setup, + ns: ns, + clock: clock, + inactiveQueue: prque.New(inactiveSetIndex), + minCap: minCap, + activeBias: activeBias, + capacityStepDiv: capacityStepDiv, + fineStepDiv: fineStepDiv, + } + if pp.activeBias < time.Duration(1) { + pp.activeBias = time.Duration(1) } pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh) - ns.SubscribeField(pp.priorityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + ns.SubscribeField(pp.setup.balanceField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { if newValue != nil { c := &ppNodeInfo{ node: node, @@ -150,18 +120,19 @@ func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, cl activeIndex: -1, inactiveIndex: -1, } - ns.SetFieldSub(node, pp.ppNodeInfoField, c) + ns.SetFieldSub(node, pp.setup.queueField, c) + ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) } else { - ns.SetStateSub(node, nodestate.Flags{}, pp.ActiveFlag.Or(pp.InactiveFlag), 0) - if n, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo); n != nil { + ns.SetStateSub(node, nodestate.Flags{}, pp.setup.activeFlag.Or(pp.setup.inactiveFlag), 0) + if n, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); n != nil { pp.disconnectedNode(n) } - ns.SetFieldSub(node, pp.CapacityField, nil) - ns.SetFieldSub(node, pp.ppNodeInfoField, nil) + ns.SetFieldSub(node, pp.setup.capacityField, nil) + ns.SetFieldSub(node, pp.setup.queueField, nil) } }) - ns.SubscribeState(pp.ActiveFlag.Or(pp.InactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - if c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo); c != nil { + ns.SubscribeState(pp.setup.activeFlag.Or(pp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { + if c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); c != nil { if oldState.IsEmpty() { pp.connectedNode(c) } @@ -170,7 +141,7 @@ func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, cl } } }) - ns.SubscribeState(pp.updateFlag, func(node *enode.Node, oldState, newState nodestate.Flags) { + ns.SubscribeState(pp.setup.updateFlag, func(node *enode.Node, oldState, newState nodestate.Flags) { if !newState.IsEmpty() { pp.updatePriority(node) } @@ -178,18 +149,12 @@ func NewPriorityPool(ns *nodestate.NodeStateMachine, setup PriorityPoolSetup, cl return pp } -// RequestCapacity checks whether changing the capacity of a node to the given target -// is possible (bias is applied in favor of other active nodes if the target is higher -// than the current capacity). -// If setCap is true then it also performs the change if possible. The function returns -// the minimum priority needed to do the change and whether it is currently allowed. -// If setCap and allowed are both true then the caller can assume that the change was -// successful. -// Note: priorityField should always be set before calling RequestCapacity. If setCap -// is false then both InactiveFlag and ActiveFlag can be unset and they are not changed -// by this function call either. -// Note 2: this function should run inside a NodeStateMachine operation -func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias time.Duration, setCap bool) (minPriority int64, allowed bool) { +// requestCapacity tries to set the capacity of a connected node to the highest possible +// value inside the given target range. If maxTarget is not reachable then the capacity is +// iteratively reduced in fine steps based on the fineStepDiv parameter until minTarget is reached. +// The function returns the new capacity if successful and the original capacity otherwise. +// Note: this function should run inside a NodeStateMachine operation +func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget uint64, bias time.Duration) uint64 { pp.lock.Lock() pp.activeQueue.Refresh() var updates []capUpdate @@ -198,39 +163,37 @@ func (pp *PriorityPool) RequestCapacity(node *enode.Node, targetCap uint64, bias pp.updateFlags(updates) }() - if targetCap < pp.minCap { - targetCap = pp.minCap + if minTarget < pp.minCap { + minTarget = pp.minCap + } + if maxTarget < minTarget { + maxTarget = minTarget } if bias < pp.activeBias { bias = pp.activeBias } - c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo) + c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) if c == nil { - log.Error("RequestCapacity called for unknown node", "id", node.ID()) - return math.MaxInt64, false + log.Error("requestCapacity called for unknown node", "id", node.ID()) + return 0 } - var priority int64 - if targetCap > c.capacity { - priority = c.nodePriority.EstimatePriority(targetCap, 0, 0, bias, false) - } else { - priority = c.nodePriority.Priority(targetCap) + pp.setTempState(c) + if maxTarget > c.capacity { + c.bias = bias + c.stepDiv = pp.fineStepDiv } - pp.markForChange(c) - pp.setCapacity(c, targetCap) - c.forced = true + pp.setTempCapacity(c, maxTarget) + c.minTarget = minTarget pp.activeQueue.Remove(c.activeIndex) pp.inactiveQueue.Remove(c.inactiveIndex) pp.activeQueue.Push(c) - _, minPriority = pp.enforceLimits() - // if capacity update is possible now then minPriority == math.MinInt64 - // if it is not possible at all then minPriority == math.MaxInt64 - allowed = priority > minPriority - updates = pp.finalizeChanges(setCap && allowed) - return + pp.enforceLimits() + updates = pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity) + return c.capacity } // SetLimits sets the maximum number and total capacity of simultaneously active nodes -func (pp *PriorityPool) SetLimits(maxCount, maxCap uint64) { +func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) { pp.lock.Lock() pp.activeQueue.Refresh() var updates []capUpdate @@ -247,27 +210,38 @@ func (pp *PriorityPool) SetLimits(maxCount, maxCap uint64) { updates = pp.finalizeChanges(true) } if inc { - updates = pp.tryActivate() + updates = append(updates, pp.tryActivate(false)...) } } -// SetActiveBias sets the bias applied when trying to activate inactive nodes -func (pp *PriorityPool) SetActiveBias(bias time.Duration) { +// setActiveBias sets the bias applied when trying to activate inactive nodes +func (pp *priorityPool) setActiveBias(bias time.Duration) { pp.lock.Lock() - defer pp.lock.Unlock() - pp.activeBias = bias - pp.tryActivate() + if pp.activeBias < time.Duration(1) { + pp.activeBias = time.Duration(1) + } + updates := pp.tryActivate(false) + pp.lock.Unlock() + pp.ns.Operation(func() { pp.updateFlags(updates) }) } // Active returns the number and total capacity of currently active nodes -func (pp *PriorityPool) Active() (uint64, uint64) { +func (pp *priorityPool) Active() (uint64, uint64) { pp.lock.Lock() defer pp.lock.Unlock() return pp.activeCount, pp.activeCap } +// Limits returns the maximum allowed number and total capacity of active nodes +func (pp *priorityPool) Limits() (uint64, uint64) { + pp.lock.Lock() + defer pp.lock.Unlock() + + return pp.maxCount, pp.maxCap +} + // inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue func inactiveSetIndex(a interface{}, index int) { a.(*ppNodeInfo).inactiveIndex = index @@ -290,37 +264,31 @@ func invertPriority(p int64) int64 { // activePriority callback returns actual priority of ppNodeInfo item in activeQueue func activePriority(a interface{}) int64 { c := a.(*ppNodeInfo) - if c.forced { - return math.MinInt64 - } if c.bias == 0 { - return invertPriority(c.nodePriority.Priority(c.capacity)) + return invertPriority(c.nodePriority.priority(c.tempCapacity)) } else { - return invertPriority(c.nodePriority.EstimatePriority(c.capacity, 0, 0, c.bias, true)) + return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, 0, c.bias, true)) } } // activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue -func (pp *PriorityPool) activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { +func (pp *priorityPool) activeMaxPriority(a interface{}, until mclock.AbsTime) int64 { c := a.(*ppNodeInfo) - if c.forced { - return math.MinInt64 - } future := time.Duration(until - pp.clock.Now()) if future < 0 { future = 0 } - return invertPriority(c.nodePriority.EstimatePriority(c.capacity, 0, future, c.bias, false)) + return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, future, c.bias, false)) } // inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue -func (pp *PriorityPool) inactivePriority(p *ppNodeInfo) int64 { - return p.nodePriority.Priority(pp.minCap) +func (pp *priorityPool) inactivePriority(p *ppNodeInfo) int64 { + return p.nodePriority.priority(pp.minCap) } -// connectedNode is called when a new node has been added to the pool (InactiveFlag set) +// connectedNode is called when a new node has been added to the pool (inactiveFlag set) // Note: this function should run inside a NodeStateMachine operation -func (pp *PriorityPool) connectedNode(c *ppNodeInfo) { +func (pp *priorityPool) connectedNode(c *ppNodeInfo) { pp.lock.Lock() pp.activeQueue.Refresh() var updates []capUpdate @@ -334,13 +302,13 @@ func (pp *PriorityPool) connectedNode(c *ppNodeInfo) { } c.connected = true pp.inactiveQueue.Push(c, pp.inactivePriority(c)) - updates = pp.tryActivate() + updates = pp.tryActivate(false) } -// disconnectedNode is called when a node has been removed from the pool (both InactiveFlag -// and ActiveFlag reset) +// disconnectedNode is called when a node has been removed from the pool (both inactiveFlag +// and activeFlag reset) // Note: this function should run inside a NodeStateMachine operation -func (pp *PriorityPool) disconnectedNode(c *ppNodeInfo) { +func (pp *priorityPool) disconnectedNode(c *ppNodeInfo) { pp.lock.Lock() pp.activeQueue.Refresh() var updates []capUpdate @@ -356,42 +324,51 @@ func (pp *PriorityPool) disconnectedNode(c *ppNodeInfo) { pp.activeQueue.Remove(c.activeIndex) pp.inactiveQueue.Remove(c.inactiveIndex) if c.capacity != 0 { - pp.setCapacity(c, 0) - updates = pp.tryActivate() + pp.setTempState(c) + pp.setTempCapacity(c, 0) + updates = pp.tryActivate(true) } } -// markForChange internally puts a node in a temporary state that can either be reverted +// setTempState internally puts a node in a temporary state that can either be reverted // or confirmed later. This temporary state allows changing the capacity of a node and -// moving it between the active and inactive queue. ActiveFlag/InactiveFlag and -// CapacityField are not changed while the changes are still temporary. -func (pp *PriorityPool) markForChange(c *ppNodeInfo) { - if c.changed { +// moving it between the active and inactive queue. activeFlag/inactiveFlag and +// capacityField are not changed while the changes are still temporary. +func (pp *priorityPool) setTempState(c *ppNodeInfo) { + if c.tempState { return } - c.changed = true - c.origCap = c.capacity - pp.changed = append(pp.changed, c) + c.tempState = true + if c.tempCapacity != c.capacity { // should never happen + log.Error("tempCapacity != capacity when entering tempState") + } + c.minTarget = pp.minCap + c.stepDiv = pp.capacityStepDiv + pp.tempState = append(pp.tempState, c) } -// setCapacity changes the capacity of a node and adjusts activeCap and activeCount -// accordingly. Note that this change is performed in the temporary state so it should -// be called after markForChange and before finalizeChanges. -func (pp *PriorityPool) setCapacity(n *ppNodeInfo, cap uint64) { - pp.activeCap += cap - n.capacity - if n.capacity == 0 { +// setTempCapacity changes the capacity of a node in the temporary state and adjusts +// activeCap and activeCount accordingly. Since this change is performed in the temporary +// state it should be called after setTempState and before finalizeChanges. +func (pp *priorityPool) setTempCapacity(n *ppNodeInfo, cap uint64) { + if !n.tempState { // should never happen + log.Error("Node is not in temporary state") + return + } + pp.activeCap += cap - n.tempCapacity + if n.tempCapacity == 0 { pp.activeCount++ } if cap == 0 { pp.activeCount-- } - n.capacity = cap + n.tempCapacity = cap } // enforceLimits enforces active node count and total capacity limits. It returns the // lowest active node priority. Note that this function is performed on the temporary // internal state. -func (pp *PriorityPool) enforceLimits() (*ppNodeInfo, int64) { +func (pp *priorityPool) enforceLimits() (*ppNodeInfo, int64) { if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount { return nil, math.MinInt64 } @@ -401,16 +378,19 @@ func (pp *PriorityPool) enforceLimits() (*ppNodeInfo, int64) { ) pp.activeQueue.MultiPop(func(data interface{}, priority int64) bool { c = data.(*ppNodeInfo) - pp.markForChange(c) + pp.setTempState(c) maxActivePriority = priority - if c.capacity == pp.minCap || pp.activeCount > pp.maxCount { - pp.setCapacity(c, 0) + if c.tempCapacity == c.minTarget || pp.activeCount > pp.maxCount { + pp.setTempCapacity(c, 0) } else { - sub := c.capacity / pp.capacityStepDiv - if c.capacity-sub < pp.minCap { - sub = c.capacity - pp.minCap + sub := c.tempCapacity / c.stepDiv + if sub == 0 { + sub = 1 } - pp.setCapacity(c, c.capacity-sub) + if c.tempCapacity-sub < c.minTarget { + sub = c.tempCapacity - c.minTarget + } + pp.setTempCapacity(c, c.tempCapacity-sub) pp.activeQueue.Push(c) } return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount @@ -421,71 +401,74 @@ func (pp *PriorityPool) enforceLimits() (*ppNodeInfo, int64) { // finalizeChanges either commits or reverts temporary changes. The necessary capacity // field and according flag updates are not performed here but returned in a list because // they should be performed while the mutex is not held. -func (pp *PriorityPool) finalizeChanges(commit bool) (updates []capUpdate) { - for _, c := range pp.changed { - // always remove and push back in order to update biased/forced priority +func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) { + for _, c := range pp.tempState { + // always remove and push back in order to update biased priority pp.activeQueue.Remove(c.activeIndex) pp.inactiveQueue.Remove(c.inactiveIndex) - c.bias = 0 - c.forced = false - c.changed = false - if !commit { - pp.setCapacity(c, c.origCap) + oldCapacity := c.capacity + if commit { + c.capacity = c.tempCapacity + } else { + pp.setTempCapacity(c, c.capacity) // revert activeCount/activeCap } + c.tempState = false + c.bias = 0 + c.stepDiv = pp.capacityStepDiv + c.minTarget = pp.minCap if c.connected { if c.capacity != 0 { pp.activeQueue.Push(c) } else { pp.inactiveQueue.Push(c, pp.inactivePriority(c)) } - if c.capacity != c.origCap && commit { - updates = append(updates, capUpdate{c.node, c.origCap, c.capacity}) + if c.capacity != oldCapacity { + updates = append(updates, capUpdate{c.node, oldCapacity, c.capacity}) } } - c.origCap = 0 } - pp.changed = nil + pp.tempState = nil if commit { pp.ccUpdateForced = true } return } -// capUpdate describes a CapacityField and ActiveFlag/InactiveFlag update +// capUpdate describes a capacityField and activeFlag/inactiveFlag update type capUpdate struct { node *enode.Node oldCap, newCap uint64 } -// updateFlags performs CapacityField and ActiveFlag/InactiveFlag updates while the +// updateFlags performs capacityField and activeFlag/inactiveFlag updates while the // pool mutex is not held // Note: this function should run inside a NodeStateMachine operation -func (pp *PriorityPool) updateFlags(updates []capUpdate) { +func (pp *priorityPool) updateFlags(updates []capUpdate) { for _, f := range updates { if f.oldCap == 0 { - pp.ns.SetStateSub(f.node, pp.ActiveFlag, pp.InactiveFlag, 0) + pp.ns.SetStateSub(f.node, pp.setup.activeFlag, pp.setup.inactiveFlag, 0) } if f.newCap == 0 { - pp.ns.SetStateSub(f.node, pp.InactiveFlag, pp.ActiveFlag, 0) - pp.ns.SetFieldSub(f.node, pp.CapacityField, nil) + pp.ns.SetStateSub(f.node, pp.setup.inactiveFlag, pp.setup.activeFlag, 0) + pp.ns.SetFieldSub(f.node, pp.setup.capacityField, nil) } else { - pp.ns.SetFieldSub(f.node, pp.CapacityField, f.newCap) + pp.ns.SetFieldSub(f.node, pp.setup.capacityField, f.newCap) } } } // tryActivate tries to activate inactive nodes if possible -func (pp *PriorityPool) tryActivate() []capUpdate { - var commit bool +func (pp *priorityPool) tryActivate(commit bool) []capUpdate { for pp.inactiveQueue.Size() > 0 { c := pp.inactiveQueue.PopItem().(*ppNodeInfo) - pp.markForChange(c) - pp.setCapacity(c, pp.minCap) + pp.setTempState(c) + pp.setTempCapacity(c, pp.minCap) c.bias = pp.activeBias pp.activeQueue.Push(c) pp.enforceLimits() - if c.capacity > 0 { + if c.tempCapacity > 0 { commit = true + c.bias = 0 } else { break } @@ -497,7 +480,7 @@ func (pp *PriorityPool) tryActivate() []capUpdate { // updatePriority gets the current priority value of the given node from the nodePriority // interface and performs the necessary changes. It is triggered by updateFlag. // Note: this function should run inside a NodeStateMachine operation -func (pp *PriorityPool) updatePriority(node *enode.Node) { +func (pp *priorityPool) updatePriority(node *enode.Node) { pp.lock.Lock() pp.activeQueue.Refresh() var updates []capUpdate @@ -506,7 +489,7 @@ func (pp *PriorityPool) updatePriority(node *enode.Node) { pp.updateFlags(updates) }() - c, _ := pp.ns.GetField(node, pp.ppNodeInfoField).(*ppNodeInfo) + c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) if c == nil || !c.connected { return } @@ -517,15 +500,15 @@ func (pp *PriorityPool) updatePriority(node *enode.Node) { } else { pp.inactiveQueue.Push(c, pp.inactivePriority(c)) } - updates = pp.tryActivate() + updates = pp.tryActivate(false) } -// CapacityCurve is a snapshot of the priority pool contents in a format that can efficiently +// capacityCurve is a snapshot of the priority pool contents in a format that can efficiently // estimate how much capacity could be granted to a given node at a given priority level. -type CapacityCurve struct { +type capacityCurve struct { points []curvePoint // curve points sorted in descending order of priority index map[enode.ID][]int // curve point indexes belonging to each node - exclude []int // curve point indexes of excluded node + excludeList []int // curve point indexes of excluded node excludeFirst bool // true if activeCount == maxCount } @@ -534,8 +517,8 @@ type curvePoint struct { nextPri int64 // next priority level where more capacity will be available } -// GetCapacityCurve returns a new or recently cached CapacityCurve based on the contents of the pool -func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { +// getCapacityCurve returns a new or recently cached capacityCurve based on the contents of the pool +func (pp *priorityPool) getCapacityCurve() *capacityCurve { pp.lock.Lock() defer pp.lock.Unlock() @@ -547,7 +530,7 @@ func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { pp.ccUpdateForced = false pp.ccUpdatedAt = now - curve := &CapacityCurve{ + curve := &capacityCurve{ index: make(map[enode.ID][]int), } pp.cachedCurve = curve @@ -556,6 +539,7 @@ func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { excludeFirst := pp.maxCount == pp.activeCount // reduce node capacities or remove nodes until nothing is left in the queue; // record the available capacity and the necessary priority after each step + lastPri := int64(math.MinInt64) for pp.activeCap > 0 { cp := curvePoint{} if pp.activeCap > pp.maxCap { @@ -570,9 +554,15 @@ func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { // enforceLimits removes the lowest priority node if it has minimal capacity, // otherwise reduces its capacity next, cp.nextPri = pp.enforceLimits() + if cp.nextPri < lastPri { + // enforce monotonicity which may be broken by continuously changing priorities + cp.nextPri = lastPri + } else { + lastPri = cp.nextPri + } pp.activeCap -= tempCap if next == nil { - log.Error("GetCapacityCurve: cannot remove next element from the priority queue") + log.Error("getCapacityCurve: cannot remove next element from the priority queue") break } id := next.node.ID() @@ -595,34 +585,34 @@ func (pp *PriorityPool) GetCapacityCurve() *CapacityCurve { nextPri: math.MaxInt64, }) if curve.excludeFirst { - curve.exclude = curve.index[excludeID] + curve.excludeList = curve.index[excludeID] } return curve } -// Exclude returns a CapacityCurve with the given node excluded from the original curve -func (cc *CapacityCurve) Exclude(id enode.ID) *CapacityCurve { - if exclude, ok := cc.index[id]; ok { +// exclude returns a capacityCurve with the given node excluded from the original curve +func (cc *capacityCurve) exclude(id enode.ID) *capacityCurve { + if excludeList, ok := cc.index[id]; ok { // return a new version of the curve (only one excluded node can be selected) // Note: if the first node was excluded by default (excludeFirst == true) then // we can forget about that and exclude the node with the given id instead. - return &CapacityCurve{ - points: cc.points, - index: cc.index, - exclude: exclude, + return &capacityCurve{ + points: cc.points, + index: cc.index, + excludeList: excludeList, } } return cc } -func (cc *CapacityCurve) getPoint(i int) curvePoint { +func (cc *capacityCurve) getPoint(i int) curvePoint { cp := cc.points[i] if i == 0 && cc.excludeFirst { cp.freeCap = 0 return cp } - for ii := len(cc.exclude) - 1; ii >= 0; ii-- { - ei := cc.exclude[ii] + for ii := len(cc.excludeList) - 1; ii >= 0; ii-- { + ei := cc.excludeList[ii] if ei < i { break } @@ -632,11 +622,11 @@ func (cc *CapacityCurve) getPoint(i int) curvePoint { return cp } -// MaxCapacity calculates the maximum capacity available for a node with a given +// maxCapacity calculates the maximum capacity available for a node with a given // (monotonically decreasing) priority vs. capacity function. Note that if the requesting // node is already in the pool then it should be excluded from the curve in order to get // the correct result. -func (cc *CapacityCurve) MaxCapacity(priority func(cap uint64) int64) uint64 { +func (cc *capacityCurve) maxCapacity(priority func(cap uint64) int64) uint64 { min, max := 0, len(cc.points)-1 // the curve always has at least one point for min < max { mid := (min + max) / 2 diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go index d83ddc1767..5152312116 100644 --- a/les/vflux/server/prioritypool_test.go +++ b/les/vflux/server/prioritypool_test.go @@ -28,18 +28,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/nodestate" ) -var ( - testSetup = &nodestate.Setup{} - ppTestClientFlag = testSetup.NewFlag("ppTestClientFlag") - ppTestClientField = testSetup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{})) - ppUpdateFlag = testSetup.NewFlag("ppUpdateFlag") - ppTestSetup = NewPriorityPoolSetup(testSetup) -) - -func init() { - ppTestSetup.Connect(ppTestClientField, ppUpdateFlag) -} - const ( testCapacityStepDiv = 100 testCapacityToleranceDiv = 10 @@ -51,25 +39,27 @@ type ppTestClient struct { balance, cap uint64 } -func (c *ppTestClient) Priority(cap uint64) int64 { +func (c *ppTestClient) priority(cap uint64) int64 { return int64(c.balance / cap) } -func (c *ppTestClient) EstimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 { +func (c *ppTestClient) estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 { return int64(c.balance / cap) } func TestPriorityPool(t *testing.T) { clock := &mclock.Simulated{} - ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) + setup := newServerSetup() + setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{})) + ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) - ns.SubscribeField(ppTestSetup.CapacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - if n := ns.GetField(node, ppTestSetup.priorityField); n != nil { + ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + if n := ns.GetField(node, setup.balanceField); n != nil { c := n.(*ppTestClient) c.cap = newValue.(uint64) } }) - pp := NewPriorityPool(ns, ppTestSetup, clock, testMinCap, 0, testCapacityStepDiv) + pp := newPriorityPool(ns, setup, clock, testMinCap, 0, testCapacityStepDiv, testCapacityStepDiv) ns.Start() pp.SetLimits(100, 1000000) clients := make([]*ppTestClient, 100) @@ -77,7 +67,8 @@ func TestPriorityPool(t *testing.T) { for { var ok bool ns.Operation(func() { - _, ok = pp.RequestCapacity(c.node, c.cap+c.cap/testCapacityStepDiv, 0, true) + newCap := c.cap + c.cap/testCapacityStepDiv + ok = pp.requestCapacity(c.node, newCap, newCap, 0) == newCap }) if !ok { return @@ -101,9 +92,8 @@ func TestPriorityPool(t *testing.T) { } sumBalance += c.balance clients[i] = c - ns.SetState(c.node, ppTestClientFlag, nodestate.Flags{}, 0) - ns.SetField(c.node, ppTestSetup.priorityField, c) - ns.SetState(c.node, ppTestSetup.InactiveFlag, nodestate.Flags{}, 0) + ns.SetField(c.node, setup.balanceField, c) + ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0) raise(c) check(c) } @@ -113,8 +103,8 @@ func TestPriorityPool(t *testing.T) { oldBalance := c.balance c.balance = uint64(rand.Int63n(100000000000) + 100000000000) sumBalance += c.balance - oldBalance - pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) - pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) + pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0) + pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0) if c.balance > oldBalance { raise(c) } else { @@ -129,32 +119,28 @@ func TestPriorityPool(t *testing.T) { if count%10 == 0 { // test available capacity calculation with capacity curve c = clients[rand.Intn(len(clients))] - curve := pp.GetCapacityCurve().Exclude(c.node.ID()) + curve := pp.getCapacityCurve().exclude(c.node.ID()) add := uint64(rand.Int63n(10000000000000)) c.balance += add sumBalance += add - expCap := curve.MaxCapacity(func(cap uint64) int64 { + expCap := curve.maxCapacity(func(cap uint64) int64 { return int64(c.balance / cap) }) - //fmt.Println(expCap, c.balance, sumBalance) - /*for i, cp := range curve.points { - fmt.Println("cp", i, cp, "ex", curve.getPoint(i)) - }*/ var ok bool - expFail := expCap + 1 + expFail := expCap + 10 if expFail < testMinCap { expFail = testMinCap } ns.Operation(func() { - _, ok = pp.RequestCapacity(c.node, expFail, 0, true) + ok = pp.requestCapacity(c.node, expFail, expFail, 0) == expFail }) if ok { t.Errorf("Request for more than expected available capacity succeeded") } if expCap >= testMinCap { ns.Operation(func() { - _, ok = pp.RequestCapacity(c.node, expCap, 0, true) + ok = pp.requestCapacity(c.node, expCap, expCap, 0) == expCap }) if !ok { t.Errorf("Request for expected available capacity failed") @@ -162,8 +148,8 @@ func TestPriorityPool(t *testing.T) { } c.balance -= add sumBalance -= add - pp.ns.SetState(c.node, ppUpdateFlag, nodestate.Flags{}, 0) - pp.ns.SetState(c.node, nodestate.Flags{}, ppUpdateFlag, 0) + pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0) + pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0) for _, c := range clients { raise(c) } @@ -175,8 +161,11 @@ func TestPriorityPool(t *testing.T) { func TestCapacityCurve(t *testing.T) { clock := &mclock.Simulated{} - ns := nodestate.NewNodeStateMachine(nil, nil, clock, testSetup) - pp := NewPriorityPool(ns, ppTestSetup, clock, 400000, 0, 2) + setup := newServerSetup() + setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{})) + ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) + + pp := newPriorityPool(ns, setup, clock, 400000, 0, 2, 2) ns.Start() pp.SetLimits(10, 10000000) clients := make([]*ppTestClient, 10) @@ -188,17 +177,16 @@ func TestCapacityCurve(t *testing.T) { cap: 1000000, } clients[i] = c - ns.SetState(c.node, ppTestClientFlag, nodestate.Flags{}, 0) - ns.SetField(c.node, ppTestSetup.priorityField, c) - ns.SetState(c.node, ppTestSetup.InactiveFlag, nodestate.Flags{}, 0) + ns.SetField(c.node, setup.balanceField, c) + ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0) ns.Operation(func() { - pp.RequestCapacity(c.node, c.cap, 0, true) + pp.requestCapacity(c.node, c.cap, c.cap, 0) }) } - curve := pp.GetCapacityCurve() + curve := pp.getCapacityCurve() check := func(balance, expCap uint64) { - cap := curve.MaxCapacity(func(cap uint64) int64 { + cap := curve.maxCapacity(func(cap uint64) int64 { return int64(balance / cap) }) var fail bool @@ -226,7 +214,7 @@ func TestCapacityCurve(t *testing.T) { check(1000000000000, 2500000) pp.SetLimits(11, 10000000) - curve = pp.GetCapacityCurve() + curve = pp.getCapacityCurve() check(0, 0) check(10000000000, 100000) diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go index ab759ae441..80a0f47543 100644 --- a/les/vflux/server/service.go +++ b/les/vflux/server/service.go @@ -40,7 +40,6 @@ type ( // Service is a service registered at the Server and identified by a string id Service interface { - ServiceInfo() (id, desc string) // only called during registration Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently } @@ -60,9 +59,8 @@ func NewServer(delayPerRequest time.Duration) *Server { } // Register registers a Service -func (s *Server) Register(b Service) { - srv := &serviceEntry{backend: b} - srv.id, srv.desc = b.ServiceInfo() +func (s *Server) Register(b Service, id, desc string) { + srv := &serviceEntry{backend: b, id: id, desc: desc} if strings.Contains(srv.id, ":") { // srv.id + ":" will be used as a service database prefix log.Error("Service ID contains ':'", "id", srv.id) diff --git a/les/vflux/server/status.go b/les/vflux/server/status.go new file mode 100644 index 0000000000..469190777b --- /dev/null +++ b/les/vflux/server/status.go @@ -0,0 +1,59 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package server + +import ( + "reflect" + + "github.com/ethereum/go-ethereum/p2p/nodestate" +) + +type peerWrapper struct{ clientPeer } // the NodeStateMachine type system needs this wrapper + +// serverSetup is a wrapper of the node state machine setup, which contains +// all the created flags and fields used in the vflux server side. +type serverSetup struct { + setup *nodestate.Setup + clientField nodestate.Field // Field contains the client peer handler + + // Flags and fields controlled by balance tracker. BalanceTracker + // is responsible for setting/deleting these flags or fields. + priorityFlag nodestate.Flags // Flag is set if the node has a positive balance + updateFlag nodestate.Flags // Flag is set whenever the node balance is changed(priority changed) + balanceField nodestate.Field // Field contains the client balance for priority calculation + + // Flags and fields controlled by priority queue. Priority queue + // is responsible for setting/deleting these flags or fields. + activeFlag nodestate.Flags // Flag is set if the node is active + inactiveFlag nodestate.Flags // Flag is set if the node is inactive + capacityField nodestate.Field // Field contains the capacity of the node + queueField nodestate.Field // Field contains the infomration in the priority queue +} + +// newServerSetup initializes the setup for state machine and returns the flags/fields group. +func newServerSetup() *serverSetup { + setup := &serverSetup{setup: &nodestate.Setup{}} + setup.clientField = setup.setup.NewField("client", reflect.TypeOf(peerWrapper{})) + setup.priorityFlag = setup.setup.NewFlag("priority") + setup.updateFlag = setup.setup.NewFlag("update") + setup.balanceField = setup.setup.NewField("balance", reflect.TypeOf(&nodeBalance{})) + setup.activeFlag = setup.setup.NewFlag("active") + setup.inactiveFlag = setup.setup.NewFlag("inactive") + setup.capacityField = setup.setup.NewField("capacity", reflect.TypeOf(uint64(0))) + setup.queueField = setup.setup.NewField("queue", reflect.TypeOf(&ppNodeInfo{})) + return setup +} diff --git a/oss-fuzz.sh b/oss-fuzz.sh index ac93a5a467..f8152f0fad 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -102,6 +102,7 @@ compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/les Fuzz fuzzLes +compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go index d3166f1d87..9323d53cbd 100644 --- a/p2p/nodestate/nodestate.go +++ b/p2p/nodestate/nodestate.go @@ -858,6 +858,23 @@ func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} { return nil } +// GetState retrieves the current state of the given node. Note that when used in a +// subscription callback the result can be out of sync with the state change represented +// by the callback parameters so extra safety checks might be necessary. +func (ns *NodeStateMachine) GetState(n *enode.Node) Flags { + ns.lock.Lock() + defer ns.lock.Unlock() + + ns.checkStarted() + if ns.closed { + return Flags{} + } + if _, node := ns.updateEnode(n); node != nil { + return Flags{mask: node.state, setup: ns.setup} + } + return Flags{} +} + // SetField sets the given field of the given node and blocks until the operation is finished func (ns *NodeStateMachine) SetField(n *enode.Node, field Field, value interface{}) error { ns.lock.Lock() diff --git a/tests/fuzzers/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go new file mode 100644 index 0000000000..41b8627348 --- /dev/null +++ b/tests/fuzzers/vflux/clientpool-fuzzer.go @@ -0,0 +1,289 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vflux + +import ( + "bytes" + "encoding/binary" + "io" + "math" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/les/vflux" + vfs "github.com/ethereum/go-ethereum/les/vflux/server" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +type fuzzer struct { + peers [256]*clientPeer + disconnectList []*clientPeer + input io.Reader + exhausted bool + activeCount, activeCap uint64 + maxCount, maxCap uint64 +} + +type clientPeer struct { + fuzzer *fuzzer + node *enode.Node + freeID string + timeout time.Duration + + balance vfs.ConnectedBalance + capacity uint64 +} + +func (p *clientPeer) Node() *enode.Node { + return p.node +} + +func (p *clientPeer) FreeClientId() string { + return p.freeID +} + +func (p *clientPeer) InactiveAllowance() time.Duration { + return p.timeout +} + +func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { + p.fuzzer.activeCap -= p.capacity + if p.capacity != 0 { + p.fuzzer.activeCount-- + } + p.capacity = newCap + p.fuzzer.activeCap += p.capacity + if p.capacity != 0 { + p.fuzzer.activeCount++ + } +} + +func (p *clientPeer) Disconnect() { + p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p) + p.fuzzer.activeCap -= p.capacity + if p.capacity != 0 { + p.fuzzer.activeCount-- + } + p.capacity = 0 + p.balance = nil +} + +func newFuzzer(input []byte) *fuzzer { + f := &fuzzer{ + input: bytes.NewReader(input), + } + for i := range f.peers { + f.peers[i] = &clientPeer{ + fuzzer: f, + node: enode.SignNull(new(enr.Record), enode.ID{byte(i)}), + freeID: string([]byte{byte(i)}), + timeout: f.randomDelay(), + } + } + return f +} + +func (f *fuzzer) read(size int) []byte { + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) randomByte() byte { + d := f.read(1) + return d[0] +} + +func (f *fuzzer) randomBool() bool { + d := f.read(1) + return d[0]&1 == 1 +} + +func (f *fuzzer) randomInt(max int) int { + if max == 0 { + return 0 + } + if max <= 256 { + return int(f.randomByte()) % max + } + var a uint16 + if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { + f.exhausted = true + } + return int(a % uint16(max)) +} + +func (f *fuzzer) randomTokenAmount(signed bool) int64 { + x := uint64(f.randomInt(65000)) + x = x * x * x * x + + if signed && (x&1) == 1 { + if x <= math.MaxInt64 { + return -int64(x) + } + return math.MinInt64 + } + if x <= math.MaxInt64 { + return int64(x) + } + return math.MaxInt64 +} + +func (f *fuzzer) randomDelay() time.Duration { + delay := f.randomByte() + if delay < 128 { + return time.Duration(delay) * time.Second + } + return 0 +} + +func (f *fuzzer) randomFactors() vfs.PriceFactors { + return vfs.PriceFactors{ + TimeFactor: float64(f.randomByte()) / 25500, + CapacityFactor: float64(f.randomByte()) / 255, + RequestFactor: float64(f.randomByte()) / 255, + } +} + +func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance) { + switch f.randomInt(3) { + case 0: + balance.RequestServed(uint64(f.randomTokenAmount(false))) + case 1: + balance.SetPriceFactors(f.randomFactors(), f.randomFactors()) + case 2: + balance.GetBalance() + balance.GetRawBalance() + balance.GetPriceFactors() + } +} + +func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator) { + switch f.randomInt(3) { + case 0: + balance.AddBalance(f.randomTokenAmount(true)) + case 1: + balance.SetBalance(uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false))) + case 2: + balance.GetBalance() + balance.GetRawBalance() + balance.GetPriceFactors() + } +} + +func FuzzClientPool(input []byte) int { + if len(input) > 10000 { + return -1 + } + f := newFuzzer(input) + if f.exhausted { + return 0 + } + clock := &mclock.Simulated{} + db := memorydb.New() + pool := vfs.NewClientPool(db, 10, f.randomDelay(), clock, func() bool { return true }) + pool.Start() + defer pool.Stop() + + count := 0 + for !f.exhausted && count < 1000 { + count++ + switch f.randomInt(11) { + case 0: + i := int(f.randomByte()) + f.peers[i].balance = pool.Register(f.peers[i]) + case 1: + i := int(f.randomByte()) + f.peers[i].Disconnect() + case 2: + f.maxCount = uint64(f.randomByte()) + f.maxCap = uint64(f.randomByte()) + f.maxCap *= f.maxCap + pool.SetLimits(f.maxCount, f.maxCap) + case 3: + pool.SetConnectedBias(f.randomDelay()) + case 4: + pool.SetDefaultFactors(f.randomFactors(), f.randomFactors()) + case 5: + pool.SetExpirationTCs(uint64(f.randomInt(50000)), uint64(f.randomInt(50000))) + case 6: + if _, err := pool.SetCapacity(f.peers[f.randomByte()].node, uint64(f.randomByte()), f.randomDelay(), f.randomBool()); err == vfs.ErrCantFindMaximum { + panic(nil) + } + case 7: + if balance := f.peers[f.randomByte()].balance; balance != nil { + f.connectedBalanceOp(balance) + } + case 8: + pool.BalanceOperation(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, func(balance vfs.AtomicBalanceOperator) { + count := f.randomInt(4) + for i := 0; i < count; i++ { + f.atomicBalanceOp(balance) + } + }) + case 9: + pool.TotalTokenAmount() + pool.GetExpirationTCs() + pool.Active() + pool.Limits() + pool.GetPosBalanceIDs(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].node.ID(), f.randomInt(100)) + case 10: + req := vflux.CapacityQueryReq{ + Bias: uint64(f.randomByte()), + AddTokens: make([]vflux.IntOrInf, f.randomInt(vflux.CapacityQueryMaxLen+1)), + } + for i := range req.AddTokens { + v := vflux.IntOrInf{Type: uint8(f.randomInt(4))} + if v.Type < 2 { + v.Value = *big.NewInt(f.randomTokenAmount(false)) + } + req.AddTokens[i] = v + } + reqEnc, err := rlp.EncodeToBytes(&req) + if err != nil { + panic(err) + } + p := int(f.randomByte()) + if p < len(reqEnc) { + reqEnc[p] = f.randomByte() + } + pool.Handle(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, vflux.CapacityQueryName, reqEnc) + } + + for _, peer := range f.disconnectList { + pool.Unregister(peer) + } + f.disconnectList = nil + if d := f.randomDelay(); d > 0 { + clock.Run(d) + } + //fmt.Println(f.activeCount, f.maxCount, f.activeCap, f.maxCap) + if activeCount, activeCap := pool.Active(); activeCount != f.activeCount || activeCap != f.activeCap { + panic(nil) + } + if f.activeCount > f.maxCount || f.activeCap > f.maxCap { + panic(nil) + } + } + return 0 +} diff --git a/tests/fuzzers/vflux/debug/main.go b/tests/fuzzers/vflux/debug/main.go new file mode 100644 index 0000000000..de0b5d4124 --- /dev/null +++ b/tests/fuzzers/vflux/debug/main.go @@ -0,0 +1,41 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/vflux" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug \n") + fmt.Fprintf(os.Stderr, "Example\n") + fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + vflux.FuzzClientPool(data) +} From a600dab7e58a72968f111f8430c0e92a0511bc9c Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 7 Apr 2021 15:30:26 +0800 Subject: [PATCH 445/948] eth, les: fix tracers (#22473) * eth, les: fix tracer * eth: isolate live trie database in tracer * eth: fix nil * eth: fix * eth, les: add checkLive param * eth/tracer: fix --- eth/api.go | 3 +- eth/api_backend.go | 10 +- eth/state_accessor.go | 207 ++++++++++++++-------------------------- eth/tracers/api.go | 96 +++++++++++-------- eth/tracers/api_test.go | 36 ++----- les/api_backend.go | 8 +- les/state_accessor.go | 36 +++---- 7 files changed, 155 insertions(+), 241 deletions(-) diff --git a/eth/api.go b/eth/api.go index fb51e78c1a..e02c0ca4d2 100644 --- a/eth/api.go +++ b/eth/api.go @@ -407,11 +407,10 @@ func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, c if block == nil { return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) } - _, _, statedb, release, err := api.eth.stateAtTransaction(block, txIndex, 0) + _, _, statedb, err := api.eth.stateAtTransaction(block, txIndex, 0) if err != nil { return StorageRangeResult{}, err } - defer release() st := statedb.StorageTrie(contractAddress) if st == nil { return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress) diff --git a/eth/api_backend.go b/eth/api_backend.go index 2569972e52..cc780775cc 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -332,14 +332,10 @@ func (b *EthAPIBackend) StartMining(threads int) error { return b.eth.StartMining(threads) } -func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { - return b.eth.stateAtBlock(block, reexec) +func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { + return b.eth.stateAtBlock(block, reexec, base, checkLive) } -func (b *EthAPIBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { - return b.eth.statesInRange(fromBlock, toBlock, reexec) -} - -func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { +func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { return b.eth.stateAtTransaction(block, txIndex, reexec) } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index cbbd9a8202..d7564a0844 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -31,39 +31,58 @@ import ( ) // stateAtBlock retrieves the state database associated with a certain block. -// If no state is locally available for the given block, a number of blocks are -// attempted to be reexecuted to generate the desired state. -func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64) (statedb *state.StateDB, release func(), err error) { - // If we have the state fully available, use that - statedb, err = eth.blockchain.StateAt(block.Root()) - if err == nil { - return statedb, func() {}, nil +// If no state is locally available for the given block, a number of blocks +// are attempted to be reexecuted to generate the desired state. The optional +// base layer statedb can be passed then it's regarded as the statedb of the +// parent block. +func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (statedb *state.StateDB, err error) { + var ( + current *types.Block + database state.Database + report = true + origin = block.NumberU64() + ) + // Check the live database first if we have the state fully available, use that. + if checkLive { + statedb, err = eth.blockchain.StateAt(block.Root()) + if err == nil { + return statedb, nil + } } - // Otherwise try to reexec blocks until we find a state or reach our limit - origin := block.NumberU64() - database := state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) + if base != nil { + // The optional base statedb is given, mark the start point as parent block + statedb, database, report = base, base.Database(), false + current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + } else { + // Otherwise try to reexec blocks until we find a state or reach our limit + current = block - for i := uint64(0); i < reexec; i++ { - if block.NumberU64() == 0 { - return nil, nil, errors.New("genesis state is missing") - } - parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, nil, fmt.Errorf("missing block %v %d", block.ParentHash(), block.NumberU64()-1) - } - block = parent + // Create an ephemeral trie.Database for isolating the live one. Otherwise + // the internal junks created by tracing will be persisted into the disk. + database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16}) - statedb, err = state.New(block.Root(), database, nil) - if err == nil { - break + for i := uint64(0); i < reexec; i++ { + if current.NumberU64() == 0 { + return nil, errors.New("genesis state is missing") + } + parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1) + if parent == nil { + return nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1) + } + current = parent + + statedb, err = state.New(current.Root(), database, nil) + if err == nil { + break + } } - } - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) - default: - return nil, nil, err + if err != nil { + switch err.(type) { + case *trie.MissingNodeError: + return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) + default: + return nil, err + } } } // State was available at historical point, regenerate @@ -72,33 +91,29 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64) (statedb *s logged time.Time parent common.Hash ) - defer func() { - if err != nil && parent != (common.Hash{}) { - database.TrieDB().Dereference(parent) - } - }() - for block.NumberU64() < origin { + for current.NumberU64() < origin { // Print progress logs if long enough time elapsed - if time.Since(logged) > 8*time.Second { - log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) + if time.Since(logged) > 8*time.Second && report { + log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start)) logged = time.Now() } // Retrieve the next block to regenerate and process it - if block = eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { - return nil, nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) + next := current.NumberU64() + 1 + if current = eth.blockchain.GetBlockByNumber(next); current == nil { + return nil, fmt.Errorf("block #%d not found", next) } - _, _, _, err := eth.blockchain.Processor().Process(block, statedb, vm.Config{}) + _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) if err != nil { - return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) + return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(block.Number())) + root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number())) if err != nil { - return nil, nil, err + return nil, err } statedb, err = state.New(root, database, nil) if err != nil { - return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) + return nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) } database.TrieDB().Reference(root, common.Hash{}) if parent != (common.Hash{}) { @@ -106,104 +121,32 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64) (statedb *s } parent = root } - nodes, imgs := database.TrieDB().Size() - log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) - return statedb, func() { database.TrieDB().Dereference(parent) }, nil -} - -// statesInRange retrieves a batch of state databases associated with the specific -// block ranges. If no state is locally available for the given range, a number of -// blocks are attempted to be reexecuted to generate the ancestor state. -func (eth *Ethereum) statesInRange(fromBlock, toBlock *types.Block, reexec uint64) (states []*state.StateDB, release func(), err error) { - statedb, err := eth.blockchain.StateAt(fromBlock.Root()) - if err != nil { - statedb, _, err = eth.stateAtBlock(fromBlock, reexec) - } - if err != nil { - return nil, nil, err - } - states = append(states, statedb.Copy()) - - var ( - logged time.Time - parent common.Hash - start = time.Now() - refs = []common.Hash{fromBlock.Root()} - database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) - ) - // Release all resources(including the states referenced by `stateAtBlock`) - // if error is returned. - defer func() { - if err != nil { - for _, ref := range refs { - database.TrieDB().Dereference(ref) - } - } - }() - for i := fromBlock.NumberU64() + 1; i <= toBlock.NumberU64(); i++ { - // Print progress logs if long enough time elapsed - if time.Since(logged) > 8*time.Second { - logged = time.Now() - log.Info("Regenerating historical state", "block", i, "target", fromBlock.NumberU64(), "remaining", toBlock.NumberU64()-i, "elapsed", time.Since(start)) - } - // Retrieve the next block to regenerate and process it - block := eth.blockchain.GetBlockByNumber(i) - if block == nil { - return nil, nil, fmt.Errorf("block #%d not found", i) - } - _, _, _, err := eth.blockchain.Processor().Process(block, statedb, vm.Config{}) - if err != nil { - return nil, nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) - } - // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(block.Number())) - if err != nil { - return nil, nil, err - } - statedb, err := eth.blockchain.StateAt(root) - if err != nil { - return nil, nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) - } - states = append(states, statedb.Copy()) - - // Reference the trie twice, once for us, once for the tracer - database.TrieDB().Reference(root, common.Hash{}) - database.TrieDB().Reference(root, common.Hash{}) - refs = append(refs, root) - - // Dereference all past tries we ourselves are done working with - if parent != (common.Hash{}) { - database.TrieDB().Dereference(parent) - } - parent = root - } - // release is handler to release all states referenced, including - // the one referenced in `stateAtBlock`. - release = func() { - for _, ref := range refs { - database.TrieDB().Dereference(ref) - } + if report { + nodes, imgs := database.TrieDB().Size() + log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) } - return states, release, nil + return statedb, nil } // stateAtTransaction returns the execution environment of a certain transaction. -func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { +func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { // Short circuit if it's genesis block. if block.NumberU64() == 0 { - return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") + return nil, vm.BlockContext{}, nil, errors.New("no transaction in genesis") } // Create the parent state database parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) } - statedb, release, err := eth.stateAtBlock(parent, reexec) + // Lookup the statedb of parent block from the live database, + // otherwise regenerate it on the flight. + statedb, err := eth.stateAtBlock(parent, reexec, nil, true) if err != nil { - return nil, vm.BlockContext{}, nil, nil, err + return nil, vm.BlockContext{}, nil, err } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.BlockContext{}, statedb, release, nil + return nil, vm.BlockContext{}, statedb, nil } // Recompute transactions up to the target index. signer := types.MakeSigner(eth.blockchain.Config(), block.Number()) @@ -213,19 +156,17 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) if idx == txIndex { - return msg, context, statedb, release, nil + return msg, context, statedb, nil } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) statedb.Prepare(tx.Hash(), block.Hash(), idx) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - release() - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - release() - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 75ab403471..5a28d6889e 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -67,9 +67,8 @@ type Backend interface { ChainConfig() *params.ChainConfig Engine() consensus.Engine ChainDb() ethdb.Database - StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) - StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) - StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) + StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) } // API is the collection of tracing APIs exposed over the private debugging endpoint. @@ -187,6 +186,7 @@ type txTraceResult struct { type blockTraceTask struct { statedb *state.StateDB // Intermediate state prepped for tracing block *types.Block // Block to trace the transactions from + rootref common.Hash // Trie root reference held for this task results []*txTraceResult // Trace results procudes by the task } @@ -233,33 +233,22 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config } sub := notifier.CreateSubscription() - // Shift the border to a block ahead in order to get the states - // before these blocks. - endBlock, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(end.NumberU64()-1), end.ParentHash()) - if err != nil { - return nil, err - } // Prepare all the states for tracing. Note this procedure can take very // long time. Timeout mechanism is necessary. reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - states, release, err := api.backend.StatesInRange(ctx, start, endBlock, reexec) - if err != nil { - return nil, err - } - defer release() // Release all the resources in the last step. - blocks := int(end.NumberU64() - start.NumberU64()) threads := runtime.NumCPU() if threads > blocks { threads = blocks } var ( - pend = new(sync.WaitGroup) - tasks = make(chan *blockTraceTask, threads) - results = make(chan *blockTraceTask, threads) + pend = new(sync.WaitGroup) + tasks = make(chan *blockTraceTask, threads) + results = make(chan *blockTraceTask, threads) + localctx = context.Background() ) for th := 0; th < threads; th++ { pend.Add(1) @@ -269,7 +258,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config // Fetch and execute the next block trace tasks for task := range tasks { signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) - blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) + blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) @@ -278,7 +267,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config hash: tx.Hash(), block: task.block.Hash(), } - res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) + res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -302,10 +291,12 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config go func() { var ( - logged time.Time - number uint64 - traced uint64 - failed error + logged time.Time + number uint64 + traced uint64 + failed error + parent common.Hash + statedb *state.StateDB ) // Ensure everything is properly cleaned up on any exit path defer func() { @@ -323,7 +314,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config close(results) }() // Feed all the blocks both into the tracer, as well as fast process concurrently - for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ { + for number = start.NumberU64(); number < end.NumberU64(); number++ { // Stop tracing if interruption was requested select { case <-notifier.Closed(): @@ -335,16 +326,39 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config logged = time.Now() log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) } - // Retrieve the next block to trace - block, err := api.blockByNumber(ctx, rpc.BlockNumber(number)) + // Retrieve the parent state to trace on top + block, err := api.blockByNumber(localctx, rpc.BlockNumber(number)) + if err != nil { + failed = err + break + } + // Prepare the statedb for tracing. Don't use the live database for + // tracing to avoid persisting state junks into the database. + statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false) + if err != nil { + failed = err + break + } + if statedb.Database().TrieDB() != nil { + // Hold the reference for tracer, will be released at the final stage + statedb.Database().TrieDB().Reference(block.Root(), common.Hash{}) + + // Release the parent state because it's already held by the tracer + if parent != (common.Hash{}) { + statedb.Database().TrieDB().Dereference(parent) + } + } + parent = block.Root() + + next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1)) if err != nil { failed = err break } // Send the block over to the concurrent tracers (if not in the fast-forward phase) - txs := block.Transactions() + txs := next.Transactions() select { - case tasks <- &blockTraceTask{statedb: states[int(number-start.NumberU64()-1)], block: block, results: make([]*txTraceResult, len(txs))}: + case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))}: case <-notifier.Closed(): return } @@ -367,6 +381,10 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config } done[uint64(result.Block)] = result + // Dereference any parent tries held in memory by this task + if res.statedb.Database().TrieDB() != nil { + res.statedb.Database().TrieDB().Dereference(res.rootref) + } // Stream completed traces to the user, aborting on the first error for result, ok := done[next]; ok; result, ok = done[next] { if len(result.Traces) > 0 || next == end.NumberU64() { @@ -470,12 +488,10 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec) + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true) if err != nil { return nil, err } - defer release() - // Execute all the transaction contained within the block concurrently var ( signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) @@ -561,12 +577,10 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, release, err := api.backend.StateAtBlock(ctx, parent, reexec) + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true) if err != nil { return nil, err } - defer release() - // Retrieve the tracing configurations, or use default values var ( logConfig vm.LogConfig @@ -690,12 +704,10 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * if err != nil { return nil, err } - msg, vmctx, statedb, release, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) + msg, vmctx, statedb, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) if err != nil { return nil, err } - defer release() - txctx := &txTraceContext{ index: int(index), hash: hash, @@ -727,12 +739,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec) + statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true) if err != nil { return nil, err } - defer release() - // Execute the trace msg := args.ToMessage(api.backend.RPCGasCap()) vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) @@ -767,7 +777,9 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac deadlineCtx, cancel := context.WithTimeout(ctx, timeout) go func() { <-deadlineCtx.Done() - tracer.(*Tracer).Stop(errors.New("execution timeout")) + if deadlineCtx.Err() == context.DeadlineExceeded { + tracer.(*Tracer).Stop(errors.New("execution timeout")) + } }() defer cancel() diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 688b983bab..7ca90a6608 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -137,25 +137,25 @@ func (b *testBackend) ChainDb() ethdb.Database { return b.chaindb } -func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { +func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { statedb, err := b.chain.StateAt(block.Root()) if err != nil { - return nil, nil, errStateNotFound + return nil, errStateNotFound } - return statedb, func() {}, nil + return statedb, nil } -func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { +func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, vm.BlockContext{}, nil, nil, errBlockNotFound + return nil, vm.BlockContext{}, nil, errBlockNotFound } statedb, err := b.chain.StateAt(parent.Root()) if err != nil { - return nil, vm.BlockContext{}, nil, nil, errStateNotFound + return nil, vm.BlockContext{}, nil, errStateNotFound } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.BlockContext{}, statedb, func() {}, nil + return nil, vm.BlockContext{}, statedb, nil } // Recompute transactions up to the target index. signer := types.MakeSigner(b.chainConfig, block.Number()) @@ -164,31 +164,15 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), b.chain, nil) if idx == txIndex { - return msg, context, statedb, func() {}, nil + return msg, context, statedb, nil } vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) -} - -func (b *testBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { - var result []*state.StateDB - for number := fromBlock.NumberU64(); number <= toBlock.NumberU64(); number += 1 { - block := b.chain.GetBlockByNumber(number) - if block == nil { - return nil, nil, errBlockNotFound - } - statedb, err := b.chain.StateAt(block.Root()) - if err != nil { - return nil, nil, errStateNotFound - } - result = append(result, statedb) - } - return result, func() {}, nil + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } func TestTraceCall(t *testing.T) { diff --git a/les/api_backend.go b/les/api_backend.go index f5d2354b60..fc7821fb06 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -298,14 +298,10 @@ func (b *LesApiBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } -func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { +func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { return b.eth.stateAtBlock(ctx, block, reexec) } -func (b *LesApiBackend) StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { - return b.eth.statesInRange(ctx, fromBlock, toBlock, reexec) -} - -func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { +func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) } diff --git a/les/state_accessor.go b/les/state_accessor.go index 2f49bb921e..af5df36508 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -29,41 +29,27 @@ import ( ) // stateAtBlock retrieves the state database associated with a certain block. -func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error) { - return light.NewState(ctx, block.Header(), leth.odr), func() {}, nil -} - -// statesInRange retrieves a batch of state databases associated with the specific -// block ranges. -func (leth *LightEthereum) statesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error) { - var states []*state.StateDB - for number := fromBlock.NumberU64(); number <= toBlock.NumberU64(); number++ { - header, err := leth.blockchain.GetHeaderByNumberOdr(ctx, number) - if err != nil { - return nil, nil, err - } - states = append(states, light.NewState(ctx, header, leth.odr)) - } - return states, nil, nil +func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, error) { + return light.NewState(ctx, block.Header(), leth.odr), nil } // stateAtTransaction returns the execution environment of a certain transaction. -func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) { +func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { // Short circuit if it's genesis block. if block.NumberU64() == 0 { - return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") + return nil, vm.BlockContext{}, nil, errors.New("no transaction in genesis") } // Create the parent state database parent, err := leth.blockchain.GetBlock(ctx, block.ParentHash(), block.NumberU64()-1) if err != nil { - return nil, vm.BlockContext{}, nil, nil, err + return nil, vm.BlockContext{}, nil, err } - statedb, _, err := leth.stateAtBlock(ctx, parent, reexec) + statedb, err := leth.stateAtBlock(ctx, parent, reexec) if err != nil { - return nil, vm.BlockContext{}, nil, nil, err + return nil, vm.BlockContext{}, nil, err } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.BlockContext{}, statedb, func() {}, nil + return nil, vm.BlockContext{}, statedb, nil } // Recompute transactions up to the target index. signer := types.MakeSigner(leth.blockchain.Config(), block.Number()) @@ -74,16 +60,16 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) statedb.Prepare(tx.Hash(), block.Hash(), idx) if idx == txIndex { - return msg, context, statedb, func() {}, nil + return msg, context, statedb, nil } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } From 9d10856e84e884936c2390360e60e9ebca6d7a34 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 7 Apr 2021 16:54:31 +0200 Subject: [PATCH 446/948] core, eth, internal/ethapi: create access list RPC API (#22550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * core/vm: implement AccessListTracer * eth: implement debug.createAccessList * core/vm: fixed nil panics in accessListTracer * eth: better error messages for createAccessList * eth: some fixes on CreateAccessList * eth: allow for provided accesslists * eth: pass accesslist by value * eth: remove created acocunt from accesslist * core/vm: simplify access list tracer * core/vm: unexport accessListTracer * eth: return best guess if al iteration times out * eth: return best guess if al iteration times out * core: docstring, unexport methods * eth: typo * internal/ethapi: move createAccessList to eth package * internal/ethapi: remove reexec from createAccessList * internal/ethapi: break if al is equal to last run, not if gas is equal * internal/web3ext: fixed arguments * core/types: fixed equality check for accesslist * core/types: no hardcoded vals * core, internal: simplify access list generation, make it precise * core/vm: fix typo Co-authored-by: Martin Holst Swende Co-authored-by: Péter Szilágyi --- core/state_transition.go | 5 +- core/vm/access_list_tracer.go | 177 ++++++++++++++++++++++++++++++++++ core/vm/contracts.go | 14 +++ core/vm/evm.go | 15 --- core/vm/runtime/runtime.go | 16 ++- eth/api_backend.go | 8 +- internal/ethapi/api.go | 104 +++++++++++++++++++- internal/ethapi/backend.go | 2 +- internal/web3ext/web3ext.go | 6 ++ les/api_backend.go | 7 +- 10 files changed, 318 insertions(+), 36 deletions(-) create mode 100644 core/vm/access_list_tracer.go diff --git a/core/state_transition.go b/core/state_transition.go index d511e40bd6..cdffc100a1 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -259,10 +259,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Set up the initial access list. - if st.evm.ChainConfig().IsBerlin(st.evm.Context.BlockNumber) { - st.state.PrepareAccessList(msg.From(), msg.To(), st.evm.ActivePrecompiles(), msg.AccessList()) + if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsBerlin { + st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) } - var ( ret []byte vmerr error // vm errors do not effect consensus and are therefore not assigned to err diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go new file mode 100644 index 0000000000..b5bc961c84 --- /dev/null +++ b/core/vm/access_list_tracer.go @@ -0,0 +1,177 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// accessList is an accumulator for the set of accounts and storage slots an EVM +// contract execution touches. +type accessList map[common.Address]accessListSlots + +// accessListSlots is an accumulator for the set of storage slots within a single +// contract that an EVM contract execution touches. +type accessListSlots map[common.Hash]struct{} + +// newAccessList creates a new accessList. +func newAccessList() accessList { + return make(map[common.Address]accessListSlots) +} + +// addAddress adds an address to the accesslist. +func (al accessList) addAddress(address common.Address) { + // Set address if not previously present + if _, present := al[address]; !present { + al[address] = make(map[common.Hash]struct{}) + } +} + +// addSlot adds a storage slot to the accesslist. +func (al accessList) addSlot(address common.Address, slot common.Hash) { + // Set address if not previously present + al.addAddress(address) + + // Set the slot on the surely existent storage set + al[address][slot] = struct{}{} +} + +// equal checks if the content of the current access list is the same as the +// content of the other one. +func (al accessList) equal(other accessList) bool { + // Cross reference the accounts first + if len(al) != len(other) { + return false + } + for addr := range al { + if _, ok := other[addr]; !ok { + return false + } + } + for addr := range other { + if _, ok := al[addr]; !ok { + return false + } + } + // Accounts match, cross reference the storage slots too + for addr, slots := range al { + otherslots := other[addr] + + if len(slots) != len(otherslots) { + return false + } + for hash := range slots { + if _, ok := otherslots[hash]; !ok { + return false + } + } + for hash := range otherslots { + if _, ok := slots[hash]; !ok { + return false + } + } + } + return true +} + +// accesslist converts the accesslist to a types.AccessList. +func (al accessList) accessList() types.AccessList { + acl := make(types.AccessList, 0, len(al)) + for addr, slots := range al { + tuple := types.AccessTuple{Address: addr} + for slot := range slots { + tuple.StorageKeys = append(tuple.StorageKeys, slot) + } + acl = append(acl, tuple) + } + return acl +} + +// AccessListTracer is a tracer that accumulates touched accounts and storage +// slots into an internal set. +type AccessListTracer struct { + excl map[common.Address]struct{} // Set of account to exclude from the list + list accessList // Set of accounts and storage slots touched +} + +// NewAccessListTracer creates a new tracer that can generate AccessLists. +// An optional AccessList can be specified to occupy slots and addresses in +// the resulting accesslist. +func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer { + excl := map[common.Address]struct{}{ + from: {}, to: {}, + } + for _, addr := range precompiles { + excl[addr] = struct{}{} + } + list := newAccessList() + for _, al := range acl { + if _, ok := excl[al.Address]; !ok { + list.addAddress(al.Address) + } + for _, slot := range al.StorageKeys { + list.addSlot(al.Address, slot) + } + } + return &AccessListTracer{ + excl: excl, + list: list, + } +} + +func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +} + +// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. +func (a *AccessListTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack + if (op == SLOAD || op == SSTORE) && stack.len() >= 1 { + slot := common.Hash(stack.data[stack.len()-1].Bytes32()) + a.list.addSlot(scope.Contract.Address(), slot) + } + if (op == EXTCODECOPY || op == EXTCODEHASH || op == EXTCODESIZE || op == BALANCE || op == SELFDESTRUCT) && stack.len() >= 1 { + addr := common.Address(stack.data[stack.len()-1].Bytes20()) + if _, ok := a.excl[addr]; !ok { + a.list.addAddress(addr) + } + } + if (op == DELEGATECALL || op == CALL || op == STATICCALL || op == CALLCODE) && stack.len() >= 5 { + addr := common.Address(stack.data[stack.len()-2].Bytes20()) + if _, ok := a.excl[addr]; !ok { + a.list.addAddress(addr) + } + } +} + +func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { +} + +func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {} + +// AccessList returns the current accesslist maintained by the tracer. +func (a *AccessListTracer) AccessList() types.AccessList { + return a.list.accessList() +} + +// Equal returns if the content of two access list traces are equal. +func (a *AccessListTracer) Equal(other *AccessListTracer) bool { + return a.list.equal(other.list) +} diff --git a/core/vm/contracts.go b/core/vm/contracts.go index a3ceece0e9..9210f5486c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -128,6 +128,20 @@ func init() { } } +// ActivePrecompiles returns the precompiles enabled with the current configuration. +func ActivePrecompiles(rules params.Rules) []common.Address { + switch { + case rules.IsBerlin: + return PrecompiledAddressesBerlin + case rules.IsIstanbul: + return PrecompiledAddressesIstanbul + case rules.IsByzantium: + return PrecompiledAddressesByzantium + default: + return PrecompiledAddressesHomestead + } +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. // It returns // - the returned bytes, diff --git a/core/vm/evm.go b/core/vm/evm.go index 6fac50f721..3f16f33b2d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -42,21 +42,6 @@ type ( GetHashFunc func(uint64) common.Hash ) -// ActivePrecompiles returns the addresses of the precompiles enabled with the current -// configuration -func (evm *EVM) ActivePrecompiles() []common.Address { - switch { - case evm.chainRules.IsBerlin: - return PrecompiledAddressesBerlin - case evm.chainRules.IsIstanbul: - return PrecompiledAddressesIstanbul - case evm.chainRules.IsByzantium: - return PrecompiledAddressesByzantium - default: - return PrecompiledAddressesHomestead - } -} - func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 9cb69e1c76..72601441d5 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -114,8 +114,8 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { - cfg.State.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) + if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { + cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) } cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. @@ -146,10 +146,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { - cfg.State.PrepareAccessList(cfg.Origin, nil, vmenv.ActivePrecompiles(), nil) + if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { + cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil) } - // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -172,10 +171,10 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er sender := cfg.State.GetOrNewStateObject(cfg.Origin) statedb := cfg.State - if cfg.ChainConfig.IsBerlin(vmenv.Context.BlockNumber) { - statedb.PrepareAccessList(cfg.Origin, &address, vmenv.ActivePrecompiles(), nil) - } + if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { + statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil) + } // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( sender, @@ -184,6 +183,5 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er cfg.GasLimit, cfg.Value, ) - return ret, leftOverGas, err } diff --git a/eth/api_backend.go b/eth/api_backend.go index cc780775cc..7ac1f82a86 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -192,12 +192,14 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(hash) } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) { vmError := func() error { return nil } - + if vmConfig == nil { + vmConfig = b.eth.blockchain.GetVMConfig() + } txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) - return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil + return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), vmError, nil } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 861d427851..fe5c3388b5 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -865,7 +865,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // Get a new instance of the EVM. msg := args.ToMessage(globalGasCap) - evm, vmError, err := b.GetEVM(ctx, msg, state, header) + evm, vmError, err := b.GetEVM(ctx, msg, state, header, nil) if err != nil { return nil, err } @@ -1303,6 +1303,106 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa return nil } +// accessListResult returns an optional accesslist +// Its the result of the `debug_createAccessList` RPC call. +// It contains an error if the transaction itself failed. +type accessListResult struct { + Accesslist *types.AccessList `json:"accessList"` + Error string `json:"error,omitempty"` + GasUsed hexutil.Uint64 `json:"gasUsed"` +} + +// CreateAccessList creates a EIP-2930 type AccessList for the given transaction. +// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state. +func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { + bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + if blockNrOrHash != nil { + bNrOrHash = *blockNrOrHash + } + acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args) + if err != nil { + return nil, err + } + result := &accessListResult{Accesslist: &acl, GasUsed: hexutil.Uint64(gasUsed)} + if vmerr != nil { + result.Error = vmerr.Error() + } + return result, nil +} + +// AccessList creates an access list for the given transaction. +// If the accesslist creation fails an error is returned. +// If the transaction itself fails, an vmErr is returned. +func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args SendTxArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) { + // Retrieve the execution context + db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if db == nil || err != nil { + return nil, 0, nil, err + } + // If the gas amount is not set, extract this as it will depend on access + // lists and we'll need to reestimate every time + nogas := args.Gas == nil + + // Ensure any missing fields are filled, extract the recipient and input data + if err := args.setDefaults(ctx, b); err != nil { + return nil, 0, nil, err + } + var to common.Address + if args.To != nil { + to = *args.To + } else { + to = crypto.CreateAddress(args.From, uint64(*args.Nonce)) + } + var input []byte + if args.Input != nil { + input = *args.Input + } else if args.Data != nil { + input = *args.Data + } + // Retrieve the precompiles since they don't need to be added to the access list + precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number)) + + // Create an initial tracer + prevTracer := vm.NewAccessListTracer(nil, args.From, to, precompiles) + if args.AccessList != nil { + prevTracer = vm.NewAccessListTracer(*args.AccessList, args.From, to, precompiles) + } + for { + // Retrieve the current access list to expand + accessList := prevTracer.AccessList() + log.Trace("Creating access list", "input", accessList) + + // If no gas amount was specified, each unique access list needs it's own + // gas calculation. This is quite expensive, but we need to be accurate + // and it's convered by the sender only anyway. + if nogas { + args.Gas = nil + if err := args.setDefaults(ctx, b); err != nil { + return nil, 0, nil, err // shouldn't happen, just in case + } + } + // Copy the original db so we don't modify it + statedb := db.Copy() + msg := types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), input, accessList, false) + + // Apply the transaction with the access list tracer + tracer := vm.NewAccessListTracer(accessList, args.From, to, precompiles) + config := vm.Config{Tracer: tracer, Debug: true} + vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config) + if err != nil { + return nil, 0, nil, err + } + res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + if err != nil { + return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) + } + if tracer.Equal(prevTracer) { + return accessList, res.UsedGas, res.Err, nil + } + prevTracer = tracer + } +} + // PublicTransactionPoolAPI exposes methods for the RPC interface type PublicTransactionPoolAPI struct { b Backend @@ -1539,7 +1639,6 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { return errors.New(`contract creation without any data provided`) } } - // Estimate the gas usage if necessary. if args.Gas == nil { // For backwards-compatibility reason, we try both input and data @@ -1580,7 +1679,6 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { } else if args.Data != nil { input = *args.Data } - var data types.TxData if args.AccessList == nil { data = &types.LegacyTx{ diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index ebb088fef5..07e76583f3 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -63,7 +63,7 @@ type Backend interface { StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetTd(ctx context.Context, hash common.Hash) *big.Int - GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) + GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index e1f20ad72a..1934412c90 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -575,6 +575,12 @@ web3._extend({ params: 3, inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter] }), + new web3._extend.Method({ + name: 'createAccessList', + call: 'eth_createAccessList', + params: 2, + inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter], + }), ], properties: [ new web3._extend.Property({ diff --git a/les/api_backend.go b/les/api_backend.go index fc7821fb06..60c64a8bdf 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -171,10 +171,13 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { +func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) { + if vmConfig == nil { + vmConfig = new(vm.Config) + } txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, b.eth.blockchain, nil) - return vm.NewEVM(context, txContext, state, b.eth.chainConfig, vm.Config{}), state.Error, nil + return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error, nil } func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { From 2a7c4b62a9cce1b3d9cd3ad68d0c8b2acd25630c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 7 Apr 2021 18:14:24 +0300 Subject: [PATCH 447/948] eth: fix tracing state retrieval if requesting the non-dirty genesis --- eth/state_accessor.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/eth/state_accessor.go b/eth/state_accessor.go index d7564a0844..84cfaf4d73 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -61,6 +61,16 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state // the internal junks created by tracing will be persisted into the disk. database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16}) + // If we didn't check the dirty database, do check the clean one, otherwise + // we would rewind past a persisted block (specific corner case is chain + // tracing from the genesis). + if !checkLive { + statedb, err = state.New(current.Root(), database, nil) + if err == nil { + return statedb, nil + } + } + // Database does not have the state for the given block, try to regenerate for i := uint64(0); i < reexec; i++ { if current.NumberU64() == 0 { return nil, errors.New("genesis state is missing") From e3ff37c47a5aedd363e32c16c83be42e364253e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 8 Apr 2021 13:23:34 +0300 Subject: [PATCH 448/948] params: update CHTs for v1.10.2 --- params/config.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/params/config.go b/params/config.go index 8f8b36b57d..143e2e2a36 100644 --- a/params/config.go +++ b/params/config.go @@ -74,10 +74,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 364, - SectionHead: common.HexToHash("0x3fd20ff221f5e962bb66f57a61973bfc2ba959879a6509384a80a45d208b5afc"), - CHTRoot: common.HexToHash("0xe35b3b807f4e9427fb4e2929961c78a9dc10f503a538319031cc7d00946a0591"), - BloomRoot: common.HexToHash("0x340553b378b2db214b898be15c80ac5be7caffc2e6448fd6f7aff23290d89296"), + SectionIndex: 371, + SectionHead: common.HexToHash("0x50fd3cec5376ede90ef9129772022690cd1467f22c18abb7faa11e793c51e9c9"), + CHTRoot: common.HexToHash("0xb57b4b22a77b5930847b1ca9f62daa11eae6578948cb7b18997f2c0fe5757025"), + BloomRoot: common.HexToHash("0xa338f8a868a194fa90327d0f5877f656a9f3640c618d2a01a01f2e76ef9ef954"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -157,10 +157,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 248, - SectionHead: common.HexToHash("0x26874cf023695778cc3175d1bec19894204d8d0b756b587e81e35f300dc5b33c"), - CHTRoot: common.HexToHash("0xc129d1ed6673c5d3e1068e9d97244e72952b7ca08acbd7b3bfa58bc3085c442c"), - BloomRoot: common.HexToHash("0x1dafe79dcd7d348782aa834a4a4397890d9ad90643736791132ed5c16879a037"), + SectionIndex: 254, + SectionHead: common.HexToHash("0x0cba01dd71baa22ac8fa0b105bc908e94f9ecfbc79b4eb97427fe07b5851dd10"), + CHTRoot: common.HexToHash("0x5673d8fc49c9c7d8729068640e4b392d46952a5a38798973bac1cf1d0d27ad7d"), + BloomRoot: common.HexToHash("0x70e01232b66df9a7778ae3291c9217afb9a2d9f799f32d7b912bd37e7bce83a8"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -198,10 +198,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 132, - SectionHead: common.HexToHash("0x29fa240c97b47ecbfef3fea8b3cff035d93154d1d48b25e3333cf2f7067c5324"), - CHTRoot: common.HexToHash("0x85e5c59e5b202284291405dadc40dc36ab6417bd189fb18be24f6dcab6b80511"), - BloomRoot: common.HexToHash("0x0b7afdd200477f46e982e2cabc822ac454424986fa50d899685dfaeede1f882d"), + SectionIndex: 138, + SectionHead: common.HexToHash("0xb7ea0566abd7d0def5b3c9afa3431debb7bb30b65af35f106ca93a59e6c859a7"), + CHTRoot: common.HexToHash("0x378c7ea9081242beb982e2e39567ba12f2ed3e59e5aba3f9db1d595646d7c9f4"), + BloomRoot: common.HexToHash("0x523c169286cfca52e8a6579d8c35dc8bf093412d8a7478163bfa81ae91c2492d"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From 97d11b0187b4695ccf44e3b71b54155fe405a36f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 8 Apr 2021 13:02:25 +0200 Subject: [PATCH 449/948] params: release go-ethereum v1.10.2 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index d88b4f8b88..3cf4941263 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 2 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 04dcc9378dc38d54eeb6a688d81e8b5e13446519 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 8 Apr 2021 13:04:30 +0200 Subject: [PATCH 450/948] params: begin v1.10.3 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 3cf4941263..d350c5ff4a 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 3 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From fe1586b094048d7f661be93a04117e92e5ebeaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 8 Apr 2021 18:06:03 +0300 Subject: [PATCH 451/948] eth, les: drop support for eth/64, fix eth/66 tests --- eth/downloader/downloader.go | 5 +- eth/downloader/downloader_test.go | 327 +++++++++++++--------------- eth/downloader/peer.go | 8 +- eth/handler.go | 6 +- eth/handler_eth_test.go | 42 ++-- eth/protocols/eth/handler.go | 51 ++--- eth/protocols/eth/handler_test.go | 115 ++++++++-- eth/protocols/eth/handshake_test.go | 4 +- eth/protocols/eth/protocol.go | 5 +- eth/sync_test.go | 4 +- les/client_handler.go | 3 +- les/server_handler.go | 1 - 12 files changed, 286 insertions(+), 285 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index a5ed3761b1..b8cb48914e 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -459,8 +460,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.mux.Post(DoneEvent{latest}) } }() - if p.version < 64 { - return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, 64) + if p.version < eth.ETH65 { + return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, eth.ETH65) } mode := d.getMode() diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 2917116144..1140a444c1 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/trie" @@ -515,16 +516,13 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng } } -func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonSync(t, 64, FullSync) } -func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonSync(t, 64, FastSync) } +func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonSync(t, eth.ETH65, FullSync) } +func TestCanonicalSynchronisation65Fast(t *testing.T) { testCanonSync(t, eth.ETH65, FastSync) } +func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonSync(t, eth.ETH65, LightSync) } -func TestCanonicalSynchronisation65Full(t *testing.T) { testCanonSync(t, 65, FullSync) } -func TestCanonicalSynchronisation65Fast(t *testing.T) { testCanonSync(t, 65, FastSync) } -func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonSync(t, 65, LightSync) } - -func TestCanonicalSynchronisation66Full(t *testing.T) { testCanonSync(t, 66, FullSync) } -func TestCanonicalSynchronisation66Fast(t *testing.T) { testCanonSync(t, 66, FastSync) } -func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, 66, LightSync) } +func TestCanonicalSynchronisation66Full(t *testing.T) { testCanonSync(t, eth.ETH66, FullSync) } +func TestCanonicalSynchronisation66Fast(t *testing.T) { testCanonSync(t, eth.ETH66, FastSync) } +func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, eth.ETH66, LightSync) } func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -545,14 +543,11 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that if a large batch of blocks are being downloaded, it is throttled // until the cached blocks are retrieved. -func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } -func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } - -func TestThrottling65Full(t *testing.T) { testThrottling(t, 65, FullSync) } -func TestThrottling65Fast(t *testing.T) { testThrottling(t, 65, FastSync) } +func TestThrottling65Full(t *testing.T) { testThrottling(t, eth.ETH65, FullSync) } +func TestThrottling65Fast(t *testing.T) { testThrottling(t, eth.ETH65, FastSync) } -func TestThrottling66Full(t *testing.T) { testThrottling(t, 66, FullSync) } -func TestThrottling66Fast(t *testing.T) { testThrottling(t, 66, FastSync) } +func TestThrottling66Full(t *testing.T) { testThrottling(t, eth.ETH66, FullSync) } +func TestThrottling66Fast(t *testing.T) { testThrottling(t, eth.ETH66, FastSync) } func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -633,16 +628,13 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { // Tests that simple synchronization against a forked chain works correctly. In // this test common ancestor lookup should *not* be short circuited, and a full // binary search should be executed. -func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } -func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } - -func TestForkedSync65Full(t *testing.T) { testForkedSync(t, 65, FullSync) } -func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, 65, FastSync) } -func TestForkedSync65Light(t *testing.T) { testForkedSync(t, 65, LightSync) } +func TestForkedSync65Full(t *testing.T) { testForkedSync(t, eth.ETH65, FullSync) } +func TestForkedSync65Fast(t *testing.T) { testForkedSync(t, eth.ETH65, FastSync) } +func TestForkedSync65Light(t *testing.T) { testForkedSync(t, eth.ETH65, LightSync) } -func TestForkedSync66Full(t *testing.T) { testForkedSync(t, 66, FullSync) } -func TestForkedSync66Fast(t *testing.T) { testForkedSync(t, 66, FastSync) } -func TestForkedSync66Light(t *testing.T) { testForkedSync(t, 66, LightSync) } +func TestForkedSync66Full(t *testing.T) { testForkedSync(t, eth.ETH66, FullSync) } +func TestForkedSync66Fast(t *testing.T) { testForkedSync(t, eth.ETH66, FastSync) } +func TestForkedSync66Light(t *testing.T) { testForkedSync(t, eth.ETH66, LightSync) } func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -669,16 +661,13 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronising against a much shorter but much heavyer fork works // corrently and is not dropped. -func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullSync) } -func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } +func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH65, FullSync) } +func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, eth.ETH65, FastSync) } +func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH65, LightSync) } -func TestHeavyForkedSync65Full(t *testing.T) { testHeavyForkedSync(t, 65, FullSync) } -func TestHeavyForkedSync65Fast(t *testing.T) { testHeavyForkedSync(t, 65, FastSync) } -func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, 65, LightSync) } - -func TestHeavyForkedSync66Full(t *testing.T) { testHeavyForkedSync(t, 66, FullSync) } -func TestHeavyForkedSync66Fast(t *testing.T) { testHeavyForkedSync(t, 66, FastSync) } -func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, 66, LightSync) } +func TestHeavyForkedSync66Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, FullSync) } +func TestHeavyForkedSync66Fast(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, FastSync) } +func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, LightSync) } func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -707,16 +696,13 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head, ensuring that malicious peers cannot waste resources by feeding // long dead chains. -func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, FullSync) } -func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } - -func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, 65, FullSync) } -func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, 65, FastSync) } -func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, 65, LightSync) } +func TestBoundedForkedSync65Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH65, FullSync) } +func TestBoundedForkedSync65Fast(t *testing.T) { testBoundedForkedSync(t, eth.ETH65, FastSync) } +func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH65, LightSync) } -func TestBoundedForkedSync66Full(t *testing.T) { testBoundedForkedSync(t, 66, FullSync) } -func TestBoundedForkedSync66Fast(t *testing.T) { testBoundedForkedSync(t, 66, FastSync) } -func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, 66, LightSync) } +func TestBoundedForkedSync66Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, FullSync) } +func TestBoundedForkedSync66Fast(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, FastSync) } +func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, LightSync) } func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -744,16 +730,25 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head for short but heavy forks too. These are a bit special because they // take different ancestor lookup paths. -func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FullSync) } -func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } - -func TestBoundedHeavyForkedSync65Full(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FullSync) } -func TestBoundedHeavyForkedSync65Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 65, FastSync) } -func TestBoundedHeavyForkedSync65Light(t *testing.T) { testBoundedHeavyForkedSync(t, 65, LightSync) } +func TestBoundedHeavyForkedSync65Full(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH65, FullSync) +} +func TestBoundedHeavyForkedSync65Fast(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH65, FastSync) +} +func TestBoundedHeavyForkedSync65Light(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH65, LightSync) +} -func TestBoundedHeavyForkedSync66Full(t *testing.T) { testBoundedHeavyForkedSync(t, 66, FullSync) } -func TestBoundedHeavyForkedSync66Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 66, FastSync) } -func TestBoundedHeavyForkedSync66Light(t *testing.T) { testBoundedHeavyForkedSync(t, 66, LightSync) } +func TestBoundedHeavyForkedSync66Full(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH66, FullSync) +} +func TestBoundedHeavyForkedSync66Fast(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH66, FastSync) +} +func TestBoundedHeavyForkedSync66Light(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH66, LightSync) +} func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -799,16 +794,13 @@ func TestInactiveDownloader63(t *testing.T) { } // Tests that a canceled download wipes all previously accumulated state. -func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } -func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } - -func TestCancel65Full(t *testing.T) { testCancel(t, 65, FullSync) } -func TestCancel65Fast(t *testing.T) { testCancel(t, 65, FastSync) } -func TestCancel65Light(t *testing.T) { testCancel(t, 65, LightSync) } +func TestCancel65Full(t *testing.T) { testCancel(t, eth.ETH65, FullSync) } +func TestCancel65Fast(t *testing.T) { testCancel(t, eth.ETH65, FastSync) } +func TestCancel65Light(t *testing.T) { testCancel(t, eth.ETH65, LightSync) } -func TestCancel66Full(t *testing.T) { testCancel(t, 66, FullSync) } -func TestCancel66Fast(t *testing.T) { testCancel(t, 66, FastSync) } -func TestCancel66Light(t *testing.T) { testCancel(t, 66, LightSync) } +func TestCancel66Full(t *testing.T) { testCancel(t, eth.ETH66, FullSync) } +func TestCancel66Fast(t *testing.T) { testCancel(t, eth.ETH66, FastSync) } +func TestCancel66Light(t *testing.T) { testCancel(t, eth.ETH66, LightSync) } func testCancel(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -835,16 +827,13 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { } // Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, 64, FullSync) } -func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } +func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH65, FullSync) } +func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, eth.ETH65, FastSync) } +func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH65, LightSync) } -func TestMultiSynchronisation65Full(t *testing.T) { testMultiSynchronisation(t, 65, FullSync) } -func TestMultiSynchronisation65Fast(t *testing.T) { testMultiSynchronisation(t, 65, FastSync) } -func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, 65, LightSync) } - -func TestMultiSynchronisation66Full(t *testing.T) { testMultiSynchronisation(t, 66, FullSync) } -func TestMultiSynchronisation66Fast(t *testing.T) { testMultiSynchronisation(t, 66, FastSync) } -func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, 66, LightSync) } +func TestMultiSynchronisation66Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, FullSync) } +func TestMultiSynchronisation66Fast(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, FastSync) } +func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, LightSync) } func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -868,16 +857,13 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronisations behave well in multi-version protocol environments // and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, 64, FullSync) } -func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } - -func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, 65, FullSync) } -func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, 65, FastSync) } -func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, 65, LightSync) } +func TestMultiProtoSynchronisation65Full(t *testing.T) { testMultiProtoSync(t, eth.ETH65, FullSync) } +func TestMultiProtoSynchronisation65Fast(t *testing.T) { testMultiProtoSync(t, eth.ETH65, FastSync) } +func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, eth.ETH65, LightSync) } -func TestMultiProtoSynchronisation66Full(t *testing.T) { testMultiProtoSync(t, 66, FullSync) } -func TestMultiProtoSynchronisation66Fast(t *testing.T) { testMultiProtoSync(t, 66, FastSync) } -func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, 66, LightSync) } +func TestMultiProtoSynchronisation66Full(t *testing.T) { testMultiProtoSync(t, eth.ETH66, FullSync) } +func TestMultiProtoSynchronisation66Fast(t *testing.T) { testMultiProtoSync(t, eth.ETH66, FastSync) } +func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, eth.ETH66, LightSync) } func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -889,9 +875,8 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { chain := testChainBase.shorten(blockCacheMaxItems - 15) // Create peers of every type - tester.newPeer("peer 64", 64, chain) - tester.newPeer("peer 65", 65, chain) - tester.newPeer("peer 66", 66, chain) + tester.newPeer("peer 65", eth.ETH65, chain) + tester.newPeer("peer 66", eth.ETH66, chain) // Synchronise with the requested peer and make sure all blocks were retrieved if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { @@ -900,7 +885,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { assertOwnChain(t, tester, chain.len()) // Check that no peers have been dropped off - for _, version := range []int{64, 65, 66} { + for _, version := range []int{65, 66} { peer := fmt.Sprintf("peer %d", version) if _, ok := tester.peers[peer]; !ok { t.Errorf("%s dropped", peer) @@ -910,16 +895,13 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that if a block is empty (e.g. header only), no body request should be // made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, FullSync) } -func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } +func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH65, FullSync) } +func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, eth.ETH65, FastSync) } +func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH65, LightSync) } -func TestEmptyShortCircuit65Full(t *testing.T) { testEmptyShortCircuit(t, 65, FullSync) } -func TestEmptyShortCircuit65Fast(t *testing.T) { testEmptyShortCircuit(t, 65, FastSync) } -func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, 65, LightSync) } - -func TestEmptyShortCircuit66Full(t *testing.T) { testEmptyShortCircuit(t, 66, FullSync) } -func TestEmptyShortCircuit66Fast(t *testing.T) { testEmptyShortCircuit(t, 66, FastSync) } -func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, 66, LightSync) } +func TestEmptyShortCircuit66Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, FullSync) } +func TestEmptyShortCircuit66Fast(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, FastSync) } +func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, LightSync) } func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -967,16 +949,13 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { // Tests that headers are enqueued continuously, preventing malicious nodes from // stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 64, FullSync) } -func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } - -func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, 65, FullSync) } -func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, 65, FastSync) } -func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, 65, LightSync) } +func TestMissingHeaderAttack65Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH65, FullSync) } +func TestMissingHeaderAttack65Fast(t *testing.T) { testMissingHeaderAttack(t, eth.ETH65, FastSync) } +func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH65, LightSync) } -func TestMissingHeaderAttack66Full(t *testing.T) { testMissingHeaderAttack(t, 66, FullSync) } -func TestMissingHeaderAttack66Fast(t *testing.T) { testMissingHeaderAttack(t, 66, FastSync) } -func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, 66, LightSync) } +func TestMissingHeaderAttack66Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, FullSync) } +func TestMissingHeaderAttack66Fast(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, FastSync) } +func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, LightSync) } func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1002,16 +981,13 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that if requested headers are shifted (i.e. first is missing), the queue // detects the invalid numbering. -func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 64, FullSync) } -func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } +func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH65, FullSync) } +func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH65, FastSync) } +func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH65, LightSync) } -func TestShiftedHeaderAttack65Full(t *testing.T) { testShiftedHeaderAttack(t, 65, FullSync) } -func TestShiftedHeaderAttack65Fast(t *testing.T) { testShiftedHeaderAttack(t, 65, FastSync) } -func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, 65, LightSync) } - -func TestShiftedHeaderAttack66Full(t *testing.T) { testShiftedHeaderAttack(t, 66, FullSync) } -func TestShiftedHeaderAttack66Fast(t *testing.T) { testShiftedHeaderAttack(t, 66, FastSync) } -func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, 66, LightSync) } +func TestShiftedHeaderAttack66Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, FullSync) } +func TestShiftedHeaderAttack66Fast(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, FastSync) } +func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, LightSync) } func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1042,9 +1018,8 @@ func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that upon detecting an invalid header, the recent ones are rolled back // for various failure scenarios. Afterwards a full sync is attempted to make // sure no state was corrupted. -func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } -func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, 65, FastSync) } -func TestInvalidHeaderRollback66Fast(t *testing.T) { testInvalidHeaderRollback(t, 66, FastSync) } +func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH65, FastSync) } +func TestInvalidHeaderRollback66Fast(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH66, FastSync) } func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1134,16 +1109,25 @@ func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { // Tests that a peer advertising a high TD doesn't get to stall the downloader // afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttack(t, 64, FullSync) } -func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } - -func TestHighTDStarvationAttack65Full(t *testing.T) { testHighTDStarvationAttack(t, 65, FullSync) } -func TestHighTDStarvationAttack65Fast(t *testing.T) { testHighTDStarvationAttack(t, 65, FastSync) } -func TestHighTDStarvationAttack65Light(t *testing.T) { testHighTDStarvationAttack(t, 65, LightSync) } +func TestHighTDStarvationAttack65Full(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH65, FullSync) +} +func TestHighTDStarvationAttack65Fast(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH65, FastSync) +} +func TestHighTDStarvationAttack65Light(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH65, LightSync) +} -func TestHighTDStarvationAttack66Full(t *testing.T) { testHighTDStarvationAttack(t, 66, FullSync) } -func TestHighTDStarvationAttack66Fast(t *testing.T) { testHighTDStarvationAttack(t, 66, FastSync) } -func TestHighTDStarvationAttack66Light(t *testing.T) { testHighTDStarvationAttack(t, 66, LightSync) } +func TestHighTDStarvationAttack66Full(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH66, FullSync) +} +func TestHighTDStarvationAttack66Fast(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH66, FastSync) +} +func TestHighTDStarvationAttack66Light(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH66, LightSync) +} func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1159,9 +1143,8 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { } // Tests that misbehaving peers are disconnected, whilst behaving ones are not. -func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } -func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, 65) } -func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, 66) } +func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH65) } +func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH66) } func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { t.Parallel() @@ -1213,16 +1196,13 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { // Tests that synchronisation progress (origin block number, current block number // and highest block number) is tracked and updated correctly. -func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } -func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } +func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, eth.ETH65, FullSync) } +func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, eth.ETH65, FastSync) } +func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, eth.ETH65, LightSync) } -func TestSyncProgress65Full(t *testing.T) { testSyncProgress(t, 65, FullSync) } -func TestSyncProgress65Fast(t *testing.T) { testSyncProgress(t, 65, FastSync) } -func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, 65, LightSync) } - -func TestSyncProgress66Full(t *testing.T) { testSyncProgress(t, 66, FullSync) } -func TestSyncProgress66Fast(t *testing.T) { testSyncProgress(t, 66, FastSync) } -func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, 66, LightSync) } +func TestSyncProgress66Full(t *testing.T) { testSyncProgress(t, eth.ETH66, FullSync) } +func TestSyncProgress66Fast(t *testing.T) { testSyncProgress(t, eth.ETH66, FastSync) } +func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, eth.ETH66, LightSync) } func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1300,16 +1280,13 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync // Tests that synchronisation progress (origin block number and highest block // number) is tracked and updated correctly in case of a fork (or manual head // revertal). -func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, FullSync) } -func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } - -func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, 65, FullSync) } -func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, 65, FastSync) } -func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, 65, LightSync) } +func TestForkedSyncProgress65Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH65, FullSync) } +func TestForkedSyncProgress65Fast(t *testing.T) { testForkedSyncProgress(t, eth.ETH65, FastSync) } +func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH65, LightSync) } -func TestForkedSyncProgress66Full(t *testing.T) { testForkedSyncProgress(t, 66, FullSync) } -func TestForkedSyncProgress66Fast(t *testing.T) { testForkedSyncProgress(t, 66, FastSync) } -func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, 66, LightSync) } +func TestForkedSyncProgress66Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, FullSync) } +func TestForkedSyncProgress66Fast(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, FastSync) } +func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, LightSync) } func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1379,16 +1356,13 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if synchronisation is aborted due to some failure, then the progress // origin is not updated in the next sync cycle, as it should be considered the // continuation of the previous sync and not a new instance. -func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, FullSync) } -func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } +func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH65, FullSync) } +func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, eth.ETH65, FastSync) } +func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH65, LightSync) } -func TestFailedSyncProgress65Full(t *testing.T) { testFailedSyncProgress(t, 65, FullSync) } -func TestFailedSyncProgress65Fast(t *testing.T) { testFailedSyncProgress(t, 65, FastSync) } -func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, 65, LightSync) } - -func TestFailedSyncProgress66Full(t *testing.T) { testFailedSyncProgress(t, 66, FullSync) } -func TestFailedSyncProgress66Fast(t *testing.T) { testFailedSyncProgress(t, 66, FastSync) } -func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, 66, LightSync) } +func TestFailedSyncProgress66Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, FullSync) } +func TestFailedSyncProgress66Fast(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, FastSync) } +func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, LightSync) } func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1455,16 +1429,13 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if an attacker fakes a chain height, after the attack is detected, // the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, FullSync) } -func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } - -func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, 65, FullSync) } -func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, 65, FastSync) } -func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, 65, LightSync) } +func TestFakedSyncProgress65Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH65, FullSync) } +func TestFakedSyncProgress65Fast(t *testing.T) { testFakedSyncProgress(t, eth.ETH65, FastSync) } +func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH65, LightSync) } -func TestFakedSyncProgress66Full(t *testing.T) { testFakedSyncProgress(t, 66, FullSync) } -func TestFakedSyncProgress66Fast(t *testing.T) { testFakedSyncProgress(t, 66, FastSync) } -func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, 66, LightSync) } +func TestFakedSyncProgress66Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, FullSync) } +func TestFakedSyncProgress66Fast(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, FastSync) } +func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, LightSync) } func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1535,16 +1506,13 @@ func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // This test reproduces an issue where unexpected deliveries would // block indefinitely if they arrived at the right time. -func TestDeliverHeadersHang64Full(t *testing.T) { testDeliverHeadersHang(t, 64, FullSync) } -func TestDeliverHeadersHang64Fast(t *testing.T) { testDeliverHeadersHang(t, 64, FastSync) } +func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, eth.ETH65, FullSync) } +func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, eth.ETH65, FastSync) } +func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, eth.ETH65, LightSync) } -func TestDeliverHeadersHang65Full(t *testing.T) { testDeliverHeadersHang(t, 65, FullSync) } -func TestDeliverHeadersHang65Fast(t *testing.T) { testDeliverHeadersHang(t, 65, FastSync) } -func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, 65, LightSync) } - -func TestDeliverHeadersHang66Full(t *testing.T) { testDeliverHeadersHang(t, 66, FullSync) } -func TestDeliverHeadersHang66Fast(t *testing.T) { testDeliverHeadersHang(t, 66, FastSync) } -func TestDeliverHeadersHang66Light(t *testing.T) { testDeliverHeadersHang(t, 66, LightSync) } +func TestDeliverHeadersHang66Full(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, FullSync) } +func TestDeliverHeadersHang66Fast(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, FastSync) } +func TestDeliverHeadersHang66Light(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, LightSync) } func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() @@ -1699,16 +1667,17 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { // Tests that peers below a pre-configured checkpoint block are prevented from // being fast-synced from, avoiding potential cheap eclipse attacks. -func TestCheckpointEnforcement64Full(t *testing.T) { testCheckpointEnforcement(t, 64, FullSync) } -func TestCheckpointEnforcement64Fast(t *testing.T) { testCheckpointEnforcement(t, 64, FastSync) } - -func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, 65, FullSync) } -func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, 65, FastSync) } -func TestCheckpointEnforcement65Light(t *testing.T) { testCheckpointEnforcement(t, 65, LightSync) } +func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH65, FullSync) } +func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, eth.ETH65, FastSync) } +func TestCheckpointEnforcement65Light(t *testing.T) { + testCheckpointEnforcement(t, eth.ETH65, LightSync) +} -func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, 66, FullSync) } -func TestCheckpointEnforcement66Fast(t *testing.T) { testCheckpointEnforcement(t, 66, FastSync) } -func TestCheckpointEnforcement66Light(t *testing.T) { testCheckpointEnforcement(t, 66, LightSync) } +func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FullSync) } +func TestCheckpointEnforcement66Fast(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FastSync) } +func TestCheckpointEnforcement66Light(t *testing.T) { + testCheckpointEnforcement(t, eth.ETH66, LightSync) +} func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index 7852569d8e..b3b6cc95a0 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -458,7 +458,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.headerThroughput } - return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) + return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within @@ -472,7 +472,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.blockThroughput } - return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) + return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers @@ -486,7 +486,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.receiptThroughput } - return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) + return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle @@ -500,7 +500,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { defer p.lock.RUnlock() return p.stateThroughput } - return ps.idlePeers(eth.ETH64, eth.ETH66, idle, throughput) + return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } // idlePeers retrieves a flat list of all currently idle peers satisfying the diff --git a/eth/handler.go b/eth/handler.go index 11c8565de1..3f10750abf 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -496,11 +496,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { for peer, hashes := range annos { annoPeers++ annoCount += len(hashes) - if peer.Version() >= eth.ETH65 { - peer.AsyncSendPooledTransactionHashes(hashes) - } else { - peer.AsyncSendTransactions(hashes) - } + peer.AsyncSendPooledTransactionHashes(hashes) } log.Debug("Transaction broadcast", "txs", len(txs), "announce packs", annoPeers, "announced hashes", annoCount, diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 5f5d4e9e82..1d38e3b666 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -80,8 +80,8 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { // Tests that peers are correctly accepted (or rejected) based on the advertised // fork IDs in the protocol handshake. -func TestForkIDSplit64(t *testing.T) { testForkIDSplit(t, 64) } -func TestForkIDSplit65(t *testing.T) { testForkIDSplit(t, 65) } +func TestForkIDSplit65(t *testing.T) { testForkIDSplit(t, eth.ETH65) } +func TestForkIDSplit66(t *testing.T) { testForkIDSplit(t, eth.ETH66) } func testForkIDSplit(t *testing.T, protocol uint) { t.Parallel() @@ -236,8 +236,8 @@ func testForkIDSplit(t *testing.T, protocol uint) { } // Tests that received transactions are added to the local pool. -func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) } -func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) } +func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, eth.ETH65) } +func TestRecvTransactions66(t *testing.T) { testRecvTransactions(t, eth.ETH66) } func testRecvTransactions(t *testing.T, protocol uint) { t.Parallel() @@ -294,8 +294,8 @@ func testRecvTransactions(t *testing.T, protocol uint) { } // This test checks that pending transactions are sent. -func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) } -func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) } +func TestSendTransactions65(t *testing.T) { testSendTransactions(t, eth.ETH65) } +func TestSendTransactions66(t *testing.T) { testSendTransactions(t, eth.ETH66) } func testSendTransactions(t *testing.T, protocol uint) { t.Parallel() @@ -354,19 +354,7 @@ func testSendTransactions(t *testing.T, protocol uint) { seen := make(map[common.Hash]struct{}) for len(seen) < len(insert) { switch protocol { - case 63, 64: - select { - case <-anns: - t.Errorf("tx announce received on pre eth/65") - case txs := <-bcasts: - for _, tx := range txs { - if _, ok := seen[tx.Hash()]; ok { - t.Errorf("duplicate transaction announced: %x", tx.Hash()) - } - seen[tx.Hash()] = struct{}{} - } - } - case 65: + case 65, 66: select { case hashes := <-anns: for _, hash := range hashes { @@ -392,8 +380,8 @@ func testSendTransactions(t *testing.T, protocol uint) { // Tests that transactions get propagated to all attached peers, either via direct // broadcasts or via announcements/retrievals. -func TestTransactionPropagation64(t *testing.T) { testTransactionPropagation(t, 64) } -func TestTransactionPropagation65(t *testing.T) { testTransactionPropagation(t, 65) } +func TestTransactionPropagation65(t *testing.T) { testTransactionPropagation(t, eth.ETH65) } +func TestTransactionPropagation66(t *testing.T) { testTransactionPropagation(t, eth.ETH66) } func testTransactionPropagation(t *testing.T, protocol uint) { t.Parallel() @@ -530,8 +518,8 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo defer p2pLocal.Close() defer p2pRemote.Close() - local := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool) - remote := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool) + local := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool) + remote := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool) defer local.Close() defer remote.Close() @@ -620,8 +608,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { defer sourcePipe.Close() defer sinkPipe.Close() - sourcePeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil) - sinkPeer := eth.NewPeer(eth.ETH64, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil) + sourcePeer := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil) + sinkPeer := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil) defer sourcePeer.Close() defer sinkPeer.Close() @@ -672,8 +660,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { // Tests that a propagated malformed block (uncles or transactions don't match // with the hashes in the header) gets discarded and not broadcast forward. -func TestBroadcastMalformedBlock64(t *testing.T) { testBroadcastMalformedBlock(t, 64) } -func TestBroadcastMalformedBlock65(t *testing.T) { testBroadcastMalformedBlock(t, 65) } +func TestBroadcastMalformedBlock65(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH65) } +func TestBroadcastMalformedBlock66(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH66) } func testBroadcastMalformedBlock(t *testing.T, protocol uint) { t.Parallel() diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 0dc3de9898..52dcf94011 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -171,44 +171,27 @@ type Decoder interface { Time() time.Time } -var eth64 = map[uint64]msgHandler{ - GetBlockHeadersMsg: handleGetBlockHeaders, - BlockHeadersMsg: handleBlockHeaders, - GetBlockBodiesMsg: handleGetBlockBodies, - BlockBodiesMsg: handleBlockBodies, - GetNodeDataMsg: handleGetNodeData, - NodeDataMsg: handleNodeData, - GetReceiptsMsg: handleGetReceipts, - ReceiptsMsg: handleReceipts, - NewBlockHashesMsg: handleNewBlockhashes, - NewBlockMsg: handleNewBlock, - TransactionsMsg: handleTransactions, -} var eth65 = map[uint64]msgHandler{ - // old 64 messages - GetBlockHeadersMsg: handleGetBlockHeaders, - BlockHeadersMsg: handleBlockHeaders, - GetBlockBodiesMsg: handleGetBlockBodies, - BlockBodiesMsg: handleBlockBodies, - GetNodeDataMsg: handleGetNodeData, - NodeDataMsg: handleNodeData, - GetReceiptsMsg: handleGetReceipts, - ReceiptsMsg: handleReceipts, - NewBlockHashesMsg: handleNewBlockhashes, - NewBlockMsg: handleNewBlock, - TransactionsMsg: handleTransactions, - // New eth65 messages + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetNodeDataMsg: handleGetNodeData, + NodeDataMsg: handleNodeData, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, GetPooledTransactionsMsg: handleGetPooledTransactions, PooledTransactionsMsg: handlePooledTransactions, } var eth66 = map[uint64]msgHandler{ - // eth64 announcement messages (no id) - NewBlockHashesMsg: handleNewBlockhashes, - NewBlockMsg: handleNewBlock, - TransactionsMsg: handleTransactions, - // eth65 announcement messages (no id) + NewBlockHashesMsg: handleNewBlockhashes, + NewBlockMsg: handleNewBlock, + TransactionsMsg: handleTransactions, NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, // eth66 messages with request-id GetBlockHeadersMsg: handleGetBlockHeaders66, @@ -236,10 +219,8 @@ func handleMessage(backend Backend, peer *Peer) error { } defer msg.Discard() - var handlers = eth64 - if peer.Version() == ETH65 { - handlers = eth65 - } else if peer.Version() >= ETH66 { + var handlers = eth65 + if peer.Version() >= ETH66 { handlers = eth66 } // Track the emount of time it takes to serve the request and run the handler diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 30beae931b..2dd2446e3d 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -110,8 +110,8 @@ func (b *testBackend) Handle(*Peer, Packet) error { } // Tests that block headers can be retrieved from a remote chain based on user queries. -func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } -func TestGetBlockHeaders65(t *testing.T) { testGetBlockHeaders(t, 65) } +func TestGetBlockHeaders65(t *testing.T) { testGetBlockHeaders(t, ETH65) } +func TestGetBlockHeaders66(t *testing.T) { testGetBlockHeaders(t, ETH66) } func testGetBlockHeaders(t *testing.T, protocol uint) { t.Parallel() @@ -254,18 +254,44 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { headers = append(headers, backend.chain.GetBlockByHash(hash).Header()) } // Send the hash request and verify the response - p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) - if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) + if protocol <= ETH65 { + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + } else { + p2p.Send(peer.app, GetBlockHeadersMsg, GetBlockHeadersPacket66{ + RequestId: 123, + GetBlockHeadersPacket: tt.query, + }) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, BlockHeadersPacket66{ + RequestId: 123, + BlockHeadersPacket: headers, + }); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } } // If the test used number origins, repeat with hashes as the too if tt.query.Origin.Hash == (common.Hash{}) { if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 - p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) - if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) + if protocol <= ETH65 { + p2p.Send(peer.app, GetBlockHeadersMsg, tt.query) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } + } else { + p2p.Send(peer.app, GetBlockHeadersMsg, GetBlockHeadersPacket66{ + RequestId: 456, + GetBlockHeadersPacket: tt.query, + }) + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, BlockHeadersPacket66{ + RequestId: 456, + BlockHeadersPacket: headers, + }); err != nil { + t.Errorf("test %d: headers mismatch: %v", i, err) + } } } } @@ -273,8 +299,8 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { } // Tests that block contents can be retrieved from a remote chain based on their hashes. -func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) } -func TestGetBlockBodies65(t *testing.T) { testGetBlockBodies(t, 65) } +func TestGetBlockBodies65(t *testing.T) { testGetBlockBodies(t, ETH65) } +func TestGetBlockBodies66(t *testing.T) { testGetBlockBodies(t, ETH66) } func testGetBlockBodies(t *testing.T, protocol uint) { t.Parallel() @@ -343,16 +369,29 @@ func testGetBlockBodies(t *testing.T, protocol uint) { } } // Send the hash request and verify the response - p2p.Send(peer.app, GetBlockBodiesMsg, hashes) - if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, bodies); err != nil { - t.Errorf("test %d: bodies mismatch: %v", i, err) + if protocol <= ETH65 { + p2p.Send(peer.app, GetBlockBodiesMsg, hashes) + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, bodies); err != nil { + t.Errorf("test %d: bodies mismatch: %v", i, err) + } + } else { + p2p.Send(peer.app, GetBlockBodiesMsg, GetBlockBodiesPacket66{ + RequestId: 123, + GetBlockBodiesPacket: hashes, + }) + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, BlockBodiesPacket66{ + RequestId: 123, + BlockBodiesPacket: bodies, + }); err != nil { + t.Errorf("test %d: bodies mismatch: %v", i, err) + } } } } // Tests that the state trie nodes can be retrieved based on hashes. -func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) } -func TestGetNodeData65(t *testing.T) { testGetNodeData(t, 65) } +func TestGetNodeData65(t *testing.T) { testGetNodeData(t, ETH65) } +func TestGetNodeData66(t *testing.T) { testGetNodeData(t, ETH66) } func testGetNodeData(t *testing.T, protocol uint) { t.Parallel() @@ -410,7 +449,14 @@ func testGetNodeData(t *testing.T, protocol uint) { } it.Release() - p2p.Send(peer.app, GetNodeDataMsg, hashes) + if protocol <= ETH65 { + p2p.Send(peer.app, GetNodeDataMsg, hashes) + } else { + p2p.Send(peer.app, GetNodeDataMsg, GetNodeDataPacket66{ + RequestId: 123, + GetNodeDataPacket: hashes, + }) + } msg, err := peer.app.ReadMsg() if err != nil { t.Fatalf("failed to read node data response: %v", err) @@ -419,8 +465,16 @@ func testGetNodeData(t *testing.T, protocol uint) { t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg) } var data [][]byte - if err := msg.Decode(&data); err != nil { - t.Fatalf("failed to decode response node data: %v", err) + if protocol <= ETH65 { + if err := msg.Decode(&data); err != nil { + t.Fatalf("failed to decode response node data: %v", err) + } + } else { + var res NodeDataPacket66 + if err := msg.Decode(&res); err != nil { + t.Fatalf("failed to decode response node data: %v", err) + } + data = res.NodeDataPacket } // Verify that all hashes correspond to the requested data, and reconstruct a state tree for i, want := range hashes { @@ -452,8 +506,8 @@ func testGetNodeData(t *testing.T, protocol uint) { } // Tests that the transaction receipts can be retrieved based on hashes. -func TestGetBlockReceipts64(t *testing.T) { testGetBlockReceipts(t, 64) } -func TestGetBlockReceipts65(t *testing.T) { testGetBlockReceipts(t, 65) } +func TestGetBlockReceipts65(t *testing.T) { testGetBlockReceipts(t, ETH65) } +func TestGetBlockReceipts66(t *testing.T) { testGetBlockReceipts(t, ETH66) } func testGetBlockReceipts(t *testing.T, protocol uint) { t.Parallel() @@ -503,7 +557,7 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { // Collect the hashes to request, and the response to expect var ( hashes []common.Hash - receipts []types.Receipts + receipts [][]*types.Receipt ) for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ { block := backend.chain.GetBlockByNumber(i) @@ -512,8 +566,21 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) } // Send the hash request and verify the response - p2p.Send(peer.app, GetReceiptsMsg, hashes) - if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, receipts); err != nil { - t.Errorf("receipts mismatch: %v", err) + if protocol <= ETH65 { + p2p.Send(peer.app, GetReceiptsMsg, hashes) + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, receipts); err != nil { + t.Errorf("receipts mismatch: %v", err) + } + } else { + p2p.Send(peer.app, GetReceiptsMsg, GetReceiptsPacket66{ + RequestId: 123, + GetReceiptsPacket: hashes, + }) + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, ReceiptsPacket66{ + RequestId: 123, + ReceiptsPacket: receipts, + }); err != nil { + t.Errorf("receipts mismatch: %v", err) + } } } diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go index 65f9a00064..3bebda2dcc 100644 --- a/eth/protocols/eth/handshake_test.go +++ b/eth/protocols/eth/handshake_test.go @@ -27,8 +27,8 @@ import ( ) // Tests that handshake failures are detected and reported correctly. -func TestHandshake64(t *testing.T) { testHandshake(t, 64) } -func TestHandshake65(t *testing.T) { testHandshake(t, 65) } +func TestHandshake65(t *testing.T) { testHandshake(t, ETH65) } +func TestHandshake66(t *testing.T) { testHandshake(t, ETH66) } func testHandshake(t *testing.T, protocol uint) { t.Parallel() diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 7f1832754f..62c018ef8e 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -30,7 +30,6 @@ import ( // Constants to match up protocol versions and messages const ( - ETH64 = 64 ETH65 = 65 ETH66 = 66 ) @@ -41,11 +40,11 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH66, ETH65, ETH64} +var ProtocolVersions = []uint{ETH66, ETH65} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{ETH66: 17, ETH65: 17, ETH64: 17} +var protocolLengths = map[uint]uint64{ETH66: 17, ETH65: 17} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 diff --git a/eth/sync_test.go b/eth/sync_test.go index 9cc806b18a..a0c6f86023 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -28,8 +28,8 @@ import ( ) // Tests that fast sync is disabled after a successful sync cycle. -func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) } -func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) } +func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, eth.ETH65) } +func TestFastSyncDisabling66(t *testing.T) { testFastSyncDisabling(t, eth.ETH66) } // Tests that fast sync gets disabled as soon as a real block is successfully // imported into the blockchain. diff --git a/les/client_handler.go b/les/client_handler.go index f8e9edc9fe..73149975c3 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" @@ -470,7 +471,7 @@ func (d *downloaderPeerNotify) registerPeer(p *serverPeer) { handler: h, peer: p, } - h.downloader.RegisterLightPeer(p.id, ethVersion, pc) + h.downloader.RegisterLightPeer(p.id, eth.ETH65, pc) } func (d *downloaderPeerNotify) unregisterPeer(p *serverPeer) { diff --git a/les/server_handler.go b/les/server_handler.go index 5e12136d90..0a683c1b41 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -41,7 +41,6 @@ import ( const ( softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - ethVersion = 64 // equivalent eth version for the downloader MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request From 6c27d8f996ee8bc0dd7ed3e8ec195dd38e14acab Mon Sep 17 00:00:00 2001 From: Balaji Shetty Pachai <32358081+balajipachai@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:30:48 +0530 Subject: [PATCH 452/948] accounts: documentation fixes (#22645) * replaces `an chance` with `a chance` * replaces `SignHashWithPassphrase` with `SignTextWithPassphrase` as there was no SignHashWithPasspharse function in the file --- accounts/accounts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index 08a1f0f2b1..7178578091 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -113,7 +113,7 @@ type Wallet interface { SignData(account Account, mimeType string, data []byte) ([]byte, error) // SignDataWithPassphrase is identical to SignData, but also takes a password - // NOTE: there's an chance that an erroneous call might mistake the two strings, and + // NOTE: there's a chance that an erroneous call might mistake the two strings, and // supply password in the mimetype field, or vice versa. Thus, an implementation // should never echo the mimetype or return the mimetype in the error-response SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error) @@ -127,7 +127,7 @@ type Wallet interface { // a password to decrypt the account, or a PIN code o verify the transaction), // an AuthNeededError instance will be returned, containing infos for the user // about which fields or actions are needed. The user may retry by providing - // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock + // the needed details via SignTextWithPassphrase, or by other means (e.g. unlock // the account in a keystore). // // This method should return the signature in 'canonical' format, with v 0 or 1 From 271e5b7fc921709bd57ec672912f63a4586717dd Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 13 Apr 2021 15:45:30 +0200 Subject: [PATCH 453/948] cmd/geth: add db-command to inspect freezer index (#22633) This PR makes it easier to inspect the freezer index, which could be useful to investigate things like #22111 --- cmd/geth/dbcmd.go | 58 ++++++++++++++++++++++++++++++++ core/rawdb/freezer.go | 2 +- core/rawdb/freezer_table.go | 19 +++++------ core/rawdb/freezer_table_test.go | 4 +-- core/rawdb/schema.go | 4 +-- 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index db1fb0b801..4c70373e9a 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strconv" "time" @@ -60,6 +61,7 @@ Remove blockchain and state databases`, dbDeleteCmd, dbPutCmd, dbGetSlotsCmd, + dbDumpFreezerIndex, }, } dbInspectCmd = cli.Command{ @@ -177,6 +179,22 @@ WARNING: This is a low-level operation which may cause database corruption!`, }, Description: "This command looks up the specified database key from the database.", } + dbDumpFreezerIndex = cli.Command{ + Action: utils.MigrateFlags(freezerInspect), + Name: "freezer-index", + Usage: "Dump out the index of a given freezer type", + ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: "This command displays information about the freezer index.", + } ) func removeDB(ctx *cli.Context) error { @@ -449,3 +467,43 @@ func dbDumpTrie(ctx *cli.Context) error { } return it.Err } + +func freezerInspect(ctx *cli.Context) error { + var ( + start, end int64 + disableSnappy bool + err error + ) + if ctx.NArg() < 3 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + kind := ctx.Args().Get(0) + if noSnap, ok := rawdb.FreezerNoSnappy[kind]; !ok { + var options []string + for opt := range rawdb.FreezerNoSnappy { + options = append(options, opt) + } + sort.Strings(options) + return fmt.Errorf("Could read freezer-type '%v'. Available options: %v", kind, options) + } else { + disableSnappy = noSnap + } + if start, err = strconv.ParseInt(ctx.Args().Get(1), 10, 64); err != nil { + log.Info("Could read start-param", "error", err) + return err + } + if end, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil { + log.Info("Could read count param", "error", err) + return err + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := filepath.Join(stack.ResolvePath("chaindata"), "ancient") + log.Info("Opening freezer", "location", path, "name", kind) + if f, err := rawdb.NewFreezerTable(path, kind, disableSnappy); err != nil { + return err + } else { + f.DumpIndex(start, end) + } + return nil +} diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 4e5ae4284e..94b99a64eb 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -118,7 +118,7 @@ func newFreezer(datadir string, namespace string, readonly bool) (*freezer, erro trigger: make(chan chan struct{}), quit: make(chan struct{}), } - for name, disableSnappy := range freezerNoSnappy { + for name, disableSnappy := range FreezerNoSnappy { table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy) if err != nil { for _, table := range freezer.tables { diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index cd273222b1..b614c10d37 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -636,25 +636,24 @@ func (t *freezerTable) Sync() error { return t.head.Sync() } -// printIndex is a debug print utility function for testing -func (t *freezerTable) printIndex() { +// DumpIndex is a debug print utility function, mainly for testing. It can also +// be used to analyse a live freezer table index. +func (t *freezerTable) DumpIndex(start, stop int64) { buf := make([]byte, indexEntrySize) - fmt.Printf("|-----------------|\n") - fmt.Printf("| fileno | offset |\n") - fmt.Printf("|--------+--------|\n") + fmt.Printf("| number | fileno | offset |\n") + fmt.Printf("|--------|--------|--------|\n") - for i := uint64(0); ; i++ { + for i := uint64(start); ; i++ { if _, err := t.index.ReadAt(buf, int64(i*indexEntrySize)); err != nil { break } var entry indexEntry entry.unmarshalBinary(buf) - fmt.Printf("| %03d | %03d | \n", entry.filenum, entry.offset) - if i > 100 { - fmt.Printf(" ... \n") + fmt.Printf("| %03d | %03d | %03d | \n", i, entry.filenum, entry.offset) + if stop > 0 && i >= uint64(stop) { break } } - fmt.Printf("|-----------------|\n") + fmt.Printf("|--------------------------|\n") } diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index b22c58e138..b8d3170c62 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -525,7 +525,7 @@ func TestOffset(t *testing.T) { f.Append(4, getChunk(20, 0xbb)) f.Append(5, getChunk(20, 0xaa)) - f.printIndex() + f.DumpIndex(0, 100) f.Close() } // Now crop it. @@ -572,7 +572,7 @@ func TestOffset(t *testing.T) { if err != nil { t.Fatal(err) } - f.printIndex() + f.DumpIndex(0, 100) // It should allow writing item 6 f.Append(numDeleted+2, getChunk(20, 0x99)) diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 0b411057f8..7a97389106 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -114,9 +114,9 @@ const ( freezerDifficultyTable = "diffs" ) -// freezerNoSnappy configures whether compression is disabled for the ancient-tables. +// FreezerNoSnappy configures whether compression is disabled for the ancient-tables. // Hashes and difficulties don't compress well. -var freezerNoSnappy = map[string]bool{ +var FreezerNoSnappy = map[string]bool{ freezerHeaderTable: false, freezerHashTable: true, freezerBodiesTable: false, From 72e37942f3f777129b6cfa2d28b1ba7bbdf6437a Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Wed, 14 Apr 2021 03:21:46 +0530 Subject: [PATCH 454/948] cmd/faucet: support testnet flags in the faucet (#22545) Co-authored-by: Felix Lange Co-authored-by: Martin Holst Swende --- cmd/faucet/faucet.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index e839f1c886..bb5375384f 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -85,6 +85,9 @@ var ( twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API") twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API") + + goerliFlag = flag.Bool("goerli", false, "Initializes the faucet with Görli network config") + rinkebyFlag = flag.Bool("rinkeby", false, "Initializes the faucet with Rinkeby network config") ) var ( @@ -144,13 +147,9 @@ func main() { log.Crit("Failed to render the faucet template", "err", err) } // Load and parse the genesis block requested by the user - blob, err := ioutil.ReadFile(*genesisFlag) + genesis, err := getGenesis(genesisFlag, *goerliFlag, *rinkebyFlag) if err != nil { - log.Crit("Failed to read genesis block contents", "genesis", *genesisFlag, "err", err) - } - genesis := new(core.Genesis) - if err = json.Unmarshal(blob, genesis); err != nil { - log.Crit("Failed to parse genesis block json", "err", err) + log.Crit("Failed to parse genesis config", "err", err) } // Convert the bootnodes to internal enode representations var enodes []*enode.Node @@ -162,7 +161,8 @@ func main() { } } // Load up the account key and decrypt its password - if blob, err = ioutil.ReadFile(*accPassFlag); err != nil { + blob, err := ioutil.ReadFile(*accPassFlag) + if err != nil { log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err) } pass := strings.TrimSuffix(string(blob), "\n") @@ -884,3 +884,19 @@ func authNoAuth(url string) (string, string, common.Address, error) { } return address.Hex() + "@noauth", "", address, nil } + +// getGenesis returns a genesis based on input args +func getGenesis(genesisFlag *string, goerliFlag bool, rinkebyFlag bool) (*core.Genesis, error) { + switch { + case genesisFlag != nil: + var genesis core.Genesis + err := common.LoadJSON(*genesisFlag, &genesis) + return &genesis, err + case goerliFlag: + return core.DefaultGoerliGenesisBlock(), nil + case rinkebyFlag: + return core.DefaultRinkebyGenesisBlock(), nil + default: + return nil, fmt.Errorf("no genesis flag provided") + } +} From a50251e6cbfecfaf26040d42c70d2812bc422a4a Mon Sep 17 00:00:00 2001 From: xD AKA Rapper King Of cn background diablo & revelations <33193253+r1cs@users.noreply.github.com> Date: Wed, 14 Apr 2021 18:44:32 +0800 Subject: [PATCH 455/948] eth/fetcher: avoid spurious timer events at startup (#22652) Co-authored-by: Felix Lange --- eth/fetcher/block_fetcher.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 5ea8a128d9..3177a877ed 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -331,8 +331,12 @@ func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transac // events. func (f *BlockFetcher) loop() { // Iterate the block fetching until a quit is requested - fetchTimer := time.NewTimer(0) - completeTimer := time.NewTimer(0) + var ( + fetchTimer = time.NewTimer(0) + completeTimer = time.NewTimer(0) + ) + <-fetchTimer.C // clear out the channel + <-completeTimer.C defer fetchTimer.Stop() defer completeTimer.Stop() From 7088f1e81456bdc0bbe801e266ea6ca6c15c1835 Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 15 Apr 2021 04:23:11 +0800 Subject: [PATCH 456/948] core, eth: faster snapshot generation (#22504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols: persist received state segments * core: initial implementation * core/state/snapshot: add tests * core, eth: updates * eth/protocols/snapshot: count flat state size * core/state: add metrics * core/state/snapshot: skip unnecessary deletion * core/state/snapshot: rename * core/state/snapshot: use the global batch * core/state/snapshot: add logs and fix wiping * core/state/snapshot: fix * core/state/snapshot: save generation progress even if the batch is empty * core/state/snapshot: fixes * core/state/snapshot: fix initial account range length * core/state/snapshot: fix initial account range * eth/protocols/snap: store flat states during the healing * eth/protocols/snap: print logs * core/state/snapshot: refactor (#4) * core/state/snapshot: refactor * core/state/snapshot: tiny fix and polish Co-authored-by: rjl493456442 * core, eth: fixes * core, eth: fix healing writer * core, trie, eth: fix paths * eth/protocols/snap: fix encoding * eth, core: add debug log * core/state/generate: release iterator asap (#5) core/state/snapshot: less copy core/state/snapshot: revert split loop core/state/snapshot: handle storage becoming empty, improve test robustness core/state: test modified codehash core/state/snapshot: polish * core/state/snapshot: optimize stats counter * core, eth: add metric * core/state/snapshot: update comments * core/state/snapshot: improve tests * core/state/snapshot: replace secure trie with standard trie * core/state/snapshot: wrap return as the struct * core/state/snapshot: skip wiping correct states * core/state/snapshot: updates * core/state/snapshot: fixes * core/state/snapshot: fix panic due to reference flaw in closure * core/state/snapshot: fix errors in state generation logic + fix log output * core/state/snapshot: remove an error case * core/state/snapshot: fix condition-check for exhausted snap state * core/state/snapshot: use stackTrie for small tries * core/state/snapshot: don't resolve small storage tries in vain * core/state/snapshot: properly clean up storage of deleted accounts * core/state/snapshot: avoid RLP-encoding in some cases + minor nitpicks * core/state/snapshot: fix error (+testcase) * core/state/snapshot: clean up tests a bit * core/state/snapshot: work in progress on better tests * core/state/snapshot: polish code * core/state/snapshot: fix trie iteration abortion trigger * core/state/snapshot: fixes flaws * core/state/snapshot: remove panic * core/state/snapshot: fix abort * core/state/snapshot: more tests (plus failing testcase) * core/state/snapshot: more testcases + fix for failing test * core/state/snapshot: testcase for malformed data * core/state/snapshot: some test nitpicks * core/state/snapshot: improvements to logging * core/state/snapshot: testcase to demo error in abortion * core/state/snapshot: fix abortion * cmd/geth: make verify-state report the root * trie: fix failing test * core/state/snapshot: add timer metrics * core/state/snapshot: fix metrics * core/state/snapshot: udpate tests * eth/protocols/snap: write snapshot account even if code or state is needed * core/state/snapshot: fix diskmore check * core/state/snapshot: review fixes * core/state/snapshot: improve error message * cmd/geth: rename 'error' to 'err' in logs * core/state/snapshot: fix some review concerns * core/state/snapshot, eth/protocols/snap: clear snapshot marker when starting/resuming snap sync * core: add error log * core/state/snapshot: use proper timers for metrics collection * core/state/snapshot: address some review concerns * eth/protocols/snap: improved log message * eth/protocols/snap: fix heal logs to condense infos * core/state/snapshot: wait for generator termination before restarting * core/state/snapshot: revert timers to counters to track total time Co-authored-by: Martin Holst Swende Co-authored-by: Péter Szilágyi --- cmd/geth/snapshot.go | 38 +- core/state/snapshot/conversion.go | 2 +- core/state/snapshot/generate.go | 679 +++++++++++++++++++++------ core/state/snapshot/generate_test.go | 649 ++++++++++++++++++++++++- core/state/snapshot/journal.go | 15 +- core/state/snapshot/snapshot.go | 10 +- core/state/snapshot/wipe.go | 32 +- core/state/statedb.go | 2 +- core/state/sync.go | 24 +- core/state/sync_test.go | 12 +- eth/downloader/statesync.go | 2 +- eth/protocols/snap/sync.go | 81 +++- trie/committer.go | 4 +- trie/sync.go | 9 +- trie/trie.go | 17 +- trie/trie_test.go | 2 +- 16 files changed, 1369 insertions(+), 209 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index e8f6a35438..1af458af20 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -155,7 +155,7 @@ func pruneState(ctx *cli.Context) error { chaindb := utils.MakeChainDatabase(ctx, stack, false) pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) if err != nil { - log.Error("Failed to open snapshot tree", "error", err) + log.Error("Failed to open snapshot tree", "err", err) return err } if ctx.NArg() > 1 { @@ -166,12 +166,12 @@ func pruneState(ctx *cli.Context) error { if ctx.NArg() == 1 { targetRoot, err = parseRoot(ctx.Args()[0]) if err != nil { - log.Error("Failed to resolve state root", "error", err) + log.Error("Failed to resolve state root", "err", err) return err } } if err = pruner.Prune(targetRoot); err != nil { - log.Error("Failed to prune state", "error", err) + log.Error("Failed to prune state", "err", err) return err } return nil @@ -189,7 +189,7 @@ func verifyState(ctx *cli.Context) error { } snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false) if err != nil { - log.Error("Failed to open snapshot tree", "error", err) + log.Error("Failed to open snapshot tree", "err", err) return err } if ctx.NArg() > 1 { @@ -200,15 +200,15 @@ func verifyState(ctx *cli.Context) error { if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args()[0]) if err != nil { - log.Error("Failed to resolve state root", "error", err) + log.Error("Failed to resolve state root", "err", err) return err } } if err := snaptree.Verify(root); err != nil { - log.Error("Failed to verfiy state", "error", err) + log.Error("Failed to verfiy state", "root", root, "err", err) return err } - log.Info("Verified the state") + log.Info("Verified the state", "root", root) return nil } @@ -236,7 +236,7 @@ func traverseState(ctx *cli.Context) error { if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args()[0]) if err != nil { - log.Error("Failed to resolve state root", "error", err) + log.Error("Failed to resolve state root", "err", err) return err } log.Info("Start traversing the state", "root", root) @@ -247,7 +247,7 @@ func traverseState(ctx *cli.Context) error { triedb := trie.NewDatabase(chaindb) t, err := trie.NewSecure(root, triedb) if err != nil { - log.Error("Failed to open trie", "root", root, "error", err) + log.Error("Failed to open trie", "root", root, "err", err) return err } var ( @@ -262,13 +262,13 @@ func traverseState(ctx *cli.Context) error { accounts += 1 var acc state.Account if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { - log.Error("Invalid account encountered during traversal", "error", err) + log.Error("Invalid account encountered during traversal", "err", err) return err } if acc.Root != emptyRoot { storageTrie, err := trie.NewSecure(acc.Root, triedb) if err != nil { - log.Error("Failed to open storage trie", "root", acc.Root, "error", err) + log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return err } storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) @@ -276,7 +276,7 @@ func traverseState(ctx *cli.Context) error { slots += 1 } if storageIter.Err != nil { - log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Err) + log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) return storageIter.Err } } @@ -294,7 +294,7 @@ func traverseState(ctx *cli.Context) error { } } if accIter.Err != nil { - log.Error("Failed to traverse state trie", "root", root, "error", accIter.Err) + log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) return accIter.Err } log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) @@ -326,7 +326,7 @@ func traverseRawState(ctx *cli.Context) error { if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args()[0]) if err != nil { - log.Error("Failed to resolve state root", "error", err) + log.Error("Failed to resolve state root", "err", err) return err } log.Info("Start traversing the state", "root", root) @@ -337,7 +337,7 @@ func traverseRawState(ctx *cli.Context) error { triedb := trie.NewDatabase(chaindb) t, err := trie.NewSecure(root, triedb) if err != nil { - log.Error("Failed to open trie", "root", root, "error", err) + log.Error("Failed to open trie", "root", root, "err", err) return err } var ( @@ -368,13 +368,13 @@ func traverseRawState(ctx *cli.Context) error { accounts += 1 var acc state.Account if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { - log.Error("Invalid account encountered during traversal", "error", err) + log.Error("Invalid account encountered during traversal", "err", err) return errors.New("invalid account") } if acc.Root != emptyRoot { storageTrie, err := trie.NewSecure(acc.Root, triedb) if err != nil { - log.Error("Failed to open storage trie", "root", acc.Root, "error", err) + log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return errors.New("missing storage trie") } storageIter := storageTrie.NodeIterator(nil) @@ -397,7 +397,7 @@ func traverseRawState(ctx *cli.Context) error { } } if storageIter.Error() != nil { - log.Error("Failed to traverse storage trie", "root", acc.Root, "error", storageIter.Error()) + log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) return storageIter.Error() } } @@ -416,7 +416,7 @@ func traverseRawState(ctx *cli.Context) error { } } if accIter.Error() != nil { - log.Error("Failed to traverse state trie", "root", root, "error", accIter.Error()) + log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) return accIter.Error() } log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index bb87ecddf1..f70cbf1e68 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -322,7 +322,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, return } if !bytes.Equal(account.Root, subroot.Bytes()) { - results <- fmt.Errorf("invalid subroot(%x), want %x, got %x", it.Hash(), account.Root, subroot) + results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) return } results <- nil diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 2b41dd5513..98c8d42a1a 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -19,17 +19,20 @@ package snapshot import ( "bytes" "encoding/binary" + "errors" "fmt" "math/big" "time" "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -40,17 +43,63 @@ var ( // emptyCode is the known hash of the empty EVM bytecode. emptyCode = crypto.Keccak256Hash(nil) + + // accountCheckRange is the upper limit of the number of accounts involved in + // each range check. This is a value estimated based on experience. If this + // value is too large, the failure rate of range prove will increase. Otherwise + // the the value is too small, the efficiency of the state recovery will decrease. + accountCheckRange = 128 + + // storageCheckRange is the upper limit of the number of storage slots involved + // in each range check. This is a value estimated based on experience. If this + // value is too large, the failure rate of range prove will increase. Otherwise + // the the value is too small, the efficiency of the state recovery will decrease. + storageCheckRange = 1024 + + // errMissingTrie is returned if the target trie is missing while the generation + // is running. In this case the generation is aborted and wait the new signal. + errMissingTrie = errors.New("missing trie") +) + +// Metrics in generation +var ( + snapGeneratedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/generated", nil) + snapRecoveredAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/recovered", nil) + snapWipedAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/wiped", nil) + snapMissallAccountMeter = metrics.NewRegisteredMeter("state/snapshot/generation/account/missall", nil) + snapGeneratedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/generated", nil) + snapRecoveredStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/recovered", nil) + snapWipedStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/wiped", nil) + snapMissallStorageMeter = metrics.NewRegisteredMeter("state/snapshot/generation/storage/missall", nil) + snapSuccessfulRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/success", nil) + snapFailedRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/failure", nil) + + // snapAccountProveCounter measures time spent on the account proving + snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil) + // snapAccountTrieReadCounter measures time spent on the account trie iteration + snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil) + // snapAccountSnapReadCounter measues time spent on the snapshot account iteration + snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil) + // snapAccountWriteCounter measures time spent on writing/updating/deleting accounts + snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil) + // snapStorageProveCounter measures time spent on storage proving + snapStorageProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/prove", nil) + // snapStorageTrieReadCounter measures time spent on the storage trie iteration + snapStorageTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/trieread", nil) + // snapStorageSnapReadCounter measures time spent on the snapshot storage iteration + snapStorageSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/snapread", nil) + // snapStorageWriteCounter measures time spent on writing/updating/deleting storages + snapStorageWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/write", nil) ) // generatorStats is a collection of statistics gathered by the snapshot generator // for logging purposes. type generatorStats struct { - wiping chan struct{} // Notification channel if wiping is in progress origin uint64 // Origin prefix where generation started start time.Time // Timestamp when generation started - accounts uint64 // Number of accounts indexed - slots uint64 // Number of storage slots indexed - storage common.StorageSize // Account and storage slot size + accounts uint64 // Number of accounts indexed(generated or recovered) + slots uint64 // Number of storage slots indexed(generated or recovered) + storage common.StorageSize // Total account and storage slot size(generation or recovery) } // Log creates an contextual log with the given message and the context pulled @@ -91,25 +140,30 @@ func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) { log.Info(msg, ctx...) } +// ClearSnapshotMarker sets the snapshot marker to zero, meaning that snapshots +// are not usable. +func ClearSnapshotMarker(diskdb ethdb.KeyValueStore) { + batch := diskdb.NewBatch() + journalProgress(batch, []byte{}, nil) + if err := batch.Write(); err != nil { + log.Crit("Failed to write initialized state marker", "err", err) + } +} + // generateSnapshot regenerates a brand new snapshot based on an existing state // database and head block asynchronously. The snapshot is returned immediately // and generation is continued in the background until done. -func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, wiper chan struct{}) *diskLayer { - // Wipe any previously existing snapshot from the database if no wiper is - // currently in progress. - if wiper == nil { - wiper = wipeSnapshot(diskdb, true) - } +func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) *diskLayer { // Create a new disk layer with an initialized state marker at zero var ( - stats = &generatorStats{wiping: wiper, start: time.Now()} + stats = &generatorStats{start: time.Now()} batch = diskdb.NewBatch() genMarker = []byte{} // Initialized but empty! ) rawdb.WriteSnapshotRoot(batch, root) journalProgress(batch, genMarker, stats) if err := batch.Write(); err != nil { - log.Crit("Failed to write initialized state marker", "error", err) + log.Crit("Failed to write initialized state marker", "err", err) } base := &diskLayer{ diskdb: diskdb, @@ -135,7 +189,6 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta Marker: marker, } if stats != nil { - entry.Wiping = (stats.wiping != nil) entry.Accounts = stats.accounts entry.Slots = stats.slots entry.Storage = uint64(stats.storage) @@ -159,169 +212,521 @@ func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorSta rawdb.WriteSnapshotGenerator(db, blob) } -// generate is a background thread that iterates over the state and storage tries, -// constructing the state snapshot. All the arguments are purely for statistics -// gathering and logging, since the method surfs the blocks as they arrive, often -// being restarted. -func (dl *diskLayer) generate(stats *generatorStats) { - // If a database wipe is in operation, wait until it's done - if stats.wiping != nil { - stats.Log("Wiper running, state snapshotting paused", common.Hash{}, dl.genMarker) - select { - // If wiper is done, resume normal mode of operation - case <-stats.wiping: - stats.wiping = nil - stats.start = time.Now() +// proofResult contains the output of range proving which can be used +// for further processing regardless if it is successful or not. +type proofResult struct { + keys [][]byte // The key set of all elements being iterated, even proving is failed + vals [][]byte // The val set of all elements being iterated, even proving is failed + diskMore bool // Set when the database has extra snapshot states since last iteration + trieMore bool // Set when the trie has extra snapshot states(only meaningful for successful proving) + proofErr error // Indicator whether the given state range is valid or not + tr *trie.Trie // The trie, in case the trie was resolved by the prover (may be nil) +} - // If generator was aborted during wipe, return - case abort := <-dl.genAbort: - abort <- stats - return +// valid returns the indicator that range proof is successful or not. +func (result *proofResult) valid() bool { + return result.proofErr == nil +} + +// last returns the last verified element key regardless of whether the range proof is +// successful or not. Nil is returned if nothing involved in the proving. +func (result *proofResult) last() []byte { + var last []byte + if len(result.keys) > 0 { + last = result.keys[len(result.keys)-1] + } + return last +} + +// forEach iterates all the visited elements and applies the given callback on them. +// The iteration is aborted if the callback returns non-nil error. +func (result *proofResult) forEach(callback func(key []byte, val []byte) error) error { + for i := 0; i < len(result.keys); i++ { + key, val := result.keys[i], result.vals[i] + if err := callback(key, val); err != nil { + return err + } + } + return nil +} + +// proveRange proves the snapshot segment with particular prefix is "valid". +// The iteration start point will be assigned if the iterator is restored from +// the last interruption. Max will be assigned in order to limit the maximum +// amount of data involved in each iteration. +// +// The proof result will be returned if the range proving is finished, otherwise +// the error will be returned to abort the entire procedure. +func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) { + var ( + keys [][]byte + vals [][]byte + proof = rawdb.NewMemoryDatabase() + diskMore = false + ) + iter := dl.diskdb.NewIterator(prefix, origin) + defer iter.Release() + + var start = time.Now() + for iter.Next() { + key := iter.Key() + if len(key) != len(prefix)+common.HashLength { + continue + } + if len(keys) == max { + // Break if we've reached the max size, and signal that we're not + // done yet. + diskMore = true + break + } + keys = append(keys, common.CopyBytes(key[len(prefix):])) + + if valueConvertFn == nil { + vals = append(vals, common.CopyBytes(iter.Value())) + } else { + val, err := valueConvertFn(iter.Value()) + if err != nil { + // Special case, the state data is corrupted (invalid slim-format account), + // don't abort the entire procedure directly. Instead, let the fallback + // generation to heal the invalid data. + // + // Here append the original value to ensure that the number of key and + // value are the same. + vals = append(vals, common.CopyBytes(iter.Value())) + log.Error("Failed to convert account state data", "err", err) + } else { + vals = append(vals, val) + } + } + } + // Update metrics for database iteration and merkle proving + if kind == "storage" { + snapStorageSnapReadCounter.Inc(time.Since(start).Nanoseconds()) + } else { + snapAccountSnapReadCounter.Inc(time.Since(start).Nanoseconds()) + } + defer func(start time.Time) { + if kind == "storage" { + snapStorageProveCounter.Inc(time.Since(start).Nanoseconds()) + } else { + snapAccountProveCounter.Inc(time.Since(start).Nanoseconds()) + } + }(time.Now()) + + // The snap state is exhausted, pass the entire key/val set for verification + if origin == nil && !diskMore { + stackTr := trie.NewStackTrie(nil) + for i, key := range keys { + stackTr.TryUpdate(key, common.CopyBytes(vals[i])) + } + if gotRoot := stackTr.Hash(); gotRoot != root { + return &proofResult{ + keys: keys, + vals: vals, + proofErr: fmt.Errorf("wrong root: have %#x want %#x", gotRoot, root), + }, nil } + return &proofResult{keys: keys, vals: vals}, nil } - // Create an account and state iterator pointing to the current generator marker - accTrie, err := trie.NewSecure(dl.root, dl.triedb) + // Snap state is chunked, generate edge proofs for verification. + tr, err := trie.New(root, dl.triedb) if err != nil { - // The account trie is missing (GC), surf the chain until one becomes available stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) + return nil, errMissingTrie + } + // Firstly find out the key of last iterated element. + var last []byte + if len(keys) > 0 { + last = keys[len(keys)-1] + } + // Generate the Merkle proofs for the first and last element + if origin == nil { + origin = common.Hash{}.Bytes() + } + if err := tr.Prove(origin, 0, proof); err != nil { + log.Debug("Failed to prove range", "kind", kind, "origin", origin, "err", err) + return &proofResult{ + keys: keys, + vals: vals, + diskMore: diskMore, + proofErr: err, + tr: tr, + }, nil + } + if last != nil { + if err := tr.Prove(last, 0, proof); err != nil { + log.Debug("Failed to prove range", "kind", kind, "last", last, "err", err) + return &proofResult{ + keys: keys, + vals: vals, + diskMore: diskMore, + proofErr: err, + tr: tr, + }, nil + } + } + // Verify the snapshot segment with range prover, ensure that all flat states + // in this range correspond to merkle trie. + _, _, _, cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) + return &proofResult{ + keys: keys, + vals: vals, + diskMore: diskMore, + trieMore: cont, + proofErr: err, + tr: tr}, + nil +} - abort := <-dl.genAbort - abort <- stats - return +// onStateCallback is a function that is called by generateRange, when processing a range of +// accounts or storage slots. For each element, the callback is invoked. +// If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot. +// If 'write' is true, then this element needs to be updated with the 'val'. +// If 'write' is false, then this element is already correct, and needs no update. However, +// for accounts, the storage trie of the account needs to be checked. +// The 'val' is the canonical encoding of the value (not the slim format for accounts) +type onStateCallback func(key []byte, val []byte, write bool, delete bool) error + +// generateRange generates the state segment with particular prefix. Generation can +// either verify the correctness of existing state through rangeproof and skip +// generation, or iterate trie to regenerate state on demand. +func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, origin []byte, max int, stats *generatorStats, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) { + // Use range prover to check the validity of the flat state in the range + result, err := dl.proveRange(stats, root, prefix, kind, origin, max, valueConvertFn) + if err != nil { + return false, nil, err } - stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker) + last := result.last() + + // Construct contextual logger + logCtx := []interface{}{"kind", kind, "prefix", hexutil.Encode(prefix)} + if len(origin) > 0 { + logCtx = append(logCtx, "origin", hexutil.Encode(origin)) + } + logger := log.New(logCtx...) + + // The range prover says the range is correct, skip trie iteration + if result.valid() { + snapSuccessfulRangeProofMeter.Mark(1) + logger.Trace("Proved state range", "last", hexutil.Encode(last)) - var accMarker []byte + // The verification is passed, process each state with the given + // callback function. If this state represents a contract, the + // corresponding storage check will be performed in the callback + if err := result.forEach(func(key []byte, val []byte) error { return onState(key, val, false, false) }); err != nil { + return false, nil, err + } + // Only abort the iteration when both database and trie are exhausted + return !result.diskMore && !result.trieMore, last, nil + } + logger.Trace("Detected outdated state range", "last", hexutil.Encode(last), "err", result.proofErr) + snapFailedRangeProofMeter.Mark(1) + + // Special case, the entire trie is missing. In the original trie scheme, + // all the duplicated subtries will be filter out(only one copy of data + // will be stored). While in the snapshot model, all the storage tries + // belong to different contracts will be kept even they are duplicated. + // Track it to a certain extent remove the noise data used for statistics. + if origin == nil && last == nil { + meter := snapMissallAccountMeter + if kind == "storage" { + meter = snapMissallStorageMeter + } + meter.Mark(1) + } + tr := result.tr + if tr == nil { + tr, err = trie.New(root, dl.triedb) + if err != nil { + stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) + return false, nil, errMissingTrie + } + } + var ( + trieMore bool + iter = trie.NewIterator(tr.NodeIterator(origin)) + kvkeys, kvvals = result.keys, result.vals + + // counters + count = 0 // number of states delivered by iterator + created = 0 // states created from the trie + updated = 0 // states updated from the trie + deleted = 0 // states not in trie, but were in snapshot + untouched = 0 // states already correct + + // timers + start = time.Now() + internal time.Duration + ) + for iter.Next() { + if last != nil && bytes.Compare(iter.Key, last) > 0 { + trieMore = true + break + } + count++ + write := true + created++ + for len(kvkeys) > 0 { + if cmp := bytes.Compare(kvkeys[0], iter.Key); cmp < 0 { + // delete the key + istart := time.Now() + if err := onState(kvkeys[0], nil, false, true); err != nil { + return false, nil, err + } + kvkeys = kvkeys[1:] + kvvals = kvvals[1:] + deleted++ + internal += time.Since(istart) + continue + } else if cmp == 0 { + // the snapshot key can be overwritten + created-- + if write = !bytes.Equal(kvvals[0], iter.Value); write { + updated++ + } else { + untouched++ + } + kvkeys = kvkeys[1:] + kvvals = kvvals[1:] + } + break + } + istart := time.Now() + if err := onState(iter.Key, iter.Value, write, false); err != nil { + return false, nil, err + } + internal += time.Since(istart) + } + if iter.Err != nil { + return false, nil, iter.Err + } + // Delete all stale snapshot states remaining + istart := time.Now() + for _, key := range kvkeys { + if err := onState(key, nil, false, true); err != nil { + return false, nil, err + } + deleted += 1 + } + internal += time.Since(istart) + + // Update metrics for counting trie iteration + if kind == "storage" { + snapStorageTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) + } else { + snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds()) + } + logger.Debug("Regenerated state range", "root", root, "last", hexutil.Encode(last), + "count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted) + + // If there are either more trie items, or there are more snap items + // (in the next segment), then we need to keep working + return !trieMore && !result.diskMore, last, nil +} + +// generate is a background thread that iterates over the state and storage tries, +// constructing the state snapshot. All the arguments are purely for statistics +// gathering and logging, since the method surfs the blocks as they arrive, often +// being restarted. +func (dl *diskLayer) generate(stats *generatorStats) { + var ( + accMarker []byte + accountRange = accountCheckRange + ) if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that - accMarker = dl.genMarker[:common.HashLength] + // Always reset the initial account range as 1 + // whenever recover from the interruption. + accMarker, accountRange = dl.genMarker[:common.HashLength], 1 } - accIt := trie.NewIterator(accTrie.NodeIterator(accMarker)) - batch := dl.diskdb.NewBatch() + var ( + batch = dl.diskdb.NewBatch() + logged = time.Now() + accOrigin = common.CopyBytes(accMarker) + abort chan *generatorStats + ) + stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker) - // Iterate from the previous marker and continue generating the state snapshot - logged := time.Now() - for accIt.Next() { - // Retrieve the current account and flatten it into the internal format - accountHash := common.BytesToHash(accIt.Key) + checkAndFlush := func(currentLocation []byte) error { + select { + case abort = <-dl.genAbort: + default: + } + if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { + // Flush out the batch anyway no matter it's empty or not. + // It's possible that all the states are recovered and the + // generation indeed makes progress. + journalProgress(batch, currentLocation, stats) + + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + + dl.lock.Lock() + dl.genMarker = currentLocation + dl.lock.Unlock() + + if abort != nil { + stats.Log("Aborting state snapshot generation", dl.root, currentLocation) + return errors.New("aborted") + } + } + if time.Since(logged) > 8*time.Second { + stats.Log("Generating state snapshot", dl.root, currentLocation) + logged = time.Now() + } + return nil + } + onAccount := func(key []byte, val []byte, write bool, delete bool) error { + var ( + start = time.Now() + accountHash = common.BytesToHash(key) + ) + if delete { + rawdb.DeleteAccountSnapshot(batch, accountHash) + snapWipedAccountMeter.Mark(1) + + // Ensure that any previous snapshot storage values are cleared + prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...) + keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength + if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil { + return err + } + snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) + return nil + } + // Retrieve the current account and flatten it into the internal format var acc struct { Nonce uint64 Balance *big.Int Root common.Hash CodeHash []byte } - if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { + if err := rlp.DecodeBytes(val, &acc); err != nil { log.Crit("Invalid account encountered during snapshot creation", "err", err) } - data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) - // If the account is not yet in-progress, write it out if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { - rawdb.WriteAccountSnapshot(batch, accountHash, data) - stats.storage += common.StorageSize(1 + common.HashLength + len(data)) + dataLen := len(val) // Approximate size, saves us a round of RLP-encoding + if !write { + if bytes.Equal(acc.CodeHash, emptyCode[:]) { + dataLen -= 32 + } + if acc.Root == emptyRoot { + dataLen -= 32 + } + snapRecoveredAccountMeter.Mark(1) + } else { + data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) + dataLen = len(data) + rawdb.WriteAccountSnapshot(batch, accountHash, data) + snapGeneratedAccountMeter.Mark(1) + } + stats.storage += common.StorageSize(1 + common.HashLength + dataLen) stats.accounts++ } // If we've exceeded our batch allowance or termination was requested, flush to disk - var abort chan *generatorStats - select { - case abort = <-dl.genAbort: - default: - } - if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { - // Only write and set the marker if we actually did something useful - if batch.ValueSize() > 0 { - // Ensure the generator entry is in sync with the data - marker := accountHash[:] - journalProgress(batch, marker, stats) - - batch.Write() - batch.Reset() - - dl.lock.Lock() - dl.genMarker = marker - dl.lock.Unlock() - } - if abort != nil { - stats.Log("Aborting state snapshot generation", dl.root, accountHash[:]) - abort <- stats - return - } + if err := checkAndFlush(accountHash[:]); err != nil { + return err } - // If the account is in-progress, continue where we left off (otherwise iterate all) - if acc.Root != emptyRoot { - storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) - if err != nil { - log.Error("Generator failed to access storage trie", "root", dl.root, "account", accountHash, "stroot", acc.Root, "err", err) - abort := <-dl.genAbort - abort <- stats - return + // If the iterated account is the contract, create a further loop to + // verify or regenerate the contract storage. + if acc.Root == emptyRoot { + // If the root is empty, we still need to ensure that any previous snapshot + // storage values are cleared + // TODO: investigate if this can be avoided, this will be very costly since it + // affects every single EOA account + // - Perhaps we can avoid if where codeHash is emptyCode + prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...) + keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength + if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil { + return err } + snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) + } else { + snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds()) + var storeMarker []byte if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { storeMarker = dl.genMarker[common.HashLength:] } - storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker)) - for storeIt.Next() { - rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value) - stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) + onStorage := func(key []byte, val []byte, write bool, delete bool) error { + defer func(start time.Time) { + snapStorageWriteCounter.Inc(time.Since(start).Nanoseconds()) + }(time.Now()) + + if delete { + rawdb.DeleteStorageSnapshot(batch, accountHash, common.BytesToHash(key)) + snapWipedStorageMeter.Mark(1) + return nil + } + if write { + rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(key), val) + snapGeneratedStorageMeter.Mark(1) + } else { + snapRecoveredStorageMeter.Mark(1) + } + stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val)) stats.slots++ // If we've exceeded our batch allowance or termination was requested, flush to disk - var abort chan *generatorStats - select { - case abort = <-dl.genAbort: - default: - } - if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { - // Only write and set the marker if we actually did something useful - if batch.ValueSize() > 0 { - // Ensure the generator entry is in sync with the data - marker := append(accountHash[:], storeIt.Key...) - journalProgress(batch, marker, stats) - - batch.Write() - batch.Reset() - - dl.lock.Lock() - dl.genMarker = marker - dl.lock.Unlock() - } - if abort != nil { - stats.Log("Aborting state snapshot generation", dl.root, append(accountHash[:], storeIt.Key...)) - abort <- stats - return - } - if time.Since(logged) > 8*time.Second { - stats.Log("Generating state snapshot", dl.root, append(accountHash[:], storeIt.Key...)) - logged = time.Now() - } + if err := checkAndFlush(append(accountHash[:], key...)); err != nil { + return err } + return nil } - if err := storeIt.Err; err != nil { - log.Error("Generator failed to iterate storage trie", "accroot", dl.root, "acchash", common.BytesToHash(accIt.Key), "stroot", acc.Root, "err", err) - abort := <-dl.genAbort - abort <- stats - return + var storeOrigin = common.CopyBytes(storeMarker) + for { + exhausted, last, err := dl.generateRange(acc.Root, append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...), "storage", storeOrigin, storageCheckRange, stats, onStorage, nil) + if err != nil { + return err + } + if exhausted { + break + } + if storeOrigin = increaseKey(last); storeOrigin == nil { + break // special case, the last is 0xffffffff...fff + } } } - if time.Since(logged) > 8*time.Second { - stats.Log("Generating state snapshot", dl.root, accIt.Key) - logged = time.Now() - } // Some account processed, unmark the marker accMarker = nil + return nil } - if err := accIt.Err; err != nil { - log.Error("Generator failed to iterate account trie", "root", dl.root, "err", err) - abort := <-dl.genAbort - abort <- stats - return + + // Global loop for regerating the entire state trie + all layered storage tries. + for { + exhausted, last, err := dl.generateRange(dl.root, rawdb.SnapshotAccountPrefix, "account", accOrigin, accountRange, stats, onAccount, FullAccountRLP) + // The procedure it aborted, either by external signal or internal error + if err != nil { + if abort == nil { // aborted by internal error, wait the signal + abort = <-dl.genAbort + } + abort <- stats + return + } + // Abort the procedure if the entire snapshot is generated + if exhausted { + break + } + if accOrigin = increaseKey(last); accOrigin == nil { + break // special case, the last is 0xffffffff...fff + } + accountRange = accountCheckRange } // Snapshot fully generated, set the marker to nil. // Note even there is nothing to commit, persist the // generator anyway to mark the snapshot is complete. journalProgress(batch, nil, stats) - batch.Write() + if err := batch.Write(); err != nil { + log.Error("Failed to flush batch", "err", err) + + abort = <-dl.genAbort + abort <- stats + return + } + batch.Reset() log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, "storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) @@ -332,6 +737,18 @@ func (dl *diskLayer) generate(stats *generatorStats) { dl.lock.Unlock() // Someone will be looking for us, wait it out - abort := <-dl.genAbort + abort = <-dl.genAbort abort <- nil } + +// increaseKey increase the input key by one bit. Return nil if the entire +// addition operation overflows, +func increaseKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]++ + if key[i] != 0x0 { + return key + } + } + return nil +} diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 03263f3976..3a669085f7 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -17,16 +17,361 @@ package snapshot import ( + "fmt" "math/big" + "os" "testing" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" ) +// Tests that snapshot generation from an empty database. +func TestGeneration(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // two of which also has the same 3-slot storage trie attached. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 + stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 + stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 + stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + + acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + root, _ := accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + triedb.Commit(root, false, nil) + + if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want { + t.Fatalf("have %#x want %#x", have, want) + } + snap := generateSnapshot(diskdb, triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +func hashData(input []byte) common.Hash { + var hasher = sha3.NewLegacyKeccak256() + var hash common.Hash + hasher.Reset() + hasher.Write(input) + hasher.Sum(hash[:0]) + return hash +} + +// Tests that snapshot generation with existent flat state. +func TestGenerateExistentState(t *testing.T) { + // We can't use statedb to make a test trie (circular dependency), so make + // a fake one manually. We're going with a small account trie of 3 accounts, + // two of which also has the same 3-slot storage trie attached. + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0 + stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371 + stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78 + stTrie.Commit(nil) // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-1")), val) + rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-3")), []byte("val-3")) + + acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + diskdb.Put(hashData([]byte("acc-2")).Bytes(), val) + rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-2")), val) + + acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ = rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-3")), val) + rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-3")), []byte("val-3")) + + root, _ := accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd + triedb.Commit(root, false, nil) + + snap := generateSnapshot(diskdb, triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) { + t.Helper() + accIt := snap.AccountIterator(common.Hash{}) + defer accIt.Release() + snapRoot, err := generateTrieRoot(nil, accIt, common.Hash{}, stackTrieGenerate, + func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + storageIt, _ := snap.StorageIterator(accountHash, common.Hash{}) + defer storageIt.Release() + + hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + + if err != nil { + t.Fatal(err) + } + if snapRoot != trieRoot { + t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot) + } +} + +type testHelper struct { + diskdb *memorydb.Database + triedb *trie.Database + accTrie *trie.SecureTrie +} + +func newHelper() *testHelper { + diskdb := memorydb.New() + triedb := trie.NewDatabase(diskdb) + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + return &testHelper{ + diskdb: diskdb, + triedb: triedb, + accTrie: accTrie, + } +} + +func (t *testHelper) addTrieAccount(acckey string, acc *Account) { + val, _ := rlp.EncodeToBytes(acc) + t.accTrie.Update([]byte(acckey), val) +} + +func (t *testHelper) addSnapAccount(acckey string, acc *Account) { + val, _ := rlp.EncodeToBytes(acc) + key := hashData([]byte(acckey)) + rawdb.WriteAccountSnapshot(t.diskdb, key, val) +} + +func (t *testHelper) addAccount(acckey string, acc *Account) { + t.addTrieAccount(acckey, acc) + t.addSnapAccount(acckey, acc) +} + +func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) { + accHash := hashData([]byte(accKey)) + for i, key := range keys { + rawdb.WriteStorageSnapshot(t.diskdb, accHash, hashData([]byte(key)), []byte(vals[i])) + } +} + +func (t *testHelper) makeStorageTrie(keys []string, vals []string) []byte { + stTrie, _ := trie.NewSecure(common.Hash{}, t.triedb) + for i, k := range keys { + stTrie.Update([]byte(k), []byte(vals[i])) + } + root, _ := stTrie.Commit(nil) + return root.Bytes() +} + +func (t *testHelper) Generate() (common.Hash, *diskLayer) { + root, _ := t.accTrie.Commit(nil) + t.triedb.Commit(root, false, nil) + snap := generateSnapshot(t.diskdb, t.triedb, 16, root) + return root, snap +} + +// Tests that snapshot generation with existent flat state, where the flat state +// contains some errors: +// - the contract with empty storage root but has storage entries in the disk +// - the contract with non empty storage root but empty storage slots +// - the contract(non-empty storage) misses some storage slots +// - miss in the beginning +// - miss in the middle +// - miss in the end +// - the contract(non-empty storage) has wrong storage slots +// - wrong slots in the beginning +// - wrong slots in the middle +// - wrong slots in the end +// - the contract(non-empty storage) has extra storage slots +// - extra slots in the beginning +// - extra slots in the middle +// - extra slots in the end +func TestGenerateExistentStateWithWrongStorage(t *testing.T) { + helper := newHelper() + stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Account one, empty root but non-empty database + helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Account two, non empty root but empty database + helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + + // Miss slots + { + // Account three, non empty root but misses slots in the beginning + helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"}) + + // Account four, non empty root but misses slots in the middle + helper.addAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"}) + + // Account five, non empty root but misses slots in the end + helper.addAccount("acc-5", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"}) + } + + // Wrong storage slots + { + // Account six, non empty root but wrong slots in the beginning + helper.addAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"}) + + // Account seven, non empty root but wrong slots in the middle + helper.addAccount("acc-7", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"}) + + // Account eight, non empty root but wrong slots in the end + helper.addAccount("acc-8", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"}) + + // Account 9, non empty root but rotated slots + helper.addAccount("acc-9", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"}) + } + + // Extra storage slots + { + // Account 10, non empty root but extra slots in the beginning + helper.addAccount("acc-10", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"}) + + // Account 11, non empty root but extra slots in the middle + helper.addAccount("acc-11", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"}) + + // Account 12, non empty root but extra slots in the end + helper.addAccount("acc-12", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"}) + } + + root, snap := helper.Generate() + t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0 + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation with existent flat state, where the flat state +// contains some errors: +// - miss accounts +// - wrong accounts +// - extra accounts +func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { + helper := newHelper() + stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + + // Trie accounts [acc-1, acc-2, acc-3, acc-4, acc-6] + // Extra accounts [acc-0, acc-5, acc-7] + + // Missing accounts, only in the trie + { + helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // Beginning + helper.addTrieAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // Middle + helper.addTrieAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // End + } + + // Wrong accounts + { + helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")}) + + helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}) + } + + // Extra accounts, only in the snap + { + helper.addSnapAccount("acc-0", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyRoot.Bytes()}) // before the beginning + helper.addSnapAccount("acc-5", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: common.Hex2Bytes("0x1234")}) // Middle + helper.addSnapAccount("acc-7", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyRoot.Bytes()}) // after the end + } + + root, snap := helper.Generate() + t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8 + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + // Tests that snapshot generation errors out correctly in case of a missing trie // node in the account trie. func TestGenerateCorruptAccountTrie(t *testing.T) { @@ -55,7 +400,7 @@ func TestGenerateCorruptAccountTrie(t *testing.T) { triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil) diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes()) - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), nil) + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978")) select { case <-snap.genPending: // Snapshot generation succeeded @@ -115,7 +460,7 @@ func TestGenerateMissingStorageTrie(t *testing.T) { // Delete a storage trie root and ensure the generator chokes diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes()) - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd")) select { case <-snap.genPending: // Snapshot generation succeeded @@ -174,7 +519,7 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { // Delete a storage trie leaf and ensure the generator chokes diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes()) - snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), nil) + snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd")) select { case <-snap.genPending: // Snapshot generation succeeded @@ -188,3 +533,301 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { snap.genAbort <- stop <-stop } + +func getStorageTrie(n int, triedb *trie.Database) *trie.SecureTrie { + stTrie, _ := trie.NewSecure(common.Hash{}, triedb) + for i := 0; i < n; i++ { + k := fmt.Sprintf("key-%d", i) + v := fmt.Sprintf("val-%d", i) + stTrie.Update([]byte(k), []byte(v)) + } + stTrie.Commit(nil) + return stTrie +} + +// Tests that snapshot generation when an extra account with storage exists in the snap state. +func TestGenerateWithExtraAccounts(t *testing.T) { + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + stTrie = getStorageTrie(5, triedb) + ) + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + { // Account one in the trie + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + // Identical in the snap + key := hashData([]byte("acc-1")) + rawdb.WriteAccountSnapshot(diskdb, key, val) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-4")), []byte("val-4")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-5")), []byte("val-5")) + } + { // Account two exists only in the snapshot + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + key := hashData([]byte("acc-2")) + rawdb.WriteAccountSnapshot(diskdb, key, val) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3")) + } + root, _ := accTrie.Commit(nil) + t.Logf("root: %x", root) + triedb.Commit(root, false, nil) + // To verify the test: If we now inspect the snap db, there should exist extraneous storage items + if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil { + t.Fatalf("expected snap storage to exist") + } + + snap := generateSnapshot(diskdb, triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop + // If we now inspect the snap db, there should exist no extraneous storage items + if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { + t.Fatalf("expected slot to be removed, got %v", string(data)) + } +} + +func enableLogging() { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) +} + +// Tests that snapshot generation when an extra account with storage exists in the snap state. +func TestGenerateWithManyExtraAccounts(t *testing.T) { + if false { + enableLogging() + } + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + stTrie = getStorageTrie(3, triedb) + ) + accTrie, _ := trie.NewSecure(common.Hash{}, triedb) + { // Account one in the trie + acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + // Identical in the snap + key := hashData([]byte("acc-1")) + rawdb.WriteAccountSnapshot(diskdb, key, val) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2")) + rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3")) + } + { // 100 accounts exist only in snapshot + for i := 0; i < 1000; i++ { + //acc := &Account{Balance: big.NewInt(int64(i)), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()} + acc := &Account{Balance: big.NewInt(int64(i)), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + key := hashData([]byte(fmt.Sprintf("acc-%d", i))) + rawdb.WriteAccountSnapshot(diskdb, key, val) + } + } + root, _ := accTrie.Commit(nil) + t.Logf("root: %x", root) + triedb.Commit(root, false, nil) + + snap := generateSnapshot(diskdb, triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests this case +// maxAccountRange 3 +// snapshot-accounts: 01, 02, 03, 04, 05, 06, 07 +// trie-accounts: 03, 07 +// +// We iterate three snapshot storage slots (max = 3) from the database. They are 0x01, 0x02, 0x03. +// The trie has a lot of deletions. +// So in trie, we iterate 2 entries 0x03, 0x07. We create the 0x07 in the database and abort the procedure, because the trie is exhausted. +// But in the database, we still have the stale storage slots 0x04, 0x05. They are not iterated yet, but the procedure is finished. +func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { + accountCheckRange = 3 + if false { + enableLogging() + } + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + accTrie, _ := trie.New(common.Hash{}, triedb) + { + acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update(common.HexToHash("0x03").Bytes(), val) + accTrie.Update(common.HexToHash("0x07").Bytes(), val) + + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x01"), val) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), val) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), val) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), val) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), val) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x06"), val) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x07"), val) + } + + root, _ := accTrie.Commit(nil) + t.Logf("root: %x", root) + triedb.Commit(root, false, nil) + + snap := generateSnapshot(diskdb, triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// TestGenerateWithMalformedSnapdata tests what happes if we have some junk +// in the snapshot database, which cannot be parsed back to an account +func TestGenerateWithMalformedSnapdata(t *testing.T) { + accountCheckRange = 3 + if false { + enableLogging() + } + var ( + diskdb = memorydb.New() + triedb = trie.NewDatabase(diskdb) + ) + accTrie, _ := trie.New(common.Hash{}, triedb) + { + acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()} + val, _ := rlp.EncodeToBytes(acc) + accTrie.Update(common.HexToHash("0x03").Bytes(), val) + + junk := make([]byte, 100) + copy(junk, []byte{0xde, 0xad}) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), junk) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), junk) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), junk) + rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), junk) + } + + root, _ := accTrie.Commit(nil) + t.Logf("root: %x", root) + triedb.Commit(root, false, nil) + + snap := generateSnapshot(diskdb, triedb, 16, root) + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop + // If we now inspect the snap db, there should exist no extraneous storage items + if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil { + t.Fatalf("expected slot to be removed, got %v", string(data)) + } +} + +func TestGenerateFromEmptySnap(t *testing.T) { + //enableLogging() + accountCheckRange = 10 + storageCheckRange = 20 + helper := newHelper() + stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) + // Add 1K accounts to the trie + for i := 0; i < 400; i++ { + helper.addTrieAccount(fmt.Sprintf("acc-%d", i), + &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) + } + root, snap := helper.Generate() + t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4 + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(1 * time.Second): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} + +// Tests that snapshot generation with existent flat state, where the flat state +// storage is correct, but incomplete. +// The incomplete part is on the second range +// snap: [ 0x01, 0x02, 0x03, 0x04] , [ 0x05, 0x06, 0x07, {missing}] (with storageCheck = 4) +// trie: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 +// This hits a case where the snap verification passes, but there are more elements in the trie +// which we must also add. +func TestGenerateWithIncompleteStorage(t *testing.T) { + storageCheckRange = 4 + helper := newHelper() + stKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8"} + stVals := []string{"v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"} + stRoot := helper.makeStorageTrie(stKeys, stVals) + // We add 8 accounts, each one is missing exactly one of the storage slots. This means + // we don't have to order the keys and figure out exactly which hash-key winds up + // on the sensitive spots at the boundaries + for i := 0; i < 8; i++ { + accKey := fmt.Sprintf("acc-%d", i) + helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: emptyCode.Bytes()}) + var moddedKeys []string + var moddedVals []string + for ii := 0; ii < 8; ii++ { + if ii != i { + moddedKeys = append(moddedKeys, stKeys[ii]) + moddedVals = append(moddedVals, stVals[ii]) + } + } + helper.addSnapStorage(accKey, moddedKeys, moddedVals) + } + + root, snap := helper.Generate() + t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff + + select { + case <-snap.genPending: + // Snapshot generation succeeded + + case <-time.After(250 * time.Millisecond): + t.Errorf("Snapshot generation failed") + } + checkSnapRoot(t, snap, root) + // Signal abortion to the generator and wait for it to tear down + stop := make(chan *generatorStats) + snap.genAbort <- stop + <-stop +} diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index d7e454cceb..b31e921ca9 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -37,7 +37,10 @@ const journalVersion uint64 = 0 // journalGenerator is a disk layer entry containing the generator progress marker. type journalGenerator struct { - Wiping bool // Whether the database was in progress of being wiped + // Indicator that whether the database was in progress of being wiped. + // It's deprecated but keep it here for background compatibility. + Wiping bool + Done bool // Whether the generator finished creating the snapshot Marker []byte Accounts uint64 @@ -193,14 +196,6 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, } // Everything loaded correctly, resume any suspended operations if !generator.Done { - // If the generator was still wiping, restart one from scratch (fine for - // now as it's rare and the wiper deletes the stuff it touches anyway, so - // restarting won't incur a lot of extra database hops. - var wiper chan struct{} - if generator.Wiping { - log.Info("Resuming previous snapshot wipe") - wiper = wipeSnapshot(diskdb, false) - } // Whether or not wiping was in progress, load any generator progress too base.genMarker = generator.Marker if base.genMarker == nil { @@ -214,7 +209,6 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, origin = binary.BigEndian.Uint64(generator.Marker) } go base.generate(&generatorStats{ - wiping: wiper, origin: origin, start: time.Now(), accounts: generator.Accounts, @@ -381,7 +375,6 @@ func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { Marker: dl.genMarker, } if stats != nil { - entry.Wiping = (stats.wiping != nil) entry.Accounts = stats.accounts entry.Slots = stats.slots entry.Storage = uint64(stats.storage) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index eccf377264..710ba4d4c2 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -656,9 +656,6 @@ func (t *Tree) Rebuild(root common.Hash) { // building a brand new snapshot. rawdb.DeleteSnapshotRecoveryNumber(t.diskdb) - // Track whether there's a wipe currently running and keep it alive if so - var wiper chan struct{} - // Iterate over and mark all layers stale for _, layer := range t.layers { switch layer := layer.(type) { @@ -667,10 +664,7 @@ func (t *Tree) Rebuild(root common.Hash) { if layer.genAbort != nil { abort := make(chan *generatorStats) layer.genAbort <- abort - - if stats := <-abort; stats != nil { - wiper = stats.wiping - } + <-abort } // Layer should be inactive now, mark it as stale layer.lock.Lock() @@ -691,7 +685,7 @@ func (t *Tree) Rebuild(root common.Hash) { // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot") t.layers = map[common.Hash]snapshot{ - root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper), + root: generateSnapshot(t.diskdb, t.triedb, t.cache, root), } } diff --git a/core/state/snapshot/wipe.go b/core/state/snapshot/wipe.go index 14b63031a5..2cab57393b 100644 --- a/core/state/snapshot/wipe.go +++ b/core/state/snapshot/wipe.go @@ -24,10 +24,11 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" ) // wipeSnapshot starts a goroutine to iterate over the entire key-value database -// and delete all the data associated with the snapshot (accounts, storage, +// and delete all the data associated with the snapshot (accounts, storage, // metadata). After all is done, the snapshot range of the database is compacted // to free up unused data blocks. func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} { @@ -53,10 +54,10 @@ func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} { // removed in sync to avoid data races. After all is done, the snapshot range of // the database is compacted to free up unused data blocks. func wipeContent(db ethdb.KeyValueStore) error { - if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, len(rawdb.SnapshotAccountPrefix)+common.HashLength); err != nil { + if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, nil, nil, len(rawdb.SnapshotAccountPrefix)+common.HashLength, snapWipedAccountMeter, true); err != nil { return err } - if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength); err != nil { + if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, nil, nil, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength, snapWipedStorageMeter, true); err != nil { return err } // Compact the snapshot section of the database to get rid of unused space @@ -82,8 +83,11 @@ func wipeContent(db ethdb.KeyValueStore) error { } // wipeKeyRange deletes a range of keys from the database starting with prefix -// and having a specific total key length. -func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int) error { +// and having a specific total key length. The start and limit is optional for +// specifying a particular key range for deletion. +// +// Origin is included for wiping and limit is excluded if they are specified. +func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, origin []byte, limit []byte, keylen int, meter metrics.Meter, report bool) error { // Batch deletions together to avoid holding an iterator for too long var ( batch = db.NewBatch() @@ -92,7 +96,11 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int // Iterate over the key-range and delete all of them start, logged := time.Now(), time.Now() - it := db.NewIterator(prefix, nil) + it := db.NewIterator(prefix, origin) + var stop []byte + if limit != nil { + stop = append(prefix, limit...) + } for it.Next() { // Skip any keys with the correct prefix but wrong length (trie nodes) key := it.Key() @@ -102,6 +110,9 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int if len(key) != keylen { continue } + if stop != nil && bytes.Compare(key, stop) >= 0 { + break + } // Delete the key and periodically recreate the batch and iterator batch.Delete(key) items++ @@ -116,7 +127,7 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int seekPos := key[len(prefix):] it = db.NewIterator(prefix, seekPos) - if time.Since(logged) > 8*time.Second { + if time.Since(logged) > 8*time.Second && report { log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) logged = time.Now() } @@ -126,6 +137,11 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int if err := batch.Write(); err != nil { return err } - log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) + if meter != nil { + meter.Mark(int64(items)) + } + if report { + log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) + } return nil } diff --git a/core/state/statedb.go b/core/state/statedb.go index 2e5d6e47c8..90f4709bfc 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -948,7 +948,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { // The onleaf func is called _serially_, so we can reuse the same account // for unmarshalling every time. var account Account - root, err := s.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error { + root, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error { if err := rlp.DecodeBytes(leaf, &account); err != nil { return nil } diff --git a/core/state/sync.go b/core/state/sync.go index 1018b78e5e..7a5852fb19 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -26,17 +26,31 @@ import ( ) // NewStateSync create a new state trie download scheduler. -func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom) *trie.Sync { +func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom, onLeaf func(paths [][]byte, leaf []byte) error) *trie.Sync { + // Register the storage slot callback if the external callback is specified. + var onSlot func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error + if onLeaf != nil { + onSlot = func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error { + return onLeaf(paths, leaf) + } + } + // Register the account callback to connect the state trie and the storage + // trie belongs to the contract. var syncer *trie.Sync - callback := func(path []byte, leaf []byte, parent common.Hash) error { + onAccount := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error { + if onLeaf != nil { + if err := onLeaf(paths, leaf); err != nil { + return err + } + } var obj Account if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil { return err } - syncer.AddSubTrie(obj.Root, path, parent, nil) - syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), path, parent) + syncer.AddSubTrie(obj.Root, hexpath, parent, onSlot) + syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), hexpath, parent) return nil } - syncer = trie.NewSync(root, database, callback, bloom) + syncer = trie.NewSync(root, database, onAccount, bloom) return syncer } diff --git a/core/state/sync_test.go b/core/state/sync_test.go index 9c4867093d..a13fcf56a3 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -133,7 +133,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { // Tests that an empty state is not scheduled for syncing. func TestEmptyStateSync(t *testing.T) { empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") - sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New())) + sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New()), nil) if nodes, paths, codes := sync.Missing(1); len(nodes) != 0 || len(paths) != 0 || len(codes) != 0 { t.Errorf(" content requested for empty state: %v, %v, %v", nodes, paths, codes) } @@ -170,7 +170,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb)) + sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil) nodes, paths, codes := sched.Missing(count) var ( @@ -249,7 +249,7 @@ func TestIterativeDelayedStateSync(t *testing.T) { // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb)) + sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil) nodes, _, codes := sched.Missing(0) queue := append(append([]common.Hash{}, nodes...), codes...) @@ -297,7 +297,7 @@ func testIterativeRandomStateSync(t *testing.T, count int) { // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb)) + sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil) queue := make(map[common.Hash]struct{}) nodes, _, codes := sched.Missing(count) @@ -347,7 +347,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb)) + sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil) queue := make(map[common.Hash]struct{}) nodes, _, codes := sched.Missing(0) @@ -414,7 +414,7 @@ func TestIncompleteStateSync(t *testing.T) { // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb)) + sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil) var added []common.Hash diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 6231588ad2..ff84a3a8f0 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -298,7 +298,7 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync { return &stateSync{ d: d, root: root, - sched: state.NewStateSync(root, d.stateDB, d.stateBloom), + sched: state.NewStateSync(root, d.stateDB, d.stateBloom, nil), keccak: sha3.NewLegacyKeccak256().(crypto.KeccakState), trieTasks: make(map[common.Hash]*trieTask), codeTasks: make(map[common.Hash]*codeTask), diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 2924fa0802..22b0c8604d 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -51,7 +52,7 @@ const ( // maxRequestSize is the maximum number of bytes to request from a remote peer. maxRequestSize = 512 * 1024 - // maxStorageSetRequestCountis th maximum number of contracts to request the + // maxStorageSetRequestCount is the maximum number of contracts to request the // storage of in a single query. If this number is too low, we're not filling // responses fully and waste round trip times. If it's too high, we're capping // responses and waste bandwidth. @@ -435,9 +436,14 @@ type Syncer struct { bytecodeHealDups uint64 // Number of bytecodes already processed bytecodeHealNops uint64 // Number of bytecodes not requested - startTime time.Time // Time instance when snapshot sync started - startAcc common.Hash // Account hash where sync started from - logTime time.Time // Time instance when status was last reported + stateWriter ethdb.Batch // Shared batch writer used for persisting raw states + accountHealed uint64 // Number of accounts downloaded during the healing stage + accountHealedBytes common.StorageSize // Number of raw account bytes persisted to disk during the healing stage + storageHealed uint64 // Number of storage slots downloaded during the healing stage + storageHealedBytes common.StorageSize // Number of raw storage bytes persisted to disk during the healing stage + + startTime time.Time // Time instance when snapshot sync started + logTime time.Time // Time instance when status was last reported pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown lock sync.RWMutex // Protects fields that can change outside of sync (peers, reqs, root) @@ -477,6 +483,7 @@ func NewSyncer(db ethdb.KeyValueStore) *Syncer { bytecodeHealReqFails: make(chan *bytecodeHealRequest), trienodeHealResps: make(chan *trienodeHealResponse), bytecodeHealResps: make(chan *bytecodeHealResponse), + stateWriter: db.NewBatch(), } } @@ -544,7 +551,7 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { s.lock.Lock() s.root = root s.healer = &healTask{ - scheduler: state.NewStateSync(root, s.db, nil), + scheduler: state.NewStateSync(root, s.db, nil, s.onHealState), trieTasks: make(map[common.Hash]trie.SyncPath), codeTasks: make(map[common.Hash]struct{}), } @@ -560,6 +567,11 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { log.Debug("Snapshot sync already completed") return nil } + // If sync is still not finished, we need to ensure that any marker is wiped. + // Otherwise, it may happen that requests for e.g. genesis-data is delivered + // from the snapshot data, instead of from the trie + snapshot.ClearSnapshotMarker(s.db) + defer func() { // Persist any progress, independent of failure for _, task := range s.tasks { s.forwardAccountTask(task) @@ -569,6 +581,14 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { }() log.Debug("Starting snapshot sync cycle", "root", root) + + // Flush out the last committed raw states + defer func() { + if s.stateWriter.ValueSize() > 0 { + s.stateWriter.Write() + s.stateWriter.Reset() + } + }() defer s.report(true) // Whether sync completed or not, disregard any future packets @@ -1694,7 +1714,7 @@ func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { // processStorageResponse integrates an already validated storage response // into the account tasks. func (s *Syncer) processStorageResponse(res *storageResponse) { - // Switch the suntask from pending to idle + // Switch the subtask from pending to idle if res.subTask != nil { res.subTask.req = nil } @@ -1826,6 +1846,14 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { nodes++ } it.Release() + + // Persist the received storage segements. These flat state maybe + // outdated during the sync, but it can be fixed later during the + // snapshot generation. + for j := 0; j < len(res.hashes[i]); j++ { + rawdb.WriteStorageSnapshot(batch, account, res.hashes[i][j], res.slots[i][j]) + bytes += common.StorageSize(1 + 2*common.HashLength + len(res.slots[i][j])) + } } if err := batch.Write(); err != nil { log.Crit("Failed to persist storage slots", "err", err) @@ -1983,6 +2011,14 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { } it.Release() + // Persist the received account segements. These flat state maybe + // outdated during the sync, but it can be fixed later during the + // snapshot generation. + for i, hash := range res.hashes { + blob := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash) + rawdb.WriteAccountSnapshot(batch, hash, blob) + bytes += common.StorageSize(1 + common.HashLength + len(blob)) + } if err := batch.Write(); err != nil { log.Crit("Failed to persist accounts", "err", err) } @@ -2569,6 +2605,33 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e return nil } +// onHealState is a callback method to invoke when a flat state(account +// or storage slot) is downloded during the healing stage. The flat states +// can be persisted blindly and can be fixed later in the generation stage. +// Note it's not concurrent safe, please handle the concurrent issue outside. +func (s *Syncer) onHealState(paths [][]byte, value []byte) error { + if len(paths) == 1 { + var account state.Account + if err := rlp.DecodeBytes(value, &account); err != nil { + return nil + } + blob := snapshot.SlimAccountRLP(account.Nonce, account.Balance, account.Root, account.CodeHash) + rawdb.WriteAccountSnapshot(s.stateWriter, common.BytesToHash(paths[0]), blob) + s.accountHealed += 1 + s.accountHealedBytes += common.StorageSize(1 + common.HashLength + len(blob)) + } + if len(paths) == 2 { + rawdb.WriteStorageSnapshot(s.stateWriter, common.BytesToHash(paths[0]), common.BytesToHash(paths[1]), value) + s.storageHealed += 1 + s.storageHealedBytes += common.StorageSize(1 + 2*common.HashLength + len(value)) + } + if s.stateWriter.ValueSize() > ethdb.IdealBatchSize { + s.stateWriter.Write() // It's fine to ignore the error here + s.stateWriter.Reset() + } + return nil +} + // hashSpace is the total size of the 256 bit hash space for accounts. var hashSpace = new(big.Int).Exp(common.Big2, common.Big256, nil) @@ -2632,7 +2695,9 @@ func (s *Syncer) reportHealProgress(force bool) { var ( trienode = fmt.Sprintf("%d@%v", s.trienodeHealSynced, s.trienodeHealBytes.TerminalString()) bytecode = fmt.Sprintf("%d@%v", s.bytecodeHealSynced, s.bytecodeHealBytes.TerminalString()) + accounts = fmt.Sprintf("%d@%v", s.accountHealed, s.accountHealedBytes.TerminalString()) + storage = fmt.Sprintf("%d@%v", s.storageHealed, s.storageHealedBytes.TerminalString()) ) - log.Info("State heal in progress", "nodes", trienode, "codes", bytecode, - "pending", s.healer.scheduler.Pending()) + log.Info("State heal in progress", "accounts", accounts, "slots", storage, + "codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending()) } diff --git a/trie/committer.go b/trie/committer.go index 33fd9e9823..ce4065f5fd 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -220,13 +220,13 @@ func (c *committer) commitLoop(db *Database) { switch n := n.(type) { case *shortNode: if child, ok := n.Val.(valueNode); ok { - c.onleaf(nil, child, hash) + c.onleaf(nil, nil, child, hash) } case *fullNode: // For children in range [0, 15], it's impossible // to contain valuenode. Only check the 17th child. if n.Children[16] != nil { - c.onleaf(nil, n.Children[16].(valueNode), hash) + c.onleaf(nil, nil, n.Children[16].(valueNode), hash) } } } diff --git a/trie/sync.go b/trie/sync.go index dd8279b665..3a6076ff8f 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -398,7 +398,14 @@ func (s *Sync) children(req *request, object node) ([]*request, error) { // Notify any external watcher of a new key/value node if req.callback != nil { if node, ok := (child.node).(valueNode); ok { - if err := req.callback(child.path, node, req.hash); err != nil { + var paths [][]byte + if len(child.path) == 2*common.HashLength { + paths = append(paths, hexToKeybytes(child.path)) + } else if len(child.path) == 4*common.HashLength { + paths = append(paths, hexToKeybytes(child.path[:2*common.HashLength])) + paths = append(paths, hexToKeybytes(child.path[2*common.HashLength:])) + } + if err := req.callback(paths, child.path, node, req.hash); err != nil { return nil, err } } diff --git a/trie/trie.go b/trie/trie.go index 87b72ecf17..7ed235fa8a 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -37,9 +37,20 @@ var ( ) // LeafCallback is a callback type invoked when a trie operation reaches a leaf -// node. It's used by state sync and commit to allow handling external references -// between account and storage tries. -type LeafCallback func(path []byte, leaf []byte, parent common.Hash) error +// node. +// +// The paths is a path tuple identifying a particular trie node either in a single +// trie (account) or a layered trie (account -> storage). Each path in the tuple +// is in the raw format(32 bytes). +// +// The hexpath is a composite hexary path identifying the trie node. All the key +// bytes are converted to the hexary nibbles and composited with the parent path +// if the trie node is in a layered trie. +// +// It's used by state sync and commit to allow handling external references +// between account and storage tries. And also it's used in the state healing +// for extracting the raw states(leaf nodes) with corresponding paths. +type LeafCallback func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error // Trie is a Merkle Patricia Trie. // The zero value is an empty trie with no database. diff --git a/trie/trie_test.go b/trie/trie_test.go index 3aa4098d14..d6930fdee7 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -569,7 +569,7 @@ func BenchmarkCommitAfterHash(b *testing.B) { benchmarkCommitAfterHash(b, nil) }) var a account - onleaf := func(path []byte, leaf []byte, parent common.Hash) error { + onleaf := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error { rlp.DecodeBytes(leaf, &a) return nil } From d5e57948d1d889b129be87e2cbcdbfaa727ef5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 14 Apr 2021 23:39:42 +0300 Subject: [PATCH 457/948] core/types: drop some relice data types --- core/types/block.go | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 553db003bb..a3318f8779 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -166,19 +166,6 @@ type Block struct { ReceivedFrom interface{} } -// DeprecatedTd is an old relic for extracting the TD of a block. It is in the -// code solely to facilitate upgrading the database from the old format to the -// new, after which it should be deleted. Do not use! -func (b *Block) DeprecatedTd() *big.Int { - return b.td -} - -// [deprecated by eth/63] -// StorageBlock defines the RLP encoding of a Block stored in the -// state database. The StorageBlock encoding contains fields that -// would otherwise need to be recomputed. -type StorageBlock Block - // "external" block encoding. used for eth protocol, etc. type extblock struct { Header *Header @@ -186,15 +173,6 @@ type extblock struct { Uncles []*Header } -// [deprecated by eth/63] -// "storage" block encoding. used for database. -type storageblock struct { - Header *Header - Txs []*Transaction - Uncles []*Header - TD *big.Int -} - // NewBlock creates a new block. The input data is copied, // changes to header and to the field values will not affect the // block. @@ -279,16 +257,6 @@ func (b *Block) EncodeRLP(w io.Writer) error { }) } -// [deprecated by eth/63] -func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error { - var sb storageblock - if err := s.Decode(&sb); err != nil { - return err - } - b.header, b.uncles, b.transactions, b.td = sb.Header, sb.Uncles, sb.Txs, sb.TD - return nil -} - // TODO: copies func (b *Block) Uncles() []*Header { return b.uncles } From 1e207342b545fb5992e2e8f38e60f219f379e5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 15 Apr 2021 20:35:00 +0300 Subject: [PATCH 458/948] all: make logs a bit easier on the eye to digest (#22665) * all: add thousandths separators for big numbers on log messages * p2p/sentry: drop accidental file * common, log: add fast number formatter * common, eth/protocols/snap: simplifty fancy num types * log: handle nil big ints --- accounts/url.go | 2 +- cmd/evm/README.md | 6 +- cmd/evm/testdata/8/readme.md | 6 +- common/types.go | 2 +- core/blockchain.go | 10 ++-- core/blockchain_test.go | 4 +- core/chain_indexer.go | 2 +- core/headerchain.go | 2 +- eth/protocols/snap/sync.go | 14 ++--- log/format.go | 106 ++++++++++++++++++++++++++++++++++- log/format_test.go | 75 +++++++++++++++++++++++++ 11 files changed, 202 insertions(+), 27 deletions(-) create mode 100644 log/format_test.go diff --git a/accounts/url.go b/accounts/url.go index a5add10216..12a84414a0 100644 --- a/accounts/url.go +++ b/accounts/url.go @@ -64,7 +64,7 @@ func (u URL) String() string { func (u URL) TerminalString() string { url := u.String() if len(url) > 32 { - return url[:31] + "…" + return url[:31] + ".." } return url } diff --git a/cmd/evm/README.md b/cmd/evm/README.md index 7742ccbbb7..cdff41f904 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -256,9 +256,9 @@ Error code: 4 Another thing that can be done, is to chain invocations: ``` ./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json -INFO [01-21|22:41:22.963] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" -INFO [01-21|22:41:22.966] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" -INFO [01-21|22:41:22.967] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.963] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.966] rejected tx index=0 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.967] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" ``` What happened here, is that we first applied two identical transactions, so the second one was rejected. diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md index 778fc6151a..e021cd7e2e 100644 --- a/cmd/evm/testdata/8/readme.md +++ b/cmd/evm/testdata/8/readme.md @@ -56,8 +56,8 @@ dir=./testdata/8 \ If we try to execute it on older rules: ``` dir=./testdata/8 && ./evm t8n --state.fork=Istanbul --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json -INFO [01-21|23:21:51.265] rejected tx index=0 hash="d2818d…6ab3da" error="tx type not supported" -INFO [01-21|23:21:51.265] rejected tx index=1 hash="26ea00…81c01b" from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" -INFO [01-21|23:21:51.265] rejected tx index=2 hash="698d01…369cee" error="tx type not supported" +INFO [01-21|23:21:51.265] rejected tx index=0 hash=d2818d..6ab3da error="tx type not supported" +INFO [01-21|23:21:51.265] rejected tx index=1 hash=26ea00..81c01b from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" +INFO [01-21|23:21:51.265] rejected tx index=2 hash=698d01..369cee error="tx type not supported" ``` Number `1` and `3` are not applicable, and therefore number `2` has wrong nonce. \ No newline at end of file diff --git a/common/types.go b/common/types.go index d920e8b1f1..d715356692 100644 --- a/common/types.go +++ b/common/types.go @@ -76,7 +76,7 @@ func (h Hash) Hex() string { return hexutil.Encode(h[:]) } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (h Hash) TerminalString() string { - return fmt.Sprintf("%x…%x", h[:3], h[29:]) + return fmt.Sprintf("%x..%x", h[:3], h[29:]) } // String implements the stringer interface and is used also by the logger when diff --git a/core/blockchain.go b/core/blockchain.go index d65ce4f048..8c3d940a69 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -630,7 +630,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { // Make sure that both the block as well at its state trie exists block := bc.GetBlockByHash(hash) if block == nil { - return fmt.Errorf("non existent block [%x…]", hash[:4]) + return fmt.Errorf("non existent block [%x..]", hash[:4]) } if _, err := trie.NewSecure(block.Root(), bc.stateCache.TrieDB()); err != nil { return err @@ -1147,7 +1147,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() { log.Error("Non contiguous receipt insert", "number", blockChain[i].Number(), "hash", blockChain[i].Hash(), "parent", blockChain[i].ParentHash(), "prevnumber", blockChain[i-1].Number(), "prevhash", blockChain[i-1].Hash()) - return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, blockChain[i-1].NumberU64(), + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, blockChain[i-1].NumberU64(), blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4]) } } @@ -1212,7 +1212,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ } // Short circuit if the owner header is unknown if !bc.HasHeader(block.Hash(), block.NumberU64()) { - return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4]) + return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4]) } var ( start = time.Now() @@ -1356,7 +1356,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ } // Short circuit if the owner header is unknown if !bc.HasHeader(block.Hash(), block.NumberU64()) { - return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4]) + return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4]) } if !skipPresenceCheck { // Ignore if the entire data is already known @@ -1679,7 +1679,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { log.Error("Non contiguous block insert", "number", block.Number(), "hash", block.Hash(), "parent", block.ParentHash(), "prevnumber", prev.Number(), "prevhash", prev.Hash()) - return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, prev.NumberU64(), + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, prev.NumberU64(), prev.Hash().Bytes()[:4], i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4]) } } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index fd7f1aea1b..5004abd1c7 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1462,13 +1462,13 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { t.Fatalf("block %d: failed to insert into chain: %v", i, err) } if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { - t.Errorf("block %d: current block/header mismatch: block #%d [%x…], header #%d [%x…]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) + t.Errorf("block %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) } if _, err := chain.InsertChain(forks[i : i+1]); err != nil { t.Fatalf(" fork %d: failed to insert into chain: %v", i, err) } if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() { - t.Errorf(" fork %d: current block/header mismatch: block #%d [%x…], header #%d [%x…]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) + t.Errorf(" fork %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4]) } } } diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 4b326c970b..95901a0eaa 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -401,7 +401,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com } header := rawdb.ReadHeader(c.chainDb, hash, number) if header == nil { - return common.Hash{}, fmt.Errorf("block #%d [%x…] not found", number, hash[:4]) + return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4]) } else if header.ParentHash != lastHead { return common.Hash{}, fmt.Errorf("chain reorged during section processing") } diff --git a/core/headerchain.go b/core/headerchain.go index 9aee660eba..1dbf958786 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -306,7 +306,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", hash, "parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash) - return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number, + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].Number, parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4]) } // If the header is a banned one, straight out abort diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 22b0c8604d..1ea589c7b2 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -2675,9 +2675,9 @@ func (s *Syncer) reportSyncProgress(force bool) { // Create a mega progress report var ( progress = fmt.Sprintf("%.2f%%", float64(synced)*100/estBytes) - accounts = fmt.Sprintf("%d@%v", s.accountSynced, s.accountBytes.TerminalString()) - storage = fmt.Sprintf("%d@%v", s.storageSynced, s.storageBytes.TerminalString()) - bytecode = fmt.Sprintf("%d@%v", s.bytecodeSynced, s.bytecodeBytes.TerminalString()) + accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountSynced), s.accountBytes.TerminalString()) + storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageSynced), s.storageBytes.TerminalString()) + bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeSynced), s.bytecodeBytes.TerminalString()) ) log.Info("State sync in progress", "synced", progress, "state", synced, "accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed)) @@ -2693,10 +2693,10 @@ func (s *Syncer) reportHealProgress(force bool) { // Create a mega progress report var ( - trienode = fmt.Sprintf("%d@%v", s.trienodeHealSynced, s.trienodeHealBytes.TerminalString()) - bytecode = fmt.Sprintf("%d@%v", s.bytecodeHealSynced, s.bytecodeHealBytes.TerminalString()) - accounts = fmt.Sprintf("%d@%v", s.accountHealed, s.accountHealedBytes.TerminalString()) - storage = fmt.Sprintf("%d@%v", s.storageHealed, s.storageHealedBytes.TerminalString()) + trienode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.trienodeHealSynced), s.trienodeHealBytes.TerminalString()) + bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeHealSynced), s.bytecodeHealBytes.TerminalString()) + accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountHealed), s.accountHealedBytes.TerminalString()) + storage = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageHealed), s.storageHealedBytes.TerminalString()) ) log.Info("State heal in progress", "accounts", accounts, "slots", storage, "codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending()) diff --git a/log/format.go b/log/format.go index 421384cc1d..0667921528 100644 --- a/log/format.go +++ b/log/format.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "math/big" "reflect" "strconv" "strings" @@ -329,11 +330,20 @@ func formatLogfmtValue(value interface{}, term bool) string { return "nil" } - if t, ok := value.(time.Time); ok { + switch v := value.(type) { + case time.Time: // Performance optimization: No need for escaping since the provided // timeFormat doesn't have any escape characters, and escaping is // expensive. - return t.Format(timeFormat) + return v.Format(timeFormat) + + case *big.Int: + // Big ints get consumed by the Stringer clause so we need to handle + // them earlier on. + if v == nil { + return "" + } + return formatLogfmtBigInt(v) } if term { if s, ok := value.(TerminalStringer); ok { @@ -349,8 +359,24 @@ func formatLogfmtValue(value interface{}, term bool) string { return strconv.FormatFloat(float64(v), floatFormat, 3, 64) case float64: return strconv.FormatFloat(v, floatFormat, 3, 64) - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + case int8, uint8: return fmt.Sprintf("%d", value) + case int: + return FormatLogfmtInt64(int64(v)) + case int16: + return FormatLogfmtInt64(int64(v)) + case int32: + return FormatLogfmtInt64(int64(v)) + case int64: + return FormatLogfmtInt64(v) + case uint: + return FormatLogfmtUint64(uint64(v)) + case uint16: + return FormatLogfmtUint64(uint64(v)) + case uint32: + return FormatLogfmtUint64(uint64(v)) + case uint64: + return FormatLogfmtUint64(v) case string: return escapeString(v) default: @@ -358,6 +384,80 @@ func formatLogfmtValue(value interface{}, term bool) string { } } +// FormatLogfmtInt64 formats a potentially big number in a friendlier split format. +func FormatLogfmtInt64(n int64) string { + if n < 0 { + return formatLogfmtUint64(uint64(-n), true) + } + return formatLogfmtUint64(uint64(n), false) +} + +// FormatLogfmtUint64 formats a potentially big number in a friendlier split format. +func FormatLogfmtUint64(n uint64) string { + return formatLogfmtUint64(n, false) +} + +func formatLogfmtUint64(n uint64, neg bool) string { + // Small numbers are fine as is + if n < 100000 { + if neg { + return strconv.Itoa(-int(n)) + } else { + return strconv.Itoa(int(n)) + } + } + // Large numbers should be split + const maxLength = 26 + + var ( + out = make([]byte, maxLength) + i = maxLength - 1 + comma = 0 + ) + for ; n > 0; i-- { + if comma == 3 { + comma = 0 + out[i] = ',' + } else { + comma++ + out[i] = '0' + byte(n%10) + n /= 10 + } + } + if neg { + out[i] = '-' + i-- + } + return string(out[i+1:]) +} + +var big1000 = big.NewInt(1000) + +// formatLogfmtBigInt formats a potentially gigantic number in a friendlier split +// format. +func formatLogfmtBigInt(n *big.Int) string { + // Most number don't need fancy handling, just downcast + if n.IsUint64() { + return FormatLogfmtUint64(n.Uint64()) + } + if n.IsInt64() { + return FormatLogfmtInt64(n.Int64()) + } + // Ok, huge number needs huge effort + groups := make([]string, 0, 8) // random initial size to cover most cases + for n.Cmp(big1000) >= 0 { + _, mod := n.DivMod(n, big1000, nil) + groups = append(groups, fmt.Sprintf("%03d", mod)) + } + groups = append(groups, n.String()) + + last := len(groups) - 1 + for i := 0; i < len(groups)/2; i++ { + groups[i], groups[last-i] = groups[last-i], groups[i] + } + return strings.Join(groups, ",") +} + // escapeString checks if the provided string needs escaping/quoting, and // calls strconv.Quote if needed func escapeString(s string) string { diff --git a/log/format_test.go b/log/format_test.go new file mode 100644 index 0000000000..348b265c9b --- /dev/null +++ b/log/format_test.go @@ -0,0 +1,75 @@ +package log + +import ( + "math" + "math/rand" + "testing" +) + +func TestPrettyInt64(t *testing.T) { + tests := []struct { + n int64 + s string + }{ + {0, "0"}, + {10, "10"}, + {-10, "-10"}, + {100, "100"}, + {-100, "-100"}, + {1000, "1000"}, + {-1000, "-1000"}, + {10000, "10000"}, + {-10000, "-10000"}, + {99999, "99999"}, + {-99999, "-99999"}, + {100000, "100,000"}, + {-100000, "-100,000"}, + {1000000, "1,000,000"}, + {-1000000, "-1,000,000"}, + {math.MaxInt64, "9,223,372,036,854,775,807"}, + {math.MinInt64, "-9,223,372,036,854,775,808"}, + } + for i, tt := range tests { + if have := FormatLogfmtInt64(tt.n); have != tt.s { + t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s) + } + } +} + +func TestPrettyUint64(t *testing.T) { + tests := []struct { + n uint64 + s string + }{ + {0, "0"}, + {10, "10"}, + {100, "100"}, + {1000, "1000"}, + {10000, "10000"}, + {99999, "99999"}, + {100000, "100,000"}, + {1000000, "1,000,000"}, + {math.MaxUint64, "18,446,744,073,709,551,615"}, + } + for i, tt := range tests { + if have := FormatLogfmtUint64(tt.n); have != tt.s { + t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s) + } + } +} + +var sink string + +func BenchmarkPrettyInt64Logfmt(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = FormatLogfmtInt64(rand.Int63()) + } +} + +func BenchmarkPrettyUint64Logfmt(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = FormatLogfmtUint64(rand.Uint64()) + } +} From 9553c98de8f730e77b612a642089824f1c35bf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 15 Apr 2021 21:01:16 +0300 Subject: [PATCH 459/948] eth/protocols/snap: use ephemeral channels to avoid cross-sync delveries --- eth/protocols/snap/sync.go | 213 +++++++++++++++++++------------------ 1 file changed, 112 insertions(+), 101 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 1ea589c7b2..cff1a77e6c 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -106,9 +106,11 @@ type accountRequest struct { peer string // Peer to which this request is assigned id uint64 // Request ID of this request - cancel chan struct{} // Channel to track sync cancellation - timeout *time.Timer // Timer to track delivery timeout - stale chan struct{} // Channel to signal the request was dropped + deliver chan *accountResponse // Channel to deliver successful response on + revert chan *accountRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped origin common.Hash // First account requested to allow continuation checks limit common.Hash // Last account requested to allow non-overlapping chunking @@ -147,9 +149,11 @@ type bytecodeRequest struct { peer string // Peer to which this request is assigned id uint64 // Request ID of this request - cancel chan struct{} // Channel to track sync cancellation - timeout *time.Timer // Timer to track delivery timeout - stale chan struct{} // Channel to signal the request was dropped + deliver chan *bytecodeResponse // Channel to deliver successful response on + revert chan *bytecodeRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped hashes []common.Hash // Bytecode hashes to validate responses task *accountTask // Task which this request is filling (only access fields through the runloop!!) @@ -176,9 +180,11 @@ type storageRequest struct { peer string // Peer to which this request is assigned id uint64 // Request ID of this request - cancel chan struct{} // Channel to track sync cancellation - timeout *time.Timer // Timer to track delivery timeout - stale chan struct{} // Channel to signal the request was dropped + deliver chan *storageResponse // Channel to deliver successful response on + revert chan *storageRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped accounts []common.Hash // Account hashes to validate responses roots []common.Hash // Storage roots to validate responses @@ -224,9 +230,11 @@ type trienodeHealRequest struct { peer string // Peer to which this request is assigned id uint64 // Request ID of this request - cancel chan struct{} // Channel to track sync cancellation - timeout *time.Timer // Timer to track delivery timeout - stale chan struct{} // Channel to signal the request was dropped + deliver chan *trienodeHealResponse // Channel to deliver successful response on + revert chan *trienodeHealRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped hashes []common.Hash // Trie node hashes to validate responses paths []trie.SyncPath // Trie node paths requested for rescheduling @@ -256,9 +264,11 @@ type bytecodeHealRequest struct { peer string // Peer to which this request is assigned id uint64 // Request ID of this request - cancel chan struct{} // Channel to track sync cancellation - timeout *time.Timer // Timer to track delivery timeout - stale chan struct{} // Channel to signal the request was dropped + deliver chan *bytecodeHealResponse // Channel to deliver successful response on + revert chan *bytecodeHealRequest // Channel to deliver request failure on + cancel chan struct{} // Channel to track sync cancellation + timeout *time.Timer // Timer to track delivery timeout + stale chan struct{} // Channel to signal the request was dropped hashes []common.Hash // Bytecode hashes to validate responses task *healTask // Task which this request is filling (only access fields through the runloop!!) @@ -399,14 +409,6 @@ type Syncer struct { bytecodeReqs map[uint64]*bytecodeRequest // Bytecode requests currently running storageReqs map[uint64]*storageRequest // Storage requests currently running - accountReqFails chan *accountRequest // Failed account range requests to revert - bytecodeReqFails chan *bytecodeRequest // Failed bytecode requests to revert - storageReqFails chan *storageRequest // Failed storage requests to revert - - accountResps chan *accountResponse // Account sub-tries to integrate into the database - bytecodeResps chan *bytecodeResponse // Bytecodes to integrate into the database - storageResps chan *storageResponse // Storage sub-tries to integrate into the database - accountSynced uint64 // Number of accounts downloaded accountBytes common.StorageSize // Number of account trie bytes persisted to disk bytecodeSynced uint64 // Number of bytecodes downloaded @@ -421,12 +423,6 @@ type Syncer struct { trienodeHealReqs map[uint64]*trienodeHealRequest // Trie node requests currently running bytecodeHealReqs map[uint64]*bytecodeHealRequest // Bytecode requests currently running - trienodeHealReqFails chan *trienodeHealRequest // Failed trienode requests to revert - bytecodeHealReqFails chan *bytecodeHealRequest // Failed bytecode requests to revert - - trienodeHealResps chan *trienodeHealResponse // Trie nodes to integrate into the database - bytecodeHealResps chan *bytecodeHealResponse // Bytecodes to integrate into the database - trienodeHealSynced uint64 // Number of state trie nodes downloaded trienodeHealBytes common.StorageSize // Number of state trie bytes persisted to disk trienodeHealDups uint64 // Number of state trie nodes already processed @@ -464,26 +460,16 @@ func NewSyncer(db ethdb.KeyValueStore) *Syncer { storageIdlers: make(map[string]struct{}), bytecodeIdlers: make(map[string]struct{}), - accountReqs: make(map[uint64]*accountRequest), - storageReqs: make(map[uint64]*storageRequest), - bytecodeReqs: make(map[uint64]*bytecodeRequest), - accountReqFails: make(chan *accountRequest), - storageReqFails: make(chan *storageRequest), - bytecodeReqFails: make(chan *bytecodeRequest), - accountResps: make(chan *accountResponse), - storageResps: make(chan *storageResponse), - bytecodeResps: make(chan *bytecodeResponse), + accountReqs: make(map[uint64]*accountRequest), + storageReqs: make(map[uint64]*storageRequest), + bytecodeReqs: make(map[uint64]*bytecodeRequest), trienodeHealIdlers: make(map[string]struct{}), bytecodeHealIdlers: make(map[string]struct{}), - trienodeHealReqs: make(map[uint64]*trienodeHealRequest), - bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest), - trienodeHealReqFails: make(chan *trienodeHealRequest), - bytecodeHealReqFails: make(chan *bytecodeHealRequest), - trienodeHealResps: make(chan *trienodeHealResponse), - bytecodeHealResps: make(chan *bytecodeHealResponse), - stateWriter: db.NewBatch(), + trienodeHealReqs: make(map[uint64]*trienodeHealRequest), + bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest), + stateWriter: db.NewBatch(), } } @@ -611,6 +597,21 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { peerDropSub := s.peerDrop.Subscribe(peerDrop) defer peerDropSub.Unsubscribe() + // Create a set of unique channels for this sync cycle. We need these to be + // ephemeral so a data race doesn't accidentally deliver something stale on + // a persistent channel across syncs (yup, this happened) + var ( + accountReqFails = make(chan *accountRequest) + storageReqFails = make(chan *storageRequest) + bytecodeReqFails = make(chan *bytecodeRequest) + accountResps = make(chan *accountResponse) + storageResps = make(chan *storageResponse) + bytecodeResps = make(chan *bytecodeResponse) + trienodeHealReqFails = make(chan *trienodeHealRequest) + bytecodeHealReqFails = make(chan *bytecodeHealRequest) + trienodeHealResps = make(chan *trienodeHealResponse) + bytecodeHealResps = make(chan *bytecodeHealResponse) + ) for { // Remove all completed tasks and terminate sync if everything's done s.cleanStorageTasks() @@ -619,14 +620,14 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { return nil } // Assign all the data retrieval tasks to any free peers - s.assignAccountTasks(cancel) - s.assignBytecodeTasks(cancel) - s.assignStorageTasks(cancel) + s.assignAccountTasks(accountResps, accountReqFails, cancel) + s.assignBytecodeTasks(bytecodeResps, bytecodeReqFails, cancel) + s.assignStorageTasks(storageResps, storageReqFails, cancel) if len(s.tasks) == 0 { // Sync phase done, run heal phase - s.assignTrienodeHealTasks(cancel) - s.assignBytecodeHealTasks(cancel) + s.assignTrienodeHealTasks(trienodeHealResps, trienodeHealReqFails, cancel) + s.assignBytecodeHealTasks(bytecodeHealResps, bytecodeHealReqFails, cancel) } // Wait for something to happen select { @@ -639,26 +640,26 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { case <-cancel: return ErrCancelled - case req := <-s.accountReqFails: + case req := <-accountReqFails: s.revertAccountRequest(req) - case req := <-s.bytecodeReqFails: + case req := <-bytecodeReqFails: s.revertBytecodeRequest(req) - case req := <-s.storageReqFails: + case req := <-storageReqFails: s.revertStorageRequest(req) - case req := <-s.trienodeHealReqFails: + case req := <-trienodeHealReqFails: s.revertTrienodeHealRequest(req) - case req := <-s.bytecodeHealReqFails: + case req := <-bytecodeHealReqFails: s.revertBytecodeHealRequest(req) - case res := <-s.accountResps: + case res := <-accountResps: s.processAccountResponse(res) - case res := <-s.bytecodeResps: + case res := <-bytecodeResps: s.processBytecodeResponse(res) - case res := <-s.storageResps: + case res := <-storageResps: s.processStorageResponse(res) - case res := <-s.trienodeHealResps: + case res := <-trienodeHealResps: s.processTrienodeHealResponse(res) - case res := <-s.bytecodeHealResps: + case res := <-bytecodeHealResps: s.processBytecodeHealResponse(res) } // Report stats if something meaningful happened @@ -801,7 +802,7 @@ func (s *Syncer) cleanStorageTasks() { // assignAccountTasks attempts to match idle peers to pending account range // retrievals. -func (s *Syncer) assignAccountTasks(cancel chan struct{}) { +func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *accountRequest, cancel chan struct{}) { s.lock.Lock() defer s.lock.Unlock() @@ -847,13 +848,15 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { } // Generate the network query and send it to the peer req := &accountRequest{ - peer: idle, - id: reqid, - cancel: cancel, - stale: make(chan struct{}), - origin: task.Next, - limit: task.Last, - task: task, + peer: idle, + id: reqid, + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + origin: task.Next, + limit: task.Last, + task: task, } req.timeout = time.AfterFunc(requestTimeout, func() { peer.Log().Debug("Account range request timed out", "reqid", reqid) @@ -879,7 +882,7 @@ func (s *Syncer) assignAccountTasks(cancel chan struct{}) { } // assignBytecodeTasks attempts to match idle peers to pending code retrievals. -func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { +func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan *bytecodeRequest, cancel chan struct{}) { s.lock.Lock() defer s.lock.Unlock() @@ -937,12 +940,14 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { } } req := &bytecodeRequest{ - peer: idle, - id: reqid, - cancel: cancel, - stale: make(chan struct{}), - hashes: hashes, - task: task, + peer: idle, + id: reqid, + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: task, } req.timeout = time.AfterFunc(requestTimeout, func() { peer.Log().Debug("Bytecode request timed out", "reqid", reqid) @@ -966,7 +971,7 @@ func (s *Syncer) assignBytecodeTasks(cancel chan struct{}) { // assignStorageTasks attempts to match idle peers to pending storage range // retrievals. -func (s *Syncer) assignStorageTasks(cancel chan struct{}) { +func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *storageRequest, cancel chan struct{}) { s.lock.Lock() defer s.lock.Unlock() @@ -1059,6 +1064,8 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { req := &storageRequest{ peer: idle, id: reqid, + deliver: success, + revert: fail, cancel: cancel, stale: make(chan struct{}), accounts: accounts, @@ -1101,7 +1108,7 @@ func (s *Syncer) assignStorageTasks(cancel chan struct{}) { // assignTrienodeHealTasks attempts to match idle peers to trie node requests to // heal any trie errors caused by the snap sync's chunked retrieval model. -func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { +func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fail chan *trienodeHealRequest, cancel chan struct{}) { s.lock.Lock() defer s.lock.Unlock() @@ -1179,13 +1186,15 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { } } req := &trienodeHealRequest{ - peer: idle, - id: reqid, - cancel: cancel, - stale: make(chan struct{}), - hashes: hashes, - paths: paths, - task: s.healer, + peer: idle, + id: reqid, + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + paths: paths, + task: s.healer, } req.timeout = time.AfterFunc(requestTimeout, func() { peer.Log().Debug("Trienode heal request timed out", "reqid", reqid) @@ -1209,7 +1218,7 @@ func (s *Syncer) assignTrienodeHealTasks(cancel chan struct{}) { // assignBytecodeHealTasks attempts to match idle peers to bytecode requests to // heal any trie errors caused by the snap sync's chunked retrieval model. -func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { +func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fail chan *bytecodeHealRequest, cancel chan struct{}) { s.lock.Lock() defer s.lock.Unlock() @@ -1280,12 +1289,14 @@ func (s *Syncer) assignBytecodeHealTasks(cancel chan struct{}) { } } req := &bytecodeHealRequest{ - peer: idle, - id: reqid, - cancel: cancel, - stale: make(chan struct{}), - hashes: hashes, - task: s.healer, + peer: idle, + id: reqid, + deliver: success, + revert: fail, + cancel: cancel, + stale: make(chan struct{}), + hashes: hashes, + task: s.healer, } req.timeout = time.AfterFunc(requestTimeout, func() { peer.Log().Debug("Bytecode heal request timed out", "reqid", reqid) @@ -1366,7 +1377,7 @@ func (s *Syncer) revertRequests(peer string) { // request and return all failed retrieval tasks to the scheduler for reassignment. func (s *Syncer) scheduleRevertAccountRequest(req *accountRequest) { select { - case s.accountReqFails <- req: + case req.revert <- req: // Sync event loop notified case <-req.cancel: // Sync cycle got cancelled @@ -1407,7 +1418,7 @@ func (s *Syncer) revertAccountRequest(req *accountRequest) { // and return all failed retrieval tasks to the scheduler for reassignment. func (s *Syncer) scheduleRevertBytecodeRequest(req *bytecodeRequest) { select { - case s.bytecodeReqFails <- req: + case req.revert <- req: // Sync event loop notified case <-req.cancel: // Sync cycle got cancelled @@ -1448,7 +1459,7 @@ func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) { // request and return all failed retrieval tasks to the scheduler for reassignment. func (s *Syncer) scheduleRevertStorageRequest(req *storageRequest) { select { - case s.storageReqFails <- req: + case req.revert <- req: // Sync event loop notified case <-req.cancel: // Sync cycle got cancelled @@ -1493,7 +1504,7 @@ func (s *Syncer) revertStorageRequest(req *storageRequest) { // request and return all failed retrieval tasks to the scheduler for reassignment. func (s *Syncer) scheduleRevertTrienodeHealRequest(req *trienodeHealRequest) { select { - case s.trienodeHealReqFails <- req: + case req.revert <- req: // Sync event loop notified case <-req.cancel: // Sync cycle got cancelled @@ -1534,7 +1545,7 @@ func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) { // request and return all failed retrieval tasks to the scheduler for reassignment. func (s *Syncer) scheduleRevertBytecodeHealRequest(req *bytecodeHealRequest) { select { - case s.bytecodeHealReqFails <- req: + case req.revert <- req: // Sync event loop notified case <-req.cancel: // Sync cycle got cancelled @@ -2147,7 +2158,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco cont: cont, } select { - case s.accountResps <- response: + case req.deliver <- response: case <-req.cancel: case <-req.stale: } @@ -2253,7 +2264,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error codes: codes, } select { - case s.bytecodeResps <- response: + case req.deliver <- response: case <-req.cancel: case <-req.stale: } @@ -2411,7 +2422,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo cont: cont, } select { - case s.storageResps <- response: + case req.deliver <- response: case <-req.cancel: case <-req.stale: } @@ -2505,7 +2516,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error nodes: nodes, } select { - case s.trienodeHealResps <- response: + case req.deliver <- response: case <-req.cancel: case <-req.stale: } @@ -2598,7 +2609,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e codes: codes, } select { - case s.bytecodeHealResps <- response: + case req.deliver <- response: case <-req.cancel: case <-req.stale: } From 3cfd0fe7a8d8e175f5d0bff1e926d5d92a7ca6ce Mon Sep 17 00:00:00 2001 From: meowsbits Date: Thu, 15 Apr 2021 17:32:16 -0500 Subject: [PATCH 460/948] core: add TestGenesisHashes and fix YoloV3 (#22559) This adds simple unit test checking if the hard-coded genesis hash values in package params match the actual genesis block hashes. --- core/genesis_test.go | 35 +++++++++++++++++++++++++++++++++++ params/config.go | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/genesis_test.go b/core/genesis_test.go index 3470d0aa01..44c1ef253a 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -162,3 +162,38 @@ func TestSetupGenesis(t *testing.T) { } } } + +// TestGenesisHashes checks the congruity of default genesis data to corresponding hardcoded genesis hash values. +func TestGenesisHashes(t *testing.T) { + cases := []struct { + genesis *Genesis + hash common.Hash + }{ + { + genesis: DefaultGenesisBlock(), + hash: params.MainnetGenesisHash, + }, + { + genesis: DefaultGoerliGenesisBlock(), + hash: params.GoerliGenesisHash, + }, + { + genesis: DefaultRopstenGenesisBlock(), + hash: params.RopstenGenesisHash, + }, + { + genesis: DefaultRinkebyGenesisBlock(), + hash: params.RinkebyGenesisHash, + }, + { + genesis: DefaultYoloV3GenesisBlock(), + hash: params.YoloV3GenesisHash, + }, + } + for i, c := range cases { + b := c.genesis.MustCommit(rawdb.NewMemoryDatabase()) + if got := b.Hash(); got != c.hash { + t.Errorf("case: %d, want: %s, got: %s", i, c.hash.Hex(), got.Hex()) + } + } +} diff --git a/params/config.go b/params/config.go index 143e2e2a36..9fca534c79 100644 --- a/params/config.go +++ b/params/config.go @@ -31,7 +31,7 @@ var ( RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") - YoloV3GenesisHash = common.HexToHash("0x374f07cc7fa7c251fc5f36849f574b43db43600526410349efdca2bcea14101a") + YoloV3GenesisHash = common.HexToHash("0xf1f2876e8500c77afcc03228757b39477eceffccf645b734967fe3c7e16967b7") ) // TrustedCheckpoints associates each known checkpoint with the genesis hash of From fda93f643efa5dd6aafde1df7f086862a5dad9e3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 16 Apr 2021 08:27:16 +0200 Subject: [PATCH 461/948] log: fix formatting of big.Int (#22679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * log: fix formatting of big.Int The implementation of formatLogfmtBigInt had two issues: it crashed when the number was actually large enough to hit the big integer case, and modified the big.Int while formatting it. * log: don't call FormatLogfmtInt64 for int16 * log: separate from decimals back, not front Co-authored-by: Péter Szilágyi --- log/format.go | 58 +++++++++++++++++++++++++++------------------- log/format_test.go | 20 ++++++++++++++++ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/log/format.go b/log/format.go index 0667921528..baf8fddac0 100644 --- a/log/format.go +++ b/log/format.go @@ -359,11 +359,16 @@ func formatLogfmtValue(value interface{}, term bool) string { return strconv.FormatFloat(float64(v), floatFormat, 3, 64) case float64: return strconv.FormatFloat(v, floatFormat, 3, 64) - case int8, uint8: - return fmt.Sprintf("%d", value) - case int: - return FormatLogfmtInt64(int64(v)) + case int8: + return strconv.FormatInt(int64(v), 10) + case uint8: + return strconv.FormatInt(int64(v), 10) case int16: + return strconv.FormatInt(int64(v), 10) + case uint16: + return strconv.FormatInt(int64(v), 10) + // Larger integers get thousands separators. + case int: return FormatLogfmtInt64(int64(v)) case int32: return FormatLogfmtInt64(int64(v)) @@ -371,8 +376,6 @@ func formatLogfmtValue(value interface{}, term bool) string { return FormatLogfmtInt64(v) case uint: return FormatLogfmtUint64(uint64(v)) - case uint16: - return FormatLogfmtUint64(uint64(v)) case uint32: return FormatLogfmtUint64(uint64(v)) case uint64: @@ -384,7 +387,7 @@ func formatLogfmtValue(value interface{}, term bool) string { } } -// FormatLogfmtInt64 formats a potentially big number in a friendlier split format. +// FormatLogfmtInt64 formats n with thousand separators. func FormatLogfmtInt64(n int64) string { if n < 0 { return formatLogfmtUint64(uint64(-n), true) @@ -392,7 +395,7 @@ func FormatLogfmtInt64(n int64) string { return formatLogfmtUint64(uint64(n), false) } -// FormatLogfmtUint64 formats a potentially big number in a friendlier split format. +// FormatLogfmtUint64 formats n with thousand separators. func FormatLogfmtUint64(n uint64) string { return formatLogfmtUint64(n, false) } @@ -431,31 +434,38 @@ func formatLogfmtUint64(n uint64, neg bool) string { return string(out[i+1:]) } -var big1000 = big.NewInt(1000) - -// formatLogfmtBigInt formats a potentially gigantic number in a friendlier split -// format. +// formatLogfmtBigInt formats n with thousand separators. func formatLogfmtBigInt(n *big.Int) string { - // Most number don't need fancy handling, just downcast if n.IsUint64() { return FormatLogfmtUint64(n.Uint64()) } if n.IsInt64() { return FormatLogfmtInt64(n.Int64()) } - // Ok, huge number needs huge effort - groups := make([]string, 0, 8) // random initial size to cover most cases - for n.Cmp(big1000) >= 0 { - _, mod := n.DivMod(n, big1000, nil) - groups = append(groups, fmt.Sprintf("%03d", mod)) - } - groups = append(groups, n.String()) - last := len(groups) - 1 - for i := 0; i < len(groups)/2; i++ { - groups[i], groups[last-i] = groups[last-i], groups[i] + var ( + text = n.String() + buf = make([]byte, len(text)+len(text)/3) + comma = 0 + i = len(buf) - 1 + ) + for j := len(text) - 1; j >= 0; j, i = j-1, i-1 { + c := text[j] + + switch { + case c == '-': + buf[i] = c + case comma == 3: + buf[i] = ',' + i-- + comma = 0 + fallthrough + default: + buf[i] = c + comma++ + } } - return strings.Join(groups, ",") + return string(buf[i+1:]) } // escapeString checks if the provided string needs escaping/quoting, and diff --git a/log/format_test.go b/log/format_test.go index 348b265c9b..d7e0a95768 100644 --- a/log/format_test.go +++ b/log/format_test.go @@ -2,6 +2,7 @@ package log import ( "math" + "math/big" "math/rand" "testing" ) @@ -58,6 +59,25 @@ func TestPrettyUint64(t *testing.T) { } } +func TestPrettyBigInt(t *testing.T) { + tests := []struct { + int string + s string + }{ + {"111222333444555678999", "111,222,333,444,555,678,999"}, + {"-111222333444555678999", "-111,222,333,444,555,678,999"}, + {"11122233344455567899900", "11,122,233,344,455,567,899,900"}, + {"-11122233344455567899900", "-11,122,233,344,455,567,899,900"}, + } + + for _, tt := range tests { + v, _ := new(big.Int).SetString(tt.int, 10) + if have := formatLogfmtBigInt(v); have != tt.s { + t.Errorf("invalid output %s, want %s", have, tt.s) + } + } +} + var sink string func BenchmarkPrettyInt64Logfmt(b *testing.B) { From 65689e7fcea8f3a813aea659e1b49f9052883ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 16 Apr 2021 09:52:33 +0200 Subject: [PATCH 462/948] les/vflux/server: fix priority cornercase causing fuzzer timeout (#22650) * les/vflux/server: fix estimatePriority corner case * les/vflux/server: simplify inactiveAllowance == 0 case --- les/vflux/server/balance.go | 10 +++++++--- les/vflux/server/balance_test.go | 6 +++--- les/vflux/server/clientpool.go | 9 +-------- les/vflux/server/clientpool_test.go | 11 ++++++++--- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go index 01e645a16a..2bc1ddd189 100644 --- a/les/vflux/server/balance.go +++ b/les/vflux/server/balance.go @@ -358,11 +358,15 @@ func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future if bias > 0 { b = n.reducedBalance(b, now+mclock.AbsTime(future), bias, capacity, 0) } - // Note: we subtract one from the estimated priority in order to ensure that biased - // estimates are always lower than actual priorities, even if the bias is very small. + pri := n.balanceToPriority(now, b, capacity) + // Ensure that biased estimates are always lower than actual priorities, even if + // the bias is very small. // This ensures that two nodes will not ping-pong update signals forever if both of // them have zero estimated priority drop in the projected future. - pri := n.balanceToPriority(now, b, capacity) - 1 + current := n.balanceToPriority(now, n.balance, capacity) + if pri >= current { + pri = current - 1 + } if update { n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) } diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go index 66f0d1f301..5af89c18ab 100644 --- a/les/vflux/server/balance_test.go +++ b/les/vflux/server/balance_test.go @@ -283,7 +283,7 @@ func TestEstimatedPriority(t *testing.T) { {time.Second, 3 * time.Second, 1000000000, 48}, // All positive balance is used up - {time.Second * 55, 0, 0, 0}, + {time.Second * 55, 0, 0, -1}, // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29}, @@ -292,8 +292,8 @@ func TestEstimatedPriority(t *testing.T) { b.clock.Run(i.runTime) node.RequestServed(i.reqCost) priority := node.estimatePriority(1000000000, 0, i.futureTime, 0, false) - if priority != i.priority-1 { - t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority-1, priority) + if priority != i.priority { + t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) } } } diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go index 2e5fdd0ee7..079d511704 100644 --- a/les/vflux/server/clientpool.go +++ b/les/vflux/server/clientpool.go @@ -103,14 +103,7 @@ func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias t if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { timeout = c.InactiveAllowance() } - if timeout > 0 { - ns.AddTimeout(node, setup.inactiveFlag, timeout) - } else { - // Note: if capacity is immediately available then priorityPool will set the active - // flag simultaneously with removing the inactive flag and therefore this will not - // initiate disconnection - ns.SetStateSub(node, nodestate.Flags{}, setup.inactiveFlag, 0) - } + ns.AddTimeout(node, setup.inactiveFlag, timeout) } if oldState.Equals(setup.inactiveFlag) && newState.Equals(setup.inactiveFlag.Or(setup.priorityFlag)) { ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) // priority gained; remove timeout diff --git a/les/vflux/server/clientpool_test.go b/les/vflux/server/clientpool_test.go index 9503121697..0953e9c800 100644 --- a/les/vflux/server/clientpool_test.go +++ b/les/vflux/server/clientpool_test.go @@ -326,12 +326,13 @@ func TestPaidClientKickedOut(t *testing.T) { if cap := connect(pool, newPoolTestPeer(11, kickedCh)); cap == 0 { t.Fatalf("Free client should be accepted") } + clock.Run(0) select { case id := <-kickedCh: if id != 0 { t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id) } - case <-time.NewTimer(time.Second).C: + default: t.Fatalf("timeout") } } @@ -399,23 +400,27 @@ func TestFreeClientKickedOut(t *testing.T) { if cap := connect(pool, newPoolTestPeer(10, kicked)); cap != 0 { t.Fatalf("New free client should be rejected") } + clock.Run(0) select { case <-kicked: - case <-time.NewTimer(time.Second).C: + default: t.Fatalf("timeout") } disconnect(pool, newPoolTestPeer(10, kicked)) clock.Run(5 * time.Minute) for i := 0; i < 10; i++ { connect(pool, newPoolTestPeer(i+10, kicked)) + } + clock.Run(0) + for i := 0; i < 10; i++ { select { case id := <-kicked: if id >= 10 { t.Fatalf("Old client should be kicked, now got: %d", id) } - case <-time.NewTimer(time.Second).C: + default: t.Fatalf("timeout") } } From 4f3ba6742fbf1cc585048fcc45072523c6de173d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 16 Apr 2021 14:21:01 +0200 Subject: [PATCH 463/948] trie: make stacktrie not mutate input values (#22673) The stacktrie is a bit un-untuitive, API-wise: since it mutates input values. Such behaviour is dangerous, and easy to get wrong if the calling code 'forgets' this quirk. The behaviour is fixed by this PR, so that the input values are not modified by the stacktrie. Note: just as with the Trie, the stacktrie still references the live input objects, so it's still _not_ safe to mutate the values form the callsite. --- trie/stacktrie.go | 16 +++++-------- trie/stacktrie_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/trie/stacktrie.go b/trie/stacktrie.go index a198eb204b..c34ac6c78c 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -346,8 +346,7 @@ func (st *StackTrie) hash() { panic(err) } case emptyNode: - st.val = st.val[:0] - st.val = append(st.val, emptyRoot[:]...) + st.val = emptyRoot.Bytes() st.key = st.key[:0] st.nodeType = hashedNode return @@ -357,17 +356,12 @@ func (st *StackTrie) hash() { st.key = st.key[:0] st.nodeType = hashedNode if len(h.tmp) < 32 { - st.val = st.val[:0] - st.val = append(st.val, h.tmp...) + st.val = common.CopyBytes(h.tmp) return } - // Going to write the hash to the 'val'. Need to ensure it's properly sized first - // Typically, 'branchNode's will have no 'val', and require this allocation - if required := 32 - len(st.val); required > 0 { - buf := make([]byte, required) - st.val = append(st.val, buf...) - } - st.val = st.val[:32] + // Write the hash to the 'val'. We allocate a new val here to not mutate + // input values + st.val = make([]byte, 32) h.sha.Reset() h.sha.Write(h.tmp) h.sha.Read(st.val) diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index 29706f2e9d..50a4eda323 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -1,9 +1,12 @@ package trie import ( + "bytes" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" ) @@ -119,3 +122,51 @@ func TestUpdateVariableKeys(t *testing.T) { t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) } } + +// TestStacktrieNotModifyValues checks that inserting blobs of data into the +// stacktrie does not mutate the blobs +func TestStacktrieNotModifyValues(t *testing.T) { + st := NewStackTrie(nil) + { // Test a very small trie + // Give it the value as a slice with large backing alloc, + // so if the stacktrie tries to append, it won't have to realloc + value := make([]byte, 1, 100) + value[0] = 0x2 + want := common.CopyBytes(value) + st.TryUpdate([]byte{0x01}, value) + st.Hash() + if have := value; !bytes.Equal(have, want) { + t.Fatalf("tiny trie: have %#x want %#x", have, want) + } + st = NewStackTrie(nil) + } + // Test with a larger trie + keyB := big.NewInt(1) + keyDelta := big.NewInt(1) + var vals [][]byte + getValue := func(i int) []byte { + if i%2 == 0 { // large + return crypto.Keccak256(big.NewInt(int64(i)).Bytes()) + } else { //small + return big.NewInt(int64(i)).Bytes() + } + } + + for i := 0; i < 1000; i++ { + key := common.BigToHash(keyB) + value := getValue(i) + st.TryUpdate(key.Bytes(), value) + vals = append(vals, value) + keyB = keyB.Add(keyB, keyDelta) + keyDelta.Add(keyDelta, common.Big1) + } + st.Hash() + for i := 0; i < 1000; i++ { + want := getValue(i) + + have := vals[i] + if !bytes.Equal(have, want) { + t.Fatalf("item %d, have %#x want %#x", i, have, want) + } + } +} From 09d44e9925a5a3f983ae1861f7ecdf69f49ab888 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 16 Apr 2021 14:58:23 +0200 Subject: [PATCH 464/948] core/state/snapshot: avoid copybytes for stacktrie --- core/state/snapshot/generate.go | 2 +- trie/trie_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 98c8d42a1a..ed431fcb3d 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -317,7 +317,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix if origin == nil && !diskMore { stackTr := trie.NewStackTrie(nil) for i, key := range keys { - stackTr.TryUpdate(key, common.CopyBytes(vals[i])) + stackTr.TryUpdate(key, vals[i]) } if gotRoot := stackTr.Hash(); gotRoot != root { return &proofResult{ diff --git a/trie/trie_test.go b/trie/trie_test.go index d6930fdee7..492b423c2f 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -830,8 +830,8 @@ func TestCommitSequenceStackTrie(t *testing.T) { val = make([]byte, 1+prng.Intn(1024)) } prng.Read(val) - trie.TryUpdate(key, common.CopyBytes(val)) - stTrie.TryUpdate(key, common.CopyBytes(val)) + trie.TryUpdate(key, val) + stTrie.TryUpdate(key, val) } // Flush trie -> database root, _ := trie.Commit(nil) From f79cce5de98332e6d54eb298fce85c69d1082ee2 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 16 Apr 2021 21:29:22 +0200 Subject: [PATCH 465/948] eth/catalyst: add catalyst API prototype (#22641) This change adds the --catalyst flag, enabling an RPC API for eth2 integration. In this initial version, catalyst mode also disables all peer-to-peer networking. Co-authored-by: Mikhail Kalinin Co-authored-by: Felix Lange --- cmd/geth/config.go | 13 +- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 18 +- consensus/ethash/consensus.go | 2 + core/blockchain.go | 16 ++ eth/backend.go | 1 + eth/catalyst/api.go | 302 ++++++++++++++++++++++++++++++++ eth/catalyst/api_test.go | 229 ++++++++++++++++++++++++ eth/catalyst/api_types.go | 70 ++++++++ eth/catalyst/gen_blockparams.go | 46 +++++ eth/catalyst/gen_ed.go | 117 +++++++++++++ params/config.go | 19 +- 13 files changed, 823 insertions(+), 12 deletions(-) create mode 100644 eth/catalyst/api.go create mode 100644 eth/catalyst/api_test.go create mode 100644 eth/catalyst/api_types.go create mode 100644 eth/catalyst/gen_blockparams.go create mode 100644 eth/catalyst/gen_ed.go diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 6fc75363c6..c867877ee6 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -28,6 +28,7 @@ import ( "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/metrics" @@ -143,7 +144,17 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) { cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name)) } - backend := utils.RegisterEthService(stack, &cfg.Eth) + backend, eth := utils.RegisterEthService(stack, &cfg.Eth) + + // Configure catalyst. + if ctx.GlobalBool(utils.CatalystFlag.Name) { + if eth == nil { + utils.Fatalf("Catalyst does not work in light client mode.") + } + if err := catalyst.Register(stack, eth); err != nil { + utils.Fatalf("%v", err) + } + } // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 12a5ae9bfc..78e65161da 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -151,6 +151,7 @@ var ( utils.EVMInterpreterFlag, utils.MinerNotifyFullFlag, configFileFlag, + utils.CatalystFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index d7f8fd7ab9..980794db73 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -235,6 +235,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.SnapshotFlag, utils.BloomFilterSizeFlag, cli.HelpFlag, + utils.CatalystFlag, }, }, } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d631b8e332..59cf32c983 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -755,6 +755,11 @@ var ( Usage: "External EVM configuration (default = built-in interpreter)", Value: "", } + + CatalystFlag = cli.BoolFlag{ + Name: "catalyst", + Usage: "Catalyst mode (eth2 integration testing)", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1186,10 +1191,11 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { cfg.NetRestrict = list } - if ctx.GlobalBool(DeveloperFlag.Name) { + if ctx.GlobalBool(DeveloperFlag.Name) || ctx.GlobalBool(CatalystFlag.Name) { // --dev mode can't use p2p networking. cfg.MaxPeers = 0 - cfg.ListenAddr = ":0" + cfg.ListenAddr = "" + cfg.NoDial = true cfg.NoDiscovery = true cfg.DiscoveryV5 = false } @@ -1693,14 +1699,16 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend { +// The second return value is the full node instance, which may be nil if the +// node is running as a light client. +func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) { if cfg.SyncMode == downloader.LightSync { backend, err := les.New(stack, cfg) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) - return backend.ApiBackend + return backend.ApiBackend, nil } backend, err := eth.New(stack, cfg) if err != nil { @@ -1713,7 +1721,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend } } stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) - return backend.APIBackend + return backend.APIBackend, backend } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 011a5688ef..e23bd824af 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -315,6 +315,8 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uin func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { next := new(big.Int).Add(parent.Number, big1) switch { + case config.IsCatalyst(next): + return big.NewInt(1) case config.IsMuirGlacier(next): return calcDifficultyEip2384(time, parent) case config.IsConstantinople(next): diff --git a/core/blockchain.go b/core/blockchain.go index 8c3d940a69..dfbc5ad0bb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1693,6 +1693,22 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { return n, err } +// InsertChainWithoutSealVerification works exactly the same +// except for seal verification, seal verification is omitted +func (bc *BlockChain) InsertChainWithoutSealVerification(block *types.Block) (int, error) { + bc.blockProcFeed.Send(true) + defer bc.blockProcFeed.Send(false) + + // Pre-checks passed, start the full block imports + bc.wg.Add(1) + bc.chainmu.Lock() + n, err := bc.insertChain(types.Blocks([]*types.Block{block}), false) + bc.chainmu.Unlock() + bc.wg.Done() + + return n, err +} + // insertChain is the internal implementation of InsertChain, which assumes that // 1) chains are contiguous, and 2) The chain mutex is held. // diff --git a/eth/backend.go b/eth/backend.go index 9cf8b85663..4c7374612e 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -223,6 +223,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { }); err != nil { return nil, err } + eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go new file mode 100644 index 0000000000..d6ea691d02 --- /dev/null +++ b/eth/catalyst/api.go @@ -0,0 +1,302 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package catalyst implements the temporary eth1/eth2 RPC integration. +package catalyst + +import ( + "errors" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + chainParams "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +// Register adds catalyst APIs to the node. +func Register(stack *node.Node, backend *eth.Ethereum) error { + chainconfig := backend.BlockChain().Config() + if chainconfig.CatalystBlock == nil { + return errors.New("catalystBlock is not set in genesis config") + } else if chainconfig.CatalystBlock.Sign() != 0 { + return errors.New("catalystBlock of genesis config must be zero") + } + + log.Warn("Catalyst mode enabled") + stack.RegisterAPIs([]rpc.API{ + { + Namespace: "consensus", + Version: "1.0", + Service: newConsensusAPI(backend), + Public: true, + }, + }) + return nil +} + +type consensusAPI struct { + eth *eth.Ethereum +} + +func newConsensusAPI(eth *eth.Ethereum) *consensusAPI { + return &consensusAPI{eth: eth} +} + +// blockExecutionEnv gathers all the data required to execute +// a block, either when assembling it or when inserting it. +type blockExecutionEnv struct { + chain *core.BlockChain + state *state.StateDB + tcount int + gasPool *core.GasPool + + header *types.Header + txs []*types.Transaction + receipts []*types.Receipt +} + +func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error { + vmconfig := *env.chain.GetVMConfig() + receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig) + if err != nil { + return err + } + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return nil +} + +func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) { + state, err := api.eth.BlockChain().StateAt(parent.Root()) + if err != nil { + return nil, err + } + env := &blockExecutionEnv{ + chain: api.eth.BlockChain(), + state: state, + header: header, + gasPool: new(core.GasPool).AddGas(header.GasLimit), + } + return env, nil +} + +// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution +// data" required for eth2 clients to process the new block. +func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableData, error) { + log.Info("Producing block", "parentHash", params.ParentHash) + + bc := api.eth.BlockChain() + parent := bc.GetBlockByHash(params.ParentHash) + pool := api.eth.TxPool() + + if parent.Time() >= params.Timestamp { + return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp) + } + if now := uint64(time.Now().Unix()); params.Timestamp > now+1 { + wait := time.Duration(params.Timestamp-now) * time.Second + log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait)) + time.Sleep(wait) + } + + pending, err := pool.Pending() + if err != nil { + return nil, err + } + + coinbase, err := api.eth.Etherbase() + if err != nil { + return nil, err + } + num := parent.Number() + header := &types.Header{ + ParentHash: parent.Hash(), + Number: num.Add(num, common.Big1), + Coinbase: coinbase, + GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype + Extra: []byte{}, + Time: params.Timestamp, + } + err = api.eth.Engine().Prepare(bc, header) + if err != nil { + return nil, err + } + + env, err := api.makeEnv(parent, header) + if err != nil { + return nil, err + } + + var ( + signer = types.MakeSigner(bc.Config(), header.Number) + txHeap = types.NewTransactionsByPriceAndNonce(signer, pending) + transactions []*types.Transaction + ) + for { + if env.gasPool.Gas() < chainParams.TxGas { + log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas) + break + } + tx := txHeap.Peek() + if tx == nil { + break + } + + // The sender is only for logging purposes, and it doesn't really matter if it's correct. + from, _ := types.Sender(signer, tx) + + // Execute the transaction + env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount) + err = env.commitTransaction(tx, coinbase) + switch err { + case core.ErrGasLimitReached: + // Pop the current out-of-gas transaction without shifting in the next from the account + log.Trace("Gas limit exceeded for current block", "sender", from) + txHeap.Pop() + + case core.ErrNonceTooLow: + // New head notification data race between the transaction pool and miner, shift + log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) + txHeap.Shift() + + case core.ErrNonceTooHigh: + // Reorg notification data race between the transaction pool and miner, skip account = + log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce()) + txHeap.Pop() + + case nil: + // Everything ok, collect the logs and shift in the next transaction from the same account + env.tcount++ + txHeap.Shift() + transactions = append(transactions, tx) + + default: + // Strange error, discard the transaction and get the next in line (note, the + // nonce-too-high clause will prevent us from executing in vain). + log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) + txHeap.Shift() + } + } + + // Create the block. + block, err := api.eth.Engine().FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts) + if err != nil { + return nil, err + } + return &executableData{ + BlockHash: block.Hash(), + ParentHash: block.ParentHash(), + Miner: block.Coinbase(), + StateRoot: block.Root(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + Timestamp: block.Time(), + ReceiptRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + Transactions: encodeTransactions(block.Transactions()), + }, nil +} + +func encodeTransactions(txs []*types.Transaction) [][]byte { + var enc = make([][]byte, len(txs)) + for i, tx := range txs { + enc[i], _ = tx.MarshalBinary() + } + return enc +} + +func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { + var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { + var tx types.Transaction + if err := tx.UnmarshalBinary(encTx); err != nil { + return nil, fmt.Errorf("invalid transaction %d: %v", i, err) + } + txs[i] = &tx + } + return txs, nil +} + +func insertBlockParamsToBlock(params executableData) (*types.Block, error) { + txs, err := decodeTransactions(params.Transactions) + if err != nil { + return nil, err + } + + number := big.NewInt(0) + number.SetUint64(params.Number) + header := &types.Header{ + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.Miner, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: big.NewInt(1), + Number: number, + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + } + block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) + return block, nil +} + +// NewBlock creates an Eth1 block, inserts it in the chain, and either returns true, +// or false + an error. This is a bit redundant for go, but simplifies things on the +// eth2 side. +func (api *consensusAPI) NewBlock(params executableData) (*newBlockResponse, error) { + parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash) + if parent == nil { + return &newBlockResponse{false}, fmt.Errorf("could not find parent %x", params.ParentHash) + } + block, err := insertBlockParamsToBlock(params) + if err != nil { + return nil, err + } + + _, err = api.eth.BlockChain().InsertChainWithoutSealVerification(block) + return &newBlockResponse{err == nil}, err +} + +// Used in tests to add a the list of transactions from a block to the tx pool. +func (api *consensusAPI) addBlockTxs(block *types.Block) error { + for _, tx := range block.Transactions() { + api.eth.TxPool().AddLocal(tx) + } + return nil +} + +// FinalizeBlock is called to mark a block as synchronized, so +// that data that is no longer needed can be removed. +func (api *consensusAPI) FinalizeBlock(blockHash common.Hash) (*genericResponse, error) { + return &genericResponse{true}, nil +} + +// SetHead is called to perform a force choice. +func (api *consensusAPI) SetHead(newHead common.Hash) (*genericResponse, error) { + return &genericResponse{true}, nil +} diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go new file mode 100644 index 0000000000..456b6867bd --- /dev/null +++ b/eth/catalyst/api_test.go @@ -0,0 +1,229 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + + testBalance = big.NewInt(2e10) +) + +func generateTestChain() (*core.Genesis, []*types.Block) { + db := rawdb.NewMemoryDatabase() + config := params.AllEthashProtocolChanges + genesis := &core.Genesis{ + Config: config, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + } + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + } + gblock := genesis.ToBlock(db) + engine := ethash.NewFaker() + blocks, _ := core.GenerateChain(config, gblock, engine, db, 10, generate) + blocks = append([]*types.Block{gblock}, blocks...) + return genesis, blocks +} + +func generateTestChainWithFork(n int, fork int) (*core.Genesis, []*types.Block, []*types.Block) { + if fork >= n { + fork = n - 1 + } + db := rawdb.NewMemoryDatabase() + //nolint:composites + config := ¶ms.ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, big.NewInt(0), new(params.EthashConfig), nil} + genesis := &core.Genesis{ + Config: config, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + } + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + } + generateFork := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("testF")) + } + gblock := genesis.ToBlock(db) + engine := ethash.NewFaker() + blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate) + blocks = append([]*types.Block{gblock}, blocks...) + forkedBlocks, _ := core.GenerateChain(config, blocks[fork], engine, db, n-fork, generateFork) + return genesis, blocks, forkedBlocks +} + +func TestEth2AssembleBlock(t *testing.T) { + genesis, blocks := generateTestChain() + n, ethservice := startEthService(t, genesis, blocks[1:9]) + defer n.Close() + + api := newConsensusAPI(ethservice) + signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID) + tx, err := types.SignTx(types.NewTransaction(0, blocks[8].Coinbase(), big.NewInt(1000), params.TxGas, nil, nil), signer, testKey) + if err != nil { + t.Fatalf("error signing transaction, err=%v", err) + } + ethservice.TxPool().AddLocal(tx) + blockParams := assembleBlockParams{ + ParentHash: blocks[8].ParentHash(), + Timestamp: blocks[8].Time(), + } + execData, err := api.AssembleBlock(blockParams) + + if err != nil { + t.Fatalf("error producing block, err=%v", err) + } + + if len(execData.Transactions) != 1 { + t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions)) + } +} + +func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { + genesis, blocks := generateTestChain() + n, ethservice := startEthService(t, genesis, blocks[1:9]) + defer n.Close() + + api := newConsensusAPI(ethservice) + + // Put the 10th block's tx in the pool and produce a new block + api.addBlockTxs(blocks[9]) + blockParams := assembleBlockParams{ + ParentHash: blocks[9].ParentHash(), + Timestamp: blocks[9].Time(), + } + execData, err := api.AssembleBlock(blockParams) + if err != nil { + t.Fatalf("error producing block, err=%v", err) + } + + if len(execData.Transactions) != blocks[9].Transactions().Len() { + t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions)) + } +} + +func TestEth2NewBlock(t *testing.T) { + genesis, blocks, forkedBlocks := generateTestChainWithFork(10, 4) + n, ethservice := startEthService(t, genesis, blocks[1:5]) + defer n.Close() + + api := newConsensusAPI(ethservice) + for i := 5; i < 10; i++ { + p := executableData{ + ParentHash: ethservice.BlockChain().CurrentBlock().Hash(), + Miner: blocks[i].Coinbase(), + StateRoot: blocks[i].Root(), + GasLimit: blocks[i].GasLimit(), + GasUsed: blocks[i].GasUsed(), + Transactions: encodeTransactions(blocks[i].Transactions()), + ReceiptRoot: blocks[i].ReceiptHash(), + LogsBloom: blocks[i].Bloom().Bytes(), + BlockHash: blocks[i].Hash(), + Timestamp: blocks[i].Time(), + Number: uint64(i), + } + success, err := api.NewBlock(p) + if err != nil || !success.Valid { + t.Fatalf("Failed to insert block: %v", err) + } + } + + exp := ethservice.BlockChain().CurrentBlock().Hash() + + // Introduce the fork point. + lastBlockNum := blocks[4].Number() + lastBlock := blocks[4] + for i := 0; i < 4; i++ { + lastBlockNum.Add(lastBlockNum, big.NewInt(1)) + p := executableData{ + ParentHash: lastBlock.Hash(), + Miner: forkedBlocks[i].Coinbase(), + StateRoot: forkedBlocks[i].Root(), + Number: lastBlockNum.Uint64(), + GasLimit: forkedBlocks[i].GasLimit(), + GasUsed: forkedBlocks[i].GasUsed(), + Transactions: encodeTransactions(blocks[i].Transactions()), + ReceiptRoot: forkedBlocks[i].ReceiptHash(), + LogsBloom: forkedBlocks[i].Bloom().Bytes(), + BlockHash: forkedBlocks[i].Hash(), + Timestamp: forkedBlocks[i].Time(), + } + success, err := api.NewBlock(p) + if err != nil || !success.Valid { + t.Fatalf("Failed to insert forked block #%d: %v", i, err) + } + lastBlock, err = insertBlockParamsToBlock(p) + if err != nil { + t.Fatal(err) + } + } + + if ethservice.BlockChain().CurrentBlock().Hash() != exp { + t.Fatalf("Wrong head after inserting fork %x != %x", exp, ethservice.BlockChain().CurrentBlock().Hash()) + } +} + +// startEthService creates a full node instance for testing. +func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) { + t.Helper() + + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatal("can't create node:", err) + } + + ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}} + ethservice, err := eth.New(n, ethcfg) + if err != nil { + t.Fatal("can't create eth service:", err) + } + if err := n.Start(); err != nil { + t.Fatal("can't start node:", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil { + n.Close() + t.Fatal("can't import test blocks:", err) + } + ethservice.SetEtherbase(testAddr) + + return n, ethservice +} diff --git a/eth/catalyst/api_types.go b/eth/catalyst/api_types.go new file mode 100644 index 0000000000..d5d351a991 --- /dev/null +++ b/eth/catalyst/api_types.go @@ -0,0 +1,70 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +//go:generate go run github.com/fjl/gencodec -type assembleBlockParams -field-override assembleBlockParamsMarshaling -out gen_blockparams.go + +// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ +type assembleBlockParams struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + Timestamp uint64 `json:"timestamp" gencodec:"required"` +} + +// JSON type overrides for assembleBlockParams. +type assembleBlockParamsMarshaling struct { + Timestamp hexutil.Uint64 +} + +//go:generate go run github.com/fjl/gencodec -type executableData -field-override executableDataMarshaling -out gen_ed.go + +// Structure described at https://notes.ethereum.org/@n0ble/rayonism-the-merge-spec#Parameters1 +type executableData struct { + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + Miner common.Address `json:"miner" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + Number uint64 `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Timestamp uint64 `json:"timestamp" gencodec:"required"` + ReceiptRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom []byte `json:"logsBloom" gencodec:"required"` + Transactions [][]byte `json:"transactions" gencodec:"required"` +} + +// JSON type overrides for executableData. +type executableDataMarshaling struct { + Number hexutil.Uint64 + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + Timestamp hexutil.Uint64 + LogsBloom hexutil.Bytes + Transactions []hexutil.Bytes +} + +type newBlockResponse struct { + Valid bool `json:"valid"` +} + +type genericResponse struct { + Success bool `json:"success"` +} diff --git a/eth/catalyst/gen_blockparams.go b/eth/catalyst/gen_blockparams.go new file mode 100644 index 0000000000..a9a08ec3a8 --- /dev/null +++ b/eth/catalyst/gen_blockparams.go @@ -0,0 +1,46 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package catalyst + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*assembleBlockParamsMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (a assembleBlockParams) MarshalJSON() ([]byte, error) { + type assembleBlockParams struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + } + var enc assembleBlockParams + enc.ParentHash = a.ParentHash + enc.Timestamp = hexutil.Uint64(a.Timestamp) + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (a *assembleBlockParams) UnmarshalJSON(input []byte) error { + type assembleBlockParams struct { + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + } + var dec assembleBlockParams + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for assembleBlockParams") + } + a.ParentHash = *dec.ParentHash + if dec.Timestamp == nil { + return errors.New("missing required field 'timestamp' for assembleBlockParams") + } + a.Timestamp = uint64(*dec.Timestamp) + return nil +} diff --git a/eth/catalyst/gen_ed.go b/eth/catalyst/gen_ed.go new file mode 100644 index 0000000000..4c2e4c8ead --- /dev/null +++ b/eth/catalyst/gen_ed.go @@ -0,0 +1,117 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package catalyst + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*executableDataMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (e executableData) MarshalJSON() ([]byte, error) { + type executableData struct { + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + Miner common.Address `json:"miner" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + Number hexutil.Uint64 `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ReceiptRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + } + var enc executableData + enc.BlockHash = e.BlockHash + enc.ParentHash = e.ParentHash + enc.Miner = e.Miner + enc.StateRoot = e.StateRoot + enc.Number = hexutil.Uint64(e.Number) + enc.GasLimit = hexutil.Uint64(e.GasLimit) + enc.GasUsed = hexutil.Uint64(e.GasUsed) + enc.Timestamp = hexutil.Uint64(e.Timestamp) + enc.ReceiptRoot = e.ReceiptRoot + enc.LogsBloom = e.LogsBloom + if e.Transactions != nil { + enc.Transactions = make([]hexutil.Bytes, len(e.Transactions)) + for k, v := range e.Transactions { + enc.Transactions[k] = v + } + } + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *executableData) UnmarshalJSON(input []byte) error { + type executableData struct { + BlockHash *common.Hash `json:"blockHash" gencodec:"required"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + Miner *common.Address `json:"miner" gencodec:"required"` + StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` + Number *hexutil.Uint64 `json:"number" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ReceiptRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + } + var dec executableData + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.BlockHash == nil { + return errors.New("missing required field 'blockHash' for executableData") + } + e.BlockHash = *dec.BlockHash + if dec.ParentHash == nil { + return errors.New("missing required field 'parentHash' for executableData") + } + e.ParentHash = *dec.ParentHash + if dec.Miner == nil { + return errors.New("missing required field 'miner' for executableData") + } + e.Miner = *dec.Miner + if dec.StateRoot == nil { + return errors.New("missing required field 'stateRoot' for executableData") + } + e.StateRoot = *dec.StateRoot + if dec.Number == nil { + return errors.New("missing required field 'number' for executableData") + } + e.Number = uint64(*dec.Number) + if dec.GasLimit == nil { + return errors.New("missing required field 'gasLimit' for executableData") + } + e.GasLimit = uint64(*dec.GasLimit) + if dec.GasUsed == nil { + return errors.New("missing required field 'gasUsed' for executableData") + } + e.GasUsed = uint64(*dec.GasUsed) + if dec.Timestamp == nil { + return errors.New("missing required field 'timestamp' for executableData") + } + e.Timestamp = uint64(*dec.Timestamp) + if dec.ReceiptRoot == nil { + return errors.New("missing required field 'receiptsRoot' for executableData") + } + e.ReceiptRoot = *dec.ReceiptRoot + if dec.LogsBloom == nil { + return errors.New("missing required field 'logsBloom' for executableData") + } + e.LogsBloom = *dec.LogsBloom + if dec.Transactions == nil { + return errors.New("missing required field 'transactions' for executableData") + } + e.Transactions = make([][]byte, len(dec.Transactions)) + for k, v := range dec.Transactions { + e.Transactions[k] = v + } + return nil +} diff --git a/params/config.go b/params/config.go index 9fca534c79..f4e2f5ea67 100644 --- a/params/config.go +++ b/params/config.go @@ -244,16 +244,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -326,8 +326,9 @@ type ChainConfig struct { MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) - YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references - EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) + YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references + EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) + CatalystBlock *big.Int `json:"catalystBlock,omitempty"` // Catalyst switch block (nil = no fork, 0 = already on catalyst) // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` @@ -440,6 +441,11 @@ func (c *ChainConfig) IsBerlin(num *big.Int) bool { return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num) } +// IsCatalyst returns whether num is either equal to the Merge fork block or greater. +func (c *ChainConfig) IsCatalyst(num *big.Int) bool { + return isForked(c.CatalystBlock, num) +} + // IsEWASM returns whether num represents a block number after the EWASM fork func (c *ChainConfig) IsEWASM(num *big.Int) bool { return isForked(c.EWASMBlock, num) @@ -623,7 +629,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsBerlin bool + IsBerlin, IsCatalyst bool } // Rules ensures c's ChainID is not nil. @@ -643,5 +649,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), + IsCatalyst: c.IsCatalyst(num), } } From 424656519a0bfc0841b112c935279c787cecd904 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 19 Apr 2021 14:54:38 +0200 Subject: [PATCH 466/948] cmd/devp2p: add support for -limit option in nodeset filter command (#22694) The new -limit option makes the filter operate on top N nodes by score. This also adds ENR attribute stats in the nodeset info command. Node set commands are now documented in README. --- cmd/devp2p/README.md | 27 +++++++++++++-- cmd/devp2p/nodeset.go | 28 +++++++++++++++- cmd/devp2p/nodesetcmd.go | 71 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 4 deletions(-) diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index e934ee25c9..7f816b602e 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -30,6 +30,29 @@ Run `devp2p dns to-route53 ` to publish a tree to Amazon Route53. You can find more information about these commands in the [DNS Discovery Setup Guide][dns-tutorial]. +### Node Set Utilities + +There are several commands for working with JSON node set files. These files are generated +by the discovery crawlers and DNS client commands. Node sets also used as the input of the +DNS deployer commands. + +Run `devp2p nodeset info ` to display statistics of a node set. + +Run `devp2p nodeset filter ` to write a new, filtered node +set to standard output. The following filters are supported: + +- `-limit ` limits the output set to N entries, taking the top N nodes by score +- `-ip ` filters nodes by IP subnet +- `-min-age ` filters nodes by 'first seen' time +- `-eth-network ` filters nodes by "eth" ENR entry +- `-les-server` filters nodes by LES server support +- `-snap` filters nodes by snap protocol support + +For example, given a node set in `nodes.json`, you could create a filtered set containing +up to 20 eth mainnet nodes which also support snap sync using this command: + + devp2p nodeset filter nodes.json -eth-network mainnet -snap -limit 20 + ### Discovery v4 Utilities The `devp2p discv4 ...` command family deals with the [Node Discovery v4][discv4] @@ -94,7 +117,7 @@ To run the eth protocol test suite against your implementation, the node needs t geth --datadir --nodiscover --nat=none --networkid 19763 --verbosity 5 ``` -Then, run the following command, replacing `` with the enode of the geth node: +Then, run the following command, replacing `` with the enode of the geth node: ``` devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json ``` @@ -103,7 +126,7 @@ Repeat the above process (re-initialising the node) in order to run the Eth Prot #### Eth66 Test Suite -The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically. +The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically. To run the eth66 protocol test suite, initialize a geth node as described above and run the following command, replacing `` with the enode of the geth node: diff --git a/cmd/devp2p/nodeset.go b/cmd/devp2p/nodeset.go index 2d86c3f65a..1d78e34c73 100644 --- a/cmd/devp2p/nodeset.go +++ b/cmd/devp2p/nodeset.go @@ -71,6 +71,7 @@ func writeNodesJSON(file string, nodes nodeSet) { } } +// nodes returns the node records contained in the set. func (ns nodeSet) nodes() []*enode.Node { result := make([]*enode.Node, 0, len(ns)) for _, n := range ns { @@ -83,12 +84,37 @@ func (ns nodeSet) nodes() []*enode.Node { return result } +// add ensures the given nodes are present in the set. func (ns nodeSet) add(nodes ...*enode.Node) { for _, n := range nodes { - ns[n.ID()] = nodeJSON{Seq: n.Seq(), N: n} + v := ns[n.ID()] + v.N = n + v.Seq = n.Seq() + ns[n.ID()] = v } } +// topN returns the top n nodes by score as a new set. +func (ns nodeSet) topN(n int) nodeSet { + if n >= len(ns) { + return ns + } + + byscore := make([]nodeJSON, 0, len(ns)) + for _, v := range ns { + byscore = append(byscore, v) + } + sort.Slice(byscore, func(i, j int) bool { + return byscore[i].Score >= byscore[j].Score + }) + result := make(nodeSet, n) + for _, v := range byscore[:n] { + result[v.N.ID()] = v + } + return result +} + +// verify performs integrity checks on the node set. func (ns nodeSet) verify() error { for id, n := range ns { if n.N.ID() != id { diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index 33de1fdf31..848288c9cf 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -17,8 +17,12 @@ package main import ( + "errors" "fmt" "net" + "sort" + "strconv" + "strings" "time" "github.com/ethereum/go-ethereum/core/forkid" @@ -60,25 +64,64 @@ func nodesetInfo(ctx *cli.Context) error { ns := loadNodesJSON(ctx.Args().First()) fmt.Printf("Set contains %d nodes.\n", len(ns)) + showAttributeCounts(ns) return nil } +// showAttributeCounts prints the distribution of ENR attributes in a node set. +func showAttributeCounts(ns nodeSet) { + attrcount := make(map[string]int) + var attrlist []interface{} + for _, n := range ns { + r := n.N.Record() + attrlist = r.AppendElements(attrlist[:0])[1:] + for i := 0; i < len(attrlist); i += 2 { + key := attrlist[i].(string) + attrcount[key]++ + } + } + + var keys []string + var maxlength int + for key := range attrcount { + keys = append(keys, key) + if len(key) > maxlength { + maxlength = len(key) + } + } + sort.Strings(keys) + fmt.Println("ENR attribute counts:") + for _, key := range keys { + fmt.Printf("%s%s: %d\n", strings.Repeat(" ", maxlength-len(key)+1), key, attrcount[key]) + } +} + func nodesetFilter(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need nodes file as argument") } - ns := loadNodesJSON(ctx.Args().First()) + // Parse -limit. + limit, err := parseFilterLimit(ctx.Args().Tail()) + if err != nil { + return err + } + // Parse the filters. filter, err := andFilter(ctx.Args().Tail()) if err != nil { return err } + // Load nodes and apply filters. + ns := loadNodesJSON(ctx.Args().First()) result := make(nodeSet) for id, n := range ns { if filter(n) { result[id] = n } } + if limit >= 0 { + result = result.topN(limit) + } writeNodesJSON("-", result) return nil } @@ -91,6 +134,7 @@ type nodeFilterC struct { } var filterFlags = map[string]nodeFilterC{ + "-limit": {1, trueFilter}, // needed to skip over -limit "-ip": {1, ipFilter}, "-min-age": {1, minAgeFilter}, "-eth-network": {1, ethFilter}, @@ -98,6 +142,7 @@ var filterFlags = map[string]nodeFilterC{ "-snap": {0, snapFilter}, } +// parseFilters parses nodeFilters from args. func parseFilters(args []string) ([]nodeFilter, error) { var filters []nodeFilter for len(args) > 0 { @@ -118,6 +163,26 @@ func parseFilters(args []string) ([]nodeFilter, error) { return filters, nil } +// parseFilterLimit parses the -limit option in args. It returns -1 if there is no limit. +func parseFilterLimit(args []string) (int, error) { + limit := -1 + for i, arg := range args { + if arg == "-limit" { + if i == len(args)-1 { + return -1, errors.New("-limit requires an argument") + } + n, err := strconv.Atoi(args[i+1]) + if err != nil { + return -1, fmt.Errorf("invalid -limit %q", args[i+1]) + } + limit = n + } + } + return limit, nil +} + +// andFilter parses node filters in args and and returns a single filter that requires all +// of them to match. func andFilter(args []string) (nodeFilter, error) { checks, err := parseFilters(args) if err != nil { @@ -134,6 +199,10 @@ func andFilter(args []string) (nodeFilter, error) { return f, nil } +func trueFilter(args []string) (nodeFilter, error) { + return func(n nodeJSON) bool { return true }, nil +} + func ipFilter(args []string) (nodeFilter, error) { _, cidr, err := net.ParseCIDR(args[0]) if err != nil { From 653b7e959d57bea49ba3628e4336a74ef4363ce2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 19 Apr 2021 14:54:55 +0200 Subject: [PATCH 467/948] cmd/devp2p: add dns nuke-route53 command (#22695) --- cmd/devp2p/dns_route53.go | 55 +++++++++++++++++++++++++++++++-------- cmd/devp2p/dnscmd.go | 35 +++++++++++++++++++++---- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index 2b6a30e60f..1d4f975dda 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -107,22 +107,48 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { return err } log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) - records := t.ToTXT(name) changes := c.computeChanges(name, records, existing) + + // Submit to API. + comment := fmt.Sprintf("enrtree update of %s at seq %d", name, t.Seq()) + return c.submitChanges(changes, comment) +} + +// deleteDomain removes all TXT records of the given domain. +func (c *route53Client) deleteDomain(name string) error { + if err := c.checkZone(name); err != nil { + return err + } + + // Compute DNS changes. + existing, err := c.collectRecords(name) + if err != nil { + return err + } + log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) + changes := makeDeletionChanges(existing, nil) + + // Submit to API. + comment := "enrtree delete of " + name + return c.submitChanges(changes, comment) +} + +// submitChanges submits the given DNS changes to Route53. +func (c *route53Client) submitChanges(changes []types.Change, comment string) error { if len(changes) == 0 { log.Info("No DNS changes needed") return nil } - // Submit all change batches. + var err error batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) changesToCheck := make([]*route53.ChangeResourceRecordSetsOutput, len(batches)) for i, changes := range batches { log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) batch := &types.ChangeBatch{ Changes: changes, - Comment: aws.String(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq())), + Comment: aws.String(fmt.Sprintf("%s (%d/%d)", comment, i+1, len(batches))), } req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch} changesToCheck[i], err = c.api.ChangeResourceRecordSets(context.TODO(), req) @@ -151,7 +177,6 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { time.Sleep(30 * time.Second) } } - return nil } @@ -186,7 +211,8 @@ func (c *route53Client) findZoneID(name string) (string, error) { return "", errors.New("can't find zone ID for " + name) } -// computeChanges creates DNS changes for the given record. +// computeChanges creates DNS changes for the given set of DNS discovery records. +// The 'existing' arg is the set of records that already exist on Route53. func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []types.Change { // Convert all names to lowercase. lrecords := make(map[string]string, len(records)) @@ -223,16 +249,23 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e } // Iterate over the old records and delete anything stale. - for path, set := range existing { - if _, ok := records[path]; ok { + changes = append(changes, makeDeletionChanges(existing, records)...) + + // Ensure changes are in the correct order. + sortChanges(changes) + return changes +} + +// makeDeletionChanges creates record changes which delete all records not contained in 'keep'. +func makeDeletionChanges(records map[string]recordSet, keep map[string]string) []types.Change { + var changes []types.Change + for path, set := range records { + if _, ok := keep[path]; ok { continue } - // Stale entry, nuke it. - log.Info(fmt.Sprintf("Deleting %s = %q", path, strings.Join(set.values, ""))) + log.Info(fmt.Sprintf("Deleting %s = %s", path, strings.Join(set.values, ""))) changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values...)) } - - sortChanges(changes) return changes } diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index 50ab7bf983..66deef56ea 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -43,6 +43,7 @@ var ( dnsTXTCommand, dnsCloudflareCommand, dnsRoute53Command, + dnsRoute53NukeCommand, }, } dnsSyncCommand = cli.Command{ @@ -84,6 +85,18 @@ var ( route53RegionFlag, }, } + dnsRoute53NukeCommand = cli.Command{ + Name: "nuke-route53", + Usage: "Deletes DNS TXT records of a subdomain on Amazon Route53", + ArgsUsage: "", + Action: dnsNukeRoute53, + Flags: []cli.Flag{ + route53AccessKeyFlag, + route53AccessSecretFlag, + route53ZoneIDFlag, + route53RegionFlag, + }, + } ) var ( @@ -174,6 +187,9 @@ func dnsSign(ctx *cli.Context) error { return nil } +// directoryName returns the directory name of the given path. +// For example, when dir is "foo/bar", it returns "bar". +// When dir is ".", and the working directory is "example/foo", it returns "foo". func directoryName(dir string) string { abs, err := filepath.Abs(dir) if err != nil { @@ -182,7 +198,7 @@ func directoryName(dir string) string { return filepath.Base(abs) } -// dnsToTXT peforms dnsTXTCommand. +// dnsToTXT performs dnsTXTCommand. func dnsToTXT(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need tree definition directory as argument") @@ -199,9 +215,9 @@ func dnsToTXT(ctx *cli.Context) error { return nil } -// dnsToCloudflare peforms dnsCloudflareCommand. +// dnsToCloudflare performs dnsCloudflareCommand. func dnsToCloudflare(ctx *cli.Context) error { - if ctx.NArg() < 1 { + if ctx.NArg() != 1 { return fmt.Errorf("need tree definition directory as argument") } domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) @@ -212,9 +228,9 @@ func dnsToCloudflare(ctx *cli.Context) error { return client.deploy(domain, t) } -// dnsToRoute53 peforms dnsRoute53Command. +// dnsToRoute53 performs dnsRoute53Command. func dnsToRoute53(ctx *cli.Context) error { - if ctx.NArg() < 1 { + if ctx.NArg() != 1 { return fmt.Errorf("need tree definition directory as argument") } domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) @@ -225,6 +241,15 @@ func dnsToRoute53(ctx *cli.Context) error { return client.deploy(domain, t) } +// dnsNukeRoute53 performs dnsRoute53NukeCommand. +func dnsNukeRoute53(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("need domain name as argument") + } + client := newRoute53Client(ctx) + return client.deleteDomain(ctx.Args().First()) +} + // loadSigningKey loads a private key in Ethereum keystore format. func loadSigningKey(keyfile string) *ecdsa.PrivateKey { keyjson, err := ioutil.ReadFile(keyfile) From d6ffa140359d24262903962f145a1236836aeeb4 Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 20 Apr 2021 13:27:46 +0800 Subject: [PATCH 468/948] core: nuke legacy snapshot supporting (#22663) --- core/blockchain.go | 15 +- core/blockchain_snapshot_test.go | 320 +------------------------------ core/state/snapshot/journal.go | 122 +----------- core/state/snapshot/snapshot.go | 27 --- 4 files changed, 12 insertions(+), 472 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index dfbc5ad0bb..49aa1c3e86 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -207,9 +207,8 @@ type BlockChain struct { processor Processor // Block transaction processor interface vmConfig vm.Config - shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. - terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. - writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format. + shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. + terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. } // NewBlockChain returns a fully initialised block chain using information @@ -1002,14 +1001,8 @@ func (bc *BlockChain) Stop() { var snapBase common.Hash if bc.snaps != nil { var err error - if bc.writeLegacyJournal { - if snapBase, err = bc.snaps.LegacyJournal(bc.CurrentBlock().Root()); err != nil { - log.Error("Failed to journal state snapshot", "err", err) - } - } else { - if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { - log.Error("Failed to journal state snapshot", "err", err) - } + if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { + log.Error("Failed to journal state snapshot", "err", err) } } // Ensure the state of a recent block is also stored to disk before exiting. diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 70ae31f7d8..75c09b421d 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -39,7 +39,6 @@ import ( // snapshotTestBasic wraps the common testing fields in the snapshot tests. type snapshotTestBasic struct { - legacy bool // Wether write the snapshot journal in legacy format chainBlocks int // Number of blocks to generate for the canonical chain snapshotBlock uint64 // Block number of the relevant snapshot disk layer commitBlock uint64 // Block number for which to commit the state to disk @@ -104,19 +103,13 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil) } if basic.snapshotBlock > 0 && basic.snapshotBlock == point { - if basic.legacy { - // Here we commit the snapshot disk root to simulate - // committing the legacy snapshot. - rawdb.WriteSnapshotRoot(db, blocks[point-1].Root()) - } else { - // Flushing the entire snap tree into the disk, the - // relavant (a) snapshot root and (b) snapshot generator - // will be persisted atomically. - chain.snaps.Cap(blocks[point-1].Root(), 0) - diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() - if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { - t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) - } + // Flushing the entire snap tree into the disk, the + // relavant (a) snapshot root and (b) snapshot generator + // will be persisted atomically. + chain.snaps.Cap(blocks[point-1].Root(), 0) + diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() + if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { + t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) } } } @@ -129,12 +122,6 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo basic.db = db basic.gendb = gendb basic.engine = engine - - // Ugly hack, notify the chain to flush the journal in legacy format - // if it's requested. - if basic.legacy { - chain.writeLegacyJournal = true - } return chain, blocks } @@ -484,46 +471,6 @@ func TestRestartWithNewSnapshot(t *testing.T) { // Expected snapshot disk : G test := &snapshotTest{ snapshotTestBasic{ - legacy: false, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 8, - expSnapshotBottom: 0, // Initial disk layer built from genesis - }, - } - test.test(t) - test.teardown() -} - -// Tests a Geth restart with valid but "legacy" snapshot. Before the shutdown, -// all snapshot journal will be persisted correctly. In this case no snapshot -// recovery is required. -func TestRestartWithLegacySnapshot(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G - // Snapshot: G - // - // SetHead(0) - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4->C5->C6->C7->C8 - // - // Expected head header : C8 - // Expected head fast block: C8 - // Expected head block : C8 - // Expected snapshot disk : G - t.Skip("Legacy format testing is not supported") - test := &snapshotTest{ - snapshotTestBasic{ - legacy: true, chainBlocks: 8, snapshotBlock: 0, commitBlock: 0, @@ -563,7 +510,6 @@ func TestNoCommitCrashWithNewSnapshot(t *testing.T) { // Expected snapshot disk : C4 test := &crashSnapshotTest{ snapshotTestBasic{ - legacy: false, chainBlocks: 8, snapshotBlock: 4, commitBlock: 0, @@ -603,7 +549,6 @@ func TestLowCommitCrashWithNewSnapshot(t *testing.T) { // Expected snapshot disk : C4 test := &crashSnapshotTest{ snapshotTestBasic{ - legacy: false, chainBlocks: 8, snapshotBlock: 4, commitBlock: 2, @@ -643,7 +588,6 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) { // Expected snapshot disk : C4 test := &crashSnapshotTest{ snapshotTestBasic{ - legacy: false, chainBlocks: 8, snapshotBlock: 4, commitBlock: 6, @@ -658,131 +602,6 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) { test.teardown() } -// Tests a Geth was crashed and restarts with a broken and "legacy format" -// snapshot. In this case the entire legacy snapshot should be discared -// and rebuild from the new chain head. The new head here refers to the -// genesis because there is no committed point. -func TestNoCommitCrashWithLegacySnapshot(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G - // Snapshot: G, C4 - // - // CRASH - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4->C5->C6->C7->C8 - // - // Expected head header : C8 - // Expected head fast block: C8 - // Expected head block : G - // Expected snapshot disk : G - t.Skip("Legacy format testing is not supported") - test := &crashSnapshotTest{ - snapshotTestBasic{ - legacy: true, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) - }, - } - test.test(t) - test.teardown() -} - -// Tests a Geth was crashed and restarts with a broken and "legacy format" -// snapshot. In this case the entire legacy snapshot should be discared -// and rebuild from the new chain head. The new head here refers to the -// block-2 because it's committed into the disk. -func TestLowCommitCrashWithLegacySnapshot(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G, C2 - // Snapshot: G, C4 - // - // CRASH - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4->C5->C6->C7->C8 - // - // Expected head header : C8 - // Expected head fast block: C8 - // Expected head block : C2 - // Expected snapshot disk : C2 - t.Skip("Legacy format testing is not supported") - test := &crashSnapshotTest{ - snapshotTestBasic{ - legacy: true, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 2, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 2, - expSnapshotBottom: 2, // Rebuilt snapshot from the latest HEAD - }, - } - test.test(t) - test.teardown() -} - -// Tests a Geth was crashed and restarts with a broken and "legacy format" -// snapshot. In this case the entire legacy snapshot should be discared -// and rebuild from the new chain head. -// -// The new head here refers to the the genesis, the reason is: -// - the state of block-6 is committed into the disk -// - the legacy disk layer of block-4 is committed into the disk -// - the head is rewound the genesis in order to find an available -// state lower than disk layer -func TestHighCommitCrashWithLegacySnapshot(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G, C6 - // Snapshot: G, C4 - // - // CRASH - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4->C5->C6->C7->C8 - // - // Expected head header : C8 - // Expected head fast block: C8 - // Expected head block : G - // Expected snapshot disk : G - t.Skip("Legacy format testing is not supported") - test := &crashSnapshotTest{ - snapshotTestBasic{ - legacy: true, - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 6, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD(genesis) - }, - } - test.test(t) - test.teardown() -} - // Tests a Geth was running with snapshot enabled. Then restarts without // enabling snapshot and after that re-enable the snapshot again. In this // case the snapshot should be rebuilt with latest chain head. @@ -806,47 +625,6 @@ func TestGappedNewSnapshot(t *testing.T) { // Expected snapshot disk : C10 test := &gappedSnapshotTest{ snapshotTestBasic: snapshotTestBasic{ - legacy: false, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD - }, - gapped: 2, - } - test.test(t) - test.teardown() -} - -// Tests a Geth was running with leagcy snapshot enabled. Then restarts -// without enabling snapshot and after that re-enable the snapshot again. -// In this case the snapshot should be rebuilt with latest chain head. -func TestGappedLegacySnapshot(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G - // Snapshot: G - // - // SetHead(0) - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 - // - // Expected head header : C10 - // Expected head fast block: C10 - // Expected head block : C10 - // Expected snapshot disk : C10 - t.Skip("Legacy format testing is not supported") - test := &gappedSnapshotTest{ - snapshotTestBasic: snapshotTestBasic{ - legacy: true, chainBlocks: 8, snapshotBlock: 0, commitBlock: 0, @@ -885,7 +663,6 @@ func TestSetHeadWithNewSnapshot(t *testing.T) { // Expected snapshot disk : G test := &setHeadSnapshotTest{ snapshotTestBasic: snapshotTestBasic{ - legacy: false, chainBlocks: 8, snapshotBlock: 0, commitBlock: 0, @@ -901,88 +678,6 @@ func TestSetHeadWithNewSnapshot(t *testing.T) { test.teardown() } -// Tests the Geth was running with snapshot(legacy-format) enabled and resetHead -// is applied. In this case the head is rewound to the target(with state available). -// After that the chain is restarted and the original disk layer is kept. -func TestSetHeadWithLegacySnapshot(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G - // Snapshot: G - // - // SetHead(4) - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4 - // - // Expected head header : C4 - // Expected head fast block: C4 - // Expected head block : C4 - // Expected snapshot disk : G - t.Skip("Legacy format testing is not supported") - test := &setHeadSnapshotTest{ - snapshotTestBasic: snapshotTestBasic{ - legacy: true, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 4, - expHeadHeader: 4, - expHeadFastBlock: 4, - expHeadBlock: 4, - expSnapshotBottom: 0, // The initial disk layer is built from the genesis - }, - setHead: 4, - } - test.test(t) - test.teardown() -} - -// Tests the Geth was running with snapshot(legacy-format) enabled and upgrades -// the disk layer journal(journal generator) to latest format. After that the Geth -// is restarted from a crash. In this case Geth will find the new-format disk layer -// journal but with legacy-format diff journal(the new-format is never committed), -// and the invalid diff journal is expected to be dropped. -func TestRecoverSnapshotFromCrashWithLegacyDiffJournal(t *testing.T) { - // Chain: - // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) - // - // Commit: G - // Snapshot: G - // - // SetHead(0) - // - // ------------------------------ - // - // Expected in leveldb: - // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 - // - // Expected head header : C10 - // Expected head fast block: C10 - // Expected head block : C8 - // Expected snapshot disk : C10 - t.Skip("Legacy format testing is not supported") - test := &restartCrashSnapshotTest{ - snapshotTestBasic: snapshotTestBasic{ - legacy: true, - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 8, // The persisted state in the first running - expSnapshotBottom: 10, // The persisted disk layer in the second running - }, - newBlocks: 2, - } - test.test(t) - test.teardown() -} - // Tests the Geth was running with a complete snapshot and then imports a few // more new blocks on top without enabling the snapshot. After the restart, // crash happens. Check everything is ok after the restart. @@ -1006,7 +701,6 @@ func TestRecoverSnapshotFromWipingCrash(t *testing.T) { // Expected snapshot disk : C10 test := &wipeCrashSnapshotTest{ snapshotTestBasic: snapshotTestBasic{ - legacy: false, chainBlocks: 8, snapshotBlock: 4, commitBlock: 0, diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index b31e921ca9..f8cec4d4ea 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -66,30 +66,6 @@ type journalStorage struct { Vals [][]byte } -// loadAndParseLegacyJournal tries to parse the snapshot journal in legacy format. -func loadAndParseLegacyJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { - // Retrieve the journal, for legacy journal it must exist since even for - // 0 layer it stores whether we've already generated the snapshot or are - // in progress only. - journal := rawdb.ReadSnapshotJournal(db) - if len(journal) == 0 { - return nil, journalGenerator{}, errors.New("missing or corrupted snapshot journal") - } - r := rlp.NewStream(bytes.NewReader(journal), 0) - - // Read the snapshot generation progress for the disk layer - var generator journalGenerator - if err := r.Decode(&generator); err != nil { - return nil, journalGenerator{}, fmt.Errorf("failed to load snapshot progress marker: %v", err) - } - // Load all the snapshot diffs from the journal - snapshot, err := loadDiffLayer(base, r) - if err != nil { - return nil, generator, err - } - return snapshot, generator, nil -} - // loadAndParseJournal tries to parse the snapshot journal in latest format. func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, journalGenerator, error) { // Retrieve the disk layer generator. It must exist, no matter the @@ -163,14 +139,9 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, cache: fastcache.New(cache * 1024 * 1024), root: baseRoot, } - var legacy bool snapshot, generator, err := loadAndParseJournal(diskdb, base) if err != nil { log.Warn("Failed to load new-format journal", "error", err) - snapshot, generator, err = loadAndParseLegacyJournal(diskdb, base) - legacy = true - } - if err != nil { return nil, err } // Entire snapshot journal loaded, sanity check the head. If the loaded @@ -185,7 +156,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, // If it's legacy snapshot, or it's new-format snapshot but // it's not in recovery mode, returns the error here for // rebuilding the entire snapshot forcibly. - if legacy || !recovery { + if !recovery { return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) } // It's in snapshot recovery, the assumption is held that @@ -346,94 +317,3 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) return base, nil } - -// LegacyJournal writes the persistent layer generator stats into a buffer -// to be stored in the database as the snapshot journal. -// -// Note it's the legacy version which is only used in testing right now. -func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { - // If the snapshot is currently being generated, abort it - var stats *generatorStats - if dl.genAbort != nil { - abort := make(chan *generatorStats) - dl.genAbort <- abort - - if stats = <-abort; stats != nil { - stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker) - } - } - // Ensure the layer didn't get stale - dl.lock.RLock() - defer dl.lock.RUnlock() - - if dl.stale { - return common.Hash{}, ErrSnapshotStale - } - // Write out the generator marker - entry := journalGenerator{ - Done: dl.genMarker == nil, - Marker: dl.genMarker, - } - if stats != nil { - entry.Accounts = stats.accounts - entry.Slots = stats.slots - entry.Storage = uint64(stats.storage) - } - log.Debug("Legacy journalled disk layer", "root", dl.root) - if err := rlp.Encode(buffer, entry); err != nil { - return common.Hash{}, err - } - return dl.root, nil -} - -// Journal writes the memory layer contents into a buffer to be stored in the -// database as the snapshot journal. -// -// Note it's the legacy version which is only used in testing right now. -func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { - // Journal the parent first - base, err := dl.parent.LegacyJournal(buffer) - if err != nil { - return common.Hash{}, err - } - // Ensure the layer didn't get stale - dl.lock.RLock() - defer dl.lock.RUnlock() - - if dl.Stale() { - return common.Hash{}, ErrSnapshotStale - } - // Everything below was journalled, persist this layer too - if err := rlp.Encode(buffer, dl.root); err != nil { - return common.Hash{}, err - } - destructs := make([]journalDestruct, 0, len(dl.destructSet)) - for hash := range dl.destructSet { - destructs = append(destructs, journalDestruct{Hash: hash}) - } - if err := rlp.Encode(buffer, destructs); err != nil { - return common.Hash{}, err - } - accounts := make([]journalAccount, 0, len(dl.accountData)) - for hash, blob := range dl.accountData { - accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) - } - if err := rlp.Encode(buffer, accounts); err != nil { - return common.Hash{}, err - } - storage := make([]journalStorage, 0, len(dl.storageData)) - for hash, slots := range dl.storageData { - keys := make([]common.Hash, 0, len(slots)) - vals := make([][]byte, 0, len(slots)) - for key, val := range slots { - keys = append(keys, key) - vals = append(vals, val) - } - storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) - } - if err := rlp.Encode(buffer, storage); err != nil { - return common.Hash{}, err - } - log.Debug("Legacy journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) - return base, nil -} diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 710ba4d4c2..9ecbd4a6c8 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -137,10 +137,6 @@ type snapshot interface { // flattening everything down (bad for reorgs). Journal(buffer *bytes.Buffer) (common.Hash, error) - // LegacyJournal is basically identical to Journal. it's the legacy version for - // flushing legacy journal. Now the only purpose of this function is for testing. - LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) - // Stale return whether this layer has become stale (was flattened across) or // if it's still live. Stale() bool @@ -622,29 +618,6 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) { return base, nil } -// LegacyJournal is basically identical to Journal. it's the legacy -// version for flushing legacy journal. Now the only purpose of this -// function is for testing. -func (t *Tree) LegacyJournal(root common.Hash) (common.Hash, error) { - // Retrieve the head snapshot to journal from var snap snapshot - snap := t.Snapshot(root) - if snap == nil { - return common.Hash{}, fmt.Errorf("snapshot [%#x] missing", root) - } - // Run the journaling - t.lock.Lock() - defer t.lock.Unlock() - - journal := new(bytes.Buffer) - base, err := snap.(snapshot).LegacyJournal(journal) - if err != nil { - return common.Hash{}, err - } - // Store the journal into the database and return - rawdb.WriteSnapshotJournal(t.diskdb, journal.Bytes()) - return base, nil -} - // Rebuild wipes all available snapshot data from the persistent database and // discard all caches and diff layers. Afterwards, it starts a new snapshot // generator with the given root hash. From d7bfb978ba9a96f638890f1abb6e5bff760832fc Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 20 Apr 2021 10:29:36 +0200 Subject: [PATCH 469/948] ethash: no block reward in catalyst mode (#22697) --- consensus/ethash/consensus.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index e23bd824af..c405d07fc6 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -618,6 +618,10 @@ var ( // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { + // Skip block reward in catalyst mode + if config.IsCatalyst(header.Number) { + return + } // Select the correct block reward based on chain progression blockReward := FrontierBlockReward if config.IsByzantium(header.Number) { From 581539c6ee795f17b631b0ae6c2ce4948d41c3ba Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 20 Apr 2021 10:42:02 +0200 Subject: [PATCH 470/948] trie: make stacktrie support binary marshal/unmarshal (#22685) --- trie/stacktrie.go | 94 ++++++++++++++++++++++++++++++++++++++++++ trie/stacktrie_test.go | 47 ++++++++++++++++++++- 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/trie/stacktrie.go b/trie/stacktrie.go index c34ac6c78c..f9ff10b62d 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -17,8 +17,12 @@ package trie import ( + "bufio" + "bytes" + "encoding/gob" "errors" "fmt" + "io" "sync" "github.com/ethereum/go-ethereum/common" @@ -66,6 +70,96 @@ func NewStackTrie(db ethdb.KeyValueWriter) *StackTrie { } } +// NewFromBinary initialises a serialized stacktrie with the given db. +func NewFromBinary(data []byte, db ethdb.KeyValueWriter) (*StackTrie, error) { + var st StackTrie + if err := st.UnmarshalBinary(data); err != nil { + return nil, err + } + // If a database is used, we need to recursively add it to every child + if db != nil { + st.setDb(db) + } + return &st, nil +} + +// MarshalBinary implements encoding.BinaryMarshaler +func (st *StackTrie) MarshalBinary() (data []byte, err error) { + var ( + b bytes.Buffer + w = bufio.NewWriter(&b) + ) + if err := gob.NewEncoder(w).Encode(struct { + Nodetype uint8 + Val []byte + Key []byte + KeyOffset uint8 + }{ + st.nodeType, + st.val, + st.key, + uint8(st.keyOffset), + }); err != nil { + return nil, err + } + for _, child := range st.children { + if child == nil { + w.WriteByte(0) + continue + } + w.WriteByte(1) + if childData, err := child.MarshalBinary(); err != nil { + return nil, err + } else { + w.Write(childData) + } + } + w.Flush() + return b.Bytes(), nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (st *StackTrie) UnmarshalBinary(data []byte) error { + r := bytes.NewReader(data) + return st.unmarshalBinary(r) +} + +func (st *StackTrie) unmarshalBinary(r io.Reader) error { + var dec struct { + Nodetype uint8 + Val []byte + Key []byte + KeyOffset uint8 + } + gob.NewDecoder(r).Decode(&dec) + st.nodeType = dec.Nodetype + st.val = dec.Val + st.key = dec.Key + st.keyOffset = int(dec.KeyOffset) + + var hasChild = make([]byte, 1) + for i := range st.children { + if _, err := r.Read(hasChild); err != nil { + return err + } else if hasChild[0] == 0 { + continue + } + var child StackTrie + child.unmarshalBinary(r) + st.children[i] = &child + } + return nil +} + +func (st *StackTrie) setDb(db ethdb.KeyValueWriter) { + st.db = db + for _, child := range st.children { + if child != nil { + child.setDb(db) + } + } +} + func newLeaf(ko int, key, val []byte, db ethdb.KeyValueWriter) *StackTrie { st := stackTrieFromPool(db) st.nodeType = leafNode diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index 50a4eda323..ccdf389d52 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -151,7 +151,6 @@ func TestStacktrieNotModifyValues(t *testing.T) { return big.NewInt(int64(i)).Bytes() } } - for i := 0; i < 1000; i++ { key := common.BigToHash(keyB) value := getValue(i) @@ -168,5 +167,51 @@ func TestStacktrieNotModifyValues(t *testing.T) { if !bytes.Equal(have, want) { t.Fatalf("item %d, have %#x want %#x", i, have, want) } + + } +} + +// TestStacktrieSerialization tests that the stacktrie works well if we +// serialize/unserialize it a lot +func TestStacktrieSerialization(t *testing.T) { + var ( + st = NewStackTrie(nil) + nt, _ = New(common.Hash{}, NewDatabase(memorydb.New())) + keyB = big.NewInt(1) + keyDelta = big.NewInt(1) + vals [][]byte + keys [][]byte + ) + getValue := func(i int) []byte { + if i%2 == 0 { // large + return crypto.Keccak256(big.NewInt(int64(i)).Bytes()) + } else { //small + return big.NewInt(int64(i)).Bytes() + } + } + for i := 0; i < 10; i++ { + vals = append(vals, getValue(i)) + keys = append(keys, common.BigToHash(keyB).Bytes()) + keyB = keyB.Add(keyB, keyDelta) + keyDelta.Add(keyDelta, common.Big1) + } + for i, k := range keys { + nt.TryUpdate(k, common.CopyBytes(vals[i])) + } + + for i, k := range keys { + blob, err := st.MarshalBinary() + if err != nil { + t.Fatal(err) + } + newSt, err := NewFromBinary(blob, nil) + if err != nil { + t.Fatal(err) + } + st = newSt + st.TryUpdate(k, common.CopyBytes(vals[i])) + } + if have, want := st.Hash(), nt.Hash(); have != want { + t.Fatalf("have %#x want %#x", have, want) } } From beee6b77a0ca8c26881dc2bb17c58e996c90a0c6 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 20 Apr 2021 10:54:41 +0200 Subject: [PATCH 471/948] go.mod: upgrade gopsutils to v3.21.4 (#22693) This fixes the OpenBSD/arm64 build. --- go.mod | 5 +++-- go.sum | 10 ++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 0b0e8bf373..74c406f78b 100644 --- a/go.mod +++ b/go.mod @@ -45,13 +45,14 @@ require ( github.com/prometheus/tsdb v0.7.1 github.com/rjeczalik/notify v0.9.1 github.com/rs/cors v1.7.0 - github.com/shirou/gopsutil v2.20.5+incompatible + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 + github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c + golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa golang.org/x/text v0.3.4 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce diff --git a/go.sum b/go.sum index 1b7212e420..0a9444323c 100644 --- a/go.sum +++ b/go.sum @@ -428,6 +428,10 @@ github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.3+incompatible h1:uenXGGa8ESCQq+dbgtl916dmg6PSAz2cXov0uORQ9v8= +github.com/shirou/gopsutil v3.21.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -459,6 +463,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKk github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= @@ -583,6 +591,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From cc33398cef4d9faeaec1e68e13029e1a0e1d6e41 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 20 Apr 2021 12:28:20 +0200 Subject: [PATCH 472/948] tests: disable blockchain tests based on general state tests (#22704) --- tests/block_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/block_test.go b/tests/block_test.go index 2649bae85a..4820ba733f 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -28,7 +28,7 @@ func TestBlockchain(t *testing.T) { // For speedier CI-runs, the line below can be uncommented, so those are skipped. // For now, in hardfork-times (Berlin), we run the tests both as StateTests and // as blockchain tests, since the latter also covers things like receipt root - //bt.skipLoad(`^GeneralStateTests/`) + bt.skipLoad(`^GeneralStateTests/`) // Skip random failures due to selfish mining test bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) From dd9c3225cf06dab0acf783fad671b4f601a4470e Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 21 Apr 2021 15:21:22 +0800 Subject: [PATCH 473/948] eth, internal: extend the TraceCall API (#22245) Adds an an optional parameter `overrides *map[common.Address]account` to the `eth_call` API in order for the caller to can customize the state. --- eth/tracers/api.go | 33 +++++++- eth/tracers/api_test.go | 170 +++++++++++++++++++++++++++++++++++++++- internal/ethapi/api.go | 44 +++++++---- 3 files changed, 226 insertions(+), 21 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5a28d6889e..1c727f1366 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -161,6 +161,16 @@ type TraceConfig struct { Reexec *uint64 } +// TraceCallConfig is the config for traceCall API. It holds one more +// field to override the state for tracing. +type TraceCallConfig struct { + *vm.LogConfig + Tracer *string + Timeout *string + Reexec *uint64 + StateOverrides *ethapi.StateOverride +} + // StdTraceConfig holds extra parameters to standard-json trace functions. type StdTraceConfig struct { vm.LogConfig @@ -720,7 +730,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * // created during the execution of EVM if the given transaction was added on // top of the provided block and returns them as a JSON object. // You can provide -2 as a block number to trace on top of the pending block. -func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) { +func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { // Try to retrieve the specified block var ( err error @@ -730,6 +740,8 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa block, err = api.blockByHash(ctx, hash) } else if number, ok := blockNrOrHash.Number(); ok { block, err = api.blockByNumber(ctx, number) + } else { + return nil, errors.New("invalid arguments; neither block nor hash specified") } if err != nil { return nil, err @@ -743,11 +755,26 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa if err != nil { return nil, err } + // Apply the customized state rules if required. + if config != nil { + if err := config.StateOverrides.Apply(statedb); err != nil { + return nil, err + } + } // Execute the trace msg := args.ToMessage(api.backend.RPCGasCap()) vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) - return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, config) + var traceConfig *TraceConfig + if config != nil { + traceConfig = &TraceConfig{ + LogConfig: config.LogConfig, + Tracer: config.Tracer, + Timeout: config.Timeout, + Reexec: config.Reexec, + } + } + return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig) } // traceTx configures a new tracer according to the provided configuration, and @@ -797,7 +824,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { - return nil, fmt.Errorf("tracing failed: %v", err) + return nil, fmt.Errorf("tracing failed: %w", err) } // Depending on the tracer type, format and return the output. diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 7ca90a6608..81a4bb5d05 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "crypto/ecdsa" + "encoding/json" "errors" "fmt" "math/big" @@ -198,7 +199,7 @@ func TestTraceCall(t *testing.T) { var testSuite = []struct { blockNumber rpc.BlockNumber call ethapi.CallArgs - config *TraceConfig + config *TraceCallConfig expectErr error expect interface{} }{ @@ -305,6 +306,147 @@ func TestTraceCall(t *testing.T) { } } +func TestOverridenTraceCall(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + randomAccounts, tracer := newAccounts(3), "callTracer" + + var testSuite = []struct { + blockNumber rpc.BlockNumber + call ethapi.CallArgs + config *TraceCallConfig + expectErr error + expect *callTrace + }{ + // Succcessful call with state overriding + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.CallArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: &TraceCallConfig{ + Tracer: &tracer, + StateOverrides: ðapi.StateOverride{ + randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))}, + }, + }, + expectErr: nil, + expect: &callTrace{ + Type: "CALL", + From: randomAccounts[0].addr, + To: randomAccounts[1].addr, + Gas: newRPCUint64(24979000), + GasUsed: newRPCUint64(0), + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + }, + // Invalid call without state overriding + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.CallArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: &TraceCallConfig{ + Tracer: &tracer, + }, + expectErr: core.ErrInsufficientFundsForTransfer, + expect: nil, + }, + // Sucessful simple contract call + // + // // SPDX-License-Identifier: GPL-3.0 + // + // pragma solidity >=0.7.0 <0.8.0; + // + // /** + // * @title Storage + // * @dev Store & retrieve value in a variable + // */ + // contract Storage { + // uint256 public number; + // constructor() { + // number = block.number; + // } + // } + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.CallArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[2].addr, + Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number() + }, + config: &TraceCallConfig{ + Tracer: &tracer, + StateOverrides: ðapi.StateOverride{ + randomAccounts[2].addr: ethapi.OverrideAccount{ + Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")), + StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}), + }, + }, + }, + expectErr: nil, + expect: &callTrace{ + Type: "CALL", + From: randomAccounts[0].addr, + To: randomAccounts[2].addr, + Input: hexutil.Bytes(common.Hex2Bytes("8381f58a")), + Output: hexutil.Bytes(common.BigToHash(big.NewInt(123)).Bytes()), + Gas: newRPCUint64(24978936), + GasUsed: newRPCUint64(2283), + Value: (*hexutil.Big)(big.NewInt(0)), + }, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !errors.Is(err, testspec.expectErr) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + ret := new(callTrace) + if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil { + t.Fatalf("failed to unmarshal trace result: %v", err) + } + if !jsonEqual(ret, testspec.expect) { + // uncomment this for easier debugging + //have, _ := json.MarshalIndent(ret, "", " ") + //want, _ := json.MarshalIndent(testspec.expect, "", " ") + //t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want)) + t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, testspec.expect) + } + } + } +} + func TestTraceTransaction(t *testing.T) { t.Parallel() @@ -469,3 +611,29 @@ func newAccounts(n int) (accounts Accounts) { sort.Sort(accounts) return accounts } + +func newRPCBalance(balance *big.Int) **hexutil.Big { + rpcBalance := (*hexutil.Big)(balance) + return &rpcBalance +} + +func newRPCUint64(number uint64) *hexutil.Uint64 { + rpcUint64 := hexutil.Uint64(number) + return &rpcUint64 +} + +func newRPCBytes(bytes []byte) *hexutil.Bytes { + rpcBytes := hexutil.Bytes(bytes) + return &rpcBytes +} + +func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.Hash { + if len(keys) != len(vals) { + panic("invalid input") + } + m := make(map[common.Hash]common.Hash) + for i := 0; i < len(keys); i++ { + m[keys[i]] = vals[i] + } + return &m +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fe5c3388b5..a657f8a8f9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -802,13 +803,13 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { return msg } -// account indicates the overriding fields of account during the execution of -// a message call. +// OverrideAccount indicates the overriding fields of account during the execution +// of a message call. // Note, state and stateDiff can't be specified at the same time. If state is // set, message execution will only use the data in the given state. Otherwise // if statDiff is set, all diff will be applied first and then execute the call // message. -type account struct { +type OverrideAccount struct { Nonce *hexutil.Uint64 `json:"nonce"` Code *hexutil.Bytes `json:"code"` Balance **hexutil.Big `json:"balance"` @@ -816,15 +817,15 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) +// StateOverride is the collection of overriden accounts. +type StateOverride map[common.Address]OverrideAccount - state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err +// Apply overrides the fields of specified accounts into the given state. +func (diff *StateOverride) Apply(state *state.StateDB) error { + if diff == nil { + return nil } - // Override the fields of specified contracts before execution. - for addr, account := range overrides { + for addr, account := range *diff { // Override account nonce. if account.Nonce != nil { state.SetNonce(addr, uint64(*account.Nonce)) @@ -838,7 +839,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo state.SetBalance(addr, (*big.Int)(*account.Balance)) } if account.State != nil && account.StateDiff != nil { - return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) } // Replace entire state if caller requires. if account.State != nil { @@ -851,6 +852,19 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo } } } + return nil +} + +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { + defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + if err := overrides.Apply(state); err != nil { + return nil, err + } // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. var cancel context.CancelFunc @@ -929,12 +943,8 @@ func (e *revertError) ErrorData() interface{} { // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) { - var accounts map[common.Address]account - if overrides != nil { - accounts = *overrides - } - result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) +func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { + result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err } From 96828c90f5ff9b73dc1a85da5999fe409324ee95 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 21 Apr 2021 16:18:27 +0800 Subject: [PATCH 474/948] eth/tracers, internal/ethapi: fix typos causing lint issue (#22711) --- eth/tracers/api_test.go | 2 +- internal/ethapi/api.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 81a4bb5d05..4c0240cd2c 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -372,7 +372,7 @@ func TestOverridenTraceCall(t *testing.T) { expectErr: core.ErrInsufficientFundsForTransfer, expect: nil, }, - // Sucessful simple contract call + // Successful simple contract call // // // SPDX-License-Identifier: GPL-3.0 // diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a657f8a8f9..fe3f80c038 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -817,7 +817,7 @@ type OverrideAccount struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -// StateOverride is the collection of overriden accounts. +// StateOverride is the collection of overridden accounts. type StateOverride map[common.Address]OverrideAccount // Apply overrides the fields of specified accounts into the given state. From 3e68d627b1b930a824942204ae3cd0b042cd1dbb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 21 Apr 2021 10:19:28 +0200 Subject: [PATCH 475/948] les: fix goroutine leaks in tests (#22707) --- les/server.go | 16 ++++++++++++---- les/test_helper.go | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/les/server.go b/les/server.go index d44b1b57d4..c135e65f2d 100644 --- a/les/server.go +++ b/les/server.go @@ -212,17 +212,25 @@ func (s *LesServer) Stop() error { close(s.closeCh) s.clientPool.Stop() - s.serverset.close() + if s.serverset != nil { + s.serverset.close() + } s.peers.close() s.fcManager.Stop() s.costTracker.stop() s.handler.stop() s.servingQueue.stop() - s.vfluxServer.Stop() + if s.vfluxServer != nil { + s.vfluxServer.Stop() + } // Note, bloom trie indexer is closed by parent bloombits indexer. - s.chtIndexer.Close() - s.lesDb.Close() + if s.chtIndexer != nil { + s.chtIndexer.Close() + } + if s.lesDb != nil { + s.lesDb.Close() + } s.wg.Wait() log.Info("Les server stopped") diff --git a/les/test_helper.go b/les/test_helper.go index ee2da2f8eb..fc85ed957f 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -189,7 +189,7 @@ func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.Indexer return indexers[:] } -func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet, ulcServers []string, ulcFraction int) *clientHandler { +func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet, ulcServers []string, ulcFraction int) (*clientHandler, func()) { var ( evmux = new(event.TypeMux) engine = ethash.NewFaker() @@ -245,10 +245,12 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index client.oracle.Start(backend) } client.handler.start() - return client.handler + return client.handler, func() { + client.handler.stop() + } } -func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) { +func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend, func()) { var ( gspec = core.Genesis{ Config: params.AllEthashProtocolChanges, @@ -314,7 +316,8 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } server.servingQueue.setThreads(4) server.handler.start() - return server.handler, simulation + closer := func() { server.Stop() } + return server.handler, simulation, closer } func alwaysTrueFn() bool { @@ -600,8 +603,8 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2] odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer) - server, b := newTestServerHandler(config.blocks, sindexers, sdb, clock) - client := newTestClientHandler(b, odr, cIndexers, cdb, speers, config.ulcServers, config.ulcFraction) + server, b, serverClose := newTestServerHandler(config.blocks, sindexers, sdb, clock) + client, clientClose := newTestClientHandler(b, odr, cIndexers, cdb, speers, config.ulcServers, config.ulcFraction) scIndexer.Start(server.blockchain) sbIndexer.Start(server.blockchain) @@ -658,7 +661,10 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC cbIndexer.Close() scIndexer.Close() sbIndexer.Close() + dist.close() + serverClose() b.Close() + clientClose() } return s, c, teardown } From 4b783c0064661be55fd35b765c2a90d1f9b9abcb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 21 Apr 2021 12:25:26 +0200 Subject: [PATCH 476/948] trie: improve the node iterator seek operation (#22470) This change improves the efficiency of the nodeIterator seek operation. Previously, seek essentially ran the iterator forward until it found the matching node. With this change, it skips over fullnode children and avoids resolving them from the database. --- trie/iterator.go | 145 +++++++++++++++++++++++++++++++++++------- trie/iterator_test.go | 81 +++++++++++++++++++++++ 2 files changed, 202 insertions(+), 24 deletions(-) diff --git a/trie/iterator.go b/trie/iterator.go index 76d437c403..4f72258a1d 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -243,7 +243,7 @@ func (it *nodeIterator) seek(prefix []byte) error { key = key[:len(key)-1] // Move forward until we're just before the closest match to key. for { - state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path)) + state, parentIndex, path, err := it.peekSeek(key) if err == errIteratorEnd { return errIteratorEnd } else if err != nil { @@ -255,16 +255,21 @@ func (it *nodeIterator) seek(prefix []byte) error { } } +// init initializes the the iterator. +func (it *nodeIterator) init() (*nodeIteratorState, error) { + root := it.trie.Hash() + state := &nodeIteratorState{node: it.trie.root, index: -1} + if root != emptyRoot { + state.hash = root + } + return state, state.resolve(it.trie, nil) +} + // peek creates the next state of the iterator. func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) { + // Initialize the iterator if we've just started. if len(it.stack) == 0 { - // Initialize the iterator if we've just started. - root := it.trie.Hash() - state := &nodeIteratorState{node: it.trie.root, index: -1} - if root != emptyRoot { - state.hash = root - } - err := state.resolve(it.trie, nil) + state, err := it.init() return state, nil, nil, err } if !descend { @@ -292,6 +297,39 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er return nil, nil, nil, errIteratorEnd } +// peekSeek is like peek, but it also tries to skip resolving hashes by skipping +// over the siblings that do not lead towards the desired seek position. +func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []byte, error) { + // Initialize the iterator if we've just started. + if len(it.stack) == 0 { + state, err := it.init() + return state, nil, nil, err + } + if !bytes.HasPrefix(seekKey, it.path) { + // If we're skipping children, pop the current node first + it.pop() + } + + // Continue iteration to the next child + for len(it.stack) > 0 { + parent := it.stack[len(it.stack)-1] + ancestor := parent.hash + if (ancestor == common.Hash{}) { + ancestor = parent.parent + } + state, path, ok := it.nextChildAt(parent, ancestor, seekKey) + if ok { + if err := state.resolve(it.trie, path); err != nil { + return parent, &parent.index, path, err + } + return state, &parent.index, path, nil + } + // No more child nodes, move back up. + it.pop() + } + return nil, nil, nil, errIteratorEnd +} + func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error { if hash, ok := st.node.(hashNode); ok { resolved, err := tr.resolveHash(hash, path) @@ -304,25 +342,38 @@ func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error { return nil } +func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) { + var ( + child node + state *nodeIteratorState + childPath []byte + ) + for ; index < len(n.Children); index++ { + if n.Children[index] != nil { + child = n.Children[index] + hash, _ := child.cache() + state = &nodeIteratorState{ + hash: common.BytesToHash(hash), + node: child, + parent: ancestor, + index: -1, + pathlen: len(path), + } + childPath = append(childPath, path...) + childPath = append(childPath, byte(index)) + return child, state, childPath, index + } + } + return nil, nil, nil, 0 +} + func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Hash) (*nodeIteratorState, []byte, bool) { switch node := parent.node.(type) { case *fullNode: - // Full node, move to the first non-nil child. - for i := parent.index + 1; i < len(node.Children); i++ { - child := node.Children[i] - if child != nil { - hash, _ := child.cache() - state := &nodeIteratorState{ - hash: common.BytesToHash(hash), - node: child, - parent: ancestor, - index: -1, - pathlen: len(it.path), - } - path := append(it.path, byte(i)) - parent.index = i - 1 - return state, path, true - } + //Full node, move to the first non-nil child. + if child, state, path, index := findChild(node, parent.index+1, it.path, ancestor); child != nil { + parent.index = index - 1 + return state, path, true } case *shortNode: // Short node, return the pointer singleton child @@ -342,6 +393,52 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has return parent, it.path, false } +// nextChildAt is similar to nextChild, except that it targets a child as close to the +// target key as possible, thus skipping siblings. +func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.Hash, key []byte) (*nodeIteratorState, []byte, bool) { + switch n := parent.node.(type) { + case *fullNode: + // Full node, move to the first non-nil child before the desired key position + child, state, path, index := findChild(n, parent.index+1, it.path, ancestor) + if child == nil { + // No more children in this fullnode + return parent, it.path, false + } + // If the child we found is already past the seek position, just return it. + if bytes.Compare(path, key) >= 0 { + parent.index = index - 1 + return state, path, true + } + // The child is before the seek position. Try advancing + for { + nextChild, nextState, nextPath, nextIndex := findChild(n, index+1, it.path, ancestor) + // If we run out of children, or skipped past the target, return the + // previous one + if nextChild == nil || bytes.Compare(nextPath, key) >= 0 { + parent.index = index - 1 + return state, path, true + } + // We found a better child closer to the target + state, path, index = nextState, nextPath, nextIndex + } + case *shortNode: + // Short node, return the pointer singleton child + if parent.index < 0 { + hash, _ := n.Val.cache() + state := &nodeIteratorState{ + hash: common.BytesToHash(hash), + node: n.Val, + parent: ancestor, + index: -1, + pathlen: len(it.path), + } + path := append(it.path, n.Key...) + return state, path, true + } + } + return parent, it.path, false +} + func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) { it.path = path it.stack = append(it.stack, state) diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 75a0a99e51..2518f7bac8 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -18,11 +18,14 @@ package trie import ( "bytes" + "encoding/binary" "fmt" "math/rand" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" ) @@ -440,3 +443,81 @@ func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) in } return len(seen) } + +type loggingDb struct { + getCount uint64 + backend ethdb.KeyValueStore +} + +func (l *loggingDb) Has(key []byte) (bool, error) { + return l.backend.Has(key) +} + +func (l *loggingDb) Get(key []byte) ([]byte, error) { + l.getCount++ + return l.backend.Get(key) +} + +func (l *loggingDb) Put(key []byte, value []byte) error { + return l.backend.Put(key, value) +} + +func (l *loggingDb) Delete(key []byte) error { + return l.backend.Delete(key) +} + +func (l *loggingDb) NewBatch() ethdb.Batch { + return l.backend.NewBatch() +} + +func (l *loggingDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + fmt.Printf("NewIterator\n") + return l.backend.NewIterator(prefix, start) +} +func (l *loggingDb) Stat(property string) (string, error) { + return l.backend.Stat(property) +} + +func (l *loggingDb) Compact(start []byte, limit []byte) error { + return l.backend.Compact(start, limit) +} + +func (l *loggingDb) Close() error { + return l.backend.Close() +} + +// makeLargeTestTrie create a sample test trie +func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) { + // Create an empty trie + logDb := &loggingDb{0, memorydb.New()} + triedb := NewDatabase(logDb) + trie, _ := NewSecure(common.Hash{}, triedb) + + // Fill it with some arbitrary data + for i := 0; i < 10000; i++ { + key := make([]byte, 32) + val := make([]byte, 32) + binary.BigEndian.PutUint64(key, uint64(i)) + binary.BigEndian.PutUint64(val, uint64(i)) + key = crypto.Keccak256(key) + val = crypto.Keccak256(val) + trie.Update(key, val) + } + trie.Commit(nil) + // Return the generated trie + return triedb, trie, logDb +} + +// Tests that the node iterator indeed walks over the entire database contents. +func TestNodeIteratorLargeTrie(t *testing.T) { + // Create some arbitrary test trie to iterate + db, trie, logDb := makeLargeTestTrie() + db.Cap(0) // flush everything + // Do a seek operation + trie.NodeIterator(common.FromHex("0x77667766776677766778855885885885")) + // master: 24 get operations + // this pr: 5 get operations + if have, want := logDb.getCount, uint64(5); have != want { + t.Fatalf("Too many lookups during seek, have %d want %d", have, want) + } +} From 67da83aca51d30df9ba2d00b9ec694422626a3ad Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 21 Apr 2021 13:03:33 +0200 Subject: [PATCH 477/948] accounts/external, signer/core: add support for EIP-2930 transactions (#22585) This adds support for signing EIP-2930 with clef. --- accounts/external/backend.go | 14 ++++++++++++++ signer/core/api.go | 8 ++++++++ signer/core/cliui.go | 12 ++++++++++++ signer/core/types.go | 34 +++++++++++++++++++++++++++++++--- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 17a747db0e..de241385c2 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -212,6 +212,20 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio To: to, From: common.NewMixedcaseAddress(account.Address), } + // We should request the default chain id that we're operating with + // (the chain we're executing on) + if chainID != nil { + args.ChainID = (*hexutil.Big)(chainID) + } + // However, if the user asked for a particular chain id, then we should + // use that instead. + if tx.Type() != types.LegacyTxType && tx.ChainId() != nil { + args.ChainID = (*hexutil.Big)(tx.ChainId()) + } + if tx.Type() == types.AccessListTxType { + accessList := tx.AccessList() + args.AccessList = &accessList + } var res signTransactionResult if err := api.client.Call(&res, "account_signTransaction", args); err != nil { return nil, err diff --git a/signer/core/api.go b/signer/core/api.go index 968dcfb2ed..3811162f8f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -534,6 +534,14 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth return nil, err } } + if args.ChainID != nil { + requestedChainId := (*big.Int)(args.ChainID) + if api.chainID.Cmp(requestedChainId) != 0 { + log.Error("Signing request with wrong chain id", "requested", requestedChainId, "configured", api.chainID) + return nil, fmt.Errorf("requested chainid %d does not match the configuration of the signer", + requestedChainId) + } + } req := SignTxRequest{ Transaction: args, Meta: MetadataFromContext(ctx), diff --git a/signer/core/cliui.go b/signer/core/cliui.go index cbfb56c9df..e0375483c3 100644 --- a/signer/core/cliui.go +++ b/signer/core/cliui.go @@ -118,6 +118,18 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro fmt.Printf("gas: %v (%v)\n", request.Transaction.Gas, uint64(request.Transaction.Gas)) fmt.Printf("gasprice: %v wei\n", request.Transaction.GasPrice.ToInt()) fmt.Printf("nonce: %v (%v)\n", request.Transaction.Nonce, uint64(request.Transaction.Nonce)) + if chainId := request.Transaction.ChainID; chainId != nil { + fmt.Printf("chainid: %v\n", chainId) + } + if list := request.Transaction.AccessList; list != nil { + fmt.Printf("Accesslist\n") + for i, el := range *list { + fmt.Printf(" %d. %v\n", i, el.Address) + for j, slot := range el.StorageKeys { + fmt.Printf(" %d. %v\n", j, slot) + } + } + } if request.Transaction.Data != nil { d := *request.Transaction.Data if len(d) > 0 { diff --git a/signer/core/types.go b/signer/core/types.go index 58b377c8d8..e952a21209 100644 --- a/signer/core/types.go +++ b/signer/core/types.go @@ -76,6 +76,10 @@ type SendTxArgs struct { // We accept "data" and "input" for backwards-compatibility reasons. Data *hexutil.Bytes `json:"data"` Input *hexutil.Bytes `json:"input,omitempty"` + + // For non-legacy transactions + AccessList *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` } func (args SendTxArgs) String() string { @@ -93,8 +97,32 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { } else if args.Input != nil { input = *args.Input } - if args.To == nil { - return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input) + var to *common.Address + if args.To != nil { + _to := args.To.Address() + to = &_to + } + var data types.TxData + if args.AccessList == nil { + data = &types.LegacyTx{ + To: to, + Nonce: uint64(args.Nonce), + Gas: uint64(args.Gas), + GasPrice: (*big.Int)(&args.GasPrice), + Value: (*big.Int)(&args.Value), + Data: input, + } + } else { + data = &types.AccessListTx{ + To: to, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(args.Nonce), + Gas: uint64(args.Gas), + GasPrice: (*big.Int)(&args.GasPrice), + Value: (*big.Int)(&args.Value), + Data: input, + AccessList: *args.AccessList, + } } - return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input) + return types.NewTx(data) } From 9357280fce5c5d57111d690a336cca5f89e34da6 Mon Sep 17 00:00:00 2001 From: ryanc414 Date: Wed, 21 Apr 2021 14:51:30 +0100 Subject: [PATCH 478/948] rpc: add HTTPError type for HTTP error responses (#22677) The new error type is returned by client operations contains details of the response error code and response body. Co-authored-by: Felix Lange --- rpc/errors.go | 29 +++++++++++++++++++++++++++++ rpc/http.go | 24 +++++++++++++----------- rpc/http_test.go | 39 +++++++++++++++++++++++++++++++++++++++ rpc/types.go | 12 ------------ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/rpc/errors.go b/rpc/errors.go index dbfde8b196..4c06a745fb 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -18,6 +18,35 @@ package rpc import "fmt" +// HTTPError is returned by client operations when the HTTP status code of the +// response is not a 2xx status. +type HTTPError struct { + StatusCode int + Status string + Body []byte +} + +func (err HTTPError) Error() string { + if len(err.Body) == 0 { + return err.Status + } + return fmt.Sprintf("%v: %s", err.Status, err.Body) +} + +// Error wraps RPC errors, which contain an error code in addition to the message. +type Error interface { + Error() string // returns the message + ErrorCode() int // returns the code +} + +// A DataError contains some data in addition to the error message. +type DataError interface { + Error() string // returns the message + ErrorData() interface{} // returns the error data +} + +// Error types defined below are the built-in JSON-RPC errors. + var ( _ Error = new(methodNotFoundError) _ Error = new(subscriptionNotFoundError) diff --git a/rpc/http.go b/rpc/http.go index 87a96e49ea..32f4e7d90a 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -134,19 +134,11 @@ func DialHTTP(endpoint string) (*Client, error) { func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { hc := c.writeConn.(*httpConn) respBody, err := hc.doRequest(ctx, msg) - if respBody != nil { - defer respBody.Close() - } - if err != nil { - if respBody != nil { - buf := new(bytes.Buffer) - if _, err2 := buf.ReadFrom(respBody); err2 == nil { - return fmt.Errorf("%v: %v", err, buf.String()) - } - } return err } + defer respBody.Close() + var respmsg jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { return err @@ -194,7 +186,17 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos return nil, err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return resp.Body, errors.New(resp.Status) + var buf bytes.Buffer + var body []byte + if _, err := buf.ReadFrom(resp.Body); err == nil { + body = buf.Bytes() + } + + return nil, HTTPError{ + Status: resp.Status, + StatusCode: resp.StatusCode, + Body: body, + } } return resp.Body, nil } diff --git a/rpc/http_test.go b/rpc/http_test.go index b75af67c52..97f8d44c39 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -123,3 +123,42 @@ func TestHTTPRespBodyUnlimited(t *testing.T) { t.Fatalf("response has wrong length %d, want %d", len(r), respLength) } } + +// Tests that an HTTP error results in an HTTPError instance +// being returned with the expected attributes. +func TestHTTPErrorResponse(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "error has occurred!", http.StatusTeapot) + })) + defer ts.Close() + + c, err := DialHTTP(ts.URL) + if err != nil { + t.Fatal(err) + } + + var r string + err = c.Call(&r, "test_method") + if err == nil { + t.Fatal("error was expected") + } + + httpErr, ok := err.(HTTPError) + if !ok { + t.Fatalf("unexpected error type %T", err) + } + + if httpErr.StatusCode != http.StatusTeapot { + t.Error("unexpected status code", httpErr.StatusCode) + } + if httpErr.Status != "418 I'm a teapot" { + t.Error("unexpected status text", httpErr.Status) + } + if body := string(httpErr.Body); body != "error has occurred!\n" { + t.Error("unexpected body", body) + } + + if errMsg := httpErr.Error(); errMsg != "418 I'm a teapot: error has occurred!\n" { + t.Error("unexpected error message", errMsg) + } +} diff --git a/rpc/types.go b/rpc/types.go index bab1b3957b..d1b878c785 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -35,18 +35,6 @@ type API struct { Public bool // indication if the methods must be considered safe for public use } -// Error wraps RPC errors, which contain an error code in addition to the message. -type Error interface { - Error() string // returns the message - ErrorCode() int // returns the code -} - -// A DataError contains some data in addition to the error message. -type DataError interface { - Error() string // returns the message - ErrorData() interface{} // returns the error data -} - // ServerCodec implements reading, parsing and writing RPC messages for the server side of // a RPC session. Implementations must be go-routine safe since the codec can be called in // multiple go-routines concurrently. From 1fb9a6dd32b581c912d672634882d7e2eb2775cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 22 Apr 2021 11:42:46 +0300 Subject: [PATCH 479/948] eth/protocols, prp/tracker: add support for req/rep rtt tracking (#22608) * eth/protocols, prp/tracker: add support for req/rep rtt tracking * p2p/tracker: sanity cap the number of pending requests * pap/tracker: linter <3 * p2p/tracker: disable entire tracker if no metrics are enabled --- eth/protocols/eth/handler.go | 2 +- eth/protocols/eth/handlers.go | 10 ++ eth/protocols/eth/peer.go | 35 ++++-- eth/protocols/eth/tracker.go | 26 +++++ eth/protocols/snap/handler.go | 8 ++ eth/protocols/snap/peer.go | 7 ++ eth/protocols/snap/tracker.go | 26 +++++ p2p/metrics.go | 3 - p2p/tracker/tracker.go | 203 ++++++++++++++++++++++++++++++++++ 9 files changed, 309 insertions(+), 11 deletions(-) create mode 100644 eth/protocols/eth/tracker.go create mode 100644 eth/protocols/snap/tracker.go create mode 100644 p2p/tracker/tracker.go diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 52dcf94011..6bbaa2f555 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -223,7 +223,7 @@ func handleMessage(backend Backend, peer *Peer) error { if peer.Version() >= ETH66 { handlers = eth66 } - // Track the emount of time it takes to serve the request and run the handler + // Track the amount of time it takes to serve the request and run the handler if metrics.Enabled { h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) defer func(start time.Time) { diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 8433fa343a..d0dec7b0b2 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -327,6 +327,8 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, BlockHeadersMsg, res.RequestId) + return backend.Handle(peer, &res.BlockHeadersPacket) } @@ -345,6 +347,8 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, BlockBodiesMsg, res.RequestId) + return backend.Handle(peer, &res.BlockBodiesPacket) } @@ -363,6 +367,8 @@ func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, NodeDataMsg, res.RequestId) + return backend.Handle(peer, &res.NodeDataPacket) } @@ -381,6 +387,8 @@ func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, ReceiptsMsg, res.RequestId) + return backend.Handle(peer, &res.ReceiptsPacket) } @@ -506,5 +514,7 @@ func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error } peer.markTransaction(tx.Hash()) } + requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) + return backend.Handle(peer, &txs.PooledTransactionsPacket) } diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 709fca8655..e619c183ba 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -413,8 +413,11 @@ func (p *Peer) RequestOneHeader(hash common.Hash) error { Reverse: false, } if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id) return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetBlockHeadersPacket: &query, }) } @@ -432,8 +435,11 @@ func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, re Reverse: reverse, } if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id) return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetBlockHeadersPacket: &query, }) } @@ -451,8 +457,11 @@ func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, rever Reverse: reverse, } if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id) return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetBlockHeadersPacket: &query, }) } @@ -476,8 +485,11 @@ func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, func (p *Peer) RequestBodies(hashes []common.Hash) error { p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetBlockBodiesMsg, BlockBodiesMsg, id) return p2p.Send(p.rw, GetBlockBodiesMsg, &GetBlockBodiesPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetBlockBodiesPacket: hashes, }) } @@ -489,8 +501,11 @@ func (p *Peer) RequestBodies(hashes []common.Hash) error { func (p *Peer) RequestNodeData(hashes []common.Hash) error { p.Log().Debug("Fetching batch of state data", "count", len(hashes)) if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetNodeDataMsg, NodeDataMsg, id) return p2p.Send(p.rw, GetNodeDataMsg, &GetNodeDataPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetNodeDataPacket: hashes, }) } @@ -501,8 +516,11 @@ func (p *Peer) RequestNodeData(hashes []common.Hash) error { func (p *Peer) RequestReceipts(hashes []common.Hash) error { p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetReceiptsMsg, ReceiptsMsg, id) return p2p.Send(p.rw, GetReceiptsMsg, &GetReceiptsPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetReceiptsPacket: hashes, }) } @@ -513,8 +531,11 @@ func (p *Peer) RequestReceipts(hashes []common.Hash) error { func (p *Peer) RequestTxs(hashes []common.Hash) error { p.Log().Debug("Fetching batch of transactions", "count", len(hashes)) if p.Version() >= ETH66 { + id := rand.Uint64() + + requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id) return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{ - RequestId: rand.Uint64(), + RequestId: id, GetPooledTransactionsPacket: hashes, }) } diff --git a/eth/protocols/eth/tracker.go b/eth/protocols/eth/tracker.go new file mode 100644 index 0000000000..324fd22839 --- /dev/null +++ b/eth/protocols/eth/tracker.go @@ -0,0 +1,26 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "time" + + "github.com/ethereum/go-ethereum/p2p/tracker" +) + +// requestTracker is a singleton tracker for eth/66 and newer request times. +var requestTracker = tracker.New(ProtocolName, 5*time.Minute) diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index f7939964f3..4c12adfa81 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -227,6 +227,8 @@ func handleMessage(backend Backend, peer *Peer) error { return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:]) } } + requestTracker.Fulfil(peer.id, peer.version, AccountRangeMsg, res.ID) + return backend.Handle(peer, res) case msg.Code == GetStorageRangesMsg: @@ -360,6 +362,8 @@ func handleMessage(backend Backend, peer *Peer) error { } } } + requestTracker.Fulfil(peer.id, peer.version, StorageRangesMsg, res.ID) + return backend.Handle(peer, res) case msg.Code == GetByteCodesMsg: @@ -404,6 +408,8 @@ func handleMessage(backend Backend, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, ByteCodesMsg, res.ID) + return backend.Handle(peer, res) case msg.Code == GetTrieNodesMsg: @@ -497,6 +503,8 @@ func handleMessage(backend Backend, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + requestTracker.Fulfil(peer.id, peer.version, TrieNodesMsg, res.ID) + return backend.Handle(peer, res) default: diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go index 4f3d550f1f..cf0ce65bd7 100644 --- a/eth/protocols/snap/peer.go +++ b/eth/protocols/snap/peer.go @@ -65,6 +65,8 @@ func (p *Peer) Log() log.Logger { // trie, starting with the origin. func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error { p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + + requestTracker.Track(p.id, p.version, GetAccountRangeMsg, AccountRangeMsg, id) return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{ ID: id, Root: root, @@ -83,6 +85,7 @@ func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []comm } else { p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes)) } + requestTracker.Track(p.id, p.version, GetStorageRangesMsg, StorageRangesMsg, id) return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{ ID: id, Root: root, @@ -96,6 +99,8 @@ func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []comm // RequestByteCodes fetches a batch of bytecodes by hash. func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) + + requestTracker.Track(p.id, p.version, GetByteCodesMsg, ByteCodesMsg, id) return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{ ID: id, Hashes: hashes, @@ -107,6 +112,8 @@ func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) e // a specificstate trie. func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + + requestTracker.Track(p.id, p.version, GetTrieNodesMsg, TrieNodesMsg, id) return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{ ID: id, Root: root, diff --git a/eth/protocols/snap/tracker.go b/eth/protocols/snap/tracker.go new file mode 100644 index 0000000000..2cf59cc23a --- /dev/null +++ b/eth/protocols/snap/tracker.go @@ -0,0 +1,26 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "time" + + "github.com/ethereum/go-ethereum/p2p/tracker" +) + +// requestTracker is a singleton tracker for request times. +var requestTracker = tracker.New(ProtocolName, time.Minute) diff --git a/p2p/metrics.go b/p2p/metrics.go index be0d2f495e..1bb505cdfb 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -33,9 +33,6 @@ const ( // HandleHistName is the prefix of the per-packet serving time histograms. HandleHistName = "p2p/handle" - - // WaitHistName is the prefix of the per-packet (req only) waiting time histograms. - WaitHistName = "p2p/wait" ) var ( diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go new file mode 100644 index 0000000000..b50a952f62 --- /dev/null +++ b/p2p/tracker/tracker.go @@ -0,0 +1,203 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package tracker + +import ( + "container/list" + "fmt" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" +) + +const ( + // trackedGaugeName is the prefix of the per-packet request tracking. + trackedGaugeName = "p2p/tracked" + + // lostMeterName is the prefix of the per-packet request expirations. + lostMeterName = "p2p/lost" + + // staleMeterName is the prefix of the per-packet stale responses. + staleMeterName = "p2p/stale" + + // waitHistName is the prefix of the per-packet (req only) waiting time histograms. + waitHistName = "p2p/wait" + + // maxTrackedPackets is a huge number to act as a failsafe on the number of + // pending requests the node will track. It should never be hit unless an + // attacker figures out a way to spin requests. + maxTrackedPackets = 100000 +) + +// request tracks sent network requests which have not yet received a response. +type request struct { + peer string + version uint // Protocol version + + reqCode uint64 // Protocol message code of the request + resCode uint64 // Protocol message code of the expected response + + time time.Time // Timestamp when the request was made + expire *list.Element // Expiration marker to untrack it +} + +// Tracker is a pending network request tracker to measure how much time it takes +// a remote peer to respond. +type Tracker struct { + protocol string // Protocol capability identifier for the metrics + timeout time.Duration // Global timeout after which to drop a tracked packet + + pending map[uint64]*request // Currently pending requests + expire *list.List // Linked list tracking the expiration order + wake *time.Timer // Timer tracking the expiration of the next item + + lock sync.Mutex // Lock protecting from concurrent updates +} + +// New creates a new network request tracker to monitor how much time it takes to +// fill certain requests and how individual peers perform. +func New(protocol string, timeout time.Duration) *Tracker { + return &Tracker{ + protocol: protocol, + timeout: timeout, + pending: make(map[uint64]*request), + expire: list.New(), + } +} + +// Track adds a network request to the tracker to wait for a response to arrive +// or until the request it cancelled or times out. +func (t *Tracker) Track(peer string, version uint, reqCode uint64, resCode uint64, id uint64) { + if !metrics.Enabled { + return + } + t.lock.Lock() + defer t.lock.Unlock() + + // If there's a duplicate request, we've just random-collided (or more probably, + // we have a bug), report it. We could also add a metric, but we're not really + // expecting ourselves to be buggy, so a noisy warning should be enough. + if _, ok := t.pending[id]; ok { + log.Error("Network request id collision", "protocol", t.protocol, "version", version, "code", reqCode, "id", id) + return + } + // If we have too many pending requests, bail out instead of leaking memory + if pending := len(t.pending); pending >= maxTrackedPackets { + log.Error("Request tracker exceeded allowance", "pending", pending, "peer", peer, "protocol", t.protocol, "version", version, "code", reqCode) + return + } + // Id doesn't exist yet, start tracking it + t.pending[id] = &request{ + peer: peer, + version: version, + reqCode: reqCode, + resCode: resCode, + time: time.Now(), + expire: t.expire.PushBack(id), + } + g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, version, reqCode) + metrics.GetOrRegisterGauge(g, nil).Inc(1) + + // If we've just inserted the first item, start the expiration timer + if t.wake == nil { + t.wake = time.AfterFunc(t.timeout, t.clean) + } +} + +// clean is called automatically when a preset time passes without a response +// being dleivered for the first network request. +func (t *Tracker) clean() { + t.lock.Lock() + defer t.lock.Unlock() + + // Expire anything within a certain threshold (might be no items at all if + // we raced with the delivery) + for t.expire.Len() > 0 { + // Stop iterating if the next pending request is still alive + var ( + head = t.expire.Front() + id = head.Value.(uint64) + req = t.pending[id] + ) + if time.Since(req.time) < t.timeout+5*time.Millisecond { + break + } + // Nope, dead, drop it + t.expire.Remove(head) + delete(t.pending, id) + + g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode) + metrics.GetOrRegisterGauge(g, nil).Dec(1) + + m := fmt.Sprintf("%s/%s/%d/%#02x", lostMeterName, t.protocol, req.version, req.reqCode) + metrics.GetOrRegisterMeter(m, nil).Mark(1) + } + t.schedule() +} + +// schedule starts a timer to trigger on the expiration of the first network +// packet. +func (t *Tracker) schedule() { + if t.expire.Len() == 0 { + t.wake = nil + return + } + t.wake = time.AfterFunc(time.Until(t.pending[t.expire.Front().Value.(uint64)].time.Add(t.timeout)), t.clean) +} + +// Fulfil fills a pending request, if any is available, reporting on various metrics. +func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) { + if !metrics.Enabled { + return + } + t.lock.Lock() + defer t.lock.Unlock() + + // If it's a non existing request, track as stale response + req, ok := t.pending[id] + if !ok { + m := fmt.Sprintf("%s/%s/%d/%#02x", staleMeterName, t.protocol, version, code) + metrics.GetOrRegisterMeter(m, nil).Mark(1) + return + } + // If the response is funky, it might be some active attack + if req.peer != peer || req.version != version || req.resCode != code { + log.Warn("Network response id collision", + "have", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, version, code), + "want", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, req.version, req.resCode), + ) + return + } + // Everything matches, mark the request serviced and meter it + t.expire.Remove(req.expire) + if req.expire.Prev() == nil { + t.wake.Stop() + t.schedule() + } + g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode) + metrics.GetOrRegisterGauge(g, nil).Dec(1) + + h := fmt.Sprintf("%s/%s/%d/%#02x", waitHistName, t.protocol, req.version, req.reqCode) + sampler := func() metrics.Sample { + return metrics.ResettingSample( + metrics.NewExpDecaySample(1028, 0.015), + ) + } + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(req.time).Microseconds()) +} From ea54c58d4f37d8cd4563332989caf943f4964333 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 23 Apr 2021 11:15:42 +0200 Subject: [PATCH 480/948] cmd/devp2p/internal/ethtest: run test suite as Go unit test (#22698) This change adds a Go unit test that runs the protocol test suite against the go-ethereum implementation of the eth protocol. --- cmd/devp2p/internal/ethtest/chain.go | 29 ++++- cmd/devp2p/internal/ethtest/eth66_suite.go | 46 ++++++-- .../internal/ethtest/eth66_suiteHelpers.go | 6 +- cmd/devp2p/internal/ethtest/suite.go | 104 ++++++++++++------ cmd/devp2p/internal/ethtest/suite_test.go | 99 +++++++++++++++++ cmd/devp2p/internal/ethtest/transaction.go | 56 ++++++---- cmd/devp2p/internal/ethtest/types.go | 3 +- 7 files changed, 269 insertions(+), 74 deletions(-) create mode 100644 cmd/devp2p/internal/ethtest/suite_test.go diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index d67387e80b..83c55181ad 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -34,6 +34,7 @@ import ( ) type Chain struct { + genesis core.Genesis blocks []*types.Block chainConfig *params.ChainConfig } @@ -124,16 +125,34 @@ func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) { // loadChain takes the given chain.rlp file, and decodes and returns // the blocks from the file. func loadChain(chainfile string, genesis string) (*Chain, error) { - chainConfig, err := ioutil.ReadFile(genesis) + gen, err := loadGenesis(genesis) if err != nil { return nil, err } + gblock := gen.ToBlock(nil) + + blocks, err := blocksFromFile(chainfile, gblock) + if err != nil { + return nil, err + } + + c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config} + return c, nil +} + +func loadGenesis(genesisFile string) (core.Genesis, error) { + chainConfig, err := ioutil.ReadFile(genesisFile) + if err != nil { + return core.Genesis{}, err + } var gen core.Genesis if err := json.Unmarshal(chainConfig, &gen); err != nil { - return nil, err + return core.Genesis{}, err } - gblock := gen.ToBlock(nil) + return gen, nil +} +func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) { // Load chain.rlp. fh, err := os.Open(chainfile) if err != nil { @@ -161,7 +180,5 @@ func loadChain(chainfile string, genesis string) (*Chain, error) { } blocks = append(blocks, &b) } - - c := &Chain{blocks: blocks, chainConfig: gen.Config} - return c, nil + return blocks, nil } diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 0995dcb3e4..41a4246f30 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -41,6 +41,7 @@ func (s *Suite) Is_66(t *utesting.T) { // make sure the chain head is correct. func (s *Suite) TestStatus_66(t *utesting.T) { conn := s.dial66(t) + defer conn.Close() // get protoHandshake conn.handshake(t) // get status @@ -60,6 +61,7 @@ func (s *Suite) TestStatus_66(t *utesting.T) { // an eth66 `GetBlockHeaders` request and that the response is accurate. func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { conn := s.setupConnection66(t) + defer conn.Close() // get block headers req := ð.GetBlockHeadersPacket66{ RequestId: 3, @@ -84,6 +86,8 @@ func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { // create two connections conn1, conn2 := s.setupConnection66(t), s.setupConnection66(t) + defer conn1.Close() + defer conn2.Close() // create two requests req1 := ð.GetBlockHeadersPacket66{ RequestId: 111, @@ -122,6 +126,9 @@ func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { // propagated to the given node's peer(s) on the eth66 protocol. func (s *Suite) TestBroadcast_66(t *utesting.T) { sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) + defer sendConn.Close() + defer receiveConn.Close() + nextBlock := len(s.chain.blocks) blockAnnouncement := &NewBlock{ Block: s.fullChain.blocks[nextBlock], @@ -141,6 +148,7 @@ func (s *Suite) TestBroadcast_66(t *utesting.T) { // the eth66 protocol. func (s *Suite) TestGetBlockBodies_66(t *utesting.T) { conn := s.setupConnection66(t) + defer conn.Close() // create block bodies request id := uint64(55) req := ð.GetBlockBodiesPacket66{ @@ -195,17 +203,20 @@ func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } // Invalid announcement, check that peer disconnected - switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { + switch msg := sendConn.ReadAndServe(s.chain, time.Second*8).(type) { case *Disconnect: case *Error: break default: t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) } + sendConn.Close() } // Test the last block as a valid block - sendConn := s.setupConnection66(t) - receiveConn := s.setupConnection66(t) + sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) + defer sendConn.Close() + defer receiveConn.Close() + s.testAnnounce66(t, sendConn, receiveConn, blocks[3]) // update test suite chain s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) @@ -216,12 +227,17 @@ func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { } func (s *Suite) TestOldAnnounce_66(t *utesting.T) { - s.oldAnnounce(t, s.setupConnection66(t), s.setupConnection66(t)) + sendConn, recvConn := s.setupConnection66(t), s.setupConnection66(t) + defer sendConn.Close() + defer recvConn.Close() + + s.oldAnnounce(t, sendConn, recvConn) } // TestMaliciousHandshake_66 tries to send malicious data during the handshake. func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { conn := s.dial66(t) + defer conn.Close() // write hello to client pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] handshakes := []*Hello{ @@ -295,6 +311,7 @@ func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { // TestMaliciousStatus_66 sends a status package with a large total difficulty. func (s *Suite) TestMaliciousStatus_66(t *utesting.T) { conn := s.dial66(t) + defer conn.Close() // get protoHandshake conn.handshake(t) status := &Status{ @@ -334,23 +351,37 @@ func (s *Suite) TestTransaction_66(t *utesting.T) { } func (s *Suite) TestMaliciousTx_66(t *utesting.T) { - tests := []*types.Transaction{ + badTxs := []*types.Transaction{ getOldTxFromChain(t, s), invalidNonceTx(t, s), hugeAmount(t, s), hugeGasPrice(t, s), hugeData(t, s), } - for i, tx := range tests { + sendConn := s.setupConnection66(t) + defer sendConn.Close() + // set up receiving connection before sending txs to make sure + // no announcements are missed + recvConn := s.setupConnection66(t) + defer recvConn.Close() + + for i, tx := range badTxs { t.Logf("Testing malicious tx propagation: %v\n", i) - sendFailingTx66(t, s, tx) + if err := sendConn.Write(&Transactions{tx}); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + } + // check to make sure bad txs aren't propagated + waitForTxPropagation(t, s, badTxs, recvConn) } // TestZeroRequestID_66 checks that a request ID of zero is still handled // by the node. func (s *Suite) TestZeroRequestID_66(t *utesting.T) { conn := s.setupConnection66(t) + defer conn.Close() + req := ð.GetBlockHeadersPacket66{ RequestId: 0, GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ @@ -367,6 +398,7 @@ func (s *Suite) TestZeroRequestID_66(t *utesting.T) { // concurrently to a single node. func (s *Suite) TestSameRequestID_66(t *utesting.T) { conn := s.setupConnection66(t) + defer conn.Close() // create two separate requests with same ID reqID := uint64(1234) req1 := ð.GetBlockHeadersPacket66{ diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go index 4ef349740f..40427fcd30 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -238,14 +238,10 @@ func (c *Conn) waitForBlock66(block *types.Block) error { func sendSuccessfulTx66(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn := s.setupConnection66(t) + defer sendConn.Close() sendSuccessfulTxWithConn(t, s, tx, sendConn) } -func sendFailingTx66(t *utesting.T, s *Suite, tx *types.Transaction) { - sendConn, recvConn := s.setupConnection66(t), s.setupConnection66(t) - sendFailingTxWithConns(t, s, tx, sendConn, recvConn) -} - func (s *Suite) getBlockHeaders66(t *utesting.T, conn *Conn, req eth.Packet, expectedID uint64) BlockHeaders { if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { t.Fatalf("could not write to connection: %v", err) diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 66fb8026a0..1cae16b7d5 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -69,20 +69,20 @@ func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, e func (s *Suite) AllEthTests() []utesting.Test { return []utesting.Test{ // status - {Name: "Status", Fn: s.TestStatus}, - {Name: "Status_66", Fn: s.TestStatus_66}, + {Name: "TestStatus", Fn: s.TestStatus}, + {Name: "TestStatus_66", Fn: s.TestStatus_66}, // get block headers - {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, - {Name: "GetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestGetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "TestGetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, // get block bodies - {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, - {Name: "GetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "TestGetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, // broadcast - {Name: "Broadcast", Fn: s.TestBroadcast}, - {Name: "Broadcast_66", Fn: s.TestBroadcast_66}, + {Name: "TestBroadcast", Fn: s.TestBroadcast}, + {Name: "TestBroadcast_66", Fn: s.TestBroadcast_66}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, @@ -91,44 +91,44 @@ func (s *Suite) AllEthTests() []utesting.Test { {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, - {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, // test transactions - {Name: "TestTransactions", Fn: s.TestTransaction}, - {Name: "TestTransactions_66", Fn: s.TestTransaction_66}, - {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, - {Name: "TestMaliciousTransactions_66", Fn: s.TestMaliciousTx_66}, + {Name: "TestTransaction", Fn: s.TestTransaction}, + {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, + {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, + {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, } } func (s *Suite) EthTests() []utesting.Test { return []utesting.Test{ - {Name: "Status", Fn: s.TestStatus}, - {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, - {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, - {Name: "Broadcast", Fn: s.TestBroadcast}, + {Name: "TestStatus", Fn: s.TestStatus}, + {Name: "TestGetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "TestBroadcast", Fn: s.TestBroadcast}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, - {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus}, - {Name: "TestTransactions", Fn: s.TestTransaction}, - {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, + {Name: "TestTransaction", Fn: s.TestTransaction}, + {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, } } func (s *Suite) Eth66Tests() []utesting.Test { return []utesting.Test{ // only proceed with eth66 test suite if node supports eth 66 protocol - {Name: "Status_66", Fn: s.TestStatus_66}, - {Name: "GetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestStatus_66", Fn: s.TestStatus_66}, + {Name: "TestGetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, - {Name: "GetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, - {Name: "Broadcast_66", Fn: s.TestBroadcast_66}, + {Name: "TestGetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + {Name: "TestBroadcast_66", Fn: s.TestBroadcast_66}, {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, - {Name: "TestTransactions_66", Fn: s.TestTransaction_66}, - {Name: "TestMaliciousTransactions_66", Fn: s.TestMaliciousTx_66}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, + {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, + {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, } } @@ -140,6 +140,7 @@ func (s *Suite) TestStatus(t *utesting.T) { if err != nil { t.Fatalf("could not dial: %v", err) } + defer conn.Close() // get protoHandshake conn.handshake(t) // get status @@ -157,6 +158,7 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) { if err != nil { t.Fatalf("could not dial: %v", err) } + defer conn.Close() // get protoHandshake conn.handshake(t) status := &Status{ @@ -191,6 +193,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { if err != nil { t.Fatalf("could not dial: %v", err) } + defer conn.Close() conn.handshake(t) conn.statusExchange(t, s.chain, nil) @@ -229,6 +232,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { if err != nil { t.Fatalf("could not dial: %v", err) } + defer conn.Close() conn.handshake(t) conn.statusExchange(t, s.chain, nil) @@ -253,6 +257,9 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { // propagated to the given node's peer(s). func (s *Suite) TestBroadcast(t *utesting.T) { sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) + defer sendConn.Close() + defer receiveConn.Close() + nextBlock := len(s.chain.blocks) blockAnnouncement := &NewBlock{ Block: s.fullChain.blocks[nextBlock], @@ -273,6 +280,7 @@ func (s *Suite) TestMaliciousHandshake(t *utesting.T) { if err != nil { t.Fatalf("could not dial: %v", err) } + defer conn.Close() // write hello to client pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] handshakes := []*Hello{ @@ -372,17 +380,21 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { t.Fatalf("could not write to connection: %v", err) } // Invalid announcement, check that peer disconnected - switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { + switch msg := sendConn.ReadAndServe(s.chain, time.Second*8).(type) { case *Disconnect: case *Error: break default: t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) } + sendConn.Close() } // Test the last block as a valid block sendConn := s.setupConnection(t) receiveConn := s.setupConnection(t) + defer sendConn.Close() + defer receiveConn.Close() + s.testAnnounce(t, sendConn, receiveConn, blocks[3]) // update test suite chain s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) @@ -393,7 +405,11 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { } func (s *Suite) TestOldAnnounce(t *utesting.T) { - s.oldAnnounce(t, s.setupConnection(t), s.setupConnection(t)) + sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) + defer sendConn.Close() + defer recvConn.Close() + + s.oldAnnounce(t, sendConn, recvConn) } func (s *Suite) oldAnnounce(t *utesting.T, sendConn, receiveConn *Conn) { @@ -406,11 +422,19 @@ func (s *Suite) oldAnnounce(t *utesting.T, sendConn, receiveConn *Conn) { t.Fatalf("could not write to connection: %v", err) } - switch msg := receiveConn.ReadAndServe(s.chain, timeout*2).(type) { + switch msg := receiveConn.ReadAndServe(s.chain, time.Second*8).(type) { case *NewBlock: - t.Fatalf("unexpected: block propagated: %s", pretty.Sdump(msg)) + block := *msg + if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { + t.Fatalf("unexpected: block propagated: %s", pretty.Sdump(msg)) + } case *NewBlockHashes: - t.Fatalf("unexpected: block announced: %s", pretty.Sdump(msg)) + hashes := *msg + for _, hash := range hashes { + if hash.Hash == oldBlockAnnounce.Block.Hash() { + t.Fatalf("unexpected: block announced: %s", pretty.Sdump(msg)) + } + } case *Error: errMsg := *msg // check to make sure error is timeout (propagation didn't come through == test successful) @@ -502,15 +526,27 @@ func (s *Suite) TestTransaction(t *utesting.T) { } func (s *Suite) TestMaliciousTx(t *utesting.T) { - tests := []*types.Transaction{ + badTxs := []*types.Transaction{ getOldTxFromChain(t, s), invalidNonceTx(t, s), hugeAmount(t, s), hugeGasPrice(t, s), hugeData(t, s), } - for i, tx := range tests { + sendConn := s.setupConnection(t) + defer sendConn.Close() + // set up receiving connection before sending txs to make sure + // no announcements are missed + recvConn := s.setupConnection(t) + defer recvConn.Close() + + for i, tx := range badTxs { t.Logf("Testing malicious tx propagation: %v\n", i) - sendFailingTx(t, s, tx) + if err := sendConn.Write(&Transactions{tx}); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + } + // check to make sure bad txs aren't propagated + waitForTxPropagation(t, s, badTxs, recvConn) } diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go new file mode 100644 index 0000000000..2c628757bc --- /dev/null +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -0,0 +1,99 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "os" + "testing" + + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + genesisFile = "./testdata/genesis.json" + halfchainFile = "./testdata/halfchain.rlp" + fullchainFile = "./testdata/chain.rlp" +) + +func TestEthSuite(t *testing.T) { + geth, err := runGeth() + if err != nil { + t.Fatalf("could not run geth: %v", err) + } + defer geth.Close() + + suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile) + if err != nil { + t.Fatalf("could not create new test suite: %v", err) + } + for _, test := range suite.AllEthTests() { + t.Run(test.Name, func(t *testing.T) { + result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) + if result[0].Failed { + t.Fatal() + } + }) + } +} + +// runGeth creates and starts a geth node +func runGeth() (*node.Node, error) { + stack, err := node.New(&node.Config{ + P2P: p2p.Config{ + ListenAddr: "127.0.0.1:0", + NoDiscovery: true, + MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future + NoDial: true, + }, + }) + if err != nil { + return nil, err + } + + err = setupGeth(stack) + if err != nil { + stack.Close() + return nil, err + } + if err = stack.Start(); err != nil { + stack.Close() + return nil, err + } + return stack, nil +} + +func setupGeth(stack *node.Node) error { + chain, err := loadChain(halfchainFile, genesisFile) + if err != nil { + return err + } + + backend, err := eth.New(stack, ðconfig.Config{ + Genesis: &chain.genesis, + NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 + }) + if err != nil { + return err + } + + _, err = backend.BlockChain().InsertChain(chain.blocks[1:]) + return err +} diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index 21aa221e8b..f8b0a9da81 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -30,6 +30,7 @@ var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c666 func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { sendConn := s.setupConnection(t) + defer sendConn.Close() sendSuccessfulTxWithConn(t, s, tx, sendConn) } @@ -65,29 +66,30 @@ func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, se } } -func sendFailingTx(t *utesting.T, s *Suite, tx *types.Transaction) { - sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) - sendFailingTxWithConns(t, s, tx, sendConn, recvConn) -} - -func sendFailingTxWithConns(t *utesting.T, s *Suite, tx *types.Transaction, sendConn, recvConn *Conn) { - // Wait for a transaction announcement - switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { - case *NewPooledTransactionHashes: - break - default: - t.Logf("unexpected message, logging: %v", pretty.Sdump(msg)) - } - // Send the transaction - if err := sendConn.Write(&Transactions{tx}); err != nil { - t.Fatal(err) - } +func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, recvConn *Conn) { // Wait for another transaction announcement - switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + switch msg := recvConn.ReadAndServe(s.chain, time.Second*8).(type) { case *Transactions: - t.Fatalf("Received unexpected transaction announcement: %v", msg) + // check to see if any of the failing txs were in the announcement + recvTxs := make([]common.Hash, len(*msg)) + for i, recvTx := range *msg { + recvTxs[i] = recvTx.Hash() + } + badTxs := containsTxs(recvTxs, txs) + if len(badTxs) > 0 { + for _, tx := range badTxs { + t.Logf("received bad tx: %v", tx) + } + t.Fatalf("received %d bad txs", len(badTxs)) + } case *NewPooledTransactionHashes: - t.Fatalf("Received unexpected pooledTx announcement: %v", msg) + badTxs := containsTxs(*msg, txs) + if len(badTxs) > 0 { + for _, tx := range badTxs { + t.Logf("received bad tx: %v", tx) + } + t.Fatalf("received %d bad txs", len(badTxs)) + } case *Error: // Transaction should not be announced -> wait for timeout return @@ -96,6 +98,20 @@ func sendFailingTxWithConns(t *utesting.T, s *Suite, tx *types.Transaction, send } } +// containsTxs checks whether the hashes of the received transactions are present in +// the given set of txs +func containsTxs(recvTxs []common.Hash, txs []*types.Transaction) []common.Hash { + containedTxs := make([]common.Hash, 0) + for _, recvTx := range recvTxs { + for _, tx := range txs { + if recvTx == tx.Hash() { + containedTxs = append(containedTxs, recvTx) + } + } + } + return containedTxs +} + func unknownTx(t *utesting.T, s *Suite) *types.Transaction { tx := getNextTxFromChain(t, s) var to common.Address diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 1e2ae77965..50108f2dc3 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -178,8 +178,7 @@ func (c *Conn) Read() Message { func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message { start := time.Now() for time.Since(start) < timeout { - timeout := time.Now().Add(10 * time.Second) - c.SetReadDeadline(timeout) + c.SetReadDeadline(time.Now().Add(5 * time.Second)) switch msg := c.Read().(type) { case *Ping: c.Write(&Pong{}) From 49281ab84fa5d2dd5704ce35ad984d415b529c4f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 23 Apr 2021 13:39:18 +0200 Subject: [PATCH 481/948] core/state/snapshot, true: reuse dirty data instead of hitting disk when generating (#22667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * core/state/snapshot: reuse memory data instead of hitting disk when generating * trie: minor nitpicks wrt the resolver optimization * core/state/snapshot, trie: use key/value store for resolver * trie: fix linter Co-authored-by: Péter Szilágyi --- core/state/snapshot/generate.go | 20 ++++++++++++- trie/iterator.go | 50 +++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index ed431fcb3d..13b34f4d69 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" @@ -434,6 +435,20 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, } meter.Mark(1) } + + // We use the snap data to build up a cache which can be used by the + // main account trie as a primary lookup when resolving hashes + var snapNodeCache ethdb.KeyValueStore + if len(result.keys) > 0 { + snapNodeCache = memorydb.New() + snapTrieDb := trie.NewDatabase(snapNodeCache) + snapTrie, _ := trie.New(common.Hash{}, snapTrieDb) + for i, key := range result.keys { + snapTrie.Update(key, result.vals[i]) + } + root, _ := snapTrie.Commit(nil) + snapTrieDb.Commit(root, false, nil) + } tr := result.tr if tr == nil { tr, err = trie.New(root, dl.triedb) @@ -442,9 +457,11 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, return false, nil, errMissingTrie } } + var ( trieMore bool - iter = trie.NewIterator(tr.NodeIterator(origin)) + nodeIt = tr.NodeIterator(origin) + iter = trie.NewIterator(nodeIt) kvkeys, kvvals = result.keys, result.vals // counters @@ -458,6 +475,7 @@ func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, start = time.Now() internal time.Duration ) + nodeIt.AddResolver(snapNodeCache) for iter.Next() { if last != nil && bytes.Compare(iter.Key, last) > 0 { trieMore = true diff --git a/trie/iterator.go b/trie/iterator.go index 4f72258a1d..406f216c22 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -22,6 +22,7 @@ import ( "errors" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" ) @@ -102,6 +103,19 @@ type NodeIterator interface { // iterator is not positioned at a leaf. Callers must not retain references // to the value after calling Next. LeafProof() [][]byte + + // AddResolver sets an intermediate database to use for looking up trie nodes + // before reaching into the real persistent layer. + // + // This is not required for normal operation, rather is an optimization for + // cases where trie nodes can be recovered from some external mechanism without + // reading from disk. In those cases, this resolver allows short circuiting + // accesses and returning them from memory. + // + // Before adding a similar mechanism to any other place in Geth, consider + // making trie.Database an interface and wrapping at that level. It's a huge + // refactor, but it could be worth it if another occurrence arises. + AddResolver(ethdb.KeyValueStore) } // nodeIteratorState represents the iteration state at one particular node of the @@ -119,6 +133,8 @@ type nodeIterator struct { stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state path []byte // Path to the current node err error // Failure set in case of an internal error in the iterator + + resolver ethdb.KeyValueStore // Optional intermediate resolver above the disk layer } // errIteratorEnd is stored in nodeIterator.err when iteration is done. @@ -143,6 +159,10 @@ func newNodeIterator(trie *Trie, start []byte) NodeIterator { return it } +func (it *nodeIterator) AddResolver(resolver ethdb.KeyValueStore) { + it.resolver = resolver +} + func (it *nodeIterator) Hash() common.Hash { if len(it.stack) == 0 { return common.Hash{} @@ -262,7 +282,7 @@ func (it *nodeIterator) init() (*nodeIteratorState, error) { if root != emptyRoot { state.hash = root } - return state, state.resolve(it.trie, nil) + return state, state.resolve(it, nil) } // peek creates the next state of the iterator. @@ -286,7 +306,7 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er } state, path, ok := it.nextChild(parent, ancestor) if ok { - if err := state.resolve(it.trie, path); err != nil { + if err := state.resolve(it, path); err != nil { return parent, &parent.index, path, err } return state, &parent.index, path, nil @@ -319,7 +339,7 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by } state, path, ok := it.nextChildAt(parent, ancestor, seekKey) if ok { - if err := state.resolve(it.trie, path); err != nil { + if err := state.resolve(it, path); err != nil { return parent, &parent.index, path, err } return state, &parent.index, path, nil @@ -330,9 +350,21 @@ func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []by return nil, nil, nil, errIteratorEnd } -func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error { +func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) { + if it.resolver != nil { + if blob, err := it.resolver.Get(hash); err == nil && len(blob) > 0 { + if resolved, err := decodeNode(hash, blob); err == nil { + return resolved, nil + } + } + } + resolved, err := it.trie.resolveHash(hash, path) + return resolved, err +} + +func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error { if hash, ok := st.node.(hashNode); ok { - resolved, err := tr.resolveHash(hash, path) + resolved, err := it.resolveHash(hash, path) if err != nil { return err } @@ -517,6 +549,10 @@ func (it *differenceIterator) Path() []byte { return it.b.Path() } +func (it *differenceIterator) AddResolver(resolver ethdb.KeyValueStore) { + panic("not implemented") +} + func (it *differenceIterator) Next(bool) bool { // Invariants: // - We always advance at least one element in b. @@ -624,6 +660,10 @@ func (it *unionIterator) Path() []byte { return (*it.items)[0].Path() } +func (it *unionIterator) AddResolver(resolver ethdb.KeyValueStore) { + panic("not implemented") +} + // Next returns the next node in the union of tries being iterated over. // // It does this by maintaining a heap of iterators, sorted by the iteration From cac1b21d392370f8768a9ee45a9a10c0b5ddcc9b Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 23 Apr 2021 18:14:39 +0200 Subject: [PATCH 482/948] cmd/devp2p/internal/ethtest: add more tx propagation tests (#22630) This adds a test for large tx announcement messages, as well as a test to check that announced tx hashes are requested by the node. --- cmd/devp2p/internal/ethtest/eth66_suite.go | 97 ++++++++++--- .../internal/ethtest/eth66_suiteHelpers.go | 90 +++++++++--- cmd/devp2p/internal/ethtest/suite.go | 5 +- cmd/devp2p/internal/ethtest/transaction.go | 132 +++++++++++++++--- cmd/devp2p/internal/ethtest/types.go | 12 ++ 5 files changed, 278 insertions(+), 58 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 41a4246f30..176d8bf33c 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -19,6 +19,7 @@ package ethtest import ( "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/protocols/eth" @@ -125,22 +126,7 @@ func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { // TestBroadcast_66 tests whether a block announcement is correctly // propagated to the given node's peer(s) on the eth66 protocol. func (s *Suite) TestBroadcast_66(t *utesting.T) { - sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) - defer sendConn.Close() - defer receiveConn.Close() - - nextBlock := len(s.chain.blocks) - blockAnnouncement := &NewBlock{ - Block: s.fullChain.blocks[nextBlock], - TD: s.fullChain.TD(nextBlock + 1), - } - s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) - // wait for client to update its chain - if err := receiveConn.waitForBlock66(s.chain.Head()); err != nil { - t.Fatal(err) - } + s.sendNextBlock66(t) } // TestGetBlockBodies_66 tests whether the given node can respond to @@ -426,3 +412,82 @@ func (s *Suite) TestSameRequestID_66(t *utesting.T) { // check response from first request headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req1, reqID)) } + +// TestLargeTxRequest_66 tests whether a node can fulfill a large GetPooledTransactions +// request. +func (s *Suite) TestLargeTxRequest_66(t *utesting.T) { + // send the next block to ensure the node is no longer syncing and is able to accept + // txs + s.sendNextBlock66(t) + // send 2000 transactions to the node + hashMap, txs := generateTxs(t, s, 2000) + sendConn := s.setupConnection66(t) + defer sendConn.Close() + + sendMultipleSuccessfulTxs(t, s, sendConn, txs) + // set up connection to receive to ensure node is peered with the receiving connection + // before tx request is sent + recvConn := s.setupConnection66(t) + defer recvConn.Close() + // create and send pooled tx request + hashes := make([]common.Hash, 0) + for _, hash := range hashMap { + hashes = append(hashes, hash) + } + getTxReq := ð.GetPooledTransactionsPacket66{ + RequestId: 1234, + GetPooledTransactionsPacket: hashes, + } + if err := recvConn.write66(getTxReq, GetPooledTransactions{}.Code()); err != nil { + t.Fatalf("could not write to conn: %v", err) + } + // check that all received transactions match those that were sent to node + switch msg := recvConn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) { + case PooledTransactions: + for _, gotTx := range msg { + if _, exists := hashMap[gotTx.Hash()]; !exists { + t.Fatalf("unexpected tx received: %v", gotTx.Hash()) + } + } + default: + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } +} + +// TestNewPooledTxs_66 tests whether a node will do a GetPooledTransactions +// request upon receiving a NewPooledTransactionHashes announcement. +func (s *Suite) TestNewPooledTxs_66(t *utesting.T) { + // send the next block to ensure the node is no longer syncing and is able to accept + // txs + s.sendNextBlock66(t) + // generate 50 txs + hashMap, _ := generateTxs(t, s, 50) + // create new pooled tx hashes announcement + hashes := make([]common.Hash, 0) + for _, hash := range hashMap { + hashes = append(hashes, hash) + } + announce := NewPooledTransactionHashes(hashes) + // send announcement + conn := s.setupConnection66(t) + defer conn.Close() + if err := conn.Write(announce); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // wait for GetPooledTxs request + for { + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case GetPooledTransactions: + if len(msg) != len(hashes) { + t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg)) + } + return + case *NewPooledTransactionHashes: + // ignore propagated txs from old tests + continue + default: + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } + } +} diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go index 40427fcd30..3af8295c61 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -111,6 +111,18 @@ func (c *Conn) read66() (uint64, Message) { msg = new(Transactions) case (NewPooledTransactionHashes{}).Code(): msg = new(NewPooledTransactionHashes) + case (GetPooledTransactions{}.Code()): + ethMsg := new(eth.GetPooledTransactionsPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetPooledTransactions(ethMsg.GetPooledTransactionsPacket) + case (PooledTransactions{}.Code()): + ethMsg := new(eth.PooledTransactionsPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, PooledTransactions(ethMsg.PooledTransactionsPacket) default: msg = errorf("invalid message code: %d", code) } @@ -124,6 +136,15 @@ func (c *Conn) read66() (uint64, Message) { return 0, errorf("invalid message: %s", string(rawData)) } +func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { + for { + id, msg := c.readAndServe66(chain, timeout) + if id == requestID { + return msg + } + } +} + // ReadAndServe serves GetBlockHeaders requests while waiting // on another message from the node. func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { @@ -173,27 +194,33 @@ func (s *Suite) testAnnounce66(t *utesting.T, sendConn, receiveConn *Conn, block } func (s *Suite) waitAnnounce66(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { - timeout := 20 * time.Second - _, msg := conn.readAndServe66(s.chain, timeout) - switch msg := msg.(type) { - case *NewBlock: - t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) - assert.Equal(t, - blockAnnouncement.Block.Header(), msg.Block.Header(), - "wrong block header in announcement", - ) - assert.Equal(t, - blockAnnouncement.TD, msg.TD, - "wrong TD in announcement", - ) - case *NewBlockHashes: - blockHashes := *msg - t.Logf("received NewBlockHashes message: %s", pretty.Sdump(blockHashes)) - assert.Equal(t, blockAnnouncement.Block.Hash(), blockHashes[0].Hash, - "wrong block hash in announcement", - ) - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + for { + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case *NewBlock: + t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) + assert.Equal(t, + blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement", + ) + assert.Equal(t, + blockAnnouncement.TD, msg.TD, + "wrong TD in announcement", + ) + return + case *NewBlockHashes: + blockHashes := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(blockHashes)) + assert.Equal(t, blockAnnouncement.Block.Hash(), blockHashes[0].Hash, + "wrong block hash in announcement", + ) + return + case *NewPooledTransactionHashes: + // ignore old txs being propagated + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } } } @@ -268,3 +295,24 @@ func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) { assert.Equal(t, chain.blocks[int(num)].Header(), header) } } + +func (s *Suite) sendNextBlock66(t *utesting.T) { + sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) + defer sendConn.Close() + defer receiveConn.Close() + + // create new block announcement + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + // send announcement and wait for node to request the header + s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) + // wait for client to update its chain + if err := receiveConn.waitForBlock66(s.chain.Head()); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 1cae16b7d5..2fa31ad31d 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -97,6 +97,8 @@ func (s *Suite) AllEthTests() []utesting.Test { {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, + {Name: "TestLargeTxRequest_66", Fn: s.TestLargeTxRequest_66}, + {Name: "TestNewPooledTxs_66", Fn: s.TestNewPooledTxs_66}, } } @@ -129,6 +131,8 @@ func (s *Suite) Eth66Tests() []utesting.Test { {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, + {Name: "TestLargeTxRequest_66", Fn: s.TestLargeTxRequest_66}, + {Name: "TestNewPooledTxs_66", Fn: s.TestNewPooledTxs_66}, } } @@ -455,7 +459,6 @@ func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAn } func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { - timeout := 20 * time.Second switch msg := conn.ReadAndServe(s.chain, timeout).(type) { case *NewBlock: t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index f8b0a9da81..a6166bd2e3 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -17,12 +17,15 @@ package ethtest import ( + "math/big" + "strings" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/params" ) //var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") @@ -40,7 +43,9 @@ func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, se if err := sendConn.Write(&Transactions{tx}); err != nil { t.Fatal(err) } - time.Sleep(100 * time.Millisecond) + // update last nonce seen + nonce = tx.Nonce() + recvConn := s.setupConnection(t) // Wait for the transaction announcement switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { @@ -66,6 +71,60 @@ func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, se } } +var nonce = uint64(99) + +func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*types.Transaction) { + txMsg := Transactions(txs) + t.Logf("sending %d txs\n", len(txs)) + + recvConn := s.setupConnection(t) + defer recvConn.Close() + + // Send the transactions + if err := sendConn.Write(&txMsg); err != nil { + t.Fatal(err) + } + // update nonce + nonce = txs[len(txs)-1].Nonce() + // Wait for the transaction announcement(s) and make sure all sent txs are being propagated + recvHashes := make([]common.Hash, 0) + // all txs should be announced within 3 announcements + for i := 0; i < 3; i++ { + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + for _, tx := range *msg { + recvHashes = append(recvHashes, tx.Hash()) + } + case *NewPooledTransactionHashes: + recvHashes = append(recvHashes, *msg...) + default: + if !strings.Contains(pretty.Sdump(msg), "i/o timeout") { + t.Fatalf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg)) + } + } + // break once all 2000 txs have been received + if len(recvHashes) == 2000 { + break + } + if len(recvHashes) > 0 { + _, missingTxs := compareReceivedTxs(recvHashes, txs) + if len(missingTxs) > 0 { + continue + } else { + t.Logf("successfully received all %d txs", len(txs)) + return + } + } + } + _, missingTxs := compareReceivedTxs(recvHashes, txs) + if len(missingTxs) > 0 { + for _, missing := range missingTxs { + t.Logf("missing tx: %v", missing.Hash()) + } + t.Fatalf("missing %d txs", len(missingTxs)) + } +} + func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, recvConn *Conn) { // Wait for another transaction announcement switch msg := recvConn.ReadAndServe(s.chain, time.Second*8).(type) { @@ -75,7 +134,7 @@ func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, rec for i, recvTx := range *msg { recvTxs[i] = recvTx.Hash() } - badTxs := containsTxs(recvTxs, txs) + badTxs, _ := compareReceivedTxs(recvTxs, txs) if len(badTxs) > 0 { for _, tx := range badTxs { t.Logf("received bad tx: %v", tx) @@ -83,7 +142,7 @@ func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, rec t.Fatalf("received %d bad txs", len(badTxs)) } case *NewPooledTransactionHashes: - badTxs := containsTxs(*msg, txs) + badTxs, _ := compareReceivedTxs(*msg, txs) if len(badTxs) > 0 { for _, tx := range badTxs { t.Logf("received bad tx: %v", tx) @@ -98,18 +157,27 @@ func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, rec } } -// containsTxs checks whether the hashes of the received transactions are present in -// the given set of txs -func containsTxs(recvTxs []common.Hash, txs []*types.Transaction) []common.Hash { - containedTxs := make([]common.Hash, 0) - for _, recvTx := range recvTxs { - for _, tx := range txs { - if recvTx == tx.Hash() { - containedTxs = append(containedTxs, recvTx) - } +// compareReceivedTxs compares the received set of txs against the given set of txs, +// returning both the set received txs that were present within the given txs, and +// the set of txs that were missing from the set of received txs +func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (present []*types.Transaction, missing []*types.Transaction) { + // create a map of the hashes received from node + recvHashes := make(map[common.Hash]common.Hash) + for _, hash := range recvTxs { + recvHashes[hash] = hash + } + + // collect present txs and missing txs separately + present = make([]*types.Transaction, 0) + missing = make([]*types.Transaction, 0) + for _, tx := range txs { + if _, exists := recvHashes[tx.Hash()]; exists { + present = append(present, tx) + } else { + missing = append(missing, tx) } } - return containedTxs + return present, missing } func unknownTx(t *utesting.T, s *Suite) *types.Transaction { @@ -119,7 +187,7 @@ func unknownTx(t *utesting.T, s *Suite) *types.Transaction { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(t, txNew) + return signWithFaucet(t, s.chain.chainConfig, txNew) } func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction { @@ -138,6 +206,30 @@ func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction { return tx } +func generateTxs(t *utesting.T, s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction) { + txHashMap := make(map[common.Hash]common.Hash, numTxs) + txs := make([]*types.Transaction, numTxs) + + nextTx := getNextTxFromChain(t, s) + gas := nextTx.Gas() + + nonce = nonce + 1 + // generate txs + for i := 0; i < numTxs; i++ { + tx := generateTx(t, s.chain.chainConfig, nonce, gas) + txHashMap[tx.Hash()] = tx.Hash() + txs[i] = tx + nonce = nonce + 1 + } + return txHashMap, txs +} + +func generateTx(t *utesting.T, chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction { + var to common.Address + tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{}) + return signWithFaucet(t, chainConfig, tx) +} + func getOldTxFromChain(t *utesting.T, s *Suite) *types.Transaction { var tx *types.Transaction for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] { @@ -160,7 +252,7 @@ func invalidNonceTx(t *utesting.T, s *Suite) *types.Transaction { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(t, txNew) + return signWithFaucet(t, s.chain.chainConfig, txNew) } func hugeAmount(t *utesting.T, s *Suite) *types.Transaction { @@ -171,7 +263,7 @@ func hugeAmount(t *utesting.T, s *Suite) *types.Transaction { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(t, txNew) + return signWithFaucet(t, s.chain.chainConfig, txNew) } func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction { @@ -182,7 +274,7 @@ func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data()) - return signWithFaucet(t, txNew) + return signWithFaucet(t, s.chain.chainConfig, txNew) } func hugeData(t *utesting.T, s *Suite) *types.Transaction { @@ -192,11 +284,11 @@ func hugeData(t *utesting.T, s *Suite) *types.Transaction { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2)) - return signWithFaucet(t, txNew) + return signWithFaucet(t, s.chain.chainConfig, txNew) } -func signWithFaucet(t *utesting.T, tx *types.Transaction) *types.Transaction { - signer := types.HomesteadSigner{} +func signWithFaucet(t *utesting.T, chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction { + signer := types.LatestSigner(chainConfig) signedTx, err := types.SignTx(tx, signer, faucetKey) if err != nil { t.Fatalf("could not sign tx: %v\n", err) diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 50108f2dc3..55adb75f85 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -120,6 +120,14 @@ type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket func (nb NewPooledTransactionHashes) Code() int { return 24 } +type GetPooledTransactions eth.GetPooledTransactionsPacket + +func (gpt GetPooledTransactions) Code() int { return 25 } + +type PooledTransactions eth.PooledTransactionsPacket + +func (pt PooledTransactions) Code() int { return 26 } + // Conn represents an individual connection with a peer type Conn struct { *rlpx.Conn @@ -163,6 +171,10 @@ func (c *Conn) Read() Message { msg = new(Transactions) case (NewPooledTransactionHashes{}).Code(): msg = new(NewPooledTransactionHashes) + case (GetPooledTransactions{}.Code()): + msg = new(GetPooledTransactions) + case (PooledTransactions{}.Code()): + msg = new(PooledTransactions) default: return errorf("invalid message code: %d", code) } From 34f3c9539b49396b17524ee72ea20498f96600b9 Mon Sep 17 00:00:00 2001 From: Nishant Das Date: Sat, 24 Apr 2021 00:18:10 +0800 Subject: [PATCH 483/948] p2p/discover: improve discv5 handling of IPv4-in-IPv6 addresses (#22703) When receiving PING from an IPv4 address over IPv6, the implementation sent back a IPv4-in-IPv6 address. This change makes it reflect the IPv4 address. --- p2p/discover/v5_udp.go | 9 ++++++++- p2p/discover/v5_udp_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index eb01d95e93..71a39ea5a5 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -763,9 +763,16 @@ func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, err // handlePing sends a PONG response. func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) { + remoteIP := fromAddr.IP + // Handle IPv4 mapped IPv6 addresses in the + // event the local node is binded to an + // ipv6 interface. + if remoteIP.To4() != nil { + remoteIP = remoteIP.To4() + } t.sendResponse(fromID, fromAddr, &v5wire.Pong{ ReqID: p.ReqID, - ToIP: fromAddr.IP, + ToIP: remoteIP, ToPort: uint16(fromAddr.Port), ENRSeq: t.localNode.Node().Seq(), }) diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index 292785bd51..f061f5ab41 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -597,6 +597,38 @@ func TestUDPv5_LocalNode(t *testing.T) { } } +func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5) + test.remoteaddr = &net.UDPAddr{ + IP: rawIP.To16(), + Port: 0, + } + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan struct{}, 1) + + // This handler will truncate the ipv4-mapped in ipv6 address. + go func() { + test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr) + done <- struct{}{} + }() + test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) { + if len(p.ToIP) == net.IPv6len { + t.Error("Received untruncated ip address") + } + if len(p.ToIP) != net.IPv4len { + t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP)) + } + if !p.ToIP.Equal(rawIP) { + t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String()) + } + }) + <-done +} + // udpV5Test is the framework for all tests above. // It runs the UDPv5 transport on a virtual socket and allows testing outgoing packets. type udpV5Test struct { From 83375b08731d95fdc91b6e1576db596c21384a3d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 26 Apr 2021 14:27:56 +0200 Subject: [PATCH 484/948] core: remove old conversion to shuffle leveldb blocks into ancients --- core/blockchain.go | 63 ++++++---------------------------------------- 1 file changed, 7 insertions(+), 56 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 49aa1c3e86..2e3c5cf908 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1207,63 +1207,14 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if !bc.HasHeader(block.Hash(), block.NumberU64()) { return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4]) } - var ( - start = time.Now() - logged = time.Now() - count int - ) - // Migrate all ancient blocks. This can happen if someone upgrades from Geth - // 1.8.x to 1.9.x mid-fast-sync. Perhaps we can get rid of this path in the - // long term. - for { - // We can ignore the error here since light client won't hit this code path. - frozen, _ := bc.db.Ancients() - if frozen >= block.NumberU64() { - break - } - h := rawdb.ReadCanonicalHash(bc.db, frozen) - b := rawdb.ReadBlock(bc.db, h, frozen) - size += rawdb.WriteAncientBlock(bc.db, b, rawdb.ReadReceipts(bc.db, h, frozen, bc.chainConfig), rawdb.ReadTd(bc.db, h, frozen)) - count += 1 - - // Always keep genesis block in active database. - if b.NumberU64() != 0 { - deleted = append(deleted, &numberHash{b.NumberU64(), b.Hash()}) + if block.NumberU64() == 1 { + // Make sure to write the genesis into the freezer + if frozen, _ := bc.db.Ancients(); frozen == 0 { + h := rawdb.ReadCanonicalHash(bc.db, 0) + b := rawdb.ReadBlock(bc.db, h, 0) + size += rawdb.WriteAncientBlock(bc.db, b, rawdb.ReadReceipts(bc.db, h, 0, bc.chainConfig), rawdb.ReadTd(bc.db, h, 0)) + log.Info("Wrote genesis to ancients") } - if time.Since(logged) > 8*time.Second { - log.Info("Migrating ancient blocks", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) - logged = time.Now() - } - // Don't collect too much in-memory, write it out every 100K blocks - if len(deleted) > 100000 { - // Sync the ancient store explicitly to ensure all data has been flushed to disk. - if err := bc.db.Sync(); err != nil { - return 0, err - } - // Wipe out canonical block data. - for _, nh := range deleted { - rawdb.DeleteBlockWithoutNumber(batch, nh.hash, nh.number) - rawdb.DeleteCanonicalHash(batch, nh.number) - } - if err := batch.Write(); err != nil { - return 0, err - } - batch.Reset() - // Wipe out side chain too. - for _, nh := range deleted { - for _, hash := range rawdb.ReadAllHashes(bc.db, nh.number) { - rawdb.DeleteBlock(batch, hash, nh.number) - } - } - if err := batch.Write(); err != nil { - return 0, err - } - batch.Reset() - deleted = deleted[0:] - } - } - if count > 0 { - log.Info("Migrated ancient blocks", "count", count, "elapsed", common.PrettyDuration(time.Since(start))) } // Flush data into ancient database. size += rawdb.WriteAncientBlock(bc.db, block, receiptChain[i], bc.GetTd(block.Hash(), block.NumberU64())) From 9b99e3dfe04baabdb51917673bc046e33731caca Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 26 Apr 2021 18:19:07 +0200 Subject: [PATCH 485/948] core/rawdb: fix datarace in freezer (#22728) The Append / truncate operations were racy. When a datafile reaches 2Gb, a new file is needed. For this operation, we require a writelock, which is not needed in the 99.99% of all cases where the data does fit in the current head-file. This transition from readlock to writelock was incorrect, and as the readlock was released, a truncate operation could slip in between, and truncate the data. This would have been fine, however, the Append operation continued writing as if no truncation had occurred, e.g writing item 5 where item 0 should reside. This PR changes the behaviour, so that if when we run into the situation that a new file is needed, it aborts, and retries, this time with a writelock. The outcome of the situation described above, running on this PR, would instead be that the Append operation exits with a failure. --- core/rawdb/freezer_table.go | 92 ++++++++++++++++++++------------ core/rawdb/freezer_table_test.go | 58 ++++++++++++++++++-- 2 files changed, 113 insertions(+), 37 deletions(-) diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index b614c10d37..d7bfe18e02 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -465,35 +465,59 @@ func (t *freezerTable) releaseFilesAfter(num uint32, remove bool) { // Note, this method will *not* flush any data to disk so be sure to explicitly // fsync before irreversibly deleting data from the database. func (t *freezerTable) Append(item uint64, blob []byte) error { + // Encode the blob before the lock portion + if !t.noCompression { + blob = snappy.Encode(nil, blob) + } // Read lock prevents competition with truncate - t.lock.RLock() + retry, err := t.append(item, blob, false) + if err != nil { + return err + } + if retry { + // Read lock was insufficient, retry with a writelock + _, err = t.append(item, blob, true) + } + return err +} + +// append injects a binary blob at the end of the freezer table. +// Normally, inserts do not require holding the write-lock, so it should be invoked with 'wlock' set to +// false. +// However, if the data will grown the current file out of bounds, then this +// method will return 'true, nil', indicating that the caller should retry, this time +// with 'wlock' set to true. +func (t *freezerTable) append(item uint64, encodedBlob []byte, wlock bool) (bool, error) { + if wlock { + t.lock.Lock() + defer t.lock.Unlock() + } else { + t.lock.RLock() + defer t.lock.RUnlock() + } // Ensure the table is still accessible if t.index == nil || t.head == nil { - t.lock.RUnlock() - return errClosed + return false, errClosed } // Ensure only the next item can be written, nothing else if atomic.LoadUint64(&t.items) != item { - t.lock.RUnlock() - return fmt.Errorf("appending unexpected item: want %d, have %d", t.items, item) - } - // Encode the blob and write it into the data file - if !t.noCompression { - blob = snappy.Encode(nil, blob) + return false, fmt.Errorf("appending unexpected item: want %d, have %d", t.items, item) } - bLen := uint32(len(blob)) + bLen := uint32(len(encodedBlob)) if t.headBytes+bLen < bLen || t.headBytes+bLen > t.maxFileSize { - // we need a new file, writing would overflow - t.lock.RUnlock() - t.lock.Lock() + // Writing would overflow, so we need to open a new data file. + // If we don't already hold the writelock, abort and let the caller + // invoke this method a second time. + if !wlock { + return true, nil + } nextID := atomic.LoadUint32(&t.headId) + 1 // We open the next file in truncated mode -- if this file already // exists, we need to start over from scratch on it newHead, err := t.openFile(nextID, openFreezerFileTruncated) if err != nil { - t.lock.Unlock() - return err + return false, err } // Close old file, and reopen in RDONLY mode t.releaseFile(t.headId) @@ -503,13 +527,9 @@ func (t *freezerTable) Append(item uint64, blob []byte) error { t.head = newHead atomic.StoreUint32(&t.headBytes, 0) atomic.StoreUint32(&t.headId, nextID) - t.lock.Unlock() - t.lock.RLock() } - - defer t.lock.RUnlock() - if _, err := t.head.Write(blob); err != nil { - return err + if _, err := t.head.Write(encodedBlob); err != nil { + return false, err } newOffset := atomic.AddUint32(&t.headBytes, bLen) idx := indexEntry{ @@ -523,7 +543,7 @@ func (t *freezerTable) Append(item uint64, blob []byte) error { t.sizeGauge.Inc(int64(bLen + indexEntrySize)) atomic.AddUint64(&t.items, 1) - return nil + return false, nil } // getBounds returns the indexes for the item @@ -562,44 +582,48 @@ func (t *freezerTable) getBounds(item uint64) (uint32, uint32, uint32, error) { // Retrieve looks up the data offset of an item with the given number and retrieves // the raw binary blob from the data file. func (t *freezerTable) Retrieve(item uint64) ([]byte, error) { + blob, err := t.retrieve(item) + if err != nil { + return nil, err + } + if t.noCompression { + return blob, nil + } + return snappy.Decode(nil, blob) +} + +// retrieve looks up the data offset of an item with the given number and retrieves +// the raw binary blob from the data file. OBS! This method does not decode +// compressed data. +func (t *freezerTable) retrieve(item uint64) ([]byte, error) { t.lock.RLock() + defer t.lock.RUnlock() // Ensure the table and the item is accessible if t.index == nil || t.head == nil { - t.lock.RUnlock() return nil, errClosed } if atomic.LoadUint64(&t.items) <= item { - t.lock.RUnlock() return nil, errOutOfBounds } // Ensure the item was not deleted from the tail either if uint64(t.itemOffset) > item { - t.lock.RUnlock() return nil, errOutOfBounds } startOffset, endOffset, filenum, err := t.getBounds(item - uint64(t.itemOffset)) if err != nil { - t.lock.RUnlock() return nil, err } dataFile, exist := t.files[filenum] if !exist { - t.lock.RUnlock() return nil, fmt.Errorf("missing data file %d", filenum) } // Retrieve the data itself, decompress and return blob := make([]byte, endOffset-startOffset) if _, err := dataFile.ReadAt(blob, int64(startOffset)); err != nil { - t.lock.RUnlock() return nil, err } - t.lock.RUnlock() t.readMeter.Mark(int64(len(blob) + 2*indexEntrySize)) - - if t.noCompression { - return blob, nil - } - return snappy.Decode(nil, blob) + return blob, nil } // has returns an indicator whether the specified number data diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index b8d3170c62..0df28f236d 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -18,10 +18,13 @@ package rawdb import ( "bytes" + "encoding/binary" "fmt" + "io/ioutil" "math/rand" "os" "path/filepath" + "sync" "testing" "time" @@ -637,6 +640,55 @@ func TestOffset(t *testing.T) { // 1. have data files d0, d1, d2, d3 // 2. remove d2,d3 // -// However, all 'normal' failure modes arising due to failing to sync() or save a file should be -// handled already, and the case described above can only (?) happen if an external process/user -// deletes files from the filesystem. +// However, all 'normal' failure modes arising due to failing to sync() or save a file +// should be handled already, and the case described above can only (?) happen if an +// external process/user deletes files from the filesystem. + +// TestAppendTruncateParallel is a test to check if the Append/truncate operations are +// racy. +// +// The reason why it's not a regular fuzzer, within tests/fuzzers, is that it is dependent +// on timing rather than 'clever' input -- there's no determinism. +func TestAppendTruncateParallel(t *testing.T) { + dir, err := ioutil.TempDir("", "freezer") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + f, err := newCustomTable(dir, "tmp", metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, 8, true) + if err != nil { + t.Fatal(err) + } + + fill := func(mark uint64) []byte { + data := make([]byte, 8) + binary.LittleEndian.PutUint64(data, mark) + return data + } + + for i := 0; i < 5000; i++ { + f.truncate(0) + data0 := fill(0) + f.Append(0, data0) + data1 := fill(1) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + f.truncate(0) + wg.Done() + }() + go func() { + f.Append(1, data1) + wg.Done() + }() + wg.Wait() + + if have, err := f.Retrieve(0); err == nil { + if !bytes.Equal(have, data0) { + t.Fatalf("have %x want %x", have, data0) + } + } + } +} From 854f068ed60918c624857594a703a6724b8efb3a Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 27 Apr 2021 15:44:59 +0800 Subject: [PATCH 486/948] les: polish code (#22625) * les: polish code * les/vflus/server: fixes * les: fix lint --- les/metrics.go | 1 - les/peer.go | 1 - les/server_handler.go | 21 +++--- les/vflux/server/prioritypool.go | 123 +++++++++++++++++++------------ 4 files changed, 83 insertions(+), 63 deletions(-) diff --git a/les/metrics.go b/les/metrics.go index d356326b76..07d3133c95 100644 --- a/les/metrics.go +++ b/les/metrics.go @@ -71,7 +71,6 @@ var ( connectionTimer = metrics.NewRegisteredTimer("les/connection/duration", nil) serverConnectionGauge = metrics.NewRegisteredGauge("les/connection/server", nil) - clientConnectionGauge = metrics.NewRegisteredGauge("les/connection/client", nil) totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil) totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil) diff --git a/les/peer.go b/les/peer.go index f6cc94dfad..c6c672942b 100644 --- a/les/peer.go +++ b/les/peer.go @@ -1099,7 +1099,6 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge // set default announceType on server side p.announceType = announceTypeSimple } - p.fcClient = flowcontrol.NewClientNode(server.fcManager, p.fcParams) } return nil }) diff --git a/les/server_handler.go b/les/server_handler.go index 0a683c1b41..80fcf1c44e 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -122,26 +123,27 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } - + // Connected to another server, no messages expected, just wait for disconnection if p.server { if err := h.server.serverset.register(p); err != nil { return err } - // connected to another server, no messages expected, just wait for disconnection _, err := p.rw.ReadMsg() h.server.serverset.unregister(p) return err } - defer p.fcClient.Disconnect() // set by handshake if it's not another server + // Setup flow control mechanism for the peer + p.fcClient = flowcontrol.NewClientNode(h.server.fcManager, p.fcParams) + defer p.fcClient.Disconnect() - // Reject light clients if server is not synced. - // - // Put this checking here, so that "non-synced" les-server peers are still allowed - // to keep the connection. + // Reject light clients if server is not synced. Put this checking here, so + // that "non-synced" les-server peers are still allowed to keep the connection. if !h.synced() { p.Log().Debug("Light server not synced, rejecting peer") return p2p.DiscRequested } + + // Register the peer into the peerset and clientpool if err := h.server.peers.register(p); err != nil { return err } @@ -150,19 +152,14 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Client pool already closed") return p2p.DiscRequested } - activeCount, _ := h.server.clientPool.Active() - clientConnectionGauge.Update(int64(activeCount)) p.connectedAt = mclock.Now() var wg sync.WaitGroup // Wait group used to track all in-flight task routines. - defer func() { wg.Wait() // Ensure all background task routines have exited. h.server.clientPool.Unregister(p) h.server.peers.unregister(p.ID()) p.balance = nil - activeCount, _ := h.server.clientPool.Active() - clientConnectionGauge.Update(int64(activeCount)) connectionTimer.Update(time.Duration(mclock.Now() - p.connectedAt)) }() diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go index 573a3570a4..480f77e6af 100644 --- a/les/vflux/server/prioritypool.go +++ b/les/vflux/server/prioritypool.go @@ -63,20 +63,22 @@ type priorityPool struct { ns *nodestate.NodeStateMachine clock mclock.Clock lock sync.Mutex - inactiveQueue *prque.Prque maxCount, maxCap uint64 minCap uint64 activeBias time.Duration capacityStepDiv, fineStepDiv uint64 + // The snapshot of priority pool for query. cachedCurve *capacityCurve ccUpdatedAt mclock.AbsTime ccUpdateForced bool - tempState []*ppNodeInfo // nodes currently in temporary state - // the following fields represent the temporary state if tempState is not empty + // Runtime status of prioritypool, represents the + // temporary state if tempState is not empty + tempState []*ppNodeInfo activeCount, activeCap uint64 activeQueue *prque.LazyQueue + inactiveQueue *prque.Prque } // ppNodeInfo is the internal node descriptor of priorityPool @@ -89,8 +91,9 @@ type ppNodeInfo struct { tempState bool // should only be true while the priorityPool lock is held tempCapacity uint64 // equals capacity when tempState is false + // the following fields only affect the temporary state and they are set to their - // default value when entering the temp state + // default value when leaving the temp state minTarget, stepDiv uint64 bias time.Duration } @@ -157,11 +160,6 @@ func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock m func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget uint64, bias time.Duration) uint64 { pp.lock.Lock() pp.activeQueue.Refresh() - var updates []capUpdate - defer func() { - pp.lock.Unlock() - pp.updateFlags(updates) - }() if minTarget < pp.minCap { minTarget = pp.minCap @@ -175,12 +173,13 @@ func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget u c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) if c == nil { log.Error("requestCapacity called for unknown node", "id", node.ID()) + pp.lock.Unlock() return 0 } pp.setTempState(c) if maxTarget > c.capacity { - c.bias = bias - c.stepDiv = pp.fineStepDiv + pp.setTempStepDiv(c, pp.fineStepDiv) + pp.setTempBias(c, bias) } pp.setTempCapacity(c, maxTarget) c.minTarget = minTarget @@ -188,7 +187,9 @@ func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget u pp.inactiveQueue.Remove(c.inactiveIndex) pp.activeQueue.Push(c) pp.enforceLimits() - updates = pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity) + updates := pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity) + pp.lock.Unlock() + pp.updateFlags(updates) return c.capacity } @@ -196,15 +197,11 @@ func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget u func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) { pp.lock.Lock() pp.activeQueue.Refresh() - var updates []capUpdate - defer func() { - pp.lock.Unlock() - pp.ns.Operation(func() { pp.updateFlags(updates) }) - }() - inc := (maxCount > pp.maxCount) || (maxCap > pp.maxCap) dec := (maxCount < pp.maxCount) || (maxCap < pp.maxCap) pp.maxCount, pp.maxCap = maxCount, maxCap + + var updates []capUpdate if dec { pp.enforceLimits() updates = pp.finalizeChanges(true) @@ -212,6 +209,8 @@ func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) { if inc { updates = append(updates, pp.tryActivate(false)...) } + pp.lock.Unlock() + pp.ns.Operation(func() { pp.updateFlags(updates) }) } // setActiveBias sets the bias applied when trying to activate inactive nodes @@ -291,18 +290,15 @@ func (pp *priorityPool) inactivePriority(p *ppNodeInfo) int64 { func (pp *priorityPool) connectedNode(c *ppNodeInfo) { pp.lock.Lock() pp.activeQueue.Refresh() - var updates []capUpdate - defer func() { - pp.lock.Unlock() - pp.updateFlags(updates) - }() - if c.connected { + pp.lock.Unlock() return } c.connected = true pp.inactiveQueue.Push(c, pp.inactivePriority(c)) - updates = pp.tryActivate(false) + updates := pp.tryActivate(false) + pp.lock.Unlock() + pp.updateFlags(updates) } // disconnectedNode is called when a node has been removed from the pool (both inactiveFlag @@ -311,23 +307,22 @@ func (pp *priorityPool) connectedNode(c *ppNodeInfo) { func (pp *priorityPool) disconnectedNode(c *ppNodeInfo) { pp.lock.Lock() pp.activeQueue.Refresh() - var updates []capUpdate - defer func() { - pp.lock.Unlock() - pp.updateFlags(updates) - }() - if !c.connected { + pp.lock.Unlock() return } c.connected = false pp.activeQueue.Remove(c.activeIndex) pp.inactiveQueue.Remove(c.inactiveIndex) + + var updates []capUpdate if c.capacity != 0 { pp.setTempState(c) pp.setTempCapacity(c, 0) updates = pp.tryActivate(true) } + pp.lock.Unlock() + pp.updateFlags(updates) } // setTempState internally puts a node in a temporary state that can either be reverted @@ -342,27 +337,62 @@ func (pp *priorityPool) setTempState(c *ppNodeInfo) { if c.tempCapacity != c.capacity { // should never happen log.Error("tempCapacity != capacity when entering tempState") } + // Assign all the defaults to the temp state. c.minTarget = pp.minCap c.stepDiv = pp.capacityStepDiv + c.bias = 0 pp.tempState = append(pp.tempState, c) } +// unsetTempState revokes the temp status of the node and reset all internal +// fields to the default value. +func (pp *priorityPool) unsetTempState(c *ppNodeInfo) { + if !c.tempState { + return + } + c.tempState = false + if c.tempCapacity != c.capacity { // should never happen + log.Error("tempCapacity != capacity when leaving tempState") + } + c.minTarget = pp.minCap + c.stepDiv = pp.capacityStepDiv + c.bias = 0 +} + // setTempCapacity changes the capacity of a node in the temporary state and adjusts // activeCap and activeCount accordingly. Since this change is performed in the temporary // state it should be called after setTempState and before finalizeChanges. -func (pp *priorityPool) setTempCapacity(n *ppNodeInfo, cap uint64) { - if !n.tempState { // should never happen +func (pp *priorityPool) setTempCapacity(c *ppNodeInfo, cap uint64) { + if !c.tempState { // should never happen log.Error("Node is not in temporary state") return } - pp.activeCap += cap - n.tempCapacity - if n.tempCapacity == 0 { + pp.activeCap += cap - c.tempCapacity + if c.tempCapacity == 0 { pp.activeCount++ } if cap == 0 { pp.activeCount-- } - n.tempCapacity = cap + c.tempCapacity = cap +} + +// setTempBias changes the connection bias of a node in the temporary state. +func (pp *priorityPool) setTempBias(c *ppNodeInfo, bias time.Duration) { + if !c.tempState { // should never happen + log.Error("Node is not in temporary state") + return + } + c.bias = bias +} + +// setTempStepDiv changes the capacity divisor of a node in the temporary state. +func (pp *priorityPool) setTempStepDiv(c *ppNodeInfo, stepDiv uint64) { + if !c.tempState { // should never happen + log.Error("Node is not in temporary state") + return + } + c.stepDiv = stepDiv } // enforceLimits enforces active node count and total capacity limits. It returns the @@ -412,10 +442,8 @@ func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) { } else { pp.setTempCapacity(c, c.capacity) // revert activeCount/activeCap } - c.tempState = false - c.bias = 0 - c.stepDiv = pp.capacityStepDiv - c.minTarget = pp.minCap + pp.unsetTempState(c) + if c.connected { if c.capacity != 0 { pp.activeQueue.Push(c) @@ -462,13 +490,13 @@ func (pp *priorityPool) tryActivate(commit bool) []capUpdate { for pp.inactiveQueue.Size() > 0 { c := pp.inactiveQueue.PopItem().(*ppNodeInfo) pp.setTempState(c) + pp.setTempBias(c, pp.activeBias) pp.setTempCapacity(c, pp.minCap) - c.bias = pp.activeBias pp.activeQueue.Push(c) pp.enforceLimits() if c.tempCapacity > 0 { commit = true - c.bias = 0 + pp.setTempBias(c, 0) } else { break } @@ -483,14 +511,9 @@ func (pp *priorityPool) tryActivate(commit bool) []capUpdate { func (pp *priorityPool) updatePriority(node *enode.Node) { pp.lock.Lock() pp.activeQueue.Refresh() - var updates []capUpdate - defer func() { - pp.lock.Unlock() - pp.updateFlags(updates) - }() - c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) if c == nil || !c.connected { + pp.lock.Unlock() return } pp.activeQueue.Remove(c.activeIndex) @@ -500,7 +523,9 @@ func (pp *priorityPool) updatePriority(node *enode.Node) { } else { pp.inactiveQueue.Push(c, pp.inactivePriority(c)) } - updates = pp.tryActivate(false) + updates := pp.tryActivate(false) + pp.lock.Unlock() + pp.updateFlags(updates) } // capacityCurve is a snapshot of the priority pool contents in a format that can efficiently From a3f0da1ac42cf78769921ebf6974e4e4c6a197ae Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 27 Apr 2021 10:49:06 +0200 Subject: [PATCH 487/948] build: upgrade to golangci-lint v1.39.0 (#22696) * build: upgrade to golangci-lint v1.39.0 * consensus/ethash: fix go vet warning regarding reflect.SliceHeader * eth/catalyst: fix lint issue * consensus/ethash: fix bug in memoryMapFile --- build/checksums.txt | 33 +++++++++++++++++---------------- build/ci.go | 2 +- consensus/ethash/ethash.go | 13 +++++++------ eth/catalyst/api_test.go | 18 +++++++++++++++--- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index d5bd4d0cd3..7c8d3fd5b1 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -13,19 +13,20 @@ d7d6c70b05a7c2f68b48aab5ab8cb5116b8444c9ddad131673b152e7cff7c726 go1.16.freebsd 27a1aaa988e930b7932ce459c8a63ad5b3333b3a06b016d87ff289f2a11aacd6 go1.16.linux-ppc64le.tar.gz be4c9e4e2cf058efc4e3eb013a760cb989ddc4362f111950c990d1c63b27ccbe go1.16.linux-s390x.tar.gz -d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz -bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip -bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint-1.27.0-windows-amd64.zip -0e2a57d6ba709440d3ed018ef1037465fa010ed02595829092860e5cf863042e golangci-lint-1.27.0-freebsd-386.tar.gz -90205fc42ab5ed0096413e790d88ac9b4ed60f4c47e576d13dc0660f7ed4b013 golangci-lint-1.27.0-linux-arm64.tar.gz -8d345e4e88520e21c113d81978e89ad77fc5b13bfdf20e5bca86b83fc4261272 golangci-lint-1.27.0-linux-amd64.tar.gz -cc619634a77f18dc73df2a0725be13116d64328dc35131ca1737a850d6f76a59 golangci-lint-1.27.0-freebsd-armv7.tar.gz -fe683583cfc9eeec83e498c0d6159d87b5e1919dbe4b6c3b3913089642906069 golangci-lint-1.27.0-linux-s390x.tar.gz -058f5579bee75bdaacbaf75b75e1369f7ad877fd8b3b145aed17a17545de913e golangci-lint-1.27.0-freebsd-armv6.tar.gz -38e1e3dadbe3f56ab62b4de82ee0b88e8fad966d8dfd740a26ef94c2edef9818 golangci-lint-1.27.0-linux-armv6.tar.gz -071b34af5516f4e1ddcaea6011e18208f4f043e1af8ba21eeccad4585cb3d095 golangci-lint.exe-1.27.0-windows-386.zip -071b34af5516f4e1ddcaea6011e18208f4f043e1af8ba21eeccad4585cb3d095 golangci-lint-1.27.0-windows-386.zip -5f37e2b33914ecddb7cad38186ef4ec61d88172fc04f930fa0267c91151ff306 golangci-lint-1.27.0-linux-386.tar.gz -4d94cfb51fdebeb205f1d5a349ac2b683c30591c5150708073c1c329e15965f0 golangci-lint-1.27.0-freebsd-amd64.tar.gz -52572ba8ff07d5169c2365d3de3fec26dc55a97522094d13d1596199580fa281 golangci-lint-1.27.0-linux-ppc64le.tar.gz -3fb1a1683a29c6c0a8cd76135f62b606fbdd538d5a7aeab94af1af70ffdc2fd4 golangci-lint-1.27.0-darwin-amd64.tar.gz +7e9a47ab540aa3e8472fbf8120d28bed3b9d9cf625b955818e8bc69628d7187c golangci-lint-1.39.0-darwin-amd64.tar.gz +574daa2c9c299b01672a6daeb1873b5f12e413cdb6dc0e30f2ff163956778064 golangci-lint-1.39.0-darwin-arm64.tar.gz +6225f7014987324ab78e9b511f294e3f25be013728283c33918c67c8576d543e golangci-lint-1.39.0-freebsd-386.tar.gz +6b3e76e1e5eaf0159411c8e2727f8d533989d3bb19f10e9caa6e0b9619ee267d golangci-lint-1.39.0-freebsd-amd64.tar.gz +a301cacfff87ed9b00313d95278533c25a4527a06b040a17d969b4b7e1b8a90d golangci-lint-1.39.0-freebsd-armv7.tar.gz +25bfd96a29c3112f508d5e4fc860dbad7afce657233c343acfa20715717d51e7 golangci-lint-1.39.0-freebsd-armv6.tar.gz +9687e4ff15545cfc722b0e46107a94195166a505023b48a316579af25ad09505 golangci-lint-1.39.0-linux-armv7.tar.gz +a7fa7ab2bfc99cbe5e5bcbf5684f5a997f920afbbe2f253d2feb1001d5e3c8b3 golangci-lint-1.39.0-linux-armv6.tar.gz +c8f9634115beddb4ed9129c1f7ecd4c97c99d07aeef33e3707234097eeb51b7b golangci-lint-1.39.0-linux-mips64le.tar.gz +d1234c213b74751f1af413302dde0e9a6d4d29aecef034af7abb07dc1b6e887f golangci-lint-1.39.0-linux-arm64.tar.gz +df25d9267168323b163147acb823ab0215a8a3bb6898a4a9320afdfedde66817 golangci-lint-1.39.0-linux-386.tar.gz +1767e75fba357b7651b1a796d38453558f371c60af805505ec99e166908c04b5 golangci-lint-1.39.0-linux-ppc64le.tar.gz +25fd75bf3186b3d930ecae10185689968fd18fd8fa6f9f555d6beb04348c20f6 golangci-lint-1.39.0-linux-s390x.tar.gz +3a73aa7468087caa62673c8adea99b4e4dff846dc72707222db85f8679b40cbf golangci-lint-1.39.0-linux-amd64.tar.gz +578caceccf81739bda67dbfec52816709d03608c6878888ecdc0e186a094a41b golangci-lint-1.39.0-linux-mips64.tar.gz +494b66ba0e32c8ddf6c4f6b1d05729b110900f6017eda943057e43598c17d7a8 golangci-lint-1.39.0-windows-386.zip +52ec2e13a3cbb47147244dff8cfc35103563deb76e0459133058086fc35fb2c7 golangci-lint-1.39.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index fb06c0f420..c94b96821e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -379,7 +379,7 @@ func doLint(cmdline []string) { // downloadLinter downloads and unpacks golangci-lint. func downloadLinter(cachedir string) string { - const version = "1.27.0" + const version = "1.39.0" csdb := build.MustLoadChecksums("build/checksums.txt") base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH) diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index d922be7773..ec06d02a54 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -112,12 +112,13 @@ func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) { if err != nil { return nil, nil, err } - // Yay, we managed to memory map the file, here be dragons - header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem)) - header.Len /= 4 - header.Cap /= 4 - - return mem, *(*[]uint32)(unsafe.Pointer(&header)), nil + // The file is now memory-mapped. Create a []uint32 view of the file. + var view []uint32 + header := (*reflect.SliceHeader)(unsafe.Pointer(&view)) + header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&mem)).Data + header.Cap = len(mem) / 4 + header.Len = header.Cap + return mem, view, nil } // memoryMapAndGenerate tries to memory map a temporary file of uint32s for write diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 456b6867bd..b8a6e43fcd 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -20,7 +20,6 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -67,8 +66,21 @@ func generateTestChainWithFork(n int, fork int) (*core.Genesis, []*types.Block, fork = n - 1 } db := rawdb.NewMemoryDatabase() - //nolint:composites - config := ¶ms.ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, big.NewInt(0), new(params.EthashConfig), nil} + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1337), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CatalystBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + } genesis := &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, From ad983b300b138b2300402187c2e50f9afdc4b2a3 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Tue, 27 Apr 2021 03:36:57 -0600 Subject: [PATCH 488/948] cmd/puppeth: add support for authentication via ssh agent (#22634) --- cmd/puppeth/ssh.go | 62 ++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/cmd/puppeth/ssh.go b/cmd/puppeth/ssh.go index da2862db2f..039cb6cb45 100644 --- a/cmd/puppeth/ssh.go +++ b/cmd/puppeth/ssh.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/log" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/terminal" ) @@ -43,6 +44,8 @@ type sshClient struct { logger log.Logger } +const EnvSSHAuthSock = "SSH_AUTH_SOCK" + // dial establishes an SSH connection to a remote node using the current user and // the user's configured private RSA key. If that fails, password authentication // is fallen back to. server can be a string like user:identity@server:port. @@ -79,38 +82,49 @@ func dial(server string, pubkey []byte) (*sshClient, error) { if username == "" { username = user.Username } - // Configure the supported authentication methods (private key and password) - var auths []ssh.AuthMethod - path := filepath.Join(user.HomeDir, ".ssh", identity) - if buf, err := ioutil.ReadFile(path); err != nil { - log.Warn("No SSH key, falling back to passwords", "path", path, "err", err) + // Configure the supported authentication methods (ssh agent, private key and password) + var ( + auths []ssh.AuthMethod + conn net.Conn + ) + if conn, err = net.Dial("unix", os.Getenv(EnvSSHAuthSock)); err != nil { + log.Warn("Unable to dial SSH agent, falling back to private keys", "err", err) } else { - key, err := ssh.ParsePrivateKey(buf) - if err != nil { - fmt.Printf("What's the decryption password for %s? (won't be echoed)\n>", path) - blob, err := terminal.ReadPassword(int(os.Stdin.Fd())) - fmt.Println() - if err != nil { - log.Warn("Couldn't read password", "err", err) - } - key, err := ssh.ParsePrivateKeyWithPassphrase(buf, blob) + client := agent.NewClient(conn) + auths = append(auths, ssh.PublicKeysCallback(client.Signers)) + } + if err != nil { + path := filepath.Join(user.HomeDir, ".ssh", identity) + if buf, err := ioutil.ReadFile(path); err != nil { + log.Warn("No SSH key, falling back to passwords", "path", path, "err", err) + } else { + key, err := ssh.ParsePrivateKey(buf) if err != nil { - log.Warn("Failed to decrypt SSH key, falling back to passwords", "path", path, "err", err) + fmt.Printf("What's the decryption password for %s? (won't be echoed)\n>", path) + blob, err := terminal.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + if err != nil { + log.Warn("Couldn't read password", "err", err) + } + key, err := ssh.ParsePrivateKeyWithPassphrase(buf, blob) + if err != nil { + log.Warn("Failed to decrypt SSH key, falling back to passwords", "path", path, "err", err) + } else { + auths = append(auths, ssh.PublicKeys(key)) + } } else { auths = append(auths, ssh.PublicKeys(key)) } - } else { - auths = append(auths, ssh.PublicKeys(key)) } - } - auths = append(auths, ssh.PasswordCallback(func() (string, error) { - fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server) - blob, err := terminal.ReadPassword(int(os.Stdin.Fd())) + auths = append(auths, ssh.PasswordCallback(func() (string, error) { + fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server) + blob, err := terminal.ReadPassword(int(os.Stdin.Fd())) - fmt.Println() - return string(blob), err - })) + fmt.Println() + return string(blob), err + })) + } // Resolve the IP address of the remote server addr, err := net.LookupHost(hostname) if err != nil { From a0a99e610df1485b685138ddd9efd12bddb8a945 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 27 Apr 2021 12:43:47 +0200 Subject: [PATCH 489/948] build: upgrade -dlgo version to Go 1.16.3 (#22746) --- build/checksums.txt | 25 +++++++++++++------------ build/ci.go | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index 7c8d3fd5b1..0e33e07f2c 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,17 +1,18 @@ # This file contains sha256 checksums of optional build dependencies. -7688063d55656105898f323d90a79a39c378d86fe89ae192eb3b7fc46347c95a go1.16.src.tar.gz -6000a9522975d116bf76044967d7e69e04e982e9625330d9a539a8b45395f9a8 go1.16.darwin-amd64.tar.gz -ea435a1ac6d497b03e367fdfb74b33e961d813883468080f6e239b3b03bea6aa go1.16.linux-386.tar.gz -013a489ebb3e24ef3d915abe5b94c3286c070dfe0818d5bca8108f1d6e8440d2 go1.16.linux-amd64.tar.gz -3770f7eb22d05e25fbee8fb53c2a4e897da043eb83c69b9a14f8d98562cd8098 go1.16.linux-arm64.tar.gz -d1d9404b1dbd77afa2bdc70934e10fbfcf7d785c372efc29462bb7d83d0a32fd go1.16.linux-armv6l.tar.gz -481492a17d42193d471b93b7a06da3555331bd833b76336afc87be820c48933f go1.16.windows-386.zip -5cc88fa506b3d5c453c54c3ea218fc8dd05d7362ae1de15bb67986b72089ce93 go1.16.windows-amd64.zip -d7d6c70b05a7c2f68b48aab5ab8cb5116b8444c9ddad131673b152e7cff7c726 go1.16.freebsd-386.tar.gz -40b03216f6945fb6883a50604fc7f409a83f62171607229a9c598e701e684f8a go1.16.freebsd-amd64.tar.gz -27a1aaa988e930b7932ce459c8a63ad5b3333b3a06b016d87ff289f2a11aacd6 go1.16.linux-ppc64le.tar.gz -be4c9e4e2cf058efc4e3eb013a760cb989ddc4362f111950c990d1c63b27ccbe go1.16.linux-s390x.tar.gz +b298d29de9236ca47a023e382313bcc2d2eed31dfa706b60a04103ce83a71a25 go1.16.3.src.tar.gz +6bb1cf421f8abc2a9a4e39140b7397cdae6aca3e8d36dcff39a1a77f4f1170ac go1.16.3.darwin-amd64.tar.gz +f4e96bbcd5d2d1942f5b55d9e4ab19564da4fad192012f6d7b0b9b055ba4208f go1.16.3.darwin-arm64.tar.gz +48b2d1481db756c88c18b1f064dbfc3e265ce4a775a23177ca17e25d13a24c5d go1.16.3.linux-386.tar.gz +951a3c7c6ce4e56ad883f97d9db74d3d6d80d5fec77455c6ada6c1f7ac4776d2 go1.16.3.linux-amd64.tar.gz +566b1d6f17d2bc4ad5f81486f0df44f3088c3ed47a3bec4099d8ed9939e90d5d go1.16.3.linux-arm64.tar.gz +0dae30385e3564a557dac7f12a63eedc73543e6da0f6017990e214ce8cc8797c go1.16.3.linux-armv6l.tar.gz +a3c16e1531bf9726f47911c4a9ed7cb665a6207a51c44f10ebad4db63b4bcc5a go1.16.3.windows-386.zip +a4400345135b36cb7942e52bbaf978b66814738b855eeff8de879a09fd99de7f go1.16.3.windows-amd64.zip +31ecd11d497684fa8b0f01ba784590c4c760943665fdc4fe0adaa1405c71736c go1.16.3.freebsd-386.tar.gz +ffbd920b309e62e807457b11d80e8c17fefe3ef6de423aaba4b1e270b2ca4c3d go1.16.3.freebsd-amd64.tar.gz +5eb046bbbbc7fe2591846a4303884cb5a01abb903e3e61e33459affe7874e811 go1.16.3.linux-ppc64le.tar.gz +3e8bd7bde533a73fd6fa75b5288678ef397e76c198cfb26b8ae086035383b1cf go1.16.3.linux-s390x.tar.gz 7e9a47ab540aa3e8472fbf8120d28bed3b9d9cf625b955818e8bc69628d7187c golangci-lint-1.39.0-darwin-amd64.tar.gz 574daa2c9c299b01672a6daeb1873b5f12e413cdb6dc0e30f2ff163956778064 golangci-lint-1.39.0-darwin-arm64.tar.gz diff --git a/build/ci.go b/build/ci.go index c94b96821e..b73435c5e6 100644 --- a/build/ci.go +++ b/build/ci.go @@ -152,7 +152,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.16" + dlgoVersion = "1.16.3" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) From 65a1c2d829ca11ba2a9b08aad977807731b6f009 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 27 Apr 2021 13:21:41 +0200 Subject: [PATCH 490/948] core/vm: make gas cost reporting to tracers correct (#22702) Previously, the makeCallVariantGasCallEIP2929 charged the cold account access cost directly, leading to an incorrect gas cost passed to the tracer from the main execution loop. This change still temporarily charges the cost (to allow for an accurate calculation of the available gas for the call), but then afterwards refunds it and instead returns the correct total gas cost to be then properly charged in the main loop. --- core/vm/operations_acl.go | 22 +++++++-- core/vm/runtime/runtime_test.go | 80 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 191953ce5e..45b51d80cd 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -177,10 +177,15 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.Back(1).Bytes20()) // Check slot presence in the access list - if !evm.StateDB.AddressInAccessList(addr) { + warmAccess := evm.StateDB.AddressInAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so + // the cost to charge for cold access, if any, is Cold - Warm + coldCost := ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929 + if !warmAccess { evm.StateDB.AddAddressToAccessList(addr) - // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost - if !contract.UseGas(ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929) { + // Charge the remaining difference here already, to correctly calculate available + // gas for call + if !contract.UseGas(coldCost) { return 0, ErrOutOfGas } } @@ -189,7 +194,16 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { // - transfer value // - memory expansion // - 63/64ths rule - return oldCalculator(evm, contract, stack, mem, memorySize) + gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + if warmAccess || err != nil { + return gas, err + } + // In case of a cold access, we temporarily add the cold charge back, and also + // add it to the returned gas. By adding it to the return, it will be charged + // outside of this function, as part of the dynamic gas, and that will make it + // also become correctly reported to tracers. + contract.Gas += coldCost + return gas + coldCost, nil } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 2692755324..dcf2d0d447 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -608,3 +608,83 @@ func TestEip2929Cases(t *testing.T) { "account (cheap)", code) } } + +// TestColdAccountAccessCost test that the cold account access cost is reported +// correctly +// see: https://github.com/ethereum/go-ethereum/issues/22649 +func TestColdAccountAccessCost(t *testing.T) { + for i, tc := range []struct { + code []byte + step int + want uint64 + }{ + { // EXTCODEHASH(0xff) + code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.EXTCODEHASH), byte(vm.POP)}, + step: 1, + want: 2600, + }, + { // BALANCE(0xff) + code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.BALANCE), byte(vm.POP)}, + step: 1, + want: 2600, + }, + { // CALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALL), byte(vm.POP), + }, + step: 7, + want: 2855, + }, + { // CALLCODE(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALLCODE), byte(vm.POP), + }, + step: 7, + want: 2855, + }, + { // DELEGATECALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.DELEGATECALL), byte(vm.POP), + }, + step: 6, + want: 2855, + }, + { // STATICCALL(0xff) + code: []byte{ + byte(vm.PUSH1), 0x0, + byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.STATICCALL), byte(vm.POP), + }, + step: 6, + want: 2855, + }, + { // SELFDESTRUCT(0xff) + code: []byte{ + byte(vm.PUSH1), 0xff, byte(vm.SELFDESTRUCT), + }, + step: 1, + want: 7600, + }, + } { + tracer := vm.NewStructLogger(nil) + Execute(tc.code, nil, &Config{ + EVMConfig: vm.Config{ + Debug: true, + Tracer: tracer, + }, + }) + have := tracer.StructLogs()[tc.step].GasCost + if want := tc.want; have != want { + for ii, op := range tracer.StructLogs() { + t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost) + } + t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want) + } + } +} From caea6c466147752ed726a79f69e71215131065dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 27 Apr 2021 17:19:59 +0300 Subject: [PATCH 491/948] eth/protocols/snap: generate storage trie from full dirty snap data (#22668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * eth/protocols/snap: generate storage trie from full dirty snap data * eth/protocols/snap: get rid of some more dead code * eth/protocols/snap: less frequent logs, also log during trie generation * eth/protocols/snap: implement dirty account range stack-hashing * eth/protocols/snap: don't loop on account trie generation * eth/protocols/snap: fix account format in trie * core, eth, ethdb: glue snap packets together, but not chunks * eth/protocols/snap: print completion log for snap phase * eth/protocols/snap: extended tests * eth/protocols/snap: make testcase pass * eth/protocols/snap: fix account stacktrie commit without defer * ethdb: fix key counts on reset * eth/protocols: fix typos * eth/protocols/snap: make better use of delivered data (#44) * eth/protocols/snap: make better use of delivered data * squashme * eth/protocols/snap: reduce chunking * squashme * eth/protocols/snap: reduce chunking further * eth/protocols/snap: break out hash range calculations * eth/protocols/snap: use sort.Search instead of looping * eth/protocols/snap: prevent crash on storage response with no keys * eth/protocols/snap: nitpicks all around * eth/protocols/snap: clear heal need on 1-chunk storage completion * eth/protocols/snap: fix range chunker, add tests Co-authored-by: Péter Szilágyi * trie: fix test API error * eth/protocols/snap: fix some further liter issues * eth/protocols/snap: fix accidental batch reuse Co-authored-by: Martin Holst Swende --- core/rawdb/database_test.go | 17 ++ core/rawdb/table.go | 5 + eth/protocols/snap/handler.go | 2 +- eth/protocols/snap/range.go | 80 +++++ eth/protocols/snap/range_test.go | 143 +++++++++ eth/protocols/snap/sync.go | 408 ++++++++++++++----------- eth/protocols/snap/sync_test.go | 113 ++++++- ethdb/batch.go | 3 + ethdb/leveldb/leveldb.go | 9 +- ethdb/memorydb/memorydb.go | 9 +- tests/fuzzers/stacktrie/trie_fuzzer.go | 1 + trie/trie_test.go | 1 + 12 files changed, 603 insertions(+), 188 deletions(-) create mode 100644 core/rawdb/database_test.go create mode 100644 eth/protocols/snap/range.go create mode 100644 eth/protocols/snap/range_test.go diff --git a/core/rawdb/database_test.go b/core/rawdb/database_test.go new file mode 100644 index 0000000000..8bf06f97d8 --- /dev/null +++ b/core/rawdb/database_test.go @@ -0,0 +1,17 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 323ef6293c..4daa6b5349 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -176,6 +176,11 @@ func (b *tableBatch) Delete(key []byte) error { return b.batch.Delete(append([]byte(b.prefix), key...)) } +// KeyCount retrieves the number of keys queued up for writing. +func (b *tableBatch) KeyCount() int { + return b.batch.KeyCount() +} + // ValueSize retrieves the amount of data queued up for writing. func (b *tableBatch) ValueSize() int { return b.batch.ValueSize() diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 4c12adfa81..9bfac6f03f 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -354,7 +354,7 @@ func handleMessage(backend Backend, peer *Peer) error { if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - // Ensure the ranges ae monotonically increasing + // Ensure the ranges are monotonically increasing for i, slots := range res.Slots { for j := 1; j < len(slots); j++ { if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 { diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go new file mode 100644 index 0000000000..dd380ff471 --- /dev/null +++ b/eth/protocols/snap/range.go @@ -0,0 +1,80 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// hashRange is a utility to handle ranges of hashes, Split up the +// hash-space into sections, and 'walk' over the sections +type hashRange struct { + current *uint256.Int + step *uint256.Int +} + +// newHashRange creates a new hashRange, initiated at the start position, +// and with the step set to fill the desired 'num' chunks +func newHashRange(start common.Hash, num uint64) *hashRange { + left := new(big.Int).Sub(hashSpace, start.Big()) + step := new(big.Int).Div( + new(big.Int).Add(left, new(big.Int).SetUint64(num-1)), + new(big.Int).SetUint64(num), + ) + step256 := new(uint256.Int) + step256.SetFromBig(step) + + return &hashRange{ + current: uint256.NewInt().SetBytes32(start[:]), + step: step256, + } +} + +// Next pushes the hash range to the next interval. +func (r *hashRange) Next() bool { + next := new(uint256.Int) + if overflow := next.AddOverflow(r.current, r.step); overflow { + return false + } + r.current = next + return true +} + +// Start returns the first hash in the current interval. +func (r *hashRange) Start() common.Hash { + return r.current.Bytes32() +} + +// End returns the last hash in the current interval. +func (r *hashRange) End() common.Hash { + // If the end overflows (non divisible range), return a shorter interval + next := new(uint256.Int) + if overflow := next.AddOverflow(r.current, r.step); overflow { + return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + } + return new(uint256.Int).Sub(next, uint256.NewInt().SetOne()).Bytes32() +} + +// incHash returns the next hash, in lexicographical order (a.k.a plus one) +func incHash(h common.Hash) common.Hash { + a := uint256.NewInt().SetBytes32(h[:]) + a.Add(a, uint256.NewInt().SetOne()) + return common.Hash(a.Bytes32()) +} diff --git a/eth/protocols/snap/range_test.go b/eth/protocols/snap/range_test.go new file mode 100644 index 0000000000..23273e50bf --- /dev/null +++ b/eth/protocols/snap/range_test.go @@ -0,0 +1,143 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snap + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// Tests that given a starting hash and a density, the hash ranger can correctly +// split up the remaining hash space into a fixed number of chunks. +func TestHashRanges(t *testing.T) { + tests := []struct { + head common.Hash + chunks uint64 + starts []common.Hash + ends []common.Hash + }{ + // Simple test case to split the entire hash range into 4 chunks + { + head: common.Hash{}, + chunks: 4, + starts: []common.Hash{ + {}, + common.HexToHash("0x4000000000000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0x8000000000000000000000000000000000000000000000000000000000000000"), + common.HexToHash("0xc000000000000000000000000000000000000000000000000000000000000000"), + }, + ends: []common.Hash{ + common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + // Split a divisible part of the hash range up into 2 chunks + { + head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"), + chunks: 2, + starts: []common.Hash{ + common.Hash{}, + common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"), + }, + ends: []common.Hash{ + common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + // Split the entire hash range into a non divisible 3 chunks + { + head: common.Hash{}, + chunks: 3, + starts: []common.Hash{ + {}, + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555556"), + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"), + }, + ends: []common.Hash{ + common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), + common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + // Split a part of hash range into a non divisible 3 chunks + { + head: common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"), + chunks: 3, + starts: []common.Hash{ + {}, + common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"), + common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555556"), + }, + ends: []common.Hash{ + common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), + common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + // Split a part of hash range into a non divisible 3 chunks, but with a + // meaningful space size for manual verification. + // - The head being 0xff...f0, we have 14 hashes left in the space + // - Chunking up 14 into 3 pieces is 4.(6), but we need the ceil of 5 to avoid a micro-last-chunk + // - Since the range is not divisible, the last interval will be shrter, capped at 0xff...f + // - The chunk ranges thus needs to be [..0, ..5], [..6, ..b], [..c, ..f] + { + head: common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"), + chunks: 3, + starts: []common.Hash{ + {}, + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"), + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"), + }, + ends: []common.Hash{ + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"), + common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"), + common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + }, + } + for i, tt := range tests { + r := newHashRange(tt.head, tt.chunks) + + var ( + starts = []common.Hash{{}} + ends = []common.Hash{r.End()} + ) + for r.Next() { + starts = append(starts, r.Start()) + ends = append(ends, r.End()) + } + if len(starts) != len(tt.starts) { + t.Errorf("test %d: starts count mismatch: have %d, want %d", i, len(starts), len(tt.starts)) + } + for j := 0; j < len(starts) && j < len(tt.starts); j++ { + if starts[j] != tt.starts[j] { + t.Errorf("test %d, start %d: hash mismatch: have %x, want %x", i, j, starts[j], tt.starts[j]) + } + } + if len(ends) != len(tt.ends) { + t.Errorf("test %d: ends count mismatch: have %d, want %d", i, len(ends), len(tt.ends)) + } + for j := 0; j < len(ends) && j < len(tt.ends); j++ { + if ends[j] != tt.ends[j] { + t.Errorf("test %d, end %d: hash mismatch: have %x, want %x", i, j, ends[j], tt.ends[j]) + } + } + } +} diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index cff1a77e6c..3ce4c8735f 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -23,10 +23,12 @@ import ( "fmt" "math/big" "math/rand" + "sort" "sync" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" @@ -73,7 +75,9 @@ const ( // and waste round trip times. If it's too high, we're capping responses and // waste bandwidth. maxTrieRequestCount = 512 +) +var ( // accountConcurrency is the number of chunks to split the account trie into // to allow concurrent retrievals. accountConcurrency = 16 @@ -81,9 +85,7 @@ const ( // storageConcurrency is the number of chunks to split the a large contract // storage trie into to allow concurrent retrievals. storageConcurrency = 16 -) -var ( // requestTimeout is the maximum time a peer is allowed to spend on serving // a single network request. requestTimeout = 15 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? @@ -127,12 +129,6 @@ type accountResponse struct { hashes []common.Hash // Account hashes in the returned range accounts []*state.Account // Expanded accounts in the returned range - nodes ethdb.KeyValueStore // Database containing the reconstructed trie nodes - trie *trie.Trie // Reconstructed trie to reject incomplete account paths - - bounds map[common.Hash]struct{} // Boundary nodes to avoid persisting incomplete accounts - overflow *light.NodeSet // Overflow nodes to avoid persisting across chunk boundaries - cont bool // Whether the account range has a continuation } @@ -209,12 +205,8 @@ type storageResponse struct { hashes [][]common.Hash // Storage slot hashes in the returned range slots [][][]byte // Storage slot values in the returned range nodes []ethdb.KeyValueStore // Database containing the reconstructed trie nodes - tries []*trie.Trie // Reconstructed tries to reject overflown slots - // Fields relevant for the last account only - bounds map[common.Hash]struct{} // Boundary nodes to avoid persisting (incomplete) - overflow *light.NodeSet // Overflow nodes to avoid persisting across chunk boundaries - cont bool // Whether the last storage range has a continuation + cont bool // Whether the last storage range has a continuation } // trienodeHealRequest tracks a pending state trie request to ensure responses @@ -301,6 +293,9 @@ type accountTask struct { codeTasks map[common.Hash]struct{} // Code hashes that need retrieval stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval + genBatch ethdb.Batch // Batch used by the node generator + genTrie *trie.StackTrie // Node generator from storage slots + done bool // Flag whether the task can be removed } @@ -312,7 +307,11 @@ type storageTask struct { // These fields are internals used during runtime root common.Hash // Storage root hash for this instance req *storageRequest // Pending request to fill this task - done bool // Flag whether the task can be removed + + genBatch ethdb.Batch // Batch used by the node generator + genTrie *trie.StackTrie // Node generator from storage slots + + done bool // Flag whether the task can be removed } // healTask represents the sync task for healing the snap-synced chunk boundaries. @@ -359,7 +358,7 @@ type SyncPeer interface { // trie, starting with the origin. RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error - // RequestStorageRange fetches a batch of storage slots belonging to one or + // RequestStorageRanges fetches a batch of storage slots belonging to one or // more accounts. If slots from only one accout is requested, an origin marker // may also be used to retrieve from there. RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error @@ -680,6 +679,17 @@ func (s *Syncer) loadSyncStatus() { log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last) } s.tasks = progress.Tasks + for _, task := range s.tasks { + task.genBatch = s.db.NewBatch() + task.genTrie = trie.NewStackTrie(task.genBatch) + + for _, subtasks := range task.SubTasks { + for _, subtask := range subtasks { + subtask.genBatch = s.db.NewBatch() + subtask.genTrie = trie.NewStackTrie(task.genBatch) + } + } + } s.snapped = len(s.tasks) == 0 s.accountSynced = progress.AccountSynced @@ -710,7 +720,7 @@ func (s *Syncer) loadSyncStatus() { step := new(big.Int).Sub( new(big.Int).Div( new(big.Int).Exp(common.Big2, common.Big256, nil), - big.NewInt(accountConcurrency), + big.NewInt(int64(accountConcurrency)), ), common.Big1, ) for i := 0; i < accountConcurrency; i++ { @@ -719,10 +729,13 @@ func (s *Syncer) loadSyncStatus() { // Make sure we don't overflow if the step is not a proper divisor last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } + batch := s.db.NewBatch() s.tasks = append(s.tasks, &accountTask{ Next: next, Last: last, SubTasks: make(map[common.Hash][]*storageTask), + genBatch: batch, + genTrie: trie.NewStackTrie(batch), }) log.Debug("Created account sync task", "from", next, "last", last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) @@ -731,6 +744,25 @@ func (s *Syncer) loadSyncStatus() { // saveSyncStatus marshals the remaining sync tasks into leveldb. func (s *Syncer) saveSyncStatus() { + // Serialize any partial progress to disk before spinning down + for _, task := range s.tasks { + keys, bytes := task.genBatch.KeyCount(), task.genBatch.ValueSize() + if err := task.genBatch.Write(); err != nil { + log.Error("Failed to persist account slots", "err", err) + } + s.accountBytes += common.StorageSize(keys*common.HashLength + bytes) + + for _, subtasks := range task.SubTasks { + for _, subtask := range subtasks { + keys, bytes := subtask.genBatch.KeyCount(), subtask.genBatch.ValueSize() + if err := subtask.genBatch.Write(); err != nil { + log.Error("Failed to persist storage slots", "err", err) + } + s.accountBytes += common.StorageSize(keys*common.HashLength + bytes) + } + } + } + // Store the actual progress markers progress := &syncProgress{ Tasks: s.tasks, AccountSynced: s.accountSynced, @@ -754,16 +786,25 @@ func (s *Syncer) saveSyncStatus() { // cleanAccountTasks removes account range retrieval tasks that have already been // completed. func (s *Syncer) cleanAccountTasks() { + // If the sync was already done before, don't even bother + if len(s.tasks) == 0 { + return + } + // Sync wasn't finished previously, check for any task that can be finalized for i := 0; i < len(s.tasks); i++ { if s.tasks[i].done { s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) i-- } } + // If everything was just finalized just, generate the account trie and start heal if len(s.tasks) == 0 { s.lock.Lock() s.snapped = true s.lock.Unlock() + + // Push the final sync report + s.reportSyncProgress(true) } } @@ -1600,12 +1641,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { continue } if cmp > 0 { - // Chunk overflown, cut off excess, but also update the boundary nodes - for j := i; j < len(res.hashes); j++ { - if err := res.trie.Prove(res.hashes[j][:], 0, res.overflow); err != nil { - panic(err) // Account range was already proven, what happened - } - } + // Chunk overflown, cut off excess res.hashes = res.hashes[:i] res.accounts = res.accounts[:i] res.cont = false // Mark range completed @@ -1681,7 +1717,6 @@ func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { var ( codes uint64 - bytes common.StorageSize ) for i, hash := range res.hashes { code := res.codes[i] @@ -1699,17 +1734,16 @@ func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) { } } // Push the bytecode into a database batch - s.bytecodeSynced++ - s.bytecodeBytes += common.StorageSize(len(code)) - codes++ - bytes += common.StorageSize(len(code)) - rawdb.WriteCode(batch, hash, code) } + bytes := common.StorageSize(batch.ValueSize()) if err := batch.Write(); err != nil { log.Crit("Failed to persist bytecodes", "err", err) } + s.bytecodeSynced += codes + s.bytecodeBytes += bytes + log.Debug("Persisted set of bytecodes", "count", codes, "bytes", bytes) // If this delivery completed the last pending task, forward the account task @@ -1732,10 +1766,9 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { batch := s.db.NewBatch() var ( - slots int - nodes int - skipped int - bytes common.StorageSize + slots int + nodes int + bytes common.StorageSize ) // Iterate over all the accounts and reconstruct their storage tries from the // delivered slots @@ -1772,27 +1805,50 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { // the subtasks for it within the main account task if tasks, ok := res.mainTask.SubTasks[account]; !ok { var ( - next common.Hash - ) - step := new(big.Int).Sub( - new(big.Int).Div( - new(big.Int).Exp(common.Big2, common.Big256, nil), - big.NewInt(storageConcurrency), - ), common.Big1, + keys = res.hashes[i] + chunks = uint64(storageConcurrency) + lastKey common.Hash ) - for k := 0; k < storageConcurrency; k++ { - last := common.BigToHash(new(big.Int).Add(next.Big(), step)) - if k == storageConcurrency-1 { - // Make sure we don't overflow if the step is not a proper divisor - last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + if len(keys) > 0 { + lastKey = keys[len(keys)-1] + } + // If the number of slots remaining is low, decrease the + // number of chunks. Somewhere on the order of 10-15K slots + // fit into a packet of 500KB. A key/slot pair is maximum 64 + // bytes, so pessimistically maxRequestSize/64 = 8K. + // + // Chunk so that at least 2 packets are needed to fill a task. + if estimate, err := estimateRemainingSlots(len(keys), lastKey); err == nil { + if n := estimate / (2 * (maxRequestSize / 64)); n+1 < chunks { + chunks = n + 1 } + log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "remaining", estimate, "chunks", chunks) + } else { + log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "chunks", chunks) + } + r := newHashRange(lastKey, chunks) + + // Our first task is the one that was just filled by this response. + batch := s.db.NewBatch() + tasks = append(tasks, &storageTask{ + Next: common.Hash{}, + Last: r.End(), + root: acc.Root, + genBatch: batch, + genTrie: trie.NewStackTrie(batch), + }) + for r.Next() { + batch := s.db.NewBatch() tasks = append(tasks, &storageTask{ - Next: next, - Last: last, - root: acc.Root, + Next: r.Start(), + Last: r.End(), + root: acc.Root, + genBatch: batch, + genTrie: trie.NewStackTrie(batch), }) - log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", next, "last", last) - next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) + } + for _, task := range tasks { + log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", task.Next, "last", task.Last) } res.mainTask.SubTasks[account] = tasks @@ -1805,74 +1861,90 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if res.subTask != nil { // Ensure the response doesn't overflow into the subsequent task last := res.subTask.Last.Big() - for k, hash := range res.hashes[i] { - // Mark the range complete if the last is already included. - // Keep iteration to delete the extra states if exists. - cmp := hash.Big().Cmp(last) - if cmp == 0 { + // Find the first overflowing key. While at it, mark res as complete + // if we find the range to include or pass the 'last' + index := sort.Search(len(res.hashes[i]), func(k int) bool { + cmp := res.hashes[i][k].Big().Cmp(last) + if cmp >= 0 { res.cont = false - continue - } - if cmp > 0 { - // Chunk overflown, cut off excess, but also update the boundary - for l := k; l < len(res.hashes[i]); l++ { - if err := res.tries[i].Prove(res.hashes[i][l][:], 0, res.overflow); err != nil { - panic(err) // Account range was already proven, what happened - } - } - res.hashes[i] = res.hashes[i][:k] - res.slots[i] = res.slots[i][:k] - res.cont = false // Mark range completed - break } + return cmp > 0 + }) + if index >= 0 { + // cut off excess + res.hashes[i] = res.hashes[i][:index] + res.slots[i] = res.slots[i][:index] } // Forward the relevant storage chunk (even if created just now) if res.cont { - res.subTask.Next = common.BigToHash(new(big.Int).Add(res.hashes[i][len(res.hashes[i])-1].Big(), big.NewInt(1))) + res.subTask.Next = incHash(res.hashes[i][len(res.hashes[i])-1]) } else { res.subTask.done = true } } } // Iterate over all the reconstructed trie nodes and push them to disk + // if the contract is fully delivered. If it's chunked, the trie nodes + // will be reconstructed later. slots += len(res.hashes[i]) - it := res.nodes[i].NewIterator(nil, nil) - for it.Next() { - // Boundary nodes are not written for the last result, since they are incomplete - if i == len(res.hashes)-1 && res.subTask != nil { - if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { - skipped++ - continue - } - if _, err := res.overflow.Get(it.Key()); err == nil { - skipped++ - continue - } - } - // Node is not a boundary, persist to disk - batch.Put(it.Key(), it.Value()) + if i < len(res.hashes)-1 || res.subTask == nil { + it := res.nodes[i].NewIterator(nil, nil) + for it.Next() { + batch.Put(it.Key(), it.Value()) - bytes += common.StorageSize(common.HashLength + len(it.Value())) - nodes++ + bytes += common.StorageSize(common.HashLength + len(it.Value())) + nodes++ + } + it.Release() } - it.Release() - // Persist the received storage segements. These flat state maybe // outdated during the sync, but it can be fixed later during the // snapshot generation. for j := 0; j < len(res.hashes[i]); j++ { rawdb.WriteStorageSnapshot(batch, account, res.hashes[i][j], res.slots[i][j]) bytes += common.StorageSize(1 + 2*common.HashLength + len(res.slots[i][j])) + + // If we're storing large contracts, generate the trie nodes + // on the fly to not trash the gluing points + if i == len(res.hashes)-1 && res.subTask != nil { + res.subTask.genTrie.Update(res.hashes[i][j][:], res.slots[i][j]) + } } } + // Large contracts could have generated new trie nodes, flush them to disk + if res.subTask != nil { + if res.subTask.done { + if root, err := res.subTask.genTrie.Commit(); err != nil { + log.Error("Failed to commit stack slots", "err", err) + } else if root == res.subTask.root { + // If the chunk's root is an overflown but full delivery, clear the heal request + for i, account := range res.mainTask.res.hashes { + if account == res.accounts[len(res.accounts)-1] { + res.mainTask.needHeal[i] = false + } + } + } + } + if data := res.subTask.genBatch.ValueSize(); data > ethdb.IdealBatchSize || res.subTask.done { + keys := res.subTask.genBatch.KeyCount() + if err := res.subTask.genBatch.Write(); err != nil { + log.Error("Failed to persist stack slots", "err", err) + } + res.subTask.genBatch.Reset() + + bytes += common.StorageSize(keys*common.HashLength + data) + nodes += keys + } + } + // Flush anything written just now and update the stats if err := batch.Write(); err != nil { log.Crit("Failed to persist storage slots", "err", err) } s.storageSynced += uint64(slots) s.storageBytes += bytes - log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "nodes", nodes, "skipped", skipped, "bytes", bytes) + log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "nodes", nodes, "bytes", bytes) // If this delivery completed the last pending task, forward the account task // to the next chunk @@ -1967,87 +2039,69 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { } task.res = nil - // Iterate over all the accounts and gather all the incomplete trie nodes. A - // node is incomplete if we haven't yet filled it (sync was interrupted), or - // if we filled it in multiple chunks (storage trie), in which case the few - // nodes on the chunk boundaries are missing. - incompletes := light.NewNodeSet() - for i := range res.accounts { - // If the filling was interrupted, mark everything after as incomplete + // Persist the received account segements. These flat state maybe + // outdated during the sync, but it can be fixed later during the + // snapshot generation. + var ( + nodes int + bytes common.StorageSize + ) + batch := s.db.NewBatch() + for i, hash := range res.hashes { if task.needCode[i] || task.needState[i] { - for j := i; j < len(res.accounts); j++ { - if err := res.trie.Prove(res.hashes[j][:], 0, incompletes); err != nil { - panic(err) // Account range was already proven, what happened - } - } break } - // Filling not interrupted until this point, mark incomplete if needs healing - if task.needHeal[i] { - if err := res.trie.Prove(res.hashes[i][:], 0, incompletes); err != nil { - panic(err) // Account range was already proven, what happened - } - } - } - // Persist every finalized trie node that's not on the boundary - batch := s.db.NewBatch() + slim := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash) + rawdb.WriteAccountSnapshot(batch, hash, slim) + bytes += common.StorageSize(1 + common.HashLength + len(slim)) - var ( - nodes int - skipped int - bytes common.StorageSize - ) - it := res.nodes.NewIterator(nil, nil) - for it.Next() { - // Boundary nodes are not written, since they are incomplete - if _, ok := res.bounds[common.BytesToHash(it.Key())]; ok { - skipped++ - continue - } - // Overflow nodes are not written, since they mess with another task - if _, err := res.overflow.Get(it.Key()); err == nil { - skipped++ - continue - } - // Accounts with split storage requests are incomplete - if _, err := incompletes.Get(it.Key()); err == nil { - skipped++ - continue + // If the task is complete, drop it into the stack trie to generate + // account trie nodes for it + if !task.needHeal[i] { + full, err := snapshot.FullAccountRLP(slim) // TODO(karalabe): Slim parsing can be omitted + if err != nil { + panic(err) // Really shouldn't ever happen + } + task.genTrie.Update(hash[:], full) } - // Node is neither a boundary, not an incomplete account, persist to disk - batch.Put(it.Key(), it.Value()) - - bytes += common.StorageSize(common.HashLength + len(it.Value())) - nodes++ - } - it.Release() - - // Persist the received account segements. These flat state maybe - // outdated during the sync, but it can be fixed later during the - // snapshot generation. - for i, hash := range res.hashes { - blob := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash) - rawdb.WriteAccountSnapshot(batch, hash, blob) - bytes += common.StorageSize(1 + common.HashLength + len(blob)) } + // Flush anything written just now and update the stats if err := batch.Write(); err != nil { log.Crit("Failed to persist accounts", "err", err) } s.accountBytes += bytes s.accountSynced += uint64(len(res.accounts)) - log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "nodes", nodes, "skipped", skipped, "bytes", bytes) - // Task filling persisted, push it the chunk marker forward to the first // account still missing data. for i, hash := range res.hashes { if task.needCode[i] || task.needState[i] { return } - task.Next = common.BigToHash(new(big.Int).Add(hash.Big(), big.NewInt(1))) + task.Next = incHash(hash) } // All accounts marked as complete, track if the entire task is done task.done = !res.cont + + // Stack trie could have generated trie nodes, push them to disk (we need to + // flush after finalizing task.done. It's fine even if we crash and lose this + // write as it will only cause more data to be downloaded during heal. + if task.done { + if _, err := task.genTrie.Commit(); err != nil { + log.Error("Failed to commit stack account", "err", err) + } + } + if data := task.genBatch.ValueSize(); data > ethdb.IdealBatchSize || task.done { + keys := task.genBatch.KeyCount() + if err := task.genBatch.Write(); err != nil { + log.Error("Failed to persist stack account", "err", err) + } + task.genBatch.Reset() + + nodes += keys + bytes += common.StorageSize(keys*common.HashLength + data) + } + log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "nodes", nodes, "bytes", bytes) } // OnAccounts is a callback method to invoke when a range of accounts are @@ -2091,7 +2145,6 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco s.lock.Unlock() return nil } - // Response is valid, but check if peer is signalling that it does not have // the requested data. For account range queries that means the state being // retrieved was either already pruned remotely, or the peer is not yet @@ -2123,22 +2176,13 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco if len(keys) > 0 { end = keys[len(keys)-1] } - db, tr, notary, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) + _, _, _, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) if err != nil { logger.Warn("Account range failed proof", "err", err) // Signal this request as failed, and ready for rescheduling s.scheduleRevertAccountRequest(req) return err } - // Partial trie reconstructed, send it to the scheduler for storage filling - bounds := make(map[common.Hash]struct{}) - - it := notary.Accessed().NewIterator(nil, nil) - for it.Next() { - bounds[common.BytesToHash(it.Key())] = struct{}{} - } - it.Release() - accs := make([]*state.Account, len(accounts)) for i, account := range accounts { acc := new(state.Account) @@ -2151,10 +2195,6 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco task: req.task, hashes: hashes, accounts: accs, - nodes: db, - trie: tr, - bounds: bounds, - overflow: light.NewNodeSet(), cont: cont, } select { @@ -2354,10 +2394,8 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo // Reconstruct the partial tries from the response and verify them var ( - dbs = make([]ethdb.KeyValueStore, len(hashes)) - tries = make([]*trie.Trie, len(hashes)) - notary *trie.KeyValueNotary - cont bool + dbs = make([]ethdb.KeyValueStore, len(hashes)) + cont bool ) for i := 0; i < len(hashes); i++ { // Convert the keys and proofs into an internal format @@ -2375,7 +2413,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(nodes) == 0 { // No proof has been attached, the response must cover the entire key // space and hash to the origin root. - dbs[i], tries[i], _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) + dbs[i], _, _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) @@ -2390,7 +2428,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(keys) > 0 { end = keys[len(keys)-1] } - dbs[i], tries[i], notary, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) + dbs[i], _, _, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage range failed proof", "err", err) @@ -2399,15 +2437,6 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo } } // Partial tries reconstructed, send them to the scheduler for storage filling - bounds := make(map[common.Hash]struct{}) - - if notary != nil { // if all contract storages are delivered in full, no notary will be created - it := notary.Accessed().NewIterator(nil, nil) - for it.Next() { - bounds[common.BytesToHash(it.Key())] = struct{}{} - } - it.Release() - } response := &storageResponse{ mainTask: req.mainTask, subTask: req.subTask, @@ -2416,9 +2445,6 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo hashes: hashes, slots: slots, nodes: dbs, - tries: tries, - bounds: bounds, - overflow: light.NewNodeSet(), cont: cont, } select { @@ -2658,7 +2684,7 @@ func (s *Syncer) report(force bool) { // reportSyncProgress calculates various status reports and provides it to the user. func (s *Syncer) reportSyncProgress(force bool) { // Don't report all the events, just occasionally - if !force && time.Since(s.logTime) < 3*time.Second { + if !force && time.Since(s.logTime) < 8*time.Second { return } // Don't report anything until we have a meaningful progress @@ -2697,7 +2723,7 @@ func (s *Syncer) reportSyncProgress(force bool) { // reportHealProgress calculates various status reports and provides it to the user. func (s *Syncer) reportHealProgress(force bool) { // Don't report all the events, just occasionally - if !force && time.Since(s.logTime) < 3*time.Second { + if !force && time.Since(s.logTime) < 8*time.Second { return } s.logTime = time.Now() @@ -2712,3 +2738,19 @@ func (s *Syncer) reportHealProgress(force bool) { log.Info("State heal in progress", "accounts", accounts, "slots", storage, "codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending()) } + +// estimateRemainingSlots tries to determine roughly how many slots are left in +// a contract storage, based on the number of keys and the last hash. This method +// assumes that the hashes are lexicographically ordered and evenly distributed. +func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) { + if last == (common.Hash{}) { + return 0, errors.New("last hash empty") + } + space := new(big.Int).Mul(math.MaxBig256, big.NewInt(int64(hashes))) + space.Div(space, last.Big()) + if !space.IsUint64() { + // Gigantic address space probably due to too few or malicious slots + return 0, errors.New("too few slots for estimation") + } + return space.Uint64() - uint64(hashes), nil +} diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 3e9778dbc7..a1cc3581a8 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -135,6 +135,12 @@ type testPeer struct { trieRequestHandler trieHandlerFunc codeRequestHandler codeHandlerFunc term func() + + // counters + nAccountRequests int + nStorageRequests int + nBytecodeRequests int + nTrienodeRequests int } func newTestPeer(id string, t *testing.T, term func()) *testPeer { @@ -156,19 +162,30 @@ func newTestPeer(id string, t *testing.T, term func()) *testPeer { func (t *testPeer) ID() string { return t.id } func (t *testPeer) Log() log.Logger { return t.logger } +func (t *testPeer) Stats() string { + return fmt.Sprintf(`Account requests: %d +Storage requests: %d +Bytecode requests: %d +Trienode requests: %d +`, t.nAccountRequests, t.nStorageRequests, t.nBytecodeRequests, t.nTrienodeRequests) +} + func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error { t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes)) + t.nAccountRequests++ go t.accountRequestHandler(t, id, root, origin, limit, bytes) return nil } func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error { t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes)) + t.nTrienodeRequests++ go t.trieRequestHandler(t, id, root, paths, bytes) return nil } func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error { + t.nStorageRequests++ if len(accounts) == 1 && origin != nil { t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes)) } else { @@ -179,6 +196,7 @@ func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts [] } func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error { + t.nBytecodeRequests++ t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes)) go t.codeRequestHandler(t, id, hashes, bytes) return nil @@ -1365,7 +1383,7 @@ func makeBoundaryAccountTrie(n int) (*trie.Trie, entrySlice) { step := new(big.Int).Sub( new(big.Int).Div( new(big.Int).Exp(common.Big2, common.Big256, nil), - big.NewInt(accountConcurrency), + big.NewInt(int64(accountConcurrency)), ), common.Big1, ) for i := 0; i < accountConcurrency; i++ { @@ -1529,7 +1547,7 @@ func makeBoundaryStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice) step := new(big.Int).Sub( new(big.Int).Div( new(big.Int).Exp(common.Big2, common.Big256, nil), - big.NewInt(accountConcurrency), + big.NewInt(int64(accountConcurrency)), ), common.Big1, ) for i := 0; i < accountConcurrency; i++ { @@ -1605,3 +1623,94 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { } t.Logf("accounts: %d, slots: %d", accounts, slots) } + +// TestSyncAccountPerformance tests how efficient the snap algo is at minimizing +// state healing +func TestSyncAccountPerformance(t *testing.T) { + // Set the account concurrency to 1. This _should_ result in the + // range root to become correct, and there should be no healing needed + defer func(old int) { accountConcurrency = old }(accountConcurrency) + accountConcurrency = 1 + + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = sourceAccountTrie + source.accountValues = elems + return source + } + src := mkSource("source") + syncer := setupSyncer(src) + if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + // The trie root will always be requested, since it is added when the snap + // sync cycle starts. When popping the queue, we do not look it up again. + // Doing so would bring this number down to zero in this artificial testcase, + // but only add extra IO for no reason in practice. + if have, want := src.nTrienodeRequests, 1; have != want { + fmt.Printf(src.Stats()) + t.Errorf("trie node heal requests wrong, want %d, have %d", want, have) + } +} + +func TestSlotEstimation(t *testing.T) { + for i, tc := range []struct { + last common.Hash + count int + want uint64 + }{ + { + // Half the space + common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + 100, + 100, + }, + { + // 1 / 16th + common.HexToHash("0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + 100, + 1500, + }, + { + // Bit more than 1 / 16th + common.HexToHash("0x1000000000000000000000000000000000000000000000000000000000000000"), + 100, + 1499, + }, + { + // Almost everything + common.HexToHash("0xF000000000000000000000000000000000000000000000000000000000000000"), + 100, + 6, + }, + { + // Almost nothing -- should lead to error + common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), + 1, + 0, + }, + { + // Nothing -- should lead to error + common.Hash{}, + 100, + 0, + }, + } { + have, _ := estimateRemainingSlots(tc.count, tc.last) + if want := tc.want; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + } +} diff --git a/ethdb/batch.go b/ethdb/batch.go index e261415bff..5f8207fc46 100644 --- a/ethdb/batch.go +++ b/ethdb/batch.go @@ -25,6 +25,9 @@ const IdealBatchSize = 100 * 1024 type Batch interface { KeyValueWriter + // KeyCount retrieves the number of keys queued up for writing. + KeyCount() int + // ValueSize retrieves the amount of data queued up for writing. ValueSize() int diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 5d19cc3577..da00226e95 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -448,6 +448,7 @@ func (db *Database) meter(refresh time.Duration) { type batch struct { db *leveldb.DB b *leveldb.Batch + keys int size int } @@ -461,10 +462,16 @@ func (b *batch) Put(key, value []byte) error { // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { b.b.Delete(key) + b.keys++ b.size += len(key) return nil } +// KeyCount retrieves the number of keys queued up for writing. +func (b *batch) KeyCount() int { + return b.keys +} + // ValueSize retrieves the amount of data queued up for writing. func (b *batch) ValueSize() int { return b.size @@ -478,7 +485,7 @@ func (b *batch) Write() error { // Reset resets the batch for reuse. func (b *batch) Reset() { b.b.Reset() - b.size = 0 + b.keys, b.size = 0, 0 } // Replay replays the batch contents. diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index fedc9e326c..ded9f5e66c 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -198,6 +198,7 @@ type keyvalue struct { type batch struct { db *Database writes []keyvalue + keys int size int } @@ -211,10 +212,16 @@ func (b *batch) Put(key, value []byte) error { // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true}) + b.keys++ b.size += len(key) return nil } +// KeyCount retrieves the number of keys queued up for writing. +func (b *batch) KeyCount() int { + return b.keys +} + // ValueSize retrieves the amount of data queued up for writing. func (b *batch) ValueSize() int { return b.size @@ -238,7 +245,7 @@ func (b *batch) Write() error { // Reset resets the batch for reuse. func (b *batch) Reset() { b.writes = b.writes[:0] - b.size = 0 + b.keys, b.size = 0, 0 } // Replay replays the batch contents. diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go index 5cea7769c2..0013c919c9 100644 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -90,6 +90,7 @@ func (b *spongeBatch) Put(key, value []byte) error { return nil } func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } +func (b *spongeBatch) KeyCount() int { panic("not implemented") } func (b *spongeBatch) ValueSize() int { return 100 } func (b *spongeBatch) Write() error { return nil } func (b *spongeBatch) Reset() {} diff --git a/trie/trie_test.go b/trie/trie_test.go index 492b423c2f..44fddf87e4 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -706,6 +706,7 @@ func (b *spongeBatch) Put(key, value []byte) error { return nil } func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } +func (b *spongeBatch) KeyCount() int { return 100 } func (b *spongeBatch) ValueSize() int { return 100 } func (b *spongeBatch) Write() error { return nil } func (b *spongeBatch) Reset() {} From 45fca44c24a1067285f341ccd9b4abdf1334604f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 27 Apr 2021 18:09:34 +0200 Subject: [PATCH 492/948] p2p/tracker: properly clean up fulfilled requests --- p2p/tracker/tracker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go index b50a952f62..19852ad6ab 100644 --- a/p2p/tracker/tracker.go +++ b/p2p/tracker/tracker.go @@ -186,6 +186,7 @@ func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) { } // Everything matches, mark the request serviced and meter it t.expire.Remove(req.expire) + delete(t.pending, id) if req.expire.Prev() == nil { t.wake.Stop() t.schedule() From ff3535e8e03ea56207109f57ec3b4a30481d2fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 27 Apr 2021 21:47:59 +0300 Subject: [PATCH 493/948] p2p/tracker: only reschedule wake if previous didn't run --- p2p/tracker/tracker.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go index 19852ad6ab..69a49087e2 100644 --- a/p2p/tracker/tracker.go +++ b/p2p/tracker/tracker.go @@ -188,8 +188,9 @@ func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) { t.expire.Remove(req.expire) delete(t.pending, id) if req.expire.Prev() == nil { - t.wake.Stop() - t.schedule() + if t.wake.Stop() { + t.schedule() + } } g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode) metrics.GetOrRegisterGauge(g, nil).Dec(1) From 0c998684167278fb09d2f5412a64fc225dc08392 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 28 Apr 2021 08:48:07 +0200 Subject: [PATCH 494/948] cmd/devp2p, eth/protocols/eth: fix tests + make sanity checks earlier (#22749) --- cmd/devp2p/internal/ethtest/eth66_suite.go | 4 ++-- cmd/devp2p/internal/ethtest/large.go | 2 +- cmd/devp2p/internal/ethtest/suite_test.go | 12 ++++++++++-- eth/protocols/eth/handlers.go | 6 +++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 176d8bf33c..4265b25f6a 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -483,8 +483,8 @@ func (s *Suite) TestNewPooledTxs_66(t *utesting.T) { t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg)) } return - case *NewPooledTransactionHashes: - // ignore propagated txs from old tests + case *NewPooledTransactionHashes, *NewBlock, *NewBlockHashes: + // ignore propagated txs and blocks from old tests continue default: t.Fatalf("unexpected %s", pretty.Sdump(msg)) diff --git a/cmd/devp2p/internal/ethtest/large.go b/cmd/devp2p/internal/ethtest/large.go index deca00be53..22421355ab 100644 --- a/cmd/devp2p/internal/ethtest/large.go +++ b/cmd/devp2p/internal/ethtest/large.go @@ -70,7 +70,7 @@ func largeHeader() *types.Header { GasUsed: 0, Coinbase: common.Address{}, GasLimit: 0, - UncleHash: randHash(), + UncleHash: types.EmptyUncleHash, Time: 1337, ParentHash: randHash(), Root: randHash(), diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go index 2c628757bc..6e3217151a 100644 --- a/cmd/devp2p/internal/ethtest/suite_test.go +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -19,6 +19,7 @@ package ethtest import ( "os" "testing" + "time" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -87,8 +88,15 @@ func setupGeth(stack *node.Node) error { } backend, err := eth.New(stack, ðconfig.Config{ - Genesis: &chain.genesis, - NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 + Genesis: &chain.genesis, + NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 + DatabaseCache: 10, + TrieCleanCache: 10, + TrieCleanCacheJournal: "", + TrieCleanCacheRejournal: 60 * time.Minute, + TrieDirtyCache: 16, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 10, }) if err != nil { return err diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index d0dec7b0b2..d7d993a23d 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -292,6 +292,9 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(ann); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + if err := ann.sanityCheck(); err != nil { + return err + } if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() { log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash()) return nil // TODO(karalabe): return error eventually, but wait a few releases @@ -300,9 +303,6 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash()) return nil // TODO(karalabe): return error eventually, but wait a few releases } - if err := ann.sanityCheck(); err != nil { - return err - } ann.Block.ReceivedAt = msg.Time() ann.Block.ReceivedFrom = peer From 256c5d68b245899f6d37c72636fdd795f66397ee Mon Sep 17 00:00:00 2001 From: Gregory Markou <16929357+GregTheGreek@users.noreply.github.com> Date: Wed, 28 Apr 2021 03:06:34 -0400 Subject: [PATCH 495/948] eth/gasprice: improve stability of estimated price (#22722) This PR makes the gas price oracle ignore transactions priced at `<=1 wei`. --- eth/gasprice/gasprice.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 5d8be08e0b..560722bec0 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -199,6 +199,9 @@ func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, bloc var prices []*big.Int for _, tx := range txs { + if tx.GasPriceIntCmp(common.Big1) <= 0 { + continue + } sender, err := types.Sender(signer, tx) if err == nil && sender != block.Coinbase() { prices = append(prices, tx.GasPrice()) From 9e5bb84c0e162c54f4532641084b9c0627b15e85 Mon Sep 17 00:00:00 2001 From: Gautam Botrel Date: Wed, 28 Apr 2021 05:04:25 -0500 Subject: [PATCH 496/948] tests/fuzzers: crypto/bn256 and crypto/bls12381 tests against gnark-crypto (#22755) Add more cross-fuzzers to fuzz bls with gnark versus geth's own bls12-381 library --- go.mod | 15 +- go.sum | 153 ++--------- oss-fuzz.sh | 5 + tests/fuzzers/bls12381/bls12381_fuzz.go | 244 ++++++++++++++++++ .../{bls_fuzzer.go => precompile_fuzzer.go} | 0 tests/fuzzers/bn256/bn256_fuzz.go | 48 ++-- 6 files changed, 306 insertions(+), 159 deletions(-) create mode 100644 tests/fuzzers/bls12381/bls12381_fuzz.go rename tests/fuzzers/bls12381/{bls_fuzzer.go => precompile_fuzzer.go} (100%) diff --git a/go.mod b/go.mod index 74c406f78b..63636caae1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,10 @@ module github.com/ethereum/go-ethereum go 1.15 require ( + github.com/Azure/azure-pipeline-go v0.2.2 // indirect github.com/Azure/azure-storage-blob-go v0.7.0 + github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect + github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.5.7 github.com/aws/aws-sdk-go-v2 v1.2.0 github.com/aws/aws-sdk-go-v2/config v1.1.1 @@ -12,15 +15,18 @@ require ( github.com/btcsuite/btcd v0.20.1-beta github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.14.0 - github.com/consensys/gurvy v0.3.8 + github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea + github.com/dlclark/regexp2 v1.2.0 // indirect github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 github.com/edsrzf/mmap-go v1.0.0 github.com/fatih/color v1.7.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff + github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-stack/stack v1.8.0 github.com/golang/protobuf v1.4.3 github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 @@ -37,8 +43,10 @@ require ( github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e github.com/julienschmidt/httprouter v1.2.0 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.0 github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 + github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.5 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 @@ -51,11 +59,12 @@ require ( github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 golang.org/x/text v0.3.4 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 gopkg.in/urfave/cli.v1 v1.20.0 + gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index 0a9444323c..cadcd1de95 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,6 @@ cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbf cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= @@ -56,11 +55,6 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v1.2.0 h1:BS+UYpbsElC82gB+2E2jiCBg36i8HlubTB/dO/moQ9c= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2/config v1.1.1 h1:ZAoq32boMzcaTW9bcUacBswAmHTbvlvDJICgHFZuECo= @@ -81,11 +75,8 @@ github.com/aws/smithy-go v1.1.0 h1:D6CSsM3gdxaGaqXnPgOBCeL6Mophqzu7KJOu7zW78sU= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -107,20 +98,12 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu2VvzMZlOFA= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/consensys/bavard v0.1.8-0.20210105233146-c16790d2aa8b/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= -github.com/consensys/goff v0.3.10/go.mod h1:xTldOBEHmFiYS0gPXd3NsaEqZWlnmeWcRLWgD3ba3xc= -github.com/consensys/gurvy v0.3.8 h1:H2hvjvT2OFMgdMn5ZbhXqHt+F8DJ2clZW7Vmc0kFFxc= -github.com/consensys/gurvy v0.3.8/go.mod h1:sN75xnsiD593XnhbhvG2PkOy194pZBzqShWF/kwuW/g= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f h1:C43yEtQ6NIf4ftFXD/V55gnGFgPbMQobd//YlnLjUJ8= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -138,18 +121,13 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmak github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= -github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.9.25/go.mod h1:vMkFiYLHI4tgPw4k2j4MHKoovchFE8plZ0M9VMk4/oM= -github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -158,7 +136,6 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -177,12 +154,10 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -225,51 +200,25 @@ github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M= github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 h1:bcAj8KroPf552TScjFPIakjH2/tdIrIH8F+cc4v4SRo= github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8= github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= @@ -283,25 +232,20 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kilic/bls12-381 v0.0.0-20201226121925-69dacb279461/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= @@ -319,37 +263,24 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4= github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= @@ -361,8 +292,6 @@ github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -378,10 +307,7 @@ github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= @@ -394,62 +320,40 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= -github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v3.21.3+incompatible h1:uenXGGa8ESCQq+dbgtl916dmg6PSAz2cXov0uORQ9v8= -github.com/shirou/gopsutil v3.21.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= -github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -458,8 +362,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -467,41 +369,34 @@ github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= @@ -519,19 +414,16 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -545,9 +437,10 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d h1:1aflnvSoWWLI2k/dMUAl5lvU1YO4Mb4hz0gh+1rjcxU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -559,13 +452,12 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -585,14 +477,13 @@ golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 h1:EjgCl+fVlIaPJSori0ikSz3uV0DOHKWOJFpv1sAAhBM= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -604,7 +495,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -624,7 +514,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -632,7 +521,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -688,17 +577,14 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -715,5 +601,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/oss-fuzz.sh b/oss-fuzz.sh index f8152f0fad..a9bac03257 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -114,5 +114,10 @@ compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1 compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 +compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1Add fuzz_cross_g1_add +compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1MultiExp fuzz_cross_g1_multiexp +compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG2Add fuzz_cross_g2_add +compile_fuzzer tests/fuzzers/bls12381 FuzzCrossPairing fuzz_cross_pairing + #TODO: move this to tests/fuzzers, if possible compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go new file mode 100644 index 0000000000..298050ad36 --- /dev/null +++ b/tests/fuzzers/bls12381/bls12381_fuzz.go @@ -0,0 +1,244 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build gofuzz + +package bls + +import ( + "bytes" + "crypto/rand" + "fmt" + "io" + "math/big" + + gnark "github.com/consensys/gnark-crypto/ecc/bls12-381" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/ethereum/go-ethereum/crypto/bls12381" +) + +func FuzzCrossPairing(data []byte) int { + input := bytes.NewReader(data) + + // get random G1 points + kpG1, cpG1, err := getG1Points(input) + if err != nil { + return 0 + } + + // get random G2 points + kpG2, cpG2, err := getG2Points(input) + if err != nil { + return 0 + } + + // compute pairing using geth + engine := bls12381.NewPairingEngine() + engine.AddPair(kpG1, kpG2) + kResult := engine.Result() + + // compute pairing using gnark + cResult, err := gnark.Pair([]gnark.G1Affine{*cpG1}, []gnark.G2Affine{*cpG2}) + if err != nil { + panic(fmt.Sprintf("gnark/bls12381 encountered error: %v", err)) + } + + // compare result + if !(bytes.Equal(cResult.Marshal(), bls12381.NewGT().ToBytes(kResult))) { + panic("pairing mismatch gnark / geth ") + } + + return 1 +} + +func FuzzCrossG1Add(data []byte) int { + input := bytes.NewReader(data) + + // get random G1 points + kp1, cp1, err := getG1Points(input) + if err != nil { + return 0 + } + + // get random G1 points + kp2, cp2, err := getG1Points(input) + if err != nil { + return 0 + } + + // compute kp = kp1 + kp2 + g1 := bls12381.NewG1() + kp := bls12381.PointG1{} + g1.Add(&kp, kp1, kp2) + + // compute cp = cp1 + cp2 + _cp1 := new(gnark.G1Jac).FromAffine(cp1) + _cp2 := new(gnark.G1Jac).FromAffine(cp2) + cp := new(gnark.G1Affine).FromJacobian(_cp1.AddAssign(_cp2)) + + // compare result + if !(bytes.Equal(cp.Marshal(), g1.ToBytes(&kp))) { + panic("G1 point addition mismatch gnark / geth ") + } + + return 1 +} + +func FuzzCrossG2Add(data []byte) int { + input := bytes.NewReader(data) + + // get random G2 points + kp1, cp1, err := getG2Points(input) + if err != nil { + return 0 + } + + // get random G2 points + kp2, cp2, err := getG2Points(input) + if err != nil { + return 0 + } + + // compute kp = kp1 + kp2 + g2 := bls12381.NewG2() + kp := bls12381.PointG2{} + g2.Add(&kp, kp1, kp2) + + // compute cp = cp1 + cp2 + _cp1 := new(gnark.G2Jac).FromAffine(cp1) + _cp2 := new(gnark.G2Jac).FromAffine(cp2) + cp := new(gnark.G2Affine).FromJacobian(_cp1.AddAssign(_cp2)) + + // compare result + if !(bytes.Equal(cp.Marshal(), g2.ToBytes(&kp))) { + panic("G2 point addition mismatch gnark / geth ") + } + + return 1 +} + +func FuzzCrossG1MultiExp(data []byte) int { + var ( + input = bytes.NewReader(data) + gethScalars []*big.Int + gnarkScalars []fr.Element + gethPoints []*bls12381.PointG1 + gnarkPoints []gnark.G1Affine + ) + // n random scalars (max 17) + for i := 0; i < 17; i++ { + // note that geth/crypto/bls12381 works only with scalars <= 32bytes + s, err := randomScalar(input, fr.Modulus()) + if err != nil { + break + } + // get a random G1 point as basis + kp1, cp1, err := getG1Points(input) + if err != nil { + break + } + gethScalars = append(gethScalars, s) + var gnarkScalar = &fr.Element{} + gnarkScalar = gnarkScalar.SetBigInt(s).FromMont() + gnarkScalars = append(gnarkScalars, *gnarkScalar) + + gethPoints = append(gethPoints, new(bls12381.PointG1).Set(kp1)) + gnarkPoints = append(gnarkPoints, *cp1) + } + if len(gethScalars) == 0{ + return 0 + } + // compute multi exponentiation + g1 := bls12381.NewG1() + kp := bls12381.PointG1{} + if _, err := g1.MultiExp(&kp, gethPoints, gethScalars); err != nil { + panic(fmt.Sprintf("G1 multi exponentiation errored (geth): %v", err)) + } + // note that geth/crypto/bls12381.MultiExp mutates the scalars slice (and sets all the scalars to zero) + + // gnark multi exp + cp := new(gnark.G1Affine) + cp.MultiExp(gnarkPoints, gnarkScalars) + + // compare result + if !(bytes.Equal(cp.Marshal(), g1.ToBytes(&kp))) { + panic("G1 multi exponentiation mismatch gnark / geth ") + } + + return 1 +} + +func getG1Points(input io.Reader) (*bls12381.PointG1, *gnark.G1Affine, error) { + // sample a random scalar + s, err := randomScalar(input, fp.Modulus()) + if err != nil { + return nil, nil, err + } + + // compute a random point + cp := new(gnark.G1Affine) + _, _, g1Gen, _ := gnark.Generators() + cp.ScalarMultiplication(&g1Gen, s) + cpBytes := cp.Marshal() + + // marshal gnark point -> geth point + g1 := bls12381.NewG1() + kp, err := g1.FromBytes(cpBytes) + if err != nil { + panic(fmt.Sprintf("Could not marshal gnark.G1 -> geth.G1: %v", err)) + } + if !bytes.Equal(g1.ToBytes(kp), cpBytes) { + panic("bytes(gnark.G1) != bytes(geth.G1)") + } + + return kp, cp, nil +} + +func getG2Points(input io.Reader) (*bls12381.PointG2, *gnark.G2Affine, error) { + // sample a random scalar + s, err := randomScalar(input, fp.Modulus()) + if err != nil { + return nil, nil, err + } + + // compute a random point + cp := new(gnark.G2Affine) + _, _, _, g2Gen := gnark.Generators() + cp.ScalarMultiplication(&g2Gen, s) + cpBytes := cp.Marshal() + + // marshal gnark point -> geth point + g2 := bls12381.NewG2() + kp, err := g2.FromBytes(cpBytes) + if err != nil { + panic(fmt.Sprintf("Could not marshal gnark.G2 -> geth.G2: %v", err)) + } + if !bytes.Equal(g2.ToBytes(kp), cpBytes) { + panic("bytes(gnark.G2) != bytes(geth.G2)") + } + + return kp, cp, nil +} + +func randomScalar(r io.Reader, max *big.Int) (k *big.Int, err error) { + for { + k, err = rand.Int(r, max) + if err != nil || k.Sign() > 0 { + return + } + } +} diff --git a/tests/fuzzers/bls12381/bls_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go similarity index 100% rename from tests/fuzzers/bls12381/bls_fuzzer.go rename to tests/fuzzers/bls12381/precompile_fuzzer.go diff --git a/tests/fuzzers/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go index c98fbc33ae..030ac19b3f 100644 --- a/tests/fuzzers/bn256/bn256_fuzz.go +++ b/tests/fuzzers/bn256/bn256_fuzz.go @@ -12,12 +12,12 @@ import ( "io" "math/big" - gurvy "github.com/consensys/gurvy/bn256" + "github.com/consensys/gnark-crypto/ecc/bn254" cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" google "github.com/ethereum/go-ethereum/crypto/bn256/google" ) -func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *gurvy.G1Affine) { +func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *bn254.G1Affine) { _, xc, err := cloudflare.RandomG1(input) if err != nil { // insufficient input @@ -25,16 +25,16 @@ func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *gurvy.G1Affine) } xg := new(google.G1) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) } - xs := new(gurvy.G1Affine) + xs := new(bn254.G1Affine) if err := xs.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> consensys:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> gnark: %v", err)) } return xc, xg, xs } -func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *gurvy.G2Affine) { +func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *bn254.G2Affine) { _, xc, err := cloudflare.RandomG2(input) if err != nil { // insufficient input @@ -42,11 +42,11 @@ func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *gurvy.G2Affine) } xg := new(google.G2) if _, err := xg.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err)) } - xs := new(gurvy.G2Affine) + xs := new(bn254.G2Affine) if err := xs.Unmarshal(xc.Marshal()); err != nil { - panic(fmt.Sprintf("Could not marshal cloudflare -> consensys:", err)) + panic(fmt.Sprintf("Could not marshal cloudflare -> gnark: %v", err)) } return xc, xg, xs } @@ -70,16 +70,16 @@ func FuzzAdd(data []byte) int { rg := new(google.G1) rg.Add(xg, yg) - tmpX := new(gurvy.G1Jac).FromAffine(xs) - tmpY := new(gurvy.G1Jac).FromAffine(ys) - rs := new(gurvy.G1Affine).FromJacobian(tmpX.AddAssign(tmpY)) + tmpX := new(bn254.G1Jac).FromAffine(xs) + tmpY := new(bn254.G1Jac).FromAffine(ys) + rs := new(bn254.G1Affine).FromJacobian(tmpX.AddAssign(tmpY)) if !bytes.Equal(rc.Marshal(), rg.Marshal()) { panic("add mismatch: cloudflare/google") } if !bytes.Equal(rc.Marshal(), rs.Marshal()) { - panic("add mismatch: cloudflare/consensys") + panic("add mismatch: cloudflare/gnark") } return 1 } @@ -112,16 +112,16 @@ func FuzzMul(data []byte) int { rg := new(google.G1) rg.ScalarMult(pg, new(big.Int).SetBytes(buf)) - rs := new(gurvy.G1Jac) - psJac := new(gurvy.G1Jac).FromAffine(ps) + rs := new(bn254.G1Jac) + psJac := new(bn254.G1Jac).FromAffine(ps) rs.ScalarMultiplication(psJac, new(big.Int).SetBytes(buf)) - rsAffine := new(gurvy.G1Affine).FromJacobian(rs) + rsAffine := new(bn254.G1Affine).FromJacobian(rs) if !bytes.Equal(rc.Marshal(), rg.Marshal()) { panic("scalar mul mismatch: cloudflare/google") } if !bytes.Equal(rc.Marshal(), rsAffine.Marshal()) { - panic("scalar mul mismatch: cloudflare/consensys") + panic("scalar mul mismatch: cloudflare/gnark") } return 1 } @@ -136,18 +136,20 @@ func FuzzPair(data []byte) int { if tc == nil { return 0 } + // Pair the two points and ensure they result in the same output - clPair := cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) - if clPair != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { + clPair := cloudflare.Pair(pc, tc).Marshal() + gPair := google.Pair(pg, tg).Marshal() + if !bytes.Equal(clPair, gPair) { panic("pairing mismatch: cloudflare/google") } - coPair, err := gurvy.PairingCheck([]gurvy.G1Affine{*ps}, []gurvy.G2Affine{*ts}) + cPair, err := bn254.Pair([]bn254.G1Affine{*ps}, []bn254.G2Affine{*ts}) if err != nil { - panic(fmt.Sprintf("gurvy encountered error: %v", err)) + panic(fmt.Sprintf("gnark/bn254 encountered error: %v", err)) } - if clPair != coPair { - panic("pairing mismatch: cloudflare/consensys") + if !bytes.Equal(clPair, cPair.Marshal()) { + panic("pairing mismatch: cloudflare/gnark") } return 1 From 6d7c9566df5d0dcb0797ef505db91287fa9ac7ce Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 28 Apr 2021 20:18:25 +0800 Subject: [PATCH 497/948] les, tests: fix les clientpool (#22756) * les, tests: fix les clientpool * tests: disable debug mode * les: polish code --- les/vflux/server/clientpool.go | 7 +-- tests/fuzzers/vflux/clientpool-fuzzer.go | 76 +++++++++++++++++++----- tests/fuzzers/vflux/debug/main.go | 3 + 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go index 079d511704..351961b74e 100644 --- a/les/vflux/server/clientpool.go +++ b/les/vflux/server/clientpool.go @@ -239,12 +239,11 @@ func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Dur maxTarget = curve.maxCapacity(func(capacity uint64) int64 { return balance.estimatePriority(capacity, 0, 0, bias, false) }) - if maxTarget <= capacity { + if maxTarget < reqCap { return } - if maxTarget > reqCap { - maxTarget = reqCap - } + maxTarget = reqCap + // Specify a narrow target range that allows a limited number of fine step // iterations minTarget = maxTarget - maxTarget/20 diff --git a/tests/fuzzers/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go index 41b8627348..0414c001ec 100644 --- a/tests/fuzzers/vflux/clientpool-fuzzer.go +++ b/tests/fuzzers/vflux/clientpool-fuzzer.go @@ -28,11 +28,22 @@ import ( "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/les/vflux" vfs "github.com/ethereum/go-ethereum/les/vflux/server" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" ) +var ( + debugMode = false + doLog = func(msg string, ctx ...interface{}) { + if !debugMode { + return + } + log.Info(msg, ctx...) + } +) + type fuzzer struct { peers [256]*clientPeer disconnectList []*clientPeer @@ -65,6 +76,7 @@ func (p *clientPeer) InactiveAllowance() time.Duration { } func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { + origin, originTotal := p.capacity, p.fuzzer.activeCap p.fuzzer.activeCap -= p.capacity if p.capacity != 0 { p.fuzzer.activeCount-- @@ -74,9 +86,11 @@ func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { if p.capacity != 0 { p.fuzzer.activeCount++ } + doLog("Update capacity", "peer", p.node.ID(), "origin", origin, "cap", newCap, "origintotal", originTotal, "total", p.fuzzer.activeCap, "requested", requested) } func (p *clientPeer) Disconnect() { + origin, originTotal := p.capacity, p.fuzzer.activeCap p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p) p.fuzzer.activeCap -= p.capacity if p.capacity != 0 { @@ -84,6 +98,7 @@ func (p *clientPeer) Disconnect() { } p.capacity = 0 p.balance = nil + doLog("Disconnect", "peer", p.node.ID(), "origin", origin, "origintotal", originTotal, "total", p.fuzzer.activeCap) } func newFuzzer(input []byte) *fuzzer { @@ -165,12 +180,16 @@ func (f *fuzzer) randomFactors() vfs.PriceFactors { } } -func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance) { +func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance, id enode.ID) { switch f.randomInt(3) { case 0: - balance.RequestServed(uint64(f.randomTokenAmount(false))) + cost := uint64(f.randomTokenAmount(false)) + balance.RequestServed(cost) + doLog("Serve request cost", "id", id, "amount", cost) case 1: - balance.SetPriceFactors(f.randomFactors(), f.randomFactors()) + posFactor, negFactor := f.randomFactors(), f.randomFactors() + balance.SetPriceFactors(posFactor, negFactor) + doLog("Set price factor", "pos", posFactor, "neg", negFactor) case 2: balance.GetBalance() balance.GetRawBalance() @@ -178,12 +197,16 @@ func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance) { } } -func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator) { +func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator, id enode.ID) { switch f.randomInt(3) { case 0: - balance.AddBalance(f.randomTokenAmount(true)) + amount := f.randomTokenAmount(true) + balance.AddBalance(amount) + doLog("Add balance", "id", id, "amount", amount) case 1: - balance.SetBalance(uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false))) + pos, neg := uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false)) + balance.SetBalance(pos, neg) + doLog("Set balance", "id", id, "pos", pos, "neg", neg) case 2: balance.GetBalance() balance.GetRawBalance() @@ -212,33 +235,53 @@ func FuzzClientPool(input []byte) int { case 0: i := int(f.randomByte()) f.peers[i].balance = pool.Register(f.peers[i]) + doLog("Register peer", "id", f.peers[i].node.ID()) case 1: i := int(f.randomByte()) f.peers[i].Disconnect() + doLog("Disconnect peer", "id", f.peers[i].node.ID()) case 2: f.maxCount = uint64(f.randomByte()) f.maxCap = uint64(f.randomByte()) f.maxCap *= f.maxCap + + count, cap := pool.Limits() pool.SetLimits(f.maxCount, f.maxCap) + doLog("Set limits", "maxcount", f.maxCount, "maxcap", f.maxCap, "origincount", count, "oricap", cap) case 3: + bias := f.randomDelay() pool.SetConnectedBias(f.randomDelay()) + doLog("Set connection bias", "bias", bias) case 4: - pool.SetDefaultFactors(f.randomFactors(), f.randomFactors()) + pos, neg := f.randomFactors(), f.randomFactors() + pool.SetDefaultFactors(pos, neg) + doLog("Set default factors", "pos", pos, "neg", neg) case 5: - pool.SetExpirationTCs(uint64(f.randomInt(50000)), uint64(f.randomInt(50000))) + pos, neg := uint64(f.randomInt(50000)), uint64(f.randomInt(50000)) + pool.SetExpirationTCs(pos, neg) + doLog("Set expiration constants", "pos", pos, "neg", neg) case 6: - if _, err := pool.SetCapacity(f.peers[f.randomByte()].node, uint64(f.randomByte()), f.randomDelay(), f.randomBool()); err == vfs.ErrCantFindMaximum { + var ( + index = f.randomByte() + reqCap = uint64(f.randomByte()) + bias = f.randomDelay() + requested = f.randomBool() + ) + if _, err := pool.SetCapacity(f.peers[index].node, reqCap, bias, requested); err == vfs.ErrCantFindMaximum { panic(nil) } + doLog("Set capacity", "id", f.peers[index].node.ID(), "reqcap", reqCap, "bias", bias, "requested", requested) case 7: - if balance := f.peers[f.randomByte()].balance; balance != nil { - f.connectedBalanceOp(balance) + index := f.randomByte() + if balance := f.peers[index].balance; balance != nil { + f.connectedBalanceOp(balance, f.peers[index].node.ID()) } case 8: - pool.BalanceOperation(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, func(balance vfs.AtomicBalanceOperator) { + index := f.randomByte() + pool.BalanceOperation(f.peers[index].node.ID(), f.peers[index].freeID, func(balance vfs.AtomicBalanceOperator) { count := f.randomInt(4) for i := 0; i < count; i++ { - f.atomicBalanceOp(balance) + f.atomicBalanceOp(balance, f.peers[index].node.ID()) } }) case 9: @@ -272,13 +315,16 @@ func FuzzClientPool(input []byte) int { for _, peer := range f.disconnectList { pool.Unregister(peer) + doLog("Unregister peer", "id", peer.node.ID()) } f.disconnectList = nil if d := f.randomDelay(); d > 0 { clock.Run(d) } - //fmt.Println(f.activeCount, f.maxCount, f.activeCap, f.maxCap) - if activeCount, activeCap := pool.Active(); activeCount != f.activeCount || activeCap != f.activeCap { + doLog("Clientpool stats in fuzzer", "count", f.activeCap, "maxcount", f.maxCount, "cap", f.activeCap, "maxcap", f.maxCap) + activeCount, activeCap := pool.Active() + doLog("Clientpool stats in pool", "count", activeCount, "cap", activeCap) + if activeCount != f.activeCount || activeCap != f.activeCap { panic(nil) } if f.activeCount > f.maxCount || f.activeCap > f.maxCap { diff --git a/tests/fuzzers/vflux/debug/main.go b/tests/fuzzers/vflux/debug/main.go index de0b5d4124..1d4a5ff19c 100644 --- a/tests/fuzzers/vflux/debug/main.go +++ b/tests/fuzzers/vflux/debug/main.go @@ -21,10 +21,13 @@ import ( "io/ioutil" "os" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests/fuzzers/vflux" ) func main() { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: debug \n") fmt.Fprintf(os.Stderr, "Example\n") From 558bff40083ccb96165be17e4e40cad4fa8193b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 28 Apr 2021 21:40:06 +0300 Subject: [PATCH 498/948] eth/protocols/snap: lower the packet size to avoid overloading link --- eth/protocols/snap/sync.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 3ce4c8735f..2ad677f949 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -52,7 +52,7 @@ var ( const ( // maxRequestSize is the maximum number of bytes to request from a remote peer. - maxRequestSize = 512 * 1024 + maxRequestSize = 128 * 1024 // maxStorageSetRequestCount is the maximum number of contracts to request the // storage of in a single query. If this number is too low, we're not filling @@ -74,7 +74,7 @@ const ( // a single query. If this number is too low, we're not filling responses fully // and waste round trip times. If it's too high, we're capping responses and // waste bandwidth. - maxTrieRequestCount = 512 + maxTrieRequestCount = 256 ) var ( From e4270cacf4aa26875affc619dbf82ad18d06226e Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 28 Apr 2021 21:38:38 +0200 Subject: [PATCH 499/948] cmd/devp2p: fix flaky SameRequestID test (#22754) --- cmd/devp2p/internal/ethtest/eth66_suite.go | 71 ++++++++++++++----- .../internal/ethtest/eth66_suiteHelpers.go | 38 ++++++---- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 4265b25f6a..41177189dd 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -76,9 +76,14 @@ func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { }, } // write message - headers := s.getBlockHeaders66(t, conn, req, req.RequestId) + headers, err := s.getBlockHeaders66(conn, req, req.RequestId) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + } // check for correct headers - headersMatch(t, s.chain, headers) + if !headersMatch(t, s.chain, headers) { + t.Fatal("received wrong header(s)") + } } // TestSimultaneousRequests_66 sends two simultaneous `GetBlockHeader` requests @@ -115,12 +120,25 @@ func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { // wait for headers for first request headerChan := make(chan BlockHeaders, 1) go func(headers chan BlockHeaders) { - headers <- s.getBlockHeaders66(t, conn1, req1, req1.RequestId) + recvHeaders, err := s.getBlockHeaders66(conn1, req1, req1.RequestId) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + return + } + headers <- recvHeaders }(headerChan) // check headers of second request - headersMatch(t, s.chain, s.getBlockHeaders66(t, conn2, req2, req2.RequestId)) + headers1, err := s.getBlockHeaders66(conn2, req2, req2.RequestId) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + } + if !headersMatch(t, s.chain, headers1) { + t.Fatal("wrong header(s) in response to req2") + } // check headers of first request - headersMatch(t, s.chain, <-headerChan) + if !headersMatch(t, s.chain, <-headerChan) { + t.Fatal("wrong header(s) in response to req1") + } } // TestBroadcast_66 tests whether a block announcement is correctly @@ -377,26 +395,31 @@ func (s *Suite) TestZeroRequestID_66(t *utesting.T) { Amount: 2, }, } - headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req, req.RequestId)) + headers, err := s.getBlockHeaders66(conn, req, req.RequestId) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + } + if !headersMatch(t, s.chain, headers) { + t.Fatal("received wrong header(s)") + } } // TestSameRequestID_66 sends two requests with the same request ID // concurrently to a single node. func (s *Suite) TestSameRequestID_66(t *utesting.T) { conn := s.setupConnection66(t) - defer conn.Close() - // create two separate requests with same ID + // create two requests with the same request ID reqID := uint64(1234) - req1 := ð.GetBlockHeadersPacket66{ + request1 := ð.GetBlockHeadersPacket66{ RequestId: reqID, GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ Origin: eth.HashOrNumber{ - Number: 0, + Number: 1, }, Amount: 2, }, } - req2 := ð.GetBlockHeadersPacket66{ + request2 := ð.GetBlockHeadersPacket66{ RequestId: reqID, GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ Origin: eth.HashOrNumber{ @@ -405,12 +428,26 @@ func (s *Suite) TestSameRequestID_66(t *utesting.T) { Amount: 2, }, } - // send requests concurrently - go func() { - headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req2, reqID)) - }() - // check response from first request - headersMatch(t, s.chain, s.getBlockHeaders66(t, conn, req1, reqID)) + // write the first request + err := conn.write66(request1, GetBlockHeaders{}.Code()) + if err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // perform second request + headers2, err := s.getBlockHeaders66(conn, request2, reqID) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + return + } + // wait for response to first request + headers1, err := s.waitForBlockHeadersResponse66(conn, reqID) + if err != nil { + t.Fatalf("could not get BlockHeaders response: %v", err) + } + // check if headers match + if !headersMatch(t, s.chain, headers1) || !headersMatch(t, s.chain, headers2) { + t.Fatal("received wrong header(s)") + } } // TestLargeTxRequest_66 tests whether a node can fulfill a large GetPooledTransactions diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go index 3af8295c61..fec02b5246 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -18,6 +18,7 @@ package ethtest import ( "fmt" + "reflect" "time" "github.com/ethereum/go-ethereum/core/types" @@ -150,8 +151,7 @@ func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID ui func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { start := time.Now() for time.Since(start) < timeout { - timeout := time.Now().Add(10 * time.Second) - c.SetReadDeadline(timeout) + c.SetReadDeadline(time.Now().Add(10 * time.Second)) reqID, msg := c.read66() @@ -257,6 +257,9 @@ func (c *Conn) waitForBlock66(block *types.Block) error { return nil } time.Sleep(100 * time.Millisecond) + case *NewPooledTransactionHashes: + // ignore old announcements + continue default: return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) } @@ -269,31 +272,38 @@ func sendSuccessfulTx66(t *utesting.T, s *Suite, tx *types.Transaction) { sendSuccessfulTxWithConn(t, s, tx, sendConn) } -func (s *Suite) getBlockHeaders66(t *utesting.T, conn *Conn, req eth.Packet, expectedID uint64) BlockHeaders { - if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // check block headers response +// waitForBlockHeadersResponse66 waits for a BlockHeaders message with the given expected request ID +func (s *Suite) waitForBlockHeadersResponse66(conn *Conn, expectedID uint64) (BlockHeaders, error) { reqID, msg := conn.readAndServe66(s.chain, timeout) - switch msg := msg.(type) { case BlockHeaders: if reqID != expectedID { - t.Fatalf("request ID mismatch: wanted %d, got %d", expectedID, reqID) + return nil, fmt.Errorf("request ID mismatch: wanted %d, got %d", expectedID, reqID) } - return msg + return msg, nil default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) - return nil + return nil, fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) } } -func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) { +func (s *Suite) getBlockHeaders66(conn *Conn, req eth.Packet, expectedID uint64) (BlockHeaders, error) { + if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { + return nil, fmt.Errorf("could not write to connection: %v", err) + } + return s.waitForBlockHeadersResponse66(conn, expectedID) +} + +func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) bool { + mismatched := 0 for _, header := range headers { num := header.Number.Uint64() t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) - assert.Equal(t, chain.blocks[int(num)].Header(), header) + if !reflect.DeepEqual(chain.blocks[int(num)].Header(), header) { + mismatched += 1 + t.Logf("received wrong header: %v", pretty.Sdump(header)) + } } + return mismatched == 0 } func (s *Suite) sendNextBlock66(t *utesting.T) { From a81cf0d2b3497e5d78b2c06427953b90c1a0d70f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 28 Apr 2021 21:47:48 +0200 Subject: [PATCH 500/948] trie: remove redundant returns + use stacktrie where applicable (#22760) * trie: add benchmark for proofless range * trie: remove unused returns + use stacktrie --- core/state/snapshot/generate.go | 2 +- eth/protocols/snap/sync.go | 6 +- tests/fuzzers/rangeproof/rangeproof-fuzzer.go | 14 +-- trie/notary.go | 14 +-- trie/proof.go | 69 ++++++--------- trie/proof_test.go | 88 ++++++++++++------- 6 files changed, 96 insertions(+), 97 deletions(-) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 13b34f4d69..78fca45e44 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -368,7 +368,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix } // Verify the snapshot segment with range prover, ensure that all flat states // in this range correspond to merkle trie. - _, _, _, cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) + _, cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) return &proofResult{ keys: keys, vals: vals, diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 2ad677f949..287ac8d727 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -2176,7 +2176,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco if len(keys) > 0 { end = keys[len(keys)-1] } - _, _, _, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) + _, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) if err != nil { logger.Warn("Account range failed proof", "err", err) // Signal this request as failed, and ready for rescheduling @@ -2413,7 +2413,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(nodes) == 0 { // No proof has been attached, the response must cover the entire key // space and hash to the origin root. - dbs[i], _, _, _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) + dbs[i], _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) @@ -2428,7 +2428,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(keys) > 0 { end = keys[len(keys)-1] } - dbs[i], _, _, cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) + dbs[i], cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage range failed proof", "err", err) diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go index b82a380723..984bb9d0a8 100644 --- a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -170,17 +170,11 @@ func (f *fuzzer) fuzz() int { } ok = 1 //nodes, subtrie - nodes, subtrie, notary, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) + nodes, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) if err != nil { if nodes != nil { panic("err != nil && nodes != nil") } - if subtrie != nil { - panic("err != nil && subtrie != nil") - } - if notary != nil { - panic("err != nil && notary != nil") - } if hasMore { panic("err != nil && hasMore == true") } @@ -188,12 +182,6 @@ func (f *fuzzer) fuzz() int { if nodes == nil { panic("err == nil && nodes == nil") } - if subtrie == nil { - panic("err == nil && subtrie == nil") - } - if notary == nil { - panic("err == nil && subtrie == nil") - } } } return ok diff --git a/trie/notary.go b/trie/notary.go index 5a64727aa7..10c7628f55 100644 --- a/trie/notary.go +++ b/trie/notary.go @@ -21,17 +21,17 @@ import ( "github.com/ethereum/go-ethereum/ethdb/memorydb" ) -// KeyValueNotary tracks which keys have been accessed through a key-value reader +// keyValueNotary tracks which keys have been accessed through a key-value reader // with te scope of verifying if certain proof datasets are maliciously bloated. -type KeyValueNotary struct { +type keyValueNotary struct { ethdb.KeyValueReader reads map[string]struct{} } -// NewKeyValueNotary wraps a key-value database with an access notary to track +// newKeyValueNotary wraps a key-value database with an access notary to track // which items have bene accessed. -func NewKeyValueNotary(db ethdb.KeyValueReader) *KeyValueNotary { - return &KeyValueNotary{ +func newKeyValueNotary(db ethdb.KeyValueReader) *keyValueNotary { + return &keyValueNotary{ KeyValueReader: db, reads: make(map[string]struct{}), } @@ -39,14 +39,14 @@ func NewKeyValueNotary(db ethdb.KeyValueReader) *KeyValueNotary { // Get retrieves an item from the underlying database, but also tracks it as an // accessed slot for bloat checks. -func (k *KeyValueNotary) Get(key []byte) ([]byte, error) { +func (k *keyValueNotary) Get(key []byte) ([]byte, error) { k.reads[string(key)] = struct{}{} return k.KeyValueReader.Get(key) } // Accessed returns s snapshot of the original key-value store containing only the // data accessed through the notary. -func (k *KeyValueNotary) Accessed() ethdb.KeyValueStore { +func (k *keyValueNotary) Accessed() ethdb.KeyValueStore { db := memorydb.New() for keystr := range k.reads { key := []byte(keystr) diff --git a/trie/proof.go b/trie/proof.go index 61c35a8423..2feed24de4 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -464,115 +464,100 @@ func hasRightElement(node node, key []byte) bool { // // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. -func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, *Trie, *KeyValueNotary, bool, error) { +func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, bool, error) { if len(keys) != len(values) { - return nil, nil, nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) + return nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) } // Ensure the received batch is monotonic increasing. for i := 0; i < len(keys)-1; i++ { if bytes.Compare(keys[i], keys[i+1]) >= 0 { - return nil, nil, nil, false, errors.New("range is not monotonically increasing") + return nil, false, errors.New("range is not monotonically increasing") } } // Create a key-value notary to track which items from the given proof the // range prover actually needed to verify the data - notary := NewKeyValueNotary(proof) + notary := newKeyValueNotary(proof) // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if proof == nil { var ( diskdb = memorydb.New() - triedb = NewDatabase(diskdb) + tr = NewStackTrie(diskdb) ) - tr, err := New(common.Hash{}, triedb) - if err != nil { - return nil, nil, nil, false, err - } for index, key := range keys { tr.TryUpdate(key, values[index]) } - if tr.Hash() != rootHash { - return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) - } - // Proof seems valid, serialize all the nodes into the database - if _, err := tr.Commit(nil); err != nil { - return nil, nil, nil, false, err + if have, want := tr.Hash(), rootHash; have != want { + return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have) } - if err := triedb.Commit(rootHash, false, nil); err != nil { - return nil, nil, nil, false, err + // Proof seems valid, serialize remaining nodes into the database + if _, err := tr.Commit(); err != nil { + return nil, false, err } - return diskdb, tr, notary, false, nil // No more elements + return diskdb, false, nil // No more elements } // Special case, there is a provided edge proof but zero key/value // pairs, ensure there are no more accounts / slots in the trie. if len(keys) == 0 { root, val, err := proofToPath(rootHash, nil, firstKey, notary, true) if err != nil { - return nil, nil, nil, false, err + return nil, false, err } if val != nil || hasRightElement(root, firstKey) { - return nil, nil, nil, false, errors.New("more entries available") + return nil, false, errors.New("more entries available") } // Since the entire proof is a single path, we can construct a trie and a // node database directly out of the inputs, no need to generate them diskdb := notary.Accessed() - tr := &Trie{ - db: NewDatabase(diskdb), - root: root, - } - return diskdb, tr, notary, hasRightElement(root, firstKey), nil + return diskdb, hasRightElement(root, firstKey), nil } // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. if len(keys) == 1 && bytes.Equal(firstKey, lastKey) { root, val, err := proofToPath(rootHash, nil, firstKey, notary, false) if err != nil { - return nil, nil, nil, false, err + return nil, false, err } if !bytes.Equal(firstKey, keys[0]) { - return nil, nil, nil, false, errors.New("correct proof but invalid key") + return nil, false, errors.New("correct proof but invalid key") } if !bytes.Equal(val, values[0]) { - return nil, nil, nil, false, errors.New("correct proof but invalid data") + return nil, false, errors.New("correct proof but invalid data") } // Since the entire proof is a single path, we can construct a trie and a // node database directly out of the inputs, no need to generate them diskdb := notary.Accessed() - tr := &Trie{ - db: NewDatabase(diskdb), - root: root, - } - return diskdb, tr, notary, hasRightElement(root, firstKey), nil + return diskdb, hasRightElement(root, firstKey), nil } // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. if bytes.Compare(firstKey, lastKey) >= 0 { - return nil, nil, nil, false, errors.New("invalid edge keys") + return nil, false, errors.New("invalid edge keys") } // todo(rjl493456442) different length edge keys should be supported if len(firstKey) != len(lastKey) { - return nil, nil, nil, false, errors.New("inconsistent edge keys") + return nil, false, errors.New("inconsistent edge keys") } // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. root, _, err := proofToPath(rootHash, nil, firstKey, notary, true) if err != nil { - return nil, nil, nil, false, err + return nil, false, err } // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent // proof is also allowed. root, _, err = proofToPath(rootHash, root, lastKey, notary, true) if err != nil { - return nil, nil, nil, false, err + return nil, false, err } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. empty, err := unsetInternal(root, firstKey, lastKey) if err != nil { - return nil, nil, nil, false, err + return nil, false, err } // Rebuild the trie with the leaf stream, the shape of trie // should be same with the original one. @@ -588,16 +573,16 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key tr.TryUpdate(key, values[index]) } if tr.Hash() != rootHash { - return nil, nil, nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) + return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) } // Proof seems valid, serialize all the nodes into the database if _, err := tr.Commit(nil); err != nil { - return nil, nil, nil, false, err + return nil, false, err } if err := triedb.Commit(rootHash, false, nil); err != nil { - return nil, nil, nil, false, err + return nil, false, err } - return diskdb, tr, notary, hasRightElement(root, keys[len(keys)-1]), nil + return diskdb, hasRightElement(root, keys[len(keys)-1]), nil } // get returns the child of the given node. Return nil if the diff --git a/trie/proof_test.go b/trie/proof_test.go index 304affa9f2..7a906e2540 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -182,7 +182,7 @@ func TestRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -233,7 +233,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -254,7 +254,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatal("Failed to verify whole rang with non-existent edges") } @@ -289,7 +289,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) + _, _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -311,7 +311,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) + _, _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -335,7 +335,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -350,7 +350,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -365,7 +365,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -380,7 +380,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -399,7 +399,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := tinyTrie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) + _, _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -421,7 +421,7 @@ func TestAllElementsProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) + _, _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -434,7 +434,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) + _, _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -449,7 +449,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -482,7 +482,7 @@ func TestSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) + _, _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -518,7 +518,7 @@ func TestReverseSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) + _, _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -590,7 +590,7 @@ func TestBadRangeProof(t *testing.T) { index = mrand.Intn(end - start) vals[index] = nil } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err == nil { t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1) } @@ -624,7 +624,7 @@ func TestGappedRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err == nil { t.Fatal("expect error, got nil") } @@ -651,7 +651,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -667,7 +667,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -735,7 +735,7 @@ func TestHasRightElement(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, _, hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) + _, hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -768,25 +768,19 @@ func TestEmptyRangeProof(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - db, tr, not, _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) + db, _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) if c.err && err == nil { t.Fatalf("Expected error, got nil") } if !c.err && err != nil { t.Fatalf("Expected no error, got %v", err) } - // If no error was returned, ensure the returned trie and database contains + // If no error was returned, ensure the returned database contains // the entire proof, since there's no value if !c.err { - if err := tr.Prove(first, 0, memorydb.New()); err != nil { - t.Errorf("returned trie doesn't contain original proof: %v", err) - } if memdb := db.(*memorydb.Database); memdb.Len() != proof.Len() { t.Errorf("database entry count mismatch: have %d, want %d", memdb.Len(), proof.Len()) } - if not == nil { - t.Errorf("missing notary") - } } } } @@ -805,6 +799,8 @@ func TestBloatedProof(t *testing.T) { var vals [][]byte proof := memorydb.New() + // In the 'malicious' case, we add proofs for every single item + // (but only one key/value pair used as leaf) for i, entry := range entries { trie.Prove(entry.k, 0, proof) if i == 50 { @@ -812,12 +808,15 @@ func TestBloatedProof(t *testing.T) { vals = append(vals, entry.v) } } + // For reference, we use the same function, but _only_ prove the first + // and last element want := memorydb.New() trie.Prove(keys[0], 0, want) trie.Prove(keys[len(keys)-1], 0, want) - _, _, notary, _, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) - if used := notary.Accessed().(*memorydb.Database); used.Len() != want.Len() { + db, _, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + // The db should not contain anything of the bloated data + if used := db.(*memorydb.Database); used.Len() != want.Len() { t.Fatalf("notary proof size mismatch: have %d, want %d", used.Len(), want.Len()) } } @@ -922,13 +921,40 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, _, _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) + _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) if err != nil { b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } } } +func BenchmarkVerifyRangeNoProof10(b *testing.B) { benchmarkVerifyRangeNoProof(b, 100) } +func BenchmarkVerifyRangeNoProof500(b *testing.B) { benchmarkVerifyRangeNoProof(b, 500) } +func BenchmarkVerifyRangeNoProof1000(b *testing.B) { benchmarkVerifyRangeNoProof(b, 1000) } + +func benchmarkVerifyRangeNoProof(b *testing.B, size int) { + trie, vals := randomTrie(size) + var entries entrySlice + for _, kv := range vals { + entries = append(entries, kv) + } + sort.Sort(entries) + + var keys [][]byte + var values [][]byte + for _, entry := range entries { + keys = append(keys, entry.k) + values = append(values, entry.v) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, nil) + if err != nil { + b.Fatalf("Expected no error, got %v", err) + } + } +} + func randomTrie(n int) (*Trie, map[string]*kv) { trie := new(Trie) vals := make(map[string]*kv) From fae165a5def1a335594cf6761164e31fa4e8d27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 28 Apr 2021 23:09:15 +0300 Subject: [PATCH 501/948] core, eth, ethdb, trie: simplify range proofs --- core/rawdb/table.go | 5 - core/state/snapshot/generate.go | 2 +- eth/protocols/snap/sync.go | 121 +++++++++--------- ethdb/batch.go | 28 +++- ethdb/leveldb/leveldb.go | 9 +- ethdb/memorydb/memorydb.go | 9 +- tests/fuzzers/rangeproof/rangeproof-fuzzer.go | 9 +- tests/fuzzers/stacktrie/trie_fuzzer.go | 1 - trie/notary.go | 57 --------- trie/proof.go | 82 +++++------- trie/proof_test.go | 62 ++++----- trie/trie_test.go | 1 - 12 files changed, 149 insertions(+), 237 deletions(-) delete mode 100644 trie/notary.go diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 4daa6b5349..323ef6293c 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -176,11 +176,6 @@ func (b *tableBatch) Delete(key []byte) error { return b.batch.Delete(append([]byte(b.prefix), key...)) } -// KeyCount retrieves the number of keys queued up for writing. -func (b *tableBatch) KeyCount() int { - return b.batch.KeyCount() -} - // ValueSize retrieves the amount of data queued up for writing. func (b *tableBatch) ValueSize() int { return b.batch.ValueSize() diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 78fca45e44..8992d3f91b 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -368,7 +368,7 @@ func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix } // Verify the snapshot segment with range prover, ensure that all flat states // in this range correspond to merkle trie. - _, cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) + cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) return &proofResult{ keys: keys, vals: vals, diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 287ac8d727..d9c0cb9b1b 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -202,9 +202,8 @@ type storageResponse struct { accounts []common.Hash // Account hashes requested, may be only partially filled roots []common.Hash // Storage roots requested, may be only partially filled - hashes [][]common.Hash // Storage slot hashes in the returned range - slots [][][]byte // Storage slot values in the returned range - nodes []ethdb.KeyValueStore // Database containing the reconstructed trie nodes + hashes [][]common.Hash // Storage slot hashes in the returned range + slots [][][]byte // Storage slot values in the returned range cont bool // Whether the last storage range has a continuation } @@ -680,12 +679,22 @@ func (s *Syncer) loadSyncStatus() { } s.tasks = progress.Tasks for _, task := range s.tasks { - task.genBatch = s.db.NewBatch() + task.genBatch = ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.accountBytes += common.StorageSize(len(key) + len(value)) + }, + } task.genTrie = trie.NewStackTrie(task.genBatch) for _, subtasks := range task.SubTasks { for _, subtask := range subtasks { - subtask.genBatch = s.db.NewBatch() + subtask.genBatch = ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } subtask.genTrie = trie.NewStackTrie(task.genBatch) } } @@ -729,7 +738,12 @@ func (s *Syncer) loadSyncStatus() { // Make sure we don't overflow if the step is not a proper divisor last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } - batch := s.db.NewBatch() + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.accountBytes += common.StorageSize(len(key) + len(value)) + }, + } s.tasks = append(s.tasks, &accountTask{ Next: next, Last: last, @@ -746,19 +760,14 @@ func (s *Syncer) loadSyncStatus() { func (s *Syncer) saveSyncStatus() { // Serialize any partial progress to disk before spinning down for _, task := range s.tasks { - keys, bytes := task.genBatch.KeyCount(), task.genBatch.ValueSize() if err := task.genBatch.Write(); err != nil { log.Error("Failed to persist account slots", "err", err) } - s.accountBytes += common.StorageSize(keys*common.HashLength + bytes) - for _, subtasks := range task.SubTasks { for _, subtask := range subtasks { - keys, bytes := subtask.genBatch.KeyCount(), subtask.genBatch.ValueSize() if err := subtask.genBatch.Write(); err != nil { log.Error("Failed to persist storage slots", "err", err) } - s.accountBytes += common.StorageSize(keys*common.HashLength + bytes) } } } @@ -1763,12 +1772,15 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if res.subTask != nil { res.subTask.req = nil } - batch := s.db.NewBatch() - + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } var ( - slots int - nodes int - bytes common.StorageSize + slots int + oldStorageBytes = s.storageBytes ) // Iterate over all the accounts and reconstruct their storage tries from the // delivered slots @@ -1829,7 +1841,12 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { r := newHashRange(lastKey, chunks) // Our first task is the one that was just filled by this response. - batch := s.db.NewBatch() + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } tasks = append(tasks, &storageTask{ Next: common.Hash{}, Last: r.End(), @@ -1838,7 +1855,12 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { genTrie: trie.NewStackTrie(batch), }) for r.Next() { - batch := s.db.NewBatch() + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.storageBytes += common.StorageSize(len(key) + len(value)) + }, + } tasks = append(tasks, &storageTask{ Next: r.Start(), Last: r.End(), @@ -1883,27 +1905,23 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { } } } - // Iterate over all the reconstructed trie nodes and push them to disk - // if the contract is fully delivered. If it's chunked, the trie nodes - // will be reconstructed later. + // Iterate over all the complete contracts, reconstruct the trie nodes and + // push them to disk. If the contract is chunked, the trie nodes will be + // reconstructed later. slots += len(res.hashes[i]) if i < len(res.hashes)-1 || res.subTask == nil { - it := res.nodes[i].NewIterator(nil, nil) - for it.Next() { - batch.Put(it.Key(), it.Value()) - - bytes += common.StorageSize(common.HashLength + len(it.Value())) - nodes++ + tr := trie.NewStackTrie(batch) + for j := 0; j < len(res.hashes[i]); j++ { + tr.Update(res.hashes[i][j][:], res.slots[i][j]) } - it.Release() + tr.Commit() } // Persist the received storage segements. These flat state maybe // outdated during the sync, but it can be fixed later during the // snapshot generation. for j := 0; j < len(res.hashes[i]); j++ { rawdb.WriteStorageSnapshot(batch, account, res.hashes[i][j], res.slots[i][j]) - bytes += common.StorageSize(1 + 2*common.HashLength + len(res.slots[i][j])) // If we're storing large contracts, generate the trie nodes // on the fly to not trash the gluing points @@ -1926,15 +1944,11 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { } } } - if data := res.subTask.genBatch.ValueSize(); data > ethdb.IdealBatchSize || res.subTask.done { - keys := res.subTask.genBatch.KeyCount() + if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize || res.subTask.done { if err := res.subTask.genBatch.Write(); err != nil { log.Error("Failed to persist stack slots", "err", err) } res.subTask.genBatch.Reset() - - bytes += common.StorageSize(keys*common.HashLength + data) - nodes += keys } } // Flush anything written just now and update the stats @@ -1942,9 +1956,8 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { log.Crit("Failed to persist storage slots", "err", err) } s.storageSynced += uint64(slots) - s.storageBytes += bytes - log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "nodes", nodes, "bytes", bytes) + log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "bytes", s.storageBytes-oldStorageBytes) // If this delivery completed the last pending task, forward the account task // to the next chunk @@ -2042,18 +2055,20 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { // Persist the received account segements. These flat state maybe // outdated during the sync, but it can be fixed later during the // snapshot generation. - var ( - nodes int - bytes common.StorageSize - ) - batch := s.db.NewBatch() + oldAccountBytes := s.accountBytes + + batch := ethdb.HookedBatch{ + Batch: s.db.NewBatch(), + OnPut: func(key []byte, value []byte) { + s.accountBytes += common.StorageSize(len(key) + len(value)) + }, + } for i, hash := range res.hashes { if task.needCode[i] || task.needState[i] { break } slim := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash) rawdb.WriteAccountSnapshot(batch, hash, slim) - bytes += common.StorageSize(1 + common.HashLength + len(slim)) // If the task is complete, drop it into the stack trie to generate // account trie nodes for it @@ -2069,7 +2084,6 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { if err := batch.Write(); err != nil { log.Crit("Failed to persist accounts", "err", err) } - s.accountBytes += bytes s.accountSynced += uint64(len(res.accounts)) // Task filling persisted, push it the chunk marker forward to the first @@ -2091,17 +2105,13 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { log.Error("Failed to commit stack account", "err", err) } } - if data := task.genBatch.ValueSize(); data > ethdb.IdealBatchSize || task.done { - keys := task.genBatch.KeyCount() + if task.genBatch.ValueSize() > ethdb.IdealBatchSize || task.done { if err := task.genBatch.Write(); err != nil { log.Error("Failed to persist stack account", "err", err) } task.genBatch.Reset() - - nodes += keys - bytes += common.StorageSize(keys*common.HashLength + data) } - log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "nodes", nodes, "bytes", bytes) + log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "bytes", s.accountBytes-oldAccountBytes) } // OnAccounts is a callback method to invoke when a range of accounts are @@ -2176,7 +2186,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco if len(keys) > 0 { end = keys[len(keys)-1] } - _, cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) + cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) if err != nil { logger.Warn("Account range failed proof", "err", err) // Signal this request as failed, and ready for rescheduling @@ -2393,10 +2403,8 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo s.lock.Unlock() // Reconstruct the partial tries from the response and verify them - var ( - dbs = make([]ethdb.KeyValueStore, len(hashes)) - cont bool - ) + var cont bool + for i := 0; i < len(hashes); i++ { // Convert the keys and proofs into an internal format keys := make([][]byte, len(hashes[i])) @@ -2413,7 +2421,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(nodes) == 0 { // No proof has been attached, the response must cover the entire key // space and hash to the origin root. - dbs[i], _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) + _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) @@ -2428,7 +2436,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(keys) > 0 { end = keys[len(keys)-1] } - dbs[i], cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) + cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage range failed proof", "err", err) @@ -2444,7 +2452,6 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo roots: req.roots, hashes: hashes, slots: slots, - nodes: dbs, cont: cont, } select { diff --git a/ethdb/batch.go b/ethdb/batch.go index 5f8207fc46..1353693318 100644 --- a/ethdb/batch.go +++ b/ethdb/batch.go @@ -25,9 +25,6 @@ const IdealBatchSize = 100 * 1024 type Batch interface { KeyValueWriter - // KeyCount retrieves the number of keys queued up for writing. - KeyCount() int - // ValueSize retrieves the amount of data queued up for writing. ValueSize() int @@ -47,3 +44,28 @@ type Batcher interface { // until a final write is called. NewBatch() Batch } + +// HookedBatch wraps an arbitrary batch where each operation may be hooked into +// to monitor from black box code. +type HookedBatch struct { + Batch + + OnPut func(key []byte, value []byte) // Callback if a key is inserted + OnDelete func(key []byte) // Callback if a key is deleted +} + +// Put inserts the given value into the key-value data store. +func (b HookedBatch) Put(key []byte, value []byte) error { + if b.OnPut != nil { + b.OnPut(key, value) + } + return b.Batch.Put(key, value) +} + +// Delete removes the key from the key-value data store. +func (b HookedBatch) Delete(key []byte) error { + if b.OnDelete != nil { + b.OnDelete(key) + } + return b.Batch.Delete(key) +} diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index da00226e95..5d19cc3577 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -448,7 +448,6 @@ func (db *Database) meter(refresh time.Duration) { type batch struct { db *leveldb.DB b *leveldb.Batch - keys int size int } @@ -462,16 +461,10 @@ func (b *batch) Put(key, value []byte) error { // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { b.b.Delete(key) - b.keys++ b.size += len(key) return nil } -// KeyCount retrieves the number of keys queued up for writing. -func (b *batch) KeyCount() int { - return b.keys -} - // ValueSize retrieves the amount of data queued up for writing. func (b *batch) ValueSize() int { return b.size @@ -485,7 +478,7 @@ func (b *batch) Write() error { // Reset resets the batch for reuse. func (b *batch) Reset() { b.b.Reset() - b.keys, b.size = 0, 0 + b.size = 0 } // Replay replays the batch contents. diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index ded9f5e66c..fedc9e326c 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -198,7 +198,6 @@ type keyvalue struct { type batch struct { db *Database writes []keyvalue - keys int size int } @@ -212,16 +211,10 @@ func (b *batch) Put(key, value []byte) error { // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true}) - b.keys++ b.size += len(key) return nil } -// KeyCount retrieves the number of keys queued up for writing. -func (b *batch) KeyCount() int { - return b.keys -} - // ValueSize retrieves the amount of data queued up for writing. func (b *batch) ValueSize() int { return b.size @@ -245,7 +238,7 @@ func (b *batch) Write() error { // Reset resets the batch for reuse. func (b *batch) Reset() { b.writes = b.writes[:0] - b.keys, b.size = 0, 0 + b.size = 0 } // Replay replays the batch contents. diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go index 984bb9d0a8..09ee6bb9c7 100644 --- a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -170,18 +170,11 @@ func (f *fuzzer) fuzz() int { } ok = 1 //nodes, subtrie - nodes, hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) + hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) if err != nil { - if nodes != nil { - panic("err != nil && nodes != nil") - } if hasMore { panic("err != nil && hasMore == true") } - } else { - if nodes == nil { - panic("err == nil && nodes == nil") - } } } return ok diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go index 0013c919c9..5cea7769c2 100644 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -90,7 +90,6 @@ func (b *spongeBatch) Put(key, value []byte) error { return nil } func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } -func (b *spongeBatch) KeyCount() int { panic("not implemented") } func (b *spongeBatch) ValueSize() int { return 100 } func (b *spongeBatch) Write() error { return nil } func (b *spongeBatch) Reset() {} diff --git a/trie/notary.go b/trie/notary.go deleted file mode 100644 index 10c7628f55..0000000000 --- a/trie/notary.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package trie - -import ( - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/memorydb" -) - -// keyValueNotary tracks which keys have been accessed through a key-value reader -// with te scope of verifying if certain proof datasets are maliciously bloated. -type keyValueNotary struct { - ethdb.KeyValueReader - reads map[string]struct{} -} - -// newKeyValueNotary wraps a key-value database with an access notary to track -// which items have bene accessed. -func newKeyValueNotary(db ethdb.KeyValueReader) *keyValueNotary { - return &keyValueNotary{ - KeyValueReader: db, - reads: make(map[string]struct{}), - } -} - -// Get retrieves an item from the underlying database, but also tracks it as an -// accessed slot for bloat checks. -func (k *keyValueNotary) Get(key []byte) ([]byte, error) { - k.reads[string(key)] = struct{}{} - return k.KeyValueReader.Get(key) -} - -// Accessed returns s snapshot of the original key-value store containing only the -// data accessed through the notary. -func (k *keyValueNotary) Accessed() ethdb.KeyValueStore { - db := memorydb.New() - for keystr := range k.reads { - key := []byte(keystr) - val, _ := k.KeyValueReader.Get(key) - db.Put(key, val) - } - return db -} diff --git a/trie/proof.go b/trie/proof.go index 2feed24de4..08a9e40422 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -464,108 +464,91 @@ func hasRightElement(node node, key []byte) bool { // // Except returning the error to indicate the proof is valid or not, the function will // also return a flag to indicate whether there exists more accounts/slots in the trie. -func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (ethdb.KeyValueStore, bool, error) { +// +// Note: This method does not verify that the proof is of minimal form. If the input +// proofs are 'bloated' with neighbour leaves or random data, aside from the 'useful' +// data, then the proof will still be accepted. +func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) { if len(keys) != len(values) { - return nil, false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) + return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) } // Ensure the received batch is monotonic increasing. for i := 0; i < len(keys)-1; i++ { if bytes.Compare(keys[i], keys[i+1]) >= 0 { - return nil, false, errors.New("range is not monotonically increasing") + return false, errors.New("range is not monotonically increasing") } } - // Create a key-value notary to track which items from the given proof the - // range prover actually needed to verify the data - notary := newKeyValueNotary(proof) - // Special case, there is no edge proof at all. The given range is expected // to be the whole leaf-set in the trie. if proof == nil { - var ( - diskdb = memorydb.New() - tr = NewStackTrie(diskdb) - ) + tr := NewStackTrie(nil) for index, key := range keys { tr.TryUpdate(key, values[index]) } if have, want := tr.Hash(), rootHash; have != want { - return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have) - } - // Proof seems valid, serialize remaining nodes into the database - if _, err := tr.Commit(); err != nil { - return nil, false, err + return false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have) } - return diskdb, false, nil // No more elements + return false, nil // No more elements } // Special case, there is a provided edge proof but zero key/value // pairs, ensure there are no more accounts / slots in the trie. if len(keys) == 0 { - root, val, err := proofToPath(rootHash, nil, firstKey, notary, true) + root, val, err := proofToPath(rootHash, nil, firstKey, proof, true) if err != nil { - return nil, false, err + return false, err } if val != nil || hasRightElement(root, firstKey) { - return nil, false, errors.New("more entries available") + return false, errors.New("more entries available") } - // Since the entire proof is a single path, we can construct a trie and a - // node database directly out of the inputs, no need to generate them - diskdb := notary.Accessed() - return diskdb, hasRightElement(root, firstKey), nil + return hasRightElement(root, firstKey), nil } // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. if len(keys) == 1 && bytes.Equal(firstKey, lastKey) { - root, val, err := proofToPath(rootHash, nil, firstKey, notary, false) + root, val, err := proofToPath(rootHash, nil, firstKey, proof, false) if err != nil { - return nil, false, err + return false, err } if !bytes.Equal(firstKey, keys[0]) { - return nil, false, errors.New("correct proof but invalid key") + return false, errors.New("correct proof but invalid key") } if !bytes.Equal(val, values[0]) { - return nil, false, errors.New("correct proof but invalid data") + return false, errors.New("correct proof but invalid data") } - // Since the entire proof is a single path, we can construct a trie and a - // node database directly out of the inputs, no need to generate them - diskdb := notary.Accessed() - return diskdb, hasRightElement(root, firstKey), nil + return hasRightElement(root, firstKey), nil } // Ok, in all other cases, we require two edge paths available. // First check the validity of edge keys. if bytes.Compare(firstKey, lastKey) >= 0 { - return nil, false, errors.New("invalid edge keys") + return false, errors.New("invalid edge keys") } // todo(rjl493456442) different length edge keys should be supported if len(firstKey) != len(lastKey) { - return nil, false, errors.New("inconsistent edge keys") + return false, errors.New("inconsistent edge keys") } // Convert the edge proofs to edge trie paths. Then we can // have the same tree architecture with the original one. // For the first edge proof, non-existent proof is allowed. - root, _, err := proofToPath(rootHash, nil, firstKey, notary, true) + root, _, err := proofToPath(rootHash, nil, firstKey, proof, true) if err != nil { - return nil, false, err + return false, err } // Pass the root node here, the second path will be merged // with the first one. For the last edge proof, non-existent // proof is also allowed. - root, _, err = proofToPath(rootHash, root, lastKey, notary, true) + root, _, err = proofToPath(rootHash, root, lastKey, proof, true) if err != nil { - return nil, false, err + return false, err } // Remove all internal references. All the removed parts should // be re-filled(or re-constructed) by the given leaves range. empty, err := unsetInternal(root, firstKey, lastKey) if err != nil { - return nil, false, err + return false, err } // Rebuild the trie with the leaf stream, the shape of trie // should be same with the original one. - var ( - diskdb = memorydb.New() - triedb = NewDatabase(diskdb) - ) - tr := &Trie{root: root, db: triedb} + tr := &Trie{root: root, db: NewDatabase(memorydb.New())} if empty { tr.root = nil } @@ -573,16 +556,9 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key tr.TryUpdate(key, values[index]) } if tr.Hash() != rootHash { - return nil, false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) - } - // Proof seems valid, serialize all the nodes into the database - if _, err := tr.Commit(nil); err != nil { - return nil, false, err - } - if err := triedb.Commit(rootHash, false, nil); err != nil { - return nil, false, err + return false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash()) } - return diskdb, hasRightElement(root, keys[len(keys)-1]), nil + return hasRightElement(root, keys[len(keys)-1]), nil } // get returns the child of the given node. Return nil if the diff --git a/trie/proof_test.go b/trie/proof_test.go index 7a906e2540..a35b7144c0 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -182,7 +182,7 @@ func TestRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -233,7 +233,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -254,7 +254,7 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatal("Failed to verify whole rang with non-existent edges") } @@ -289,7 +289,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) + _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -311,7 +311,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) + _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -335,7 +335,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - _, _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -350,7 +350,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(entries[start].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -365,7 +365,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -380,7 +380,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) + _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -399,7 +399,7 @@ func TestOneElementRangeProof(t *testing.T) { if err := tinyTrie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) + _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -421,7 +421,7 @@ func TestAllElementsProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) + _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -434,7 +434,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) + _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -449,7 +449,7 @@ func TestAllElementsProof(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) + _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -482,7 +482,7 @@ func TestSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) + _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -518,7 +518,7 @@ func TestReverseSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) + _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -590,7 +590,7 @@ func TestBadRangeProof(t *testing.T) { index = mrand.Intn(end - start) vals[index] = nil } - _, _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) if err == nil { t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1) } @@ -624,7 +624,7 @@ func TestGappedRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) if err == nil { t.Fatal("expect error, got nil") } @@ -651,7 +651,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -667,7 +667,7 @@ func TestSameSideProofs(t *testing.T) { if err := trie.Prove(last, 0, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } - _, _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } @@ -735,7 +735,7 @@ func TestHasRightElement(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) + hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -768,25 +768,19 @@ func TestEmptyRangeProof(t *testing.T) { if err := trie.Prove(first, 0, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - db, _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) + _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) if c.err && err == nil { t.Fatalf("Expected error, got nil") } if !c.err && err != nil { t.Fatalf("Expected no error, got %v", err) } - // If no error was returned, ensure the returned database contains - // the entire proof, since there's no value - if !c.err { - if memdb := db.(*memorydb.Database); memdb.Len() != proof.Len() { - t.Errorf("database entry count mismatch: have %d, want %d", memdb.Len(), proof.Len()) - } - } } } // TestBloatedProof tests a malicious proof, where the proof is more or less the -// whole trie. +// whole trie. Previously we didn't accept such packets, but the new APIs do, so +// lets leave this test as a bit weird, but present. func TestBloatedProof(t *testing.T) { // Use a small trie trie, kvs := nonRandomTrie(100) @@ -814,10 +808,8 @@ func TestBloatedProof(t *testing.T) { trie.Prove(keys[0], 0, want) trie.Prove(keys[len(keys)-1], 0, want) - db, _, _ := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) - // The db should not contain anything of the bloated data - if used := db.(*memorydb.Database); used.Len() != want.Len() { - t.Fatalf("notary proof size mismatch: have %d, want %d", used.Len(), want.Len()) + if _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof); err != nil { + t.Fatalf("expected bloated proof to succeed, got %v", err) } } @@ -921,7 +913,7 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) if err != nil { b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -948,7 +940,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) { } b.ResetTimer() for i := 0; i < b.N; i++ { - _, _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, nil) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, nil) if err != nil { b.Fatalf("Expected no error, got %v", err) } diff --git a/trie/trie_test.go b/trie/trie_test.go index 44fddf87e4..492b423c2f 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -706,7 +706,6 @@ func (b *spongeBatch) Put(key, value []byte) error { return nil } func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } -func (b *spongeBatch) KeyCount() int { return 100 } func (b *spongeBatch) ValueSize() int { return 100 } func (b *spongeBatch) Write() error { return nil } func (b *spongeBatch) Reset() {} From 06f44c0fd425a0a3d82c7f3470da0314d6dc369e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 29 Apr 2021 12:02:30 +0300 Subject: [PATCH 502/948] eth: restore eth_hashrate API endpoint --- eth/api.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eth/api.go b/eth/api.go index e02c0ca4d2..7387459c94 100644 --- a/eth/api.go +++ b/eth/api.go @@ -61,6 +61,11 @@ func (api *PublicEthereumAPI) Coinbase() (common.Address, error) { return api.Etherbase() } +// Hashrate returns the POW hashrate +func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { + return hexutil.Uint64(api.e.Miner().Hashrate()) +} + // PublicMinerAPI provides an API to control the miner. // It offers only methods that operate on data that pose no security risk when it is publicly accessible. type PublicMinerAPI struct { From c7d07294a6f855d1c0fef9dcb55e0f87fb0e4962 Mon Sep 17 00:00:00 2001 From: Diederik Loerakker Date: Thu, 29 Apr 2021 16:42:21 +0200 Subject: [PATCH 503/948] catalyst: check if block exists in assemble-block call with unknown parent-hash (#22770) --- eth/catalyst/api.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index d6ea691d02..d7e2af1c1a 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -109,6 +109,11 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD bc := api.eth.BlockChain() parent := bc.GetBlockByHash(params.ParentHash) + if parent == nil { + log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash) + return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash) + } + pool := api.eth.TxPool() if parent.Time() >= params.Timestamp { From 793c8f889f7c565c4775385e5197c199bfde01a5 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 29 Apr 2021 18:36:22 +0200 Subject: [PATCH 504/948] add myself as code owner for catalyst (#22778) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 58c1a4a62e..2015604e64 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,6 +9,7 @@ cmd/puppeth @karalabe consensus @karalabe core/ @karalabe @holiman @rjl493456442 eth/ @karalabe @holiman @rjl493456442 +eth/catalyst/ @gballet graphql/ @gballet les/ @zsfelfoldi @rjl493456442 light/ @zsfelfoldi @rjl493456442 From 56f533d00ceea30b9111e1d6572b29a32f490ac2 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 29 Apr 2021 13:23:07 -0400 Subject: [PATCH 505/948] docs: fix docstring on read head block (#22776) --- core/rawdb/accessors_chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 92450313b4..76132bf37e 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -839,7 +839,7 @@ func ReadHeadHeader(db ethdb.Reader) *types.Header { return ReadHeader(db, headHeaderHash, *headHeaderNumber) } -// ReadHeadHeader returns the current canonical head block. +// ReadHeadBlock returns the current canonical head block. func ReadHeadBlock(db ethdb.Reader) *types.Block { headBlockHash := ReadHeadBlockHash(db) if headBlockHash == (common.Hash{}) { From 63bad18c33e215c6ef6f33aae7e686211cf945bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 29 Apr 2021 19:30:16 +0200 Subject: [PATCH 506/948] evm: remove unused errors left after EIP-2315 removal (#22767) --- core/vm/errors.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/vm/errors.go b/core/vm/errors.go index f6b156a02e..c813aa36af 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -23,9 +23,6 @@ import ( // List evm execution errors var ( - // ErrInvalidSubroutineEntry means that a BEGINSUB was reached via iteration, - // as opposed to from a JUMPSUB instruction - ErrInvalidSubroutineEntry = errors.New("invalid subroutine entry") ErrOutOfGas = errors.New("out of gas") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") ErrDepth = errors.New("max call depth exceeded") @@ -37,8 +34,6 @@ var ( ErrWriteProtection = errors.New("write protection") ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrGasUintOverflow = errors.New("gas uint64 overflow") - ErrInvalidRetsub = errors.New("invalid retsub") - ErrReturnStackExceeded = errors.New("return stack limit reached") ) // ErrStackUnderflow wraps an evm error when the items on the stack less From b50b17ac695ffcaa9a084f87a8ea6f53e37d2a75 Mon Sep 17 00:00:00 2001 From: ligi Date: Thu, 29 Apr 2021 19:30:37 +0200 Subject: [PATCH 507/948] github: add note about screenshots in issue template (#22764) --- .github/ISSUE_TEMPLATE/bug.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index c5a3654bde..2aa2c48a60 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -26,3 +26,5 @@ Commit hash : (if `develop`) ```` [backtrace] ```` + +When submitting logs: please submit them as text and not screenshots. \ No newline at end of file From bb43cd7a792ce3e89961d29410b692073b935ac8 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 29 Apr 2021 16:14:57 -0400 Subject: [PATCH 508/948] core/types: add license header (#22781) --- core/types/transaction_marshalling.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 184a17d5b5..e561485556 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -1,3 +1,19 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package types import ( From 8130dd5cefb11fe6bf70264087368c318692b9ff Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 30 Apr 2021 06:46:34 -0400 Subject: [PATCH 509/948] core/vm: fix typo in comment (#22785) --- core/vm/operations_acl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 45b51d80cd..c56941899e 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -30,7 +30,7 @@ const ( WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST ) -// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929" +// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 // // When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. // If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. From 1e57ab5de6612f9b3bea4d3eb6f08641be36944c Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 30 Apr 2021 06:47:05 -0400 Subject: [PATCH 510/948] core: remove unused else branch in reorg (#22783) --- core/blockchain.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 49aa1c3e86..b1da30a1b2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2162,7 +2162,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { l := *log if removed { l.Removed = true - } else { } logs = append(logs, &l) } From dde6cb0b9248f5bc7ac3619304ee3b1fce35f5d4 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 30 Apr 2021 06:49:13 -0400 Subject: [PATCH 511/948] core/vm: replace repeated string with variable in tests (#22774) --- core/vm/instructions_test.go | 69 ++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 14f9e181f9..d17ccfab89 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -40,6 +40,7 @@ type twoOperandParams struct { y string } +var alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" var commonParams []*twoOperandParams var twoOpMethods map[string]executionFunc @@ -347,8 +348,8 @@ func BenchmarkOpSub256(b *testing.B) { } func BenchmarkOpMul(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opMul, x, y) } @@ -379,64 +380,64 @@ func BenchmarkOpSdiv(b *testing.B) { } func BenchmarkOpMod(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opMod, x, y) } func BenchmarkOpSmod(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opSmod, x, y) } func BenchmarkOpExp(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opExp, x, y) } func BenchmarkOpSignExtend(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opSignExtend, x, y) } func BenchmarkOpLt(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opLt, x, y) } func BenchmarkOpGt(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opGt, x, y) } func BenchmarkOpSlt(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opSlt, x, y) } func BenchmarkOpSgt(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opSgt, x, y) } func BenchmarkOpEq(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opEq, x, y) } @@ -446,45 +447,45 @@ func BenchmarkOpEq2(b *testing.B) { opBenchmark(b, opEq, x, y) } func BenchmarkOpAnd(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opAnd, x, y) } func BenchmarkOpOr(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opOr, x, y) } func BenchmarkOpXor(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opXor, x, y) } func BenchmarkOpByte(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup opBenchmark(b, opByte, x, y) } func BenchmarkOpAddmod(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - z := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup + z := alphabetSoup opBenchmark(b, opAddmod, x, y, z) } func BenchmarkOpMulmod(b *testing.B) { - x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" - z := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff" + x := alphabetSoup + y := alphabetSoup + z := alphabetSoup opBenchmark(b, opMulmod, x, y, z) } From b778e37daa915f1b0d53e8ce3beb3884ee58120b Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 30 Apr 2021 06:50:02 -0400 Subject: [PATCH 512/948] core: fix typo in comment (#22773) --- core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index b1da30a1b2..e5e10fd643 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -482,7 +482,7 @@ func (bc *BlockChain) SetHead(head uint64) error { // SetHeadBeyondRoot rewinds the local chain to a new head with the extra condition // that the rewind must pass the specified state root. This method is meant to be -// used when rewiding with snapshots enabled to ensure that we go back further than +// used when rewinding with snapshots enabled to ensure that we go back further than // persistent disk layer. Depending on whether the node was fast synced or full, and // in which state, the method will try to delete minimal data from disk whilst // retaining chain consistency. From ff75b21f255d112b9a828150da25e9528451a574 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 30 Apr 2021 12:52:25 +0200 Subject: [PATCH 513/948] README.md: update commands table, add note about web3.js version (#22748) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a083d117a..77a403f7f3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ archives are published at https://geth.ethereum.org/downloads/. For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth). -Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install +Building `geth` requires both a Go (version 1.14 or later) and a C compiler. You can install them using your favourite package manager. Once the dependencies are installed, run ```shell @@ -37,10 +37,11 @@ directory. | Command | Description | | :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. | +| `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. | +| `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. | | `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. | | `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | | `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | -| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://eth.wiki/json-rpc/API) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | | `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://eth.wiki/en/fundamentals/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | | `puppeth` | a CLI wizard that aids in creating a new Ethereum network. | @@ -67,7 +68,8 @@ This command will: causing it to download more data in exchange for avoiding processing the entire history of the Ethereum network, which is very CPU intensive. * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console), - (via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://web3js.readthedocs.io/en/) + (via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://web3js.readthedocs.io/en/) + (note: the `web3` version bundled within `geth` is very old, and not up to date with official docs), as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server). This tool is optional and if you leave it out you can always attach to an already running `geth` instance with `geth attach`. @@ -228,7 +230,8 @@ aware of and agree upon. This consists of a small JSON file (e.g. call it `genes "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, - "istanbulBlock": 0 + "istanbulBlock": 0, + "berlinBlock": 0 }, "alloc": {}, "coinbase": "0x0000000000000000000000000000000000000000", From f66f1a16b3c480d3a43ac7e8a09ab3e362e96ae4 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 30 Apr 2021 07:00:48 -0400 Subject: [PATCH 514/948] eth/filters: fix comment on PublicFilterAPI timeoutLoop (#22782) --- eth/filters/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 4b36a5379e..e0b07e318e 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -72,8 +72,8 @@ func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration) return api } -// timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. -// Tt is started when the api is created. +// timeoutLoop runs at the interval set by 'timeout' and deletes filters +// that have not been recently used. It is started when the API is created. func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) { var toUninstall []*Subscription ticker := time.NewTicker(timeout) From bbb57fd64b70e3c843b5171d0a4719cf457374fc Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Fri, 30 Apr 2021 07:10:12 -0400 Subject: [PATCH 515/948] core/state: remove toAddr helper in tests (#22772) --- core/state/state_test.go | 14 ++++++-------- core/state/statedb_test.go | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/core/state/state_test.go b/core/state/state_test.go index 22e93d7a95..9566531466 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -27,8 +27,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb" ) -var toAddr = common.BytesToAddress - type stateTest struct { db ethdb.Database state *StateDB @@ -46,11 +44,11 @@ func TestDump(t *testing.T) { s := &stateTest{db: db, state: sdb} // generate a few entries - obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) + obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) obj1.AddBalance(big.NewInt(22)) - obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02})) + obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) - obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02})) + obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) obj3.SetBalance(big.NewInt(44)) // write some of them to the trie @@ -108,7 +106,7 @@ func TestNull(t *testing.T) { } func TestSnapshot(t *testing.T) { - stateobjaddr := toAddr([]byte("aa")) + stateobjaddr := common.BytesToAddress([]byte("aa")) var storageaddr common.Hash data1 := common.BytesToHash([]byte{42}) data2 := common.BytesToHash([]byte{43}) @@ -150,8 +148,8 @@ func TestSnapshotEmpty(t *testing.T) { func TestSnapshot2(t *testing.T) { state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) - stateobjaddr0 := toAddr([]byte("so0")) - stateobjaddr1 := toAddr([]byte("so1")) + stateobjaddr0 := common.BytesToAddress([]byte("so0")) + stateobjaddr1 := common.BytesToAddress([]byte("so1")) var storageaddr common.Hash data0 := common.BytesToHash([]byte{17}) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 220e28525c..9524e3730d 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -672,7 +672,7 @@ func TestDeleteCreateRevert(t *testing.T) { // Create an initial state with a single contract state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) - addr := toAddr([]byte("so")) + addr := common.BytesToAddress([]byte("so")) state.SetBalance(addr, big.NewInt(1)) root, _ := state.Commit(false) @@ -705,11 +705,11 @@ func TestMissingTrieNodes(t *testing.T) { db := NewDatabase(memDb) var root common.Hash state, _ := New(common.Hash{}, db, nil) - addr := toAddr([]byte("so")) + addr := common.BytesToAddress([]byte("so")) { state.SetBalance(addr, big.NewInt(1)) state.SetCode(addr, []byte{1, 2, 3}) - a2 := toAddr([]byte("another")) + a2 := common.BytesToAddress([]byte("another")) state.SetBalance(a2, big.NewInt(100)) state.SetCode(a2, []byte{1, 2, 4}) root, _ = state.Commit(false) From 745757ac6bb10c296ab30874ddde774f4fcdec1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 29 Apr 2021 17:33:45 +0300 Subject: [PATCH 516/948] core, eth: abort snapshot generation on snap sync and resume later --- core/blockchain.go | 3 +- core/rawdb/accessors_snapshot.go | 20 +++++++++ core/rawdb/database.go | 6 +-- core/rawdb/schema.go | 3 ++ core/state/snapshot/generate.go | 10 ----- core/state/snapshot/journal.go | 15 ++++--- core/state/snapshot/snapshot.go | 68 +++++++++++++++++++++++++++---- eth/downloader/downloader.go | 10 +++++ eth/downloader/downloader_test.go | 6 +++ eth/protocols/snap/sync.go | 5 --- 10 files changed, 115 insertions(+), 31 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 49aa1c3e86..c3562902d7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -640,7 +640,8 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { headBlockGauge.Update(int64(block.NumberU64())) bc.chainmu.Unlock() - // Destroy any existing state snapshot and regenerate it in the background + // Destroy any existing state snapshot and regenerate it in the background, + // also resuming the normal maintenance of any previously paused snapshot. if bc.snaps != nil { bc.snaps.Rebuild(block.Root()) } diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index c3616ba3aa..88446e0792 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -24,6 +24,26 @@ import ( "github.com/ethereum/go-ethereum/log" ) +// ReadSnapshotDisabled retrieves if the snapshot maintenance is disabled. +func ReadSnapshotDisabled(db ethdb.KeyValueReader) bool { + disabled, _ := db.Has(snapshotDisabledKey) + return disabled +} + +// WriteSnapshotDisabled stores the snapshot pause flag. +func WriteSnapshotDisabled(db ethdb.KeyValueWriter) { + if err := db.Put(snapshotDisabledKey, []byte("42")); err != nil { + log.Crit("Failed to store snapshot disabled flag", "err", err) + } +} + +// DeleteSnapshotDisabled deletes the flag keeping the snapshot maintenance disabled. +func DeleteSnapshotDisabled(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotDisabledKey); err != nil { + log.Crit("Failed to remove snapshot disabled flag", "err", err) + } +} + // ReadSnapshotRoot retrieves the root of the block whose state is contained in // the persisted snapshot. func ReadSnapshotRoot(db ethdb.KeyValueReader) common.Hash { diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 94759eb984..3a0a26c61d 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -371,9 +371,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { var accounted bool for _, meta := range [][]byte{ databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey, - fastTrieProgressKey, snapshotRootKey, snapshotJournalKey, snapshotGeneratorKey, - snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, - badBlockKey, + fastTrieProgressKey, snapshotDisabledKey, snapshotRootKey, snapshotJournalKey, + snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, + uncleanShutdownKey, badBlockKey, } { if bytes.Equal(key, meta) { metadata.Add(size) diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 7a97389106..2505ce90b9 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -45,6 +45,9 @@ var ( // fastTrieProgressKey tracks the number of trie entries imported during fast sync. fastTrieProgressKey = []byte("TrieSync") + // snapshotDisabledKey flags that the snapshot should not be maintained due to initial sync. + snapshotDisabledKey = []byte("SnapshotDisabled") + // snapshotRootKey tracks the hash of the last snapshot. snapshotRootKey = []byte("SnapshotRoot") diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 8992d3f91b..7e29e51b21 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -141,16 +141,6 @@ func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) { log.Info(msg, ctx...) } -// ClearSnapshotMarker sets the snapshot marker to zero, meaning that snapshots -// are not usable. -func ClearSnapshotMarker(diskdb ethdb.KeyValueStore) { - batch := diskdb.NewBatch() - journalProgress(batch, []byte{}, nil) - if err := batch.Write(); err != nil { - log.Crit("Failed to write initialized state marker", "err", err) - } -} - // generateSnapshot regenerates a brand new snapshot based on an existing state // database and head block asynchronously. The snapshot is returned immediately // and generation is continued in the background until done. diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index f8cec4d4ea..5cfb9a9f2a 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -126,12 +126,17 @@ func loadAndParseJournal(db ethdb.KeyValueStore, base *diskLayer) (snapshot, jou } // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. -func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, error) { +func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, recovery bool) (snapshot, bool, error) { + // If snapshotting is disabled (initial sync in progress), don't do anything, + // wait for the chain to permit us to do something meaningful + if rawdb.ReadSnapshotDisabled(diskdb) { + return nil, true, nil + } // Retrieve the block number and hash of the snapshot, failing if no snapshot // is present in the database (or crashed mid-update). baseRoot := rawdb.ReadSnapshotRoot(diskdb) if baseRoot == (common.Hash{}) { - return nil, errors.New("missing or corrupted snapshot") + return nil, false, errors.New("missing or corrupted snapshot") } base := &diskLayer{ diskdb: diskdb, @@ -142,7 +147,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, snapshot, generator, err := loadAndParseJournal(diskdb, base) if err != nil { log.Warn("Failed to load new-format journal", "error", err) - return nil, err + return nil, false, err } // Entire snapshot journal loaded, sanity check the head. If the loaded // snapshot is not matched with current state root, print a warning log @@ -157,7 +162,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, // it's not in recovery mode, returns the error here for // rebuilding the entire snapshot forcibly. if !recovery { - return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) + return nil, false, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) } // It's in snapshot recovery, the assumption is held that // the disk layer is always higher than chain head. It can @@ -187,7 +192,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, storage: common.StorageSize(generator.Storage), }) } - return snapshot, nil + return snapshot, false, nil } // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 9ecbd4a6c8..cb8ec7a707 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -148,11 +148,11 @@ type snapshot interface { StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) } -// SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent -// base layer backed by a key-value store, on top of which arbitrarily many in- -// memory diff layers are topped. The memory diffs can form a tree with branching, -// but the disk layer is singleton and common to all. If a reorg goes deeper than -// the disk layer, everything needs to be deleted. +// Tree is an Ethereum state snapshot tree. It consists of one persistent base +// layer backed by a key-value store, on top of which arbitrarily many in-memory +// diff layers are topped. The memory diffs can form a tree with branching, but +// the disk layer is singleton and common to all. If a reorg goes deeper than the +// disk layer, everything needs to be deleted. // // The goal of a state snapshot is twofold: to allow direct access to account and // storage data to avoid expensive multi-level trie lookups; and to allow sorted, @@ -186,7 +186,11 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm defer snap.waitBuild() } // Attempt to load a previously persisted snapshot and rebuild one if failed - head, err := loadSnapshot(diskdb, triedb, cache, root, recovery) + head, disabled, err := loadSnapshot(diskdb, triedb, cache, root, recovery) + if disabled { + log.Warn("Snapshot maintenance disabled (syncing)") + return snap, nil + } if err != nil { if rebuild { log.Warn("Failed to load snapshot, regenerating", "err", err) @@ -224,6 +228,55 @@ func (t *Tree) waitBuild() { } } +// Disable interrupts any pending snapshot generator, deletes all the snapshot +// layers in memory and marks snapshots disabled globally. In order to resume +// the snapshot functionality, the caller must invoke Rebuild. +func (t *Tree) Disable() { + // Interrupt any live snapshot layers + t.lock.Lock() + defer t.lock.Unlock() + + for _, layer := range t.layers { + switch layer := layer.(type) { + case *diskLayer: + // If the base layer is generating, abort it + if layer.genAbort != nil { + abort := make(chan *generatorStats) + layer.genAbort <- abort + <-abort + } + // Layer should be inactive now, mark it as stale + layer.lock.Lock() + layer.stale = true + layer.lock.Unlock() + + case *diffLayer: + // If the layer is a simple diff, simply mark as stale + layer.lock.Lock() + atomic.StoreUint32(&layer.stale, 1) + layer.lock.Unlock() + + default: + panic(fmt.Sprintf("unknown layer type: %T", layer)) + } + } + t.layers = map[common.Hash]snapshot{} + + // Delete all snapshot liveness information from the database + batch := t.diskdb.NewBatch() + + rawdb.WriteSnapshotDisabled(batch) + rawdb.DeleteSnapshotRoot(batch) + rawdb.DeleteSnapshotJournal(batch) + rawdb.DeleteSnapshotGenerator(batch) + rawdb.DeleteSnapshotRecoveryNumber(batch) + // Note, we don't delete the sync progress + + if err := batch.Write(); err != nil { + log.Crit("Failed to disable snapshots", "err", err) + } +} + // Snapshot retrieves a snapshot belonging to the given block root, or nil if no // snapshot is maintained for that block. func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { @@ -626,8 +679,9 @@ func (t *Tree) Rebuild(root common.Hash) { defer t.lock.Unlock() // Firstly delete any recovery flag in the database. Because now we are - // building a brand new snapshot. + // building a brand new snapshot. Also reenable the snapshot feature. rawdb.DeleteSnapshotRecoveryNumber(t.diskdb) + rawdb.DeleteSnapshotDisabled(t.diskdb) // Iterate over and mark all layers stale for _, layer := range t.layers { diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index b8cb48914e..6f59b29a5e 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/eth/protocols/snap" @@ -214,6 +215,9 @@ type BlockChain interface { // InsertReceiptChain inserts a batch of receipts into the local chain. InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error) + + // Snapshots returns the blockchain snapshot tree to paused it during sync. + Snapshots() *snapshot.Tree } // New creates a new downloader to fetch hashes and blocks from remote peers. @@ -393,6 +397,12 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode // but until snap becomes prevalent, we should support both. TODO(karalabe). if mode == SnapSync { if !d.snapSync { + // Snap sync uses the snapshot namespace to store potentially flakey data until + // sync completely heals and finishes. Pause snapshot maintenance in the mean + // time to prevent access. + if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests + snapshots.Disable() + } log.Warn("Enabling snapshot sync prototype") d.snapSync = true } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 1140a444c1..794160993b 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/ethdb" @@ -409,6 +410,11 @@ func (dl *downloadTester) dropPeer(id string) { dl.downloader.UnregisterPeer(id) } +// Snapshots implements the BlockChain interface for the downloader, but is a noop. +func (dl *downloadTester) Snapshots() *snapshot.Tree { + return nil +} + type downloadTesterPeer struct { dl *downloadTester id string diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index d9c0cb9b1b..2373149165 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -551,11 +551,6 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { log.Debug("Snapshot sync already completed") return nil } - // If sync is still not finished, we need to ensure that any marker is wiped. - // Otherwise, it may happen that requests for e.g. genesis-data is delivered - // from the snapshot data, instead of from the trie - snapshot.ClearSnapshotMarker(s.db) - defer func() { // Persist any progress, independent of failure for _, task := range s.tasks { s.forwardAccountTask(task) From 52b5d2d8699c2ffea6ea6a2de69b30a1911359bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 30 Apr 2021 18:24:34 +0300 Subject: [PATCH 517/948] eth/protocols/snap: use storage batch, not account batch in st task --- eth/protocols/snap/sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 2373149165..e283473207 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -690,7 +690,7 @@ func (s *Syncer) loadSyncStatus() { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } - subtask.genTrie = trie.NewStackTrie(task.genBatch) + subtask.genTrie = trie.NewStackTrie(subtask.genBatch) } } } From 8ff98108e53c01acb4266f23a272c9d707cb3dcd Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 30 Apr 2021 22:47:36 +0200 Subject: [PATCH 518/948] cmd/devp2p: fix flakey tests in CI (#22757) This PR fixes a couple of issues in the eth test suite that caused flakiness when run in the CI. --- cmd/devp2p/internal/ethtest/eth66_suite.go | 12 +-------- .../internal/ethtest/eth66_suiteHelpers.go | 19 ++++++++------ cmd/devp2p/internal/ethtest/suite.go | 25 ++++++++----------- cmd/devp2p/internal/ethtest/types.go | 13 +++++++--- 4 files changed, 32 insertions(+), 37 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index 41177189dd..d4890d8de6 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -217,17 +217,7 @@ func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { sendConn.Close() } // Test the last block as a valid block - sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) - defer sendConn.Close() - defer receiveConn.Close() - - s.testAnnounce66(t, sendConn, receiveConn, blocks[3]) - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) - // wait for client to update its chain - if err := receiveConn.waitForBlock66(s.fullChain.blocks[nextBlock]); err != nil { - t.Fatal(err) - } + s.sendNextBlock66(t) } func (s *Suite) TestOldAnnounce_66(t *utesting.T) { diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go index fec02b5246..3c5b22f0b5 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -229,8 +229,11 @@ func (s *Suite) waitAnnounce66(t *utesting.T, conn *Conn, blockAnnouncement *New func (c *Conn) waitForBlock66(block *types.Block) error { defer c.SetReadDeadline(time.Time{}) - timeout := time.Now().Add(20 * time.Second) - c.SetReadDeadline(timeout) + c.SetReadDeadline(time.Now().Add(20 * time.Second)) + // note: if the node has not yet imported the block, it will respond + // to the GetBlockHeaders request with an empty BlockHeaders response, + // so the GetBlockHeaders request must be sent again until the BlockHeaders + // response contains the desired header. for { req := eth.GetBlockHeadersPacket66{ RequestId: 54, @@ -253,8 +256,10 @@ func (c *Conn) waitForBlock66(block *types.Block) error { if reqID != req.RequestId { return fmt.Errorf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) } - if len(msg) > 0 { - return nil + for _, header := range msg { + if header.Number.Uint64() == block.NumberU64() { + return nil + } } time.Sleep(100 * time.Millisecond) case *NewPooledTransactionHashes: @@ -319,10 +324,10 @@ func (s *Suite) sendNextBlock66(t *utesting.T) { } // send announcement and wait for node to request the header s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) // wait for client to update its chain - if err := receiveConn.waitForBlock66(s.chain.Head()); err != nil { + if err := receiveConn.waitForBlock66(s.fullChain.blocks[nextBlock]); err != nil { t.Fatal(err) } + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 2fa31ad31d..abc6bcddce 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -260,22 +260,28 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { // TestBroadcast tests whether a block announcement is correctly // propagated to the given node's peer(s). func (s *Suite) TestBroadcast(t *utesting.T) { + s.sendNextBlock(t) +} + +func (s *Suite) sendNextBlock(t *utesting.T) { sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) defer sendConn.Close() defer receiveConn.Close() + // create new block announcement nextBlock := len(s.chain.blocks) blockAnnouncement := &NewBlock{ Block: s.fullChain.blocks[nextBlock], TD: s.fullChain.TD(nextBlock + 1), } + // send announcement and wait for node to request the header s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement) - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) // wait for client to update its chain - if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { + if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { t.Fatal(err) } + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) } // TestMaliciousHandshake tries to send malicious data during the handshake. @@ -394,18 +400,7 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { sendConn.Close() } // Test the last block as a valid block - sendConn := s.setupConnection(t) - receiveConn := s.setupConnection(t) - defer sendConn.Close() - defer receiveConn.Close() - - s.testAnnounce(t, sendConn, receiveConn, blocks[3]) - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) - // wait for client to update its chain - if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { - t.Fatal(err) - } + s.sendNextBlock(t) } func (s *Suite) TestOldAnnounce(t *utesting.T) { diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 55adb75f85..50a69b9418 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -334,8 +334,11 @@ loop: func (c *Conn) waitForBlock(block *types.Block) error { defer c.SetReadDeadline(time.Time{}) - timeout := time.Now().Add(20 * time.Second) - c.SetReadDeadline(timeout) + c.SetReadDeadline(time.Now().Add(20 * time.Second)) + // note: if the node has not yet imported the block, it will respond + // to the GetBlockHeaders request with an empty BlockHeaders response, + // so the GetBlockHeaders request must be sent again until the BlockHeaders + // response contains the desired header. for { req := &GetBlockHeaders{Origin: eth.HashOrNumber{Hash: block.Hash()}, Amount: 1} if err := c.Write(req); err != nil { @@ -343,8 +346,10 @@ func (c *Conn) waitForBlock(block *types.Block) error { } switch msg := c.Read().(type) { case *BlockHeaders: - if len(*msg) > 0 { - return nil + for _, header := range *msg { + if header.Number.Uint64() == block.NumberU64() { + return nil + } } time.Sleep(100 * time.Millisecond) default: From 0e00ee42ec4e43ce3b9b1ffdadea3c66aa6eeba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Sat, 1 May 2021 13:19:24 +0200 Subject: [PATCH 519/948] core/vm: clean up contract creation error handling (#22766) Do not keep separate flag for "max code size exceeded" case, but instead assign appropriate error for it sooner. --- core/vm/evm.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 3f16f33b2d..bd54e855c6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -463,13 +463,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := run(evm, contract, nil, false) - // check whether the max code size has been exceeded - maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize + // Check whether the max code size has been exceeded, assign err if the case. + if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + err = ErrMaxCodeSizeExceeded + } + // if the contract creation ran successfully and no errors were returned // calculate the gas required to store the code. If the code could not // be stored due to not enough gas set an error and let it be handled // by the error checking condition below. - if err == nil && !maxCodeSizeExceeded { + if err == nil { createDataGas := uint64(len(ret)) * params.CreateDataGas if contract.UseGas(createDataGas) { evm.StateDB.SetCode(address, ret) @@ -481,21 +484,17 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. - if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) { + if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { contract.UseGas(contract.Gas) } } - // Assign err if contract code size exceeds the max while the err is still empty. - if maxCodeSizeExceeded && err == nil { - err = ErrMaxCodeSizeExceeded - } + if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) } return ret, address, contract.Gas, err - } // Create creates a new contract using code as deployment code. From ca9c576e6214d7d9607e59dd9912e3def4095d85 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Mon, 3 May 2021 04:58:00 -0400 Subject: [PATCH 520/948] core/vm: fix interpreter comments (#22797) * Fix interpreter comment * Fix comment --- core/vm/interpreter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 3b67ad6dea..1022c355c1 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -144,7 +144,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( defer func() { in.evm.depth-- }() // Make sure the readOnly is only set if we aren't in readOnly yet. - // This makes also sure that the readOnly flag isn't removed for child calls. + // This also makes sure that the readOnly flag isn't removed for child calls. if readOnly && !in.readOnly { in.readOnly = true defer func() { in.readOnly = false }() @@ -226,7 +226,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } - // If the operation is valid, enforce and write restrictions + // If the operation is valid, enforce write restrictions if in.readOnly && in.evm.chainRules.IsByzantium { // If the interpreter is operating in readonly mode, make sure no // state-modifying operation is performed. The 3rd stack item From afb097eda83bbc5d69d7fa9aa5ed18b1869af4ba Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 May 2021 14:28:02 +0200 Subject: [PATCH 521/948] params: remove dependency on crypto (#22788) * params: remove dependency on crypto Package params should not depend on package crypto because building crypto requires cgo. Since build/ci.go needs package params to get the go-ethereum version number, C code must be compiled in order to run the build tool, which is annoying for certain cross-compilation setups. * params: add SectionHead --- params/config.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/params/config.go b/params/config.go index f4e2f5ea67..eb80bb2e27 100644 --- a/params/config.go +++ b/params/config.go @@ -22,7 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/crypto/sha3" ) // Genesis hashes to enforce below configs on. @@ -278,12 +278,18 @@ func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool { // Hash returns the hash of checkpoint's four key fields(index, sectionHead, chtRoot and bloomTrieRoot). func (c *TrustedCheckpoint) Hash() common.Hash { - buf := make([]byte, 8+3*common.HashLength) - binary.BigEndian.PutUint64(buf, c.SectionIndex) - copy(buf[8:], c.SectionHead.Bytes()) - copy(buf[8+common.HashLength:], c.CHTRoot.Bytes()) - copy(buf[8+2*common.HashLength:], c.BloomRoot.Bytes()) - return crypto.Keccak256Hash(buf) + var sectionIndex [8]byte + binary.BigEndian.PutUint64(sectionIndex[:], c.SectionIndex) + + w := sha3.NewLegacyKeccak256() + w.Write(sectionIndex[:]) + w.Write(c.SectionHead[:]) + w.Write(c.CHTRoot[:]) + w.Write(c.BloomRoot[:]) + + var h common.Hash + w.Sum(h[:0]) + return h } // Empty returns an indicator whether the checkpoint is regarded as empty. From 8f94fc26e3a9de7a756df455f12f6a8fe2a6ed09 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 3 May 2021 14:29:05 +0200 Subject: [PATCH 522/948] cmd/utils: don't crash on nonexistent datadir (#22738) --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 59cf32c983..a50fdd968a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1662,7 +1662,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.GlobalIsSet(DataDirFlag.Name) { // Check if we have an already initialized chain and fall back to // that if so. Otherwise we need to generate a new genesis spec. - chaindb := MakeChainDatabase(ctx, stack, true) + chaindb := MakeChainDatabase(ctx, stack, false) // TODO (MariusVanDerWijden) make this read only if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } From 856c379626c2ff6d37dc28e79596cfbb814bf1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 3 May 2021 15:42:43 +0300 Subject: [PATCH 523/948] eth: don't print db upgrade warning on db init --- eth/backend.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index 4c7374612e..7aac17eeb3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -165,7 +165,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if bcVersion != nil && *bcVersion > core.BlockChainVersion { return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { - log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) + if bcVersion != nil { // only print warning on upgrade, not on init + log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) + } rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) } } From b8040a430e34117f121c67e8deee4a5e889e8372 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 May 2021 11:29:32 +0200 Subject: [PATCH 524/948] cmd/utils: use eth DNS tree for snap discovery (#22808) This removes auto-configuration of the snap.*.ethdisco.net DNS discovery tree. Since measurements have shown that > 75% of nodes in all.*.ethdisco.net support snap, we have decided to retire the dedicated index for snap and just use the eth tree instead. The dial iterators of eth and snap now use the same DNS tree in the default configuration, so both iterators should use the same DNS discovery client instance. This ensures that the record cache and rate limit are shared. Records will not be requested multiple times. While testing the change, I noticed that duplicate DNS requests do happen even when the client instance is shared. This is because the two iterators request the tree root, link tree root, and first levels of the tree in lockstep. To avoid this problem, the change also adds a singleflight.Group instance in the client. When one iterator attempts to resolve an entry which is already being resolved, the singleflight object waits for the existing resolve call to finish and returns the entry to both places. --- cmd/utils/flags.go | 6 +---- eth/backend.go | 10 +++++-- eth/discovery.go | 11 -------- eth/protocols/snap/handler.go | 6 +++++ go.mod | 1 + go.sum | 2 ++ p2p/dnsdisc/client.go | 51 ++++++++++++++++++++--------------- 7 files changed, 48 insertions(+), 39 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a50fdd968a..a81188342f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1690,11 +1690,7 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { } if url := params.KnownDNSNetwork(genesis, protocol); url != "" { cfg.EthDiscoveryURLs = []string{url} - } - if cfg.SyncMode == downloader.SnapSync { - if url := params.KnownDNSNetwork(genesis, "snap"); url != "" { - cfg.SnapDiscoveryURLs = []string{url} - } + cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs } } diff --git a/eth/backend.go b/eth/backend.go index 7aac17eeb3..7d8b0c52c6 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -50,6 +50,7 @@ import ( "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" @@ -239,14 +240,17 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) - eth.ethDialCandidates, err = setupDiscovery(eth.config.EthDiscoveryURLs) + // Setup DNS discovery iterators. + dnsclient := dnsdisc.NewClient(dnsdisc.Config{}) + eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...) if err != nil { return nil, err } - eth.snapDialCandidates, err = setupDiscovery(eth.config.SnapDiscoveryURLs) + eth.snapDialCandidates, err = dnsclient.NewIterator(eth.config.SnapDiscoveryURLs...) if err != nil { return nil, err } + // Start the RPC service eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId) @@ -544,6 +548,8 @@ func (s *Ethereum) Start() error { // Ethereum protocol. func (s *Ethereum) Stop() error { // Stop all the peer-related stuff first. + s.ethDialCandidates.Close() + s.snapDialCandidates.Close() s.handler.Stop() // Then stop everything else. diff --git a/eth/discovery.go b/eth/discovery.go index 855ce3b0e1..70668b2b70 100644 --- a/eth/discovery.go +++ b/eth/discovery.go @@ -19,7 +19,6 @@ package eth import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" ) @@ -62,13 +61,3 @@ func (eth *Ethereum) currentEthEntry() *ethEntry { return ðEntry{ForkID: forkid.NewID(eth.blockchain.Config(), eth.blockchain.Genesis().Hash(), eth.blockchain.CurrentHeader().Number.Uint64())} } - -// setupDiscovery creates the node discovery source for the `eth` and `snap` -// protocols. -func setupDiscovery(urls []string) (enode.Iterator, error) { - if len(urls) == 0 { - return nil, nil - } - client := dnsdisc.NewClient(dnsdisc.Config{}) - return client.NewIterator(urls...) -} diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index 9bfac6f03f..3d668a2ebb 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -84,6 +84,12 @@ type Backend interface { // MakeProtocols constructs the P2P protocol definitions for `snap`. func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol { + // Filter the discovery iterator for nodes advertising snap support. + dnsdisc = enode.Filter(dnsdisc, func(n *enode.Node) bool { + var snap enrEntry + return n.Load(&snap) == nil + }) + protocols := make([]p2p.Protocol, len(ProtocolVersions)) for i, version := range ProtocolVersions { version := version // Closure diff --git a/go.mod b/go.mod index 63636caae1..079d40ae6f 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,7 @@ require ( github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 golang.org/x/text v0.3.4 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 diff --git a/go.sum b/go.sum index cadcd1de95..3ae80f7b9c 100644 --- a/go.sum +++ b/go.sum @@ -455,6 +455,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index 3e4b50aadd..f2a4bed4c6 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -32,15 +32,17 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" lru "github.com/hashicorp/golang-lru" + "golang.org/x/sync/singleflight" "golang.org/x/time/rate" ) // Client discovers nodes by querying DNS servers. type Client struct { - cfg Config - clock mclock.Clock - entries *lru.Cache - ratelimit *rate.Limiter + cfg Config + clock mclock.Clock + entries *lru.Cache + ratelimit *rate.Limiter + singleflight singleflight.Group } // Config holds configuration options for the client. @@ -135,17 +137,20 @@ func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) { // resolveRoot retrieves a root entry via DNS. func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) { - txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain) - c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err) - if err != nil { - return rootEntry{}, err - } - for _, txt := range txts { - if strings.HasPrefix(txt, rootPrefix) { - return parseAndVerifyRoot(txt, loc) + e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) { + txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain) + c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err) + if err != nil { + return rootEntry{}, err } - } - return rootEntry{}, nameError{loc.domain, errNoRoot} + for _, txt := range txts { + if strings.HasPrefix(txt, rootPrefix) { + return parseAndVerifyRoot(txt, loc) + } + } + return rootEntry{}, nameError{loc.domain, errNoRoot} + }) + return e.(rootEntry), err } func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) { @@ -168,17 +173,21 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, if err := c.ratelimit.Wait(ctx); err != nil { return nil, err } - cacheKey := truncateHash(hash) if e, ok := c.entries.Get(cacheKey); ok { return e.(entry), nil } - e, err := c.doResolveEntry(ctx, domain, hash) - if err != nil { - return nil, err - } - c.entries.Add(cacheKey, e) - return e, nil + + ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) { + e, err := c.doResolveEntry(ctx, domain, hash) + if err != nil { + return nil, err + } + c.entries.Add(cacheKey, e) + return e, nil + }) + e, _ := ei.(entry) + return e, err } // doResolveEntry fetches an entry via DNS. From effaf185234533b787926a67a6f73eb8d636e7af Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 May 2021 13:01:20 +0200 Subject: [PATCH 525/948] build: improve cross compilation setup (#22804) This PR cleans up the CI build system and fixes a couple of issues. - The go tool launcher code has been moved to internal/build. With the new toolchain functions, the environment of the host Go (i.e. the one that built ci.go) and the target Go (i.e. the toolchain downloaded by -dlgo) are isolated more strictly. This is important to make cross compilation and -dlgo work correctly in more cases. - The -dlgo option now skips the download and uses the host Go if the running Go version matches dlgoVersion exactly. - The 'test' command now supports -dlgo, -cc and -arch. Running unit tests with foreign GOARCH is occasionally useful. For example, it can be used to run 32-bit tests on Windows. It can also be used to run darwin/amd64 tests on darwin/arm64 using Rosetta 2. - The 'aar', 'xcode' and 'xgo' commands now use a slightly different method to install external tools. They previously used `go get`, but this comes with the annoying side effect of modifying go.mod. They now use `go install` instead, which is the recommended way of installing tools without modifying the local module. - The old build warning about outdated Go version has been removed because we're much better at keeping backwards compatibility now. --- Makefile | 11 +-- build/ci.go | 206 ++++++++++++--------------------------- internal/build/env.go | 3 + internal/build/gotool.go | 149 ++++++++++++++++++++++++++++ internal/build/util.go | 14 --- 5 files changed, 221 insertions(+), 162 deletions(-) create mode 100644 internal/build/gotool.go diff --git a/Makefile b/Makefile index ecee5f1894..cb5a87dad0 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ android: @echo "Import \"$(GOBIN)/geth.aar\" to use the library." @echo "Import \"$(GOBIN)/geth-sources.jar\" to add javadocs" @echo "For more info see https://stackoverflow.com/questions/20994336/android-studio-how-to-attach-javadoc" - + ios: $(GORUN) build/ci.go xcode --local @echo "Done building." @@ -46,12 +46,11 @@ clean: # You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'. devtools: - env GOBIN= go get -u golang.org/x/tools/cmd/stringer - env GOBIN= go get -u github.com/kevinburke/go-bindata/go-bindata - env GOBIN= go get -u github.com/fjl/gencodec - env GOBIN= go get -u github.com/golang/protobuf/protoc-gen-go + env GOBIN= go install golang.org/x/tools/cmd/stringer@latest + env GOBIN= go install github.com/kevinburke/go-bindata/go-bindata@latest + env GOBIN= go install github.com/fjl/gencodec@latest + env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest env GOBIN= go install ./cmd/abigen - @type "npm" 2> /dev/null || echo 'Please install node.js and npm' @type "solc" 2> /dev/null || echo 'Please install solc' @type "protoc" 2> /dev/null || echo 'Please install protoc' diff --git a/build/ci.go b/build/ci.go index b73435c5e6..a89c6e02fd 100644 --- a/build/ci.go +++ b/build/ci.go @@ -208,58 +208,25 @@ func doInstall(cmdline []string) { cc = flag.String("cc", "", "C compiler to cross build with") ) flag.CommandLine.Parse(cmdline) - env := build.Env() - // Check local Go version. People regularly open issues about compilation - // failure with outdated Go. This should save them the trouble. - if !strings.Contains(runtime.Version(), "devel") { - // Figure out the minor version number since we can't textually compare (1.10 < 1.9) - var minor int - fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) - if minor < 13 { - log.Println("You have Go version", runtime.Version()) - log.Println("go-ethereum requires at least Go version 1.13 and cannot") - log.Println("be compiled with an earlier version. Please upgrade your Go installation.") - os.Exit(1) - } - } - - // Choose which go command we're going to use. - var gobuild *exec.Cmd - if !*dlgo { - // Default behavior: use the go version which runs ci.go right now. - gobuild = goTool("build") - } else { - // Download of Go requested. This is for build environments where the - // installed version is too old and cannot be upgraded easily. - cachedir := filepath.Join("build", "cache") - goroot := downloadGo(runtime.GOARCH, runtime.GOOS, cachedir) - gobuild = localGoTool(goroot, "build") + // Configure the toolchain. + tc := build.GoToolchain{GOARCH: *arch, CC: *cc} + if *dlgo { + csdb := build.MustLoadChecksums("build/checksums.txt") + tc.Root = build.DownloadGo(csdb, dlgoVersion) } - // Configure environment for cross build. - if *arch != "" || *arch != runtime.GOARCH { - gobuild.Env = append(gobuild.Env, "CGO_ENABLED=1") - gobuild.Env = append(gobuild.Env, "GOARCH="+*arch) - } - - // Configure C compiler. - if *cc != "" { - gobuild.Env = append(gobuild.Env, "CC="+*cc) - } else if os.Getenv("CC") != "" { - gobuild.Env = append(gobuild.Env, "CC="+os.Getenv("CC")) - } + // Configure the build. + env := build.Env() + gobuild := tc.Go("build", buildFlags(env)...) // arm64 CI builders are memory-constrained and can't handle concurrent builds, // better disable it. This check isn't the best, it should probably // check for something in env instead. - if runtime.GOARCH == "arm64" { + if env.CI && runtime.GOARCH == "arm64" { gobuild.Args = append(gobuild.Args, "-p", "1") } - // Put the default settings in. - gobuild.Args = append(gobuild.Args, buildFlags(env)...) - // We use -trimpath to avoid leaking local paths into the built executables. gobuild.Args = append(gobuild.Args, "-trimpath") @@ -301,53 +268,30 @@ func buildFlags(env build.Environment) (flags []string) { return flags } -// goTool returns the go tool. This uses the Go version which runs ci.go. -func goTool(subcmd string, args ...string) *exec.Cmd { - cmd := build.GoTool(subcmd, args...) - goToolSetEnv(cmd) - return cmd -} - -// localGoTool returns the go tool from the given GOROOT. -func localGoTool(goroot string, subcmd string, args ...string) *exec.Cmd { - gotool := filepath.Join(goroot, "bin", "go") - cmd := exec.Command(gotool, subcmd) - goToolSetEnv(cmd) - cmd.Env = append(cmd.Env, "GOROOT="+goroot) - cmd.Args = append(cmd.Args, args...) - return cmd -} - -// goToolSetEnv forwards the build environment to the go tool. -func goToolSetEnv(cmd *exec.Cmd) { - cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) - for _, e := range os.Environ() { - if strings.HasPrefix(e, "GOBIN=") || strings.HasPrefix(e, "CC=") { - continue - } - cmd.Env = append(cmd.Env, e) - } -} - // Running The Tests // // "tests" also includes static analysis tools such as vet. func doTest(cmdline []string) { - coverage := flag.Bool("coverage", false, "Whether to record code coverage") - verbose := flag.Bool("v", false, "Whether to log verbosely") + var ( + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Run tests for given architecture") + cc = flag.String("cc", "", "Sets C compiler binary") + coverage = flag.Bool("coverage", false, "Whether to record code coverage") + verbose = flag.Bool("v", false, "Whether to log verbosely") + ) flag.CommandLine.Parse(cmdline) - env := build.Env() - packages := []string{"./..."} - if len(flag.CommandLine.Args()) > 0 { - packages = flag.CommandLine.Args() + // Configure the toolchain. + tc := build.GoToolchain{GOARCH: *arch, CC: *cc} + if *dlgo { + csdb := build.MustLoadChecksums("build/checksums.txt") + tc.Root = build.DownloadGo(csdb, dlgoVersion) } + gotest := tc.Go("test") - // Run the actual tests. // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. - gotest := goTool("test", buildFlags(env)...) gotest.Args = append(gotest.Args, "-p", "1") if *coverage { gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") @@ -356,6 +300,10 @@ func doTest(cmdline []string) { gotest.Args = append(gotest.Args, "-v") } + packages := []string{"./..."} + if len(flag.CommandLine.Args()) > 0 { + packages = flag.CommandLine.Args() + } gotest.Args = append(gotest.Args, packages...) build.MustRun(gotest) } @@ -415,8 +363,7 @@ func doArchive(cmdline []string) { } var ( - env = build.Env() - + env = build.Env() basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit)) geth = "geth-" + basegeth + ext alltools = "geth-alltools-" + basegeth + ext @@ -492,15 +439,15 @@ func archiveUpload(archive string, blobstore string, signer string, signifyVar s // skips archiving for some build configurations. func maybeSkipArchive(env build.Environment) { if env.IsPullRequest { - log.Printf("skipping because this is a PR build") + log.Printf("skipping archive creation because this is a PR build") os.Exit(0) } if env.IsCronJob { - log.Printf("skipping because this is a cron job") + log.Printf("skipping archive creation because this is a cron job") os.Exit(0) } if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { - log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) + log.Printf("skipping archive creation because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) os.Exit(0) } } @@ -518,6 +465,7 @@ func doDebianSource(cmdline []string) { flag.CommandLine.Parse(cmdline) *workdir = makeWorkdir(*workdir) env := build.Env() + tc := new(build.GoToolchain) maybeSkipArchive(env) // Import the signing key. @@ -531,12 +479,12 @@ func doDebianSource(cmdline []string) { gobundle := downloadGoSources(*cachedir) // Download all the dependencies needed to build the sources and run the ci script - srcdepfetch := goTool("mod", "download") - srcdepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath")) + srcdepfetch := tc.Go("mod", "download") + srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) build.MustRun(srcdepfetch) - cidepfetch := goTool("run", "./build/ci.go") - cidepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath")) + cidepfetch := tc.Go("run", "./build/ci.go") + cidepfetch.Env = append(cidepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) cidepfetch.Run() // Command fails, don't care, we only need the deps to start it // Create Debian packages and upload them. @@ -592,41 +540,6 @@ func downloadGoSources(cachedir string) string { return dst } -// downloadGo downloads the Go binary distribution and unpacks it into a temporary -// directory. It returns the GOROOT of the unpacked toolchain. -func downloadGo(goarch, goos, cachedir string) string { - if goarch == "arm" { - goarch = "armv6l" - } - - csdb := build.MustLoadChecksums("build/checksums.txt") - file := fmt.Sprintf("go%s.%s-%s", dlgoVersion, goos, goarch) - if goos == "windows" { - file += ".zip" - } else { - file += ".tar.gz" - } - url := "https://golang.org/dl/" + file - dst := filepath.Join(cachedir, file) - if err := csdb.DownloadFile(url, dst); err != nil { - log.Fatal(err) - } - - ucache, err := os.UserCacheDir() - if err != nil { - log.Fatal(err) - } - godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", dlgoVersion, goos, goarch)) - if err := build.ExtractArchive(dst, godir); err != nil { - log.Fatal(err) - } - goroot, err := filepath.Abs(filepath.Join(godir, "go")) - if err != nil { - log.Fatal(err) - } - return goroot -} - func ppaUpload(workdir, ppa, sshUser string, files []string) { p := strings.Split(ppa, "/") if len(p) != 2 { @@ -901,13 +814,23 @@ func doAndroidArchive(cmdline []string) { ) flag.CommandLine.Parse(cmdline) env := build.Env() + tc := new(build.GoToolchain) // Sanity check that the SDK and NDK are installed and set if os.Getenv("ANDROID_HOME") == "" { log.Fatal("Please ensure ANDROID_HOME points to your Android SDK") } + + // Build gomobile. + install := tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest") + install.Env = append(install.Env) + build.MustRun(install) + + // Ensure all dependencies are available. This is required to make + // gomobile bind work because it expects go.sum to contain all checksums. + build.MustRun(tc.Go("mod", "download")) + // Build the Android archive and Maven resources - build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile")) if *local { @@ -1027,10 +950,16 @@ func doXCodeFramework(cmdline []string) { ) flag.CommandLine.Parse(cmdline) env := build.Env() + tc := new(build.GoToolchain) + + // Build gomobile. + build.MustRun(tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest")) + + // Ensure all dependencies are available. This is required to make + // gomobile bind work because it expects go.sum to contain all checksums. + build.MustRun(tc.Go("mod", "download")) // Build the iOS XCode framework - build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) - build.MustRun(gomobileTool("init")) bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/ethereum/go-ethereum/mobile") if *local { @@ -1039,17 +968,14 @@ func doXCodeFramework(cmdline []string) { build.MustRun(bind) return } + + // Create the archive. + maybeSkipArchive(env) archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit)) - if err := os.Mkdir(archive, os.ModePerm); err != nil { - log.Fatal(err) - } bind.Dir, _ = filepath.Abs(archive) build.MustRun(bind) build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) - // Skip CocoaPods deploy and Azure upload for PR builds - maybeSkipArchive(env) - // Sign and upload the framework to Azure if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil { log.Fatal(err) @@ -1115,10 +1041,10 @@ func doXgo(cmdline []string) { ) flag.CommandLine.Parse(cmdline) env := build.Env() + var tc build.GoToolchain // Make sure xgo is available for cross compilation - gogetxgo := goTool("get", "github.com/karalabe/xgo") - build.MustRun(gogetxgo) + build.MustRun(tc.Install(GOBIN, "github.com/karalabe/xgo@latest")) // If all tools building is requested, build everything the builder wants args := append(buildFlags(env), flag.Args()...) @@ -1129,27 +1055,23 @@ func doXgo(cmdline []string) { if strings.HasPrefix(res, GOBIN) { // Binary tool found, cross build it explicitly args = append(args, "./"+filepath.Join("cmd", filepath.Base(res))) - xgo := xgoTool(args) - build.MustRun(xgo) + build.MustRun(xgoTool(args)) args = args[:len(args)-1] } } return } - // Otherwise xxecute the explicit cross compilation + + // Otherwise execute the explicit cross compilation path := args[len(args)-1] args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...) - - xgo := xgoTool(args) - build.MustRun(xgo) + build.MustRun(xgoTool(args)) } func xgoTool(args []string) *exec.Cmd { cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...) cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, []string{ - "GOBIN=" + GOBIN, - }...) + cmd.Env = append(cmd.Env, []string{"GOBIN=" + GOBIN}...) return cmd } diff --git a/internal/build/env.go b/internal/build/env.go index a3017182e3..d70c0d50a4 100644 --- a/internal/build/env.go +++ b/internal/build/env.go @@ -38,6 +38,7 @@ var ( // Environment contains metadata provided by the build environment. type Environment struct { + CI bool Name string // name of the environment Repo string // name of GitHub repo Commit, Date, Branch, Tag string // Git info @@ -61,6 +62,7 @@ func Env() Environment { commit = os.Getenv("TRAVIS_COMMIT") } return Environment{ + CI: true, Name: "travis", Repo: os.Getenv("TRAVIS_REPO_SLUG"), Commit: commit, @@ -77,6 +79,7 @@ func Env() Environment { commit = os.Getenv("APPVEYOR_REPO_COMMIT") } return Environment{ + CI: true, Name: "appveyor", Repo: os.Getenv("APPVEYOR_REPO_NAME"), Commit: commit, diff --git a/internal/build/gotool.go b/internal/build/gotool.go new file mode 100644 index 0000000000..e644b5f695 --- /dev/null +++ b/internal/build/gotool.go @@ -0,0 +1,149 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package build + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" +) + +type GoToolchain struct { + Root string // GOROOT + + // Cross-compilation variables. These are set when running the go tool. + GOARCH string + GOOS string + CC string +} + +// Go creates an invocation of the go command. +func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd { + tool := g.goTool(command, args...) + + // Configure environment for cross build. + if g.GOARCH != "" && g.GOARCH != runtime.GOARCH { + tool.Env = append(tool.Env, "CGO_ENABLED=1") + tool.Env = append(tool.Env, "GOARCH="+g.GOARCH) + } + if g.GOOS != "" && g.GOOS != runtime.GOOS { + tool.Env = append(tool.Env, "GOOS="+g.GOOS) + } + // Configure C compiler. + if g.CC != "" { + tool.Env = append(tool.Env, "CC="+g.CC) + } else if os.Getenv("CC") != "" { + tool.Env = append(tool.Env, "CC="+os.Getenv("CC")) + } + return tool +} + +// Install creates an invocation of 'go install'. The command is configured to output +// executables to the given 'gobin' directory. +// +// This can be used to install auxiliary build tools without modifying the local go.mod and +// go.sum files. To install tools which are not required by go.mod, ensure that all module +// paths in 'args' contain a module version suffix (e.g. "...@latest"). +func (g *GoToolchain) Install(gobin string, args ...string) *exec.Cmd { + if !filepath.IsAbs(gobin) { + panic("GOBIN must be an absolute path") + } + tool := g.goTool("install") + tool.Env = append(tool.Env, "GOBIN="+gobin) + tool.Args = append(tool.Args, "-mod=readonly") + tool.Args = append(tool.Args, args...) + + // Ensure GOPATH is set because go install seems to absolutely require it. This uses + // 'go env' because it resolves the default value when GOPATH is not set in the + // environment. Ignore errors running go env and leave any complaining about GOPATH to + // the install command. + pathTool := g.goTool("env", "GOPATH") + output, _ := pathTool.Output() + tool.Env = append(tool.Env, "GOPATH="+string(output)) + return tool +} + +func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd { + if g.Root == "" { + g.Root = runtime.GOROOT() + } + tool := exec.Command(filepath.Join(g.Root, "bin", "go"), command) + tool.Args = append(tool.Args, args...) + tool.Env = append(tool.Env, "GOROOT="+g.Root) + + // Forward environment variables to the tool, but skip compiler target settings. + // TODO: what about GOARM? + skip := map[string]struct{}{"GOROOT": {}, "GOARCH": {}, "GOOS": {}, "GOBIN": {}, "CC": {}} + for _, e := range os.Environ() { + if i := strings.IndexByte(e, '='); i >= 0 { + if _, ok := skip[e[:i]]; ok { + continue + } + } + tool.Env = append(tool.Env, e) + } + return tool +} + +// DownloadGo downloads the Go binary distribution and unpacks it into a temporary +// directory. It returns the GOROOT of the unpacked toolchain. +func DownloadGo(csdb *ChecksumDB, version string) string { + // Shortcut: if the Go version that runs this script matches the + // requested version exactly, there is no need to download anything. + activeGo := strings.TrimPrefix(runtime.Version(), "go") + if activeGo == version { + log.Printf("-dlgo version matches active Go version %s, skipping download.", activeGo) + return runtime.GOROOT() + } + + ucache, err := os.UserCacheDir() + if err != nil { + log.Fatal(err) + } + + // For Arm architecture, GOARCH includes ISA version. + os := runtime.GOOS + arch := runtime.GOARCH + if arch == "arm" { + arch = "armv6l" + } + file := fmt.Sprintf("go%s.%s-%s", version, os, arch) + if os == "windows" { + file += ".zip" + } else { + file += ".tar.gz" + } + url := "https://golang.org/dl/" + file + dst := filepath.Join(ucache, file) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Fatal(err) + } + + godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", version, os, arch)) + if err := ExtractArchive(dst, godir); err != nil { + log.Fatal(err) + } + goroot, err := filepath.Abs(filepath.Join(godir, "go")) + if err != nil { + log.Fatal(err) + } + return goroot +} diff --git a/internal/build/util.go b/internal/build/util.go index 91149926f7..2bdced82ee 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -29,7 +29,6 @@ import ( "os/exec" "path" "path/filepath" - "runtime" "strings" "text/template" ) @@ -111,19 +110,6 @@ func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x } } -// GoTool returns the command that runs a go tool. This uses go from GOROOT instead of PATH -// so that go commands executed by build use the same version of Go as the 'host' that runs -// build code. e.g. -// -// /usr/lib/go-1.12.1/bin/go run build/ci.go ... -// -// runs using go 1.12.1 and invokes go 1.12.1 tools from the same GOROOT. This is also important -// because runtime.Version checks on the host should match the tools that are run. -func GoTool(tool string, args ...string) *exec.Cmd { - args = append([]string{tool}, args...) - return exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...) -} - // UploadSFTP uploads files to a remote host using the sftp command line tool. // The destination host may be specified either as [user@]host[: or as a URI in // the form sftp://[user@]host[:port]. From d107f90d1ce74c5d60c11a7171c0178bde12f14f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 May 2021 21:45:21 +0200 Subject: [PATCH 526/948] go.mod: go mod tidy (#22814) This updates go.mod for the addition of golang.org/x/sync. --- go.mod | 2 +- go.sum | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 079d40ae6f..512d541f41 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 golang.org/x/text v0.3.4 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 diff --git a/go.sum b/go.sum index 3ae80f7b9c..b6a27a2cf1 100644 --- a/go.sum +++ b/go.sum @@ -453,7 +453,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 973ad66b498546468c66ec3cc9d5aa111c1df948 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 May 2021 21:45:45 +0200 Subject: [PATCH 527/948] build: fix iOS framework build (#22813) This fixes a regression introduced in #22804. --- build/ci.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/ci.go b/build/ci.go index a89c6e02fd..d9f147ef0e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -972,6 +972,9 @@ func doXCodeFramework(cmdline []string) { // Create the archive. maybeSkipArchive(env) archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit)) + if err := os.MkdirAll(archive, 0755); err != nil { + log.Fatal(err) + } bind.Dir, _ = filepath.Abs(archive) build.MustRun(bind) build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) From 3a2b29c1ed91e1099318c3829a44926a1c2a37d9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 May 2021 22:39:09 +0200 Subject: [PATCH 528/948] appveyor.yml: upgrade to VisualStudio 2019 image (#22811) --- appveyor.yml | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 052280be15..a72163382a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,41 +1,29 @@ -os: Visual Studio 2015 - -# Clone directly into GOPATH. -clone_folder: C:\gopath\src\github.com\ethereum\go-ethereum +os: Visual Studio 2019 clone_depth: 5 version: "{branch}.{build}" environment: - global: - GO111MODULE: on - GOPATH: C:\gopath - CC: gcc.exe matrix: + # We use gcc from MSYS2 because it is the most recent compiler version available on + # AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is + # contained in PATH. - GETH_ARCH: amd64 - MSYS2_ARCH: x86_64 - MSYS2_BITS: 64 - MSYSTEM: MINGW64 - PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% + GETH_CC: C:\msys64\mingw64\bin\gcc.exe + PATH: C:\msys64\mingw64\bin;C:\Program Files (x86)\NSIS\;%PATH% - GETH_ARCH: 386 - MSYS2_ARCH: i686 - MSYS2_BITS: 32 - MSYSTEM: MINGW32 - PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% + GETH_CC: C:\msys64\mingw32\bin\gcc.exe + PATH: C:\msys64\mingw32\bin;C:\Program Files (x86)\NSIS\;%PATH% install: - - git submodule update --init - - rmdir C:\go /s /q - - appveyor DownloadFile https://dl.google.com/go/go1.16.windows-%GETH_ARCH%.zip - - 7z x go1.16.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - git submodule update --init --depth 1 - go version - - gcc --version + - "%GETH_CC% --version" build_script: - - go run build\ci.go install -dlgo + - go run build\ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC% after_build: - - go run build\ci.go archive -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - - go run build\ci.go nsis -signer WINDOWS_SIGNING_KEY -upload gethstore/builds + - go run build\ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds + - go run build\ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds test_script: - - set CGO_ENABLED=1 - - go run build\ci.go test -coverage + - go run build\ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -coverage From 41671d449f5b3a19119eef070151ec72fdbb31f8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 May 2021 12:19:51 +0200 Subject: [PATCH 529/948] build: fix windows installer build for NSIS v3.05 (#22821) With the update to a newer AppVeyor build image, creating the Windows installer no longer worked because of a string quoting error in EnvVarUpdate.nsh. This applies the fix recommended in https://stackoverflow.com/questions/62081765. --- build/nsis.envvarupdate.nsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/nsis.envvarupdate.nsh b/build/nsis.envvarupdate.nsh index 9c3ecbe337..95c2f1f639 100644 --- a/build/nsis.envvarupdate.nsh +++ b/build/nsis.envvarupdate.nsh @@ -43,7 +43,7 @@ !ifndef Un${StrFuncName}_INCLUDED ${Un${StrFuncName}} !endif - !define un.${StrFuncName} "${Un${StrFuncName}}" + !define un.${StrFuncName} '${Un${StrFuncName}}' !macroend !insertmacro _IncludeStrFunction StrTok From 0f3a1e7f9b069c7dcc934238bd75ace72c166f36 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 5 May 2021 12:27:27 +0200 Subject: [PATCH 530/948] cmd/devp2p/internal/ethtest: send simultaneous requests on one connection (#22801) This changes the SimultaneousRequests test to send the requests from the same connection, as it doesn't really make sense to test whether a node can respond to two requests with different request IDs from separate connections. --- cmd/devp2p/internal/ethtest/eth66_suite.go | 41 +++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go index d4890d8de6..903a90c7eb 100644 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -91,9 +91,8 @@ func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { // headers per request. func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { // create two connections - conn1, conn2 := s.setupConnection66(t), s.setupConnection66(t) - defer conn1.Close() - defer conn2.Close() + conn := s.setupConnection66(t) + defer conn.Close() // create two requests req1 := ð.GetBlockHeadersPacket66{ RequestId: 111, @@ -117,27 +116,29 @@ func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { Reverse: false, }, } - // wait for headers for first request - headerChan := make(chan BlockHeaders, 1) - go func(headers chan BlockHeaders) { - recvHeaders, err := s.getBlockHeaders66(conn1, req1, req1.RequestId) - if err != nil { - t.Fatalf("could not get block headers: %v", err) - return - } - headers <- recvHeaders - }(headerChan) - // check headers of second request - headers1, err := s.getBlockHeaders66(conn2, req2, req2.RequestId) + // write first request + if err := conn.write66(req1, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // write second request + if err := conn.write66(req2, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // wait for responses + headers1, err := s.waitForBlockHeadersResponse66(conn, req1.RequestId) if err != nil { - t.Fatalf("could not get block headers: %v", err) + t.Fatalf("error while waiting for block headers: %v", err) + } + headers2, err := s.waitForBlockHeadersResponse66(conn, req2.RequestId) + if err != nil { + t.Fatalf("error while waiting for block headers: %v", err) } + // check headers of both responses if !headersMatch(t, s.chain, headers1) { - t.Fatal("wrong header(s) in response to req2") + t.Fatalf("wrong header(s) in response to req1: got %v", headers1) } - // check headers of first request - if !headersMatch(t, s.chain, <-headerChan) { - t.Fatal("wrong header(s) in response to req1") + if !headersMatch(t, s.chain, headers2) { + t.Fatalf("wrong header(s) in response to req2: got %v", headers2) } } From 991384a7f6719e1125ca0be7fb27d0c4d1c5d2d3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 May 2021 13:20:06 +0200 Subject: [PATCH 531/948] params: go-ethereum v1.10.3 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index d350c5ff4a..1c7bf8d88e 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 3 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 3 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 37b5595456e7049e3ed487c41564281de52e00ab Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 5 May 2021 13:21:13 +0200 Subject: [PATCH 532/948] params: begin v1.10.4 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 1c7bf8d88e..89969705dc 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 3 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 4 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From df20b3b98286c9333c143c0a873d09e27b0e0693 Mon Sep 17 00:00:00 2001 From: Evgeny Danilenko <6655321@bk.ru> Date: Thu, 6 May 2021 11:46:27 +0300 Subject: [PATCH 533/948] core/vm: avoid duplicate log in json logger (#22825) --- core/vm/logger_json.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index e54be08596..93878b9806 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -87,8 +87,9 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, Time time.Duration `json:"time"` Err string `json:"error,omitempty"` } + var errMsg string if err != nil { - l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()}) + errMsg = err.Error() } - l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""}) + l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg}) } From cc606be74c6f1f05b0b0a6226a400e734b9aac31 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 6 May 2021 11:07:42 +0200 Subject: [PATCH 534/948] all: define London+baikal, undefine yolov3, add london override flag (#22822) * all: define London+baikal, undefine yolov3, add london override flag * cmd, core, params: add baikal genesis definition --- cmd/geth/chaincmd.go | 2 +- cmd/geth/config.go | 4 ++-- cmd/geth/consolecmd.go | 4 ++-- cmd/geth/dbcmd.go | 16 +++++++------- cmd/geth/main.go | 8 +++---- cmd/geth/usage.go | 2 +- cmd/puppeth/wizard_genesis.go | 4 ++-- cmd/utils/flags.go | 36 +++++++++++++++---------------- core/blockchain_test.go | 2 +- core/genesis.go | 20 ++++++++--------- core/genesis_alloc.go | 2 +- core/genesis_test.go | 4 ++-- core/state/statedb.go | 2 +- core/types/transaction_signing.go | 2 +- core/vm/runtime/runtime.go | 2 +- eth/backend.go | 2 +- eth/ethconfig/config.go | 2 +- les/client.go | 2 +- params/bootnodes.go | 10 ++++----- params/config.go | 34 +++++++++++++++++------------ tests/init.go | 22 ++++++++++++++----- 21 files changed, 100 insertions(+), 82 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 3cae32aa9e..d00b4bc1f6 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -63,7 +63,7 @@ It expects the genesis file as argument.`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` diff --git a/cmd/geth/config.go b/cmd/geth/config.go index c867877ee6..c4ebf64881 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -141,8 +141,8 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // makeFullNode loads geth configuration and creates the Ethereum backend. func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) - if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) { - cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name)) + if ctx.GlobalIsSet(utils.OverrideLondonFlag.Name) { + cfg.Eth.OverrideLondon = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideLondonFlag.Name)) } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 9d8794eb15..5c715a1250 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -134,8 +134,8 @@ func remoteConsole(ctx *cli.Context) error { path = filepath.Join(path, "rinkeby") } else if ctx.GlobalBool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") - } else if ctx.GlobalBool(utils.YoloV3Flag.Name) { - path = filepath.Join(path, "yolo-v3") + } else if ctx.GlobalBool(utils.BaikalFlag.Name) { + path = filepath.Join(path, "baikal") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 4c70373e9a..2cd481b706 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -75,7 +75,7 @@ Remove blockchain and state databases`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Usage: "Inspect the storage size for each type of data in the database", Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, @@ -91,7 +91,7 @@ Remove blockchain and state databases`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, } dbCompactCmd = cli.Command{ @@ -105,7 +105,7 @@ Remove blockchain and state databases`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, utils.CacheFlag, utils.CacheDatabaseFlag, }, @@ -125,7 +125,7 @@ corruption if it is aborted during execution'!`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Description: "This command looks up the specified database key from the database.", } @@ -141,7 +141,7 @@ corruption if it is aborted during execution'!`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Description: `This command deletes the specified database key from the database. WARNING: This is a low-level operation which may cause database corruption!`, @@ -158,7 +158,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Description: `This command sets a given database key to the given value. WARNING: This is a low-level operation which may cause database corruption!`, @@ -175,7 +175,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Description: "This command looks up the specified database key from the database.", } @@ -191,7 +191,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, }, Description: "This command displays information about the freezer index.", } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 78e65161da..bed4d8a488 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -66,7 +66,7 @@ var ( utils.NoUSBFlag, utils.USBFlag, utils.SmartCardDaemonPathFlag, - utils.OverrideBerlinFlag, + utils.OverrideLondonFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, @@ -138,7 +138,7 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV3Flag, + utils.BaikalFlag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, @@ -275,8 +275,8 @@ func prepare(ctx *cli.Context) { case ctx.GlobalIsSet(utils.GoerliFlag.Name): log.Info("Starting Geth on Görli testnet...") - case ctx.GlobalIsSet(utils.YoloV3Flag.Name): - log.Info("Starting Geth on YOLOv3 testnet...") + case ctx.GlobalIsSet(utils.BaikalFlag.Name): + log.Info("Starting Geth on Baikal testnet...") case ctx.GlobalIsSet(utils.DeveloperFlag.Name): log.Info("Starting Geth in ephemeral dev mode...") diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 980794db73..aa0a4b2901 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -44,7 +44,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, - utils.YoloV3Flag, + utils.BaikalFlag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 4f701fa1c3..ae5977b372 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -240,8 +240,8 @@ func (w *wizard) manageGenesis() { w.conf.Genesis.Config.BerlinBlock = w.readDefaultBigInt(w.conf.Genesis.Config.BerlinBlock) fmt.Println() - fmt.Printf("Which block should YOLOv3 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV3Block) - w.conf.Genesis.Config.YoloV3Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV3Block) + fmt.Printf("Which block should London come into effect? (default = %v)\n", w.conf.Genesis.Config.LondonBlock) + w.conf.Genesis.Config.LondonBlock = w.readDefaultBigInt(w.conf.Genesis.Config.LondonBlock) out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ") fmt.Printf("Chain configuration updated:\n\n%s\n", out) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a81188342f..aa00f96c92 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -151,9 +151,9 @@ var ( Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", } - YoloV3Flag = cli.BoolFlag{ - Name: "yolov3", - Usage: "YOLOv3 network: pre-configured proof-of-authority shortlived test network.", + BaikalFlag = cli.BoolFlag{ + Name: "baikal", + Usage: "Bailkal network: pre-configured proof-of-authority shortlived test network.", } RinkebyFlag = cli.BoolFlag{ Name: "rinkeby", @@ -233,9 +233,9 @@ var ( Usage: "Megabytes of memory allocated to bloom-filter for pruning", Value: 2048, } - OverrideBerlinFlag = cli.Uint64Flag{ - Name: "override.berlin", - Usage: "Manually specify Berlin fork-block, overriding the bundled setting", + OverrideLondonFlag = cli.Uint64Flag{ + Name: "override.london", + Usage: "Manually specify London fork-block, overriding the bundled setting", } // Light server and client settings LightServeFlag = cli.IntFlag{ @@ -778,8 +778,8 @@ func MakeDataDir(ctx *cli.Context) string { if ctx.GlobalBool(GoerliFlag.Name) { return filepath.Join(path, "goerli") } - if ctx.GlobalBool(YoloV3Flag.Name) { - return filepath.Join(path, "yolo-v3") + if ctx.GlobalBool(BaikalFlag.Name) { + return filepath.Join(path, "baikal") } return path } @@ -833,8 +833,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls = params.RinkebyBootnodes case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV3Flag.Name): - urls = params.YoloV3Bootnodes + case ctx.GlobalBool(BaikalFlag.Name): + urls = params.BaikalBootnodes case cfg.BootstrapNodes != nil: return // already set, don't apply defaults. } @@ -1275,8 +1275,8 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") - case ctx.GlobalBool(YoloV3Flag.Name) && cfg.DataDir == node.DefaultDataDir(): - cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v3") + case ctx.GlobalBool(BaikalFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "baikal") } } @@ -1460,7 +1460,7 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, BaikalFlag) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { @@ -1620,11 +1620,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.Genesis = core.DefaultGoerliGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) - case ctx.GlobalBool(YoloV3Flag.Name): + case ctx.GlobalBool(BaikalFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3x")).Uint64() // "yolov3x" + cfg.NetworkId = 1642 // https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/client-integration-testnets/baikal.md } - cfg.Genesis = core.DefaultYoloV3GenesisBlock() + cfg.Genesis = core.DefaultBaikalGenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 @@ -1813,8 +1813,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { genesis = core.DefaultRinkebyGenesisBlock() case ctx.GlobalBool(GoerliFlag.Name): genesis = core.DefaultGoerliGenesisBlock() - case ctx.GlobalBool(YoloV3Flag.Name): - genesis = core.DefaultYoloV3GenesisBlock() + case ctx.GlobalBool(BaikalFlag.Name): + genesis = core.DefaultBaikalGenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): Fatalf("Developer chains are ephemeral") } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 5004abd1c7..75ef399c9a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3049,7 +3049,7 @@ func TestEIP2718Transition(t *testing.T) { address = crypto.PubkeyToAddress(key.PublicKey) funds = big.NewInt(1000000000) gspec = &Genesis{ - Config: params.YoloV3ChainConfig, + Config: params.TestChainConfig, Alloc: GenesisAlloc{ address: {Balance: funds}, // The address 0xAAAA sloads 0x00 and 0x01 diff --git a/core/genesis.go b/core/genesis.go index e05e27fe17..9ae718beb6 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -156,7 +156,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig return SetupGenesisBlockWithOverride(db, genesis, nil) } -func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideBerlin *big.Int) (*params.ChainConfig, common.Hash, error) { +func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideLondon *big.Int) (*params.ChainConfig, common.Hash, error) { if genesis != nil && genesis.Config == nil { return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } @@ -202,8 +202,8 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override } // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) - if overrideBerlin != nil { - newcfg.BerlinBlock = overrideBerlin + if overrideLondon != nil { + newcfg.LondonBlock = overrideLondon } if err := newcfg.CheckConfigForkOrder(); err != nil { return newcfg, common.Hash{}, err @@ -246,8 +246,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig - case ghash == params.YoloV3GenesisHash: - return params.YoloV3ChainConfig + case ghash == params.BaikalGenesisHash: + return params.BaikalChainConfig default: return params.AllEthashProtocolChanges } @@ -383,15 +383,15 @@ func DefaultGoerliGenesisBlock() *Genesis { } } -func DefaultYoloV3GenesisBlock() *Genesis { +func DefaultBaikalGenesisBlock() *Genesis { // Full genesis: https://gist.github.com/holiman/c6ed9269dce28304ad176314caa75e97 return &Genesis{ - Config: params.YoloV3ChainConfig, - Timestamp: 0x6027dd2e, - ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001041afbcb359d5a8dc58c15b2ff51354ff8a217d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + Config: params.BaikalChainConfig, + Timestamp: 0x6092ca7f, + ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000005211cea3870c7ba7c6c44b185e62eecdb864cd8c560228ce57d31efbf64c200b2c200aacec78cf17a7148e784fe95a7a750335f8b9572ee28d72e7650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), GasLimit: 0x47b760, Difficulty: big.NewInt(1), - Alloc: decodePrealloc(yoloV3AllocData), + Alloc: decodePrealloc(baikalAllocData), } } diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 5b0e933d7a..0828d1067e 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -25,4 +25,4 @@ const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908' const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" -const yoloV3AllocData = "\xf9\x05o\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x10A\xaf\xbc\xb3Y\u0568\xdcX\xc1[/\xf5\x13T\xff\x8a!}\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xdf\n\x88\xb2\xb6\x8cg7\x13\xa8\xec\x82`\x03go'.5s\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +const baikalAllocData = "\xf9\x05\x01\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/core/genesis_test.go b/core/genesis_test.go index 44c1ef253a..eec4c171e8 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -186,8 +186,8 @@ func TestGenesisHashes(t *testing.T) { hash: params.RinkebyGenesisHash, }, { - genesis: DefaultYoloV3GenesisBlock(), - hash: params.YoloV3GenesisHash, + genesis: DefaultBaikalGenesisBlock(), + hash: params.BaikalGenesisHash, }, } for i, c := range cases { diff --git a/core/state/statedb.go b/core/state/statedb.go index 90f4709bfc..203556c6b7 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -991,7 +991,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { // - Add precompiles to access list (2929) // - Add the contents of the optional tx access list (2930) // -// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. +// This method should only be called if Berlin/2929+2930 is applicable at the current number. func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { s.AddAddressToAccessList(sender) if dst != nil { diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 126748efeb..5d94b26b36 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -61,7 +61,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.ChainConfig) Signer { if config.ChainID != nil { - if config.BerlinBlock != nil || config.YoloV3Block != nil { + if config.BerlinBlock != nil { return NewEIP2930Signer(config.ChainID) } if config.EIP155Block != nil { diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 72601441d5..be612fb0ef 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -66,7 +66,7 @@ func setDefaults(cfg *Config) { IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), BerlinBlock: new(big.Int), - YoloV3Block: nil, + LondonBlock: nil, } } diff --git a/eth/backend.go b/eth/backend.go index 7d8b0c52c6..3e770fe83d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -131,7 +131,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideLondon) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 0c6eb0bdd7..f5629e6a39 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -200,7 +200,7 @@ type Config struct { CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` // Berlin block override (TODO: remove after the fork) - OverrideBerlin *big.Int `toml:",omitempty"` + OverrideLondon *big.Int `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain configuration. diff --git a/les/client.go b/les/client.go index 7534eb3ea0..1d8a2c6f9a 100644 --- a/les/client.go +++ b/les/client.go @@ -88,7 +88,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideLondon) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr } diff --git a/params/bootnodes.go b/params/bootnodes.go index f36ad61729..20bf0b7cbf 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -67,11 +67,11 @@ var GoerliBootnodes = []string{ "enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303", } -// YoloV3Bootnodes are the enode URLs of the P2P bootstrap nodes running on the -// YOLOv3 ephemeral test network. -// TODO: Set Yolov3 bootnodes -var YoloV3Bootnodes = []string{ - "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", +// BaikalBootnodes are the enode URLs of the P2P bootstrap nodes running on the +// Baikal ephemeral test network. +// TODO: Set Baikal bootnodes +var BaikalBootnodes = []string{ + "", } var V5Bootnodes = []string{ diff --git a/params/config.go b/params/config.go index eb80bb2e27..2cafa0e449 100644 --- a/params/config.go +++ b/params/config.go @@ -31,7 +31,7 @@ var ( RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") - YoloV3GenesisHash = common.HexToHash("0xf1f2876e8500c77afcc03228757b39477eceffccf645b734967fe3c7e16967b7") + BaikalGenesisHash = common.HexToHash("0xa0bc5d43c72a990cedeb59d305702602b34c3ee8585e77d03c7a4fa64d79636e") ) // TrustedCheckpoints associates each known checkpoint with the genesis hash of @@ -217,9 +217,8 @@ var ( Threshold: 2, } - // YoloV3ChainConfig contains the chain parameters to run a node on the YOLOv3 test network. - YoloV3ChainConfig = &ChainConfig{ - ChainID: new(big.Int).SetBytes([]byte("yolov3x")), + BaikalChainConfig = &ChainConfig{ + ChainID: big.NewInt(1642), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, @@ -231,10 +230,10 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, - BerlinBlock: nil, // Don't enable Berlin directly, we're YOLOing it - YoloV3Block: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), Clique: &CliqueConfig{ - Period: 15, + Period: 30, Epoch: 30000, }, } @@ -331,8 +330,8 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin) + LondonBlock *big.Int `json:"londonBlock,omitempty"` // London switch block (nil = no fork, 0 = already on london) - YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) CatalystBlock *big.Int `json:"catalystBlock,omitempty"` // Catalyst switch block (nil = no fork, 0 = already on catalyst) @@ -371,7 +370,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, YOLO v3: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -385,7 +384,7 @@ func (c *ChainConfig) String() string { c.IstanbulBlock, c.MuirGlacierBlock, c.BerlinBlock, - c.YoloV3Block, + c.LondonBlock, engine, ) } @@ -444,7 +443,12 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { // IsBerlin returns whether num is either equal to the Berlin fork block or greater. func (c *ChainConfig) IsBerlin(num *big.Int) bool { - return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num) + return isForked(c.BerlinBlock, num) +} + +// IsLondon returns whether num is either equal to the London fork block or greater. +func (c *ChainConfig) IsLondon(num *big.Int) bool { + return isForked(c.LondonBlock, num) } // IsCatalyst returns whether num is either equal to the Merge fork block or greater. @@ -496,6 +500,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, {name: "berlinBlock", block: c.BerlinBlock}, + {name: "londonBlock", block: c.LondonBlock}, } { if lastFork.name != "" { // Next one must be higher number @@ -562,8 +567,8 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) } - if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) { - return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block) + if isForkIncompatible(c.LondonBlock, newcfg.LondonBlock, head) { + return newCompatError("London fork block", c.LondonBlock, newcfg.LondonBlock) } if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) @@ -635,7 +640,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsBerlin, IsCatalyst bool + IsBerlin, IsLondon, IsCatalyst bool } // Rules ensures c's ChainID is not nil. @@ -655,6 +660,7 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsBerlin: c.IsBerlin(num), + IsLondon: c.IsLondon(num), IsCatalyst: c.IsCatalyst(num), } } diff --git a/tests/init.go b/tests/init.go index 67f706eb50..240b7159d5 100644 --- a/tests/init.go +++ b/tests/init.go @@ -141,7 +141,7 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(5), }, - "YOLOv3": { + "Berlin": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -151,11 +151,22 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV3Block: big.NewInt(0), + BerlinBlock: big.NewInt(0), }, - // This specification is subject to change, but is for now identical to YOLOv3 - // for cross-client testing purposes - "Berlin": { + "BerlinToLondonAt5": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(5), + }, + "London": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -166,6 +177,7 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), }, } From e69130d9f15d85c5955e1fa94abc74d606accba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 29 Apr 2021 11:46:03 +0200 Subject: [PATCH 535/948] core/vm, params: implement EIP 3541 --- core/vm/errors.go | 1 + core/vm/evm.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/core/vm/errors.go b/core/vm/errors.go index c813aa36af..c7cfeae53c 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -34,6 +34,7 @@ var ( ErrWriteProtection = errors.New("write protection") ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/evm.go b/core/vm/evm.go index bd54e855c6..8e3c9fe00f 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -468,6 +468,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, err = ErrMaxCodeSizeExceeded } + // Reject code starting with 0xEF if EIP-3541 is enabled. + if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { + err = ErrInvalidCode + } + // if the contract creation ran successfully and no errors were returned // calculate the gas required to store the code. If the code could not // be stored due to not enough gas set an error and let it be handled From a5669ae292274d3b0a223cda07ebe64f943bded4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 7 May 2021 08:25:32 +0200 Subject: [PATCH 536/948] core, params: implement EIP-3529 (#22733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * core, params: implement EIP-3529 * core/vm: add london instructionset * core/vm: add method doc for EIP enabler Co-authored-by: Péter Szilágyi --- core/blockchain_test.go | 3 +- core/state_transition.go | 15 ++- core/vm/eips.go | 26 +++-- core/vm/interpreter.go | 2 + core/vm/jump_table.go | 9 ++ core/vm/operations_acl.go | 210 ++++++++++++++++++++------------------ params/protocol_params.go | 15 +++ 7 files changed, 166 insertions(+), 114 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 75ef399c9a..a8ce4c7b9a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3102,7 +3102,8 @@ func TestEIP2718Transition(t *testing.T) { block := chain.GetBlockByNumber(1) // Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list - expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + vm.GasQuickStep*2 + vm.WarmStorageReadCostEIP2929 + vm.ColdSloadCostEIP2929 + expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + + vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929 if block.GasUsed() != expected { t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed()) diff --git a/core/state_transition.go b/core/state_transition.go index cdffc100a1..05becd9a00 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -241,6 +241,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { sender := vm.AccountRef(msg.From()) homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) + eip3529 := st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) contractCreation := msg.To() == nil // Check clauses 4-5, subtract intrinsic gas if everything is correct @@ -273,7 +274,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } - st.refundGas() + if !eip3529 { + // Before EIP-3529: refunds were capped to gasUsed / 2 + st.refundGas(params.RefundQuotient) + } else { + // After EIP-3529: refunds are capped to gasUsed / 5 + st.refundGas(params.RefundQuotientEIP3529) + } st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) return &ExecutionResult{ @@ -283,9 +290,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { }, nil } -func (st *StateTransition) refundGas() { - // Apply refund counter, capped to half of the used gas. - refund := st.gasUsed() / 2 +func (st *StateTransition) refundGas(refundQuotient uint64) { + // Apply refund counter, capped to a refund quotient + refund := st.gasUsed() / refundQuotient if refund > st.state.GetRefund() { refund = st.state.GetRefund() } diff --git a/core/vm/eips.go b/core/vm/eips.go index 6bb941d5f9..025502760b 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -25,6 +25,7 @@ import ( ) var activators = map[int]func(*JumpTable){ + 3529: enable3529, 2929: enable2929, 2200: enable2200, 1884: enable1884, @@ -115,28 +116,28 @@ func enable2929(jt *JumpTable) { jt[SLOAD].constantGas = 0 jt[SLOAD].dynamicGas = gasSLoadEIP2929 - jt[EXTCODECOPY].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929 jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 - jt[EXTCODESIZE].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929 jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck - jt[EXTCODEHASH].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929 jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck - jt[BALANCE].constantGas = WarmStorageReadCostEIP2929 + jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929 jt[BALANCE].dynamicGas = gasEip2929AccountCheck - jt[CALL].constantGas = WarmStorageReadCostEIP2929 + jt[CALL].constantGas = params.WarmStorageReadCostEIP2929 jt[CALL].dynamicGas = gasCallEIP2929 - jt[CALLCODE].constantGas = WarmStorageReadCostEIP2929 + jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929 jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 - jt[STATICCALL].constantGas = WarmStorageReadCostEIP2929 + jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929 jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 - jt[DELEGATECALL].constantGas = WarmStorageReadCostEIP2929 + jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929 jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 // This was previously part of the dynamic cost, but we're using it as a constantGas @@ -144,3 +145,12 @@ func enable2929(jt *JumpTable) { jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 } + +// enable3529 enabled "EIP-3529: Reduction in refunds": +// - Removes refunds for selfdestructs +// - Reduces refunds for SSTORE +// - Reduces max refunds to 20% gas +func enable3529(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP3529 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529 +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 1022c355c1..8b755038fd 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -98,6 +98,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { + case evm.chainRules.IsLondon: + jt = londonInstructionSet case evm.chainRules.IsBerlin: jt = berlinInstructionSet case evm.chainRules.IsIstanbul: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index bb1800ea91..a0609a0d78 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -57,11 +57,20 @@ var ( constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() berlinInstructionSet = newBerlinInstructionSet() + londonInstructionSet = newLondonInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation +// newLondonInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg, berlin and london instructions. +func newLondonInstructionSet() JumpTable { + instructionSet := newBerlinInstructionSet() + enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 + return instructionSet +} + // newBerlinInstructionSet returns the frontier, homestead, byzantium, // contantinople, istanbul, petersburg and berlin instructions. func newBerlinInstructionSet() JumpTable { diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index c56941899e..483226eefa 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -24,91 +24,75 @@ import ( "github.com/ethereum/go-ethereum/params" ) -const ( - ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST - ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST - WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST -) - -// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 -// -// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. -// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. -// Additionally, modify the parameters defined in EIP 2200 as follows: -// -// Parameter Old value New value -// SLOAD_GAS 800 = WARM_STORAGE_READ_COST -// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST -// -//The other parameters defined in EIP 2200 are unchanged. -// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified -func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { - return 0, errors.New("not enough gas for reentrancy sentry") - } - // Gas sentry honoured, do the actual gas calculation based on the stored value - var ( - y, x = stack.Back(1), stack.peek() - slot = common.Hash(x.Bytes32()) - current = evm.StateDB.GetState(contract.Address(), slot) - cost = uint64(0) - ) - // Check slot presence in the access list - if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { - cost = ColdSloadCostEIP2929 - // If the caller cannot afford the cost, this change will be rolled back - evm.StateDB.AddSlotToAccessList(contract.Address(), slot) - if !addrPresent { - // Once we're done with YOLOv2 and schedule this for mainnet, might - // be good to remove this panic here, which is just really a - // canary to have during testing - panic("impossible case: address was not present in access list during sstore op") +func makeGasSStoreFunc(clearingRefund uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") } - } - value := common.Hash(y.Bytes32()) + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current = evm.StateDB.GetState(contract.Address(), slot) + cost = uint64(0) + ) + // Check slot presence in the access list + if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = params.ColdSloadCostEIP2929 + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { + // Once we're done with YOLOv2 and schedule this for mainnet, might + // be good to remove this panic here, which is just really a + // canary to have during testing + panic("impossible case: address was not present in access list during sstore op") + } + } + value := common.Hash(y.Bytes32()) - if current == value { // noop (1) - // EIP 2200 original clause: - // return params.SloadGasEIP2200, nil - return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS - } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) - if original == current { - if original == (common.Hash{}) { // create slot (2.1.1) - return cost + params.SstoreSetGasEIP2200, nil + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return cost + params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(clearingRefund) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929), nil // write existing slot (2.1.2) } - // EIP-2200 original clause: - // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) - return cost + (params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929), nil // write existing slot (2.1.2) - } - if original != (common.Hash{}) { - if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) - } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(clearingRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(clearingRefund) + } } - } - if original == value { - if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - // EIP 2200 Original clause: - //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) - evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929) - } else { // reset to original existing slot (2.2.2.2) - // EIP 2200 Original clause: - // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) - // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) - // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST - // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST - evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929) - WarmStorageReadCostEIP2929) + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929) + } } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return cost + params.WarmStorageReadCostEIP2929, nil // dirty update (2.2) } - // EIP-2200 original clause: - //return params.SloadGasEIP2200, nil // dirty update (2.2) - return cost + WarmStorageReadCostEIP2929, nil // dirty update (2.2) } // gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 @@ -124,9 +108,9 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) - return ColdSloadCostEIP2929, nil + return params.ColdSloadCostEIP2929, nil } - return WarmStorageReadCostEIP2929, nil + return params.WarmStorageReadCostEIP2929, nil } // gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 @@ -146,7 +130,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo evm.StateDB.AddAddressToAccessList(addr) var overflow bool // We charge (cold-warm), since 'warm' is already charged as constantGas - if gas, overflow = math.SafeAdd(gas, ColdAccountAccessCostEIP2929-WarmStorageReadCostEIP2929); overflow { + if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow { return 0, ErrGasUintOverflow } return gas, nil @@ -168,7 +152,7 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(addr) // The warm storage read cost is already charged as constantGas - return ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, nil + return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil } return 0, nil } @@ -180,7 +164,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { warmAccess := evm.StateDB.AddressInAccessList(addr) // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // the cost to charge for cold access, if any, is Cold - Warm - coldCost := ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929 + coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 if !warmAccess { evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available @@ -212,25 +196,49 @@ var ( gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) + gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) + // gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds) + gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) + + // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 + // + // When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. + // If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. + // Additionally, modify the parameters defined in EIP 2200 as follows: + // + // Parameter Old value New value + // SLOAD_GAS 800 = WARM_STORAGE_READ_COST + // SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST + // + //The other parameters defined in EIP 2200 are unchanged. + // see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified + gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200) + + // gasSStoreEIP2539 implements gas cost for SSTORE according to EPI-2539 + // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800) + gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) ) -func gasSelfdestructEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - var ( - gas uint64 - address = common.Address(stack.peek().Bytes20()) - ) - if !evm.StateDB.AddressInAccessList(address) { - // If the caller cannot afford the cost, this change will be rolled back - evm.StateDB.AddAddressToAccessList(address) - gas = ColdAccountAccessCostEIP2929 - } - // if empty and transfers value - if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { - gas += params.CreateBySelfdestructGas - } - if !evm.StateDB.HasSuicided(contract.Address()) { - evm.StateDB.AddRefund(params.SelfdestructRefundGas) +// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539 +func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + address = common.Address(stack.peek().Bytes20()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas = params.ColdAccountAccessCostEIP2929 + } + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil } - return gas, nil - + return gasFunc } diff --git a/params/protocol_params.go b/params/protocol_params.go index 88f1a06e12..22b4c0651c 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -57,6 +57,16 @@ const ( SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot + ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST + ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST + WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST + + // In EIP-2200: SstoreResetGas was 5000. + // In EIP-2929: SstoreResetGas was changed to '5000 - COLD_SLOAD_COST'. + // In EIP-3529: SSTORE_CLEARS_SCHEDULE is defined as SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST + // Which becomes: 5000 - 2100 + 1900 = 4800 + SstoreClearsScheduleRefundEIP3529 uint64 = SstoreResetGasEIP2200 - ColdSloadCostEIP2929 + TxAccessListStorageKeyGas + JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. @@ -137,6 +147,11 @@ const ( Bls12381PairingPerPairGas uint64 = 23000 // Per-point pair gas price for BLS12-381 elliptic curve pairing check Bls12381MapG1Gas uint64 = 5500 // Gas price for BLS12-381 mapping field element to G1 operation Bls12381MapG2Gas uint64 = 110000 // Gas price for BLS12-381 mapping field element to G2 operation + + // The Refund Quotient is the cap on how much of the used gas can be refunded. Before EIP-3529, + // up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529 + RefundQuotient uint64 = 2 + RefundQuotientEIP3529 uint64 = 5 ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations From 8a070e8f7d45eca745c29d4523cc2e05ff2f117f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 7 May 2021 10:31:01 +0200 Subject: [PATCH 537/948] consensus/clique: add some missing checks (#22836) --- consensus/clique/clique.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c62e180faa..9954a023e5 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -20,6 +20,7 @@ package clique import ( "bytes" "errors" + "fmt" "io" "math/big" "math/rand" @@ -293,6 +294,15 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H return errInvalidDifficulty } } + // Verify that the gas limit is <= 2^63-1 + cap := uint64(0x7fffffffffffffff) + if header.GasLimit > cap { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) + } + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } // If all checks passed, validate any special fields for hard forks if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { return err @@ -324,6 +334,15 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header if parent.Time+c.config.Period > header.Time { return errInvalidTimestamp } + // Verify that the gas limit remains within allowed bounds + diff := int64(parent.GasLimit) - int64(header.GasLimit) + if diff < 0 { + diff *= -1 + } + limit := parent.GasLimit / params.GasLimitBoundDivisor + if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { + return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) + } // Retrieve the snapshot needed to verify this header and cache it snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) if err != nil { From 17b1be26617268504a8ef3a6ee3c5c6400bfb63a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 7 May 2021 14:04:54 +0200 Subject: [PATCH 538/948] consensus/ethash: implement EIP-3554 (bomb delay) --- consensus/ethash/consensus.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 249c2f65eb..492fc83538 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -45,6 +45,11 @@ var ( maxUncles = 2 // Maximum number of uncles allowed in a single block allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks + // calcDifficultyEip3554 is the difficulty adjustment algorithm as specified by EIP 3554. + // It offsets the bomb a total of 9.5M blocks. + // Specification EIP-3554: https://eips.ethereum.org/EIPS/eip-3554 + calcDifficultyEip3554 = makeDifficultyCalculator(big.NewInt(9500000)) + // calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384. // It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks. // Specification EIP-2384: https://eips.ethereum.org/EIPS/eip-2384 @@ -325,6 +330,8 @@ func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Heade switch { case config.IsCatalyst(next): return big.NewInt(1) + case config.IsLondon(next): + return calcDifficultyEip3554(time, parent) case config.IsMuirGlacier(next): return calcDifficultyEip2384(time, parent) case config.IsConstantinople(next): From 700df1442d714cb3c42a602c39c042ce88be463f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 7 May 2021 14:37:13 +0200 Subject: [PATCH 539/948] rlp: add support for optional struct fields (#22832) This adds support for a new struct tag "optional". Using this tag, structs used for RLP encoding/decoding can be extended in a backwards-compatible way, by adding new fields at the end. --- rlp/decode.go | 18 ++++- rlp/decode_test.go | 181 +++++++++++++++++++++++++++++++++++++++++++-- rlp/doc.go | 61 +++++++++++---- rlp/encode.go | 39 ++++++++-- rlp/encode_test.go | 22 +++++- rlp/typecache.go | 54 +++++++++++--- 6 files changed, 330 insertions(+), 45 deletions(-) diff --git a/rlp/decode.go b/rlp/decode.go index 79b7ef0626..b340aa029e 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -229,7 +229,7 @@ func decodeBigInt(s *Stream, val reflect.Value) error { i = new(big.Int) val.Set(reflect.ValueOf(i)) } - // Reject leading zero bytes + // Reject leading zero bytes. if len(b) > 0 && b[0] == 0 { return wrapStreamError(ErrCanonInt, val.Type()) } @@ -394,9 +394,16 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { if _, err := s.List(); err != nil { return wrapStreamError(err, typ) } - for _, f := range fields { + for i, f := range fields { err := f.info.decoder(s, val.Field(f.index)) if err == EOL { + if f.optional { + // The field is optional, so reaching the end of the list before + // reaching the last field is acceptable. All remaining undecoded + // fields are zeroed. + zeroFields(val, fields[i:]) + break + } return &decodeError{msg: "too few elements", typ: typ} } else if err != nil { return addErrorContext(err, "."+typ.Field(f.index).Name) @@ -407,6 +414,13 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { return dec, nil } +func zeroFields(structval reflect.Value, fields []field) { + for _, f := range fields { + fv := structval.Field(f.index) + fv.Set(reflect.Zero(fv.Type())) + } +} + // makePtrDecoder creates a decoder that decodes into the pointer's element type. func makePtrDecoder(typ reflect.Type, tag tags) (decoder, error) { etype := typ.Elem() diff --git a/rlp/decode_test.go b/rlp/decode_test.go index d94c3969b2..87a3306332 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -369,6 +369,39 @@ type intField struct { X int } +type optionalFields struct { + A uint + B uint `rlp:"optional"` + C uint `rlp:"optional"` +} + +type optionalAndTailField struct { + A uint + B uint `rlp:"optional"` + Tail []uint `rlp:"tail"` +} + +type optionalBigIntField struct { + A uint + B *big.Int `rlp:"optional"` +} + +type optionalPtrField struct { + A uint + B *[3]byte `rlp:"optional"` +} + +type optionalPtrFieldNil struct { + A uint + B *[3]byte `rlp:"optional,nil"` +} + +type ignoredField struct { + A uint + B uint `rlp:"-"` + C uint +} + var ( veryBigInt = big.NewInt(0).Add( big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), @@ -376,12 +409,6 @@ var ( ) ) -type hasIgnoredField struct { - A uint - B uint `rlp:"-"` - C uint -} - var decodeTests = []decodeTest{ // booleans {input: "01", ptr: new(bool), value: true}, @@ -551,8 +578,8 @@ var decodeTests = []decodeTest{ // struct tag "-" { input: "C20102", - ptr: new(hasIgnoredField), - value: hasIgnoredField{A: 1, C: 2}, + ptr: new(ignoredField), + value: ignoredField{A: 1, C: 2}, }, // struct tag "nilList" @@ -592,6 +619,110 @@ var decodeTests = []decodeTest{ value: nilStringSlice{X: &[]uint{3}}, }, + // struct tag "optional" + { + input: "C101", + ptr: new(optionalFields), + value: optionalFields{1, 0, 0}, + }, + { + input: "C20102", + ptr: new(optionalFields), + value: optionalFields{1, 2, 0}, + }, + { + input: "C3010203", + ptr: new(optionalFields), + value: optionalFields{1, 2, 3}, + }, + { + input: "C401020304", + ptr: new(optionalFields), + error: "rlp: input list has too many elements for rlp.optionalFields", + }, + { + input: "C101", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1}, + }, + { + input: "C20102", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}}, + }, + { + input: "C401020304", + ptr: new(optionalAndTailField), + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{3, 4}}, + }, + { + input: "C101", + ptr: new(optionalBigIntField), + value: optionalBigIntField{A: 1, B: nil}, + }, + { + input: "C20102", + ptr: new(optionalBigIntField), + value: optionalBigIntField{A: 1, B: big.NewInt(2)}, + }, + { + input: "C101", + ptr: new(optionalPtrField), + value: optionalPtrField{A: 1}, + }, + { + input: "C20180", // not accepted because "optional" doesn't enable "nil" + ptr: new(optionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B", + }, + { + input: "C20102", + ptr: new(optionalPtrField), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B", + }, + { + input: "C50183010203", + ptr: new(optionalPtrField), + value: optionalPtrField{A: 1, B: &[3]byte{1, 2, 3}}, + }, + { + input: "C101", + ptr: new(optionalPtrFieldNil), + value: optionalPtrFieldNil{A: 1}, + }, + { + input: "C20180", // accepted because "nil" tag allows empty input + ptr: new(optionalPtrFieldNil), + value: optionalPtrFieldNil{A: 1}, + }, + { + input: "C20102", + ptr: new(optionalPtrFieldNil), + error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrFieldNil).B", + }, + + // struct tag "optional" field clearing + { + input: "C101", + ptr: &optionalFields{A: 9, B: 8, C: 7}, + value: optionalFields{A: 1, B: 0, C: 0}, + }, + { + input: "C20102", + ptr: &optionalFields{A: 9, B: 8, C: 7}, + value: optionalFields{A: 1, B: 2, C: 0}, + }, + { + input: "C20102", + ptr: &optionalAndTailField{A: 9, B: 8, Tail: []uint{7, 6, 5}}, + value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}}, + }, + { + input: "C101", + ptr: &optionalPtrField{A: 9, B: &[3]byte{8, 7, 6}}, + value: optionalPtrField{A: 1}, + }, + // RawValue {input: "01", ptr: new(RawValue), value: RawValue(unhex("01"))}, {input: "82FFFF", ptr: new(RawValue), value: RawValue(unhex("82FFFF"))}, @@ -822,6 +953,40 @@ func TestDecoderFunc(t *testing.T) { x() } +// This tests the validity checks for fields with struct tag "optional". +func TestInvalidOptionalField(t *testing.T) { + type ( + invalid1 struct { + A uint `rlp:"optional"` + B uint + } + invalid2 struct { + T []uint `rlp:"tail,optional"` + } + invalid3 struct { + T []uint `rlp:"optional,tail"` + } + ) + + tests := []struct { + v interface{} + err string + }{ + {v: new(invalid1), err: `rlp: struct field rlp.invalid1.B needs "optional" tag`}, + {v: new(invalid2), err: `rlp: invalid struct tag "optional" for rlp.invalid2.T (also has "tail" tag)`}, + {v: new(invalid3), err: `rlp: invalid struct tag "tail" for rlp.invalid3.T (also has "optional" tag)`}, + } + for _, test := range tests { + err := DecodeBytes(unhex("C20102"), test.v) + if err == nil { + t.Errorf("no error for %T", test.v) + } else if err.Error() != test.err { + t.Errorf("wrong error for %T: %v", test.v, err.Error()) + } + } + +} + func ExampleDecode() { input, _ := hex.DecodeString("C90A1486666F6F626172") diff --git a/rlp/doc.go b/rlp/doc.go index 7e6ee85200..113828e39b 100644 --- a/rlp/doc.go +++ b/rlp/doc.go @@ -102,29 +102,60 @@ Signed integers, floating point numbers, maps, channels and functions cannot be Struct Tags -Package rlp honours certain struct tags: "-", "tail", "nil", "nilList" and "nilString". +As with other encoding packages, the "-" tag ignores fields. -The "-" tag ignores fields. + type StructWithIgnoredField struct{ + Ignored uint `rlp:"-"` + Field uint + } + +Go struct values encode/decode as RLP lists. There are two ways of influencing the mapping +of fields to list elements. The "tail" tag, which may only be used on the last exported +struct field, allows slurping up any excess list elements into a slice. + + type StructWithTail struct{ + Field uint + Tail []string `rlp:"tail"` + } -The "tail" tag, which may only be used on the last exported struct field, allows slurping -up any excess list elements into a slice. See examples for more details. +The "optional" tag says that the field may be omitted if it is zero-valued. If this tag is +used on a struct field, all subsequent public fields must also be declared optional. -The "nil" tag applies to pointer-typed fields and changes the decoding rules for the field -such that input values of size zero decode as a nil pointer. This tag can be useful when -decoding recursive types. +When encoding a struct with optional fields, the output RLP list contains all values up to +the last non-zero optional field. - type StructWithOptionalFoo struct { - Foo *[20]byte `rlp:"nil"` +When decoding into a struct, optional fields may be omitted from the end of the input +list. For the example below, this means input lists of one, two, or three elements are +accepted. + + type StructWithOptionalFields struct{ + Required uint + Optional1 uint `rlp:"optional"` + Optional2 uint `rlp:"optional"` + } + +The "nil", "nilList" and "nilString" tags apply to pointer-typed fields only, and change +the decoding rules for the field type. For regular pointer fields without the "nil" tag, +input values must always match the required input length exactly and the decoder does not +produce nil values. When the "nil" tag is set, input values of size zero decode as a nil +pointer. This is especially useful for recursive types. + + type StructWithNilField struct { + Field *[3]byte `rlp:"nil"` } +In the example above, Field allows two possible input sizes. For input 0xC180 (a list +containing an empty string) Field is set to nil after decoding. For input 0xC483000000 (a +list containing a 3-byte string), Field is set to a non-nil array pointer. + RLP supports two kinds of empty values: empty lists and empty strings. When using the -"nil" tag, the kind of empty value allowed for a type is chosen automatically. A struct -field whose Go type is a pointer to an unsigned integer, string, boolean or byte -array/slice expects an empty RLP string. Any other pointer field type encodes/decodes as -an empty RLP list. +"nil" tag, the kind of empty value allowed for a type is chosen automatically. A field +whose Go type is a pointer to an unsigned integer, string, boolean or byte array/slice +expects an empty RLP string. Any other pointer field type encodes/decodes as an empty RLP +list. The choice of null value can be made explicit with the "nilList" and "nilString" struct -tags. Using these tags encodes/decodes a Go nil pointer value as the kind of empty -RLP value defined by the tag. +tags. Using these tags encodes/decodes a Go nil pointer value as the empty RLP value kind +defined by the tag. */ package rlp diff --git a/rlp/encode.go b/rlp/encode.go index 77b591045d..b7e74a133f 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -546,15 +546,40 @@ func makeStructWriter(typ reflect.Type) (writer, error) { return nil, structFieldError{typ, f.index, f.info.writerErr} } } - writer := func(val reflect.Value, w *encbuf) error { - lh := w.list() - for _, f := range fields { - if err := f.info.writer(val.Field(f.index), w); err != nil { - return err + + var writer writer + firstOptionalField := firstOptionalField(fields) + if firstOptionalField == len(fields) { + // This is the writer function for structs without any optional fields. + writer = func(val reflect.Value, w *encbuf) error { + lh := w.list() + for _, f := range fields { + if err := f.info.writer(val.Field(f.index), w); err != nil { + return err + } } + w.listEnd(lh) + return nil + } + } else { + // If there are any "optional" fields, the writer needs to perform additional + // checks to determine the output list length. + writer = func(val reflect.Value, w *encbuf) error { + lastField := len(fields) - 1 + for ; lastField >= firstOptionalField; lastField-- { + if !val.Field(fields[lastField].index).IsZero() { + break + } + } + lh := w.list() + for i := 0; i <= lastField; i++ { + if err := fields[i].info.writer(val.Field(fields[i].index), w); err != nil { + return err + } + } + w.listEnd(lh) + return nil } - w.listEnd(lh) - return nil } return writer, nil } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 418ee10a35..74e8ededcb 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -257,12 +257,30 @@ var encTests = []encTest{ {val: simplestruct{A: 3, B: "foo"}, output: "C50383666F6F"}, {val: &recstruct{5, nil}, output: "C205C0"}, {val: &recstruct{5, &recstruct{4, &recstruct{3, nil}}}, output: "C605C404C203C0"}, + {val: &intField{X: 3}, error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)"}, + + // struct tag "-" + {val: &ignoredField{A: 1, B: 2, C: 3}, output: "C20103"}, + + // struct tag "tail" {val: &tailRaw{A: 1, Tail: []RawValue{unhex("02"), unhex("03")}}, output: "C3010203"}, {val: &tailRaw{A: 1, Tail: []RawValue{unhex("02")}}, output: "C20102"}, {val: &tailRaw{A: 1, Tail: []RawValue{}}, output: "C101"}, {val: &tailRaw{A: 1, Tail: nil}, output: "C101"}, - {val: &hasIgnoredField{A: 1, B: 2, C: 3}, output: "C20103"}, - {val: &intField{X: 3}, error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)"}, + + // struct tag "optional" + {val: &optionalFields{}, output: "C180"}, + {val: &optionalFields{A: 1}, output: "C101"}, + {val: &optionalFields{A: 1, B: 2}, output: "C20102"}, + {val: &optionalFields{A: 1, B: 2, C: 3}, output: "C3010203"}, + {val: &optionalFields{A: 1, B: 0, C: 3}, output: "C3018003"}, + {val: &optionalAndTailField{A: 1}, output: "C101"}, + {val: &optionalAndTailField{A: 1, B: 2}, output: "C20102"}, + {val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"}, + {val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"}, + {val: &optionalBigIntField{A: 1}, output: "C101"}, + {val: &optionalPtrField{A: 1}, output: "C101"}, + {val: &optionalPtrFieldNil{A: 1}, output: "C101"}, // nil {val: (*uint)(nil), output: "80"}, diff --git a/rlp/typecache.go b/rlp/typecache.go index 6026e1a649..3910dcf080 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -38,15 +38,16 @@ type typeinfo struct { // tags represents struct tags. type tags struct { // rlp:"nil" controls whether empty input results in a nil pointer. - nilOK bool - - // This controls whether nil pointers are encoded/decoded as empty strings - // or empty lists. + // nilKind is the kind of empty value allowed for the field. nilKind Kind + nilOK bool + + // rlp:"optional" allows for a field to be missing in the input list. + // If this is set, all subsequent fields must also be optional. + optional bool - // rlp:"tail" controls whether this field swallows additional list - // elements. It can only be set for the last field, which must be - // of slice type. + // rlp:"tail" controls whether this field swallows additional list elements. It can + // only be set for the last field, which must be of slice type. tail bool // rlp:"-" ignores fields. @@ -104,28 +105,51 @@ func cachedTypeInfo1(typ reflect.Type, tags tags) *typeinfo { } type field struct { - index int - info *typeinfo + index int + info *typeinfo + optional bool } +// structFields resolves the typeinfo of all public fields in a struct type. func structFields(typ reflect.Type) (fields []field, err error) { - lastPublic := lastPublicField(typ) + var ( + lastPublic = lastPublicField(typ) + anyOptional = false + ) for i := 0; i < typ.NumField(); i++ { if f := typ.Field(i); f.PkgPath == "" { // exported tags, err := parseStructTag(typ, i, lastPublic) if err != nil { return nil, err } + + // Skip rlp:"-" fields. if tags.ignored { continue } + // If any field has the "optional" tag, subsequent fields must also have it. + if tags.optional || tags.tail { + anyOptional = true + } else if anyOptional { + return nil, fmt.Errorf(`rlp: struct field %v.%s needs "optional" tag`, typ, f.Name) + } info := cachedTypeInfo1(f.Type, tags) - fields = append(fields, field{i, info}) + fields = append(fields, field{i, info, tags.optional}) } } return fields, nil } +// anyOptionalFields returns the index of the first field with "optional" tag. +func firstOptionalField(fields []field) int { + for i, f := range fields { + if f.optional { + return i + } + } + return len(fields) +} + type structFieldError struct { typ reflect.Type field int @@ -166,11 +190,19 @@ func parseStructTag(typ reflect.Type, fi, lastPublic int) (tags, error) { case "nilList": ts.nilKind = List } + case "optional": + ts.optional = true + if ts.tail { + return ts, structTagError{typ, f.Name, t, `also has "tail" tag`} + } case "tail": ts.tail = true if fi != lastPublic { return ts, structTagError{typ, f.Name, t, "must be on last field"} } + if ts.optional { + return ts, structTagError{typ, f.Name, t, `also has "optional" tag`} + } if f.Type.Kind() != reflect.Slice { return ts, structTagError{typ, f.Name, t, "field type is not slice"} } From 7ab7acfded9d121e39ef0dc99ba427211693e4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Kj=C3=A6rstad?= Date: Mon, 10 May 2021 12:18:42 +0200 Subject: [PATCH 540/948] build: upgrade -dlgo version to Go 1.16.4 (#22848) --- build/checksums.txt | 26 +++++++++++++------------- build/ci.go | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index 0e33e07f2c..e667b30ce6 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,18 +1,18 @@ # This file contains sha256 checksums of optional build dependencies. -b298d29de9236ca47a023e382313bcc2d2eed31dfa706b60a04103ce83a71a25 go1.16.3.src.tar.gz -6bb1cf421f8abc2a9a4e39140b7397cdae6aca3e8d36dcff39a1a77f4f1170ac go1.16.3.darwin-amd64.tar.gz -f4e96bbcd5d2d1942f5b55d9e4ab19564da4fad192012f6d7b0b9b055ba4208f go1.16.3.darwin-arm64.tar.gz -48b2d1481db756c88c18b1f064dbfc3e265ce4a775a23177ca17e25d13a24c5d go1.16.3.linux-386.tar.gz -951a3c7c6ce4e56ad883f97d9db74d3d6d80d5fec77455c6ada6c1f7ac4776d2 go1.16.3.linux-amd64.tar.gz -566b1d6f17d2bc4ad5f81486f0df44f3088c3ed47a3bec4099d8ed9939e90d5d go1.16.3.linux-arm64.tar.gz -0dae30385e3564a557dac7f12a63eedc73543e6da0f6017990e214ce8cc8797c go1.16.3.linux-armv6l.tar.gz -a3c16e1531bf9726f47911c4a9ed7cb665a6207a51c44f10ebad4db63b4bcc5a go1.16.3.windows-386.zip -a4400345135b36cb7942e52bbaf978b66814738b855eeff8de879a09fd99de7f go1.16.3.windows-amd64.zip -31ecd11d497684fa8b0f01ba784590c4c760943665fdc4fe0adaa1405c71736c go1.16.3.freebsd-386.tar.gz -ffbd920b309e62e807457b11d80e8c17fefe3ef6de423aaba4b1e270b2ca4c3d go1.16.3.freebsd-amd64.tar.gz -5eb046bbbbc7fe2591846a4303884cb5a01abb903e3e61e33459affe7874e811 go1.16.3.linux-ppc64le.tar.gz -3e8bd7bde533a73fd6fa75b5288678ef397e76c198cfb26b8ae086035383b1cf go1.16.3.linux-s390x.tar.gz +ae4f6b6e2a1677d31817984655a762074b5356da50fb58722b99104870d43503 go1.16.4.src.tar.gz +18fe94775763db3878717393b6d41371b0b45206055e49b3838328120c977d13 go1.16.4.darwin-amd64.tar.gz +cb6b972cc42e669f3585c648198cd5b6f6d7a0811d413ad64b50c02ba06ccc3a go1.16.4.darwin-arm64.tar.gz +cd1b146ef6e9006f27dd99e9687773e7fef30e8c985b7d41bff33e955a3bb53a go1.16.4.linux-386.tar.gz +7154e88f5a8047aad4b80ebace58a059e36e7e2e4eb3b383127a28c711b4ff59 go1.16.4.linux-amd64.tar.gz +8b18eb05ddda2652d69ab1b1dd1f40dd731799f43c6a58b512ad01ae5b5bba21 go1.16.4.linux-arm64.tar.gz +a53391a800ddec749ee90d38992babb27b95cfb864027350c737b9aa8e069494 go1.16.4.linux-armv6l.tar.gz +e75c0b114a09eb5499874162b208931dc260de0fedaeedac8621bf263c974605 go1.16.4.windows-386.zip +d40139b7ade8a3008e3240a6f86fe8f899a9c465c917e11dac8758af216f5eb0 go1.16.4.windows-amd64.zip +7cf2bc8a175d6d656861165bfc554f92dc78d2abf5afe5631db3579555d97409 go1.16.4.freebsd-386.tar.gz +ccdd2b76de1941b60734408fda0d750aaa69330d8a07430eed4c56bdb3502f6f go1.16.4.freebsd-amd64.tar.gz +80cfac566e344096a8df8f37bbd21f89e76a6fbe601406565d71a87a665fc125 go1.16.4.linux-ppc64le.tar.gz +d6431881b3573dc29ecc24fbeab5e5ec25d8c9273aa543769c86a1a3bbac1ddf go1.16.4.linux-s390x.tar.gz 7e9a47ab540aa3e8472fbf8120d28bed3b9d9cf625b955818e8bc69628d7187c golangci-lint-1.39.0-darwin-amd64.tar.gz 574daa2c9c299b01672a6daeb1873b5f12e413cdb6dc0e30f2ff163956778064 golangci-lint-1.39.0-darwin-arm64.tar.gz diff --git a/build/ci.go b/build/ci.go index d9f147ef0e..3752b1bed9 100644 --- a/build/ci.go +++ b/build/ci.go @@ -152,7 +152,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.16.3" + dlgoVersion = "1.16.4" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) From f19a679b09500e63b2fd134a4e829591174d9e48 Mon Sep 17 00:00:00 2001 From: Ceelog Date: Mon, 10 May 2021 18:19:32 +0800 Subject: [PATCH 541/948] cmd/geth: remove reference to monitor command (#22844) 'geth monitor' subcommand is no longer supported. --- cmd/geth/consolecmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 5c715a1250..85eabb527b 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -171,7 +171,7 @@ func remoteConsole(ctx *cli.Context) error { // dialRPC returns a RPC client which connects to the given endpoint. // The check for empty endpoint implements the defaulting logic -// for "geth attach" and "geth monitor" with no argument. +// for "geth attach" with no argument. func dialRPC(endpoint string) (*rpc.Client, error) { if endpoint == "" { endpoint = node.DefaultIPCEndpoint(clientIdentifier) From ae5fcdc67fbfa208de257487cb86271b429545df Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 10 May 2021 12:29:33 +0200 Subject: [PATCH 542/948] go.mod: upgrade to github.com/holiman/uint256 v1.2.0 (#22745) --- consensus/ethash/difficulty.go | 6 ++---- core/vm/instructions_test.go | 4 ++-- core/vm/logger_test.go | 4 ++-- go.mod | 2 +- go.sum | 2 ++ 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/consensus/ethash/difficulty.go b/consensus/ethash/difficulty.go index 59c4ac7419..66a18059c6 100644 --- a/consensus/ethash/difficulty.go +++ b/consensus/ethash/difficulty.go @@ -52,8 +52,7 @@ func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int { - num = block.number */ - pDiff := uint256.NewInt() - pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff + pDiff, _ := uint256.FromBig(parent.Difficulty) // pDiff: pdiff adjust := pDiff.Clone() adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 @@ -96,8 +95,7 @@ func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int { - num = block.number */ - pDiff := uint256.NewInt() - pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff + pDiff, _ := uint256.FromBig(parent.Difficulty) // pDiff: pdiff adjust := pDiff.Clone() adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048 diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index d17ccfab89..c881ba0aca 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -568,11 +568,11 @@ func BenchmarkOpSHA3(bench *testing.B) { env.interpreter = evmInterpreter mem.Resize(32) pc := uint64(0) - start := uint256.NewInt() + start := new(uint256.Int) bench.ResetTimer() for i := 0; i < bench.N; i++ { - stack.pushN(*uint256.NewInt().SetUint64(32), *start) + stack.pushN(*uint256.NewInt(32), *start) opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) } } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 9c936af36a..0bbfd44692 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -60,8 +60,8 @@ func TestStoreCapture(t *testing.T) { Contract: contract, } ) - scope.Stack.push(uint256.NewInt().SetUint64(1)) - scope.Stack.push(uint256.NewInt()) + scope.Stack.push(uint256.NewInt(1)) + scope.Stack.push(new(uint256.Int)) var index common.Hash logger.CaptureState(env, 0, SSTORE, 0, 0, scope, nil, 0, nil) if len(logger.storage[contract.Address()]) == 0 { diff --git a/go.mod b/go.mod index 512d541f41..9a2e66e101 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/bloomfilter/v2 v2.0.3 - github.com/holiman/uint256 v1.1.1 + github.com/holiman/uint256 v1.2.0 github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 github.com/influxdata/influxdb v1.8.3 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 diff --git a/go.sum b/go.sum index b6a27a2cf1..4cc4dbf20b 100644 --- a/go.sum +++ b/go.sum @@ -212,6 +212,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 h1:bcAj8KroPf552TScjFPIakjH2/tdIrIH8F+cc4v4SRo= github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo= From c0e201b69039b50a28ec7f8beee79306c9844cff Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 10 May 2021 12:38:54 +0200 Subject: [PATCH 543/948] eth/protocols/eth, les: avoid Raw() when decoding HashOrNumber (#22841) Getting the raw value is not necessary to decode this type, and decoding it directly from the stream is faster. --- eth/protocols/eth/protocol.go | 24 ++++++++++++------------ les/protocol.go | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 62c018ef8e..de1b0ed1ee 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -155,19 +155,19 @@ func (hn *HashOrNumber) EncodeRLP(w io.Writer) error { // DecodeRLP is a specialized decoder for HashOrNumber to decode the contents // into either a block hash or a block number. func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } + _, size, err := s.Kind() + switch { + case err != nil: + return err + case size == 32: + hn.Number = 0 + return s.Decode(&hn.Hash) + case size <= 8: + hn.Hash = common.Hash{} + return s.Decode(&hn.Number) + default: + return fmt.Errorf("invalid input size %d for origin", size) } - return err } // BlockHeadersPacket represents a block header response. diff --git a/les/protocol.go b/les/protocol.go index 07a4452f40..06db9024eb 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -307,19 +307,19 @@ func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { // DecodeRLP is a specialized decoder for hashOrNumber to decode the contents // into either a block hash or a block number. func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, _ := s.Kind() - origin, err := s.Raw() - if err == nil { - switch { - case size == 32: - err = rlp.DecodeBytes(origin, &hn.Hash) - case size <= 8: - err = rlp.DecodeBytes(origin, &hn.Number) - default: - err = fmt.Errorf("invalid input size %d for origin", size) - } + _, size, err := s.Kind() + switch { + case err != nil: + return err + case size == 32: + hn.Number = 0 + return s.Decode(&hn.Hash) + case size <= 8: + hn.Hash = common.Hash{} + return s.Decode(&hn.Number) + default: + return fmt.Errorf("invalid input size %d for origin", size) } - return err } // CodeData is the network response packet for a node data retrieval. From e536bb52ff87cfffe9b413d02dfcf80e7f265e5a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 10 May 2021 13:35:07 +0200 Subject: [PATCH 544/948] eth/protocols/snap: adapt to uint256 API changes (#22851) --- eth/protocols/snap/range.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go index dd380ff471..2627cb954b 100644 --- a/eth/protocols/snap/range.go +++ b/eth/protocols/snap/range.go @@ -42,15 +42,15 @@ func newHashRange(start common.Hash, num uint64) *hashRange { step256.SetFromBig(step) return &hashRange{ - current: uint256.NewInt().SetBytes32(start[:]), + current: new(uint256.Int).SetBytes32(start[:]), step: step256, } } // Next pushes the hash range to the next interval. func (r *hashRange) Next() bool { - next := new(uint256.Int) - if overflow := next.AddOverflow(r.current, r.step); overflow { + next, overflow := new(uint256.Int).AddOverflow(r.current, r.step) + if overflow { return false } r.current = next @@ -65,16 +65,17 @@ func (r *hashRange) Start() common.Hash { // End returns the last hash in the current interval. func (r *hashRange) End() common.Hash { // If the end overflows (non divisible range), return a shorter interval - next := new(uint256.Int) - if overflow := next.AddOverflow(r.current, r.step); overflow { + next, overflow := new(uint256.Int).AddOverflow(r.current, r.step) + if overflow { return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") } - return new(uint256.Int).Sub(next, uint256.NewInt().SetOne()).Bytes32() + return next.SubUint64(next, 1).Bytes32() } // incHash returns the next hash, in lexicographical order (a.k.a plus one) func incHash(h common.Hash) common.Hash { - a := uint256.NewInt().SetBytes32(h[:]) - a.Add(a, uint256.NewInt().SetOne()) + var a uint256.Int + a.SetBytes32(h[:]) + a.AddUint64(&a, 1) return common.Hash(a.Bytes32()) } From 643fd0efc6ebc8397311d1ca68fc71d8da247643 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 11 May 2021 10:43:35 +0200 Subject: [PATCH 545/948] core/types: remove support for legacy receipt/log storage encoding (#22852) * core/types: remove support for legacy receipt storage encoding * core/types: remove support for legacy log storage encoding --- core/types/log.go | 39 ++---------- core/types/receipt.go | 89 +-------------------------- core/types/receipt_test.go | 123 ------------------------------------- 3 files changed, 7 insertions(+), 244 deletions(-) diff --git a/core/types/log.go b/core/types/log.go index 88274e39da..87865bdb25 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -71,18 +71,6 @@ type rlpLog struct { // rlpStorageLog is the storage encoding of a log. type rlpStorageLog rlpLog -// legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields. -type legacyRlpStorageLog struct { - Address common.Address - Topics []common.Hash - Data []byte - BlockNumber uint64 - TxHash common.Hash - TxIndex uint - BlockHash common.Hash - Index uint -} - // EncodeRLP implements rlp.Encoder. func (l *Log) EncodeRLP(w io.Writer) error { return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}) @@ -115,29 +103,10 @@ func (l *LogForStorage) EncodeRLP(w io.Writer) error { // // Note some redundant fields(e.g. block number, tx hash etc) will be assembled later. func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error { - blob, err := s.Raw() - if err != nil { - return err - } var dec rlpStorageLog - err = rlp.DecodeBytes(blob, &dec) - if err == nil { - *l = LogForStorage{ - Address: dec.Address, - Topics: dec.Topics, - Data: dec.Data, - } - } else { - // Try to decode log with previous definition. - var dec legacyRlpStorageLog - err = rlp.DecodeBytes(blob, &dec) - if err == nil { - *l = LogForStorage{ - Address: dec.Address, - Topics: dec.Topics, - Data: dec.Data, - } - } + if err := s.Decode(&dec); err != nil { + return err } - return err + *l = LogForStorage{Address: dec.Address, Topics: dec.Topics, Data: dec.Data} + return nil } diff --git a/core/types/receipt.go b/core/types/receipt.go index e04259b9d8..6b519a79d2 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -97,27 +97,6 @@ type storedReceiptRLP struct { Logs []*LogForStorage } -// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4. -type v4StoredReceiptRLP struct { - PostStateOrStatus []byte - CumulativeGasUsed uint64 - TxHash common.Hash - ContractAddress common.Address - Logs []*LogForStorage - GasUsed uint64 -} - -// v3StoredReceiptRLP is the original storage encoding of a receipt including some unnecessary fields. -type v3StoredReceiptRLP struct { - PostStateOrStatus []byte - CumulativeGasUsed uint64 - Bloom Bloom - TxHash common.Hash - ContractAddress common.Address - Logs []*LogForStorage - GasUsed uint64 -} - // NewReceipt creates a barebone transaction receipt, copying the init fields. // Deprecated: create receipts using a struct literal instead. func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt { @@ -237,8 +216,7 @@ func (r *Receipt) Size() common.StorageSize { // entire content of a receipt, as opposed to only the consensus fields originally. type ReceiptForStorage Receipt -// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt -// into an RLP stream. +// EncodeRLP implements rlp.Encoder. func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { enc := &storedReceiptRLP{ PostStateOrStatus: (*Receipt)(r).statusEncoding(), @@ -251,82 +229,21 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { return rlp.Encode(w, enc) } -// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation -// fields of a receipt from an RLP stream. +// DecodeRLP implements rlp.Decoder. func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { - // Retrieve the entire receipt blob as we need to try multiple decoders - blob, err := s.Raw() - if err != nil { - return err - } - // Try decoding from the newest format for future proofness, then the older one - // for old nodes that just upgraded. V4 was an intermediate unreleased format so - // we do need to decode it, but it's not common (try last). - if err := decodeStoredReceiptRLP(r, blob); err == nil { - return nil - } - if err := decodeV3StoredReceiptRLP(r, blob); err == nil { - return nil - } - return decodeV4StoredReceiptRLP(r, blob) -} - -func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { var stored storedReceiptRLP - if err := rlp.DecodeBytes(blob, &stored); err != nil { - return err - } - if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { - return err - } - r.CumulativeGasUsed = stored.CumulativeGasUsed - r.Logs = make([]*Log, len(stored.Logs)) - for i, log := range stored.Logs { - r.Logs[i] = (*Log)(log) - } - r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) - - return nil -} - -func decodeV4StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { - var stored v4StoredReceiptRLP - if err := rlp.DecodeBytes(blob, &stored); err != nil { + if err := s.Decode(&stored); err != nil { return err } if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { return err } r.CumulativeGasUsed = stored.CumulativeGasUsed - r.TxHash = stored.TxHash - r.ContractAddress = stored.ContractAddress - r.GasUsed = stored.GasUsed r.Logs = make([]*Log, len(stored.Logs)) for i, log := range stored.Logs { r.Logs[i] = (*Log)(log) } r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) - - return nil -} - -func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { - var stored v3StoredReceiptRLP - if err := rlp.DecodeBytes(blob, &stored); err != nil { - return err - } - if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { - return err - } - r.CumulativeGasUsed = stored.CumulativeGasUsed - r.Bloom = stored.Bloom - r.TxHash = stored.TxHash - r.ContractAddress = stored.ContractAddress - r.GasUsed = stored.GasUsed - r.Logs = make([]*Log, len(stored.Logs)) - for i, log := range stored.Logs { - r.Logs[i] = (*Log)(log) - } return nil } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 22a316c237..87fc16a510 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -20,7 +20,6 @@ import ( "bytes" "math" "math/big" - "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -38,128 +37,6 @@ func TestDecodeEmptyTypedReceipt(t *testing.T) { } } -func TestLegacyReceiptDecoding(t *testing.T) { - tests := []struct { - name string - encode func(*Receipt) ([]byte, error) - }{ - { - "StoredReceiptRLP", - encodeAsStoredReceiptRLP, - }, - { - "V4StoredReceiptRLP", - encodeAsV4StoredReceiptRLP, - }, - { - "V3StoredReceiptRLP", - encodeAsV3StoredReceiptRLP, - }, - } - - tx := NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil) - receipt := &Receipt{ - Status: ReceiptStatusFailed, - CumulativeGasUsed: 1, - Logs: []*Log{ - { - Address: common.BytesToAddress([]byte{0x11}), - Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, - Data: []byte{0x01, 0x00, 0xff}, - }, - { - Address: common.BytesToAddress([]byte{0x01, 0x11}), - Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, - Data: []byte{0x01, 0x00, 0xff}, - }, - }, - TxHash: tx.Hash(), - ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), - GasUsed: 111111, - } - receipt.Bloom = CreateBloom(Receipts{receipt}) - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - enc, err := tc.encode(receipt) - if err != nil { - t.Fatalf("Error encoding receipt: %v", err) - } - var dec ReceiptForStorage - if err := rlp.DecodeBytes(enc, &dec); err != nil { - t.Fatalf("Error decoding RLP receipt: %v", err) - } - // Check whether all consensus fields are correct. - if dec.Status != receipt.Status { - t.Fatalf("Receipt status mismatch, want %v, have %v", receipt.Status, dec.Status) - } - if dec.CumulativeGasUsed != receipt.CumulativeGasUsed { - t.Fatalf("Receipt CumulativeGasUsed mismatch, want %v, have %v", receipt.CumulativeGasUsed, dec.CumulativeGasUsed) - } - if dec.Bloom != receipt.Bloom { - t.Fatalf("Bloom data mismatch, want %v, have %v", receipt.Bloom, dec.Bloom) - } - if len(dec.Logs) != len(receipt.Logs) { - t.Fatalf("Receipt log number mismatch, want %v, have %v", len(receipt.Logs), len(dec.Logs)) - } - for i := 0; i < len(dec.Logs); i++ { - if dec.Logs[i].Address != receipt.Logs[i].Address { - t.Fatalf("Receipt log %d address mismatch, want %v, have %v", i, receipt.Logs[i].Address, dec.Logs[i].Address) - } - if !reflect.DeepEqual(dec.Logs[i].Topics, receipt.Logs[i].Topics) { - t.Fatalf("Receipt log %d topics mismatch, want %v, have %v", i, receipt.Logs[i].Topics, dec.Logs[i].Topics) - } - if !bytes.Equal(dec.Logs[i].Data, receipt.Logs[i].Data) { - t.Fatalf("Receipt log %d data mismatch, want %v, have %v", i, receipt.Logs[i].Data, dec.Logs[i].Data) - } - } - }) - } -} - -func encodeAsStoredReceiptRLP(want *Receipt) ([]byte, error) { - stored := &storedReceiptRLP{ - PostStateOrStatus: want.statusEncoding(), - CumulativeGasUsed: want.CumulativeGasUsed, - Logs: make([]*LogForStorage, len(want.Logs)), - } - for i, log := range want.Logs { - stored.Logs[i] = (*LogForStorage)(log) - } - return rlp.EncodeToBytes(stored) -} - -func encodeAsV4StoredReceiptRLP(want *Receipt) ([]byte, error) { - stored := &v4StoredReceiptRLP{ - PostStateOrStatus: want.statusEncoding(), - CumulativeGasUsed: want.CumulativeGasUsed, - TxHash: want.TxHash, - ContractAddress: want.ContractAddress, - Logs: make([]*LogForStorage, len(want.Logs)), - GasUsed: want.GasUsed, - } - for i, log := range want.Logs { - stored.Logs[i] = (*LogForStorage)(log) - } - return rlp.EncodeToBytes(stored) -} - -func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) { - stored := &v3StoredReceiptRLP{ - PostStateOrStatus: want.statusEncoding(), - CumulativeGasUsed: want.CumulativeGasUsed, - Bloom: want.Bloom, - TxHash: want.TxHash, - ContractAddress: want.ContractAddress, - Logs: make([]*LogForStorage, len(want.Logs)), - GasUsed: want.GasUsed, - } - for i, log := range want.Logs { - stored.Logs[i] = (*LogForStorage)(log) - } - return rlp.EncodeToBytes(stored) -} - // Tests that receipt data can be correctly derived from the contextual infos func TestDeriveFields(t *testing.T) { // Create a few transactions to have receipts for From ca9808079844c55961cad6ddd9df04fbe21ff78c Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Tue, 11 May 2021 02:25:51 -0700 Subject: [PATCH 546/948] cmd/geth, eth/gasprice: add configurable threshold to gas price oracle (#22752) This adds a cmd line parameter `--gpo.ignoreprice`, to make the gas price oracle ignore transactions below the given threshold. --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 8 ++++++++ eth/ethconfig/config.go | 14 ++++++++------ eth/gasprice/gasprice.go | 39 +++++++++++++++++++++++++-------------- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index bed4d8a488..04274d75f7 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -147,6 +147,7 @@ var ( utils.GpoBlocksFlag, utils.GpoPercentileFlag, utils.GpoMaxGasPriceFlag, + utils.GpoIgnoreGasPriceFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, utils.MinerNotifyFullFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index aa0a4b2901..117f6dc02b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -196,6 +196,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.GpoBlocksFlag, utils.GpoPercentileFlag, utils.GpoMaxGasPriceFlag, + utils.GpoIgnoreGasPriceFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index aa00f96c92..5c0bba4655 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -687,6 +687,11 @@ var ( Usage: "Maximum gas price will be recommended by gpo", Value: ethconfig.Defaults.GPO.MaxPrice.Int64(), } + GpoIgnoreGasPriceFlag = cli.Int64Flag{ + Name: "gpo.ignoreprice", + Usage: "Gas price below which gpo will ignore transactions", + Value: ethconfig.Defaults.GPO.IgnorePrice.Int64(), + } // Metrics flags MetricsEnabledFlag = cli.BoolFlag{ @@ -1296,6 +1301,9 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { if ctx.GlobalIsSet(GpoMaxGasPriceFlag.Name) { cfg.MaxPrice = big.NewInt(ctx.GlobalInt64(GpoMaxGasPriceFlag.Name)) } + if ctx.GlobalIsSet(GpoIgnoreGasPriceFlag.Name) { + cfg.IgnorePrice = big.NewInt(ctx.GlobalInt64(GpoIgnoreGasPriceFlag.Name)) + } } func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index f5629e6a39..f297019ba5 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -41,16 +41,18 @@ import ( // FullNodeGPO contains default gasprice oracle settings for full node. var FullNodeGPO = gasprice.Config{ - Blocks: 20, - Percentile: 60, - MaxPrice: gasprice.DefaultMaxPrice, + Blocks: 20, + Percentile: 60, + MaxPrice: gasprice.DefaultMaxPrice, + IgnorePrice: gasprice.DefaultIgnorePrice, } // LightClientGPO contains default gasprice oracle settings for light client. var LightClientGPO = gasprice.Config{ - Blocks: 2, - Percentile: 60, - MaxPrice: gasprice.DefaultMaxPrice, + Blocks: 2, + Percentile: 60, + MaxPrice: gasprice.DefaultMaxPrice, + IgnorePrice: gasprice.DefaultIgnorePrice, } // Defaults contains default settings for use on the Ethereum main net. diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 560722bec0..566c954ecc 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -32,12 +32,14 @@ import ( const sampleNumber = 3 // Number of transactions sampled in a block var DefaultMaxPrice = big.NewInt(500 * params.GWei) +var DefaultIgnorePrice = big.NewInt(2 * params.Wei) type Config struct { - Blocks int - Percentile int - Default *big.Int `toml:",omitempty"` - MaxPrice *big.Int `toml:",omitempty"` + Blocks int + Percentile int + Default *big.Int `toml:",omitempty"` + MaxPrice *big.Int `toml:",omitempty"` + IgnorePrice *big.Int `toml:",omitempty"` } // OracleBackend includes all necessary background APIs for oracle. @@ -50,12 +52,13 @@ type OracleBackend interface { // Oracle recommends gas prices based on the content of recent // blocks. Suitable for both light and full clients. type Oracle struct { - backend OracleBackend - lastHead common.Hash - lastPrice *big.Int - maxPrice *big.Int - cacheLock sync.RWMutex - fetchLock sync.Mutex + backend OracleBackend + lastHead common.Hash + lastPrice *big.Int + maxPrice *big.Int + ignorePrice *big.Int + cacheLock sync.RWMutex + fetchLock sync.Mutex checkBlocks int percentile int @@ -83,10 +86,18 @@ func NewOracle(backend OracleBackend, params Config) *Oracle { maxPrice = DefaultMaxPrice log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice) } + ignorePrice := params.IgnorePrice + if ignorePrice == nil || ignorePrice.Int64() <= 0 { + ignorePrice = DefaultIgnorePrice + log.Warn("Sanitizing invalid gasprice oracle ignore price", "provided", params.IgnorePrice, "updated", ignorePrice) + } else if ignorePrice.Int64() > 0 { + log.Info("Gasprice oracle is ignoring threshold set", "threshold", ignorePrice) + } return &Oracle{ backend: backend, lastPrice: params.Default, maxPrice: maxPrice, + ignorePrice: ignorePrice, checkBlocks: blocks, percentile: percent, } @@ -123,7 +134,7 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { txPrices []*big.Int ) for sent < gpo.checkBlocks && number > 0 { - go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit) + go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit) sent++ exp++ number-- @@ -146,7 +157,7 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) { // meaningful returned, try to query more blocks. But the maximum // is 2*checkBlocks. if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 { - go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit) + go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, gpo.ignorePrice, result, quit) sent++ exp++ number-- @@ -183,7 +194,7 @@ func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[ // and sends it to the result channel. If the block is empty or all transactions // are sent by the miner itself(it doesn't make any sense to include this kind of // transaction prices for sampling), nil gasprice is returned. -func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, result chan getBlockPricesResult, quit chan struct{}) { +func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, ignoreUnder *big.Int, result chan getBlockPricesResult, quit chan struct{}) { block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum)) if block == nil { select { @@ -199,7 +210,7 @@ func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, bloc var prices []*big.Int for _, tx := range txs { - if tx.GasPriceIntCmp(common.Big1) <= 0 { + if ignoreUnder != nil && tx.GasPriceIntCmp(ignoreUnder) == -1 { continue } sender, err := types.Sender(signer, tx) From 0524cede37508068b3031912989493e10eb0544a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 11 May 2021 16:23:54 +0300 Subject: [PATCH 547/948] eth/tracers: do the JSON serialization via .js to capture C faults --- eth/tracers/tracer.go | 24 +++++++++++++++++++----- eth/tracers/tracer_test.go | 14 +++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index ba65925373..218903dd92 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -505,7 +505,7 @@ func (jst *Tracer) Stop(err error) { // call executes a method on a JS object, catching any errors, formatting and // returning them as error objects. -func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error) { +func (jst *Tracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { // Execute the JavaScript call and return any error jst.vm.PushString(method) for _, arg := range args { @@ -519,7 +519,21 @@ func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error) return nil, errors.New(err) } // No error occurred, extract return value and return - return json.RawMessage(jst.vm.JsonEncode(-1)), nil + if noret { + return nil, nil + } + // Push a JSON marshaller onto the stack. We can't marshal from the out- + // side because duktape can crash on large nestings and we can't catch + // C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?! + jst.vm.PushString("(JSON.stringify)") + jst.vm.Eval() + + jst.vm.Swap(-1, -2) + if code = jst.vm.Pcall(1); code != 0 { + err := jst.vm.SafeToString(-1) + return nil, errors.New(err) + } + return json.RawMessage(jst.vm.SafeToString(-1)), nil } func wrapError(context string, err error) error { @@ -578,7 +592,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost *jst.errorValue = err.Error() } - if _, err := jst.call("step", "log", "db"); err != nil { + if _, err := jst.call(true, "step", "log", "db"); err != nil { jst.err = wrapError("step", err) } } @@ -592,7 +606,7 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost jst.errorValue = new(string) *jst.errorValue = err.Error() - if _, err := jst.call("fault", "log", "db"); err != nil { + if _, err := jst.call(true, "fault", "log", "db"); err != nil { jst.err = wrapError("fault", err) } } @@ -640,7 +654,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) { jst.vm.PutPropString(jst.stateObject, "ctx") // Finalize the trace and return the results - result, err := jst.call("result", "ctx", "db") + result, err := jst.call(false, "result", "ctx", "db") if err != nil { jst.err = wrapError("result", err) } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 7cda2e5330..033824474d 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -78,7 +78,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { } func TestTracer(t *testing.T) { - execTracer := func(code string) []byte { + execTracer := func(code string) ([]byte, string) { t.Helper() ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} tracer, err := New(code, ctx.txCtx) @@ -87,13 +87,14 @@ func TestTracer(t *testing.T) { } ret, err := runTrace(tracer, ctx) if err != nil { - t.Fatal(err) + return nil, err.Error() // Stringify to allow comparison without nil checks } - return ret + return ret, "" } for i, tt := range []struct { code string want string + fail string }{ { // tests that we don't panic on bad arguments to memory access code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", @@ -116,10 +117,13 @@ func TestTracer(t *testing.T) { }, { // tests intrinsic gas code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}", want: `"100000.6.21000"`, + }, { // tests too deep object / serialization crash + code: "{step: function() {}, fault: function() {}, result: function() { var o={}; var x=o; for (var i=0; i<1000; i++){ o.foo={}; o=o.foo; } return x; }}", + fail: "RangeError: json encode recursion limit in server-side tracer function 'result'", }, } { - if have := execTracer(tt.code); tt.want != string(have) { - t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) + if have, err := execTracer(tt.code); tt.want != string(have) || tt.fail != err { + t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code) } } } From a2c456a526ae9c07944f12913c6830add93a553f Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 11 May 2021 17:12:10 +0200 Subject: [PATCH 548/948] core: ensure a broken trie invariant crashes genesis creation (#22780) * Ensure state could be created in ToBlock * Fix rebase errors * use a panic instead --- core/genesis.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/genesis.go b/core/genesis.go index 9ae718beb6..d7d08e0909 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -259,7 +259,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { if db == nil { db = rawdb.NewMemoryDatabase() } - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) + statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + if err != nil { + panic(err) + } for addr, account := range g.Alloc { statedb.AddBalance(addr, account.Balance) statedb.SetCode(addr, account.Code) From addd8824cf3ad6133c1b1bbc3387a621eafba6a3 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 12 May 2021 10:05:39 +0200 Subject: [PATCH 549/948] cmd/geth, eth, core: snapshot dump + unify with trie dump (#22795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cmd/geth, eth, core: snapshot dump + unify with trie dump * cmd/evm: dump API fixes * cmd/geth, core, eth: fix some remaining errors * cmd/evm: dump - add limit, support address startkey, address review concerns * cmd, core/state, eth: minor polishes, fix snap dump crash, unify format Co-authored-by: Péter Szilágyi --- cmd/evm/internal/t8ntool/transition.go | 7 +- cmd/evm/runner.go | 2 +- cmd/evm/staterunner.go | 10 +-- cmd/geth/chaincmd.go | 112 +++++++++++++++++-------- cmd/geth/snapshot.go | 97 +++++++++++++++++++++ cmd/utils/flags.go | 12 ++- core/state/dump.go | 80 ++++++++++++------ core/state/state_test.go | 19 +++-- eth/api.go | 19 ++++- eth/api_test.go | 23 +++-- 10 files changed, 293 insertions(+), 88 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index fedcd12435..22cd0dd851 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -212,16 +212,15 @@ func Main(ctx *cli.Context) error { // Iterate over all the tests, run them and aggregate the results // Run the test and aggregate the result - state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) + s, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) if err != nil { return err } body, _ := rlp.EncodeToBytes(txs) // Dump the excution result collector := make(Alloc) - state.DumpToCollector(collector, false, false, false, nil, -1) + s.DumpToCollector(collector, nil) return dispatchOutput(ctx, baseDir, result, collector, body) - } // txWithKey is a helper-struct, to allow us to use the types.Transaction along with @@ -303,7 +302,7 @@ func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) { } } genesisAccount := core.GenesisAccount{ - Code: common.FromHex(dumpAccount.Code), + Code: dumpAccount.Code, Storage: storage, Balance: balance, Nonce: dumpAccount.Nonce, diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 4063767cb8..2d890ef1a2 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -270,7 +270,7 @@ func runCmd(ctx *cli.Context) error { if ctx.GlobalBool(DumpFlag.Name) { statedb.Commit(true) statedb.IntermediateRoot(true) - fmt.Println(string(statedb.Dump(false, false, true))) + fmt.Println(string(statedb.Dump(nil))) } if memProfilePath := ctx.GlobalString(MemProfileFlag.Name); memProfilePath != "" { diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index c4df936c75..d8bc4eae80 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -98,16 +98,16 @@ func stateTestCmd(ctx *cli.Context) error { for _, st := range test.Subtests() { // Run the test and aggregate the result result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} - _, state, err := test.Run(st, cfg, false) + _, s, err := test.Run(st, cfg, false) // print state root for evmlab tracing - if ctx.GlobalBool(MachineFlag.Name) && state != nil { - fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false)) + if ctx.GlobalBool(MachineFlag.Name) && s != nil { + fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", s.IntermediateRoot(false)) } if err != nil { // Test failed, mark as so and dump any state to aid debugging result.Pass, result.Error = false, err.Error() - if ctx.GlobalBool(DumpFlag.Name) && state != nil { - dump := state.RawDump(false, false, true) + if ctx.GlobalBool(DumpFlag.Name) && s != nil { + dump := s.RawDump(nil) result.State = &dump } } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index d00b4bc1f6..b9bd88e213 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -18,6 +18,7 @@ package main import ( "encoding/json" + "errors" "fmt" "os" "runtime" @@ -27,12 +28,16 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/node" "gopkg.in/urfave/cli.v1" ) @@ -152,20 +157,21 @@ The export-preimages command export hash preimages to an RLP encoded stream`, Action: utils.MigrateFlags(dump), Name: "dump", Usage: "Dump a specific block from storage", - ArgsUsage: "[ | ]...", + ArgsUsage: "[? | ]", Flags: []cli.Flag{ utils.DataDirFlag, utils.CacheFlag, - utils.SyncModeFlag, utils.IterativeOutputFlag, utils.ExcludeCodeFlag, utils.ExcludeStorageFlag, utils.IncludeIncompletesFlag, + utils.StartKeyFlag, + utils.DumpLimitFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` -The arguments are interpreted as block numbers or hashes. -Use "ethereum dump 0" to dump the genesis block.`, +This command dumps out the state for a given block (or latest, if none provided). +`, } ) @@ -373,47 +379,85 @@ func exportPreimages(ctx *cli.Context) error { return nil } -func dump(ctx *cli.Context) error { - stack, _ := makeConfigNode(ctx) - defer stack.Close() - +func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, ethdb.Database, common.Hash, error) { db := utils.MakeChainDatabase(ctx, stack, true) - for _, arg := range ctx.Args() { - var header *types.Header + var header *types.Header + if ctx.NArg() > 1 { + return nil, nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg()) + } + if ctx.NArg() == 1 { + arg := ctx.Args().First() if hashish(arg) { hash := common.HexToHash(arg) - number := rawdb.ReadHeaderNumber(db, hash) - if number != nil { + if number := rawdb.ReadHeaderNumber(db, hash); number != nil { header = rawdb.ReadHeader(db, hash, *number) + } else { + return nil, nil, common.Hash{}, fmt.Errorf("block %x not found", hash) } } else { - number, _ := strconv.Atoi(arg) - hash := rawdb.ReadCanonicalHash(db, uint64(number)) - if hash != (common.Hash{}) { - header = rawdb.ReadHeader(db, hash, uint64(number)) - } - } - if header == nil { - fmt.Println("{}") - utils.Fatalf("block not found") - } else { - state, err := state.New(header.Root, state.NewDatabase(db), nil) + number, err := strconv.Atoi(arg) if err != nil { - utils.Fatalf("could not create new state: %v", err) + return nil, nil, common.Hash{}, err } - excludeCode := ctx.Bool(utils.ExcludeCodeFlag.Name) - excludeStorage := ctx.Bool(utils.ExcludeStorageFlag.Name) - includeMissing := ctx.Bool(utils.IncludeIncompletesFlag.Name) - if ctx.Bool(utils.IterativeOutputFlag.Name) { - state.IterativeDump(excludeCode, excludeStorage, !includeMissing, json.NewEncoder(os.Stdout)) + if hash := rawdb.ReadCanonicalHash(db, uint64(number)); hash != (common.Hash{}) { + header = rawdb.ReadHeader(db, hash, uint64(number)) } else { - if includeMissing { - fmt.Printf("If you want to include accounts with missing preimages, you need iterative output, since" + - " otherwise the accounts will overwrite each other in the resulting mapping.") - } - fmt.Printf("%v %s\n", includeMissing, state.Dump(excludeCode, excludeStorage, false)) + return nil, nil, common.Hash{}, fmt.Errorf("header for block %d not found", number) } } + } else { + // Use latest + header = rawdb.ReadHeadHeader(db) + } + if header == nil { + return nil, nil, common.Hash{}, errors.New("no head block found") + } + startArg := common.FromHex(ctx.String(utils.StartKeyFlag.Name)) + var start common.Hash + switch len(startArg) { + case 0: // common.Hash + case 32: + start = common.BytesToHash(startArg) + case 20: + start = crypto.Keccak256Hash(startArg) + log.Info("Converting start-address to hash", "address", common.BytesToAddress(startArg), "hash", start.Hex()) + default: + return nil, nil, common.Hash{}, fmt.Errorf("invalid start argument: %x. 20 or 32 hex-encoded bytes required", startArg) + } + var conf = &state.DumpConfig{ + SkipCode: ctx.Bool(utils.ExcludeCodeFlag.Name), + SkipStorage: ctx.Bool(utils.ExcludeStorageFlag.Name), + OnlyWithAddresses: !ctx.Bool(utils.IncludeIncompletesFlag.Name), + Start: start.Bytes(), + Max: ctx.Uint64(utils.DumpLimitFlag.Name), + } + log.Info("State dump configured", "block", header.Number, "hash", header.Hash().Hex(), + "skipcode", conf.SkipCode, "skipstorage", conf.SkipStorage, + "start", hexutil.Encode(conf.Start), "limit", conf.Max) + return conf, db, header.Root, nil +} + +func dump(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + conf, db, root, err := parseDumpConfig(ctx, stack) + if err != nil { + return err + } + state, err := state.New(root, state.NewDatabase(db), nil) + if err != nil { + return err + } + if ctx.Bool(utils.IterativeOutputFlag.Name) { + state.IterativeDump(conf, json.NewEncoder(os.Stdout)) + } else { + if conf.OnlyWithAddresses { + fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+ + " otherwise the accounts will overwrite each other in the resulting mapping.") + return fmt.Errorf("incompatible options") + } + fmt.Println(string(state.Dump(conf))) } return nil } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 1af458af20..35d027fb16 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -18,7 +18,9 @@ package main import ( "bytes" + "encoding/json" "errors" + "os" "time" "github.com/ethereum/go-ethereum/cmd/utils" @@ -142,6 +144,31 @@ verification. The default checking target is the HEAD state. It's basically iden to traverse-state, but the check granularity is smaller. It's also usable without snapshot enabled. +`, + }, + { + Name: "dump", + Usage: "Dump a specific block from storage (same as 'geth dump' but using snapshots)", + ArgsUsage: "[? | ]", + Action: utils.MigrateFlags(dumpState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.ExcludeCodeFlag, + utils.ExcludeStorageFlag, + utils.StartKeyFlag, + utils.DumpLimitFlag, + }, + Description: ` +This command is semantically equivalent to 'geth dump', but uses the snapshots +as the backend data source, making this command a lot faster. + +The argument is interpreted as block number or hash. If none is provided, the latest +block is used. `, }, }, @@ -430,3 +457,73 @@ func parseRoot(input string) (common.Hash, error) { } return h, nil } + +func dumpState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + conf, db, root, err := parseDumpConfig(ctx, stack) + if err != nil { + return err + } + snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false) + if err != nil { + return err + } + accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) + if err != nil { + return err + } + defer accIt.Release() + + log.Info("Snapshot dumping started", "root", root) + var ( + start = time.Now() + logged = time.Now() + accounts uint64 + ) + enc := json.NewEncoder(os.Stdout) + enc.Encode(struct { + Root common.Hash `json:"root"` + }{root}) + for accIt.Next() { + account, err := snapshot.FullAccount(accIt.Account()) + if err != nil { + return err + } + da := &state.DumpAccount{ + Balance: account.Balance.String(), + Nonce: account.Nonce, + Root: account.Root, + CodeHash: account.CodeHash, + SecureKey: accIt.Hash().Bytes(), + } + if !conf.SkipCode && !bytes.Equal(account.CodeHash, emptyCode) { + da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash)) + } + if !conf.SkipStorage { + da.Storage = make(map[common.Hash]string) + + stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) + if err != nil { + return err + } + for stIt.Next() { + da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot()) + } + } + enc.Encode(da) + accounts++ + if time.Since(logged) > 8*time.Second { + log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + if conf.Max > 0 && accounts >= conf.Max { + break + } + } + log.Info("Snapshot dumping complete", "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5c0bba4655..d3fb3f2cbd 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -184,7 +184,7 @@ var ( Name: "exitwhensynced", Usage: "Exits after block synchronisation completes", } - IterativeOutputFlag = cli.BoolFlag{ + IterativeOutputFlag = cli.BoolTFlag{ Name: "iterative", Usage: "Print streaming JSON iteratively, delimited by newlines", } @@ -200,6 +200,16 @@ var ( Name: "nocode", Usage: "Exclude contract code (save db lookups)", } + StartKeyFlag = cli.StringFlag{ + Name: "start", + Usage: "Start position. Either a hash or address", + Value: "0x0000000000000000000000000000000000000000000000000000000000000000", + } + DumpLimitFlag = cli.Uint64Flag{ + Name: "limit", + Usage: "Max number of elements (0 = no limit)", + Value: 0, + } defaultSyncMode = ethconfig.Defaults.SyncMode SyncModeFlag = TextMarshalerFlag{ Name: "syncmode", diff --git a/core/state/dump.go b/core/state/dump.go index b25da714fd..00faa4ed6a 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -19,6 +19,7 @@ package state import ( "encoding/json" "fmt" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -27,6 +28,16 @@ import ( "github.com/ethereum/go-ethereum/trie" ) +// DumpConfig is a set of options to control what portions of the statewill be +// iterated and collected. +type DumpConfig struct { + SkipCode bool + SkipStorage bool + OnlyWithAddresses bool + Start []byte + Max uint64 +} + // DumpCollector interface which the state trie calls during iteration type DumpCollector interface { // OnRoot is called with the state root @@ -39,9 +50,9 @@ type DumpCollector interface { type DumpAccount struct { Balance string `json:"balance"` Nonce uint64 `json:"nonce"` - Root string `json:"root"` - CodeHash string `json:"codeHash"` - Code string `json:"code,omitempty"` + Root hexutil.Bytes `json:"root"` + CodeHash hexutil.Bytes `json:"codeHash"` + Code hexutil.Bytes `json:"code,omitempty"` Storage map[common.Hash]string `json:"storage,omitempty"` Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key @@ -111,38 +122,50 @@ func (d iterativeDump) OnRoot(root common.Hash) { }{root}) } -func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) { - missingPreimages := 0 +// DumpToCollector iterates the state according to the given options and inserts +// the items into a collector for aggregation or serialization. +func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) { + // Sanitize the input to allow nil configs + if conf == nil { + conf = new(DumpConfig) + } + var ( + missingPreimages int + accounts uint64 + start = time.Now() + logged = time.Now() + ) + log.Info("Trie dumping started", "root", s.trie.Hash()) c.OnRoot(s.trie.Hash()) - var count int - it := trie.NewIterator(s.trie.NodeIterator(start)) + it := trie.NewIterator(s.trie.NodeIterator(conf.Start)) for it.Next() { var data Account if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) } account := DumpAccount{ - Balance: data.Balance.String(), - Nonce: data.Nonce, - Root: common.Bytes2Hex(data.Root[:]), - CodeHash: common.Bytes2Hex(data.CodeHash), + Balance: data.Balance.String(), + Nonce: data.Nonce, + Root: data.Root[:], + CodeHash: data.CodeHash, + SecureKey: it.Key, } addrBytes := s.trie.GetKey(it.Key) if addrBytes == nil { // Preimage missing missingPreimages++ - if excludeMissingPreimages { + if conf.OnlyWithAddresses { continue } account.SecureKey = it.Key } addr := common.BytesToAddress(addrBytes) obj := newObject(s, addr, data) - if !excludeCode { - account.Code = common.Bytes2Hex(obj.Code(s.db)) + if !conf.SkipCode { + account.Code = obj.Code(s.db) } - if !excludeStorage { + if !conf.SkipStorage { account.Storage = make(map[common.Hash]string) storageIt := trie.NewIterator(obj.getTrie(s.db).NodeIterator(nil)) for storageIt.Next() { @@ -155,8 +178,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, } } c.OnAccount(addr, account) - count++ - if maxResults > 0 && count >= maxResults { + accounts++ + if time.Since(logged) > 8*time.Second { + log.Info("Trie dumping in progress", "at", it.Key, "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + if conf.Max > 0 && accounts >= conf.Max { if it.Next() { nextKey = it.Key } @@ -166,22 +194,24 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, if missingPreimages > 0 { log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages) } + log.Info("Trie dumping complete", "accounts", accounts, + "elapsed", common.PrettyDuration(time.Since(start))) return nextKey } // RawDump returns the entire state an a single large object -func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages bool) Dump { +func (s *StateDB) RawDump(opts *DumpConfig) Dump { dump := &Dump{ Accounts: make(map[common.Address]DumpAccount), } - s.DumpToCollector(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) + s.DumpToCollector(dump, opts) return *dump } // Dump returns a JSON string representing the entire state as a single json-object -func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool) []byte { - dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages) +func (s *StateDB) Dump(opts *DumpConfig) []byte { + dump := s.RawDump(opts) json, err := json.MarshalIndent(dump, "", " ") if err != nil { fmt.Println("Dump err", err) @@ -190,15 +220,15 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool } // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout -func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) { - s.DumpToCollector(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) +func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) { + s.DumpToCollector(iterativeDump{output}, opts) } // IteratorDump dumps out a batch of accounts starts with the given start key -func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) IteratorDump { +func (s *StateDB) IteratorDump(opts *DumpConfig) IteratorDump { iterator := &IteratorDump{ Accounts: make(map[common.Address]DumpAccount), } - iterator.Next = s.DumpToCollector(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults) + iterator.Next = s.DumpToCollector(iterator, opts) return *iterator } diff --git a/core/state/state_test.go b/core/state/state_test.go index 9566531466..0a55d7781f 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -57,28 +57,31 @@ func TestDump(t *testing.T) { s.state.Commit(false) // check that DumpToCollector contains the state objects that are in trie - got := string(s.state.Dump(false, false, true)) + got := string(s.state.Dump(nil)) want := `{ "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", "accounts": { "0x0000000000000000000000000000000000000001": { "balance": "22", "nonce": 0, - "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d" }, "0x0000000000000000000000000000000000000002": { "balance": "44", "nonce": 0, - "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62" }, "0x0000000000000000000000000000000000000102": { "balance": "0", "nonce": 0, - "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3", - "code": "03030303030303" + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3", + "code": "0x03030303030303", + "key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1" } } }` diff --git a/eth/api.go b/eth/api.go index 7387459c94..6a22c9e416 100644 --- a/eth/api.go +++ b/eth/api.go @@ -264,12 +264,16 @@ func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI { // DumpBlock retrieves the entire state of the database at a given block. func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { + opts := &state.DumpConfig{ + OnlyWithAddresses: true, + Max: AccountRangeMaxResults, // Sanity limit over RPC + } if blockNr == rpc.PendingBlockNumber { // If we're dumping the pending state, we need to request // both the pending block as well as the pending state from // the miner and operate on those _, stateDb := api.eth.miner.Pending() - return stateDb.RawDump(false, false, true), nil + return stateDb.RawDump(opts), nil } var block *types.Block if blockNr == rpc.LatestBlockNumber { @@ -284,7 +288,7 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error if err != nil { return state.Dump{}, err } - return stateDb.RawDump(false, false, true), nil + return stateDb.RawDump(opts), nil } // PrivateDebugAPI is the collection of Ethereum full node APIs exposed over @@ -386,10 +390,17 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta return state.IteratorDump{}, errors.New("either block number or block hash must be specified") } + opts := &state.DumpConfig{ + SkipCode: nocode, + SkipStorage: nostorage, + OnlyWithAddresses: !incompletes, + Start: start, + Max: uint64(maxResults), + } if maxResults > AccountRangeMaxResults || maxResults <= 0 { - maxResults = AccountRangeMaxResults + opts.Max = AccountRangeMaxResults } - return stateDb.IteratorDump(nocode, nostorage, incompletes, start, maxResults), nil + return stateDb.IteratorDump(opts), nil } // StorageRangeResult is the result of a debug_storageRangeAt API call. diff --git a/eth/api_test.go b/eth/api_test.go index b44eed40bc..39a1d58460 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -34,7 +34,13 @@ import ( var dumper = spew.ConfigState{Indent: " "} func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump { - result := statedb.IteratorDump(true, true, false, start.Bytes(), requestedNum) + result := statedb.IteratorDump(&state.DumpConfig{ + SkipCode: true, + SkipStorage: true, + OnlyWithAddresses: false, + Start: start.Bytes(), + Max: uint64(requestedNum), + }) if len(result.Accounts) != expectedNum { t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts)) @@ -131,12 +137,17 @@ func TestEmptyAccountRange(t *testing.T) { t.Parallel() var ( - statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) - state, _ = state.New(common.Hash{}, statedb, nil) + statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) + st, _ = state.New(common.Hash{}, statedb, nil) ) - state.Commit(true) - state.IntermediateRoot(true) - results := state.IteratorDump(true, true, true, (common.Hash{}).Bytes(), AccountRangeMaxResults) + st.Commit(true) + st.IntermediateRoot(true) + results := st.IteratorDump(&state.DumpConfig{ + SkipCode: true, + SkipStorage: true, + OnlyWithAddresses: true, + Max: uint64(AccountRangeMaxResults), + }) if bytes.Equal(results.Next, (common.Hash{}).Bytes()) { t.Fatalf("Empty results should not return a second page") } From 597ecb39cc963eb3b85b01b759c75c45e71f41d0 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Mon, 17 May 2021 00:52:32 -0600 Subject: [PATCH 550/948] cmd/evm: return json error if unmarshalling from stdin fails (#22871) * cmd/evm: return json error if unmarshalling from stdin fails * cmd/evm: make error capitalizations uniform (all lowercase starts) * cmd/evm: capitalize error sent directly to stderror --- cmd/evm/internal/t8ntool/transition.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 22cd0dd851..9bb03c2c6a 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -142,7 +142,9 @@ func Main(ctx *cli.Context) error { // Figure out the prestate alloc if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { decoder := json.NewDecoder(os.Stdin) - decoder.Decode(inputData) + if err := decoder.Decode(inputData); err != nil { + return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) + } } if allocStr != stdinSelector { inFile, err := os.Open(allocStr) @@ -152,7 +154,7 @@ func Main(ctx *cli.Context) error { defer inFile.Close() decoder := json.NewDecoder(inFile) if err := decoder.Decode(&inputData.Alloc); err != nil { - return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err)) + return NewError(ErrorJson, fmt.Errorf("failed unmarshaling alloc-file: %v", err)) } } prestate.Pre = inputData.Alloc @@ -167,7 +169,7 @@ func Main(ctx *cli.Context) error { decoder := json.NewDecoder(inFile) var env stEnv if err := decoder.Decode(&env); err != nil { - return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling env-file: %v", err)) + return NewError(ErrorJson, fmt.Errorf("failed unmarshaling env-file: %v", err)) } inputData.Env = &env } @@ -180,7 +182,7 @@ func Main(ctx *cli.Context) error { // Construct the chainconfig var chainConfig *params.ChainConfig if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { - return NewError(ErrorVMConfig, fmt.Errorf("Failed constructing chain configuration: %v", err)) + return NewError(ErrorVMConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) } else { chainConfig = cConf vmConfig.ExtraEips = extraEips @@ -197,7 +199,7 @@ func Main(ctx *cli.Context) error { defer inFile.Close() decoder := json.NewDecoder(inFile) if err := decoder.Decode(&txsWithKeys); err != nil { - return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) + return NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err)) } } else { txsWithKeys = inputData.Txs @@ -206,7 +208,7 @@ func Main(ctx *cli.Context) error { signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number))) if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { - return NewError(ErrorJson, fmt.Errorf("Failed signing transactions: %v", err)) + return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err)) } // Iterate over all the tests, run them and aggregate the results @@ -277,7 +279,7 @@ func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Tran // This transaction needs to be signed signed, err := types.SignTx(tx, signer, key) if err != nil { - return nil, NewError(ErrorJson, fmt.Errorf("Tx %d: failed to sign tx: %v", i, err)) + return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err)) } signedTxs = append(signedTxs, signed) } else { From 14bc6e5130ebd291026da82f9ad9684bde895479 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 17 May 2021 10:49:23 +0200 Subject: [PATCH 551/948] consensus/ethash: change eip3554 from 9.5M to 9.7M (#22870) --- consensus/ethash/consensus.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 492fc83538..b04cb24fb4 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -46,9 +46,9 @@ var ( allowedFutureBlockTimeSeconds = int64(15) // Max seconds from current time allowed for blocks, before they're considered future blocks // calcDifficultyEip3554 is the difficulty adjustment algorithm as specified by EIP 3554. - // It offsets the bomb a total of 9.5M blocks. + // It offsets the bomb a total of 9.7M blocks. // Specification EIP-3554: https://eips.ethereum.org/EIPS/eip-3554 - calcDifficultyEip3554 = makeDifficultyCalculator(big.NewInt(9500000)) + calcDifficultyEip3554 = makeDifficultyCalculator(big.NewInt(9700000)) // calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384. // It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks. From 94451c2788295901c302c9bf5fa2f7b021c924e2 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 17 May 2021 15:13:22 +0200 Subject: [PATCH 552/948] all: implement EIP-1559 (#22837) This is the initial implementation of EIP-1559 in packages core/types and core. Mining, RPC, etc. will be added in subsequent commits. Co-authored-by: Marius van der Wijden Co-authored-by: lightclient@protonmail.com Co-authored-by: Felix Lange --- accounts/abi/bind/backends/simulated.go | 2 + accounts/external/backend.go | 12 +- cmd/evm/internal/t8ntool/execution.go | 8 +- cmd/evm/internal/t8ntool/gen_stenv.go | 22 ++- cmd/evm/internal/t8ntool/transition.go | 10 +- cmd/evm/poststate.json | 23 --- cmd/evm/testdata/10/alloc.json | 23 +++ cmd/evm/testdata/10/env.json | 12 ++ cmd/evm/testdata/10/readme.md | 79 +++++++++ cmd/evm/testdata/10/txs.json | 70 ++++++++ cmd/evm/testdata/11/alloc.json | 25 +++ cmd/evm/testdata/11/env.json | 12 ++ cmd/evm/testdata/11/readme.md | 13 ++ cmd/evm/testdata/11/txs.json | 14 ++ cmd/evm/testdata/9/alloc.json | 11 ++ cmd/evm/testdata/9/env.json | 8 + cmd/evm/testdata/9/readme.md | 75 +++++++++ cmd/evm/testdata/9/txs.json | 37 +++++ consensus/clique/clique.go | 34 ++-- consensus/ethash/consensus.go | 30 ++-- consensus/misc/eip1559.go | 93 +++++++++++ consensus/misc/eip1559_test.go | 133 ++++++++++++++++ consensus/misc/gaslimit.go | 42 +++++ core/blockchain_test.go | 157 ++++++++++++++++++ core/chain_makers.go | 8 +- core/error.go | 4 + core/evm.go | 10 +- core/genesis.go | 3 + core/state_prefetcher.go | 2 +- core/state_processor.go | 6 +- core/state_processor_test.go | 203 +++++++++++++++++------- core/state_transition.go | 22 ++- core/types/access_list_tx.go | 3 +- core/types/block.go | 14 ++ core/types/block_test.go | 65 ++++++++ core/types/dynamic_fee_tx.go | 104 ++++++++++++ core/types/legacy_tx.go | 3 +- core/types/receipt.go | 9 +- core/types/transaction.go | 31 +++- core/types/transaction_marshalling.go | 104 ++++++++++-- core/types/transaction_signing.go | 77 ++++++++- core/vm/eips.go | 20 +++ core/vm/evm.go | 1 + core/vm/jump_table.go | 1 + core/vm/opcodes.go | 3 + eth/state_accessor.go | 2 +- eth/tracers/api.go | 8 +- eth/tracers/api_test.go | 2 +- eth/tracers/tracers_test.go | 4 +- interfaces.go | 3 + internal/ethapi/api.go | 6 +- les/odr_test.go | 4 +- les/state_accessor.go | 2 +- light/odr_test.go | 2 +- params/bootnodes.go | 3 +- params/config.go | 2 +- params/protocol_params.go | 4 + tests/init.go | 13 ++ tests/state_test_util.go | 2 +- 59 files changed, 1522 insertions(+), 173 deletions(-) delete mode 100644 cmd/evm/poststate.json create mode 100644 cmd/evm/testdata/10/alloc.json create mode 100644 cmd/evm/testdata/10/env.json create mode 100644 cmd/evm/testdata/10/readme.md create mode 100644 cmd/evm/testdata/10/txs.json create mode 100644 cmd/evm/testdata/11/alloc.json create mode 100644 cmd/evm/testdata/11/env.json create mode 100644 cmd/evm/testdata/11/readme.md create mode 100644 cmd/evm/testdata/11/txs.json create mode 100644 cmd/evm/testdata/9/alloc.json create mode 100644 cmd/evm/testdata/9/env.json create mode 100644 cmd/evm/testdata/9/readme.md create mode 100644 cmd/evm/testdata/9/txs.json create mode 100644 consensus/misc/eip1559.go create mode 100644 consensus/misc/eip1559_test.go create mode 100644 consensus/misc/gaslimit.go create mode 100644 core/types/dynamic_fee_tx.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index d6d525eae1..9de427ae43 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -716,6 +716,8 @@ func (m callMsg) Nonce() uint64 { return 0 } func (m callMsg) CheckNonce() bool { return false } func (m callMsg) To() *common.Address { return m.CallMsg.To } func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callMsg) FeeCap() *big.Int { return m.CallMsg.FeeCap } +func (m callMsg) Tip() *big.Int { return m.CallMsg.Tip } func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Data() []byte { return m.CallMsg.Data } diff --git a/accounts/external/backend.go b/accounts/external/backend.go index de241385c2..59766217d2 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -217,12 +217,12 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio if chainID != nil { args.ChainID = (*hexutil.Big)(chainID) } - // However, if the user asked for a particular chain id, then we should - // use that instead. - if tx.Type() != types.LegacyTxType && tx.ChainId() != nil { - args.ChainID = (*hexutil.Big)(tx.ChainId()) - } - if tx.Type() == types.AccessListTxType { + if tx.Type() != types.LegacyTxType { + // However, if the user asked for a particular chain id, then we should + // use that instead. + if tx.ChainId() != nil { + args.ChainID = (*hexutil.Big)(tx.ChainId()) + } accessList := tx.AccessList() args.AccessList = &accessList } diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index c3f1b16efc..cf6974bc43 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -69,6 +69,7 @@ type stEnv struct { Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` + BaseFee *big.Int `json:"currentBaseFee,omitempty"` } type stEnvMarshaling struct { @@ -77,6 +78,7 @@ type stEnvMarshaling struct { GasLimit math.HexOrDecimal64 Number math.HexOrDecimal64 Timestamp math.HexOrDecimal64 + BaseFee *math.HexOrDecimal256 } // Apply applies a set of transactions to a pre-state @@ -120,6 +122,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, GasLimit: pre.Env.GasLimit, GetHash: getHash, } + // If currentBaseFee is defined, add it to the vmContext. + if pre.Env.BaseFee != nil { + vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee) + } // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's // done in StateProcessor.Process(block, ...), right before transactions are applied. if chainConfig.DAOForkSupport && @@ -129,7 +135,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } for i, tx := range txs { - msg, err := tx.AsMessage(signer) + msg, err := tx.AsMessage(signer, pre.Env.BaseFee) if err != nil { log.Info("rejected tx", "index", i, "hash", tx.Hash(), "error", err) rejectedTxs = append(rejectedTxs, i) diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go index ab5951534e..695fdba1e1 100644 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -16,13 +16,14 @@ var _ = (*stEnvMarshaling)(nil) // MarshalJSON marshals as JSON. func (s stEnv) MarshalJSON() ([]byte, error) { type stEnv struct { - Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` } var enc stEnv enc.Coinbase = common.UnprefixedAddress(s.Coinbase) @@ -32,19 +33,21 @@ func (s stEnv) MarshalJSON() ([]byte, error) { enc.Timestamp = math.HexOrDecimal64(s.Timestamp) enc.BlockHashes = s.BlockHashes enc.Ommers = s.Ommers + enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (s *stEnv) UnmarshalJSON(input []byte) error { type stEnv struct { - Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` Ommers []ommer `json:"ommers,omitempty"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` } var dec stEnv if err := json.Unmarshal(input, &dec); err != nil { @@ -76,5 +79,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { if dec.Ommers != nil { s.Ommers = dec.Ommers } + if dec.BaseFee != nil { + s.BaseFee = (*big.Int)(dec.BaseFee) + } return nil } diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 9bb03c2c6a..bab6e63faa 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -19,6 +19,7 @@ package t8ntool import ( "crypto/ecdsa" "encoding/json" + "errors" "fmt" "io/ioutil" "math/big" @@ -210,9 +211,12 @@ func Main(ctx *cli.Context) error { if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err)) } - - // Iterate over all the tests, run them and aggregate the results - + // Sanity check, to not `panic` in state_transition + if chainConfig.IsLondon(big.NewInt(int64(prestate.Env.Number))) { + if prestate.Env.BaseFee == nil { + return NewError(ErrorVMConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) + } + } // Run the test and aggregate the result s, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) if err != nil { diff --git a/cmd/evm/poststate.json b/cmd/evm/poststate.json deleted file mode 100644 index 9ee17f18d1..0000000000 --- a/cmd/evm/poststate.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "root": "f4157bb27bcb1d1a63001434a249a80948f2e9fe1f53d551244c1dae826b5b23", - "accounts": { - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "balance": "4276951709", - "nonce": 1, - "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "6916764286133345652", - "nonce": 172, - "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "42500", - "nonce": 0, - "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/10/alloc.json b/cmd/evm/testdata/10/alloc.json new file mode 100644 index 0000000000..6e98e7513c --- /dev/null +++ b/cmd/evm/testdata/10/alloc.json @@ -0,0 +1,23 @@ +{ + "0x1111111111111111111111111111111111111111" : { + "balance" : "0x010000000000", + "code" : "0xfe", + "nonce" : "0x01", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x010000000000", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363" : { + "balance" : "0x01000000000000", + "code" : "0x", + "nonce" : "0x01", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/10/env.json b/cmd/evm/testdata/10/env.json new file mode 100644 index 0000000000..3a82d46a77 --- /dev/null +++ b/cmd/evm/testdata/10/env.json @@ -0,0 +1,12 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentNumber" : "0x01", + "currentTimestamp" : "0x079e", + "previousHash" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f", + "currentGasLimit" : "0x40000000", + "currentBaseFee" : "0x036b", + "blockHashes" : { + "0" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/10/readme.md b/cmd/evm/testdata/10/readme.md new file mode 100644 index 0000000000..c34be80bb7 --- /dev/null +++ b/cmd/evm/testdata/10/readme.md @@ -0,0 +1,79 @@ +## EIP-1559 testing + +This test contains testcases for EIP-1559, which were reported by Ori as misbehaving. + +``` +[user@work evm]$ dir=./testdata/10 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout --output.result=stdout 2>&1 +INFO [05-09|22:11:59.436] rejected tx index=3 hash=db07bf..ede1e8 from=0xd02d72E067e77158444ef2020Ff2d325f929B363 error="gas limit reached" +``` +Output: +```json +{ + "alloc": { + "0x1111111111111111111111111111111111111111": { + "code": "0xfe", + "balance": "0x10000000000", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x10000000000", + "nonce": "0x1" + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363": { + "balance": "0xff5beffffc95", + "nonce": "0x4" + } + }, + "result": { + "stateRoot": "0xf91a7ec08e4bfea88719aab34deabb000c86902360532b52afa9599d41f2bb8b", + "txRoot": "0xda925f2306a52fa24c15d5cd212d736ee016415fd8dd0c45fd368de7917d64bb", + "receiptRoot": "0x439a25f7fc424c10fb1f89800e4aa1df74156b137239d9ac3eaa7c911c353cd5", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x10000001", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x88980f6efcc5358d9c359663e7b9414722d430497637340ea056b076bc206701", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10000001", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x20000001", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0xd7bf3886f4e2aef74d525ae072c680f3846f550254401b67cbfda4a233757582", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x0", + "cumulativeGasUsed": "0x30000001", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x50308296760f01f1eeec7500e9e73cad67469249b1f59e9a9f55e6625a4923db", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10000000", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x2" + } + ], + "rejected": [ + 3 + ] + } +} +``` diff --git a/cmd/evm/testdata/10/txs.json b/cmd/evm/testdata/10/txs.json new file mode 100644 index 0000000000..014f9db9af --- /dev/null +++ b/cmd/evm/testdata/10/txs.json @@ -0,0 +1,70 @@ +[ + { + "input" : "0x", + "gas" : "0x10000001", + "nonce" : "0x1", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x7a45f00bcde9036b026cdf1628b023cd8a31a95c62b5e4dbbee2fa7debe668fb", + "s" : "0x3cc9d6f2cd00a045b0263f2d6dad7d60938d5d13d061af4969f95928aa934d4a", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "feeCap" : "0xfa0", + "tip" : "0x0", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x2", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x4c564b94b0281a8210eeec2dd1fe2e16ff1c1903a8c3a1078d735d7f8208b2af", + "s" : "0x56432b2593e6de95db1cb997b7385217aca03f1615327e231734446b39f266d", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "feeCap" : "0xfa0", + "tip" : "0x0", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x3", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x2ed2ef52f924f59d4a21e1f2a50d3b1109303ce5e32334a7ece9b46f4fbc2a57", + "s" : "0x2980257129cbd3da987226f323d50ba3975a834d165e0681f991b75615605c44", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "feeCap" : "0xfa0", + "tip" : "0x0", + "accessList" : [ + ] + }, + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x4", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "v" : "0x0", + "r" : "0x5df7d7f8f8e15b36fc9f189cacb625040fad10398d08fc90812595922a2c49b2", + "s" : "0x565fc1803f77a84d754ffe3c5363ab54a8d93a06ea1bb9d4c73c73a282b35917", + "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", + "chainId" : "0x1", + "type" : "0x2", + "feeCap" : "0xfa0", + "tip" : "0x0", + "accessList" : [ + ] + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/11/alloc.json b/cmd/evm/testdata/11/alloc.json new file mode 100644 index 0000000000..86938230fa --- /dev/null +++ b/cmd/evm/testdata/11/alloc.json @@ -0,0 +1,25 @@ +{ + "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x61ffff5060046000f3", + "nonce" : "0x01", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + "0x00" : "0x00" + } + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x00", + "code" : "0x6001600055", + "nonce" : "0x00", + "storage" : { + } + } +} + diff --git a/cmd/evm/testdata/11/env.json b/cmd/evm/testdata/11/env.json new file mode 100644 index 0000000000..37dedf0947 --- /dev/null +++ b/cmd/evm/testdata/11/env.json @@ -0,0 +1,12 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8", + "previousHash" : "0xfda4419b3660e99f37e536dae1ab081c180136bb38c837a93e93d9aab58553b2", + "currentGasLimit" : "0x0f4240", + "blockHashes" : { + "0" : "0xfda4419b3660e99f37e536dae1ab081c180136bb38c837a93e93d9aab58553b2" + } +} + diff --git a/cmd/evm/testdata/11/readme.md b/cmd/evm/testdata/11/readme.md new file mode 100644 index 0000000000..d499f8e99f --- /dev/null +++ b/cmd/evm/testdata/11/readme.md @@ -0,0 +1,13 @@ +## Test missing basefee + +In this test, the `currentBaseFee` is missing from the env portion. +On a live blockchain, the basefee is present in the header, and verified as part of header validation. + +In `evm t8n`, we don't have blocks, so it needs to be added in the `env`instead. + +When it's missing, an error is expected. + +``` +dir=./testdata/11 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout --output.result=stdout 2>&1>/dev/null +ERROR(3): EIP-1559 config but missing 'currentBaseFee' in env section +``` \ No newline at end of file diff --git a/cmd/evm/testdata/11/txs.json b/cmd/evm/testdata/11/txs.json new file mode 100644 index 0000000000..c54b0a1f5b --- /dev/null +++ b/cmd/evm/testdata/11/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x38600060013960015160005560006000f3", + "gas" : "0x61a80", + "gasPrice" : "0x1", + "nonce" : "0x0", + "value" : "0x186a0", + "v" : "0x1c", + "r" : "0x2e1391fd903387f1cc2b51df083805fb4bbb0d4710a2cdf4a044d191ff7be63e", + "s" : "0x7f10a933c42ab74927db02b1db009e923d9d2ab24ac24d63c399f2fe5d9c9b22", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] + diff --git a/cmd/evm/testdata/9/alloc.json b/cmd/evm/testdata/9/alloc.json new file mode 100644 index 0000000000..c14e38e845 --- /dev/null +++ b/cmd/evm/testdata/9/alloc.json @@ -0,0 +1,11 @@ +{ + "0x000000000000000000000000000000000000aaaa": { + "balance": "0x03", + "code": "0x58585454", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x100000000000000", + "nonce": "0x00" + } +} diff --git a/cmd/evm/testdata/9/env.json b/cmd/evm/testdata/9/env.json new file mode 100644 index 0000000000..ec5164b995 --- /dev/null +++ b/cmd/evm/testdata/9/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasTarget": "0x1000000000", + "currentBaseFee": "0x3B9ACA00", + "currentNumber": "0x1000000", + "currentTimestamp": "0x04" +} diff --git a/cmd/evm/testdata/9/readme.md b/cmd/evm/testdata/9/readme.md new file mode 100644 index 0000000000..88f0f12aaa --- /dev/null +++ b/cmd/evm/testdata/9/readme.md @@ -0,0 +1,75 @@ +## EIP-1559 testing + +This test contains testcases for EIP-1559, which uses an new transaction type and has a new block parameter. + +### Prestate + +The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the +following code: `0x58585454`: `PC; PC; SLOAD; SLOAD`. + +Essentialy, this contract does `SLOAD(0)` and `SLOAD(1)`. + +The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. + +## Transactions + +There are two transactions, each invokes the contract above. + +1. EIP-1559 ACL-transaction, which contains the `0x0` slot for `0xaaaa` +2. Legacy transaction + +## Execution + +Running it yields: +``` +$ dir=./testdata/9 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace && cat trace-* | grep SLOAD +{"pc":2,"op":84,"gas":"0x48c28","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0","0x1"],"returnStack":null,"returnD +ata":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":3,"op":84,"gas":"0x483f4","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0","0x0"],"returnStack":null,"returnDa +ta":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":2,"op":84,"gas":"0x49cf4","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0","0x1"],"returnStack":null,"returnD +ata":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":3,"op":84,"gas":"0x494c0","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0","0x0"],"returnStack":null,"returnD +ata":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +``` + +We can also get the post-alloc: +``` +$ dir=./testdata/9 && ./evm t8n --state.fork=London --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout +{ + "alloc": { + "0x000000000000000000000000000000000000aaaa": { + "code": "0x58585454", + "balance": "0x3", + "nonce": "0x1" + }, + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0xbfc02677a000" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xff104fcfea7800", + "nonce": "0x2" + } + } +} +``` + +If we try to execute it on older rules: +``` +dir=./testdata/9 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout +ERROR(10): Failed signing transactions: ERROR(10): Tx 0: failed to sign tx: transaction type not supported +``` + +It fails, due to the `evm t8n` cannot sign them in with the given signer. We can bypass that, however, +by feeding it presigned transactions, located in `txs_signed.json`. + +``` +dir=./testdata/9 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs_signed.json --input.env=$dir/env.json +INFO [05-07|12:28:42.072] rejected tx index=0 hash=b4821e..536819 error="transaction type not supported" +INFO [05-07|12:28:42.072] rejected tx index=1 hash=a9c6c6..fa4036 from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" +INFO [05-07|12:28:42.073] Wrote file file=alloc.json +INFO [05-07|12:28:42.073] Wrote file file=result.json +``` + +Number `0` is not applicable, and therefore number `1` has wrong nonce, and both are rejected. + diff --git a/cmd/evm/testdata/9/txs.json b/cmd/evm/testdata/9/txs.json new file mode 100644 index 0000000000..f349ae4a24 --- /dev/null +++ b/cmd/evm/testdata/9/txs.json @@ -0,0 +1,37 @@ +[ + { + "gas": "0x4ef00", + "tip": "0x2", + "feeCap": "0x12A05F200", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x0", + "type" : "0x2", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x12A05F200", + "chainId": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x0", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 9954a023e5..b693e80518 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -299,10 +299,6 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.GasLimit > cap { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) } - // Verify that the gasUsed is <= gasLimit - if header.GasUsed > header.GasLimit { - return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) - } // If all checks passed, validate any special fields for hard forks if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil { return err @@ -334,14 +330,21 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header if parent.Time+c.config.Period > header.Time { return errInvalidTimestamp } - // Verify that the gas limit remains within allowed bounds - diff := int64(parent.GasLimit) - int64(header.GasLimit) - if diff < 0 { - diff *= -1 + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } - limit := parent.GasLimit / params.GasLimitBoundDivisor - if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { - return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) + if !chain.Config().IsLondon(header.Number) { + // Verify BaseFee not present before EIP-1559 fork. + if header.BaseFee != nil { + return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) + } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { + return err + } + } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + // Verify the header's EIP-1559 attributes. + return err } // Retrieve the snapshot needed to verify this header and cache it snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) @@ -725,7 +728,7 @@ func CliqueRLP(header *types.Header) []byte { } func encodeSigHeader(w io.Writer, header *types.Header) { - err := rlp.Encode(w, []interface{}{ + enc := []interface{}{ header.ParentHash, header.UncleHash, header.Coinbase, @@ -741,8 +744,11 @@ func encodeSigHeader(w io.Writer, header *types.Header) { header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short header.MixDigest, header.Nonce, - }) - if err != nil { + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index b04cb24fb4..9b9657e190 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -284,16 +284,18 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if header.GasUsed > header.GasLimit { return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } - - // Verify that the gas limit remains within allowed bounds - diff := int64(parent.GasLimit) - int64(header.GasLimit) - if diff < 0 { - diff *= -1 - } - limit := parent.GasLimit / params.GasLimitBoundDivisor - - if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { - return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) + // Verify the block's gas usage and (if applicable) verify the base fee. + if !chain.Config().IsLondon(header.Number) { + // Verify BaseFee not present before EIP-1559 fork. + if header.BaseFee != nil { + return fmt.Errorf("invalid baseFee before fork: have %d, expected 'nil'", header.BaseFee) + } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { + return err + } + } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + // Verify the header's EIP-1559 attributes. + return err } // Verify that the block number is parent's +1 if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { @@ -604,7 +606,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { hasher := sha3.NewLegacyKeccak256() - rlp.Encode(hasher, []interface{}{ + enc := []interface{}{ header.ParentHash, header.UncleHash, header.Coinbase, @@ -618,7 +620,11 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { header.GasUsed, header.Time, header.Extra, - }) + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + rlp.Encode(hasher, enc) hasher.Sum(hash[:0]) return hash } diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go new file mode 100644 index 0000000000..8fca0fdc70 --- /dev/null +++ b/consensus/misc/eip1559.go @@ -0,0 +1,93 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package misc + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// VerifyEip1559Header verifies some header attributes which were changed in EIP-1559, +// - gas limit check +// - basefee check +func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Header) error { + // Verify that the gas limit remains within allowed bounds + parentGasLimit := parent.GasLimit + if !config.IsLondon(parent.Number) { + parentGasLimit = parent.GasLimit * params.ElasticityMultiplier + } + if err := VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { + return err + } + // Verify the header is not malformed + if header.BaseFee == nil { + return fmt.Errorf("header is missing baseFee") + } + // Verify the baseFee is correct based on the parent header. + expectedBaseFee := CalcBaseFee(config, parent) + if header.BaseFee.Cmp(expectedBaseFee) != 0 { + return fmt.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d", + expectedBaseFee, header.BaseFee, parent.BaseFee, parent.GasUsed) + } + return nil +} + +// CalcBaseFee calculates the basefee of the header. +func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { + // If the current block is the first EIP-1559 block, return the InitialBaseFee. + if !config.IsLondon(parent.Number) { + return new(big.Int).SetUint64(params.InitialBaseFee) + } + + var ( + parentGasTarget = parent.GasLimit / params.ElasticityMultiplier + parentGasTargetBig = new(big.Int).SetUint64(parentGasTarget) + baseFeeChangeDenominator = new(big.Int).SetUint64(params.BaseFeeChangeDenominator) + ) + // If the parent gasUsed is the same as the target, the baseFee remains unchanged. + if parent.GasUsed == parentGasTarget { + return new(big.Int).Set(parent.BaseFee) + } + if parent.GasUsed > parentGasTarget { + // If the parent block used more gas than its target, the baseFee should increase. + gasUsedDelta := new(big.Int).SetUint64(parent.GasUsed - parentGasTarget) + x := new(big.Int).Mul(parent.BaseFee, gasUsedDelta) + y := x.Div(x, parentGasTargetBig) + baseFeeDelta := math.BigMax( + x.Div(y, baseFeeChangeDenominator), + common.Big1, + ) + + return x.Add(parent.BaseFee, baseFeeDelta) + } else { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease. + gasUsedDelta := new(big.Int).SetUint64(parentGasTarget - parent.GasUsed) + x := new(big.Int).Mul(parent.BaseFee, gasUsedDelta) + y := x.Div(x, parentGasTargetBig) + baseFeeDelta := x.Div(y, baseFeeChangeDenominator) + + return math.BigMax( + x.Sub(parent.BaseFee, baseFeeDelta), + common.Big0, + ) + } +} diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559_test.go new file mode 100644 index 0000000000..333411db51 --- /dev/null +++ b/consensus/misc/eip1559_test.go @@ -0,0 +1,133 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package misc + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +// copyConfig does a _shallow_ copy of a given config. Safe to set new values, but +// do not use e.g. SetInt() on the numbers. For testing only +func copyConfig(original *params.ChainConfig) *params.ChainConfig { + return ¶ms.ChainConfig{ + ChainID: original.ChainID, + HomesteadBlock: original.HomesteadBlock, + DAOForkBlock: original.DAOForkBlock, + DAOForkSupport: original.DAOForkSupport, + EIP150Block: original.EIP150Block, + EIP150Hash: original.EIP150Hash, + EIP155Block: original.EIP155Block, + EIP158Block: original.EIP158Block, + ByzantiumBlock: original.ByzantiumBlock, + ConstantinopleBlock: original.ConstantinopleBlock, + PetersburgBlock: original.PetersburgBlock, + IstanbulBlock: original.IstanbulBlock, + MuirGlacierBlock: original.MuirGlacierBlock, + BerlinBlock: original.BerlinBlock, + LondonBlock: original.LondonBlock, + EWASMBlock: original.EWASMBlock, + CatalystBlock: original.CatalystBlock, + Ethash: original.Ethash, + Clique: original.Clique, + } +} + +func config() *params.ChainConfig { + config := copyConfig(params.TestChainConfig) + config.LondonBlock = big.NewInt(5) + return config +} + +// TestBlockGasLimits tests the gasLimit checks for blocks both across +// the EIP-1559 boundary and post-1559 blocks +func TestBlockGasLimits(t *testing.T) { + initial := new(big.Int).SetUint64(params.InitialBaseFee) + + for i, tc := range []struct { + pGasLimit uint64 + pNum int64 + gasLimit uint64 + ok bool + }{ + // Transitions from non-london to london + {10000000, 4, 20000000, true}, // No change + {10000000, 4, 20019530, true}, // Upper limit + {10000000, 4, 20019531, false}, // Upper +1 + {10000000, 4, 19980470, true}, // Lower limit + {10000000, 4, 19980469, false}, // Lower limit -1 + // London to London + {20000000, 5, 20000000, true}, + {20000000, 5, 20019530, true}, // Upper limit + {20000000, 5, 20019531, false}, // Upper limit +1 + {20000000, 5, 19980470, true}, // Lower limit + {20000000, 5, 19980469, false}, // Lower limit -1 + {40000000, 5, 40039061, true}, // Upper limit + {40000000, 5, 40039062, false}, // Upper limit +1 + {40000000, 5, 39960939, true}, // lower limit + {40000000, 5, 39960938, false}, // Lower limit -1 + } { + parent := &types.Header{ + GasUsed: tc.pGasLimit / 2, + GasLimit: tc.pGasLimit, + BaseFee: initial, + Number: big.NewInt(tc.pNum), + } + header := &types.Header{ + GasUsed: tc.gasLimit / 2, + GasLimit: tc.gasLimit, + BaseFee: initial, + Number: big.NewInt(tc.pNum + 1), + } + err := VerifyEip1559Header(config(), parent, header) + if tc.ok && err != nil { + t.Errorf("test %d: Expected valid header: %s", i, err) + } + if !tc.ok && err == nil { + t.Errorf("test %d: Expected invalid header", i) + } + } +} + +// TestCalcBaseFee assumes all blocks are 1559-blocks +func TestCalcBaseFee(t *testing.T) { + tests := []struct { + parentBaseFee int64 + parentGasLimit uint64 + parentGasUsed uint64 + expectedBaseFee int64 + }{ + {params.InitialBaseFee, 20000000, 10000000, params.InitialBaseFee}, // usage == target + {params.InitialBaseFee, 20000000, 9000000, 987500000}, // usage below target + {params.InitialBaseFee, 20000000, 11000000, 1012500000}, // usage above target + } + for i, test := range tests { + parent := &types.Header{ + Number: common.Big32, + GasLimit: test.parentGasLimit, + GasUsed: test.parentGasUsed, + BaseFee: big.NewInt(test.parentBaseFee), + } + if have, want := CalcBaseFee(config(), parent), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 { + t.Errorf("test %d: have %d want %d, ", i, have, want) + } + } +} diff --git a/consensus/misc/gaslimit.go b/consensus/misc/gaslimit.go new file mode 100644 index 0000000000..25f35300b9 --- /dev/null +++ b/consensus/misc/gaslimit.go @@ -0,0 +1,42 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package misc + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/params" +) + +// VerifyGaslimit verifies the header gas limit according increase/decrease +// in relation to the parent gas limit. +func VerifyGaslimit(parentGasLimit, headerGasLimit uint64) error { + // Verify that the gas limit remains within allowed bounds + diff := int64(parentGasLimit) - int64(headerGasLimit) + if diff < 0 { + diff *= -1 + } + limit := parentGasLimit / params.GasLimitBoundDivisor + if uint64(diff) >= limit { + return fmt.Errorf("invalid gas limit: have %d, want %d +-= %d", headerGasLimit, parentGasLimit, limit-1) + } + if headerGasLimit < params.MinGasLimit { + return errors.New("invalid gas limit below 5000") + } + return nil +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index a8ce4c7b9a..8ace9f6a6d 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -73,6 +73,10 @@ func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *B return db, blockchain, err } +func newGwei(n int64) *big.Int { + return new(big.Int).Mul(big.NewInt(n), big.NewInt(params.GWei)) +} + // Test fork of length N starting from block i func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { // Copy old chain up to #i into a new db @@ -3109,3 +3113,156 @@ func TestEIP2718Transition(t *testing.T) { } } + +// TestEIP1559Transition tests the following: +// +// 1. A tranaction whose feeCap is greater than the baseFee is valid. +// 2. Gas accounting for access lists on EIP-1559 transactions is correct. +// 3. Only the transaction's tip will be received by the coinbase. +// 4. The transaction sender pays for both the tip and baseFee. +// 5. The coinbase receives only the partially realized tip when +// feeCap - tip < baseFee. +// 6. Legacy transaction behave as expected (e.g. gasPrice = feeCap = tip). +func TestEIP1559Transition(t *testing.T) { + var ( + aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") + + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + + // A sender who makes transactions, has some funds + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + gspec = &Genesis{ + Config: params.AllEthashProtocolChanges, + Alloc: GenesisAlloc{ + addr1: {Balance: funds}, + addr2: {Balance: funds}, + // The address 0xAAAA sloads 0x00 and 0x01 + aa: { + Code: []byte{ + byte(vm.PC), + byte(vm.PC), + byte(vm.SLOAD), + byte(vm.SLOAD), + }, + Nonce: 0, + Balance: big.NewInt(0), + }, + }, + } + ) + + gspec.Config.BerlinBlock = common.Big0 + gspec.Config.LondonBlock = common.Big0 + genesis := gspec.MustCommit(db) + signer := types.LatestSigner(gspec.Config) + + blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + + // One transaction to 0xAAAA + accesses := types.AccessList{types.AccessTuple{ + Address: aa, + StorageKeys: []common.Hash{{0}}, + }} + + txdata := &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: &aa, + Gas: 30000, + FeeCap: newGwei(5), + Tip: big.NewInt(2), + AccessList: accesses, + Data: []byte{}, + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key1) + + b.AddTx(tx) + }) + + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block := chain.GetBlockByNumber(1) + + // 1+2: Ensure EIP-1559 access lists are accounted for via gas usage. + expectedGas := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + + vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929 + if block.GasUsed() != expectedGas { + t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed()) + } + + state, _ := chain.State() + + // 3: Ensure that miner received only the tx's tip. + actual := state.GetBalance(block.Coinbase()) + expected := new(big.Int).Add( + new(big.Int).SetUint64(block.GasUsed()*block.Transactions()[0].Tip().Uint64()), + ethash.ConstantinopleBlockReward, + ) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } + + // 4: Ensure the tx sender paid for the gasUsed * (tip + block baseFee). + actual = new(big.Int).Sub(funds, state.GetBalance(addr1)) + expected = new(big.Int).SetUint64(block.GasUsed() * (block.Transactions()[0].Tip().Uint64() + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { + t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) + } + + blocks, _ = GenerateChain(gspec.Config, block, engine, db, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{2}) + + txdata := &types.LegacyTx{ + Nonce: 0, + To: &aa, + Gas: 30000, + GasPrice: newGwei(5), + } + tx := types.NewTx(txdata) + tx, _ = types.SignTx(tx, signer, key2) + + b.AddTx(tx) + }) + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + block = chain.GetBlockByNumber(2) + state, _ = chain.State() + effectiveTip := block.Transactions()[0].Tip().Uint64() - block.BaseFee().Uint64() + + // 6+5: Ensure that miner received only the tx's effective tip. + actual = state.GetBalance(block.Coinbase()) + expected = new(big.Int).Add( + new(big.Int).SetUint64(block.GasUsed()*effectiveTip), + ethash.ConstantinopleBlockReward, + ) + if actual.Cmp(expected) != 0 { + t.Fatalf("miner balance incorrect: expected %d, got %d", expected, actual) + } + + // 4: Ensure the tx sender paid for the gasUsed * (effectiveTip + block baseFee). + actual = new(big.Int).Sub(funds, state.GetBalance(addr2)) + expected = new(big.Int).SetUint64(block.GasUsed() * (effectiveTip + block.BaseFee().Uint64())) + if actual.Cmp(expected) != 0 { + t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index e058e5a78e..b1b7dc3591 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -253,7 +253,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S time = parent.Time() + 10 // block time is fixed at 10 seconds } - return &types.Header{ + header := &types.Header{ Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), @@ -267,6 +267,12 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S Number: new(big.Int).Add(parent.Number(), common.Big1), Time: time, } + + if chain.Config().IsLondon(parent.Number()) { + header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header()) + } + + return header } // makeHeaderChain creates a deterministic chain of headers rooted at parent. diff --git a/core/error.go b/core/error.go index 197dd81567..3d62cb9bcf 100644 --- a/core/error.go +++ b/core/error.go @@ -71,4 +71,8 @@ var ( // ErrTxTypeNotSupported is returned if a transaction is not supported in the // current network configuration. ErrTxTypeNotSupported = types.ErrTxTypeNotSupported + + // ErrFeeCapTooLow is returned if the transaction fee cap is less than the + // the base fee of the block. + ErrFeeCapTooLow = errors.New("fee cap less than block base fee") ) diff --git a/core/evm.go b/core/evm.go index 8f69d51499..6c67fc4376 100644 --- a/core/evm.go +++ b/core/evm.go @@ -37,13 +37,20 @@ type ChainContext interface { // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { + var ( + beneficiary common.Address + baseFee *big.Int + ) + // If we don't have an explicit author (i.e. not mining), extract from the header - var beneficiary common.Address if author == nil { beneficiary, _ = chain.Engine().Author(header) // Ignore error, we're past header validation } else { beneficiary = *author } + if header.BaseFee != nil { + baseFee = new(big.Int).Set(header.BaseFee) + } return vm.BlockContext{ CanTransfer: CanTransfer, Transfer: Transfer, @@ -52,6 +59,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common BlockNumber: new(big.Int).Set(header.Number), Time: new(big.Int).SetUint64(header.Time), Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: baseFee, GasLimit: header.GasLimit, } } diff --git a/core/genesis.go b/core/genesis.go index d7d08e0909..b68ae4ef56 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -291,6 +291,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { if g.Difficulty == nil { head.Difficulty = params.GenesisDifficulty } + if g.Config != nil && g.Config.IsLondon(common.Big0) { + head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee) + } statedb.Commit(false) statedb.Database().TrieDB().Commit(root, true, nil) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 05394321f7..ecdfa67f00 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -63,7 +63,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return } // Convert the transaction into an executable message and pre-cache its sender - msg, err := tx.AsMessage(signer) + msg, err := tx.AsMessage(signer, header.BaseFee) if err != nil { return // Also invalid block, bail out } diff --git a/core/state_processor.go b/core/state_processor.go index 40a953f0d4..6f6bc1879b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -71,9 +71,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number)) + msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee) if err != nil { - return nil, nil, 0, err + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } statedb.Prepare(tx.Hash(), block.Hash(), i) receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) @@ -139,7 +139,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { - msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) + msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee) if err != nil { return nil, err } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 5976ecc3d4..28baf6e7d2 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -38,75 +39,157 @@ import ( // contain invalid transactions func TestStateProcessorErrors(t *testing.T) { var ( - signer = types.HomesteadSigner{} + signer = types.LatestSigner(params.TestChainConfig) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ - Config: params.TestChainConfig, - } - genesis = gspec.MustCommit(db) - blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) ) - defer blockchain.Stop() var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey) return tx } - for i, tt := range []struct { - txs []*types.Transaction - want string - }{ - { - txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, tip, feeCap *big.Int) *types.Transaction { + tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ + Nonce: nonce, + Tip: tip, + FeeCap: feeCap, + Gas: 0, + To: &to, + Value: big.NewInt(0), + }), signer, testKey) + return tx + } + { // Tests against a 'recent' chain definition + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + }, + } + genesis = gspec.MustCommit(db) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { // ErrNonceTooLow + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 1 [0x0026256b3939ed97e2c4a6f3fce8ecf83bdcfa6d507c47838c308a1fb0436f62]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", + }, + { // ErrNonceTooHigh + txs: []*types.Transaction{ + makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xdebad714ca7f363bd0d8121c4518ad48fa469ca81b0a081be3d10c17460f751b]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", + }, + { // ErrGasLimitReached + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), 21000000, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xbd49d8dadfd47fb846986695f7d4da3f7b2c48c8da82dbc211a26eb124883de9]: gas limit reached", }, - want: "could not apply tx 1 [0x36bfa6d14f1cd35a1be8cc2322982a595fabc0e799f09c1de3bad7bd5b1f7626]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", - }, - { - txs: []*types.Transaction{ - makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), + { // ErrInsufficientFundsForTransfer + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(1000000000000000000), params.TxGas, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0x98c796b470f7fcab40aaef5c965a602b0238e1034cce6fb73823042dd0638d74]: insufficient funds for transfer: address 0x71562b71999873DB5b286dF957af199Ec94617F7", }, - want: "could not apply tx 0 [0x51cd272d41ef6011d8138e18bf4043797aca9b713c7d39a97563f9bbe6bdbe6f]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0", - }, - { - txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), 21000000, nil, nil), + { // ErrInsufficientFunds + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(900000000000000000), nil), + }, + want: "could not apply tx 0 [0x4a69690c4b0cd85e64d0d9ea06302455b01e10a83db964d60281739752003440]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 18900000000000000000000", }, - want: "could not apply tx 0 [0x54c58b530824b0bb84b7a98183f08913b5d74e1cebc368515ef3c65edf8eb56a]: gas limit reached", - }, - { - txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(1), params.TxGas, nil, nil), + // ErrGasUintOverflow + // One missing 'core' error is ErrGasUintOverflow: "gas uint64 overflow", + // In order to trigger that one, we'd have to allocate a _huge_ chunk of data, such that the + // multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment + { // ErrIntrinsicGas + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xcf3b049a0b516cb4f9274b3e2a264359e2ba53b2fb64b7bda2c634d5c9d01fca]: intrinsic gas too low: have 20000, want 21000", }, - want: "could not apply tx 0 [0x3094b17498940d92b13baccf356ce8bfd6f221e926abc903d642fa1466c5b50e]: insufficient funds for transfer: address 0x71562b71999873DB5b286dF957af199Ec94617F7", - }, - { - txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(0xffffff), nil), + { // ErrGasLimitReached + txs: []*types.Transaction{ + makeTx(0, common.Address{}, big.NewInt(0), params.TxGas*1000, big.NewInt(875000000), nil), + }, + want: "could not apply tx 0 [0xbd49d8dadfd47fb846986695f7d4da3f7b2c48c8da82dbc211a26eb124883de9]: gas limit reached", }, - want: "could not apply tx 0 [0xaa3f7d86802b1f364576d9071bf231e31d61b392d306831ac9cf706ff5371ce0]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 0 want 352321515000", - }, - { - txs: []*types.Transaction{ - makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), - makeTx(1, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), - makeTx(2, common.Address{}, big.NewInt(0), params.TxGas, nil, nil), - makeTx(3, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(0), nil), + { // ErrFeeCapTooLow + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), + }, + want: "could not apply tx 0 [0x21e9b9015150fc7f6bd5059890a5e1727f2452df285e8a84f4ca61a74c159ded]: fee cap less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, feeCap: 0 baseFee: 875000000", }, - want: "could not apply tx 3 [0x836fab5882205362680e49b311a20646de03b630920f18ec6ee3b111a2cf6835]: intrinsic gas too low: have 20000, want 21000", - }, - // The last 'core' error is ErrGasUintOverflow: "gas uint64 overflow", but in order to - // trigger that one, we'd have to allocate a _huge_ chunk of data, such that the - // multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment - } { - block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs) - _, err := blockchain.InsertChain(types.Blocks{block}) - if err == nil { - t.Fatal("block imported without errors") + } { + block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } } - if have, want := err.Error(), tt.want; have != want { - t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } + + // One final error is ErrTxTypeNotSupported. For this, we need an older chain + { + var ( + db = rawdb.NewMemoryDatabase() + gspec = &Genesis{ + Config: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + }, + Alloc: GenesisAlloc{ + common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + }, + } + genesis = gspec.MustCommit(db) + blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil) + ) + defer blockchain.Stop() + for i, tt := range []struct { + txs []*types.Transaction + want string + }{ + { // ErrTxTypeNotSupported + txs: []*types.Transaction{ + mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), + }, + want: "could not apply tx 0 [0x21e9b9015150fc7f6bd5059890a5e1727f2452df285e8a84f4ca61a74c159ded]: transaction type not supported", + }, + } { + block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) + _, err := blockchain.InsertChain(types.Blocks{block}) + if err == nil { + t.Fatal("block imported without errors") + } + if have, want := err.Error(), tt.want; have != want { + t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) + } } } } @@ -115,11 +198,11 @@ func TestStateProcessorErrors(t *testing.T) { // valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently // valid to be considered for import: // - valid pow (fake), ancestry, difficulty, gaslimit etc -func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions) *types.Block { +func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { header := &types.Header{ ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), - Difficulty: engine.CalcDifficulty(&fakeChainReader{params.TestChainConfig}, parent.Time()+10, &types.Header{ + Difficulty: engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{ Number: parent.Number(), Time: parent.Time(), Difficulty: parent.Difficulty(), @@ -130,8 +213,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr Time: parent.Time() + 10, UncleHash: types.EmptyUncleHash, } + if config.IsLondon(header.Number) { + header.BaseFee = misc.CalcBaseFee(config, parent.Header()) + } var receipts []*types.Receipt - // The post-state result doesn't need to be correct (this is a bad block), but we do need something there // Preferably something unique. So let's use a combo of blocknum + txhash hasher := sha3.NewLegacyKeccak256() diff --git a/core/state_transition.go b/core/state_transition.go index 05becd9a00..881d34f4bc 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + cmath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -49,6 +50,8 @@ type StateTransition struct { msg Message gas uint64 gasPrice *big.Int + feeCap *big.Int + tip *big.Int initialGas uint64 value *big.Int data []byte @@ -62,6 +65,8 @@ type Message interface { To() *common.Address GasPrice() *big.Int + FeeCap() *big.Int + Tip() *big.Int Gas() uint64 Value() *big.Int @@ -154,6 +159,8 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition evm: evm, msg: msg, gasPrice: msg.GasPrice(), + feeCap: msg.FeeCap(), + tip: msg.Tip(), value: msg.Value(), data: msg.Data(), state: evm.StateDB, @@ -206,6 +213,15 @@ func (st *StateTransition) preCheck() error { st.msg.From().Hex(), msgNonce, stNonce) } } + // Make sure that transaction feeCap is greater than the baseFee (post london) + if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { + // This will panic if baseFee is nil, but basefee presence is verified + // as part of header validation. + if st.feeCap.Cmp(st.evm.Context.BaseFee) < 0 { + return fmt.Errorf("%w: address %v, feeCap: %s baseFee: %s", ErrFeeCapTooLow, + st.msg.From().Hex(), st.feeCap, st.evm.Context.BaseFee) + } + } return st.buyGas() } @@ -281,7 +297,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // After EIP-3529: refunds are capped to gasUsed / 5 st.refundGas(params.RefundQuotientEIP3529) } - st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) + effectiveTip := st.gasPrice + if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { + effectiveTip = cmath.BigMin(st.tip, new(big.Int).Sub(st.feeCap, st.evm.Context.BaseFee)) + } + st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) return &ExecutionResult{ UsedGas: st.gasUsed(), diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go index 65ee95adf6..48102a1d40 100644 --- a/core/types/access_list_tx.go +++ b/core/types/access_list_tx.go @@ -94,7 +94,6 @@ func (tx *AccessListTx) copy() TxData { } // accessors for innerTx. - func (tx *AccessListTx) txType() byte { return AccessListTxType } func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } func (tx *AccessListTx) protected() bool { return true } @@ -102,6 +101,8 @@ func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } func (tx *AccessListTx) data() []byte { return tx.Data } func (tx *AccessListTx) gas() uint64 { return tx.Gas } func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) tip() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) feeCap() *big.Int { return tx.GasPrice } func (tx *AccessListTx) value() *big.Int { return tx.Value } func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } func (tx *AccessListTx) to() *common.Address { return tx.To } diff --git a/core/types/block.go b/core/types/block.go index a3318f8779..5f3dbb957b 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -82,6 +82,9 @@ type Header struct { Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *big.Int `json:"baseFee" rlp:"optional"` } // field type overrides for gencodec @@ -92,6 +95,7 @@ type headerMarshaling struct { GasUsed hexutil.Uint64 Time hexutil.Uint64 Extra hexutil.Bytes + BaseFee *hexutil.Big Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON } @@ -229,6 +233,9 @@ func CopyHeader(h *Header) *Header { if cpy.Number = new(big.Int); h.Number != nil { cpy.Number.Set(h.Number) } + if h.BaseFee != nil { + cpy.BaseFee = new(big.Int).Set(h.BaseFee) + } if len(h.Extra) > 0 { cpy.Extra = make([]byte, len(h.Extra)) copy(cpy.Extra, h.Extra) @@ -289,6 +296,13 @@ func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash } func (b *Block) UncleHash() common.Hash { return b.header.UncleHash } func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) } +func (b *Block) BaseFee() *big.Int { + if b.header.BaseFee == nil { + return nil + } + return new(big.Int).Set(b.header.BaseFee) +} + func (b *Block) Header() *Header { return CopyHeader(b.header) } // Body returns the non-header content of the block. diff --git a/core/types/block_test.go b/core/types/block_test.go index 63904f882c..9ecb1a4d8f 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -68,6 +68,71 @@ func TestBlockEncoding(t *testing.T) { } } +func TestEIP1559BlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + + check("Difficulty", block.Difficulty(), big.NewInt(131072)) + check("GasLimit", block.GasLimit(), uint64(3141592)) + check("GasUsed", block.GasUsed(), uint64(21000)) + check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) + check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) + check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) + check("Hash", block.Hash(), common.HexToHash("c7252048cd273fe0dac09650027d07f0e3da4ee0675ebbb26627cea92729c372")) + check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) + check("Time", block.Time(), uint64(1426516743)) + check("Size", block.Size(), common.StorageSize(len(blockEnc))) + check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(params.InitialBaseFee)) + + tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) + tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) + + addr := common.HexToAddress("0x0000000000000000000000000000000000000001") + accesses := AccessList{AccessTuple{ + Address: addr, + StorageKeys: []common.Hash{ + {0}, + }, + }} + to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") + txdata := &DynamicFeeTx{ + ChainID: big.NewInt(1), + Nonce: 0, + To: &to, + Gas: 123457, + FeeCap: new(big.Int).Set(block.BaseFee()), + Tip: big.NewInt(0), + AccessList: accesses, + Data: []byte{}, + } + tx2 := NewTx(txdata) + tx2, err := tx2.WithSignature(LatestSignerForChainID(big.NewInt(1)), common.Hex2Bytes("fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a800")) + if err != nil { + t.Fatal("invalid signature error: ", err) + } + + check("len(Transactions)", len(block.Transactions()), 2) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) + check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) + check("Transactions[1].Type", block.Transactions()[1].Type(), tx2.Type()) + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + func TestEIP2718BlockEncoding(t *testing.T) { blockEnc := common.FromHex("f90319f90211a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c0") var block Block diff --git a/core/types/dynamic_fee_tx.go b/core/types/dynamic_fee_tx.go new file mode 100644 index 0000000000..777babe027 --- /dev/null +++ b/core/types/dynamic_fee_tx.go @@ -0,0 +1,104 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type DynamicFeeTx struct { + ChainID *big.Int + Nonce uint64 + Tip *big.Int + FeeCap *big.Int + Gas uint64 + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int + Data []byte + AccessList AccessList + + // Signature values + V *big.Int `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *DynamicFeeTx) copy() TxData { + cpy := &DynamicFeeTx{ + Nonce: tx.Nonce, + To: tx.To, // TODO: copy pointed-to address + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + Tip: new(big.Int), + FeeCap: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.Tip != nil { + cpy.Tip.Set(tx.Tip) + } + if tx.FeeCap != nil { + cpy.FeeCap.Set(tx.FeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType } +func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID } +func (tx *DynamicFeeTx) protected() bool { return true } +func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList } +func (tx *DynamicFeeTx) data() []byte { return tx.Data } +func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas } +func (tx *DynamicFeeTx) feeCap() *big.Int { return tx.FeeCap } +func (tx *DynamicFeeTx) tip() *big.Int { return tx.Tip } +func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.FeeCap } +func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } +func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } +func (tx *DynamicFeeTx) to() *common.Address { return tx.To } + +func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go index 41ad44f379..f5e0f70783 100644 --- a/core/types/legacy_tx.go +++ b/core/types/legacy_tx.go @@ -91,13 +91,14 @@ func (tx *LegacyTx) copy() TxData { } // accessors for innerTx. - func (tx *LegacyTx) txType() byte { return LegacyTxType } func (tx *LegacyTx) chainID() *big.Int { return deriveChainId(tx.V) } func (tx *LegacyTx) accessList() AccessList { return nil } func (tx *LegacyTx) data() []byte { return tx.Data } func (tx *LegacyTx) gas() uint64 { return tx.Gas } func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) tip() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) feeCap() *big.Int { return tx.GasPrice } func (tx *LegacyTx) value() *big.Int { return tx.Value } func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } func (tx *LegacyTx) to() *common.Address { return tx.To } diff --git a/core/types/receipt.go b/core/types/receipt.go index 6b519a79d2..b949bd2bd5 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -120,10 +120,6 @@ func (r *Receipt) EncodeRLP(w io.Writer) error { if r.Type == LegacyTxType { return rlp.Encode(w, data) } - // It's an EIP-2718 typed TX receipt. - if r.Type != AccessListTxType { - return ErrTxTypeNotSupported - } buf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(buf) buf.Reset() @@ -159,7 +155,7 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { return errEmptyTypedReceipt } r.Type = b[0] - if r.Type == AccessListTxType { + if r.Type == AccessListTxType || r.Type == DynamicFeeTxType { var dec receiptRLP if err := rlp.DecodeBytes(b[1:], &dec); err != nil { return err @@ -263,6 +259,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { case AccessListTxType: w.WriteByte(AccessListTxType) rlp.Encode(w, data) + case DynamicFeeTxType: + w.WriteByte(DynamicFeeTxType) + rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for // DeriveSha, the error will be caught matching the derived hash diff --git a/core/types/transaction.go b/core/types/transaction.go index a35e07a5a3..ace1843e93 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -26,6 +26,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) @@ -42,6 +43,7 @@ var ( const ( LegacyTxType = iota AccessListTxType + DynamicFeeTxType ) // Transaction is an Ethereum transaction. @@ -74,6 +76,8 @@ type TxData interface { data() []byte gas() uint64 gasPrice() *big.Int + tip() *big.Int + feeCap() *big.Int value() *big.Int nonce() uint64 to() *common.Address @@ -177,6 +181,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { var inner AccessListTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err + case DynamicFeeTxType: + var inner DynamicFeeTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported } @@ -260,6 +268,12 @@ func (tx *Transaction) Gas() uint64 { return tx.inner.gas() } // GasPrice returns the gas price of the transaction. func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) } +// Tip returns the tip per gas of the transaction. +func (tx *Transaction) Tip() *big.Int { return new(big.Int).Set(tx.inner.tip()) } + +// FeeCap returns the fee cap per gas of the transaction. +func (tx *Transaction) FeeCap() *big.Int { return new(big.Int).Set(tx.inner.feeCap()) } + // Value returns the ether amount of the transaction. func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) } @@ -486,12 +500,14 @@ type Message struct { amount *big.Int gasLimit uint64 gasPrice *big.Int + feeCap *big.Int + tip *big.Int data []byte accessList AccessList checkNonce bool } -func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, accessList AccessList, checkNonce bool) Message { +func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, feeCap, tip *big.Int, data []byte, accessList AccessList, checkNonce bool) Message { return Message{ from: from, to: to, @@ -499,6 +515,8 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b amount: amount, gasLimit: gasLimit, gasPrice: gasPrice, + feeCap: feeCap, + tip: tip, data: data, accessList: accessList, checkNonce: checkNonce, @@ -506,11 +524,13 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b } // AsMessage returns the transaction as a core.Message. -func (tx *Transaction) AsMessage(s Signer) (Message, error) { +func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { msg := Message{ nonce: tx.Nonce(), gasLimit: tx.Gas(), gasPrice: new(big.Int).Set(tx.GasPrice()), + feeCap: new(big.Int).Set(tx.FeeCap()), + tip: new(big.Int).Set(tx.Tip()), to: tx.To(), amount: tx.Value(), data: tx.Data(), @@ -518,6 +538,11 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) { checkNonce: true, } + // If baseFee provided, set gasPrice to effectiveGasPrice. + if baseFee != nil { + msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.tip, baseFee), msg.feeCap) + } + var err error msg.from, err = Sender(s, tx) return msg, err @@ -526,6 +551,8 @@ func (tx *Transaction) AsMessage(s Signer) (Message, error) { func (m Message) From() common.Address { return m.from } func (m Message) To() *common.Address { return m.to } func (m Message) GasPrice() *big.Int { return m.gasPrice } +func (m Message) FeeCap() *big.Int { return m.feeCap } +func (m Message) Tip() *big.Int { return m.tip } func (m Message) Value() *big.Int { return m.amount } func (m Message) Gas() uint64 { return m.gasLimit } func (m Message) Nonce() uint64 { return m.nonce } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index e561485556..ecdbd70afa 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -30,15 +30,19 @@ type txJSON struct { Type hexutil.Uint64 `json:"type"` // Common transaction fields: - Nonce *hexutil.Uint64 `json:"nonce"` - GasPrice *hexutil.Big `json:"gasPrice"` - Gas *hexutil.Uint64 `json:"gas"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"input"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - To *common.Address `json:"to"` + Nonce *hexutil.Uint64 `json:"nonce"` + GasPrice *hexutil.Big `json:"gasPrice"` + FeeCap *hexutil.Big `json:"feeCap"` + Tip *hexutil.Big `json:"tip"` + MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` + MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` + Gas *hexutil.Uint64 `json:"gas"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"input"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + To *common.Address `json:"to"` // Access list transaction fields: ChainID *hexutil.Big `json:"chainId,omitempty"` @@ -79,6 +83,19 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) + case *DynamicFeeTx: + enc.ChainID = (*hexutil.Big)(tx.ChainID) + enc.AccessList = &tx.AccessList + enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.FeeCap = (*hexutil.Big)(tx.FeeCap) + enc.Tip = (*hexutil.Big)(tx.Tip) + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.To = t.To() + enc.V = (*hexutil.Big)(tx.V) + enc.R = (*hexutil.Big)(tx.R) + enc.S = (*hexutil.Big)(tx.S) } return json.Marshal(&enc) } @@ -191,6 +208,75 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { } } + case DynamicFeeTxType: + var itx DynamicFeeTx + inner = &itx + // Access list is optional for now. + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.To != nil { + itx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + switch { + case dec.Tip == nil && dec.MaxPriorityFeePerGas == nil: + return errors.New("at least one of 'tip' or 'maxPriorityFeePerGas' must be defined") + case dec.Tip != nil && dec.MaxPriorityFeePerGas != nil: + return errors.New("only one of 'tip' or 'maxPriorityFeePerGas' may be defined") + case dec.Tip != nil && dec.MaxPriorityFeePerGas == nil: + itx.Tip = (*big.Int)(dec.Tip) + case dec.Tip == nil && dec.MaxPriorityFeePerGas != nil: + itx.Tip = (*big.Int)(dec.MaxPriorityFeePerGas) + } + switch { + case dec.FeeCap == nil && dec.MaxFeePerGas == nil: + return errors.New("at least one of 'feeCap' or 'maxFeePerGas' must be defined") + case dec.FeeCap != nil && dec.MaxFeePerGas != nil: + return errors.New("only one of 'feeCap' or 'maxFeePerGas' may be defined") + case dec.FeeCap != nil && dec.MaxFeePerGas == nil: + itx.FeeCap = (*big.Int)(dec.FeeCap) + case dec.FeeCap == nil && dec.MaxFeePerGas != nil: + itx.FeeCap = (*big.Int)(dec.MaxFeePerGas) + } + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + default: return ErrTxTypeNotSupported } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 5d94b26b36..e553e6af96 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -40,6 +40,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer switch { + case config.IsLondon(blockNumber): + signer = NewLondonSigner(config.ChainID) case config.IsBerlin(blockNumber): signer = NewEIP2930Signer(config.ChainID) case config.IsEIP155(blockNumber): @@ -61,6 +63,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.ChainConfig) Signer { if config.ChainID != nil { + if config.LondonBlock != nil { + return NewLondonSigner(config.ChainID) + } if config.BerlinBlock != nil { return NewEIP2930Signer(config.ChainID) } @@ -82,7 +87,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer { if chainID == nil { return HomesteadSigner{} } - return NewEIP2930Signer(chainID) + return NewLondonSigner(chainID) } // SignTx signs the transaction using the given signer and private key. @@ -165,6 +170,72 @@ type Signer interface { Equal(Signer) bool } +type londonSigner struct{ eip2930Signer } + +// NewLondonSigner returns a signer that accepts +// - EIP-1559 dynamic fee transactions +// - EIP-2930 access list transactions, +// - EIP-155 replay protected transactions, and +// - legacy Homestead transactions. +func NewLondonSigner(chainId *big.Int) Signer { + return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}} +} + +func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.Type() != DynamicFeeTxType { + return s.eip2930Signer.Sender(tx) + } + V, R, S := tx.RawSignatureValues() + // DynamicFee txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. + V = new(big.Int).Add(V, big.NewInt(27)) + if tx.ChainId().Cmp(s.chainId) != 0 { + return common.Address{}, ErrInvalidChainId + } + return recoverPlain(s.Hash(tx), R, S, V, true) +} + +func (s londonSigner) Equal(s2 Signer) bool { + x, ok := s2.(londonSigner) + return ok && x.chainId.Cmp(s.chainId) == 0 +} + +func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + txdata, ok := tx.inner.(*DynamicFeeTx) + if !ok { + return s.eip2930Signer.SignatureValues(tx, sig) + } + // Check that chain ID of tx matches the signer. We also accept ID zero here, + // because it indicates that the chain ID was not specified in the tx. + if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + return nil, nil, nil, ErrInvalidChainId + } + R, S, _ = decodeSignature(sig) + V = big.NewInt(int64(sig[64])) + return R, S, V, nil +} + +// Hash returns the hash to be signed by the sender. +// It does not uniquely identify the transaction. +func (s londonSigner) Hash(tx *Transaction) common.Hash { + if tx.Type() != DynamicFeeTxType { + return s.eip2930Signer.Hash(tx) + } + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.Tip(), + tx.FeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) +} + type eip2930Signer struct{ EIP155Signer } // NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions, @@ -192,8 +263,8 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { V = new(big.Int).Sub(V, s.chainIdMul) V.Sub(V, big8) case AccessListTxType: - // ACL txs are defined to use 0 and 1 as their recovery id, add - // 27 to become equivalent to unprotected Homestead signatures. + // AL txs are defined to use 0 and 1 as their recovery + // id, add 27 to become equivalent to unprotected Homestead signatures. V = new(big.Int).Add(V, big.NewInt(27)) default: return common.Address{}, ErrTxTypeNotSupported diff --git a/core/vm/eips.go b/core/vm/eips.go index 025502760b..4070a2db53 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -26,6 +26,7 @@ import ( var activators = map[int]func(*JumpTable){ 3529: enable3529, + 3198: enable3198, 2929: enable2929, 2200: enable2200, 1884: enable1884, @@ -154,3 +155,22 @@ func enable3529(jt *JumpTable) { jt[SSTORE].dynamicGas = gasSStoreEIP3529 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529 } + +// enable3198 applies EIP-3198 (BASEFEE Opcode) +// - Adds an opcode that returns the current block's base fee. +func enable3198(jt *JumpTable) { + // New opcode + jt[BASEFEE] = &operation{ + execute: opBaseFee, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opBaseFee implements BASEFEE opcode +func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee) + scope.Stack.push(baseFee) + return nil, nil +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 8e3c9fe00f..980dc6d201 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -93,6 +93,7 @@ type BlockContext struct { BlockNumber *big.Int // Provides information for NUMBER Time *big.Int // Provides information for TIME Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE } // TxContext provides the EVM with information about a transaction. diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a0609a0d78..329ad77cbf 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -68,6 +68,7 @@ type JumpTable [256]*operation func newLondonInstructionSet() JumpTable { instructionSet := newBerlinInstructionSet() enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529 + enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 return instructionSet } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index b0adf37d0c..286307ae91 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -103,6 +103,7 @@ const ( GASLIMIT CHAINID OpCode = 0x46 SELFBALANCE OpCode = 0x47 + BASEFEE OpCode = 0x48 ) // 0x50 range - 'storage' and execution. @@ -280,6 +281,7 @@ var opCodeToString = map[OpCode]string{ GASLIMIT: "GASLIMIT", CHAINID: "CHAINID", SELFBALANCE: "SELFBALANCE", + BASEFEE: "BASEFEE", // 0x50 range - 'storage' and execution. POP: "POP", @@ -432,6 +434,7 @@ var stringToOp = map[string]OpCode{ "CALLDATASIZE": CALLDATASIZE, "CALLDATACOPY": CALLDATACOPY, "CHAINID": CHAINID, + "BASEFEE": BASEFEE, "DELEGATECALL": DELEGATECALL, "STATICCALL": STATICCALL, "CODESIZE": CODESIZE, diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 84cfaf4d73..8d53739721 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -162,7 +162,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec signer := types.MakeSigner(eth.blockchain.Config(), block.Number()) for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) + msg, _ := tx.AsMessage(signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) if idx == txIndex { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 1c727f1366..172054e9b8 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -271,7 +271,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { - msg, _ := tx.AsMessage(signer) + msg, _ := tx.AsMessage(signer, task.block.BaseFee()) txctx := &txTraceContext{ index: i, hash: tx.Hash(), @@ -523,7 +523,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac defer pend.Done() // Fetch and execute the next transaction trace tasks for task := range jobs { - msg, _ := txs[task.index].AsMessage(signer) + msg, _ := txs[task.index].AsMessage(signer, block.BaseFee()) txctx := &txTraceContext{ index: task.index, hash: txs[task.index].Hash(), @@ -545,7 +545,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} // Generate the next state snapshot fast without tracing - msg, _ := tx.AsMessage(signer) + msg, _ := tx.AsMessage(signer, block.BaseFee()) statedb.Prepare(tx.Hash(), block.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { @@ -630,7 +630,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution var ( - msg, _ = tx.AsMessage(signer) + msg, _ = tx.AsMessage(signer, block.BaseFee()) txContext = core.NewEVMTxContext(msg) vmConf vm.Config dump *os.File diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 4c0240cd2c..24bce320cf 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -161,7 +161,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block // Recompute transactions up to the target index. signer := types.MakeSigner(b.chainConfig, block.Number()) for idx, tx := range block.Transactions() { - msg, _ := tx.AsMessage(signer) + msg, _ := tx.AsMessage(signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), b.chain, nil) if idx == txIndex { diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 9dc4c69631..8b01edd7b4 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -179,7 +179,7 @@ func TestPrestateTracerCreate2(t *testing.T) { } evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) - msg, err := tx.AsMessage(signer) + msg, err := tx.AsMessage(signer, nil) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } @@ -254,7 +254,7 @@ func TestCallTracer(t *testing.T) { } evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) - msg, err := tx.AsMessage(signer) + msg, err := tx.AsMessage(signer, nil) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } diff --git a/interfaces.go b/interfaces.go index afcdc17e58..857d309be9 100644 --- a/interfaces.go +++ b/interfaces.go @@ -120,6 +120,9 @@ type CallMsg struct { Value *big.Int // amount of wei sent along with the call Data []byte // input data, usually an ABI-encoded contract method invocation + FeeCap *big.Int // EIP-1559 fee cap per gas. + Tip *big.Int // EIP-1559 tip per gas. + AccessList types.AccessList // EIP-2930 access list. } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index fe3f80c038..7bc0477bd2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -799,7 +799,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { accessList = *args.AccessList } - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, accessList, false) + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false) return msg } @@ -1271,7 +1271,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } - if tx.Type() == types.AccessListTxType { + if tx.Type() != types.LegacyTxType { al := tx.AccessList() result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) @@ -1393,7 +1393,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } // Copy the original db so we don't modify it statedb := db.Copy() - msg := types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), input, accessList, false) + msg := types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), nil, nil, input, accessList, false) // Apply the transaction with the access list tracer tracer := vm.NewAccessListTracer(accessList, args.From, to, precompiles) diff --git a/les/odr_test.go b/les/odr_test.go index 0c75014d49..5fb881b5c4 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -135,7 +135,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai from := statedb.GetOrNewStateObject(bankAddr) from.SetBalance(math.MaxBig256) - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)} + msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), nil, nil, data, nil, false)} context := core.NewEVMBlockContext(header, bc, nil) txContext := core.NewEVMTxContext(msg) @@ -150,7 +150,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai header := lc.GetHeaderByHash(bhash) state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(bankAddr, math.MaxBig256) - msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)} + msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), nil, nil, data, nil, false)} context := core.NewEVMBlockContext(header, lc, nil) txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{}) diff --git a/les/state_accessor.go b/les/state_accessor.go index af5df36508..e276b06dc7 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -55,7 +55,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. signer := types.MakeSigner(leth.blockchain.Config(), block.Number()) for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) + msg, _ := tx.AsMessage(signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) statedb.Prepare(tx.Hash(), block.Hash(), idx) diff --git a/light/odr_test.go b/light/odr_test.go index 0fc45b8734..bb47c69eb1 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -194,7 +194,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) - msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, nil, false)} + msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), nil, nil, data, nil, false)} txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, chain, nil) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{}) diff --git a/params/bootnodes.go b/params/bootnodes.go index 20bf0b7cbf..99068750fc 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -69,9 +69,8 @@ var GoerliBootnodes = []string{ // BaikalBootnodes are the enode URLs of the P2P bootstrap nodes running on the // Baikal ephemeral test network. -// TODO: Set Baikal bootnodes var BaikalBootnodes = []string{ - "", + "enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303", } var V5Bootnodes = []string{ diff --git a/params/config.go b/params/config.go index 2cafa0e449..c777172381 100644 --- a/params/config.go +++ b/params/config.go @@ -231,7 +231,7 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), + LondonBlock: big.NewInt(500), Clique: &CliqueConfig{ Period: 30, Epoch: 30000, diff --git a/params/protocol_params.go b/params/protocol_params.go index 22b4c0651c..a49c4489f1 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -118,6 +118,10 @@ const ( // Introduced in Tangerine Whistle (Eip 150) CreateBySelfdestructGas uint64 = 25000 + BaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. + ElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. + InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. + MaxCodeSize = 24576 // Maximum bytecode to permit for a contract // Precompiled contract gas prices diff --git a/tests/init.go b/tests/init.go index 240b7159d5..b0a38e68b0 100644 --- a/tests/init.go +++ b/tests/init.go @@ -179,6 +179,19 @@ var Forks = map[string]*params.ChainConfig{ BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(0), }, + "Aleut": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + }, } // Returns the set of defined fork names diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 46834de6da..9778f058fe 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -297,7 +297,7 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) { if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil { accessList = *tx.AccessLists[ps.Indexes.Data] } - msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, accessList, true) + msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, nil, nil, data, accessList, true) return msg, nil } From 67e7f61af7fffbc47aadf48777e2dd5da39796ff Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 18 May 2021 01:10:28 +0200 Subject: [PATCH 553/948] core: fix failing tests (#22888) This PR fixes two errors that regressed when EIP-1559 was merged. --- core/blockchain_test.go | 4 ++-- core/state_processor_test.go | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 8ace9f6a6d..1869b0f183 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1357,8 +1357,8 @@ func TestEIP155Transition(t *testing.T) { } }) _, err := blockchain.InsertChain(blocks) - if err != types.ErrInvalidChainId { - t.Error("expected error:", types.ErrInvalidChainId) + if have, want := err, types.ErrInvalidChainId; !errors.Is(have, want) { + t.Errorf("have %v, want %v", have, want) } } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 28baf6e7d2..c15d3d276d 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -39,7 +39,22 @@ import ( // contain invalid transactions func TestStateProcessorErrors(t *testing.T) { var ( - signer = types.LatestSigner(params.TestChainConfig) + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + } + signer = types.LatestSigner(config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") ) var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction { @@ -61,7 +76,7 @@ func TestStateProcessorErrors(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() gspec = &Genesis{ - Config: params.TestChainConfig, + Config: config, Alloc: GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ Balance: big.NewInt(1000000000000000000), // 1 ether From bb9f9ccf4fdc1256a31abe634ab882baa6f1c888 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 18 May 2021 01:30:01 +0200 Subject: [PATCH 554/948] core/rawdb: wait for background freezing to exit when closing freezer (#22878) --- core/rawdb/database.go | 6 +++++- core/rawdb/freezer.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 3a0a26c61d..698f1ced84 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -197,7 +197,11 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st } // Freezer is consistent with the key-value database, permit combining the two if !frdb.readonly { - go frdb.freeze(db) + frdb.wg.Add(1) + go func() { + frdb.freeze(db) + frdb.wg.Done() + }() } return &freezerdb{ KeyValueStore: db, diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 94b99a64eb..ff8919b59e 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -84,6 +84,7 @@ type freezer struct { trigger chan chan struct{} // Manual blocking freeze trigger, test determinism quit chan struct{} + wg sync.WaitGroup closeOnce sync.Once } @@ -145,6 +146,8 @@ func (f *freezer) Close() error { var errs []error f.closeOnce.Do(func() { close(f.quit) + // Wait for any background freezing to stop + f.wg.Wait() for _, table := range f.tables { if err := table.Close(); err != nil { errs = append(errs, err) From b7a91663ab93abe360ae1b4bc68b75b5944625c4 Mon Sep 17 00:00:00 2001 From: Evolution404 <35091674+Evolution404@users.noreply.github.com> Date: Tue, 18 May 2021 16:22:58 +0800 Subject: [PATCH 555/948] core/asm: fix the bug of "00" prefix number (#22883) --- core/asm/lex_test.go | 4 ++++ core/asm/lexer.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/asm/lex_test.go b/core/asm/lex_test.go index 6b8bd3d740..53e05fbbba 100644 --- a/core/asm/lex_test.go +++ b/core/asm/lex_test.go @@ -60,6 +60,10 @@ func TestLexer(t *testing.T) { input: "0123abc", tokens: []token{{typ: lineStart}, {typ: number, text: "0123"}, {typ: element, text: "abc"}, {typ: eof}}, }, + { + input: "00123abc", + tokens: []token{{typ: lineStart}, {typ: number, text: "00123"}, {typ: element, text: "abc"}, {typ: eof}}, + }, { input: "@foo", tokens: []token{{typ: lineStart}, {typ: label, text: "foo"}, {typ: eof}}, diff --git a/core/asm/lexer.go b/core/asm/lexer.go index 9eb8f914ac..21cc8c4658 100644 --- a/core/asm/lexer.go +++ b/core/asm/lexer.go @@ -254,7 +254,7 @@ func lexInsideString(l *lexer) stateFn { func lexNumber(l *lexer) stateFn { acceptance := Numbers - if l.accept("0") || l.accept("xX") { + if l.accept("xX") { acceptance = HexadecimalNumbers } l.acceptRun(acceptance) From 32c1ed8a9c963dbf75faa80116b37f8cf53d4a38 Mon Sep 17 00:00:00 2001 From: Shane Bammel Date: Tue, 18 May 2021 03:37:18 -0500 Subject: [PATCH 556/948] core/forkid: fix off-by-one bug (#22879) * forkid: added failing test * forkid: fixed off-by-one bug --- core/forkid/forkid.go | 2 +- core/forkid/forkid_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 1bf3406828..f56ce85fee 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -155,7 +155,7 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() ui for i, fork := range forks { // If our head is beyond this fork, continue to the next (we have a dummy // fork of maxuint64 as the last item to always fail this check eventually). - if head > fork { + if head >= fork { continue } // Found the first unpassed fork block, check if our current state matches diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index a20598fa9d..2a7938bd29 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -163,6 +163,10 @@ func TestValidation(t *testing.T) { // neither forks passed at neither nodes, they may mismatch, but we still connect for now. {7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: math.MaxUint64}, nil}, + // Local is mainnet exactly on Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote + // is simply out of sync, accept. + {7280000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, + // Local is mainnet Petersburg, remote announces Byzantium + knowledge about Petersburg. Remote // is simply out of sync, accept. {7987396, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}, nil}, From 3e6f46caec51d82aef363632517eb5842eef6db6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 18 May 2021 11:48:41 +0200 Subject: [PATCH 557/948] p2p/discover/v4wire: use optional RLP field for EIP-868 seq (#22842) This changes the definitions of Ping and Pong, adding an optional field for the sequence number. This field was previously encoded/decoded using the "tail" struct tag, but using "optional" is much nicer. --- p2p/discover/v4_udp.go | 9 +++------ p2p/discover/v4_udp_test.go | 8 ++++---- p2p/discover/v4wire/v4wire.go | 23 ++++++++--------------- p2p/discover/v4wire/v4wire_test.go | 24 ++---------------------- 4 files changed, 17 insertions(+), 47 deletions(-) diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index ad23eee6b4..2b3eb48391 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -34,7 +34,6 @@ import ( "github.com/ethereum/go-ethereum/p2p/discover/v4wire" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/netutil" - "github.com/ethereum/go-ethereum/rlp" ) // Errors @@ -217,7 +216,7 @@ func (t *UDPv4) Ping(n *enode.Node) error { func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) { rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil) if err = <-rm.errc; err == nil { - seq = rm.reply.(*v4wire.Pong).ENRSeq() + seq = rm.reply.(*v4wire.Pong).ENRSeq } return seq, err } @@ -248,13 +247,12 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r } func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping { - seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq()) return &v4wire.Ping{ Version: 4, From: t.ourEndpoint(), To: v4wire.NewEndpoint(toaddr, 0), Expiration: uint64(time.Now().Add(expiration).Unix()), - Rest: []rlp.RawValue{seq}, + ENRSeq: t.localNode.Node().Seq(), } } @@ -660,12 +658,11 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.I req := h.Packet.(*v4wire.Ping) // Reply. - seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq()) t.send(from, fromID, &v4wire.Pong{ To: v4wire.NewEndpoint(from, req.From.TCP), ReplyTok: mac, Expiration: uint64(time.Now().Add(expiration).Unix()), - Rest: []rlp.RawValue{seq}, + ENRSeq: t.localNode.Node().Seq(), }) // Ping back if our last pong on file is too far in the past. diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 262e3f0ba3..e36912f010 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -470,13 +470,13 @@ func TestUDPv4_EIP868(t *testing.T) { // Perform endpoint proof and check for sequence number in packet tail. test.packetIn(nil, &v4wire.Ping{Expiration: futureExp}) test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) { - if p.ENRSeq() != wantNode.Seq() { - t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq(), wantNode.Seq()) + if p.ENRSeq != wantNode.Seq() { + t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq, wantNode.Seq()) } }) test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) { - if p.ENRSeq() != wantNode.Seq() { - t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq(), wantNode.Seq()) + if p.ENRSeq != wantNode.Seq() { + t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq, wantNode.Seq()) } test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash}) }) diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index b5dcb6e517..23e7134414 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -50,6 +50,8 @@ type ( Version uint From, To Endpoint Expiration uint64 + ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } @@ -62,6 +64,8 @@ type ( To Endpoint ReplyTok []byte // This contains the hash of the ping packet. Expiration uint64 // Absolute timestamp at which the packet becomes invalid. + ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. + // Ignore additional fields (for forward compatibility). Rest []rlp.RawValue `rlp:"tail"` } @@ -162,13 +166,11 @@ type Packet interface { Kind() byte } -func (req *Ping) Name() string { return "PING/v4" } -func (req *Ping) Kind() byte { return PingPacket } -func (req *Ping) ENRSeq() uint64 { return seqFromTail(req.Rest) } +func (req *Ping) Name() string { return "PING/v4" } +func (req *Ping) Kind() byte { return PingPacket } -func (req *Pong) Name() string { return "PONG/v4" } -func (req *Pong) Kind() byte { return PongPacket } -func (req *Pong) ENRSeq() uint64 { return seqFromTail(req.Rest) } +func (req *Pong) Name() string { return "PONG/v4" } +func (req *Pong) Kind() byte { return PongPacket } func (req *Findnode) Name() string { return "FINDNODE/v4" } func (req *Findnode) Kind() byte { return FindnodePacket } @@ -187,15 +189,6 @@ func Expired(ts uint64) bool { return time.Unix(int64(ts), 0).Before(time.Now()) } -func seqFromTail(tail []rlp.RawValue) uint64 { - if len(tail) == 0 { - return 0 - } - var seq uint64 - rlp.DecodeBytes(tail[0], &seq) - return seq -} - // Encoder/decoder. const ( diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go index 4dddeadd20..3b4161998d 100644 --- a/p2p/discover/v4wire/v4wire_test.go +++ b/p2p/discover/v4wire/v4wire_test.go @@ -23,7 +23,6 @@ import ( "testing" "github.com/davecgh/go-spew/spew" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) @@ -40,7 +39,6 @@ var testPackets = []struct { From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, To: Endpoint{net.ParseIP("::1"), 2222, 3333}, Expiration: 1136239445, - Rest: []rlp.RawValue{}, }, }, { @@ -50,26 +48,8 @@ var testPackets = []struct { From: Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, To: Endpoint{net.ParseIP("::1"), 2222, 3333}, Expiration: 1136239445, - Rest: []rlp.RawValue{{0x01}, {0x02}}, - }, - }, - { - input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3", - wantPacket: &Ping{ - Version: 555, - From: Endpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, - To: Endpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}}, - }, - }, - { - input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e", - wantPacket: &Pong{ - To: Endpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, - ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), - Expiration: 1136239445, - Rest: []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}}, + ENRSeq: 1, + Rest: []rlp.RawValue{{0x02}}, }, }, { From 088da24ebfb17a50652d4f9c3657670abcf055c8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 18 May 2021 12:10:27 +0200 Subject: [PATCH 558/948] rlp: improve decoder stream implementation (#22858) This commit makes various cleanup changes to rlp.Stream. * rlp: shrink Stream struct This removes a lot of unused padding space in Stream by reordering the fields. The size of Stream changes from 120 bytes to 88 bytes. Stream instances are internally cached and reused using sync.Pool, so this does not improve performance. * rlp: simplify list stack The list stack kept track of the size of the current list context as well as the current offset into it. The size had to be stored in the stack in order to subtract it from the remaining bytes of any enclosing list in ListEnd. It seems that this can be implemented in a simpler way: just subtract the size from the enclosing list context in List instead. --- rlp/decode.go | 175 +++++++++++++++++++++++++------------------------- 1 file changed, 86 insertions(+), 89 deletions(-) diff --git a/rlp/decode.go b/rlp/decode.go index b340aa029e..9767809717 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -518,7 +518,7 @@ func decodeDecoder(s *Stream, val reflect.Value) error { } // Kind represents the kind of value contained in an RLP stream. -type Kind int +type Kind int8 const ( Byte Kind = iota @@ -561,22 +561,16 @@ type ByteReader interface { type Stream struct { r ByteReader - // number of bytes remaining to be read from r. - remaining uint64 - limited bool - - // auxiliary buffer for integer decoding - uintbuf []byte - - kind Kind // kind of value ahead - size uint64 // size of value ahead - byteval byte // value of single byte in type tag - kinderr error // error from last readKind - stack []listpos + remaining uint64 // number of bytes remaining to be read from r + size uint64 // size of value ahead + kinderr error // error from last readKind + stack []uint64 // list sizes + uintbuf [8]byte // auxiliary buffer for integer decoding + kind Kind // kind of value ahead + byteval byte // value of single byte in type tag + limited bool // true if input limit is in effect } -type listpos struct{ pos, size uint64 } - // NewStream creates a new decoding stream reading from r. // // If r implements the ByteReader interface, Stream will @@ -646,8 +640,8 @@ func (s *Stream) Raw() ([]byte, error) { s.kind = -1 // rearm Kind return []byte{s.byteval}, nil } - // the original header has already been read and is no longer - // available. read content and put a new header in front of it. + // The original header has already been read and is no longer + // available. Read content and put a new header in front of it. start := headsize(size) buf := make([]byte, uint64(start)+size) if err := s.readFull(buf[start:]); err != nil { @@ -730,7 +724,14 @@ func (s *Stream) List() (size uint64, err error) { if kind != List { return 0, ErrExpectedList } - s.stack = append(s.stack, listpos{0, size}) + + // Remove size of inner list from outer list before pushing the new size + // onto the stack. This ensures that the remaining outer list size will + // be correct after the matching call to ListEnd. + if inList, limit := s.listLimit(); inList { + s.stack[len(s.stack)-1] = limit - size + } + s.stack = append(s.stack, size) s.kind = -1 s.size = 0 return size, nil @@ -739,17 +740,13 @@ func (s *Stream) List() (size uint64, err error) { // ListEnd returns to the enclosing list. // The input reader must be positioned at the end of a list. func (s *Stream) ListEnd() error { - if len(s.stack) == 0 { + // Ensure that no more data is remaining in the current list. + if inList, listLimit := s.listLimit(); !inList { return errNotInList - } - tos := s.stack[len(s.stack)-1] - if tos.pos != tos.size { + } else if listLimit > 0 { return errNotAtEOL } s.stack = s.stack[:len(s.stack)-1] // pop - if len(s.stack) > 0 { - s.stack[len(s.stack)-1].pos += tos.size - } s.kind = -1 s.size = 0 return nil @@ -777,7 +774,7 @@ func (s *Stream) Decode(val interface{}) error { err = decoder(s, rval.Elem()) if decErr, ok := err.(*decodeError); ok && len(decErr.ctx) > 0 { - // add decode target type to error so context has more meaning + // Add decode target type to error so context has more meaning. decErr.ctx = append(decErr.ctx, fmt.Sprint("(", rtyp.Elem(), ")")) } return err @@ -800,6 +797,9 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { case *bytes.Reader: s.remaining = uint64(br.Len()) s.limited = true + case *bytes.Buffer: + s.remaining = uint64(br.Len()) + s.limited = true case *strings.Reader: s.remaining = uint64(br.Len()) s.limited = true @@ -818,10 +818,8 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { s.size = 0 s.kind = -1 s.kinderr = nil - if s.uintbuf == nil { - s.uintbuf = make([]byte, 8) - } s.byteval = 0 + s.uintbuf = [8]byte{} } // Kind returns the kind and size of the next value in the @@ -836,35 +834,29 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { // the value. Subsequent calls to Kind (until the value is decoded) // will not advance the input reader and return cached information. func (s *Stream) Kind() (kind Kind, size uint64, err error) { - var tos *listpos - if len(s.stack) > 0 { - tos = &s.stack[len(s.stack)-1] - } - if s.kind < 0 { - s.kinderr = nil - // Don't read further if we're at the end of the - // innermost list. - if tos != nil && tos.pos == tos.size { - return 0, 0, EOL - } - s.kind, s.size, s.kinderr = s.readKind() - if s.kinderr == nil { - if tos == nil { - // At toplevel, check that the value is smaller - // than the remaining input length. - if s.limited && s.size > s.remaining { - s.kinderr = ErrValueTooLarge - } - } else { - // Inside a list, check that the value doesn't overflow the list. - if s.size > tos.size-tos.pos { - s.kinderr = ErrElemTooLarge - } - } + if s.kind >= 0 { + return s.kind, s.size, s.kinderr + } + + // Check for end of list. This needs to be done here because readKind + // checks against the list size, and would return the wrong error. + inList, listLimit := s.listLimit() + if inList && listLimit == 0 { + return 0, 0, EOL + } + // Read the actual size tag. + s.kind, s.size, s.kinderr = s.readKind() + if s.kinderr == nil { + // Check the data size of the value ahead against input limits. This + // is done here because many decoders require allocating an input + // buffer matching the value size. Checking it here protects those + // decoders from inputs declaring very large value size. + if inList && s.size > listLimit { + s.kinderr = ErrElemTooLarge + } else if s.limited && s.size > s.remaining { + s.kinderr = ErrValueTooLarge } } - // Note: this might return a sticky error generated - // by an earlier call to readKind. return s.kind, s.size, s.kinderr } @@ -891,37 +883,35 @@ func (s *Stream) readKind() (kind Kind, size uint64, err error) { s.byteval = b return Byte, 0, nil case b < 0xB8: - // Otherwise, if a string is 0-55 bytes long, - // the RLP encoding consists of a single byte with value 0x80 plus the - // length of the string followed by the string. The range of the first - // byte is thus [0x80, 0xB7]. + // Otherwise, if a string is 0-55 bytes long, the RLP encoding consists + // of a single byte with value 0x80 plus the length of the string + // followed by the string. The range of the first byte is thus [0x80, 0xB7]. return String, uint64(b - 0x80), nil case b < 0xC0: - // If a string is more than 55 bytes long, the - // RLP encoding consists of a single byte with value 0xB7 plus the length - // of the length of the string in binary form, followed by the length of - // the string, followed by the string. For example, a length-1024 string - // would be encoded as 0xB90400 followed by the string. The range of - // the first byte is thus [0xB8, 0xBF]. + // If a string is more than 55 bytes long, the RLP encoding consists of a + // single byte with value 0xB7 plus the length of the length of the + // string in binary form, followed by the length of the string, followed + // by the string. For example, a length-1024 string would be encoded as + // 0xB90400 followed by the string. The range of the first byte is thus + // [0xB8, 0xBF]. size, err = s.readUint(b - 0xB7) if err == nil && size < 56 { err = ErrCanonSize } return String, size, err case b < 0xF8: - // If the total payload of a list - // (i.e. the combined length of all its items) is 0-55 bytes long, the - // RLP encoding consists of a single byte with value 0xC0 plus the length - // of the list followed by the concatenation of the RLP encodings of the - // items. The range of the first byte is thus [0xC0, 0xF7]. + // If the total payload of a list (i.e. the combined length of all its + // items) is 0-55 bytes long, the RLP encoding consists of a single byte + // with value 0xC0 plus the length of the list followed by the + // concatenation of the RLP encodings of the items. The range of the + // first byte is thus [0xC0, 0xF7]. return List, uint64(b - 0xC0), nil default: - // If the total payload of a list is more than 55 bytes long, - // the RLP encoding consists of a single byte with value 0xF7 - // plus the length of the length of the payload in binary - // form, followed by the length of the payload, followed by - // the concatenation of the RLP encodings of the items. The - // range of the first byte is thus [0xF8, 0xFF]. + // If the total payload of a list is more than 55 bytes long, the RLP + // encoding consists of a single byte with value 0xF7 plus the length of + // the length of the payload in binary form, followed by the length of + // the payload, followed by the concatenation of the RLP encodings of + // the items. The range of the first byte is thus [0xF8, 0xFF]. size, err = s.readUint(b - 0xF7) if err == nil && size < 56 { err = ErrCanonSize @@ -940,22 +930,20 @@ func (s *Stream) readUint(size byte) (uint64, error) { return uint64(b), err default: start := int(8 - size) - for i := 0; i < start; i++ { - s.uintbuf[i] = 0 - } + s.uintbuf = [8]byte{} if err := s.readFull(s.uintbuf[start:]); err != nil { return 0, err } if s.uintbuf[start] == 0 { - // Note: readUint is also used to decode integer - // values. The error needs to be adjusted to become - // ErrCanonInt in this case. + // Note: readUint is also used to decode integer values. + // The error needs to be adjusted to become ErrCanonInt in this case. return 0, ErrCanonSize } - return binary.BigEndian.Uint64(s.uintbuf), nil + return binary.BigEndian.Uint64(s.uintbuf[:]), nil } } +// readFull reads into buf from the underlying stream. func (s *Stream) readFull(buf []byte) (err error) { if err := s.willRead(uint64(len(buf))); err != nil { return err @@ -977,6 +965,7 @@ func (s *Stream) readFull(buf []byte) (err error) { return err } +// readByte reads a single byte from the underlying stream. func (s *Stream) readByte() (byte, error) { if err := s.willRead(1); err != nil { return 0, err @@ -988,16 +977,16 @@ func (s *Stream) readByte() (byte, error) { return b, err } +// willRead is called before any read from the underlying stream. It checks +// n against size limits, and updates the limits if n doesn't overflow them. func (s *Stream) willRead(n uint64) error { s.kind = -1 // rearm Kind - if len(s.stack) > 0 { - // check list overflow - tos := s.stack[len(s.stack)-1] - if n > tos.size-tos.pos { + if inList, limit := s.listLimit(); inList { + if n > limit { return ErrElemTooLarge } - s.stack[len(s.stack)-1].pos += n + s.stack[len(s.stack)-1] = limit - n } if s.limited { if n > s.remaining { @@ -1007,3 +996,11 @@ func (s *Stream) willRead(n uint64) error { } return nil } + +// listLimit returns the amount of data remaining in the innermost list. +func (s *Stream) listLimit() (inList bool, limit uint64) { + if len(s.stack) == 0 { + return false, 0 + } + return true, s.stack[len(s.stack)-1] +} From b3a1fda6509c8dd64b5f5916f62a6602bcdc7a9d Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Tue, 18 May 2021 11:54:10 -0600 Subject: [PATCH 559/948] cmd/utils: expand tilde in --jspath (#22900) --- cmd/utils/flags.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d3fb3f2cbd..1ae07108e4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -675,10 +675,10 @@ var ( } // ATM the url is left to the user and deployment to - JSpathFlag = cli.StringFlag{ + JSpathFlag = DirectoryFlag{ Name: "jspath", Usage: "JavaScript root path for `loadScript`", - Value: ".", + Value: DirectoryString("."), } // Gas price oracle settings From 3e795881ea6d68c32da5da3c95f0d458a64e35c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 19 May 2021 15:09:03 +0300 Subject: [PATCH 560/948] eth, p2p/msgrate: move peer QoS tracking to its own package and use it for snap (#22876) This change extracts the peer QoS tracking logic from eth/downloader, moving it into the new package p2p/msgrate. The job of msgrate.Tracker is determining suitable timeout values and request sizes per peer. The snap sync scheduler now uses msgrate.Tracker instead of the hard-coded 15s timeout. This should make the sync work better on network links with high latency. --- eth/downloader/downloader.go | 119 +-------- eth/downloader/peer.go | 171 ++++-------- eth/downloader/peer_test.go | 53 ---- eth/downloader/statesync.go | 4 +- eth/protocols/snap/sync.go | 331 +++++++++++++++-------- eth/protocols/snap/sync_test.go | 18 +- p2p/msgrate/msgrate.go | 458 ++++++++++++++++++++++++++++++++ 7 files changed, 745 insertions(+), 409 deletions(-) delete mode 100644 eth/downloader/peer_test.go create mode 100644 p2p/msgrate/msgrate.go diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 6f59b29a5e..e8a4a76ca2 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -47,16 +47,6 @@ var ( MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request MaxStateFetch = 384 // Amount of node state values to allow fetching per request - rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests - rttMaxEstimate = 20 * time.Second // Maximum round-trip time to target for download requests - rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value - ttlScaling = 3 // Constant scaling factor for RTT -> TTL conversion - ttlLimit = time.Minute // Maximum TTL allowance to prevent reaching crazy timeouts - - qosTuningPeers = 5 // Number of peers to tune based on (best peers) - qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence - qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value - maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) maxHeadersProcess = 2048 // Number of header download results to import at once into the chain maxResultsProcess = 2048 // Number of content download results to import at once into the chain @@ -96,13 +86,6 @@ var ( ) type Downloader struct { - // WARNING: The `rttEstimate` and `rttConfidence` fields are accessed atomically. - // On 32 bit platforms, only 64-bit aligned fields can be atomic. The struct is - // guaranteed to be so aligned, so take advantage of that. For more information, - // see https://golang.org/pkg/sync/atomic/#pkg-note-BUG. - rttEstimate uint64 // Round trip time to target for download requests - rttConfidence uint64 // Confidence in the estimated RTT (unit: millionths to allow atomic ops) - mode uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode mux *event.TypeMux // Event multiplexer to announce sync operation events @@ -232,8 +215,6 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, checkpoint: checkpoint, queue: newQueue(blockCacheMaxItems, blockCacheInitialItems), peers: newPeerSet(), - rttEstimate: uint64(rttMaxEstimate), - rttConfidence: uint64(1000000), blockchain: chain, lightchain: lightchain, dropPeer: dropPeer, @@ -252,7 +233,6 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom, }, trackStateReq: make(chan *stateReq), } - go dl.qosTuner() go dl.stateFetcher() return dl } @@ -310,8 +290,6 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { logger.Error("Failed to register sync peer", "err", err) return err } - d.qosReduceConfidence() - return nil } @@ -670,7 +648,7 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty } go p.peer.RequestHeadersByHash(latest, fetch, fsMinFullBlocks-1, true) - ttl := d.requestTTL() + ttl := d.peers.rates.TargetTimeout() timeout := time.After(ttl) for { select { @@ -853,7 +831,7 @@ func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, re // Wait for the remote response to the head fetch number, hash := uint64(0), common.Hash{} - ttl := d.requestTTL() + ttl := d.peers.rates.TargetTimeout() timeout := time.After(ttl) for finished := false; !finished; { @@ -942,7 +920,7 @@ func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, // Split our chain interval in two, and request the hash to cross check check := (start + end) / 2 - ttl := d.requestTTL() + ttl := d.peers.rates.TargetTimeout() timeout := time.After(ttl) go p.peer.RequestHeadersByNumber(check, 1, 0, false) @@ -1035,7 +1013,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { getHeaders := func(from uint64) { request = time.Now() - ttl = d.requestTTL() + ttl = d.peers.rates.TargetTimeout() timeout.Reset(ttl) if skeleton { @@ -1050,7 +1028,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { pivoting = true request = time.Now() - ttl = d.requestTTL() + ttl = d.peers.rates.TargetTimeout() timeout.Reset(ttl) d.pivotLock.RLock() @@ -1262,12 +1240,12 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ( pack := packet.(*headerPack) return d.queue.DeliverHeaders(pack.peerID, pack.headers, d.headerProcCh) } - expire = func() map[string]int { return d.queue.ExpireHeaders(d.requestTTL()) } + expire = func() map[string]int { return d.queue.ExpireHeaders(d.peers.rates.TargetTimeout()) } reserve = func(p *peerConnection, count int) (*fetchRequest, bool, bool) { return d.queue.ReserveHeaders(p, count), false, false } fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) } - capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.requestRTT()) } + capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.peers.rates.TargetRoundTrip()) } setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetHeadersIdle(accepted, deliveryTime) } @@ -1293,9 +1271,9 @@ func (d *Downloader) fetchBodies(from uint64) error { pack := packet.(*bodyPack) return d.queue.DeliverBodies(pack.peerID, pack.transactions, pack.uncles) } - expire = func() map[string]int { return d.queue.ExpireBodies(d.requestTTL()) } + expire = func() map[string]int { return d.queue.ExpireBodies(d.peers.rates.TargetTimeout()) } fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchBodies(req) } - capacity = func(p *peerConnection) int { return p.BlockCapacity(d.requestRTT()) } + capacity = func(p *peerConnection) int { return p.BlockCapacity(d.peers.rates.TargetRoundTrip()) } setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) } ) err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire, @@ -1317,9 +1295,9 @@ func (d *Downloader) fetchReceipts(from uint64) error { pack := packet.(*receiptPack) return d.queue.DeliverReceipts(pack.peerID, pack.receipts) } - expire = func() map[string]int { return d.queue.ExpireReceipts(d.requestTTL()) } + expire = func() map[string]int { return d.queue.ExpireReceipts(d.peers.rates.TargetTimeout()) } fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchReceipts(req) } - capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.requestRTT()) } + capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.peers.rates.TargetRoundTrip()) } setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetReceiptsIdle(accepted, deliveryTime) } @@ -2031,78 +2009,3 @@ func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dro return errNoSyncActive } } - -// qosTuner is the quality of service tuning loop that occasionally gathers the -// peer latency statistics and updates the estimated request round trip time. -func (d *Downloader) qosTuner() { - for { - // Retrieve the current median RTT and integrate into the previoust target RTT - rtt := time.Duration((1-qosTuningImpact)*float64(atomic.LoadUint64(&d.rttEstimate)) + qosTuningImpact*float64(d.peers.medianRTT())) - atomic.StoreUint64(&d.rttEstimate, uint64(rtt)) - - // A new RTT cycle passed, increase our confidence in the estimated RTT - conf := atomic.LoadUint64(&d.rttConfidence) - conf = conf + (1000000-conf)/2 - atomic.StoreUint64(&d.rttConfidence, conf) - - // Log the new QoS values and sleep until the next RTT - log.Debug("Recalculated downloader QoS values", "rtt", rtt, "confidence", float64(conf)/1000000.0, "ttl", d.requestTTL()) - select { - case <-d.quitCh: - return - case <-time.After(rtt): - } - } -} - -// qosReduceConfidence is meant to be called when a new peer joins the downloader's -// peer set, needing to reduce the confidence we have in out QoS estimates. -func (d *Downloader) qosReduceConfidence() { - // If we have a single peer, confidence is always 1 - peers := uint64(d.peers.Len()) - if peers == 0 { - // Ensure peer connectivity races don't catch us off guard - return - } - if peers == 1 { - atomic.StoreUint64(&d.rttConfidence, 1000000) - return - } - // If we have a ton of peers, don't drop confidence) - if peers >= uint64(qosConfidenceCap) { - return - } - // Otherwise drop the confidence factor - conf := atomic.LoadUint64(&d.rttConfidence) * (peers - 1) / peers - if float64(conf)/1000000 < rttMinConfidence { - conf = uint64(rttMinConfidence * 1000000) - } - atomic.StoreUint64(&d.rttConfidence, conf) - - rtt := time.Duration(atomic.LoadUint64(&d.rttEstimate)) - log.Debug("Relaxed downloader QoS values", "rtt", rtt, "confidence", float64(conf)/1000000.0, "ttl", d.requestTTL()) -} - -// requestRTT returns the current target round trip time for a download request -// to complete in. -// -// Note, the returned RTT is .9 of the actually estimated RTT. The reason is that -// the downloader tries to adapt queries to the RTT, so multiple RTT values can -// be adapted to, but smaller ones are preferred (stabler download stream). -func (d *Downloader) requestRTT() time.Duration { - return time.Duration(atomic.LoadUint64(&d.rttEstimate)) * 9 / 10 -} - -// requestTTL returns the current timeout allowance for a single download request -// to finish under. -func (d *Downloader) requestTTL() time.Duration { - var ( - rtt = time.Duration(atomic.LoadUint64(&d.rttEstimate)) - conf = float64(atomic.LoadUint64(&d.rttConfidence)) / 1000000.0 - ) - ttl := time.Duration(ttlScaling) * time.Duration(float64(rtt)/conf) - if ttl > ttlLimit { - ttl = ttlLimit - } - return ttl -} diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index b3b6cc95a0..b9c7716941 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -32,11 +32,11 @@ import ( "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/msgrate" ) const ( - maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items - measurementImpact = 0.1 // The impact a single measurement has on a peer's final throughput value. + maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items ) var ( @@ -54,18 +54,12 @@ type peerConnection struct { receiptIdle int32 // Current receipt activity state of the peer (idle = 0, active = 1) stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1) - headerThroughput float64 // Number of headers measured to be retrievable per second - blockThroughput float64 // Number of blocks (bodies) measured to be retrievable per second - receiptThroughput float64 // Number of receipts measured to be retrievable per second - stateThroughput float64 // Number of node data pieces measured to be retrievable per second - - rtt time.Duration // Request round trip time to track responsiveness (QoS) - headerStarted time.Time // Time instance when the last header fetch was started blockStarted time.Time // Time instance when the last block (body) fetch was started receiptStarted time.Time // Time instance when the last receipt fetch was started stateStarted time.Time // Time instance when the last node data fetch was started + rates *msgrate.Tracker // Tracker to hone in on the number of items retrievable per second lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously) peer Peer @@ -133,11 +127,6 @@ func (p *peerConnection) Reset() { atomic.StoreInt32(&p.receiptIdle, 0) atomic.StoreInt32(&p.stateIdle, 0) - p.headerThroughput = 0 - p.blockThroughput = 0 - p.receiptThroughput = 0 - p.stateThroughput = 0 - p.lacking = make(map[common.Hash]struct{}) } @@ -212,93 +201,72 @@ func (p *peerConnection) FetchNodeData(hashes []common.Hash) error { // requests. Its estimated header retrieval throughput is updated with that measured // just now. func (p *peerConnection) SetHeadersIdle(delivered int, deliveryTime time.Time) { - p.setIdle(deliveryTime.Sub(p.headerStarted), delivered, &p.headerThroughput, &p.headerIdle) + p.rates.Update(eth.BlockHeadersMsg, deliveryTime.Sub(p.headerStarted), delivered) + atomic.StoreInt32(&p.headerIdle, 0) } // SetBodiesIdle sets the peer to idle, allowing it to execute block body retrieval // requests. Its estimated body retrieval throughput is updated with that measured // just now. func (p *peerConnection) SetBodiesIdle(delivered int, deliveryTime time.Time) { - p.setIdle(deliveryTime.Sub(p.blockStarted), delivered, &p.blockThroughput, &p.blockIdle) + p.rates.Update(eth.BlockBodiesMsg, deliveryTime.Sub(p.blockStarted), delivered) + atomic.StoreInt32(&p.blockIdle, 0) } // SetReceiptsIdle sets the peer to idle, allowing it to execute new receipt // retrieval requests. Its estimated receipt retrieval throughput is updated // with that measured just now. func (p *peerConnection) SetReceiptsIdle(delivered int, deliveryTime time.Time) { - p.setIdle(deliveryTime.Sub(p.receiptStarted), delivered, &p.receiptThroughput, &p.receiptIdle) + p.rates.Update(eth.ReceiptsMsg, deliveryTime.Sub(p.receiptStarted), delivered) + atomic.StoreInt32(&p.receiptIdle, 0) } // SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie // data retrieval requests. Its estimated state retrieval throughput is updated // with that measured just now. func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) { - p.setIdle(deliveryTime.Sub(p.stateStarted), delivered, &p.stateThroughput, &p.stateIdle) -} - -// setIdle sets the peer to idle, allowing it to execute new retrieval requests. -// Its estimated retrieval throughput is updated with that measured just now. -func (p *peerConnection) setIdle(elapsed time.Duration, delivered int, throughput *float64, idle *int32) { - // Irrelevant of the scaling, make sure the peer ends up idle - defer atomic.StoreInt32(idle, 0) - - p.lock.Lock() - defer p.lock.Unlock() - - // If nothing was delivered (hard timeout / unavailable data), reduce throughput to minimum - if delivered == 0 { - *throughput = 0 - return - } - // Otherwise update the throughput with a new measurement - if elapsed <= 0 { - elapsed = 1 // +1 (ns) to ensure non-zero divisor - } - measured := float64(delivered) / (float64(elapsed) / float64(time.Second)) - - *throughput = (1-measurementImpact)*(*throughput) + measurementImpact*measured - p.rtt = time.Duration((1-measurementImpact)*float64(p.rtt) + measurementImpact*float64(elapsed)) - - p.log.Trace("Peer throughput measurements updated", - "hps", p.headerThroughput, "bps", p.blockThroughput, - "rps", p.receiptThroughput, "sps", p.stateThroughput, - "miss", len(p.lacking), "rtt", p.rtt) + p.rates.Update(eth.NodeDataMsg, deliveryTime.Sub(p.stateStarted), delivered) + atomic.StoreInt32(&p.stateIdle, 0) } // HeaderCapacity retrieves the peers header download allowance based on its // previously discovered throughput. func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { - p.lock.RLock() - defer p.lock.RUnlock() - - return int(math.Min(1+math.Max(1, p.headerThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxHeaderFetch))) + cap := int(math.Ceil(p.rates.Capacity(eth.BlockHeadersMsg, targetRTT))) + if cap > MaxHeaderFetch { + cap = MaxHeaderFetch + } + return cap } // BlockCapacity retrieves the peers block download allowance based on its // previously discovered throughput. func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int { - p.lock.RLock() - defer p.lock.RUnlock() - - return int(math.Min(1+math.Max(1, p.blockThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxBlockFetch))) + cap := int(math.Ceil(p.rates.Capacity(eth.BlockBodiesMsg, targetRTT))) + if cap > MaxBlockFetch { + cap = MaxBlockFetch + } + return cap } // ReceiptCapacity retrieves the peers receipt download allowance based on its // previously discovered throughput. func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { - p.lock.RLock() - defer p.lock.RUnlock() - - return int(math.Min(1+math.Max(1, p.receiptThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxReceiptFetch))) + cap := int(math.Ceil(p.rates.Capacity(eth.ReceiptsMsg, targetRTT))) + if cap > MaxReceiptFetch { + cap = MaxReceiptFetch + } + return cap } // NodeDataCapacity retrieves the peers state download allowance based on its // previously discovered throughput. func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int { - p.lock.RLock() - defer p.lock.RUnlock() - - return int(math.Min(1+math.Max(1, p.stateThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxStateFetch))) + cap := int(math.Ceil(p.rates.Capacity(eth.NodeDataMsg, targetRTT))) + if cap > MaxStateFetch { + cap = MaxStateFetch + } + return cap } // MarkLacking appends a new entity to the set of items (blocks, receipts, states) @@ -330,16 +298,20 @@ func (p *peerConnection) Lacks(hash common.Hash) bool { // peerSet represents the collection of active peer participating in the chain // download procedure. type peerSet struct { - peers map[string]*peerConnection + peers map[string]*peerConnection + rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat + newPeerFeed event.Feed peerDropFeed event.Feed - lock sync.RWMutex + + lock sync.RWMutex } // newPeerSet creates a new peer set top track the active download sources. func newPeerSet() *peerSet { return &peerSet{ peers: make(map[string]*peerConnection), + rates: msgrate.NewTrackers(log.New("proto", "eth")), } } @@ -371,30 +343,15 @@ func (ps *peerSet) Reset() { // average of all existing peers, to give it a realistic chance of being used // for data retrievals. func (ps *peerSet) Register(p *peerConnection) error { - // Retrieve the current median RTT as a sane default - p.rtt = ps.medianRTT() - // Register the new peer with some meaningful defaults ps.lock.Lock() if _, ok := ps.peers[p.id]; ok { ps.lock.Unlock() return errAlreadyRegistered } - if len(ps.peers) > 0 { - p.headerThroughput, p.blockThroughput, p.receiptThroughput, p.stateThroughput = 0, 0, 0, 0 - - for _, peer := range ps.peers { - peer.lock.RLock() - p.headerThroughput += peer.headerThroughput - p.blockThroughput += peer.blockThroughput - p.receiptThroughput += peer.receiptThroughput - p.stateThroughput += peer.stateThroughput - peer.lock.RUnlock() - } - p.headerThroughput /= float64(len(ps.peers)) - p.blockThroughput /= float64(len(ps.peers)) - p.receiptThroughput /= float64(len(ps.peers)) - p.stateThroughput /= float64(len(ps.peers)) + p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip()) + if err := ps.rates.Track(p.id, p.rates); err != nil { + return err } ps.peers[p.id] = p ps.lock.Unlock() @@ -413,6 +370,7 @@ func (ps *peerSet) Unregister(id string) error { return errNotRegistered } delete(ps.peers, id) + ps.rates.Untrack(id) ps.lock.Unlock() ps.peerDropFeed.Send(p) @@ -454,9 +412,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { return atomic.LoadInt32(&p.headerIdle) == 0 } throughput := func(p *peerConnection) float64 { - p.lock.RLock() - defer p.lock.RUnlock() - return p.headerThroughput + return p.rates.Capacity(eth.BlockHeadersMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } @@ -468,9 +424,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { return atomic.LoadInt32(&p.blockIdle) == 0 } throughput := func(p *peerConnection) float64 { - p.lock.RLock() - defer p.lock.RUnlock() - return p.blockThroughput + return p.rates.Capacity(eth.BlockBodiesMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } @@ -482,9 +436,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { return atomic.LoadInt32(&p.receiptIdle) == 0 } throughput := func(p *peerConnection) float64 { - p.lock.RLock() - defer p.lock.RUnlock() - return p.receiptThroughput + return p.rates.Capacity(eth.ReceiptsMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } @@ -496,9 +448,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { return atomic.LoadInt32(&p.stateIdle) == 0 } throughput := func(p *peerConnection) float64 { - p.lock.RLock() - defer p.lock.RUnlock() - return p.stateThroughput + return p.rates.Capacity(eth.NodeDataMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) } @@ -527,37 +477,6 @@ func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peer return sortPeers.p, total } -// medianRTT returns the median RTT of the peerset, considering only the tuning -// peers if there are more peers available. -func (ps *peerSet) medianRTT() time.Duration { - // Gather all the currently measured round trip times - ps.lock.RLock() - defer ps.lock.RUnlock() - - rtts := make([]float64, 0, len(ps.peers)) - for _, p := range ps.peers { - p.lock.RLock() - rtts = append(rtts, float64(p.rtt)) - p.lock.RUnlock() - } - sort.Float64s(rtts) - - median := rttMaxEstimate - if qosTuningPeers <= len(rtts) { - median = time.Duration(rtts[qosTuningPeers/2]) // Median of our tuning peers - } else if len(rtts) > 0 { - median = time.Duration(rtts[len(rtts)/2]) // Median of our connected peers (maintain even like this some baseline qos) - } - // Restrict the RTT into some QoS defaults, irrelevant of true RTT - if median < rttMinEstimate { - median = rttMinEstimate - } - if median > rttMaxEstimate { - median = rttMaxEstimate - } - return median -} - // peerThroughputSort implements the Sort interface, and allows for // sorting a set of peers by their throughput // The sorted data is with the _highest_ throughput first diff --git a/eth/downloader/peer_test.go b/eth/downloader/peer_test.go deleted file mode 100644 index 4bf0e200bb..0000000000 --- a/eth/downloader/peer_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package downloader - -import ( - "sort" - "testing" -) - -func TestPeerThroughputSorting(t *testing.T) { - a := &peerConnection{ - id: "a", - headerThroughput: 1.25, - } - b := &peerConnection{ - id: "b", - headerThroughput: 1.21, - } - c := &peerConnection{ - id: "c", - headerThroughput: 1.23, - } - - peers := []*peerConnection{a, b, c} - tps := []float64{a.headerThroughput, - b.headerThroughput, c.headerThroughput} - sortPeers := &peerThroughputSort{peers, tps} - sort.Sort(sortPeers) - if got, exp := sortPeers.p[0].id, "a"; got != exp { - t.Errorf("sort fail, got %v exp %v", got, exp) - } - if got, exp := sortPeers.p[1].id, "c"; got != exp { - t.Errorf("sort fail, got %v exp %v", got, exp) - } - if got, exp := sortPeers.p[2].id, "b"; got != exp { - t.Errorf("sort fail, got %v exp %v", got, exp) - } - -} diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index ff84a3a8f0..6c53e5577a 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -433,8 +433,8 @@ func (s *stateSync) assignTasks() { peers, _ := s.d.peers.NodeDataIdlePeers() for _, p := range peers { // Assign a batch of fetches proportional to the estimated latency/bandwidth - cap := p.NodeDataCapacity(s.d.requestRTT()) - req := &stateReq{peer: p, timeout: s.d.requestTTL()} + cap := p.NodeDataCapacity(s.d.peers.rates.TargetRoundTrip()) + req := &stateReq{peer: p, timeout: s.d.peers.rates.TargetTimeout()} nodes, _, codes := s.fillTasks(cap, req) diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index e283473207..c57fcd71f6 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/msgrate" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "golang.org/x/crypto/sha3" @@ -51,14 +52,15 @@ var ( ) const ( - // maxRequestSize is the maximum number of bytes to request from a remote peer. - maxRequestSize = 128 * 1024 + // minRequestSize is the minimum number of bytes to request from a remote peer. + // This number is used as the low cap for account and storage range requests. + // Bytecode and trienode are limited inherently by item count (1). + minRequestSize = 64 * 1024 - // maxStorageSetRequestCount is the maximum number of contracts to request the - // storage of in a single query. If this number is too low, we're not filling - // responses fully and waste round trip times. If it's too high, we're capping - // responses and waste bandwidth. - maxStorageSetRequestCount = maxRequestSize / 1024 + // maxRequestSize is the maximum number of bytes to request from a remote peer. + // This number is used as the high cap for account and storage range requests. + // Bytecode and trienode are limited more explicitly by the caps below. + maxRequestSize = 512 * 1024 // maxCodeRequestCount is the maximum number of bytecode blobs to request in a // single query. If this number is too low, we're not filling responses fully @@ -74,7 +76,7 @@ const ( // a single query. If this number is too low, we're not filling responses fully // and waste round trip times. If it's too high, we're capping responses and // waste bandwidth. - maxTrieRequestCount = 256 + maxTrieRequestCount = maxRequestSize / 512 ) var ( @@ -85,10 +87,6 @@ var ( // storageConcurrency is the number of chunks to split the a large contract // storage trie into to allow concurrent retrievals. storageConcurrency = 16 - - // requestTimeout is the maximum time a peer is allowed to spend on serving - // a single network request. - requestTimeout = 15 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync? ) // ErrCancelled is returned from snap syncing if the operation was prematurely @@ -105,8 +103,9 @@ var ErrCancelled = errors.New("sync cancelled") // is only included to allow the runloop to match a response to the task being // synced without having yet another set of maps. type accountRequest struct { - peer string // Peer to which this request is assigned - id uint64 // Request ID of this request + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent deliver chan *accountResponse // Channel to deliver successful response on revert chan *accountRequest // Channel to deliver request failure on @@ -142,8 +141,9 @@ type accountResponse struct { // is only included to allow the runloop to match a response to the task being // synced without having yet another set of maps. type bytecodeRequest struct { - peer string // Peer to which this request is assigned - id uint64 // Request ID of this request + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent deliver chan *bytecodeResponse // Channel to deliver successful response on revert chan *bytecodeRequest // Channel to deliver request failure on @@ -173,8 +173,9 @@ type bytecodeResponse struct { // is only included to allow the runloop to match a response to the task being // synced without having yet another set of maps. type storageRequest struct { - peer string // Peer to which this request is assigned - id uint64 // Request ID of this request + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent deliver chan *storageResponse // Channel to deliver successful response on revert chan *storageRequest // Channel to deliver request failure on @@ -218,8 +219,9 @@ type storageResponse struct { // is only included to allow the runloop to match a response to the task being // synced without having yet another set of maps. type trienodeHealRequest struct { - peer string // Peer to which this request is assigned - id uint64 // Request ID of this request + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent deliver chan *trienodeHealResponse // Channel to deliver successful response on revert chan *trienodeHealRequest // Channel to deliver request failure on @@ -252,8 +254,9 @@ type trienodeHealResponse struct { // is only included to allow the runloop to match a response to the task being // synced without having yet another set of maps. type bytecodeHealRequest struct { - peer string // Peer to which this request is assigned - id uint64 // Request ID of this request + peer string // Peer to which this request is assigned + id uint64 // Request ID of this request + time time.Time // Timestamp when the request was sent deliver chan *bytecodeHealResponse // Channel to deliver successful response on revert chan *bytecodeHealRequest // Channel to deliver request failure on @@ -396,6 +399,7 @@ type Syncer struct { peers map[string]SyncPeer // Currently active peers to download from peerJoin *event.Feed // Event feed to react to peers joining peerDrop *event.Feed // Event feed to react to peers dropping + rates *msgrate.Trackers // Message throughput rates for peers // Request tracking during syncing phase statelessPeers map[string]struct{} // Peers that failed to deliver state data @@ -452,6 +456,7 @@ func NewSyncer(db ethdb.KeyValueStore) *Syncer { peers: make(map[string]SyncPeer), peerJoin: new(event.Feed), peerDrop: new(event.Feed), + rates: msgrate.NewTrackers(log.New("proto", "snap")), update: make(chan struct{}, 1), accountIdlers: make(map[string]struct{}), @@ -484,6 +489,7 @@ func (s *Syncer) Register(peer SyncPeer) error { return errors.New("already registered") } s.peers[id] = peer + s.rates.Track(id, msgrate.NewTracker(s.rates.MeanCapacities(), s.rates.MedianRoundTrip())) // Mark the peer as idle, even if no sync is running s.accountIdlers[id] = struct{}{} @@ -509,6 +515,7 @@ func (s *Syncer) Unregister(id string) error { return errors.New("not registered") } delete(s.peers, id) + s.rates.Untrack(id) // Remove status markers, even if no sync is running delete(s.statelessPeers, id) @@ -851,10 +858,24 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac s.lock.Lock() defer s.lock.Unlock() - // If there are no idle peers, short circuit assignment - if len(s.accountIdlers) == 0 { + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.accountIdlers)), + caps: make([]float64, 0, len(s.accountIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.accountIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, AccountRangeMsg, targetTTL)) + } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) + // Iterate over all the tasks and try to find a pending one for _, task := range s.tasks { // Skip any tasks already filling @@ -864,20 +885,15 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac // Task pending retrieval, try to find an idle peer. If no such peer // exists, we probably assigned tasks for all (or they are stateless). // Abort the entire assignment mechanism. - var idle string - for id := range s.accountIdlers { - // If the peer rejected a query in this sync cycle, don't bother asking - // again for anything, it's either out of sync or already pruned - if _, ok := s.statelessPeers[id]; ok { - continue - } - idle = id - break - } - if idle == "" { + if len(idlers.ids) == 0 { return } - peer := s.peers[idle] + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 @@ -895,6 +911,7 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac req := &accountRequest{ peer: idle, id: reqid, + time: time.Now(), deliver: success, revert: fail, cancel: cancel, @@ -903,8 +920,9 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac limit: task.Last, task: task, } - req.timeout = time.AfterFunc(requestTimeout, func() { + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Account range request timed out", "reqid", reqid) + s.rates.Update(idle, AccountRangeMsg, 0, 0) s.scheduleRevertAccountRequest(req) }) s.accountReqs[reqid] = req @@ -915,7 +933,13 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac defer s.pend.Done() // Attempt to send the remote request and revert if it fails - if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, maxRequestSize); err != nil { + if cap > maxRequestSize { + cap = maxRequestSize + } + if cap < minRequestSize { // Don't bother with peers below a bare minimum performance + cap = minRequestSize + } + if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, uint64(cap)); err != nil { peer.Log().Debug("Failed to request account range", "err", err) s.scheduleRevertAccountRequest(req) } @@ -931,10 +955,24 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * s.lock.Lock() defer s.lock.Unlock() - // If there are no idle peers, short circuit assignment - if len(s.bytecodeIdlers) == 0 { + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.bytecodeIdlers)), + caps: make([]float64, 0, len(s.bytecodeIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.bytecodeIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) + // Iterate over all the tasks and try to find a pending one for _, task := range s.tasks { // Skip any tasks not in the bytecode retrieval phase @@ -948,20 +986,15 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * // Task pending retrieval, try to find an idle peer. If no such peer // exists, we probably assigned tasks for all (or they are stateless). // Abort the entire assignment mechanism. - var idle string - for id := range s.bytecodeIdlers { - // If the peer rejected a query in this sync cycle, don't bother asking - // again for anything, it's either out of sync or already pruned - if _, ok := s.statelessPeers[id]; ok { - continue - } - idle = id - break - } - if idle == "" { + if len(idlers.ids) == 0 { return } - peer := s.peers[idle] + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 @@ -976,17 +1009,21 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * break } // Generate the network query and send it to the peer - hashes := make([]common.Hash, 0, maxCodeRequestCount) + if cap > maxCodeRequestCount { + cap = maxCodeRequestCount + } + hashes := make([]common.Hash, 0, int(cap)) for hash := range task.codeTasks { delete(task.codeTasks, hash) hashes = append(hashes, hash) - if len(hashes) >= maxCodeRequestCount { + if len(hashes) >= int(cap) { break } } req := &bytecodeRequest{ peer: idle, id: reqid, + time: time.Now(), deliver: success, revert: fail, cancel: cancel, @@ -994,8 +1031,9 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * hashes: hashes, task: task, } - req.timeout = time.AfterFunc(requestTimeout, func() { + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Bytecode request timed out", "reqid", reqid) + s.rates.Update(idle, ByteCodesMsg, 0, 0) s.scheduleRevertBytecodeRequest(req) }) s.bytecodeReqs[reqid] = req @@ -1020,10 +1058,24 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st s.lock.Lock() defer s.lock.Unlock() - // If there are no idle peers, short circuit assignment - if len(s.storageIdlers) == 0 { + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.storageIdlers)), + caps: make([]float64, 0, len(s.storageIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.storageIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, StorageRangesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) + // Iterate over all the tasks and try to find a pending one for _, task := range s.tasks { // Skip any tasks not in the storage retrieval phase @@ -1037,20 +1089,15 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st // Task pending retrieval, try to find an idle peer. If no such peer // exists, we probably assigned tasks for all (or they are stateless). // Abort the entire assignment mechanism. - var idle string - for id := range s.storageIdlers { - // If the peer rejected a query in this sync cycle, don't bother asking - // again for anything, it's either out of sync or already pruned - if _, ok := s.statelessPeers[id]; ok { - continue - } - idle = id - break - } - if idle == "" { + if len(idlers.ids) == 0 { return } - peer := s.peers[idle] + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 @@ -1067,9 +1114,17 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st // Generate the network query and send it to the peer. If there are // large contract tasks pending, complete those before diving into // even more new contracts. + if cap > maxRequestSize { + cap = maxRequestSize + } + if cap < minRequestSize { // Don't bother with peers below a bare minimum performance + cap = minRequestSize + } + storageSets := int(cap / 1024) + var ( - accounts = make([]common.Hash, 0, maxStorageSetRequestCount) - roots = make([]common.Hash, 0, maxStorageSetRequestCount) + accounts = make([]common.Hash, 0, storageSets) + roots = make([]common.Hash, 0, storageSets) subtask *storageTask ) for account, subtasks := range task.SubTasks { @@ -1096,7 +1151,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st accounts = append(accounts, acccount) roots = append(roots, root) - if len(accounts) >= maxStorageSetRequestCount { + if len(accounts) >= storageSets { break } } @@ -1109,6 +1164,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st req := &storageRequest{ peer: idle, id: reqid, + time: time.Now(), deliver: success, revert: fail, cancel: cancel, @@ -1122,8 +1178,9 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st req.origin = subtask.Next req.limit = subtask.Last } - req.timeout = time.AfterFunc(requestTimeout, func() { + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Storage request timed out", "reqid", reqid) + s.rates.Update(idle, StorageRangesMsg, 0, 0) s.scheduleRevertStorageRequest(req) }) s.storageReqs[reqid] = req @@ -1138,7 +1195,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st if subtask != nil { origin, limit = req.origin[:], req.limit[:] } - if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, maxRequestSize); err != nil { + if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, uint64(cap)); err != nil { log.Debug("Failed to request storage", "err", err) s.scheduleRevertStorageRequest(req) } @@ -1157,10 +1214,24 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai s.lock.Lock() defer s.lock.Unlock() - // If there are no idle peers, short circuit assignment - if len(s.trienodeHealIdlers) == 0 { + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.trienodeHealIdlers)), + caps: make([]float64, 0, len(s.trienodeHealIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.trienodeHealIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, TrieNodesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) + // Iterate over pending tasks and try to find a peer to retrieve with for len(s.healer.trieTasks) > 0 || s.healer.scheduler.Pending() > 0 { // If there are not enough trie tasks queued to fully assign, fill the @@ -1186,20 +1257,15 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai // Task pending retrieval, try to find an idle peer. If no such peer // exists, we probably assigned tasks for all (or they are stateless). // Abort the entire assignment mechanism. - var idle string - for id := range s.trienodeHealIdlers { - // If the peer rejected a query in this sync cycle, don't bother asking - // again for anything, it's either out of sync or already pruned - if _, ok := s.statelessPeers[id]; ok { - continue - } - idle = id - break - } - if idle == "" { + if len(idlers.ids) == 0 { return } - peer := s.peers[idle] + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 @@ -1214,10 +1280,13 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai break } // Generate the network query and send it to the peer + if cap > maxTrieRequestCount { + cap = maxTrieRequestCount + } var ( - hashes = make([]common.Hash, 0, maxTrieRequestCount) - paths = make([]trie.SyncPath, 0, maxTrieRequestCount) - pathsets = make([]TrieNodePathSet, 0, maxTrieRequestCount) + hashes = make([]common.Hash, 0, int(cap)) + paths = make([]trie.SyncPath, 0, int(cap)) + pathsets = make([]TrieNodePathSet, 0, int(cap)) ) for hash, pathset := range s.healer.trieTasks { delete(s.healer.trieTasks, hash) @@ -1226,13 +1295,14 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai paths = append(paths, pathset) pathsets = append(pathsets, [][]byte(pathset)) // TODO(karalabe): group requests by account hash - if len(hashes) >= maxTrieRequestCount { + if len(hashes) >= int(cap) { break } } req := &trienodeHealRequest{ peer: idle, id: reqid, + time: time.Now(), deliver: success, revert: fail, cancel: cancel, @@ -1241,8 +1311,9 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai paths: paths, task: s.healer, } - req.timeout = time.AfterFunc(requestTimeout, func() { + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Trienode heal request timed out", "reqid", reqid) + s.rates.Update(idle, TrieNodesMsg, 0, 0) s.scheduleRevertTrienodeHealRequest(req) }) s.trienodeHealReqs[reqid] = req @@ -1267,10 +1338,24 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai s.lock.Lock() defer s.lock.Unlock() - // If there are no idle peers, short circuit assignment - if len(s.bytecodeHealIdlers) == 0 { + // Sort the peers by download capacity to use faster ones if many available + idlers := &capacitySort{ + ids: make([]string, 0, len(s.bytecodeHealIdlers)), + caps: make([]float64, 0, len(s.bytecodeHealIdlers)), + } + targetTTL := s.rates.TargetTimeout() + for id := range s.bytecodeHealIdlers { + if _, ok := s.statelessPeers[id]; ok { + continue + } + idlers.ids = append(idlers.ids, id) + idlers.caps = append(idlers.caps, s.rates.Capacity(id, ByteCodesMsg, targetTTL)) + } + if len(idlers.ids) == 0 { return } + sort.Sort(sort.Reverse(idlers)) + // Iterate over pending tasks and try to find a peer to retrieve with for len(s.healer.codeTasks) > 0 || s.healer.scheduler.Pending() > 0 { // If there are not enough trie tasks queued to fully assign, fill the @@ -1296,20 +1381,15 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai // Task pending retrieval, try to find an idle peer. If no such peer // exists, we probably assigned tasks for all (or they are stateless). // Abort the entire assignment mechanism. - var idle string - for id := range s.bytecodeHealIdlers { - // If the peer rejected a query in this sync cycle, don't bother asking - // again for anything, it's either out of sync or already pruned - if _, ok := s.statelessPeers[id]; ok { - continue - } - idle = id - break - } - if idle == "" { + if len(idlers.ids) == 0 { return } - peer := s.peers[idle] + var ( + idle = idlers.ids[0] + peer = s.peers[idle] + cap = idlers.caps[0] + ) + idlers.ids, idlers.caps = idlers.ids[1:], idlers.caps[1:] // Matched a pending task to an idle peer, allocate a unique request id var reqid uint64 @@ -1324,18 +1404,22 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai break } // Generate the network query and send it to the peer - hashes := make([]common.Hash, 0, maxCodeRequestCount) + if cap > maxCodeRequestCount { + cap = maxCodeRequestCount + } + hashes := make([]common.Hash, 0, int(cap)) for hash := range s.healer.codeTasks { delete(s.healer.codeTasks, hash) hashes = append(hashes, hash) - if len(hashes) >= maxCodeRequestCount { + if len(hashes) >= int(cap) { break } } req := &bytecodeHealRequest{ peer: idle, id: reqid, + time: time.Now(), deliver: success, revert: fail, cancel: cancel, @@ -1343,8 +1427,9 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai hashes: hashes, task: s.healer, } - req.timeout = time.AfterFunc(requestTimeout, func() { + req.timeout = time.AfterFunc(s.rates.TargetTimeout(), func() { peer.Log().Debug("Bytecode heal request timed out", "reqid", reqid) + s.rates.Update(idle, ByteCodesMsg, 0, 0) s.scheduleRevertBytecodeHealRequest(req) }) s.bytecodeHealReqs[reqid] = req @@ -2142,6 +2227,7 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco return nil } delete(s.accountReqs, id) + s.rates.Update(peer.ID(), AccountRangeMsg, time.Since(req.time), int(size)) // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content @@ -2253,6 +2339,7 @@ func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error return nil } delete(s.bytecodeReqs, id) + s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes)) // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content @@ -2361,6 +2448,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo return nil } delete(s.storageReqs, id) + s.rates.Update(peer.ID(), StorageRangesMsg, time.Since(req.time), int(size)) // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content @@ -2487,6 +2575,7 @@ func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error return nil } delete(s.trienodeHealReqs, id) + s.rates.Update(peer.ID(), TrieNodesMsg, time.Since(req.time), len(trienodes)) // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content @@ -2581,6 +2670,7 @@ func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) e return nil } delete(s.bytecodeHealReqs, id) + s.rates.Update(peer.ID(), ByteCodesMsg, time.Since(req.time), len(bytecodes)) // Clean up the request timeout timer, we'll see how to proceed further based // on the actual delivered content @@ -2756,3 +2846,24 @@ func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) { } return space.Uint64() - uint64(hashes), nil } + +// capacitySort implements the Sort interface, allowing sorting by peer message +// throughput. Note, callers should use sort.Reverse to get the desired effect +// of highest capacity being at the front. +type capacitySort struct { + ids []string + caps []float64 +} + +func (s *capacitySort) Len() int { + return len(s.ids) +} + +func (s *capacitySort) Less(i, j int) bool { + return s.caps[i] < s.caps[j] +} + +func (s *capacitySort) Swap(i, j int) { + s.ids[i], s.ids[j] = s.ids[j], s.ids[i] + s.caps[i], s.caps[j] = s.caps[j], s.caps[i] +} diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index a1cc3581a8..023fc8ee00 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -796,12 +796,6 @@ func TestMultiSyncManyUseless(t *testing.T) { // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { - // We're setting the timeout to very low, to increase the chance of the timeout - // being triggered. This was previously a cause of panic, when a response - // arrived simultaneously as a timeout was triggered. - defer func(old time.Duration) { requestTimeout = old }(requestTimeout) - requestTimeout = time.Millisecond - var ( once sync.Once cancel = make(chan struct{}) @@ -838,6 +832,11 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { mkSource("noStorage", true, false, true), mkSource("noTrie", true, true, false), ) + // We're setting the timeout to very low, to increase the chance of the timeout + // being triggered. This was previously a cause of panic, when a response + // arrived simultaneously as a timeout was triggered. + syncer.rates.OverrideTTLLimit = time.Millisecond + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) @@ -848,10 +847,6 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { // TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all func TestMultiSyncManyUnresponsive(t *testing.T) { - // We're setting the timeout to very low, to make the test run a bit faster - defer func(old time.Duration) { requestTimeout = old }(requestTimeout) - requestTimeout = time.Millisecond - var ( once sync.Once cancel = make(chan struct{}) @@ -888,6 +883,9 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { mkSource("noStorage", true, false, true), mkSource("noTrie", true, true, false), ) + // We're setting the timeout to very low, to make the test run a bit faster + syncer.rates.OverrideTTLLimit = time.Millisecond + done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) diff --git a/p2p/msgrate/msgrate.go b/p2p/msgrate/msgrate.go new file mode 100644 index 0000000000..7cd172c566 --- /dev/null +++ b/p2p/msgrate/msgrate.go @@ -0,0 +1,458 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package msgrate allows estimating the throughput of peers for more balanced syncs. +package msgrate + +import ( + "errors" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +// measurementImpact is the impact a single measurement has on a peer's final +// capacity value. A value closer to 0 reacts slower to sudden network changes, +// but it is also more stable against temporary hiccups. 0.1 worked well for +// most of Ethereum's existence, so might as well go with it. +const measurementImpact = 0.1 + +// capacityOverestimation is the ratio of items to over-estimate when retrieving +// a peer's capacity to avoid locking into a lower value due to never attempting +// to fetch more than some local stable value. +const capacityOverestimation = 1.01 + +// qosTuningPeers is the number of best peers to tune round trip times based on. +// An Ethereum node doesn't need hundreds of connections to operate correctly, +// so instead of lowering our download speed to the median of potentially many +// bad nodes, we can target a smaller set of vey good nodes. At worse this will +// result in less nodes to sync from, but that's still better than some hogging +// the pipeline. +const qosTuningPeers = 5 + +// rttMinEstimate is the minimal round trip time to target requests for. Since +// every request entails a 2 way latency + bandwidth + serving database lookups, +// it should be generous enough to permit meaningful work to be done on top of +// the transmission costs. +const rttMinEstimate = 2 * time.Second + +// rttMaxEstimate is the maximal round trip time to target requests for. Although +// the expectation is that a well connected node will never reach this, certain +// special connectivity ones might experience significant delays (e.g. satellite +// uplink with 3s RTT). This value should be low enough to forbid stalling the +// pipeline too long, but large enough to cover the worst of the worst links. +const rttMaxEstimate = 20 * time.Second + +// rttPushdownFactor is a multiplier to attempt forcing quicker requests than +// what the message rate tracker estimates. The reason is that message rate +// tracking adapts queries to the RTT, but multiple RTT values can be perfectly +// valid, they just result in higher packet sizes. Since smaller packets almost +// always result in stabler download streams, this factor hones in on the lowest +// RTT from all the functional ones. +const rttPushdownFactor = 0.9 + +// rttMinConfidence is the minimum value the roundtrip confidence factor may drop +// to. Since the target timeouts are based on how confident the tracker is in the +// true roundtrip, it's important to not allow too huge fluctuations. +const rttMinConfidence = 0.1 + +// ttlScaling is the multiplier that converts the estimated roundtrip time to a +// timeout cap for network requests. The expectation is that peers' response time +// will fluctuate around the estimated roundtrip, but depending in their load at +// request time, it might be higher than anticipated. This scaling factor ensures +// that we allow remote connections some slack but at the same time do enforce a +// behavior similar to our median peers. +const ttlScaling = 3 + +// ttlLimit is the maximum timeout allowance to prevent reaching crazy numbers +// if some unforeseen network events shappen. As much as we try to hone in on +// the most optimal values, it doesn't make any sense to go above a threshold, +// even if everything is slow and screwy. +const ttlLimit = time.Minute + +// tuningConfidenceCap is the number of active peers above which to stop detuning +// the confidence number. The idea here is that once we hone in on the capacity +// of a meaningful number of peers, adding one more should ot have a significant +// impact on things, so just ron with the originals. +const tuningConfidenceCap = 10 + +// tuningImpact is the influence that a new tuning target has on the previously +// cached value. This number is mostly just an out-of-the-blue heuristic that +// prevents the estimates from jumping around. There's no particular reason for +// the current value. +const tuningImpact = 0.25 + +// Tracker estimates the throughput capacity of a peer with regard to each data +// type it can deliver. The goal is to dynamically adjust request sizes to max +// out network throughput without overloading either the peer or th elocal node. +// +// By tracking in real time the latencies and bandiwdths peers exhibit for each +// packet type, it's possible to prevent overloading by detecting a slowdown on +// one type when another type is pushed too hard. +// +// Similarly, real time measurements also help avoid overloading the local net +// connection if our peers would otherwise be capable to deliver more, but the +// local link is saturated. In that case, the live measurements will force us +// to reduce request sizes until the throughput gets stable. +// +// Lastly, message rate measurements allows us to detect if a peer is unsuaully +// slow compared to other peers, in which case we can decide to keep it around +// or free up the slot so someone closer. +// +// Since throughput tracking and estimation adapts dynamically to live network +// conditions, it's fine to have multiple trackers locally track the same peer +// in different subsystem. The throughput will simply be distributed across the +// two trackers if both are highly active. +type Tracker struct { + // capacity is the number of items retrievable per second of a given type. + // It is analogous to bandwidth, but we deliberately avoided using bytes + // as the unit, since serving nodes also spend a lot of time loading data + // from disk, which is linear in the number of items, but mostly constant + // in their sizes. + // + // Callers of course are free to use the item counter as a byte counter if + // or when their protocol of choise if capped by bytes instead of items. + // (eg. eth.getHeaders vs snap.getAccountRange). + capacity map[uint64]float64 + + // roundtrip is the latency a peer in general responds to data requests. + // This number is not used inside the tracker, but is exposed to compare + // peers to each other and filter out slow ones. Note however, it only + // makes sense to compare RTTs if the caller caters request sizes for + // each peer to target the same RTT. There's no need to make this number + // the real networking RTT, we just need a number to compare peers with. + roundtrip time.Duration + + lock sync.RWMutex +} + +// NewTracker creates a new message rate tracker for a specific peer. An initial +// RTT is needed to avoid a peer getting marked as an outlier compared to others +// right after joining. It's suggested to use the median rtt across all peers to +// init a new peer tracker. +func NewTracker(caps map[uint64]float64, rtt time.Duration) *Tracker { + if caps == nil { + caps = make(map[uint64]float64) + } + return &Tracker{ + capacity: caps, + roundtrip: rtt, + } +} + +// Capacity calculates the number of items the peer is estimated to be able to +// retrieve within the alloted time slot. The method will round up any division +// errors and will add an additional overestimation ratio on top. The reason for +// overshooting the capacity is because certain message types might not increase +// the load proportionally to the requested items, so fetching a bit more might +// still take the same RTT. By forcefully overshooting by a small amount, we can +// avoid locking into a lower-that-real capacity. +func (t *Tracker) Capacity(kind uint64, targetRTT time.Duration) float64 { + t.lock.RLock() + defer t.lock.RUnlock() + + // Calculate the actual measured throughput + throughput := t.capacity[kind] * float64(targetRTT) / float64(time.Second) + + // Return an overestimation to force the peer out of a stuck minima, adding + // +1 in case the item count is too low for the overestimator to dent + return 1 + capacityOverestimation*throughput +} + +// Update modifies the peer's capacity values for a specific data type with a new +// measurement. If the delivery is zero, the peer is assumed to have either timed +// out or to not have the requested data, resulting in a slash to 0 capacity. This +// avoids assigning the peer retrievals that it won't be able to honour. +func (t *Tracker) Update(kind uint64, elapsed time.Duration, items int) { + t.lock.Lock() + defer t.lock.Unlock() + + // If nothing was delivered (timeout / unavailable data), reduce throughput + // to minimum + if items == 0 { + t.capacity[kind] = 0 + return + } + // Otherwise update the throughput with a new measurement + if elapsed <= 0 { + elapsed = 1 // +1 (ns) to ensure non-zero divisor + } + measured := float64(items) / (float64(elapsed) / float64(time.Second)) + + t.capacity[kind] = (1-measurementImpact)*(t.capacity[kind]) + measurementImpact*measured + t.roundtrip = time.Duration((1-measurementImpact)*float64(t.roundtrip) + measurementImpact*float64(elapsed)) +} + +// Trackers is a set of message rate trackers across a number of peers with the +// goal of aggregating certain measurements across the entire set for outlier +// filtering and newly joining initialization. +type Trackers struct { + trackers map[string]*Tracker + + // roundtrip is the current best guess as to what is a stable round trip time + // across the entire collection of connected peers. This is derived from the + // various trackers added, but is used as a cache to avoid recomputing on each + // network request. The value is updated once every RTT to avoid fluctuations + // caused by hiccups or peer events. + roundtrip time.Duration + + // confidence represents the probability that the estimated roundtrip value + // is the real one across all our peers. The confidence value is used as an + // impact factor of new measurements on old estimates. As our connectivity + // stabilizes, this value gravitates towards 1, new measurements havinng + // almost no impact. If there's a large peer churn and few peers, then new + // measurements will impact it more. The confidence is increased with every + // packet and dropped with every new connection. + confidence float64 + + // tuned is the time instance the tracker recalculated its cached roundtrip + // value and confidence values. A cleaner way would be to have a heartbeat + // goroutine do it regularly, but that requires a lot of maintenance to just + // run every now and again. + tuned time.Time + + // The fields below can be used to override certain default values. Their + // purpose is to allow quicker tests. Don't use them in production. + OverrideTTLLimit time.Duration + + log log.Logger + lock sync.RWMutex +} + +// NewTrackers creates an empty set of trackers to be filled with peers. +func NewTrackers(log log.Logger) *Trackers { + return &Trackers{ + trackers: make(map[string]*Tracker), + roundtrip: rttMaxEstimate, + confidence: 1, + tuned: time.Now(), + OverrideTTLLimit: ttlLimit, + log: log, + } +} + +// Track inserts a new tracker into the set. +func (t *Trackers) Track(id string, tracker *Tracker) error { + t.lock.Lock() + defer t.lock.Unlock() + + if _, ok := t.trackers[id]; ok { + return errors.New("already tracking") + } + t.trackers[id] = tracker + t.detune() + + return nil +} + +// Untrack stops tracking a previously added peer. +func (t *Trackers) Untrack(id string) error { + t.lock.Lock() + defer t.lock.Unlock() + + if _, ok := t.trackers[id]; !ok { + return errors.New("not tracking") + } + delete(t.trackers, id) + return nil +} + +// MedianRoundTrip returns the median RTT across all known trackers. The purpose +// of the median RTT is to initialize a new peer with sane statistics that it will +// hopefully outperform. If it seriously underperforms, there's a risk of dropping +// the peer, but that is ok as we're aiming for a strong median. +func (t *Trackers) MedianRoundTrip() time.Duration { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.medianRoundTrip() +} + +// medianRoundTrip is the internal lockless version of MedianRoundTrip to be used +// by the QoS tuner. +func (t *Trackers) medianRoundTrip() time.Duration { + // Gather all the currently measured round trip times + rtts := make([]float64, 0, len(t.trackers)) + for _, tt := range t.trackers { + tt.lock.RLock() + rtts = append(rtts, float64(tt.roundtrip)) + tt.lock.RUnlock() + } + sort.Float64s(rtts) + + median := rttMaxEstimate + if qosTuningPeers <= len(rtts) { + median = time.Duration(rtts[qosTuningPeers/2]) // Median of our best few peers + } else if len(rtts) > 0 { + median = time.Duration(rtts[len(rtts)/2]) // Median of all out connected peers + } + // Restrict the RTT into some QoS defaults, irrelevant of true RTT + if median < rttMinEstimate { + median = rttMinEstimate + } + if median > rttMaxEstimate { + median = rttMaxEstimate + } + return median +} + +// MeanCapacities returns the capacities averaged across all the added trackers. +// The purpos of the mean capacities are to initialize a new peer with some sane +// starting values that it will hopefully outperform. If the mean overshoots, the +// peer will be cut back to minimal capacity and given another chance. +func (t *Trackers) MeanCapacities() map[uint64]float64 { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.meanCapacities() +} + +// meanCapacities is the internal lockless version of MeanCapacities used for +// debug logging. +func (t *Trackers) meanCapacities() map[uint64]float64 { + capacities := make(map[uint64]float64) + for _, tt := range t.trackers { + tt.lock.RLock() + for key, val := range tt.capacity { + capacities[key] += val + } + tt.lock.RUnlock() + } + for key, val := range capacities { + capacities[key] = val / float64(len(t.trackers)) + } + return capacities +} + +// TargetRoundTrip returns the current target round trip time for a request to +// complete in.The returned RTT is slightly under the estimated RTT. The reason +// is that message rate estimation is a 2 dimensional problem which is solvable +// for any RTT. The goal is to gravitate towards smaller RTTs instead of large +// messages, to result in a stabler download stream. +func (t *Trackers) TargetRoundTrip() time.Duration { + // Recalculate the internal caches if it's been a while + t.tune() + + // Caches surely recent, return target roundtrip + t.lock.RLock() + defer t.lock.RUnlock() + + return time.Duration(float64(t.roundtrip) * rttPushdownFactor) +} + +// TargetTimeout returns the timeout allowance for a single request to finish +// under. The timeout is proportional to the roundtrip, but also takes into +// consideration the tracker's confidence in said roundtrip and scales it +// accordingly. The final value is capped to avoid runaway requests. +func (t *Trackers) TargetTimeout() time.Duration { + // Recalculate the internal caches if it's been a while + t.tune() + + // Caches surely recent, return target timeout + t.lock.RLock() + defer t.lock.RUnlock() + + return t.targetTimeout() +} + +// targetTimeout is the internal lockless version of TargetTimeout to be used +// during QoS tuning. +func (t *Trackers) targetTimeout() time.Duration { + timeout := time.Duration(ttlScaling * float64(t.roundtrip) / t.confidence) + if timeout > t.OverrideTTLLimit { + timeout = t.OverrideTTLLimit + } + return timeout +} + +// tune gathers the individual tracker statistics and updates the estimated +// request round trip time. +func (t *Trackers) tune() { + // Tune may be called concurrently all over the place, but we only want to + // periodically update and even then only once. First check if it was updated + // recently and abort if so. + t.lock.RLock() + dirty := time.Since(t.tuned) > t.roundtrip + t.lock.RUnlock() + if !dirty { + return + } + // If an update is needed, obtain a write lock but make sure we don't update + // it on all concurrent threads one by one. + t.lock.Lock() + defer t.lock.Unlock() + + if dirty := time.Since(t.tuned) > t.roundtrip; !dirty { + return // A concurrent request beat us to the tuning + } + // First thread reaching the tuning point, update the estimates and return + t.roundtrip = time.Duration((1-tuningImpact)*float64(t.roundtrip) + tuningImpact*float64(t.medianRoundTrip())) + t.confidence = t.confidence + (1-t.confidence)/2 + + t.tuned = time.Now() + t.log.Debug("Recalculated msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout(), "next", t.tuned.Add(t.roundtrip)) + t.log.Trace("Debug dump of mean capacities", "caps", log.Lazy{Fn: t.meanCapacities}) +} + +// detune reduces the tracker's confidence in order to make fresh measurements +// have a larger impact on the estimates. It is meant to be used during new peer +// connections so they can have a proper impact on the estimates. +func (t *Trackers) detune() { + // If we have a single peer, confidence is always 1 + if len(t.trackers) == 1 { + t.confidence = 1 + return + } + // If we have a ton of peers, don't drop the confidence since there's enough + // remaining to retain the same throughput + if len(t.trackers) >= tuningConfidenceCap { + return + } + // Otherwise drop the confidence factor + peers := float64(len(t.trackers)) + + t.confidence = t.confidence * (peers - 1) / peers + if t.confidence < rttMinConfidence { + t.confidence = rttMinConfidence + } + t.log.Debug("Relaxed msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout()) +} + +// Capacity is a helper function to access a specific tracker without having to +// track it explicitly outside. +func (t *Trackers) Capacity(id string, kind uint64, targetRTT time.Duration) float64 { + t.lock.RLock() + defer t.lock.RUnlock() + + tracker := t.trackers[id] + if tracker == nil { + return 1 // Unregister race, don't return 0, it's a dangerous number + } + return tracker.Capacity(kind, targetRTT) +} + +// Update is a helper function to access a specific tracker without having to +// track it explicitly outside. +func (t *Trackers) Update(id string, kind uint64, elapsed time.Duration, items int) { + t.lock.RLock() + defer t.lock.RUnlock() + + if tracker := t.trackers[id]; tracker != nil { + tracker.Update(kind, elapsed, items) + } +} From 16bc57438bd3d28e947d12f6f295da62e4ca9e26 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 20 May 2021 09:24:41 +0200 Subject: [PATCH 561/948] p2p/dnsdisc: fix crash when iterator closed before first call to Next (#22906) --- p2p/dnsdisc/client.go | 6 ++++++ p2p/dnsdisc/client_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go index f2a4bed4c6..096df06a54 100644 --- a/p2p/dnsdisc/client.go +++ b/p2p/dnsdisc/client.go @@ -298,6 +298,12 @@ func (it *randomIterator) pickTree() *clientTree { it.mu.Lock() defer it.mu.Unlock() + // First check if iterator was closed. + // Need to do this here to avoid nil map access in rebuildTrees. + if it.trees == nil { + return nil + } + // Rebuild the trees map if any links have changed. if it.lc.changed { it.rebuildTrees() diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go index 741bee4230..9320dd667a 100644 --- a/p2p/dnsdisc/client_test.go +++ b/p2p/dnsdisc/client_test.go @@ -115,6 +115,21 @@ func TestIterator(t *testing.T) { checkIterator(t, it, nodes) } +func TestIteratorCloseWithoutNext(t *testing.T) { + tree1, url1 := makeTestTree("t1", nil, nil) + c := NewClient(Config{Resolver: newMapResolver(tree1.ToTXT("t1"))}) + it, err := c.NewIterator(url1) + if err != nil { + t.Fatal(err) + } + + it.Close() + ok := it.Next() + if ok { + t.Fatal("Next returned true after Close") + } +} + // This test checks if closing randomIterator races. func TestIteratorClose(t *testing.T) { nodes := testNodes(nodesSeed1, 500) From a6c462781f2ebac39b8bbcbbfeb01a6e70b46997 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 21 May 2021 09:59:26 +0200 Subject: [PATCH 562/948] EIP-1559: miner changes (#22896) * core/types, miner: create TxWithMinerFee wrapper, add EIP-1559 support to TransactionsByMinerFeeAndNonce miner: set base fee when creating a new header, handle gas limit, log miner fees * all: rename to NewTransactionsByPriceAndNonce * core/types, miner: rename to NewTransactionsByPriceAndNonce + EffectiveTip miner: activate 1559 for testGenerateBlockAndImport tests * core,miner: revert naming to TransactionsByPriceAndTime * core/types/transaction: update effective tip calculation logic * miner: update aleut to london * core/types/transaction_test: use correct signer for 1559 txs + add back sender check * miner/worker: calculate gas target from gas limit * core, miner: fix block gas limits for 1559 Co-authored-by: Ansgar Dietrichs Co-authored-by: lightclient@protonmail.com --- core/bench_test.go | 2 +- core/block_validator.go | 36 ++++++++++++--- core/block_validator_test.go | 33 ++++++++++++++ core/chain_makers.go | 2 +- core/state_processor_test.go | 2 +- core/types/transaction.go | 80 +++++++++++++++++++++++++--------- core/types/transaction_test.go | 63 ++++++++++++++++++++++---- eth/catalyst/api.go | 2 +- miner/worker.go | 28 ++++++++---- miner/worker_test.go | 6 ++- 10 files changed, 206 insertions(+), 48 deletions(-) diff --git a/core/bench_test.go b/core/bench_test.go index 0c49907e64..ce288d372e 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -112,7 +112,7 @@ func genTxRing(naccounts int) func(int, *BlockGen) { from := 0 return func(i int, gen *BlockGen) { block := gen.PrevBlock(i - 1) - gas := CalcGasLimit(block, block.GasLimit(), block.GasLimit()) + gas := block.GasLimit() for { gas -= params.TxGas if gas < params.TxGas { diff --git a/core/block_validator.go b/core/block_validator.go index 8dbd0f7552..d317d82ed4 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -106,12 +106,12 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD // to keep the baseline gas above the provided floor, and increase it towards the // ceil if the blocks are full. If the ceil is exceeded, it will always decrease // the gas allowance. -func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 { +func CalcGasLimit(parentGasUsed, parentGasLimit, gasFloor, gasCeil uint64) uint64 { // contrib = (parentGasUsed * 3 / 2) / 1024 - contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor + contrib := (parentGasUsed + parentGasUsed/2) / params.GasLimitBoundDivisor // decay = parentGasLimit / 1024 -1 - decay := parent.GasLimit()/params.GasLimitBoundDivisor - 1 + decay := parentGasLimit/params.GasLimitBoundDivisor - 1 /* strategy: gasLimit of block-to-mine is set based on parent's @@ -120,21 +120,45 @@ func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 { at that usage) the amount increased/decreased depends on how far away from parentGasLimit * (2/3) parentGasUsed is. */ - limit := parent.GasLimit() - decay + contrib + limit := parentGasLimit - decay + contrib if limit < params.MinGasLimit { limit = params.MinGasLimit } // If we're outside our allowed gas range, we try to hone towards them if limit < gasFloor { - limit = parent.GasLimit() + decay + limit = parentGasLimit + decay if limit > gasFloor { limit = gasFloor } } else if limit > gasCeil { - limit = parent.GasLimit() - decay + limit = parentGasLimit - decay if limit < gasCeil { limit = gasCeil } } return limit } + +// CalcGasLimit1559 calculates the next block gas limit under 1559 rules. +func CalcGasLimit1559(parentGasLimit, desiredLimit uint64) uint64 { + delta := parentGasLimit/params.GasLimitBoundDivisor - 1 + limit := parentGasLimit + if desiredLimit < params.MinGasLimit { + desiredLimit = params.MinGasLimit + } + // If we're outside our allowed gas range, we try to hone towards them + if limit < desiredLimit { + limit = parentGasLimit + delta + if limit > desiredLimit { + limit = desiredLimit + } + return limit + } + if limit > desiredLimit { + limit = parentGasLimit - delta + if limit < desiredLimit { + limit = desiredLimit + } + } + return limit +} diff --git a/core/block_validator_test.go b/core/block_validator_test.go index dfb37b88cf..3b4de33789 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -197,3 +197,36 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { t.Errorf("verification count too large: have %d, want below %d", verified, 2*threads) } } + +func TestCalcGasLimit1559(t *testing.T) { + + for i, tc := range []struct { + pGasLimit uint64 + max uint64 + min uint64 + }{ + {20000000, 20019530, 19980470}, + {40000000, 40039061, 39960939}, + } { + // Increase + if have, want := CalcGasLimit1559(tc.pGasLimit, 2*tc.pGasLimit), tc.max; have != want { + t.Errorf("test %d: have %d want <%d", i, have, want) + } + // Decrease + if have, want := CalcGasLimit1559(tc.pGasLimit, 0), tc.min; have != want { + t.Errorf("test %d: have %d want >%d", i, have, want) + } + // Small decrease + if have, want := CalcGasLimit1559(tc.pGasLimit, tc.pGasLimit-1), tc.pGasLimit-1; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + // Small increase + if have, want := CalcGasLimit1559(tc.pGasLimit, tc.pGasLimit+1), tc.pGasLimit+1; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + // No change + if have, want := CalcGasLimit1559(tc.pGasLimit, tc.pGasLimit), tc.pGasLimit; have != want { + t.Errorf("test %d: have %d want %d", i, have, want) + } + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index b1b7dc3591..f7353ffce3 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -263,7 +263,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S Difficulty: parent.Difficulty(), UncleHash: parent.UncleHash(), }), - GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + GasLimit: parent.GasLimit(), Number: new(big.Int).Add(parent.Number(), common.Big1), Time: time, } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index c15d3d276d..ab446eb0a2 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -223,7 +223,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr Difficulty: parent.Difficulty(), UncleHash: parent.UncleHash(), }), - GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + GasLimit: parent.GasLimit(), Number: new(big.Int).Add(parent.Number(), common.Big1), Time: parent.Time() + 10, UncleHash: types.EmptyUncleHash, diff --git a/core/types/transaction.go b/core/types/transaction.go index ace1843e93..5347fdab8c 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -36,6 +36,7 @@ var ( ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures") ErrInvalidTxType = errors.New("transaction type not valid in this context") ErrTxTypeNotSupported = errors.New("transaction type not supported") + ErrFeeCapTooLow = errors.New("fee cap less than base fee") errEmptyTypedTx = errors.New("empty typed transaction bytes") ) @@ -299,6 +300,19 @@ func (tx *Transaction) Cost() *big.Int { return total } +// EffectiveTip returns the effective miner tip for the given base fee. +// Returns error in case of a negative effective miner tip. +func (tx *Transaction) EffectiveTip(baseFee *big.Int) (*big.Int, error) { + if baseFee == nil { + return tx.Tip(), nil + } + feeCap := tx.FeeCap() + if feeCap.Cmp(baseFee) == -1 { + return nil, ErrFeeCapTooLow + } + return math.BigMin(tx.Tip(), feeCap.Sub(feeCap, baseFee)), nil +} + // RawSignatureValues returns the V, R, S signature values of the transaction. // The return values should not be modified by the caller. func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) { @@ -400,24 +414,44 @@ func (s TxByNonce) Len() int { return len(s) } func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() } func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +// TxWithMinerFee wraps a transaction with its gas price or effective miner tip +type TxWithMinerFee struct { + tx *Transaction + minerFee *big.Int +} + +// NewTxWithMinerFee creates a wrapped transaction, calculating the effective +// miner tip if a base fee is provided. +// Returns error in case of a negative effective miner tip. +func NewTxWithMinerFee(tx *Transaction, baseFee *big.Int) (*TxWithMinerFee, error) { + minerFee, err := tx.EffectiveTip(baseFee) + if err != nil { + return nil, err + } + return &TxWithMinerFee{ + tx: tx, + minerFee: minerFee, + }, nil +} + // TxByPriceAndTime implements both the sort and the heap interface, making it useful // for all at once sorting as well as individually adding and removing elements. -type TxByPriceAndTime Transactions +type TxByPriceAndTime []*TxWithMinerFee func (s TxByPriceAndTime) Len() int { return len(s) } func (s TxByPriceAndTime) Less(i, j int) bool { // If the prices are equal, use the time the transaction was first seen for // deterministic sorting - cmp := s[i].GasPrice().Cmp(s[j].GasPrice()) + cmp := s[i].minerFee.Cmp(s[j].minerFee) if cmp == 0 { - return s[i].time.Before(s[j].time) + return s[i].tx.time.Before(s[j].tx.time) } return cmp > 0 } func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s *TxByPriceAndTime) Push(x interface{}) { - *s = append(*s, x.(*Transaction)) + *s = append(*s, x.(*TxWithMinerFee)) } func (s *TxByPriceAndTime) Pop() interface{} { @@ -432,9 +466,10 @@ func (s *TxByPriceAndTime) Pop() interface{} { // transactions in a profit-maximizing sorted order, while supporting removing // entire batches of transactions for non-executable accounts. type TransactionsByPriceAndNonce struct { - txs map[common.Address]Transactions // Per account nonce-sorted list of transactions - heads TxByPriceAndTime // Next transaction for each unique account (price heap) - signer Signer // Signer for the set of transactions + txs map[common.Address]Transactions // Per account nonce-sorted list of transactions + heads TxByPriceAndTime // Next transaction for each unique account (price heap) + signer Signer // Signer for the set of transactions + baseFee *big.Int // Current base fee } // NewTransactionsByPriceAndNonce creates a transaction set that can retrieve @@ -442,25 +477,28 @@ type TransactionsByPriceAndNonce struct { // // Note, the input map is reowned so the caller should not interact any more with // if after providing it to the constructor. -func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce { +func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions, baseFee *big.Int) *TransactionsByPriceAndNonce { // Initialize a price and received time based heap with the head transactions heads := make(TxByPriceAndTime, 0, len(txs)) for from, accTxs := range txs { - // Ensure the sender address is from the signer - if acc, _ := Sender(signer, accTxs[0]); acc != from { + acc, _ := Sender(signer, accTxs[0]) + wrapped, err := NewTxWithMinerFee(accTxs[0], baseFee) + // Remove transaction if sender doesn't match from, or if wrapping fails. + if acc != from || err != nil { delete(txs, from) continue } - heads = append(heads, accTxs[0]) + heads = append(heads, wrapped) txs[from] = accTxs[1:] } heap.Init(&heads) // Assemble and return the transaction set return &TransactionsByPriceAndNonce{ - txs: txs, - heads: heads, - signer: signer, + txs: txs, + heads: heads, + signer: signer, + baseFee: baseFee, } } @@ -469,18 +507,20 @@ func (t *TransactionsByPriceAndNonce) Peek() *Transaction { if len(t.heads) == 0 { return nil } - return t.heads[0] + return t.heads[0].tx } // Shift replaces the current best head with the next one from the same account. func (t *TransactionsByPriceAndNonce) Shift() { - acc, _ := Sender(t.signer, t.heads[0]) + acc, _ := Sender(t.signer, t.heads[0].tx) if txs, ok := t.txs[acc]; ok && len(txs) > 0 { - t.heads[0], t.txs[acc] = txs[0], txs[1:] - heap.Fix(&t.heads, 0) - } else { - heap.Pop(&t.heads) + if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil { + t.heads[0], t.txs[acc] = wrapped, txs[1:] + heap.Fix(&t.heads, 0) + return + } } + heap.Pop(&t.heads) } // Pop removes the best transaction, *not* replacing it with the next one from diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 3cece9c235..7c30834c02 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "math/big" + "math/rand" "reflect" "testing" "time" @@ -258,36 +259,77 @@ func TestRecipientNormal(t *testing.T) { } } +func TestTransactionPriceNonceSortLegacy(t *testing.T) { + testTransactionPriceNonceSort(t, nil) +} + +func TestTransactionPriceNonceSort1559(t *testing.T) { + testTransactionPriceNonceSort(t, big.NewInt(0)) + testTransactionPriceNonceSort(t, big.NewInt(5)) + testTransactionPriceNonceSort(t, big.NewInt(50)) +} + // Tests that transactions can be correctly sorted according to their price in // decreasing order, but at the same time with increasing nonces when issued by // the same account. -func TestTransactionPriceNonceSort(t *testing.T) { +func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { // Generate a batch of accounts to start with keys := make([]*ecdsa.PrivateKey, 25) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() } - signer := HomesteadSigner{} + signer := LatestSignerForChainID(common.Big1) // Generate a batch of transactions with overlapping values, but shifted nonces groups := map[common.Address]Transactions{} + expectedCount := 0 for start, key := range keys { addr := crypto.PubkeyToAddress(key.PublicKey) + count := 25 for i := 0; i < 25; i++ { - tx, _ := SignTx(NewTransaction(uint64(start+i), common.Address{}, big.NewInt(100), 100, big.NewInt(int64(start+i)), nil), signer, key) + var tx *Transaction + feeCap := rand.Intn(50) + if baseFee == nil { + tx = NewTx(&LegacyTx{ + Nonce: uint64(start + i), + To: &common.Address{}, + Value: big.NewInt(100), + Gas: 100, + GasPrice: big.NewInt(int64(feeCap)), + Data: nil, + }) + } else { + tx = NewTx(&DynamicFeeTx{ + Nonce: uint64(start + i), + To: &common.Address{}, + Value: big.NewInt(100), + Gas: 100, + FeeCap: big.NewInt(int64(feeCap)), + Tip: big.NewInt(int64(rand.Intn(feeCap + 1))), + Data: nil, + }) + if count == 25 && int64(feeCap) < baseFee.Int64() { + count = i + } + } + tx, err := SignTx(tx, signer, key) + if err != nil { + t.Fatalf("failed to sign tx: %s", err) + } groups[addr] = append(groups[addr], tx) } + expectedCount += count } // Sort the transactions and cross check the nonce ordering - txset := NewTransactionsByPriceAndNonce(signer, groups) + txset := NewTransactionsByPriceAndNonce(signer, groups, baseFee) txs := Transactions{} for tx := txset.Peek(); tx != nil; tx = txset.Peek() { txs = append(txs, tx) txset.Shift() } - if len(txs) != 25*25 { - t.Errorf("expected %d transactions, found %d", 25*25, len(txs)) + if len(txs) != expectedCount { + t.Errorf("expected %d transactions, found %d", expectedCount, len(txs)) } for i, txi := range txs { fromi, _ := Sender(signer, txi) @@ -303,7 +345,12 @@ func TestTransactionPriceNonceSort(t *testing.T) { if i+1 < len(txs) { next := txs[i+1] fromNext, _ := Sender(signer, next) - if fromi != fromNext && txi.GasPrice().Cmp(next.GasPrice()) < 0 { + tip, err := txi.EffectiveTip(baseFee) + nextTip, nextErr := next.EffectiveTip(baseFee) + if err != nil || nextErr != nil { + t.Errorf("error calculating effective tip") + } + if fromi != fromNext && tip.Cmp(nextTip) < 0 { t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) } } @@ -331,7 +378,7 @@ func TestTransactionTimeSort(t *testing.T) { groups[addr] = append(groups[addr], tx) } // Sort the transactions and cross check the nonce ordering - txset := NewTransactionsByPriceAndNonce(signer, groups) + txset := NewTransactionsByPriceAndNonce(signer, groups, nil) txs := Transactions{} for tx := txset.Peek(); tx != nil; tx = txset.Peek() { diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index d7e2af1c1a..d577e2a9ec 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -155,7 +155,7 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD var ( signer = types.MakeSigner(bc.Config(), header.Number) - txHeap = types.NewTransactionsByPriceAndNonce(signer, pending) + txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil) transactions []*types.Transaction ) for { diff --git a/miner/worker.go b/miner/worker.go index 2cee6af0c3..f9aae0fdc9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -499,7 +499,7 @@ func (w *worker) mainLoop() { acc, _ := types.Sender(w.current.signer, tx) txs[acc] = append(txs[acc], tx) } - txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs) + txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee) tcount := w.current.tcount w.commitTransactions(txset, coinbase, nil) // Only update the snapshot if any new transactons were added @@ -753,8 +753,9 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin return true } + gasLimit := w.current.header.GasLimit if w.current.gasPool == nil { - w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit) + w.current.gasPool = new(core.GasPool).AddGas(gasLimit) } var coalescedLogs []*types.Log @@ -769,7 +770,7 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone { // Notify resubmit loop to increase resubmitting interval due to too frequent commits. if atomic.LoadInt32(interrupt) == commitInterruptResubmit { - ratio := float64(w.current.header.GasLimit-w.current.gasPool.Gas()) / float64(w.current.header.GasLimit) + ratio := float64(gasLimit-w.current.gasPool.Gas()) / float64(gasLimit) if ratio < 0.1 { ratio = 0.1 } @@ -880,10 +881,20 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), - GasLimit: core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil), + GasLimit: core.CalcGasLimit(parent.GasUsed(), parent.GasLimit(), w.config.GasFloor, w.config.GasCeil), Extra: w.extra, Time: uint64(timestamp), } + // Set baseFee and GasLimit if we are on an EIP-1559 chain + if w.chainConfig.IsLondon(header.Number) { + header.BaseFee = misc.CalcBaseFee(w.chainConfig, parent.Header()) + parentGasLimit := parent.GasLimit() + if !w.chainConfig.IsLondon(parent.Number()) { + // Bump by 2x + parentGasLimit = parent.GasLimit() * params.ElasticityMultiplier + } + header.GasLimit = core.CalcGasLimit1559(parentGasLimit, w.config.GasCeil) + } // Only set the coinbase if our consensus engine is running (avoid spurious block rewards) if w.isRunning() { if w.coinbase == (common.Address{}) { @@ -973,13 +984,13 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) } } if len(localTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs) + txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs, header.BaseFee) if w.commitTransactions(txs, w.coinbase, interrupt) { return } } if len(remoteTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs) + txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs, header.BaseFee) if w.commitTransactions(txs, w.coinbase, interrupt) { return } @@ -1037,11 +1048,12 @@ func (w *worker) postSideBlock(event core.ChainSideEvent) { } } -// totalFees computes total consumed fees in ETH. Block transactions and receipts have to have the same order. +// totalFees computes total consumed miner fees in ETH. Block transactions and receipts have to have the same order. func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float { feesWei := new(big.Int) for i, tx := range block.Transactions() { - feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice())) + minerFee, _ := tx.EffectiveTip(block.BaseFee()) + feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), minerFee)) } return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether))) } diff --git a/miner/worker_test.go b/miner/worker_test.go index 0fe62316e1..0a1e55ff33 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -182,10 +182,11 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block { func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { var tx *types.Transaction + gasPrice := big.NewInt(10 * params.InitialBaseFee) if creation { - tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, nil, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey) + tx, _ = types.SignTx(types.NewContractCreation(b.txPool.Nonce(testBankAddress), big.NewInt(0), testGas, gasPrice, common.FromHex(testCode)), types.HomesteadSigner{}, testBankKey) } else { - tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey) + tx, _ = types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), testUserAddress, big.NewInt(1000), params.TxGas, gasPrice, nil), types.HomesteadSigner{}, testBankKey) } return tx } @@ -221,6 +222,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { engine = ethash.NewFaker() } + chainConfig.LondonBlock = big.NewInt(0) w, b := newTestWorker(t, chainConfig, engine, db, 0) defer w.close() From 81662fe82788a7b66aa95de86d7afd2cb4567370 Mon Sep 17 00:00:00 2001 From: Evolution404 <35091674+Evolution404@users.noreply.github.com> Date: Fri, 21 May 2021 16:33:59 +0800 Subject: [PATCH 563/948] core/rawdb: handle prefix in table.compact method (#22911) --- core/rawdb/table.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 323ef6293c..d5ef60ae50 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -131,6 +131,8 @@ func (t *table) Compact(start []byte, limit []byte) error { // If no start was specified, use the table prefix as the first value if start == nil { start = []byte(t.prefix) + } else { + start = append([]byte(t.prefix), start...) } // If no limit was specified, use the first element not matching the prefix // as the limit @@ -147,6 +149,8 @@ func (t *table) Compact(start []byte, limit []byte) error { limit = nil } } + } else { + limit = append([]byte(t.prefix), limit...) } // Range correctly calculated based on table prefix, delegate down return t.db.Compact(start, limit) From 835fe06f1dba8ce2764d6d501f977c22e97eac9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 21 May 2021 12:36:04 +0300 Subject: [PATCH 564/948] les: generate random nums directly, not via strange conversions --- les/client_handler.go | 7 ++++--- les/fetcher.go | 2 +- les/odr.go | 5 +++-- les/retrieve.go | 9 --------- les/txrelay.go | 3 ++- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/les/client_handler.go b/les/client_handler.go index 73149975c3..e95996c51f 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -19,6 +19,7 @@ package les import ( "context" "math/big" + "math/rand" "sync" "sync/atomic" "time" @@ -388,7 +389,7 @@ func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, s return dp.(*serverPeer) == pc.peer }, request: func(dp distPeer) func() { - reqID := genReqID() + reqID := rand.Uint64() peer := dp.(*serverPeer) cost := peer.getRequestCost(GetBlockHeadersMsg, amount) peer.fcServer.QueuedRequest(reqID, cost) @@ -412,7 +413,7 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip return dp.(*serverPeer) == pc.peer }, request: func(dp distPeer) func() { - reqID := genReqID() + reqID := rand.Uint64() peer := dp.(*serverPeer) cost := peer.getRequestCost(GetBlockHeadersMsg, amount) peer.fcServer.QueuedRequest(reqID, cost) @@ -429,7 +430,7 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip // RetrieveSingleHeaderByNumber requests a single header by the specified block // number. This function will wait the response until it's timeout or delivered. func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, number uint64) (*types.Header, error) { - reqID := genReqID() + reqID := rand.Uint64() rq := &distReq{ getCost: func(dp distPeer) uint64 { peer := dp.(*serverPeer) diff --git a/les/fetcher.go b/les/fetcher.go index fc4c5e386a..a6d1c93c4b 100644 --- a/les/fetcher.go +++ b/les/fetcher.go @@ -507,7 +507,7 @@ func (f *lightFetcher) requestHeaderByHash(peerid enode.ID) func(common.Hash) er getCost: func(dp distPeer) uint64 { return dp.(*serverPeer).getRequestCost(GetBlockHeadersMsg, 1) }, canSend: func(dp distPeer) bool { return dp.(*serverPeer).ID() == peerid }, request: func(dp distPeer) func() { - peer, id := dp.(*serverPeer), genReqID() + peer, id := dp.(*serverPeer), rand.Uint64() cost := peer.getRequestCost(GetBlockHeadersMsg, 1) peer.fcServer.QueuedRequest(id, cost) diff --git a/les/odr.go b/les/odr.go index d45c6a1a5d..10ff0854d3 100644 --- a/les/odr.go +++ b/les/odr.go @@ -18,6 +18,7 @@ package les import ( "context" + "math/rand" "sort" "time" @@ -156,7 +157,7 @@ func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequ var ( // Deep copy the request, so that the partial result won't be mixed. req = &TxStatusRequest{Hashes: req.Hashes} - id = genReqID() + id = rand.Uint64() distreq = &distReq{ getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) }, canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] }, @@ -200,7 +201,7 @@ func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) { lreq := LesRequest(req) - reqID := genReqID() + reqID := rand.Uint64() rq := &distReq{ getCost: func(dp distPeer) uint64 { return lreq.GetCost(dp.(*serverPeer)) diff --git a/les/retrieve.go b/les/retrieve.go index 3174d49878..307af04212 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -18,8 +18,6 @@ package les import ( "context" - "crypto/rand" - "encoding/binary" "fmt" "sync" "time" @@ -430,10 +428,3 @@ func (r *sentReq) stop(err error) { func (r *sentReq) getError() error { return r.err } - -// genReqID generates a new random request ID -func genReqID() uint64 { - var rnd [8]byte - rand.Read(rnd[:]) - return binary.BigEndian.Uint64(rnd[:]) -} diff --git a/les/txrelay.go b/les/txrelay.go index 9d29b2f234..40a51fb76f 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -18,6 +18,7 @@ package les import ( "context" + "math/rand" "sync" "github.com/ethereum/go-ethereum/common" @@ -117,7 +118,7 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { ll := list enc, _ := rlp.EncodeToBytes(ll) - reqID := genReqID() + reqID := rand.Uint64() rq := &distReq{ getCost: func(dp distPeer) uint64 { peer := dp.(*serverPeer) From 59f259b058b85eea38cd2686051a9076abb1e712 Mon Sep 17 00:00:00 2001 From: gary rong Date: Sat, 22 May 2021 02:52:51 +0800 Subject: [PATCH 565/948] miner/stress: update stress tests (#22919) This PR updates the miner stress tests and moves them to standalone packages, so that they can be run directly. --- miner/{stress_clique.go => stress/clique/clique.go} | 5 ++--- miner/{stress_ethash.go => stress/ethash/main.go} | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) rename miner/{stress_clique.go => stress/clique/clique.go} (98%) rename miner/{stress_ethash.go => stress/ethash/main.go} (97%) diff --git a/miner/stress_clique.go b/miner/stress/clique/clique.go similarity index 98% rename from miner/stress_clique.go rename to miner/stress/clique/clique.go index c585e0b1f6..dea1ab7453 100644 --- a/miner/stress_clique.go +++ b/miner/stress/clique/clique.go @@ -14,8 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build none - // This file contains a miner stress test based on the Clique consensus engine. package main @@ -36,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" @@ -192,7 +191,7 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { DatabaseCache: 256, DatabaseHandles: 256, TxPool: core.DefaultTxPoolConfig, - GPO: eth.DefaultConfig.GPO, + GPO: ethconfig.Defaults.GPO, Miner: miner.Config{ GasFloor: genesis.GasLimit * 9 / 10, GasCeil: genesis.GasLimit * 11 / 10, diff --git a/miner/stress_ethash.go b/miner/stress/ethash/main.go similarity index 97% rename from miner/stress_ethash.go rename to miner/stress/ethash/main.go index 0b838d48b9..0f27c5e74c 100644 --- a/miner/stress_ethash.go +++ b/miner/stress/ethash/main.go @@ -14,8 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// +build none - // This file contains a miner stress test based on the Ethash consensus engine. package main @@ -37,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" @@ -169,8 +168,8 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { DatabaseCache: 256, DatabaseHandles: 256, TxPool: core.DefaultTxPoolConfig, - GPO: eth.DefaultConfig.GPO, - Ethash: eth.DefaultConfig.Ethash, + GPO: ethconfig.Defaults.GPO, + Ethash: ethconfig.Defaults.Ethash, Miner: miner.Config{ GasFloor: genesis.GasLimit * 9 / 10, GasCeil: genesis.GasLimit * 11 / 10, From 0d076d92db39940ff181a0b07970c21bbe3521c2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 22 May 2021 13:34:29 +0200 Subject: [PATCH 566/948] rlp: use atomic.Value for type cache (#22902) All encoding/decoding operations read the type cache to find the writer/decoder function responsible for a type. When analyzing CPU profiles of geth during sync, I found that the use of sync.RWMutex in cache lookups appears in the profiles. It seems we are running into CPU cache contention problems when package rlp is heavily used on all CPU cores during sync. This change makes it use atomic.Value + a writer lock instead of sync.RWMutex. In the common case where the typeinfo entry is present in the cache, we simply fetch the map and lookup the type. --- rlp/decode.go | 4 +-- rlp/encode.go | 4 +-- rlp/encode_test.go | 33 +++++++++++++++++++ rlp/typecache.go | 82 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 94 insertions(+), 29 deletions(-) diff --git a/rlp/decode.go b/rlp/decode.go index 9767809717..e4262b64df 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -245,7 +245,7 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { } return decodeByteSlice, nil } - etypeinfo := cachedTypeInfo1(etype, tags{}) + etypeinfo := theTC.infoWhileGenerating(etype, tags{}) if etypeinfo.decoderErr != nil { return nil, etypeinfo.decoderErr } @@ -424,7 +424,7 @@ func zeroFields(structval reflect.Value, fields []field) { // makePtrDecoder creates a decoder that decodes into the pointer's element type. func makePtrDecoder(typ reflect.Type, tag tags) (decoder, error) { etype := typ.Elem() - etypeinfo := cachedTypeInfo1(etype, tags{}) + etypeinfo := theTC.infoWhileGenerating(etype, tags{}) switch { case etypeinfo.decoderErr != nil: return nil, etypeinfo.decoderErr diff --git a/rlp/encode.go b/rlp/encode.go index b7e74a133f..2e1b0102ca 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -517,7 +517,7 @@ func writeInterface(val reflect.Value, w *encbuf) error { } func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) { - etypeinfo := cachedTypeInfo1(typ.Elem(), tags{}) + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) if etypeinfo.writerErr != nil { return nil, etypeinfo.writerErr } @@ -585,7 +585,7 @@ func makeStructWriter(typ reflect.Type) (writer, error) { } func makePtrWriter(typ reflect.Type, ts tags) (writer, error) { - etypeinfo := cachedTypeInfo1(typ.Elem(), tags{}) + etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) if etypeinfo.writerErr != nil { return nil, etypeinfo.writerErr } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 74e8ededcb..0177bb0350 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -23,6 +23,7 @@ import ( "io" "io/ioutil" "math/big" + "runtime" "sync" "testing" @@ -480,3 +481,35 @@ func BenchmarkEncodeBigInts(b *testing.B) { } } } + +func BenchmarkEncodeConcurrentInterface(b *testing.B) { + type struct1 struct { + A string + B *big.Int + C [20]byte + } + value := []interface{}{ + uint(999), + &struct1{A: "hello", B: big.NewInt(0xFFFFFFFF)}, + [10]byte{1, 2, 3, 4, 5, 6}, + []string{"yeah", "yeah", "yeah"}, + } + + var wg sync.WaitGroup + for cpu := 0; cpu < runtime.NumCPU(); cpu++ { + wg.Add(1) + go func() { + defer wg.Done() + + var buffer bytes.Buffer + for i := 0; i < b.N; i++ { + buffer.Reset() + err := Encode(&buffer, value) + if err != nil { + panic(err) + } + } + }() + } + wg.Wait() +} diff --git a/rlp/typecache.go b/rlp/typecache.go index 3910dcf080..62553d3b55 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -21,13 +21,10 @@ import ( "reflect" "strings" "sync" + "sync/atomic" ) -var ( - typeCacheMutex sync.RWMutex - typeCache = make(map[typekey]*typeinfo) -) - +// typeinfo is an entry in the type cache. type typeinfo struct { decoder decoder decoderErr error // error from makeDecoder @@ -65,41 +62,76 @@ type decoder func(*Stream, reflect.Value) error type writer func(reflect.Value, *encbuf) error +var theTC = newTypeCache() + +type typeCache struct { + cur atomic.Value + + // This lock synchronizes writers. + mu sync.Mutex + next map[typekey]*typeinfo +} + +func newTypeCache() *typeCache { + c := new(typeCache) + c.cur.Store(make(map[typekey]*typeinfo)) + return c +} + func cachedDecoder(typ reflect.Type) (decoder, error) { - info := cachedTypeInfo(typ, tags{}) + info := theTC.info(typ) return info.decoder, info.decoderErr } func cachedWriter(typ reflect.Type) (writer, error) { - info := cachedTypeInfo(typ, tags{}) + info := theTC.info(typ) return info.writer, info.writerErr } -func cachedTypeInfo(typ reflect.Type, tags tags) *typeinfo { - typeCacheMutex.RLock() - info := typeCache[typekey{typ, tags}] - typeCacheMutex.RUnlock() - if info != nil { +func (c *typeCache) info(typ reflect.Type) *typeinfo { + key := typekey{Type: typ} + if info := c.cur.Load().(map[typekey]*typeinfo)[key]; info != nil { return info } - // not in the cache, need to generate info for this type. - typeCacheMutex.Lock() - defer typeCacheMutex.Unlock() - return cachedTypeInfo1(typ, tags) + + // Not in the cache, need to generate info for this type. + return c.generate(typ, tags{}) +} + +func (c *typeCache) generate(typ reflect.Type, tags tags) *typeinfo { + c.mu.Lock() + defer c.mu.Unlock() + + cur := c.cur.Load().(map[typekey]*typeinfo) + if info := cur[typekey{typ, tags}]; info != nil { + return info + } + + // Copy cur to next. + c.next = make(map[typekey]*typeinfo, len(cur)+1) + for k, v := range cur { + c.next[k] = v + } + + // Generate. + info := c.infoWhileGenerating(typ, tags) + + // next -> cur + c.cur.Store(c.next) + c.next = nil + return info } -func cachedTypeInfo1(typ reflect.Type, tags tags) *typeinfo { +func (c *typeCache) infoWhileGenerating(typ reflect.Type, tags tags) *typeinfo { key := typekey{typ, tags} - info := typeCache[key] - if info != nil { - // another goroutine got the write lock first + if info := c.next[key]; info != nil { return info } - // put a dummy value into the cache before generating. - // if the generator tries to lookup itself, it will get + // Put a dummy value into the cache before generating. + // If the generator tries to lookup itself, it will get // the dummy value and won't call itself recursively. - info = new(typeinfo) - typeCache[key] = info + info := new(typeinfo) + c.next[key] = info info.generate(typ, tags) return info } @@ -133,7 +165,7 @@ func structFields(typ reflect.Type) (fields []field, err error) { } else if anyOptional { return nil, fmt.Errorf(`rlp: struct field %v.%s needs "optional" tag`, typ, f.Name) } - info := cachedTypeInfo1(f.Type, tags) + info := theTC.infoWhileGenerating(f.Type, tags) fields = append(fields, field{i, info, tags.optional}) } } From 154ca32a8ade0a7d2461d0eb432361261a51a395 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Sat, 22 May 2021 15:10:16 +0200 Subject: [PATCH 567/948] rlp: optimize byte array handling (#22924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change improves the performance of encoding/decoding [N]byte. name old time/op new time/op delta DecodeByteArrayStruct-8 336ns ± 0% 246ns ± 0% -26.98% (p=0.000 n=9+10) EncodeByteArrayStruct-8 225ns ± 1% 148ns ± 1% -34.12% (p=0.000 n=10+10) name old alloc/op new alloc/op delta DecodeByteArrayStruct-8 120B ± 0% 48B ± 0% -60.00% (p=0.000 n=10+10) EncodeByteArrayStruct-8 0.00B 0.00B ~ (all equal) --- rlp/decode.go | 16 ++++++------- rlp/decode_test.go | 44 ++++++++++++++++++++++++++++++++++-- rlp/encode.go | 56 ++++++++++++++-------------------------------- rlp/encode_test.go | 19 ++++++++++++++++ rlp/safe.go | 26 +++++++++++++++++++++ rlp/unsafe.go | 35 +++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 rlp/safe.go create mode 100644 rlp/unsafe.go diff --git a/rlp/decode.go b/rlp/decode.go index e4262b64df..8121ab2e72 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -348,25 +348,23 @@ func decodeByteArray(s *Stream, val reflect.Value) error { if err != nil { return err } - vlen := val.Len() + slice := byteArrayBytes(val) switch kind { case Byte: - if vlen == 0 { + if len(slice) == 0 { return &decodeError{msg: "input string too long", typ: val.Type()} - } - if vlen > 1 { + } else if len(slice) > 1 { return &decodeError{msg: "input string too short", typ: val.Type()} } - bv, _ := s.Uint() - val.Index(0).SetUint(bv) + slice[0] = s.byteval + s.kind = -1 case String: - if uint64(vlen) < size { + if uint64(len(slice)) < size { return &decodeError{msg: "input string too long", typ: val.Type()} } - if uint64(vlen) > size { + if uint64(len(slice)) > size { return &decodeError{msg: "input string too short", typ: val.Type()} } - slice := val.Slice(0, vlen).Interface().([]byte) if err := s.readFull(slice); err != nil { return err } diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 87a3306332..36d254e18e 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -26,6 +26,8 @@ import ( "reflect" "strings" "testing" + + "github.com/ethereum/go-ethereum/common/math" ) func TestStreamKind(t *testing.T) { @@ -1063,7 +1065,7 @@ func ExampleStream() { // [102 111 111 98 97 114] } -func BenchmarkDecode(b *testing.B) { +func BenchmarkDecodeUints(b *testing.B) { enc := encodeTestSlice(90000) b.SetBytes(int64(len(enc))) b.ReportAllocs() @@ -1078,7 +1080,7 @@ func BenchmarkDecode(b *testing.B) { } } -func BenchmarkDecodeIntSliceReuse(b *testing.B) { +func BenchmarkDecodeUintsReused(b *testing.B) { enc := encodeTestSlice(100000) b.SetBytes(int64(len(enc))) b.ReportAllocs() @@ -1093,6 +1095,44 @@ func BenchmarkDecodeIntSliceReuse(b *testing.B) { } } +func BenchmarkDecodeByteArrayStruct(b *testing.B) { + enc, err := EncodeToBytes(&byteArrayStruct{}) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var out byteArrayStruct + for i := 0; i < b.N; i++ { + if err := DecodeBytes(enc, &out); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecodeBigInts(b *testing.B) { + ints := make([]*big.Int, 200) + for i := range ints { + ints[i] = math.BigPow(2, int64(i)) + } + enc, err := EncodeToBytes(ints) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(enc))) + b.ReportAllocs() + b.ResetTimer() + + var out []*big.Int + for i := 0; i < b.N; i++ { + if err := DecodeBytes(enc, &out); err != nil { + b.Fatal(err) + } + } +} + func encodeTestSlice(n uint) []byte { s := make([]uint, n) for i := uint(0); i < n; i++ { diff --git a/rlp/encode.go b/rlp/encode.go index 2e1b0102ca..3348644342 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -124,19 +124,15 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { } type encbuf struct { - str []byte // string data, contains everything except list headers - lheads []listhead // all list headers - lhsize int // sum of sizes of all encoded list headers - sizebuf [9]byte // auxiliary buffer for uint encoding - bufvalue reflect.Value // used in writeByteArrayCopy + str []byte // string data, contains everything except list headers + lheads []listhead // all list headers + lhsize int // sum of sizes of all encoded list headers + sizebuf [9]byte // auxiliary buffer for uint encoding } // encbufs are pooled. var encbufPool = sync.Pool{ - New: func() interface{} { - var bytes []byte - return &encbuf{bufvalue: reflect.ValueOf(&bytes).Elem()} - }, + New: func() interface{} { return new(encbuf) }, } func (w *encbuf) reset() { @@ -429,21 +425,14 @@ func writeBytes(val reflect.Value, w *encbuf) error { return nil } -var byteType = reflect.TypeOf(byte(0)) - func makeByteArrayWriter(typ reflect.Type) writer { - length := typ.Len() - if length == 0 { + switch typ.Len() { + case 0: return writeLengthZeroByteArray - } else if length == 1 { + case 1: return writeLengthOneByteArray - } - if typ.Elem() != byteType { - return writeNamedByteArray - } - return func(val reflect.Value, w *encbuf) error { - writeByteArrayCopy(length, val, w) - return nil + default: + return writeByteArray } } @@ -462,29 +451,18 @@ func writeLengthOneByteArray(val reflect.Value, w *encbuf) error { return nil } -// writeByteArrayCopy encodes byte arrays using reflect.Copy. This is -// the fast path for [N]byte where N > 1. -func writeByteArrayCopy(length int, val reflect.Value, w *encbuf) { - w.encodeStringHeader(length) - offset := len(w.str) - w.str = append(w.str, make([]byte, length)...) - w.bufvalue.SetBytes(w.str[offset:]) - reflect.Copy(w.bufvalue, val) -} - -// writeNamedByteArray encodes byte arrays with named element type. -// This exists because reflect.Copy can't be used with such types. -func writeNamedByteArray(val reflect.Value, w *encbuf) error { +func writeByteArray(val reflect.Value, w *encbuf) error { if !val.CanAddr() { - // Slice requires the value to be addressable. - // Make it addressable by copying. + // Getting the byte slice of val requires it to be addressable. Make it + // addressable by copying. copy := reflect.New(val.Type()).Elem() copy.Set(val) val = copy } - size := val.Len() - slice := val.Slice(0, size).Bytes() - w.encodeString(slice) + + slice := byteArrayBytes(val) + w.encodeStringHeader(len(slice)) + w.str = append(w.str, slice...) return nil } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 0177bb0350..08a2a84c62 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -513,3 +513,22 @@ func BenchmarkEncodeConcurrentInterface(b *testing.B) { } wg.Wait() } + +type byteArrayStruct struct { + A [20]byte + B [32]byte + C [32]byte +} + +func BenchmarkEncodeByteArrayStruct(b *testing.B) { + var out bytes.Buffer + var value byteArrayStruct + + b.ReportAllocs() + for i := 0; i < b.N; i++ { + out.Reset() + if err := Encode(&out, &value); err != nil { + b.Fatal(err) + } + } +} diff --git a/rlp/safe.go b/rlp/safe.go new file mode 100644 index 0000000000..c881650a0d --- /dev/null +++ b/rlp/safe.go @@ -0,0 +1,26 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build nacl js !cgo + +package rlp + +import "reflect" + +// byteArrayBytes returns a slice of the byte array v. +func byteArrayBytes(v reflect.Value) []byte { + return v.Slice(0, v.Len()).Bytes() +} diff --git a/rlp/unsafe.go b/rlp/unsafe.go new file mode 100644 index 0000000000..94ed5405a8 --- /dev/null +++ b/rlp/unsafe.go @@ -0,0 +1,35 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build !nacl,!js,cgo + +package rlp + +import ( + "reflect" + "unsafe" +) + +// byteArrayBytes returns a slice of the byte array v. +func byteArrayBytes(v reflect.Value) []byte { + len := v.Len() + var s []byte + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) + hdr.Data = v.UnsafeAddr() + hdr.Cap = len + hdr.Len = len + return s +} From 93407b14a66287d1a0ad0140b9c5f754c11ad437 Mon Sep 17 00:00:00 2001 From: Fire Man <55934298+basdevelop@users.noreply.github.com> Date: Mon, 24 May 2021 20:34:38 +0800 Subject: [PATCH 568/948] core: make txpool free space calculation more accurate (#22933) --- core/tx_pool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 5db1d3df32..0abc092ecb 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -595,7 +595,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e return false, err } // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Count()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { + if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it if !isLocal && pool.priced.Underpriced(tx) { log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) From 017cf71fbd51ede510dd0e40e02f89b5340eea93 Mon Sep 17 00:00:00 2001 From: ucwong Date: Tue, 25 May 2021 16:14:39 +0800 Subject: [PATCH 569/948] rlp, tests/fuzzers/bls12381: gofmt (#22937) --- rlp/safe.go | 2 +- tests/fuzzers/bls12381/bls12381_fuzz.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rlp/safe.go b/rlp/safe.go index c881650a0d..a80380aef4 100644 --- a/rlp/safe.go +++ b/rlp/safe.go @@ -22,5 +22,5 @@ import "reflect" // byteArrayBytes returns a slice of the byte array v. func byteArrayBytes(v reflect.Value) []byte { - return v.Slice(0, v.Len()).Bytes() + return v.Slice(0, v.Len()).Bytes() } diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go index 298050ad36..c0f452f3ed 100644 --- a/tests/fuzzers/bls12381/bls12381_fuzz.go +++ b/tests/fuzzers/bls12381/bls12381_fuzz.go @@ -159,7 +159,7 @@ func FuzzCrossG1MultiExp(data []byte) int { gethPoints = append(gethPoints, new(bls12381.PointG1).Set(kp1)) gnarkPoints = append(gnarkPoints, *cp1) } - if len(gethScalars) == 0{ + if len(gethScalars) == 0 { return 0 } // compute multi exponentiation From 4d33de9b4975be0f3450fa44d6c912e4331ca0c8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 25 May 2021 21:56:25 +0200 Subject: [PATCH 570/948] rlp: optimize big.Int decoding for size <= 32 bytes (#22927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change grows the static integer buffer in Stream to 32 bytes, making it possible to decode 256bit integers without allocating a temporary buffer. In the recent commit 088da24, Stream struct size decreased from 120 bytes down to 88 bytes. This commit grows the struct to 112 bytes again, but the size change will not degrade performance because Stream instances are internally cached in sync.Pool. name old time/op new time/op delta DecodeBigInts-8 12.2µs ± 0% 8.6µs ± 4% -29.58% (p=0.000 n=9+10) name old speed new speed delta DecodeBigInts-8 230MB/s ± 0% 326MB/s ± 4% +42.04% (p=0.000 n=9+10) --- rlp/decode.go | 60 ++++++++++++++++++++++++++++++++++++---------- rlp/decode_test.go | 22 ++++++++++++++--- rlp/encode_test.go | 8 +++++++ 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/rlp/decode.go b/rlp/decode.go index 8121ab2e72..ac04d5d569 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -220,20 +220,51 @@ func decodeBigIntNoPtr(s *Stream, val reflect.Value) error { } func decodeBigInt(s *Stream, val reflect.Value) error { - b, err := s.Bytes() - if err != nil { + var buffer []byte + kind, size, err := s.Kind() + switch { + case err != nil: return wrapStreamError(err, val.Type()) + case kind == List: + return wrapStreamError(ErrExpectedString, val.Type()) + case kind == Byte: + buffer = s.uintbuf[:1] + buffer[0] = s.byteval + s.kind = -1 // re-arm Kind + case size == 0: + // Avoid zero-length read. + s.kind = -1 + case size <= uint64(len(s.uintbuf)): + // For integers smaller than s.uintbuf, allocating a buffer + // can be avoided. + buffer = s.uintbuf[:size] + if err := s.readFull(buffer); err != nil { + return wrapStreamError(err, val.Type()) + } + // Reject inputs where single byte encoding should have been used. + if size == 1 && buffer[0] < 128 { + return wrapStreamError(ErrCanonSize, val.Type()) + } + default: + // For large integers, a temporary buffer is needed. + buffer = make([]byte, size) + if err := s.readFull(buffer); err != nil { + return wrapStreamError(err, val.Type()) + } + } + + // Reject leading zero bytes. + if len(buffer) > 0 && buffer[0] == 0 { + return wrapStreamError(ErrCanonInt, val.Type()) } + + // Set the integer bytes. i := val.Interface().(*big.Int) if i == nil { i = new(big.Int) val.Set(reflect.ValueOf(i)) } - // Reject leading zero bytes. - if len(b) > 0 && b[0] == 0 { - return wrapStreamError(ErrCanonInt, val.Type()) - } - i.SetBytes(b) + i.SetBytes(buffer) return nil } @@ -563,7 +594,7 @@ type Stream struct { size uint64 // size of value ahead kinderr error // error from last readKind stack []uint64 // list sizes - uintbuf [8]byte // auxiliary buffer for integer decoding + uintbuf [32]byte // auxiliary buffer for integer decoding kind Kind // kind of value ahead byteval byte // value of single byte in type tag limited bool // true if input limit is in effect @@ -817,7 +848,7 @@ func (s *Stream) Reset(r io.Reader, inputLimit uint64) { s.kind = -1 s.kinderr = nil s.byteval = 0 - s.uintbuf = [8]byte{} + s.uintbuf = [32]byte{} } // Kind returns the kind and size of the next value in the @@ -927,17 +958,20 @@ func (s *Stream) readUint(size byte) (uint64, error) { b, err := s.readByte() return uint64(b), err default: + buffer := s.uintbuf[:8] + for i := range buffer { + buffer[i] = 0 + } start := int(8 - size) - s.uintbuf = [8]byte{} - if err := s.readFull(s.uintbuf[start:]); err != nil { + if err := s.readFull(buffer[start:]); err != nil { return 0, err } - if s.uintbuf[start] == 0 { + if buffer[start] == 0 { // Note: readUint is also used to decode integer values. // The error needs to be adjusted to become ErrCanonInt in this case. return 0, ErrCanonSize } - return binary.BigEndian.Uint64(s.uintbuf[:]), nil + return binary.BigEndian.Uint64(buffer[:]), nil } } diff --git a/rlp/decode_test.go b/rlp/decode_test.go index 36d254e18e..7c3dafeac4 100644 --- a/rlp/decode_test.go +++ b/rlp/decode_test.go @@ -329,6 +329,11 @@ type recstruct struct { Child *recstruct `rlp:"nil"` } +type bigIntStruct struct { + I *big.Int + B string +} + type invalidNilTag struct { X []byte `rlp:"nil"` } @@ -405,10 +410,11 @@ type ignoredField struct { } var ( - veryBigInt = big.NewInt(0).Add( + veryBigInt = new(big.Int).Add( big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16), big.NewInt(0xFFFF), ) + veryVeryBigInt = new(big.Int).Exp(veryBigInt, big.NewInt(8), nil) ) var decodeTests = []decodeTest{ @@ -479,12 +485,15 @@ var decodeTests = []decodeTest{ {input: "C0", ptr: new(string), error: "rlp: expected input string or byte for string"}, // big ints + {input: "80", ptr: new(*big.Int), value: big.NewInt(0)}, {input: "01", ptr: new(*big.Int), value: big.NewInt(1)}, {input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt}, + {input: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", ptr: new(*big.Int), value: veryVeryBigInt}, {input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works {input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"}, - {input: "820001", ptr: new(big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, - {input: "8105", ptr: new(big.Int), error: "rlp: non-canonical size information for *big.Int"}, + {input: "00", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "820001", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"}, + {input: "8105", ptr: new(*big.Int), error: "rlp: non-canonical size information for *big.Int"}, // structs { @@ -497,6 +506,13 @@ var decodeTests = []decodeTest{ ptr: new(recstruct), value: recstruct{1, &recstruct{2, &recstruct{3, nil}}}, }, + { + // This checks that empty big.Int works correctly in struct context. It's easy to + // miss the update of s.kind for this case, so it needs its own test. + input: "C58083343434", + ptr: new(bigIntStruct), + value: bigIntStruct{new(big.Int), "444"}, + }, // struct errors { diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 08a2a84c62..25d4aac267 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -131,6 +131,14 @@ var encTests = []encTest{ val: big.NewInt(0).SetBytes(unhex("010000000000000000000000000000000000000000000000000000000000000000")), output: "A1010000000000000000000000000000000000000000000000000000000000000000", }, + { + val: veryBigInt, + output: "89FFFFFFFFFFFFFFFFFF", + }, + { + val: veryVeryBigInt, + output: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", + }, // non-pointer big.Int {val: *big.NewInt(0), output: "80"}, From 836c647bdd65896c0e5bef97466d5d514db419e7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 25 May 2021 22:20:36 +0200 Subject: [PATCH 571/948] eth: unregister peer only when handler exits (#22908) This removes the error log message that says Ethereum peer removal failed ... err="peer not registered" The error happened because removePeer was called multiple times: once to disconnect the peer, and another time when the handler exited. With this change, removePeer now has the sole purpose of disconnecting the peer. Unregistering happens exactly once, when the handler exits. --- eth/handler.go | 15 +++++++---- eth/handler_eth_test.go | 59 ++++++++++++++++++++++++----------------- p2p/peer.go | 16 ++++++++++- 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 3f10750abf..cd16538044 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -287,7 +287,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { peer.Log().Error("Ethereum peer registration failed", "err", err) return err } - defer h.removePeer(peer.ID()) + defer h.unregisterPeer(peer.ID()) p := h.peers.peer(peer.ID()) if p == nil { @@ -354,9 +354,16 @@ func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error return handler(peer) } -// removePeer unregisters a peer from the downloader and fetchers, removes it from -// the set of tracked peers and closes the network connection to it. +// removePeer requests disconnection of a peer. func (h *handler) removePeer(id string) { + peer := h.peers.peer(id) + if peer != nil { + peer.Peer.Disconnect(p2p.DiscUselessPeer) + } +} + +// unregisterPeer removes a peer from the downloader, fetchers and main peer set. +func (h *handler) unregisterPeer(id string) { // Create a custom logger to avoid printing the entire id var logger log.Logger if len(id) < 16 { @@ -384,8 +391,6 @@ func (h *handler) removePeer(id string) { if err := h.peers.unregisterPeer(id); err != nil { logger.Error("Ethereum peer removal failed", "err", err) } - // Hard disconnect at the networking layer - peer.Peer.Disconnect(p2p.DiscUselessPeer) } func (h *handler) Start(maxPeers int) { diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 1d38e3b666..038de46990 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -144,8 +144,8 @@ func testForkIDSplit(t *testing.T, protocol uint) { defer p2pNoFork.Close() defer p2pProFork.Close() - peerNoFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + peerNoFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil) + peerProFork := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil) defer peerNoFork.Close() defer peerProFork.Close() @@ -206,8 +206,8 @@ func testForkIDSplit(t *testing.T, protocol uint) { defer p2pNoFork.Close() defer p2pProFork.Close() - peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil) - peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil) + peerNoFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pNoFork), p2pNoFork, nil) + peerProFork = eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pProFork), p2pProFork, nil) defer peerNoFork.Close() defer peerProFork.Close() @@ -257,8 +257,8 @@ func testRecvTransactions(t *testing.T, protocol uint) { defer p2pSrc.Close() defer p2pSink.Close() - src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool) - sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool) + src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool) defer src.Close() defer sink.Close() @@ -319,8 +319,8 @@ func testSendTransactions(t *testing.T, protocol uint) { defer p2pSrc.Close() defer p2pSink.Close() - src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool) - sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool) + src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, handler.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, handler.txpool) defer src.Close() defer sink.Close() @@ -407,8 +407,8 @@ func testTransactionPropagation(t *testing.T, protocol uint) { defer sourcePipe.Close() defer sinkPipe.Close() - sourcePeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, source.txpool) - sinkPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, sink.txpool) + sourcePeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, source.txpool) + sinkPeer := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, sink.txpool) defer sourcePeer.Close() defer sinkPeer.Close() @@ -490,6 +490,8 @@ func TestCheckpointChallenge(t *testing.T) { } func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) { + t.Parallel() + // Reduce the checkpoint handshake challenge timeout defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout) syncChallengeTimeout = 250 * time.Millisecond @@ -513,20 +515,26 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo handler.handler.checkpointNumber = number handler.handler.checkpointHash = response.Hash() } - // Create a challenger peer and a challenged one + + // Create a challenger peer and a challenged one. p2pLocal, p2pRemote := p2p.MsgPipe() defer p2pLocal.Close() defer p2pRemote.Close() - local := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool) - remote := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool) + local := eth.NewPeer(eth.ETH65, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pLocal), p2pLocal, handler.txpool) + remote := eth.NewPeer(eth.ETH65, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pRemote), p2pRemote, handler.txpool) defer local.Close() defer remote.Close() - go handler.handler.runEthPeer(local, func(peer *eth.Peer) error { - return eth.Handle((*ethHandler)(handler.handler), peer) - }) - // Run the handshake locally to avoid spinning up a remote handler + handlerDone := make(chan struct{}) + go func() { + defer close(handlerDone) + handler.handler.runEthPeer(local, func(peer *eth.Peer) error { + return eth.Handle((*ethHandler)(handler.handler), peer) + }) + }() + + // Run the handshake locally to avoid spinning up a remote handler. var ( genesis = handler.chain.Genesis() head = handler.chain.CurrentBlock() @@ -535,12 +543,13 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil { t.Fatalf("failed to run protocol handshake") } - // Connect a new peer and check that we receive the checkpoint challenge + + // Connect a new peer and check that we receive the checkpoint challenge. if checkpoint { if err := remote.ExpectRequestHeadersByNumber(response.Number.Uint64(), 1, 0, false); err != nil { t.Fatalf("challenge mismatch: %v", err) } - // Create a block to reply to the challenge if no timeout is simulated + // Create a block to reply to the challenge if no timeout is simulated. if !timeout { if empty { if err := remote.SendBlockHeaders([]*types.Header{}); err != nil { @@ -557,11 +566,13 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo } } } + // Wait until the test timeout passes to ensure proper cleanup time.Sleep(syncChallengeTimeout + 300*time.Millisecond) - // Verify that the remote peer is maintained or dropped + // Verify that the remote peer is maintained or dropped. if drop { + <-handlerDone if peers := handler.handler.peers.len(); peers != 0 { t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) } @@ -608,8 +619,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { defer sourcePipe.Close() defer sinkPipe.Close() - sourcePeer := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil) - sinkPeer := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil) + sourcePeer := eth.NewPeer(eth.ETH65, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil) + sinkPeer := eth.NewPeer(eth.ETH65, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil) defer sourcePeer.Close() defer sinkPeer.Close() @@ -676,8 +687,8 @@ func testBroadcastMalformedBlock(t *testing.T, protocol uint) { defer p2pSrc.Close() defer p2pSink.Close() - src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, source.txpool) - sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, source.txpool) + src := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pSrc), p2pSrc, source.txpool) + sink := eth.NewPeer(protocol, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pSink), p2pSink, source.txpool) defer src.Close() defer sink.Close() diff --git a/p2p/peer.go b/p2p/peer.go index 8ebc858392..b6d0dbd1ae 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -115,7 +115,8 @@ type Peer struct { disc chan DiscReason // events receives message send / receive events if set - events *event.Feed + events *event.Feed + testPipe *MsgPipeRW // for testing } // NewPeer returns a peer for testing purposes. @@ -128,6 +129,15 @@ func NewPeer(id enode.ID, name string, caps []Cap) *Peer { return peer } +// NewPeerPipe creates a peer for testing purposes. +// The message pipe given as the last parameter is closed when +// Disconnect is called on the peer. +func NewPeerPipe(id enode.ID, name string, caps []Cap, pipe *MsgPipeRW) *Peer { + p := NewPeer(id, name, caps) + p.testPipe = pipe + return p +} + // ID returns the node's public key. func (p *Peer) ID() enode.ID { return p.rw.node.ID() @@ -185,6 +195,10 @@ func (p *Peer) LocalAddr() net.Addr { // Disconnect terminates the peer connection with the given reason. // It returns immediately and does not wait until the connection is closed. func (p *Peer) Disconnect(reason DiscReason) { + if p.testPipe != nil { + p.testPipe.Close() + } + select { case p.disc <- reason: case <-p.closed: From 51b32cc7e44e174316d3ae970680cfa9b77f6146 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 26 May 2021 04:30:21 +0800 Subject: [PATCH 572/948] internal/ethapi: merge CallArgs and SendTxArgs (#22718) There are two transaction parameter structures defined in the codebase, although for different purposes. But most of the parameters are shared. So it's nice to reduce the code duplication by merging them together. Co-authored-by: Martin Holst Swende --- eth/tracers/api.go | 2 +- eth/tracers/api_test.go | 20 +-- graphql/graphql.go | 8 +- internal/ethapi/api.go | 244 ++++------------------------ internal/ethapi/transaction_args.go | 185 +++++++++++++++++++++ 5 files changed, 236 insertions(+), 223 deletions(-) create mode 100644 internal/ethapi/transaction_args.go diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 172054e9b8..1e8759e69c 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -730,7 +730,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * // created during the execution of EVM if the given transaction was added on // top of the provided block and returns them as a JSON object. // You can provide -2 as a block number to trace on top of the pending block. -func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { +func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { // Try to retrieve the specified block var ( err error diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 24bce320cf..9ff01d66d5 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -198,7 +198,7 @@ func TestTraceCall(t *testing.T) { var testSuite = []struct { blockNumber rpc.BlockNumber - call ethapi.CallArgs + call ethapi.TransactionArgs config *TraceCallConfig expectErr error expect interface{} @@ -206,7 +206,7 @@ func TestTraceCall(t *testing.T) { // Standard JSON trace upon the genesis, plain transfer. { blockNumber: rpc.BlockNumber(0), - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -223,7 +223,7 @@ func TestTraceCall(t *testing.T) { // Standard JSON trace upon the head, plain transfer. { blockNumber: rpc.BlockNumber(genBlocks), - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -240,7 +240,7 @@ func TestTraceCall(t *testing.T) { // Standard JSON trace upon the non-existent block, error expects { blockNumber: rpc.BlockNumber(genBlocks + 1), - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -252,7 +252,7 @@ func TestTraceCall(t *testing.T) { // Standard JSON trace upon the latest block { blockNumber: rpc.LatestBlockNumber, - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -269,7 +269,7 @@ func TestTraceCall(t *testing.T) { // Standard JSON trace upon the pending block { blockNumber: rpc.PendingBlockNumber, - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -329,7 +329,7 @@ func TestOverridenTraceCall(t *testing.T) { var testSuite = []struct { blockNumber rpc.BlockNumber - call ethapi.CallArgs + call ethapi.TransactionArgs config *TraceCallConfig expectErr error expect *callTrace @@ -337,7 +337,7 @@ func TestOverridenTraceCall(t *testing.T) { // Succcessful call with state overriding { blockNumber: rpc.PendingBlockNumber, - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &randomAccounts[0].addr, To: &randomAccounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -361,7 +361,7 @@ func TestOverridenTraceCall(t *testing.T) { // Invalid call without state overriding { blockNumber: rpc.PendingBlockNumber, - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &randomAccounts[0].addr, To: &randomAccounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), @@ -390,7 +390,7 @@ func TestOverridenTraceCall(t *testing.T) { // } { blockNumber: rpc.PendingBlockNumber, - call: ethapi.CallArgs{ + call: ethapi.TransactionArgs{ From: &randomAccounts[0].addr, To: &randomAccounts[2].addr, Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number() diff --git a/graphql/graphql.go b/graphql/graphql.go index b1af7675bd..71d80d8abd 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -862,7 +862,7 @@ func (c *CallResult) Status() Long { } func (b *Block) Call(ctx context.Context, args struct { - Data ethapi.CallArgs + Data ethapi.TransactionArgs }) (*CallResult, error) { if b.numberOrHash == nil { _, err := b.resolve(ctx) @@ -887,7 +887,7 @@ func (b *Block) Call(ctx context.Context, args struct { } func (b *Block) EstimateGas(ctx context.Context, args struct { - Data ethapi.CallArgs + Data ethapi.TransactionArgs }) (Long, error) { if b.numberOrHash == nil { _, err := b.resolveHeader(ctx) @@ -937,7 +937,7 @@ func (p *Pending) Account(ctx context.Context, args struct { } func (p *Pending) Call(ctx context.Context, args struct { - Data ethapi.CallArgs + Data ethapi.TransactionArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) @@ -957,7 +957,7 @@ func (p *Pending) Call(ctx context.Context, args struct { } func (p *Pending) EstimateGas(ctx context.Context, args struct { - Data ethapi.CallArgs + Data ethapi.TransactionArgs }) (Long, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) gas, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7bc0477bd2..b06df8ff9f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -17,7 +17,6 @@ package ethapi import ( - "bytes" "context" "errors" "fmt" @@ -351,9 +350,9 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { // signTransaction sets defaults and signs the given transaction // NOTE: the caller needs to ensure that the nonceLock is held, if applicable, // and release it after the transaction has been submitted to the tx pool -func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArgs, passwd string) (*types.Transaction, error) { +func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *TransactionArgs, passwd string) (*types.Transaction, error) { // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} + account := accounts.Account{Address: args.from()} wallet, err := s.am.Find(account) if err != nil { return nil, err @@ -369,18 +368,18 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg } // SendTransaction will create a transaction from the given arguments and -// tries to sign it with the key associated with args.From. If the given passwd isn't -// able to decrypt the key it fails. -func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { +// tries to sign it with the key associated with args.From. If the given +// passwd isn't able to decrypt the key it fails. +func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) { if args.Nonce == nil { // Hold the addresse's mutex around signing to prevent concurrent assignment of // the same nonce to multiple accounts. - s.nonceLock.LockAddr(args.From) - defer s.nonceLock.UnlockAddr(args.From) + s.nonceLock.LockAddr(args.from()) + defer s.nonceLock.UnlockAddr(args.from()) } signed, err := s.signTransaction(ctx, &args, passwd) if err != nil { - log.Warn("Failed transaction send attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) + log.Warn("Failed transaction send attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err) return common.Hash{}, err } return SubmitTransaction(ctx, s.b, signed) @@ -390,9 +389,12 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs // tries to sign it with the key associated with args.From. If the given passwd isn't // able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast // to other nodes -func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) { +func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args TransactionArgs, passwd string) (*SignTransactionResult, error) { // No need to obtain the noncelock mutex, since we won't be sending this // tx into the transaction pool, but right back to the user + if args.From == nil { + return nil, fmt.Errorf("sender not specified") + } if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } @@ -408,7 +410,7 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs } signed, err := s.signTransaction(ctx, &args, passwd) if err != nil { - log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err) + log.Warn("Failed transaction sign attempt", "from", args.from(), "to", args.To, "value", args.Value.ToInt(), "err", err) return nil, err } data, err := signed.MarshalBinary() @@ -473,7 +475,7 @@ func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Byt // SignAndSendTransaction was renamed to SendTransaction. This method is deprecated // and will be removed in the future. It primary goal is to give clients time to update. -func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) { +func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args TransactionArgs, passwd string) (common.Hash, error) { return s.SendTransaction(ctx, args, passwd) } @@ -566,6 +568,7 @@ type AccountResult struct { StorageHash common.Hash `json:"storageHash"` StorageProof []StorageResult `json:"storageProof"` } + type StorageResult struct { Key string `json:"key"` Value *hexutil.Big `json:"value"` @@ -751,58 +754,6 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A return res[:], state.Error() } -// CallArgs represents the arguments for a call. -type CallArgs struct { - From *common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Data *hexutil.Bytes `json:"data"` - AccessList *types.AccessList `json:"accessList"` -} - -// ToMessage converts CallArgs to the Message type used by the core evm -func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message { - // Set sender address or use zero address if none specified. - var addr common.Address - if args.From != nil { - addr = *args.From - } - - // Set default gas & gas price if none were set - gas := globalGasCap - if gas == 0 { - gas = uint64(math.MaxUint64 / 2) - } - if args.Gas != nil { - gas = uint64(*args.Gas) - } - if globalGasCap != 0 && globalGasCap < gas { - log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) - gas = globalGasCap - } - gasPrice := new(big.Int) - if args.GasPrice != nil { - gasPrice = args.GasPrice.ToInt() - } - value := new(big.Int) - if args.Value != nil { - value = args.Value.ToInt() - } - var data []byte - if args.Data != nil { - data = *args.Data - } - var accessList types.AccessList - if args.AccessList != nil { - accessList = *args.AccessList - } - - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false) - return msg -} - // OverrideAccount indicates the overriding fields of account during the execution // of a message call. // Note, state and stateDiff can't be specified at the same time. If state is @@ -855,7 +806,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { return nil } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { +func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) @@ -943,7 +894,7 @@ func (e *revertError) ErrorData() interface{} { // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { +func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err @@ -955,7 +906,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr return result.Return(), result.Err } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( lo uint64 = params.TxGas - 1 @@ -1066,7 +1017,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash // EstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { +func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash @@ -1324,7 +1275,7 @@ type accessListResult struct { // CreateAccessList creates a EIP-2930 type AccessList for the given transaction. // Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state. -func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { +func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) { bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash @@ -1343,7 +1294,7 @@ func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxA // AccessList creates an access list for the given transaction. // If the accesslist creation fails an error is returned. // If the transaction itself fails, an vmErr is returned. -func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args SendTxArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) { +func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) { // Retrieve the execution context db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if db == nil || err != nil { @@ -1361,21 +1312,15 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if args.To != nil { to = *args.To } else { - to = crypto.CreateAddress(args.From, uint64(*args.Nonce)) - } - var input []byte - if args.Input != nil { - input = *args.Input - } else if args.Data != nil { - input = *args.Data + to = crypto.CreateAddress(args.from(), uint64(*args.Nonce)) } // Retrieve the precompiles since they don't need to be added to the access list precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number)) // Create an initial tracer - prevTracer := vm.NewAccessListTracer(nil, args.From, to, precompiles) + prevTracer := vm.NewAccessListTracer(nil, args.from(), to, precompiles) if args.AccessList != nil { - prevTracer = vm.NewAccessListTracer(*args.AccessList, args.From, to, precompiles) + prevTracer = vm.NewAccessListTracer(*args.AccessList, args.from(), to, precompiles) } for { // Retrieve the current access list to expand @@ -1393,10 +1338,10 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } // Copy the original db so we don't modify it statedb := db.Copy() - msg := types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), nil, nil, input, accessList, false) + msg := types.NewMessage(args.from(), args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), nil, nil, args.data(), accessList, false) // Apply the transaction with the access list tracer - tracer := vm.NewAccessListTracer(accessList, args.From, to, precompiles) + tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer, Debug: true} vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config) if err != nil { @@ -1597,123 +1542,6 @@ func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transacti return wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) } -// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool. -type SendTxArgs struct { - From common.Address `json:"from"` - To *common.Address `json:"to"` - Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Value *hexutil.Big `json:"value"` - Nonce *hexutil.Uint64 `json:"nonce"` - // We accept "data" and "input" for backwards-compatibility reasons. "input" is the - // newer name and should be preferred by clients. - Data *hexutil.Bytes `json:"data"` - Input *hexutil.Bytes `json:"input"` - - // For non-legacy transactions - AccessList *types.AccessList `json:"accessList,omitempty"` - ChainID *hexutil.Big `json:"chainId,omitempty"` -} - -// setDefaults fills in default values for unspecified tx fields. -func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { - if args.GasPrice == nil { - price, err := b.SuggestPrice(ctx) - if err != nil { - return err - } - args.GasPrice = (*hexutil.Big)(price) - } - if args.Value == nil { - args.Value = new(hexutil.Big) - } - if args.Nonce == nil { - nonce, err := b.GetPoolNonce(ctx, args.From) - if err != nil { - return err - } - args.Nonce = (*hexutil.Uint64)(&nonce) - } - if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { - return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) - } - if args.To == nil { - // Contract creation - var input []byte - if args.Data != nil { - input = *args.Data - } else if args.Input != nil { - input = *args.Input - } - if len(input) == 0 { - return errors.New(`contract creation without any data provided`) - } - } - // Estimate the gas usage if necessary. - if args.Gas == nil { - // For backwards-compatibility reason, we try both input and data - // but input is preferred. - input := args.Input - if input == nil { - input = args.Data - } - callArgs := CallArgs{ - From: &args.From, // From shouldn't be nil - To: args.To, - GasPrice: args.GasPrice, - Value: args.Value, - Data: input, - AccessList: args.AccessList, - } - pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) - if err != nil { - return err - } - args.Gas = &estimated - log.Trace("Estimate gas usage automatically", "gas", args.Gas) - } - if args.ChainID == nil { - id := (*hexutil.Big)(b.ChainConfig().ChainID) - args.ChainID = id - } - return nil -} - -// toTransaction converts the arguments to a transaction. -// This assumes that setDefaults has been called. -func (args *SendTxArgs) toTransaction() *types.Transaction { - var input []byte - if args.Input != nil { - input = *args.Input - } else if args.Data != nil { - input = *args.Data - } - var data types.TxData - if args.AccessList == nil { - data = &types.LegacyTx{ - To: args.To, - Nonce: uint64(*args.Nonce), - Gas: uint64(*args.Gas), - GasPrice: (*big.Int)(args.GasPrice), - Value: (*big.Int)(args.Value), - Data: input, - } - } else { - data = &types.AccessListTx{ - To: args.To, - ChainID: (*big.Int)(args.ChainID), - Nonce: uint64(*args.Nonce), - Gas: uint64(*args.Gas), - GasPrice: (*big.Int)(args.GasPrice), - Value: (*big.Int)(args.Value), - Data: input, - AccessList: *args.AccessList, - } - } - return types.NewTx(data) -} - // SubmitTransaction is a helper function that submits tx to txPool and logs a message. func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { // If the transaction fee cap is already specified, ensure the @@ -1746,9 +1574,9 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c // SendTransaction creates a transaction for the given argument, sign it and submit it to the // transaction pool. -func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { +func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) { // Look up the wallet containing the requested signer - account := accounts.Account{Address: args.From} + account := accounts.Account{Address: args.from()} wallet, err := s.b.AccountManager().Find(account) if err != nil { @@ -1758,8 +1586,8 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen if args.Nonce == nil { // Hold the addresse's mutex around signing to prevent concurrent assignment of // the same nonce to multiple accounts. - s.nonceLock.LockAddr(args.From) - defer s.nonceLock.UnlockAddr(args.From) + s.nonceLock.LockAddr(args.from()) + defer s.nonceLock.UnlockAddr(args.from()) } // Set some sanity defaults and terminate on failure @@ -1778,7 +1606,7 @@ func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args Sen // FillTransaction fills the defaults (nonce, gas, gasPrice) on a given unsigned transaction, // and returns it to the caller for further processing (signing + broadcast) -func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) { +func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { // Set some sanity defaults and terminate on failure if err := args.setDefaults(ctx, s.b); err != nil { return nil, err @@ -1836,7 +1664,7 @@ type SignTransactionResult struct { // SignTransaction will sign the given transaction with the from account. // The node needs to have the private key of the account corresponding with // the given from address and it needs to be unlocked. -func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) { +func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } @@ -1853,7 +1681,7 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil { return nil, err } - tx, err := s.sign(args.From, args.toTransaction()) + tx, err := s.sign(args.from(), args.toTransaction()) if err != nil { return nil, err } @@ -1889,7 +1717,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err // Resend accepts an existing transaction and a new gas price and limit. It will remove // the given transaction from the pool and reinsert it with the new gas price and limit. -func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { +func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) { if sendArgs.Nonce == nil { return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec") } @@ -1918,7 +1746,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr for _, p := range pending { wantSigHash := s.signer.Hash(matchTx) pFrom, err := types.Sender(s.signer, p) - if err == nil && pFrom == sendArgs.From && s.signer.Hash(p) == wantSigHash { + if err == nil && pFrom == sendArgs.from() && s.signer.Hash(p) == wantSigHash { // Match. Re-sign and send the transaction. if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 { sendArgs.GasPrice = gasPrice @@ -1926,7 +1754,7 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr if gasLimit != nil && *gasLimit != 0 { sendArgs.Gas = gasLimit } - signedTx, err := s.sign(sendArgs.From, sendArgs.toTransaction()) + signedTx, err := s.sign(sendArgs.from(), sendArgs.toTransaction()) if err != nil { return common.Hash{}, err } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go new file mode 100644 index 0000000000..4385a9e97a --- /dev/null +++ b/internal/ethapi/transaction_args.go @@ -0,0 +1,185 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethapi + +import ( + "bytes" + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// TransactionArgs represents the arguments to construct a new transaction +// or a message call. +type TransactionArgs struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Nonce *hexutil.Uint64 `json:"nonce"` + + // We accept "data" and "input" for backwards-compatibility reasons. + // "input" is the newer name and should be preferred by clients. + // Issue detail: https://github.com/ethereum/go-ethereum/issues/15628 + Data *hexutil.Bytes `json:"data"` + Input *hexutil.Bytes `json:"input"` + + // For non-legacy transactions + AccessList *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` +} + +// from retrieves the transaction sender address. +func (arg *TransactionArgs) from() common.Address { + if arg.From == nil { + return common.Address{} + } + return *arg.From +} + +// data retrieves the transaction calldata. Input field is preferred. +func (arg *TransactionArgs) data() []byte { + if arg.Input != nil { + return *arg.Input + } + if arg.Data != nil { + return *arg.Data + } + return nil +} + +// setDefaults fills in default values for unspecified tx fields. +func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { + if args.GasPrice == nil { + price, err := b.SuggestPrice(ctx) + if err != nil { + return err + } + args.GasPrice = (*hexutil.Big)(price) + } + if args.Value == nil { + args.Value = new(hexutil.Big) + } + if args.Nonce == nil { + nonce, err := b.GetPoolNonce(ctx, args.from()) + if err != nil { + return err + } + args.Nonce = (*hexutil.Uint64)(&nonce) + } + if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) { + return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`) + } + if args.To == nil && len(args.data()) == 0 { + return errors.New(`contract creation without any data provided`) + } + // Estimate the gas usage if necessary. + if args.Gas == nil { + // These fields are immutable during the estimation, safe to + // pass the pointer directly. + callArgs := TransactionArgs{ + From: args.From, + To: args.To, + GasPrice: args.GasPrice, + Value: args.Value, + Data: args.Data, + AccessList: args.AccessList, + } + pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) + if err != nil { + return err + } + args.Gas = &estimated + log.Trace("Estimate gas usage automatically", "gas", args.Gas) + } + if args.ChainID == nil { + id := (*hexutil.Big)(b.ChainConfig().ChainID) + args.ChainID = id + } + return nil +} + +// ToMessage converts TransactionArgs to the Message type used by the core evm +func (args *TransactionArgs) ToMessage(globalGasCap uint64) types.Message { + // Set sender address or use zero address if none specified. + addr := args.from() + + // Set default gas & gas price if none were set + gas := globalGasCap + if gas == 0 { + gas = uint64(math.MaxUint64 / 2) + } + if args.Gas != nil { + gas = uint64(*args.Gas) + } + if globalGasCap != 0 && globalGasCap < gas { + log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) + gas = globalGasCap + } + gasPrice := new(big.Int) + if args.GasPrice != nil { + gasPrice = args.GasPrice.ToInt() + } + value := new(big.Int) + if args.Value != nil { + value = args.Value.ToInt() + } + data := args.data() + var accessList types.AccessList + if args.AccessList != nil { + accessList = *args.AccessList + } + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, nil, nil, data, accessList, false) + return msg +} + +// toTransaction converts the arguments to a transaction. +// This assumes that setDefaults has been called. +func (args *TransactionArgs) toTransaction() *types.Transaction { + var data types.TxData + if args.AccessList == nil { + data = &types.LegacyTx{ + To: args.To, + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: args.data(), + } + } else { + data = &types.AccessListTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasPrice: (*big.Int)(args.GasPrice), + Value: (*big.Int)(args.Value), + Data: args.data(), + AccessList: *args.AccessList, + } + } + return types.NewTx(data) +} From 750115ff3903a4d54267e0934a3391e4e2c2e84a Mon Sep 17 00:00:00 2001 From: meowsbits Date: Tue, 25 May 2021 15:37:30 -0500 Subject: [PATCH 573/948] p2p/nat: skip TestUPNP in non-CI environments if discover fails (#22877) Fixes #21476 --- p2p/nat/natupnp_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/p2p/nat/natupnp_test.go b/p2p/nat/natupnp_test.go index 79f6d25ae8..17483a7036 100644 --- a/p2p/nat/natupnp_test.go +++ b/p2p/nat/natupnp_test.go @@ -21,6 +21,7 @@ import ( "io" "net" "net/http" + "os" "runtime" "strings" "testing" @@ -162,7 +163,11 @@ func TestUPNP_DDWRT(t *testing.T) { // Attempt to discover the fake device. discovered := discoverUPnP() if discovered == nil { - t.Fatalf("not discovered") + if os.Getenv("CI") != "" { + t.Fatalf("not discovered") + } else { + t.Skipf("UPnP not discovered (known issue, see https://github.com/ethereum/go-ethereum/issues/21476)") + } } upnp, _ := discovered.(*upnp) if upnp.service != "IGDv1-IP1" { From 6c7d6cf886484805ae218af9b372c96e3ca8315c Mon Sep 17 00:00:00 2001 From: Eugene Lepeico Date: Tue, 25 May 2021 23:47:14 +0300 Subject: [PATCH 574/948] tests: get test name from testing.T (#22941) There were 2 TODOs about that fix after Golang 1.8 release. It's here for 3 years already, so now should be the right time. --- tests/block_test.go | 4 ++-- tests/difficulty_test.go | 4 ++-- tests/init_test.go | 10 ++++------ tests/rlp_test.go | 2 +- tests/state_test.go | 9 ++++----- tests/transaction_test.go | 2 +- tests/vm_test.go | 4 ++-- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/tests/block_test.go b/tests/block_test.go index 4820ba733f..f7fbaea2a4 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -48,10 +48,10 @@ func TestBlockchain(t *testing.T) { // using 4.6 TGas bt.skipLoad(`.*randomStatetest94.json.*`) bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { + if err := bt.checkFailure(t, test.Run(false)); err != nil { t.Errorf("test without snapshotter failed: %v", err) } - if err := bt.checkFailure(t, name+"/snap", test.Run(true)); err != nil { + if err := bt.checkFailure(t, test.Run(true)); err != nil { t.Errorf("test with snapshotter failed: %v", err) } }) diff --git a/tests/difficulty_test.go b/tests/difficulty_test.go index e80cd248bc..acbf96e712 100644 --- a/tests/difficulty_test.go +++ b/tests/difficulty_test.go @@ -79,12 +79,12 @@ func TestDifficulty(t *testing.T) { dt.config("difficulty.json", mainnetChainConfig) dt.walk(t, difficultyTestDir, func(t *testing.T, name string, test *DifficultyTest) { - cfg := dt.findConfig(name) + cfg := dt.findConfig(t) if test.ParentDifficulty.Cmp(params.MinimumDifficulty) < 0 { t.Skip("difficulty below minimum") return } - if err := dt.checkFailure(t, name, test.Run(cfg)); err != nil { + if err := dt.checkFailure(t, test.Run(cfg)); err != nil { t.Error(err) } }) diff --git a/tests/init_test.go b/tests/init_test.go index 5af3e44bff..dc923dc75e 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -167,10 +167,9 @@ func (tm *testMatcher) findSkip(name string) (reason string, skipload bool) { } // findConfig returns the chain config matching defined patterns. -func (tm *testMatcher) findConfig(name string) *params.ChainConfig { - // TODO(fjl): name can be derived from testing.T when min Go version is 1.8 +func (tm *testMatcher) findConfig(t *testing.T) *params.ChainConfig { for _, m := range tm.configpat { - if m.p.MatchString(name) { + if m.p.MatchString(t.Name()) { return &m.config } } @@ -178,11 +177,10 @@ func (tm *testMatcher) findConfig(name string) *params.ChainConfig { } // checkFailure checks whether a failure is expected. -func (tm *testMatcher) checkFailure(t *testing.T, name string, err error) error { - // TODO(fjl): name can be derived from t when min Go version is 1.8 +func (tm *testMatcher) checkFailure(t *testing.T, err error) error { failReason := "" for _, m := range tm.failpat { - if m.p.MatchString(name) { + if m.p.MatchString(t.Name()) { failReason = m.reason break } diff --git a/tests/rlp_test.go b/tests/rlp_test.go index 1601625df5..79a1683eb2 100644 --- a/tests/rlp_test.go +++ b/tests/rlp_test.go @@ -24,7 +24,7 @@ func TestRLP(t *testing.T) { t.Parallel() tm := new(testMatcher) tm.walk(t, rlpTestDir, func(t *testing.T, name string, test *RLPTest) { - if err := tm.checkFailure(t, name, test.Run()); err != nil { + if err := tm.checkFailure(t, test.Run()); err != nil { t.Error(err) } }) diff --git a/tests/state_test.go b/tests/state_test.go index b77a898c21..43009afdd5 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -64,12 +64,11 @@ func TestState(t *testing.T) { for _, subtest := range test.Subtests() { subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) - name := name + "/" + key t.Run(key+"/trie", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { _, _, err := test.Run(subtest, vmconfig, false) - return st.checkFailure(t, name+"/trie", err) + return st.checkFailure(t, err) }) }) t.Run(key+"/snap", func(t *testing.T) { @@ -78,7 +77,7 @@ func TestState(t *testing.T) { if _, err := snaps.Journal(statedb.IntermediateRoot(false)); err != nil { return err } - return st.checkFailure(t, name+"/snap", err) + return st.checkFailure(t, err) }) }) } @@ -117,6 +116,6 @@ func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) { } else { t.Log("EVM operation log:\n" + buf.String()) } - //t.Logf("EVM output: 0x%x", tracer.Output()) - //t.Logf("EVM error: %v", tracer.Error()) + // t.Logf("EVM output: 0x%x", tracer.Output()) + // t.Logf("EVM error: %v", tracer.Error()) } diff --git a/tests/transaction_test.go b/tests/transaction_test.go index 0e3670d04b..cb0f262318 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -47,7 +47,7 @@ func TestTransaction(t *testing.T) { txt.skipLoad("^ttValue/TransactionWithHighValueOverflow.json") txt.walk(t, transactionTestDir, func(t *testing.T, name string, test *TransactionTest) { cfg := params.MainnetChainConfig - if err := txt.checkFailure(t, name, test.Run(cfg)); err != nil { + if err := txt.checkFailure(t, test.Run(cfg)); err != nil { t.Error(err) } }) diff --git a/tests/vm_test.go b/tests/vm_test.go index fb839827ac..2150df9e23 100644 --- a/tests/vm_test.go +++ b/tests/vm_test.go @@ -30,10 +30,10 @@ func TestVM(t *testing.T) { vmt.walk(t, vmTestDir, func(t *testing.T, name string, test *VMTest) { withTrace(t, test.json.Exec.GasLimit, func(vmconfig vm.Config) error { - return vmt.checkFailure(t, name+"/trie", test.Run(vmconfig, false)) + return vmt.checkFailure(t, test.Run(vmconfig, false)) }) withTrace(t, test.json.Exec.GasLimit, func(vmconfig vm.Config) error { - return vmt.checkFailure(t, name+"/snap", test.Run(vmconfig, true)) + return vmt.checkFailure(t, test.Run(vmconfig, true)) }) }) } From 49bde05a55260469abcbeb64fd3c7b85c7536b5c Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 25 May 2021 23:09:11 +0200 Subject: [PATCH 575/948] cmd/devp2p: refactor eth test suite (#22843) This PR refactors the eth test suite to make it more readable and easier to use. Some notable differences: - A new file helpers.go stores all of the methods used between both eth66 and eth65 and below tests, as well as methods shared among many test functions. - suite.go now contains all of the test functions for both eth65 tests and eth66 tests. - The utesting.T object doesn't get passed through to other helper methods, but is instead only used within the scope of the test function, whereas helper methods return errors, so only the test function itself can fatal out in the case of an error. - The full test suite now only takes 13.5 seconds to run. --- cmd/devp2p/internal/ethtest/chain.go | 20 +- cmd/devp2p/internal/ethtest/eth66_suite.go | 521 ----------- .../internal/ethtest/eth66_suiteHelpers.go | 333 ------- cmd/devp2p/internal/ethtest/helpers.go | 635 +++++++++++++ cmd/devp2p/internal/ethtest/suite.go | 844 +++++++++++------- cmd/devp2p/internal/ethtest/transaction.go | 298 +++++-- cmd/devp2p/internal/ethtest/types.go | 238 ++--- 7 files changed, 1467 insertions(+), 1422 deletions(-) delete mode 100644 cmd/devp2p/internal/ethtest/eth66_suite.go delete mode 100644 cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go create mode 100644 cmd/devp2p/internal/ethtest/helpers.go diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 83c55181ad..34a20c515b 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -54,10 +54,24 @@ func (c *Chain) Len() int { return len(c.blocks) } -// TD calculates the total difficulty of the chain. -func (c *Chain) TD(height int) *big.Int { // TODO later on channge scheme so that the height is included in range +// TD calculates the total difficulty of the chain at the +// chain head. +func (c *Chain) TD() *big.Int { sum := big.NewInt(0) - for _, block := range c.blocks[:height] { + for _, block := range c.blocks[:c.Len()] { + sum.Add(sum, block.Difficulty()) + } + return sum +} + +// TotalDifficultyAt calculates the total difficulty of the chain +// at the given block height. +func (c *Chain) TotalDifficultyAt(height int) *big.Int { + sum := big.NewInt(0) + if height >= c.Len() { + return sum + } + for _, block := range c.blocks[:height+1] { sum.Add(sum, block.Difficulty()) } return sum diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go deleted file mode 100644 index 903a90c7eb..0000000000 --- a/cmd/devp2p/internal/ethtest/eth66_suite.go +++ /dev/null @@ -1,521 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package ethtest - -import ( - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/p2p" -) - -// Is_66 checks if the node supports the eth66 protocol version, -// and if not, exists the test suite -func (s *Suite) Is_66(t *utesting.T) { - conn := s.dial66(t) - conn.handshake(t) - if conn.negotiatedProtoVersion < 66 { - t.Fail() - } -} - -// TestStatus_66 attempts to connect to the given node and exchange -// a status message with it on the eth66 protocol, and then check to -// make sure the chain head is correct. -func (s *Suite) TestStatus_66(t *utesting.T) { - conn := s.dial66(t) - defer conn.Close() - // get protoHandshake - conn.handshake(t) - // get status - switch msg := conn.statusExchange66(t, s.chain).(type) { - case *Status: - status := *msg - if status.ProtocolVersion != uint32(66) { - t.Fatalf("mismatch in version: wanted 66, got %d", status.ProtocolVersion) - } - t.Logf("got status message: %s", pretty.Sdump(msg)) - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) - } -} - -// TestGetBlockHeaders_66 tests whether the given node can respond to -// an eth66 `GetBlockHeaders` request and that the response is accurate. -func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { - conn := s.setupConnection66(t) - defer conn.Close() - // get block headers - req := ð.GetBlockHeadersPacket66{ - RequestId: 3, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Hash: s.chain.blocks[1].Hash(), - }, - Amount: 2, - Skip: 1, - Reverse: false, - }, - } - // write message - headers, err := s.getBlockHeaders66(conn, req, req.RequestId) - if err != nil { - t.Fatalf("could not get block headers: %v", err) - } - // check for correct headers - if !headersMatch(t, s.chain, headers) { - t.Fatal("received wrong header(s)") - } -} - -// TestSimultaneousRequests_66 sends two simultaneous `GetBlockHeader` requests -// with different request IDs and checks to make sure the node responds with the correct -// headers per request. -func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { - // create two connections - conn := s.setupConnection66(t) - defer conn.Close() - // create two requests - req1 := ð.GetBlockHeadersPacket66{ - RequestId: 111, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Hash: s.chain.blocks[1].Hash(), - }, - Amount: 2, - Skip: 1, - Reverse: false, - }, - } - req2 := ð.GetBlockHeadersPacket66{ - RequestId: 222, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Hash: s.chain.blocks[1].Hash(), - }, - Amount: 4, - Skip: 1, - Reverse: false, - }, - } - // write first request - if err := conn.write66(req1, GetBlockHeaders{}.Code()); err != nil { - t.Fatalf("failed to write to connection: %v", err) - } - // write second request - if err := conn.write66(req2, GetBlockHeaders{}.Code()); err != nil { - t.Fatalf("failed to write to connection: %v", err) - } - // wait for responses - headers1, err := s.waitForBlockHeadersResponse66(conn, req1.RequestId) - if err != nil { - t.Fatalf("error while waiting for block headers: %v", err) - } - headers2, err := s.waitForBlockHeadersResponse66(conn, req2.RequestId) - if err != nil { - t.Fatalf("error while waiting for block headers: %v", err) - } - // check headers of both responses - if !headersMatch(t, s.chain, headers1) { - t.Fatalf("wrong header(s) in response to req1: got %v", headers1) - } - if !headersMatch(t, s.chain, headers2) { - t.Fatalf("wrong header(s) in response to req2: got %v", headers2) - } -} - -// TestBroadcast_66 tests whether a block announcement is correctly -// propagated to the given node's peer(s) on the eth66 protocol. -func (s *Suite) TestBroadcast_66(t *utesting.T) { - s.sendNextBlock66(t) -} - -// TestGetBlockBodies_66 tests whether the given node can respond to -// a `GetBlockBodies` request and that the response is accurate over -// the eth66 protocol. -func (s *Suite) TestGetBlockBodies_66(t *utesting.T) { - conn := s.setupConnection66(t) - defer conn.Close() - // create block bodies request - id := uint64(55) - req := ð.GetBlockBodiesPacket66{ - RequestId: id, - GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ - s.chain.blocks[54].Hash(), - s.chain.blocks[75].Hash(), - }, - } - if err := conn.write66(req, GetBlockBodies{}.Code()); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - - reqID, msg := conn.readAndServe66(s.chain, timeout) - switch msg := msg.(type) { - case BlockBodies: - if reqID != req.RequestId { - t.Fatalf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) - } - t.Logf("received %d block bodies", len(msg)) - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) - } -} - -// TestLargeAnnounce_66 tests the announcement mechanism with a large block. -func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { - nextBlock := len(s.chain.blocks) - blocks := []*NewBlock{ - { - Block: largeBlock(), - TD: s.fullChain.TD(nextBlock + 1), - }, - { - Block: s.fullChain.blocks[nextBlock], - TD: largeNumber(2), - }, - { - Block: largeBlock(), - TD: largeNumber(2), - }, - { - Block: s.fullChain.blocks[nextBlock], - TD: s.fullChain.TD(nextBlock + 1), - }, - } - - for i, blockAnnouncement := range blocks[0:3] { - t.Logf("Testing malicious announcement: %v\n", i) - sendConn := s.setupConnection66(t) - if err := sendConn.Write(blockAnnouncement); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // Invalid announcement, check that peer disconnected - switch msg := sendConn.ReadAndServe(s.chain, time.Second*8).(type) { - case *Disconnect: - case *Error: - break - default: - t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) - } - sendConn.Close() - } - // Test the last block as a valid block - s.sendNextBlock66(t) -} - -func (s *Suite) TestOldAnnounce_66(t *utesting.T) { - sendConn, recvConn := s.setupConnection66(t), s.setupConnection66(t) - defer sendConn.Close() - defer recvConn.Close() - - s.oldAnnounce(t, sendConn, recvConn) -} - -// TestMaliciousHandshake_66 tries to send malicious data during the handshake. -func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { - conn := s.dial66(t) - defer conn.Close() - // write hello to client - pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] - handshakes := []*Hello{ - { - Version: 5, - Caps: []p2p.Cap{ - {Name: largeString(2), Version: 66}, - }, - ID: pub0, - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - {Name: "eth", Version: 66}, - }, - ID: append(pub0, byte(0)), - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - {Name: "eth", Version: 66}, - }, - ID: append(pub0, pub0...), - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - {Name: "eth", Version: 66}, - }, - ID: largeBuffer(2), - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: largeString(2), Version: 66}, - }, - ID: largeBuffer(2), - }, - } - for i, handshake := range handshakes { - t.Logf("Testing malicious handshake %v\n", i) - // Init the handshake - if err := conn.Write(handshake); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // check that the peer disconnected - timeout := 20 * time.Second - // Discard one hello - for i := 0; i < 2; i++ { - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { - case *Disconnect: - case *Error: - case *Hello: - // Hello's are sent concurrently, so ignore them - continue - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) - } - } - // Dial for the next round - conn = s.dial66(t) - } -} - -// TestMaliciousStatus_66 sends a status package with a large total difficulty. -func (s *Suite) TestMaliciousStatus_66(t *utesting.T) { - conn := s.dial66(t) - defer conn.Close() - // get protoHandshake - conn.handshake(t) - status := &Status{ - ProtocolVersion: uint32(66), - NetworkID: s.chain.chainConfig.ChainID.Uint64(), - TD: largeNumber(2), - Head: s.chain.blocks[s.chain.Len()-1].Hash(), - Genesis: s.chain.blocks[0].Hash(), - ForkID: s.chain.ForkID(), - } - // get status - switch msg := conn.statusExchange(t, s.chain, status).(type) { - case *Status: - t.Logf("%+v\n", msg) - default: - t.Fatalf("expected status, got: %#v ", msg) - } - // wait for disconnect - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { - case *Disconnect: - case *Error: - return - default: - t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) - } -} - -func (s *Suite) TestTransaction_66(t *utesting.T) { - tests := []*types.Transaction{ - getNextTxFromChain(t, s), - unknownTx(t, s), - } - for i, tx := range tests { - t.Logf("Testing tx propagation: %v\n", i) - sendSuccessfulTx66(t, s, tx) - } -} - -func (s *Suite) TestMaliciousTx_66(t *utesting.T) { - badTxs := []*types.Transaction{ - getOldTxFromChain(t, s), - invalidNonceTx(t, s), - hugeAmount(t, s), - hugeGasPrice(t, s), - hugeData(t, s), - } - sendConn := s.setupConnection66(t) - defer sendConn.Close() - // set up receiving connection before sending txs to make sure - // no announcements are missed - recvConn := s.setupConnection66(t) - defer recvConn.Close() - - for i, tx := range badTxs { - t.Logf("Testing malicious tx propagation: %v\n", i) - if err := sendConn.Write(&Transactions{tx}); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - - } - // check to make sure bad txs aren't propagated - waitForTxPropagation(t, s, badTxs, recvConn) -} - -// TestZeroRequestID_66 checks that a request ID of zero is still handled -// by the node. -func (s *Suite) TestZeroRequestID_66(t *utesting.T) { - conn := s.setupConnection66(t) - defer conn.Close() - - req := ð.GetBlockHeadersPacket66{ - RequestId: 0, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Number: 0, - }, - Amount: 2, - }, - } - headers, err := s.getBlockHeaders66(conn, req, req.RequestId) - if err != nil { - t.Fatalf("could not get block headers: %v", err) - } - if !headersMatch(t, s.chain, headers) { - t.Fatal("received wrong header(s)") - } -} - -// TestSameRequestID_66 sends two requests with the same request ID -// concurrently to a single node. -func (s *Suite) TestSameRequestID_66(t *utesting.T) { - conn := s.setupConnection66(t) - // create two requests with the same request ID - reqID := uint64(1234) - request1 := ð.GetBlockHeadersPacket66{ - RequestId: reqID, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Number: 1, - }, - Amount: 2, - }, - } - request2 := ð.GetBlockHeadersPacket66{ - RequestId: reqID, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Number: 33, - }, - Amount: 2, - }, - } - // write the first request - err := conn.write66(request1, GetBlockHeaders{}.Code()) - if err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // perform second request - headers2, err := s.getBlockHeaders66(conn, request2, reqID) - if err != nil { - t.Fatalf("could not get block headers: %v", err) - return - } - // wait for response to first request - headers1, err := s.waitForBlockHeadersResponse66(conn, reqID) - if err != nil { - t.Fatalf("could not get BlockHeaders response: %v", err) - } - // check if headers match - if !headersMatch(t, s.chain, headers1) || !headersMatch(t, s.chain, headers2) { - t.Fatal("received wrong header(s)") - } -} - -// TestLargeTxRequest_66 tests whether a node can fulfill a large GetPooledTransactions -// request. -func (s *Suite) TestLargeTxRequest_66(t *utesting.T) { - // send the next block to ensure the node is no longer syncing and is able to accept - // txs - s.sendNextBlock66(t) - // send 2000 transactions to the node - hashMap, txs := generateTxs(t, s, 2000) - sendConn := s.setupConnection66(t) - defer sendConn.Close() - - sendMultipleSuccessfulTxs(t, s, sendConn, txs) - // set up connection to receive to ensure node is peered with the receiving connection - // before tx request is sent - recvConn := s.setupConnection66(t) - defer recvConn.Close() - // create and send pooled tx request - hashes := make([]common.Hash, 0) - for _, hash := range hashMap { - hashes = append(hashes, hash) - } - getTxReq := ð.GetPooledTransactionsPacket66{ - RequestId: 1234, - GetPooledTransactionsPacket: hashes, - } - if err := recvConn.write66(getTxReq, GetPooledTransactions{}.Code()); err != nil { - t.Fatalf("could not write to conn: %v", err) - } - // check that all received transactions match those that were sent to node - switch msg := recvConn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) { - case PooledTransactions: - for _, gotTx := range msg { - if _, exists := hashMap[gotTx.Hash()]; !exists { - t.Fatalf("unexpected tx received: %v", gotTx.Hash()) - } - } - default: - t.Fatalf("unexpected %s", pretty.Sdump(msg)) - } -} - -// TestNewPooledTxs_66 tests whether a node will do a GetPooledTransactions -// request upon receiving a NewPooledTransactionHashes announcement. -func (s *Suite) TestNewPooledTxs_66(t *utesting.T) { - // send the next block to ensure the node is no longer syncing and is able to accept - // txs - s.sendNextBlock66(t) - // generate 50 txs - hashMap, _ := generateTxs(t, s, 50) - // create new pooled tx hashes announcement - hashes := make([]common.Hash, 0) - for _, hash := range hashMap { - hashes = append(hashes, hash) - } - announce := NewPooledTransactionHashes(hashes) - // send announcement - conn := s.setupConnection66(t) - defer conn.Close() - if err := conn.Write(announce); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // wait for GetPooledTxs request - for { - _, msg := conn.readAndServe66(s.chain, timeout) - switch msg := msg.(type) { - case GetPooledTransactions: - if len(msg) != len(hashes) { - t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg)) - } - return - case *NewPooledTransactionHashes, *NewBlock, *NewBlockHashes: - // ignore propagated txs and blocks from old tests - continue - default: - t.Fatalf("unexpected %s", pretty.Sdump(msg)) - } - } -} diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go deleted file mode 100644 index 3c5b22f0b5..0000000000 --- a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package ethtest - -import ( - "fmt" - "reflect" - "time" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" - "github.com/stretchr/testify/assert" -) - -func (c *Conn) statusExchange66(t *utesting.T, chain *Chain) Message { - status := &Status{ - ProtocolVersion: uint32(66), - NetworkID: chain.chainConfig.ChainID.Uint64(), - TD: chain.TD(chain.Len()), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), - } - return c.statusExchange(t, chain, status) -} - -func (s *Suite) dial66(t *utesting.T) *Conn { - conn, err := s.dial() - if err != nil { - t.Fatalf("could not dial: %v", err) - } - conn.caps = append(conn.caps, p2p.Cap{Name: "eth", Version: 66}) - conn.ourHighestProtoVersion = 66 - return conn -} - -func (c *Conn) write66(req eth.Packet, code int) error { - payload, err := rlp.EncodeToBytes(req) - if err != nil { - return err - } - _, err = c.Conn.Write(uint64(code), payload) - return err -} - -func (c *Conn) read66() (uint64, Message) { - code, rawData, _, err := c.Conn.Read() - if err != nil { - return 0, errorf("could not read from connection: %v", err) - } - - var msg Message - - switch int(code) { - case (Hello{}).Code(): - msg = new(Hello) - - case (Ping{}).Code(): - msg = new(Ping) - case (Pong{}).Code(): - msg = new(Pong) - case (Disconnect{}).Code(): - msg = new(Disconnect) - case (Status{}).Code(): - msg = new(Status) - case (GetBlockHeaders{}).Code(): - ethMsg := new(eth.GetBlockHeadersPacket66) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket) - case (BlockHeaders{}).Code(): - ethMsg := new(eth.BlockHeadersPacket66) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket) - case (GetBlockBodies{}).Code(): - ethMsg := new(eth.GetBlockBodiesPacket66) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket) - case (BlockBodies{}).Code(): - ethMsg := new(eth.BlockBodiesPacket66) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket) - case (NewBlock{}).Code(): - msg = new(NewBlock) - case (NewBlockHashes{}).Code(): - msg = new(NewBlockHashes) - case (Transactions{}).Code(): - msg = new(Transactions) - case (NewPooledTransactionHashes{}).Code(): - msg = new(NewPooledTransactionHashes) - case (GetPooledTransactions{}.Code()): - ethMsg := new(eth.GetPooledTransactionsPacket66) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return ethMsg.RequestId, GetPooledTransactions(ethMsg.GetPooledTransactionsPacket) - case (PooledTransactions{}.Code()): - ethMsg := new(eth.PooledTransactionsPacket66) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return ethMsg.RequestId, PooledTransactions(ethMsg.PooledTransactionsPacket) - default: - msg = errorf("invalid message code: %d", code) - } - - if msg != nil { - if err := rlp.DecodeBytes(rawData, msg); err != nil { - return 0, errorf("could not rlp decode message: %v", err) - } - return 0, msg - } - return 0, errorf("invalid message: %s", string(rawData)) -} - -func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { - for { - id, msg := c.readAndServe66(chain, timeout) - if id == requestID { - return msg - } - } -} - -// ReadAndServe serves GetBlockHeaders requests while waiting -// on another message from the node. -func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { - start := time.Now() - for time.Since(start) < timeout { - c.SetReadDeadline(time.Now().Add(10 * time.Second)) - - reqID, msg := c.read66() - - switch msg := msg.(type) { - case *Ping: - c.Write(&Pong{}) - case *GetBlockHeaders: - headers, err := chain.GetHeaders(*msg) - if err != nil { - return 0, errorf("could not get headers for inbound header request: %v", err) - } - resp := ð.BlockHeadersPacket66{ - RequestId: reqID, - BlockHeadersPacket: eth.BlockHeadersPacket(headers), - } - if err := c.write66(resp, BlockHeaders{}.Code()); err != nil { - return 0, errorf("could not write to connection: %v", err) - } - default: - return reqID, msg - } - } - return 0, errorf("no message received within %v", timeout) -} - -func (s *Suite) setupConnection66(t *utesting.T) *Conn { - // create conn - sendConn := s.dial66(t) - sendConn.handshake(t) - sendConn.statusExchange66(t, s.chain) - return sendConn -} - -func (s *Suite) testAnnounce66(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { - // Announce the block. - if err := sendConn.Write(blockAnnouncement); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - s.waitAnnounce66(t, receiveConn, blockAnnouncement) -} - -func (s *Suite) waitAnnounce66(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { - for { - _, msg := conn.readAndServe66(s.chain, timeout) - switch msg := msg.(type) { - case *NewBlock: - t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) - assert.Equal(t, - blockAnnouncement.Block.Header(), msg.Block.Header(), - "wrong block header in announcement", - ) - assert.Equal(t, - blockAnnouncement.TD, msg.TD, - "wrong TD in announcement", - ) - return - case *NewBlockHashes: - blockHashes := *msg - t.Logf("received NewBlockHashes message: %s", pretty.Sdump(blockHashes)) - assert.Equal(t, blockAnnouncement.Block.Hash(), blockHashes[0].Hash, - "wrong block hash in announcement", - ) - return - case *NewPooledTransactionHashes: - // ignore old txs being propagated - continue - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) - } - } -} - -// waitForBlock66 waits for confirmation from the client that it has -// imported the given block. -func (c *Conn) waitForBlock66(block *types.Block) error { - defer c.SetReadDeadline(time.Time{}) - - c.SetReadDeadline(time.Now().Add(20 * time.Second)) - // note: if the node has not yet imported the block, it will respond - // to the GetBlockHeaders request with an empty BlockHeaders response, - // so the GetBlockHeaders request must be sent again until the BlockHeaders - // response contains the desired header. - for { - req := eth.GetBlockHeadersPacket66{ - RequestId: 54, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{ - Hash: block.Hash(), - }, - Amount: 1, - }, - } - if err := c.write66(req, GetBlockHeaders{}.Code()); err != nil { - return err - } - - reqID, msg := c.read66() - // check message - switch msg := msg.(type) { - case BlockHeaders: - // check request ID - if reqID != req.RequestId { - return fmt.Errorf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) - } - for _, header := range msg { - if header.Number.Uint64() == block.NumberU64() { - return nil - } - } - time.Sleep(100 * time.Millisecond) - case *NewPooledTransactionHashes: - // ignore old announcements - continue - default: - return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) - } - } -} - -func sendSuccessfulTx66(t *utesting.T, s *Suite, tx *types.Transaction) { - sendConn := s.setupConnection66(t) - defer sendConn.Close() - sendSuccessfulTxWithConn(t, s, tx, sendConn) -} - -// waitForBlockHeadersResponse66 waits for a BlockHeaders message with the given expected request ID -func (s *Suite) waitForBlockHeadersResponse66(conn *Conn, expectedID uint64) (BlockHeaders, error) { - reqID, msg := conn.readAndServe66(s.chain, timeout) - switch msg := msg.(type) { - case BlockHeaders: - if reqID != expectedID { - return nil, fmt.Errorf("request ID mismatch: wanted %d, got %d", expectedID, reqID) - } - return msg, nil - default: - return nil, fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) - } -} - -func (s *Suite) getBlockHeaders66(conn *Conn, req eth.Packet, expectedID uint64) (BlockHeaders, error) { - if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { - return nil, fmt.Errorf("could not write to connection: %v", err) - } - return s.waitForBlockHeadersResponse66(conn, expectedID) -} - -func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) bool { - mismatched := 0 - for _, header := range headers { - num := header.Number.Uint64() - t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) - if !reflect.DeepEqual(chain.blocks[int(num)].Header(), header) { - mismatched += 1 - t.Logf("received wrong header: %v", pretty.Sdump(header)) - } - } - return mismatched == 0 -} - -func (s *Suite) sendNextBlock66(t *utesting.T) { - sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) - defer sendConn.Close() - defer receiveConn.Close() - - // create new block announcement - nextBlock := len(s.chain.blocks) - blockAnnouncement := &NewBlock{ - Block: s.fullChain.blocks[nextBlock], - TD: s.fullChain.TD(nextBlock + 1), - } - // send announcement and wait for node to request the header - s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) - // wait for client to update its chain - if err := receiveConn.waitForBlock66(s.fullChain.blocks[nextBlock]); err != nil { - t.Fatal(err) - } - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) -} diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go new file mode 100644 index 0000000000..d99376124d --- /dev/null +++ b/cmd/devp2p/internal/ethtest/helpers.go @@ -0,0 +1,635 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "fmt" + "net" + "reflect" + "strings" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/rlpx" +) + +var ( + pretty = spew.ConfigState{ + Indent: " ", + DisableCapacities: true, + DisablePointerAddresses: true, + SortKeys: true, + } + timeout = 20 * time.Second +) + +// Is_66 checks if the node supports the eth66 protocol version, +// and if not, exists the test suite +func (s *Suite) Is_66(t *utesting.T) { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + if err := conn.handshake(); err != nil { + t.Fatalf("handshake failed: %v", err) + } + if conn.negotiatedProtoVersion < 66 { + t.Fail() + } +} + +// dial attempts to dial the given node and perform a handshake, +// returning the created Conn if successful. +func (s *Suite) dial() (*Conn, error) { + // dial + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) + if err != nil { + return nil, err + } + conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} + // do encHandshake + conn.ourKey, _ = crypto.GenerateKey() + _, err = conn.Handshake(conn.ourKey) + if err != nil { + conn.Close() + return nil, err + } + // set default p2p capabilities + conn.caps = []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + } + conn.ourHighestProtoVersion = 65 + return &conn, nil +} + +// dial66 attempts to dial the given node and perform a handshake, +// returning the created Conn with additional eth66 capabilities if +// successful +func (s *Suite) dial66() (*Conn, error) { + conn, err := s.dial() + if err != nil { + return nil, fmt.Errorf("dial failed: %v", err) + } + conn.caps = append(conn.caps, p2p.Cap{Name: "eth", Version: 66}) + conn.ourHighestProtoVersion = 66 + return conn, nil +} + +// peer performs both the protocol handshake and the status message +// exchange with the node in order to peer with it. +func (c *Conn) peer(chain *Chain, status *Status) error { + if err := c.handshake(); err != nil { + return fmt.Errorf("handshake failed: %v", err) + } + if _, err := c.statusExchange(chain, status); err != nil { + return fmt.Errorf("status exchange failed: %v", err) + } + return nil +} + +// handshake performs a protocol handshake with the node. +func (c *Conn) handshake() error { + defer c.SetDeadline(time.Time{}) + c.SetDeadline(time.Now().Add(10 * time.Second)) + // write hello to client + pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &Hello{ + Version: 5, + Caps: c.caps, + ID: pub0, + } + if err := c.Write(ourHandshake); err != nil { + return fmt.Errorf("write to connection failed: %v", err) + } + // read hello from client + switch msg := c.Read().(type) { + case *Hello: + // set snappy if version is at least 5 + if msg.Version >= 5 { + c.SetSnappy(true) + } + c.negotiateEthProtocol(msg.Caps) + if c.negotiatedProtoVersion == 0 { + return fmt.Errorf("unexpected eth protocol version") + } + return nil + default: + return fmt.Errorf("bad handshake: %#v", msg) + } +} + +// negotiateEthProtocol sets the Conn's eth protocol version to highest +// advertised capability from peer. +func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { + var highestEthVersion uint + for _, capability := range caps { + if capability.Name != "eth" { + continue + } + if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { + highestEthVersion = capability.Version + } + } + c.negotiatedProtoVersion = highestEthVersion +} + +// statusExchange performs a `Status` message exchange with the given node. +func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) { + defer c.SetDeadline(time.Time{}) + c.SetDeadline(time.Now().Add(20 * time.Second)) + + // read status message from client + var message Message +loop: + for { + switch msg := c.Read().(type) { + case *Status: + if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { + return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", + want, chain.blocks[chain.Len()-1].NumberU64(), have) + } + if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { + return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want) + } + if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { + return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) + } + if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { + return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want) + } + message = msg + break loop + case *Disconnect: + return nil, fmt.Errorf("disconnect received: %v", msg.Reason) + case *Ping: + c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error + // (PINGs should not be a response upon fresh connection) + default: + return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg)) + } + } + // make sure eth protocol version is set for negotiation + if c.negotiatedProtoVersion == 0 { + return nil, fmt.Errorf("eth protocol version must be set in Conn") + } + if status == nil { + // default status message + status = &Status{ + ProtocolVersion: uint32(c.negotiatedProtoVersion), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + } + if err := c.Write(status); err != nil { + return nil, fmt.Errorf("write to connection failed: %v", err) + } + return message, nil +} + +// createSendAndRecvConns creates two connections, one for sending messages to the +// node, and one for receiving messages from the node. +func (s *Suite) createSendAndRecvConns(isEth66 bool) (*Conn, *Conn, error) { + var ( + sendConn *Conn + recvConn *Conn + err error + ) + if isEth66 { + sendConn, err = s.dial66() + if err != nil { + return nil, nil, fmt.Errorf("dial failed: %v", err) + } + recvConn, err = s.dial66() + if err != nil { + sendConn.Close() + return nil, nil, fmt.Errorf("dial failed: %v", err) + } + } else { + sendConn, err = s.dial() + if err != nil { + return nil, nil, fmt.Errorf("dial failed: %v", err) + } + recvConn, err = s.dial() + if err != nil { + sendConn.Close() + return nil, nil, fmt.Errorf("dial failed: %v", err) + } + } + return sendConn, recvConn, nil +} + +// readAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message { + start := time.Now() + for time.Since(start) < timeout { + c.SetReadDeadline(time.Now().Add(5 * time.Second)) + switch msg := c.Read().(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + req := *msg + headers, err := chain.GetHeaders(req) + if err != nil { + return errorf("could not get headers for inbound header request: %v", err) + } + if err := c.Write(headers); err != nil { + return errorf("could not write to connection: %v", err) + } + default: + return msg + } + } + return errorf("no message received within %v", timeout) +} + +// readAndServe66 serves eth66 GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { + start := time.Now() + for time.Since(start) < timeout { + c.SetReadDeadline(time.Now().Add(10 * time.Second)) + + reqID, msg := c.Read66() + + switch msg := msg.(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + headers, err := chain.GetHeaders(*msg) + if err != nil { + return 0, errorf("could not get headers for inbound header request: %v", err) + } + resp := ð.BlockHeadersPacket66{ + RequestId: reqID, + BlockHeadersPacket: eth.BlockHeadersPacket(headers), + } + if err := c.Write66(resp, BlockHeaders{}.Code()); err != nil { + return 0, errorf("could not write to connection: %v", err) + } + default: + return reqID, msg + } + } + return 0, errorf("no message received within %v", timeout) +} + +// headersRequest executes the given `GetBlockHeaders` request. +func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, isEth66 bool, reqID uint64) (BlockHeaders, error) { + defer c.SetReadDeadline(time.Time{}) + c.SetReadDeadline(time.Now().Add(20 * time.Second)) + // if on eth66 connection, perform eth66 GetBlockHeaders request + if isEth66 { + return getBlockHeaders66(chain, c, request, reqID) + } + if err := c.Write(request); err != nil { + return nil, err + } + switch msg := c.readAndServe(chain, timeout).(type) { + case *BlockHeaders: + return *msg, nil + default: + return nil, fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) + } +} + +// getBlockHeaders66 executes the given `GetBlockHeaders` request over the eth66 protocol. +func getBlockHeaders66(chain *Chain, conn *Conn, request *GetBlockHeaders, id uint64) (BlockHeaders, error) { + // write request + packet := eth.GetBlockHeadersPacket(*request) + req := ð.GetBlockHeadersPacket66{ + RequestId: id, + GetBlockHeadersPacket: &packet, + } + if err := conn.Write66(req, GetBlockHeaders{}.Code()); err != nil { + return nil, fmt.Errorf("could not write to connection: %v", err) + } + // wait for response + msg := conn.waitForResponse(chain, timeout, req.RequestId) + headers, ok := msg.(BlockHeaders) + if !ok { + return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg)) + } + return headers, nil +} + +// headersMatch returns whether the received headers match the given request +func headersMatch(expected BlockHeaders, headers BlockHeaders) bool { + return reflect.DeepEqual(expected, headers) +} + +// waitForResponse reads from the connection until a response with the expected +// request ID is received. +func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { + for { + id, msg := c.readAndServe66(chain, timeout) + if id == requestID { + return msg + } + } +} + +// sendNextBlock broadcasts the next block in the chain and waits +// for the node to propagate the block and import it into its chain. +func (s *Suite) sendNextBlock(isEth66 bool) error { + // set up sending and receiving connections + sendConn, recvConn, err := s.createSendAndRecvConns(isEth66) + if err != nil { + return err + } + defer sendConn.Close() + defer recvConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + // create new block announcement + nextBlock := s.fullChain.blocks[s.chain.Len()] + blockAnnouncement := &NewBlock{ + Block: nextBlock, + TD: s.fullChain.TotalDifficultyAt(s.chain.Len()), + } + // send announcement and wait for node to request the header + if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil { + return fmt.Errorf("failed to announce block: %v", err) + } + // wait for client to update its chain + if err = s.waitForBlockImport(recvConn, nextBlock, isEth66); err != nil { + return fmt.Errorf("failed to receive confirmation of block import: %v", err) + } + // update test suite chain + s.chain.blocks = append(s.chain.blocks, nextBlock) + return nil +} + +// testAnnounce writes a block announcement to the node and waits for the node +// to propagate it. +func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error { + if err := sendConn.Write(blockAnnouncement); err != nil { + return fmt.Errorf("could not write to connection: %v", err) + } + return s.waitAnnounce(receiveConn, blockAnnouncement) +} + +// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node. +func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error { + for { + switch msg := conn.readAndServe(s.chain, timeout).(type) { + case *NewBlock: + if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) { + return fmt.Errorf("wrong header in block announcement: \nexpected %v "+ + "\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header()) + } + if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) { + return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD) + } + return nil + case *NewBlockHashes: + hashes := *msg + if blockAnnouncement.Block.Hash() != hashes[0].Hash { + return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash) + } + return nil + case *NewPooledTransactionHashes: + // ignore tx announcements from previous tests + continue + default: + return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) + } + } +} + +func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block, isEth66 bool) error { + defer conn.SetReadDeadline(time.Time{}) + conn.SetReadDeadline(time.Now().Add(20 * time.Second)) + // create request + req := &GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Hash: block.Hash(), + }, + Amount: 1, + } + // loop until BlockHeaders response contains desired block, confirming the + // node imported the block + for { + var ( + headers BlockHeaders + err error + ) + if isEth66 { + requestID := uint64(54) + headers, err = conn.headersRequest(req, s.chain, eth66, requestID) + } else { + headers, err = conn.headersRequest(req, s.chain, eth65, 0) + } + if err != nil { + return fmt.Errorf("GetBlockHeader request failed: %v", err) + } + // if headers response is empty, node hasn't imported block yet, try again + if len(headers) == 0 { + time.Sleep(100 * time.Millisecond) + continue + } + if !reflect.DeepEqual(block.Header(), headers[0]) { + return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0]) + } + return nil + } +} + +func (s *Suite) oldAnnounce(isEth66 bool) error { + sendConn, receiveConn, err := s.createSendAndRecvConns(isEth66) + if err != nil { + return err + } + defer sendConn.Close() + defer receiveConn.Close() + if err := sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + if err := receiveConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + // create old block announcement + oldBlockAnnounce := &NewBlock{ + Block: s.chain.blocks[len(s.chain.blocks)/2], + TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), + } + if err := sendConn.Write(oldBlockAnnounce); err != nil { + return fmt.Errorf("could not write to connection: %v", err) + } + // wait to see if the announcement is propagated + switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) { + case *NewBlock: + block := *msg + if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { + return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg)) + } + case *NewBlockHashes: + hashes := *msg + for _, hash := range hashes { + if hash.Hash == oldBlockAnnounce.Block.Hash() { + return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg)) + } + } + case *Error: + errMsg := *msg + // check to make sure error is timeout (propagation didn't come through == test successful) + if !strings.Contains(errMsg.String(), "timeout") { + return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg)) + } + default: + return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) + } + return nil +} + +func (s *Suite) maliciousHandshakes(t *utesting.T, isEth66 bool) error { + var ( + conn *Conn + err error + ) + if isEth66 { + conn, err = s.dial66() + if err != nil { + return fmt.Errorf("dial failed: %v", err) + } + } else { + conn, err = s.dial() + if err != nil { + return fmt.Errorf("dial failed: %v", err) + } + } + defer conn.Close() + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + t.Logf("Testing malicious handshake %v\n", i) + if err := conn.Write(handshake); err != nil { + return fmt.Errorf("could not write to connection: %v", err) + } + // check that the peer disconnected + for i := 0; i < 2; i++ { + switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Discard one hello as Hello's are sent concurrently + continue + default: + return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) + } + } + // dial for the next round + if isEth66 { + conn, err = s.dial66() + if err != nil { + return fmt.Errorf("dial failed: %v", err) + } + } else { + conn, err = s.dial() + if err != nil { + return fmt.Errorf("dial failed: %v", err) + } + } + } + return nil +} + +func (s *Suite) maliciousStatus(conn *Conn) error { + if err := conn.handshake(); err != nil { + return fmt.Errorf("handshake failed: %v", err) + } + status := &Status{ + ProtocolVersion: uint32(conn.negotiatedProtoVersion), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + msg, err := conn.statusExchange(s.chain, status) + if err != nil { + return fmt.Errorf("status exchange failed: %v", err) + } + switch msg := msg.(type) { + case *Status: + default: + return fmt.Errorf("expected status, got: %#v ", msg) + } + // wait for disconnect + switch msg := conn.readAndServe(s.chain, timeout).(type) { + case *Disconnect: + return nil + case *Error: + return nil + default: + return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index abc6bcddce..ad832dddd2 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -17,33 +17,16 @@ package ethtest import ( - "fmt" - "net" - "strings" "time" - "github.com/davecgh/go-spew/spew" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/rlpx" - "github.com/stretchr/testify/assert" ) -var pretty = spew.ConfigState{ - Indent: " ", - DisableCapacities: true, - DisablePointerAddresses: true, - SortKeys: true, -} - -var timeout = 20 * time.Second - -// Suite represents a structure used to test the eth -// protocol of a node(s). +// Suite represents a structure used to test a node's conformance +// to the eth protocol. type Suite struct { Dest *enode.Node @@ -70,35 +53,35 @@ func (s *Suite) AllEthTests() []utesting.Test { return []utesting.Test{ // status {Name: "TestStatus", Fn: s.TestStatus}, - {Name: "TestStatus_66", Fn: s.TestStatus_66}, + {Name: "TestStatus66", Fn: s.TestStatus66}, // get block headers {Name: "TestGetBlockHeaders", Fn: s.TestGetBlockHeaders}, - {Name: "TestGetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, - {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, - {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, - {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, + {Name: "TestGetBlockHeaders66", Fn: s.TestGetBlockHeaders66}, + {Name: "TestSimultaneousRequests66", Fn: s.TestSimultaneousRequests66}, + {Name: "TestSameRequestID66", Fn: s.TestSameRequestID66}, + {Name: "TestZeroRequestID66", Fn: s.TestZeroRequestID66}, // get block bodies {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, - {Name: "TestGetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + {Name: "TestGetBlockBodies66", Fn: s.TestGetBlockBodies66}, // broadcast {Name: "TestBroadcast", Fn: s.TestBroadcast}, - {Name: "TestBroadcast_66", Fn: s.TestBroadcast_66}, + {Name: "TestBroadcast66", Fn: s.TestBroadcast66}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, - {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + {Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66}, {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, - {Name: "TestOldAnnounce_66", Fn: s.TestOldAnnounce_66}, + {Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66}, // malicious handshakes + status {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, - {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, - {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, + {Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66}, + {Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66}, // test transactions {Name: "TestTransaction", Fn: s.TestTransaction}, - {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, + {Name: "TestTransaction66", Fn: s.TestTransaction66}, {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, - {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, - {Name: "TestLargeTxRequest_66", Fn: s.TestLargeTxRequest_66}, - {Name: "TestNewPooledTxs_66", Fn: s.TestNewPooledTxs_66}, + {Name: "TestMaliciousTx66", Fn: s.TestMaliciousTx66}, + {Name: "TestLargeTxRequest66", Fn: s.TestLargeTxRequest66}, + {Name: "TestNewPooledTxs66", Fn: s.TestNewPooledTxs66}, } } @@ -109,6 +92,7 @@ func (s *Suite) EthTests() []utesting.Test { {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, {Name: "TestBroadcast", Fn: s.TestBroadcast}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, {Name: "TestTransaction", Fn: s.TestTransaction}, @@ -119,90 +103,101 @@ func (s *Suite) EthTests() []utesting.Test { func (s *Suite) Eth66Tests() []utesting.Test { return []utesting.Test{ // only proceed with eth66 test suite if node supports eth 66 protocol - {Name: "TestStatus_66", Fn: s.TestStatus_66}, - {Name: "TestGetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, - {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, - {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, - {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, - {Name: "TestGetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, - {Name: "TestBroadcast_66", Fn: s.TestBroadcast_66}, - {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, - {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, - {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, - {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, - {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, - {Name: "TestLargeTxRequest_66", Fn: s.TestLargeTxRequest_66}, - {Name: "TestNewPooledTxs_66", Fn: s.TestNewPooledTxs_66}, + {Name: "TestStatus66", Fn: s.TestStatus66}, + {Name: "TestGetBlockHeaders66", Fn: s.TestGetBlockHeaders66}, + {Name: "TestSimultaneousRequests66", Fn: s.TestSimultaneousRequests66}, + {Name: "TestSameRequestID66", Fn: s.TestSameRequestID66}, + {Name: "TestZeroRequestID66", Fn: s.TestZeroRequestID66}, + {Name: "TestGetBlockBodies66", Fn: s.TestGetBlockBodies66}, + {Name: "TestBroadcast66", Fn: s.TestBroadcast66}, + {Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66}, + {Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66}, + {Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66}, + {Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66}, + {Name: "TestTransaction66", Fn: s.TestTransaction66}, + {Name: "TestMaliciousTx66", Fn: s.TestMaliciousTx66}, + {Name: "TestLargeTxRequest66", Fn: s.TestLargeTxRequest66}, + {Name: "TestNewPooledTxs66", Fn: s.TestNewPooledTxs66}, } } +var ( + eth66 = true // indicates whether suite should negotiate eth66 connection + eth65 = false // indicates whether suite should negotiate eth65 connection or below. +) + // TestStatus attempts to connect to the given node and exchange -// a status message with it, and then check to make sure -// the chain head is correct. +// a status message with it. func (s *Suite) TestStatus(t *utesting.T) { conn, err := s.dial() if err != nil { - t.Fatalf("could not dial: %v", err) + t.Fatalf("dial failed: %v", err) } defer conn.Close() - // get protoHandshake - conn.handshake(t) - // get status - switch msg := conn.statusExchange(t, s.chain, nil).(type) { - case *Status: - t.Logf("got status message: %s", pretty.Sdump(msg)) - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) } } -// TestMaliciousStatus sends a status package with a large total difficulty. -func (s *Suite) TestMaliciousStatus(t *utesting.T) { - conn, err := s.dial() +// TestStatus66 attempts to connect to the given node and exchange +// a status message with it on the eth66 protocol. +func (s *Suite) TestStatus66(t *utesting.T) { + conn, err := s.dial66() if err != nil { - t.Fatalf("could not dial: %v", err) + t.Fatalf("dial failed: %v", err) } defer conn.Close() - // get protoHandshake - conn.handshake(t) - status := &Status{ - ProtocolVersion: uint32(conn.negotiatedProtoVersion), - NetworkID: s.chain.chainConfig.ChainID.Uint64(), - TD: largeNumber(2), - Head: s.chain.blocks[s.chain.Len()-1].Hash(), - Genesis: s.chain.blocks[0].Hash(), - ForkID: s.chain.ForkID(), - } - // get status - switch msg := conn.statusExchange(t, s.chain, status).(type) { - case *Status: - t.Logf("%+v\n", msg) - default: - t.Fatalf("expected status, got: %#v ", msg) - } - // wait for disconnect - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { - case *Disconnect: - case *Error: - return - default: - t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) } } // TestGetBlockHeaders tests whether the given node can respond to -// a `GetBlockHeaders` request and that the response is accurate. +// a `GetBlockHeaders` request accurately. func (s *Suite) TestGetBlockHeaders(t *utesting.T) { conn, err := s.dial() if err != nil { - t.Fatalf("could not dial: %v", err) + t.Fatalf("dial failed: %v", err) } defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("handshake(s) failed: %v", err) + } + // write request + req := &GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + } + headers, err := conn.headersRequest(req, s.chain, eth65, 0) + if err != nil { + t.Fatalf("GetBlockHeaders request failed: %v", err) + } + // check for correct headers + expected, err := s.chain.GetHeaders(*req) + if err != nil { + t.Fatalf("failed to get headers for given request: %v", err) + } + if !headersMatch(expected, headers) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) + } +} - conn.handshake(t) - conn.statusExchange(t, s.chain, nil) - - // get block headers +// TestGetBlockHeaders66 tests whether the given node can respond to +// an eth66 `GetBlockHeaders` request and that the response is accurate. +func (s *Suite) TestGetBlockHeaders66(t *utesting.T) { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // write request req := &GetBlockHeaders{ Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), @@ -211,21 +206,185 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { Skip: 1, Reverse: false, } + headers, err := conn.headersRequest(req, s.chain, eth66, 33) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + } + // check for correct headers + expected, err := s.chain.GetHeaders(*req) + if err != nil { + t.Fatalf("failed to get headers for given request: %v", err) + } + if !headersMatch(expected, headers) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) + } +} - if err := conn.Write(req); err != nil { - t.Fatalf("could not write to connection: %v", err) +// TestSimultaneousRequests66 sends two simultaneous `GetBlockHeader` requests from +// the same connection with different request IDs and checks to make sure the node +// responds with the correct headers per request. +func (s *Suite) TestSimultaneousRequests66(t *utesting.T) { + // create a connection + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // create two requests + req1 := ð.GetBlockHeadersPacket66{ + RequestId: uint64(111), + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + req2 := ð.GetBlockHeadersPacket66{ + RequestId: uint64(222), + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 4, + Skip: 1, + Reverse: false, + }, + } + // write the first request + if err := conn.Write66(req1, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // write the second request + if err := conn.Write66(req2, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // wait for responses + msg := conn.waitForResponse(s.chain, timeout, req1.RequestId) + headers1, ok := msg.(BlockHeaders) + if !ok { + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } + msg = conn.waitForResponse(s.chain, timeout, req2.RequestId) + headers2, ok := msg.(BlockHeaders) + if !ok { + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } + // check received headers for accuracy + expected1, err := s.chain.GetHeaders(GetBlockHeaders(*req1.GetBlockHeadersPacket)) + if err != nil { + t.Fatalf("failed to get expected headers for request 1: %v", err) + } + expected2, err := s.chain.GetHeaders(GetBlockHeaders(*req2.GetBlockHeadersPacket)) + if err != nil { + t.Fatalf("failed to get expected headers for request 2: %v", err) + } + if !headersMatch(expected1, headers1) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) } + if !headersMatch(expected2, headers2) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) + } +} - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { - case *BlockHeaders: - headers := *msg - for _, header := range headers { - num := header.Number.Uint64() - t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) - assert.Equal(t, s.chain.blocks[int(num)].Header(), header) - } - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) +// TestSameRequestID66 sends two requests with the same request ID to a +// single node. +func (s *Suite) TestSameRequestID66(t *utesting.T) { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // create requests + reqID := uint64(1234) + request1 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 1, + }, + Amount: 2, + }, + } + request2 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 33, + }, + Amount: 2, + }, + } + // write the requests + if err = conn.Write66(request1, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + if err = conn.Write66(request2, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // wait for responses + msg := conn.waitForResponse(s.chain, timeout, reqID) + headers1, ok := msg.(BlockHeaders) + if !ok { + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } + msg = conn.waitForResponse(s.chain, timeout, reqID) + headers2, ok := msg.(BlockHeaders) + if !ok { + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } + // check if headers match + expected1, err := s.chain.GetHeaders(GetBlockHeaders(*request1.GetBlockHeadersPacket)) + if err != nil { + t.Fatalf("failed to get expected block headers: %v", err) + } + expected2, err := s.chain.GetHeaders(GetBlockHeaders(*request2.GetBlockHeadersPacket)) + if err != nil { + t.Fatalf("failed to get expected block headers: %v", err) + } + if !headersMatch(expected1, headers1) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) + } + if !headersMatch(expected2, headers2) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) + } +} + +// TestZeroRequestID_66 checks that a message with a request ID of zero is still handled +// by the node. +func (s *Suite) TestZeroRequestID66(t *utesting.T) { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + req := &GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Number: 0, + }, + Amount: 2, + } + headers, err := conn.headersRequest(req, s.chain, eth66, 0) + if err != nil { + t.Fatalf("failed to get block headers: %v", err) + } + expected, err := s.chain.GetHeaders(*req) + if err != nil { + t.Fatalf("failed to get expected block headers: %v", err) + } + if !headersMatch(expected, headers) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) } } @@ -234,12 +393,12 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { func (s *Suite) TestGetBlockBodies(t *utesting.T) { conn, err := s.dial() if err != nil { - t.Fatalf("could not dial: %v", err) + t.Fatalf("dial failed: %v", err) } defer conn.Close() - - conn.handshake(t) - conn.statusExchange(t, s.chain, nil) + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } // create block bodies request req := &GetBlockBodies{ s.chain.blocks[54].Hash(), @@ -248,126 +407,125 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { if err := conn.Write(req); err != nil { t.Fatalf("could not write to connection: %v", err) } - - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + // wait for response + switch msg := conn.readAndServe(s.chain, timeout).(type) { case *BlockBodies: t.Logf("received %d block bodies", len(*msg)) + if len(*msg) != len(*req) { + t.Fatalf("wrong bodies in response: expected %d bodies, "+ + "got %d", len(*req), len(*msg)) + } default: t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } } +// TestGetBlockBodies66 tests whether the given node can respond to +// a `GetBlockBodies` request and that the response is accurate over +// the eth66 protocol. +func (s *Suite) TestGetBlockBodies66(t *utesting.T) { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // create block bodies request + req := ð.GetBlockBodiesPacket66{ + RequestId: uint64(55), + GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + }, + } + if err := conn.Write66(req, GetBlockBodies{}.Code()); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // wait for block bodies response + msg := conn.waitForResponse(s.chain, timeout, req.RequestId) + blockBodies, ok := msg.(BlockBodies) + if !ok { + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + t.Logf("received %d block bodies", len(blockBodies)) + if len(blockBodies) != len(req.GetBlockBodiesPacket) { + t.Fatalf("wrong bodies in response: expected %d bodies, "+ + "got %d", len(req.GetBlockBodiesPacket), len(blockBodies)) + } +} + // TestBroadcast tests whether a block announcement is correctly // propagated to the given node's peer(s). func (s *Suite) TestBroadcast(t *utesting.T) { - s.sendNextBlock(t) + if err := s.sendNextBlock(eth65); err != nil { + t.Fatalf("block broadcast failed: %v", err) + } } -func (s *Suite) sendNextBlock(t *utesting.T) { - sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) - defer sendConn.Close() - defer receiveConn.Close() - - // create new block announcement - nextBlock := len(s.chain.blocks) - blockAnnouncement := &NewBlock{ - Block: s.fullChain.blocks[nextBlock], - TD: s.fullChain.TD(nextBlock + 1), - } - // send announcement and wait for node to request the header - s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement) - // wait for client to update its chain - if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { - t.Fatal(err) +// TestBroadcast66 tests whether a block announcement is correctly +// propagated to the given node's peer(s) on the eth66 protocol. +func (s *Suite) TestBroadcast66(t *utesting.T) { + if err := s.sendNextBlock(eth66); err != nil { + t.Fatalf("block broadcast failed: %v", err) } - // update test suite chain - s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) } -// TestMaliciousHandshake tries to send malicious data during the handshake. -func (s *Suite) TestMaliciousHandshake(t *utesting.T) { - conn, err := s.dial() - if err != nil { - t.Fatalf("could not dial: %v", err) - } - defer conn.Close() - // write hello to client - pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] - handshakes := []*Hello{ - { - Version: 5, - Caps: []p2p.Cap{ - {Name: largeString(2), Version: 64}, - }, - ID: pub0, - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: append(pub0, byte(0)), - }, +// TestLargeAnnounce tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: append(pub0, pub0...), + Block: largeBlock(), + TD: s.fullChain.TotalDifficultyAt(nextBlock), }, { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: largeBuffer(2), + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), }, { - Version: 5, - Caps: []p2p.Cap{ - {Name: largeString(2), Version: 64}, - }, - ID: largeBuffer(2), + Block: largeBlock(), + TD: largeNumber(2), }, } - for i, handshake := range handshakes { - t.Logf("Testing malicious handshake %v\n", i) - // Init the handshake - if err := conn.Write(handshake); err != nil { - t.Fatalf("could not write to connection: %v", err) + + for i, blockAnnouncement := range blocks { + t.Logf("Testing malicious announcement: %v\n", i) + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) } - // check that the peer disconnected - timeout := 20 * time.Second - // Discard one hello - for i := 0; i < 2; i++ { - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { - case *Disconnect: - case *Error: - case *Hello: - // Hello's are send concurrently, so ignore them - continue - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) - } + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) } - // Dial for the next round - conn, err = s.dial() - if err != nil { - t.Fatalf("could not dial: %v", err) + if err = conn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) } + // Invalid announcement, check that peer disconnected + switch msg := conn.readAndServe(s.chain, time.Second*8).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + conn.Close() + } + // Test the last block as a valid block + if err := s.sendNextBlock(eth65); err != nil { + t.Fatalf("failed to broadcast next block: %v", err) } } -// TestLargeAnnounce tests the announcement mechanism with a large block. -func (s *Suite) TestLargeAnnounce(t *utesting.T) { +// TestLargeAnnounce66 tests the announcement mechanism with a large +// block over the eth66 protocol. +func (s *Suite) TestLargeAnnounce66(t *utesting.T) { nextBlock := len(s.chain.blocks) blocks := []*NewBlock{ { Block: largeBlock(), - TD: s.fullChain.TD(nextBlock + 1), + TD: s.fullChain.TotalDifficultyAt(nextBlock), }, { Block: s.fullChain.blocks[nextBlock], @@ -377,174 +535,220 @@ func (s *Suite) TestLargeAnnounce(t *utesting.T) { Block: largeBlock(), TD: largeNumber(2), }, - { - Block: s.fullChain.blocks[nextBlock], - TD: s.fullChain.TD(nextBlock + 1), - }, } for i, blockAnnouncement := range blocks[0:3] { t.Logf("Testing malicious announcement: %v\n", i) - sendConn := s.setupConnection(t) - if err := sendConn.Write(blockAnnouncement); err != nil { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + if err := conn.Write(blockAnnouncement); err != nil { t.Fatalf("could not write to connection: %v", err) } // Invalid announcement, check that peer disconnected - switch msg := sendConn.ReadAndServe(s.chain, time.Second*8).(type) { + switch msg := conn.readAndServe(s.chain, time.Second*8).(type) { case *Disconnect: case *Error: break default: t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) } - sendConn.Close() + conn.Close() } // Test the last block as a valid block - s.sendNextBlock(t) + if err := s.sendNextBlock(eth66); err != nil { + t.Fatalf("failed to broadcast next block: %v", err) + } } +// TestOldAnnounce tests the announcement mechanism with an old block. func (s *Suite) TestOldAnnounce(t *utesting.T) { - sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) - defer sendConn.Close() - defer recvConn.Close() + if err := s.oldAnnounce(eth65); err != nil { + t.Fatal(err) + } +} + +// TestOldAnnounce66 tests the announcement mechanism with an old block, +// over the eth66 protocol. +func (s *Suite) TestOldAnnounce66(t *utesting.T) { + if err := s.oldAnnounce(eth66); err != nil { + t.Fatal(err) + } +} + +// TestMaliciousHandshake tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake(t *utesting.T) { + if err := s.maliciousHandshakes(t, eth65); err != nil { + t.Fatal(err) + } +} - s.oldAnnounce(t, sendConn, recvConn) +// TestMaliciousHandshake66 tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake66(t *utesting.T) { + if err := s.maliciousHandshakes(t, eth66); err != nil { + t.Fatal(err) + } } -func (s *Suite) oldAnnounce(t *utesting.T, sendConn, receiveConn *Conn) { - oldBlockAnnounce := &NewBlock{ - Block: s.chain.blocks[len(s.chain.blocks)/2], - TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), +// TestMaliciousStatus sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("dial failed: %v", err) } + defer conn.Close() - if err := sendConn.Write(oldBlockAnnounce); err != nil { - t.Fatalf("could not write to connection: %v", err) + if err := s.maliciousStatus(conn); err != nil { + t.Fatal(err) } +} - switch msg := receiveConn.ReadAndServe(s.chain, time.Second*8).(type) { - case *NewBlock: - block := *msg - if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { - t.Fatalf("unexpected: block propagated: %s", pretty.Sdump(msg)) - } - case *NewBlockHashes: - hashes := *msg - for _, hash := range hashes { - if hash.Hash == oldBlockAnnounce.Block.Hash() { - t.Fatalf("unexpected: block announced: %s", pretty.Sdump(msg)) - } - } - case *Error: - errMsg := *msg - // check to make sure error is timeout (propagation didn't come through == test successful) - if !strings.Contains(errMsg.String(), "timeout") { - t.Fatalf("unexpected error: %v", pretty.Sdump(msg)) - } - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) +// TestMaliciousStatus66 sends a status package with a large total +// difficulty over the eth66 protocol. +func (s *Suite) TestMaliciousStatus66(t *utesting.T) { + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + + if err := s.maliciousStatus(conn); err != nil { + t.Fatal(err) } } -func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { - // Announce the block. - if err := sendConn.Write(blockAnnouncement); err != nil { - t.Fatalf("could not write to connection: %v", err) +// TestTransaction sends a valid transaction to the node and +// checks if the transaction gets propagated. +func (s *Suite) TestTransaction(t *utesting.T) { + if err := s.sendSuccessfulTxs(t, eth65); err != nil { + t.Fatal(err) } - s.waitAnnounce(t, receiveConn, blockAnnouncement) } -func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { - switch msg := conn.ReadAndServe(s.chain, timeout).(type) { - case *NewBlock: - t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) - assert.Equal(t, - blockAnnouncement.Block.Header(), msg.Block.Header(), - "wrong block header in announcement", - ) - assert.Equal(t, - blockAnnouncement.TD, msg.TD, - "wrong TD in announcement", - ) - case *NewBlockHashes: - message := *msg - t.Logf("received NewBlockHashes message: %s", pretty.Sdump(message)) - assert.Equal(t, blockAnnouncement.Block.Hash(), message[0].Hash, - "wrong block hash in announcement", - ) - default: - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) +// TestTransaction66 sends a valid transaction to the node and +// checks if the transaction gets propagated. +func (s *Suite) TestTransaction66(t *utesting.T) { + if err := s.sendSuccessfulTxs(t, eth66); err != nil { + t.Fatal(err) } } -func (s *Suite) setupConnection(t *utesting.T) *Conn { - // create conn - sendConn, err := s.dial() - if err != nil { - t.Fatalf("could not dial: %v", err) +// TestMaliciousTx sends several invalid transactions and tests whether +// the node will propagate them. +func (s *Suite) TestMaliciousTx(t *utesting.T) { + if err := s.sendMaliciousTxs(t, eth65); err != nil { + t.Fatal(err) + } +} + +// TestMaliciousTx66 sends several invalid transactions and tests whether +// the node will propagate them. +func (s *Suite) TestMaliciousTx66(t *utesting.T) { + if err := s.sendMaliciousTxs(t, eth66); err != nil { + t.Fatal(err) } - sendConn.handshake(t) - sendConn.statusExchange(t, s.chain, nil) - return sendConn } -// dial attempts to dial the given node and perform a handshake, -// returning the created Conn if successful. -func (s *Suite) dial() (*Conn, error) { - var conn Conn - // dial - fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) +// TestLargeTxRequest66 tests whether a node can fulfill a large GetPooledTransactions +// request. +func (s *Suite) TestLargeTxRequest66(t *utesting.T) { + // send the next block to ensure the node is no longer syncing and + // is able to accept txs + if err := s.sendNextBlock(eth66); err != nil { + t.Fatalf("failed to send next block: %v", err) + } + // send 2000 transactions to the node + hashMap, txs, err := generateTxs(s, 2000) if err != nil { - return nil, err + t.Fatalf("failed to generate transactions: %v", err) } - conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey()) - // do encHandshake - conn.ourKey, _ = crypto.GenerateKey() - _, err = conn.Handshake(conn.ourKey) + if err = sendMultipleSuccessfulTxs(t, s, txs); err != nil { + t.Fatalf("failed to send multiple txs: %v", err) + } + // set up connection to receive to ensure node is peered with the receiving connection + // before tx request is sent + conn, err := s.dial66() if err != nil { - return nil, err + t.Fatalf("dial failed: %v", err) } - // set default p2p capabilities - conn.caps = []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + // create and send pooled tx request + hashes := make([]common.Hash, 0) + for _, hash := range hashMap { + hashes = append(hashes, hash) + } + getTxReq := ð.GetPooledTransactionsPacket66{ + RequestId: 1234, + GetPooledTransactionsPacket: hashes, + } + if err = conn.Write66(getTxReq, GetPooledTransactions{}.Code()); err != nil { + t.Fatalf("could not write to conn: %v", err) + } + // check that all received transactions match those that were sent to node + switch msg := conn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) { + case PooledTransactions: + for _, gotTx := range msg { + if _, exists := hashMap[gotTx.Hash()]; !exists { + t.Fatalf("unexpected tx received: %v", gotTx.Hash()) + } + } + default: + t.Fatalf("unexpected %s", pretty.Sdump(msg)) } - conn.ourHighestProtoVersion = 65 - return &conn, nil } -func (s *Suite) TestTransaction(t *utesting.T) { - tests := []*types.Transaction{ - getNextTxFromChain(t, s), - unknownTx(t, s), +// TestNewPooledTxs_66 tests whether a node will do a GetPooledTransactions +// request upon receiving a NewPooledTransactionHashes announcement. +func (s *Suite) TestNewPooledTxs66(t *utesting.T) { + // send the next block to ensure the node is no longer syncing and + // is able to accept txs + if err := s.sendNextBlock(eth66); err != nil { + t.Fatalf("failed to send next block: %v", err) } - for i, tx := range tests { - t.Logf("Testing tx propagation: %v\n", i) - sendSuccessfulTx(t, s, tx) + // generate 50 txs + hashMap, _, err := generateTxs(s, 50) + if err != nil { + t.Fatalf("failed to generate transactions: %v", err) } -} - -func (s *Suite) TestMaliciousTx(t *utesting.T) { - badTxs := []*types.Transaction{ - getOldTxFromChain(t, s), - invalidNonceTx(t, s), - hugeAmount(t, s), - hugeGasPrice(t, s), - hugeData(t, s), - } - sendConn := s.setupConnection(t) - defer sendConn.Close() - // set up receiving connection before sending txs to make sure - // no announcements are missed - recvConn := s.setupConnection(t) - defer recvConn.Close() - - for i, tx := range badTxs { - t.Logf("Testing malicious tx propagation: %v\n", i) - if err := sendConn.Write(&Transactions{tx}); err != nil { - t.Fatalf("could not write to connection: %v", err) + // create new pooled tx hashes announcement + hashes := make([]common.Hash, 0) + for _, hash := range hashMap { + hashes = append(hashes, hash) + } + announce := NewPooledTransactionHashes(hashes) + // send announcement + conn, err := s.dial66() + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + if err = conn.Write(announce); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // wait for GetPooledTxs request + for { + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case GetPooledTransactions: + if len(msg) != len(hashes) { + t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg)) + } + return + case *NewPooledTransactionHashes: + // ignore propagated txs from old tests + continue + default: + t.Fatalf("unexpected %s", pretty.Sdump(msg)) } - } - // check to make sure bad txs aren't propagated - waitForTxPropagation(t, s, badTxs, recvConn) } diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index a6166bd2e3..d2dbe0a7d6 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -17,6 +17,7 @@ package ethtest import ( + "fmt" "math/big" "strings" "time" @@ -31,58 +32,171 @@ import ( //var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") -func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { - sendConn := s.setupConnection(t) - defer sendConn.Close() - sendSuccessfulTxWithConn(t, s, tx, sendConn) +func (s *Suite) sendSuccessfulTxs(t *utesting.T, isEth66 bool) error { + tests := []*types.Transaction{ + getNextTxFromChain(s), + unknownTx(s), + } + for i, tx := range tests { + if tx == nil { + return fmt.Errorf("could not find tx to send") + } + t.Logf("Testing tx propagation %d: sending tx %v %v %v\n", i, tx.Hash().String(), tx.GasPrice(), tx.Gas()) + // get previous tx if exists for reference in case of old tx propagation + var prevTx *types.Transaction + if i != 0 { + prevTx = tests[i-1] + } + // write tx to connection + if err := sendSuccessfulTx(s, tx, prevTx, isEth66); err != nil { + return fmt.Errorf("send successful tx test failed: %v", err) + } + } + return nil } -func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, sendConn *Conn) { - t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) +func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction, isEth66 bool) error { + sendConn, recvConn, err := s.createSendAndRecvConns(isEth66) + if err != nil { + return err + } + defer sendConn.Close() + defer recvConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } // Send the transaction - if err := sendConn.Write(&Transactions{tx}); err != nil { - t.Fatal(err) + if err = sendConn.Write(&Transactions{tx}); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } + // peer receiving connection to node + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) } // update last nonce seen nonce = tx.Nonce() - - recvConn := s.setupConnection(t) // Wait for the transaction announcement - switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { - case *Transactions: - recTxs := *msg - for _, gotTx := range recTxs { - if gotTx.Hash() == tx.Hash() { - // Ok - return + for { + switch msg := recvConn.readAndServe(s.chain, timeout).(type) { + case *Transactions: + recTxs := *msg + // if you receive an old tx propagation, read from connection again + if len(recTxs) == 1 && prevTx != nil { + if recTxs[0] == prevTx { + continue + } } - } - t.Fatalf("missing transaction: got %v missing %v", recTxs, tx.Hash()) - case *NewPooledTransactionHashes: - txHashes := *msg - for _, gotHash := range txHashes { - if gotHash == tx.Hash() { - return + for _, gotTx := range recTxs { + if gotTx.Hash() == tx.Hash() { + // Ok + return nil + } } + return fmt.Errorf("missing transaction: got %v missing %v", recTxs, tx.Hash()) + case *NewPooledTransactionHashes: + txHashes := *msg + // if you receive an old tx propagation, read from connection again + if len(txHashes) == 1 && prevTx != nil { + if txHashes[0] == prevTx.Hash() { + continue + } + } + for _, gotHash := range txHashes { + if gotHash == tx.Hash() { + // Ok + return nil + } + } + return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) + default: + return fmt.Errorf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) } - t.Fatalf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) - default: - t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) } } +func (s *Suite) sendMaliciousTxs(t *utesting.T, isEth66 bool) error { + badTxs := []*types.Transaction{ + getOldTxFromChain(s), + invalidNonceTx(s), + hugeAmount(s), + hugeGasPrice(s), + hugeData(s), + } + // setup receiving connection before sending malicious txs + var ( + recvConn *Conn + err error + ) + if isEth66 { + recvConn, err = s.dial66() + } else { + recvConn, err = s.dial() + } + if err != nil { + return fmt.Errorf("dial failed: %v", err) + } + defer recvConn.Close() + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + for i, tx := range badTxs { + t.Logf("Testing malicious tx propagation: %v\n", i) + if err = sendMaliciousTx(s, tx, isEth66); err != nil { + return fmt.Errorf("malicious tx test failed:\ntx: %v\nerror: %v", tx, err) + } + } + // check to make sure bad txs aren't propagated + return checkMaliciousTxPropagation(s, badTxs, recvConn) +} + +func sendMaliciousTx(s *Suite, tx *types.Transaction, isEth66 bool) error { + // setup connection + var ( + conn *Conn + err error + ) + if isEth66 { + conn, err = s.dial66() + } else { + conn, err = s.dial() + } + if err != nil { + return fmt.Errorf("dial failed: %v", err) + } + defer conn.Close() + if err = conn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + // write malicious tx + if err = conn.Write(&Transactions{tx}); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } + return nil +} + var nonce = uint64(99) -func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*types.Transaction) { +// sendMultipleSuccessfulTxs sends the given transactions to the node and +// expects the node to accept and propagate them. +func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction) error { txMsg := Transactions(txs) t.Logf("sending %d txs\n", len(txs)) - recvConn := s.setupConnection(t) + sendConn, recvConn, err := s.createSendAndRecvConns(true) + if err != nil { + return err + } + defer sendConn.Close() defer recvConn.Close() - + if err = sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } // Send the transactions - if err := sendConn.Write(&txMsg); err != nil { - t.Fatal(err) + if err = sendConn.Write(&txMsg); err != nil { + return fmt.Errorf("failed to write message to connection: %v", err) } // update nonce nonce = txs[len(txs)-1].Nonce() @@ -90,7 +204,7 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*t recvHashes := make([]common.Hash, 0) // all txs should be announced within 3 announcements for i := 0; i < 3; i++ { - switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + switch msg := recvConn.readAndServe(s.chain, timeout).(type) { case *Transactions: for _, tx := range *msg { recvHashes = append(recvHashes, tx.Hash()) @@ -99,7 +213,7 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*t recvHashes = append(recvHashes, *msg...) default: if !strings.Contains(pretty.Sdump(msg), "i/o timeout") { - t.Fatalf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg)) + return fmt.Errorf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg)) } } // break once all 2000 txs have been received @@ -112,7 +226,7 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*t continue } else { t.Logf("successfully received all %d txs", len(txs)) - return + return nil } } } @@ -121,13 +235,15 @@ func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*t for _, missing := range missingTxs { t.Logf("missing tx: %v", missing.Hash()) } - t.Fatalf("missing %d txs", len(missingTxs)) + return fmt.Errorf("missing %d txs", len(missingTxs)) } + return nil } -func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, recvConn *Conn) { - // Wait for another transaction announcement - switch msg := recvConn.ReadAndServe(s.chain, time.Second*8).(type) { +// checkMaliciousTxPropagation checks whether the given malicious transactions were +// propagated by the node. +func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) error { + switch msg := conn.readAndServe(s.chain, time.Second*8).(type) { case *Transactions: // check to see if any of the failing txs were in the announcement recvTxs := make([]common.Hash, len(*msg)) @@ -136,25 +252,20 @@ func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, rec } badTxs, _ := compareReceivedTxs(recvTxs, txs) if len(badTxs) > 0 { - for _, tx := range badTxs { - t.Logf("received bad tx: %v", tx) - } - t.Fatalf("received %d bad txs", len(badTxs)) + return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs) } case *NewPooledTransactionHashes: badTxs, _ := compareReceivedTxs(*msg, txs) if len(badTxs) > 0 { - for _, tx := range badTxs { - t.Logf("received bad tx: %v", tx) - } - t.Fatalf("received %d bad txs", len(badTxs)) + return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs) } case *Error: // Transaction should not be announced -> wait for timeout - return + return nil default: - t.Fatalf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) + return fmt.Errorf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) } + return nil } // compareReceivedTxs compares the received set of txs against the given set of txs, @@ -180,118 +291,129 @@ func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (presen return present, missing } -func unknownTx(t *utesting.T, s *Suite) *types.Transaction { - tx := getNextTxFromChain(t, s) +func unknownTx(s *Suite) *types.Transaction { + tx := getNextTxFromChain(s) + if tx == nil { + return nil + } var to common.Address if tx.To() != nil { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(t, s.chain.chainConfig, txNew) + return signWithFaucet(s.chain.chainConfig, txNew) } -func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction { +func getNextTxFromChain(s *Suite) *types.Transaction { // Get a new transaction - var tx *types.Transaction for _, blocks := range s.fullChain.blocks[s.chain.Len():] { txs := blocks.Transactions() if txs.Len() != 0 { - tx = txs[0] - break + return txs[0] } } - if tx == nil { - t.Fatal("could not find transaction") - } - return tx + return nil } -func generateTxs(t *utesting.T, s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction) { +func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction, error) { txHashMap := make(map[common.Hash]common.Hash, numTxs) txs := make([]*types.Transaction, numTxs) - nextTx := getNextTxFromChain(t, s) + nextTx := getNextTxFromChain(s) + if nextTx == nil { + return nil, nil, fmt.Errorf("failed to get the next transaction") + } gas := nextTx.Gas() nonce = nonce + 1 // generate txs for i := 0; i < numTxs; i++ { - tx := generateTx(t, s.chain.chainConfig, nonce, gas) + tx := generateTx(s.chain.chainConfig, nonce, gas) + if tx == nil { + return nil, nil, fmt.Errorf("failed to get the next transaction") + } txHashMap[tx.Hash()] = tx.Hash() txs[i] = tx nonce = nonce + 1 } - return txHashMap, txs + return txHashMap, txs, nil } -func generateTx(t *utesting.T, chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction { +func generateTx(chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction { var to common.Address tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{}) - return signWithFaucet(t, chainConfig, tx) + return signWithFaucet(chainConfig, tx) } -func getOldTxFromChain(t *utesting.T, s *Suite) *types.Transaction { - var tx *types.Transaction +func getOldTxFromChain(s *Suite) *types.Transaction { for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] { txs := blocks.Transactions() if txs.Len() != 0 { - tx = txs[0] - break + return txs[0] } } - if tx == nil { - t.Fatal("could not find transaction") - } - return tx + return nil } -func invalidNonceTx(t *utesting.T, s *Suite) *types.Transaction { - tx := getNextTxFromChain(t, s) +func invalidNonceTx(s *Suite) *types.Transaction { + tx := getNextTxFromChain(s) + if tx == nil { + return nil + } var to common.Address if tx.To() != nil { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(t, s.chain.chainConfig, txNew) + return signWithFaucet(s.chain.chainConfig, txNew) } -func hugeAmount(t *utesting.T, s *Suite) *types.Transaction { - tx := getNextTxFromChain(t, s) +func hugeAmount(s *Suite) *types.Transaction { + tx := getNextTxFromChain(s) + if tx == nil { + return nil + } amount := largeNumber(2) var to common.Address if tx.To() != nil { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(t, s.chain.chainConfig, txNew) + return signWithFaucet(s.chain.chainConfig, txNew) } -func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction { - tx := getNextTxFromChain(t, s) +func hugeGasPrice(s *Suite) *types.Transaction { + tx := getNextTxFromChain(s) + if tx == nil { + return nil + } gasPrice := largeNumber(2) var to common.Address if tx.To() != nil { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data()) - return signWithFaucet(t, s.chain.chainConfig, txNew) + return signWithFaucet(s.chain.chainConfig, txNew) } -func hugeData(t *utesting.T, s *Suite) *types.Transaction { - tx := getNextTxFromChain(t, s) +func hugeData(s *Suite) *types.Transaction { + tx := getNextTxFromChain(s) + if tx == nil { + return nil + } var to common.Address if tx.To() != nil { to = *tx.To() } txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2)) - return signWithFaucet(t, s.chain.chainConfig, txNew) + return signWithFaucet(s.chain.chainConfig, txNew) } -func signWithFaucet(t *utesting.T, chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction { +func signWithFaucet(chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction { signer := types.LatestSigner(chainConfig) signedTx, err := types.SignTx(tx, signer, faucetKey) if err != nil { - t.Fatalf("could not sign tx: %v\n", err) + return nil } return signedTx } diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index 50a69b9418..e49ea284e9 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -19,13 +19,8 @@ package ethtest import ( "crypto/ecdsa" "fmt" - "reflect" - "time" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" @@ -137,6 +132,7 @@ type Conn struct { caps []p2p.Cap } +// Read reads an eth packet from the connection. func (c *Conn) Read() Message { code, rawData, _, err := c.Conn.Read() if err != nil { @@ -185,32 +181,83 @@ func (c *Conn) Read() Message { return msg } -// ReadAndServe serves GetBlockHeaders requests while waiting -// on another message from the node. -func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message { - start := time.Now() - for time.Since(start) < timeout { - c.SetReadDeadline(time.Now().Add(5 * time.Second)) - switch msg := c.Read().(type) { - case *Ping: - c.Write(&Pong{}) - case *GetBlockHeaders: - req := *msg - headers, err := chain.GetHeaders(req) - if err != nil { - return errorf("could not get headers for inbound header request: %v", err) - } - - if err := c.Write(headers); err != nil { - return errorf("could not write to connection: %v", err) - } - default: - return msg +// Read66 reads an eth66 packet from the connection. +func (c *Conn) Read66() (uint64, Message) { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return 0, errorf("could not read from connection: %v", err) + } + + var msg Message + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + ethMsg := new(eth.GetBlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) } + return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket) + case (BlockHeaders{}).Code(): + ethMsg := new(eth.BlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket) + case (GetBlockBodies{}).Code(): + ethMsg := new(eth.GetBlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket) + case (BlockBodies{}).Code(): + ethMsg := new(eth.BlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) + case (GetPooledTransactions{}.Code()): + ethMsg := new(eth.GetPooledTransactionsPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetPooledTransactions(ethMsg.GetPooledTransactionsPacket) + case (PooledTransactions{}.Code()): + ethMsg := new(eth.PooledTransactionsPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, PooledTransactions(ethMsg.PooledTransactionsPacket) + default: + msg = errorf("invalid message code: %d", code) + } + + if msg != nil { + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return 0, msg } - return errorf("no message received within %v", timeout) + return 0, errorf("invalid message: %s", string(rawData)) } +// Write writes a eth packet to the connection. func (c *Conn) Write(msg Message) error { // check if message is eth protocol message var ( @@ -225,135 +272,12 @@ func (c *Conn) Write(msg Message) error { return err } -// handshake checks to make sure a `HELLO` is received. -func (c *Conn) handshake(t *utesting.T) Message { - defer c.SetDeadline(time.Time{}) - c.SetDeadline(time.Now().Add(10 * time.Second)) - - // write hello to client - pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] - ourHandshake := &Hello{ - Version: 5, - Caps: c.caps, - ID: pub0, - } - if err := c.Write(ourHandshake); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - // read hello from client - switch msg := c.Read().(type) { - case *Hello: - // set snappy if version is at least 5 - if msg.Version >= 5 { - c.SetSnappy(true) - } - c.negotiateEthProtocol(msg.Caps) - if c.negotiatedProtoVersion == 0 { - t.Fatalf("unexpected eth protocol version") - } - return msg - default: - t.Fatalf("bad handshake: %#v", msg) - return nil - } -} - -// negotiateEthProtocol sets the Conn's eth protocol version -// to highest advertised capability from peer -func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { - var highestEthVersion uint - for _, capability := range caps { - if capability.Name != "eth" { - continue - } - if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { - highestEthVersion = capability.Version - } - } - c.negotiatedProtoVersion = highestEthVersion -} - -// statusExchange performs a `Status` message exchange with the given -// node. -func (c *Conn) statusExchange(t *utesting.T, chain *Chain, status *Status) Message { - defer c.SetDeadline(time.Time{}) - c.SetDeadline(time.Now().Add(20 * time.Second)) - - // read status message from client - var message Message -loop: - for { - switch msg := c.Read().(type) { - case *Status: - if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { - t.Fatalf("wrong head block in status, want: %#x (block %d) have %#x", - want, chain.blocks[chain.Len()-1].NumberU64(), have) - } - if have, want := msg.TD.Cmp(chain.TD(chain.Len())), 0; have != want { - t.Fatalf("wrong TD in status: have %v want %v", have, want) - } - if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { - t.Fatalf("wrong fork ID in status: have %v, want %v", have, want) - } - message = msg - break loop - case *Disconnect: - t.Fatalf("disconnect received: %v", msg.Reason) - case *Ping: - c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error - // (PINGs should not be a response upon fresh connection) - default: - t.Fatalf("bad status message: %s", pretty.Sdump(msg)) - } - } - // make sure eth protocol version is set for negotiation - if c.negotiatedProtoVersion == 0 { - t.Fatalf("eth protocol version must be set in Conn") - } - if status == nil { - // write status message to client - status = &Status{ - ProtocolVersion: uint32(c.negotiatedProtoVersion), - NetworkID: chain.chainConfig.ChainID.Uint64(), - TD: chain.TD(chain.Len()), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), - } - } - - if err := c.Write(status); err != nil { - t.Fatalf("could not write to connection: %v", err) - } - - return message -} - -// waitForBlock waits for confirmation from the client that it has -// imported the given block. -func (c *Conn) waitForBlock(block *types.Block) error { - defer c.SetReadDeadline(time.Time{}) - - c.SetReadDeadline(time.Now().Add(20 * time.Second)) - // note: if the node has not yet imported the block, it will respond - // to the GetBlockHeaders request with an empty BlockHeaders response, - // so the GetBlockHeaders request must be sent again until the BlockHeaders - // response contains the desired header. - for { - req := &GetBlockHeaders{Origin: eth.HashOrNumber{Hash: block.Hash()}, Amount: 1} - if err := c.Write(req); err != nil { - return err - } - switch msg := c.Read().(type) { - case *BlockHeaders: - for _, header := range *msg { - if header.Number.Uint64() == block.NumberU64() { - return nil - } - } - time.Sleep(100 * time.Millisecond) - default: - return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) - } +// Write66 writes an eth66 packet to the connection. +func (c *Conn) Write66(req eth.Packet, code int) error { + payload, err := rlp.EncodeToBytes(req) + if err != nil { + return err } + _, err = c.Conn.Write(uint64(code), payload) + return err } From 10962b685ef08579846342b3ab80507306ccd494 Mon Sep 17 00:00:00 2001 From: meowsbits Date: Tue, 25 May 2021 16:22:46 -0500 Subject: [PATCH 576/948] ethstats: fix URL parser for '@' or ':' in node name/password (#21640) Fixes the case (example below) where the value passed to --ethstats flag would be parsed wrongly because the node name and/or password value contained the special characters '@' or ':' --ethstats "ETC Labs Metrics @meowsbits":mypass@ws://mordor.dash.fault.dev:3000 --- ethstats/ethstats.go | 39 +++++++++++++++++------ ethstats/ethstats_test.go | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 ethstats/ethstats_test.go diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index c7acb9481c..ef83e5a4eb 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -24,7 +24,6 @@ import ( "fmt" "math/big" "net/http" - "regexp" "runtime" "strconv" "strings" @@ -144,21 +143,43 @@ func (w *connWrapper) Close() error { return w.conn.Close() } +// parseEthstatsURL parses the netstats connection url. +// URL argument should be of the form +// If non-erroring, the returned slice contains 3 elements: [nodename, pass, host] +func parseEthstatsURL(url string) (parts []string, err error) { + err = fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) + + hostIndex := strings.LastIndex(url, "@") + if hostIndex == -1 || hostIndex == len(url)-1 { + return nil, err + } + preHost, host := url[:hostIndex], url[hostIndex+1:] + + passIndex := strings.LastIndex(preHost, ":") + if passIndex == -1 { + return []string{preHost, "", host}, nil + } + nodename, pass := preHost[:passIndex], "" + if passIndex != len(preHost)-1 { + pass = preHost[passIndex+1:] + } + + return []string{nodename, pass, host}, nil +} + // New returns a monitoring service ready for stats reporting. func New(node *node.Node, backend backend, engine consensus.Engine, url string) error { - // Parse the netstats connection url - re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)") - parts := re.FindStringSubmatch(url) - if len(parts) != 5 { - return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url) + parts, err := parseEthstatsURL(url) + if err != nil { + return err } ethstats := &Service{ backend: backend, engine: engine, server: node.Server(), - node: parts[1], - pass: parts[3], - host: parts[4], + node: parts[0], + pass: parts[1], + host: parts[2], pongCh: make(chan struct{}), histCh: make(chan []uint64, 1), } diff --git a/ethstats/ethstats_test.go b/ethstats/ethstats_test.go new file mode 100644 index 0000000000..92cec50c4d --- /dev/null +++ b/ethstats/ethstats_test.go @@ -0,0 +1,67 @@ +package ethstats + +import ( + "strconv" + "testing" +) + +func TestParseEthstatsURL(t *testing.T) { + cases := []struct { + url string + node, pass, host string + }{ + { + url: `"debug meowsbits":mypass@ws://mordor.dash.fault.dev:3000`, + node: "debug meowsbits", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `"debug @meowsbits":mypass@ws://mordor.dash.fault.dev:3000`, + node: "debug @meowsbits", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `"debug: @meowsbits":mypass@ws://mordor.dash.fault.dev:3000`, + node: "debug: @meowsbits", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `name:@ws://mordor.dash.fault.dev:3000`, + node: "name", pass: "", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `name@ws://mordor.dash.fault.dev:3000`, + node: "name", pass: "", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `:mypass@ws://mordor.dash.fault.dev:3000`, + node: "", pass: "mypass", host: "ws://mordor.dash.fault.dev:3000", + }, + { + url: `:@ws://mordor.dash.fault.dev:3000`, + node: "", pass: "", host: "ws://mordor.dash.fault.dev:3000", + }, + } + + for i, c := range cases { + parts, err := parseEthstatsURL(c.url) + if err != nil { + t.Fatal(err) + } + node, pass, host := parts[0], parts[1], parts[2] + + // unquote because the value provided will be used as a CLI flag value, so unescaped quotes will be removed + nodeUnquote, err := strconv.Unquote(node) + if err == nil { + node = nodeUnquote + } + + if node != c.node { + t.Errorf("case=%d mismatch node value, got: %v ,want: %v", i, node, c.node) + } + if pass != c.pass { + t.Errorf("case=%d mismatch pass value, got: %v ,want: %v", i, pass, c.pass) + } + if host != c.host { + t.Errorf("case=%d mismatch host value, got: %v ,want: %v", i, host, c.host) + } + } + +} From 05dab7f6bde376d1608ee7800b38cee7ce600d4d Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 26 May 2021 02:39:41 -0400 Subject: [PATCH 577/948] internal/ethapi: remove unused vm.Config parameter of DoCall (#22942) --- graphql/graphql.go | 5 ++--- internal/ethapi/api.go | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/graphql/graphql.go b/graphql/graphql.go index 71d80d8abd..94287b0d6b 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/rpc" @@ -870,7 +869,7 @@ func (b *Block) Call(ctx context.Context, args struct { return nil, err } } - result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) + result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, 5*time.Second, b.backend.RPCGasCap()) if err != nil { return nil, err } @@ -940,7 +939,7 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) + result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, 5*time.Second, p.backend.RPCGasCap()) if err != nil { return nil, err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b06df8ff9f..bf7c8e533d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -806,7 +806,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { return nil } -func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { +func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) @@ -895,7 +895,7 @@ func (e *revertError) ErrorData() interface{} { // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { - result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) + result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err } @@ -969,7 +969,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) + result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit From c73652da0bb0ca4a4ecf3b88b0efed085be9adc4 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 26 May 2021 15:58:09 +0800 Subject: [PATCH 578/948] core/state/snapshot: fix flaky tests (#22944) * core/state/snapshot: fix flaky tests * core/state/snapshot: fix tests --- core/state/snapshot/generate_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 3a669085f7..a92517b315 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -71,7 +71,7 @@ func TestGeneration(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -136,7 +136,7 @@ func TestGenerateExistentState(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -309,7 +309,7 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -361,7 +361,7 @@ func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -406,7 +406,7 @@ func TestGenerateCorruptAccountTrie(t *testing.T) { // Snapshot generation succeeded t.Errorf("Snapshot generated against corrupt account trie") - case <-time.After(250 * time.Millisecond): + case <-time.After(time.Second): // Not generated fast enough, hopefully blocked inside on missing trie node fail } // Signal abortion to the generator and wait for it to tear down @@ -466,7 +466,7 @@ func TestGenerateMissingStorageTrie(t *testing.T) { // Snapshot generation succeeded t.Errorf("Snapshot generated against corrupt storage trie") - case <-time.After(250 * time.Millisecond): + case <-time.After(time.Second): // Not generated fast enough, hopefully blocked inside on missing trie node fail } // Signal abortion to the generator and wait for it to tear down @@ -525,7 +525,7 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { // Snapshot generation succeeded t.Errorf("Snapshot generated against corrupt storage trie") - case <-time.After(250 * time.Millisecond): + case <-time.After(time.Second): // Not generated fast enough, hopefully blocked inside on missing trie node fail } // Signal abortion to the generator and wait for it to tear down @@ -588,7 +588,7 @@ func TestGenerateWithExtraAccounts(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -646,7 +646,7 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -699,7 +699,7 @@ func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -743,7 +743,7 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -775,7 +775,7 @@ func TestGenerateFromEmptySnap(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(1 * time.Second): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) @@ -822,7 +822,7 @@ func TestGenerateWithIncompleteStorage(t *testing.T) { case <-snap.genPending: // Snapshot generation succeeded - case <-time.After(250 * time.Millisecond): + case <-time.After(3 * time.Second): t.Errorf("Snapshot generation failed") } checkSnapRoot(t, snap, root) From 5869789d7500dba00e8f78382d5af82ef73a9b0e Mon Sep 17 00:00:00 2001 From: Mike Burr Date: Wed, 26 May 2021 14:33:00 -0600 Subject: [PATCH 579/948] ethstats: fix typo in comment (#22952) Trivial but helpful to understanding. --- ethstats/ethstats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index ef83e5a4eb..42d88f6db6 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -353,7 +353,7 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core // it, if they themselves are requests it initiates a reply, and lastly it drops // unknown packets. func (s *Service) readLoop(conn *connWrapper) { - // If the read loop exists, close the connection + // If the read loop exits, close the connection defer conn.Close() for { From 2e7714f8648b375f69f4146e702d5f19b36e55ba Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 27 May 2021 10:13:35 +0200 Subject: [PATCH 580/948] cmd/utils: avoid large alloc in --dev mode (#22949) * cmd/utils: avoid 1Gb alloc in --dev mode * cmd/geth: avoid 512Mb alloc in genesis query tests --- cmd/geth/genesis_test.go | 2 +- cmd/utils/flags.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index cbc1b38374..0563ef3c42 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -84,7 +84,7 @@ func TestCustomGenesis(t *testing.T) { runGeth(t, "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, "--networkid", "1337", "--syncmode=full", + geth := runGeth(t, "--networkid", "1337", "--syncmode=full", "--cache", "16", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1ae07108e4..c41286e4ec 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1234,6 +1234,9 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { if ctx.GlobalIsSet(KeyStoreDirFlag.Name) { cfg.KeyStoreDir = ctx.GlobalString(KeyStoreDirFlag.Name) } + if ctx.GlobalIsSet(DeveloperFlag.Name) { + cfg.UseLightweightKDF = true + } if ctx.GlobalIsSet(LightKDFFlag.Name) { cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name) } @@ -1647,6 +1650,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 } + cfg.SyncMode = downloader.FullSync // Create new developer account or reuse existing one var ( developer accounts.Account From 7194c847b6e7f545f2aad57d8eae0a046e08d7a4 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 27 May 2021 10:19:13 +0200 Subject: [PATCH 581/948] p2p/rlpx: reduce allocation and syscalls (#22899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change significantly improves the performance of RLPx message reads and writes. In the previous implementation, reading and writing of message frames performed multiple reads and writes on the underlying network connection, and allocated a new []byte buffer for every read. In the new implementation, reads and writes re-use buffers, and perform much fewer system calls on the underlying connection. This doubles the theoretically achievable throughput on a single connection, as shown by the benchmark result: name old speed new speed delta Throughput-8 70.3MB/s ± 0% 155.4MB/s ± 0% +121.11% (p=0.000 n=9+8) The change also removes support for the legacy, pre-EIP-8 handshake encoding. As of May 2021, no actively maintained client sends this format. --- p2p/rlpx/buffer.go | 127 +++++++++++++ p2p/rlpx/buffer_test.go | 51 ++++++ p2p/rlpx/rlpx.go | 385 ++++++++++++++++++++-------------------- p2p/rlpx/rlpx_test.go | 123 +++++++++---- p2p/transport.go | 5 + rlp/raw.go | 8 + rlp/raw_test.go | 6 + 7 files changed, 478 insertions(+), 227 deletions(-) create mode 100644 p2p/rlpx/buffer.go create mode 100644 p2p/rlpx/buffer_test.go diff --git a/p2p/rlpx/buffer.go b/p2p/rlpx/buffer.go new file mode 100644 index 0000000000..bb38e10577 --- /dev/null +++ b/p2p/rlpx/buffer.go @@ -0,0 +1,127 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlpx + +import ( + "io" +) + +// readBuffer implements buffering for network reads. This type is similar to bufio.Reader, +// with two crucial differences: the buffer slice is exposed, and the buffer keeps all +// read data available until reset. +// +// How to use this type: +// +// Keep a readBuffer b alongside the underlying network connection. When reading a packet +// from the connection, first call b.reset(). This empties b.data. Now perform reads +// through b.read() until the end of the packet is reached. The complete packet data is +// now available in b.data. +type readBuffer struct { + data []byte + end int +} + +// reset removes all processed data which was read since the last call to reset. +// After reset, len(b.data) is zero. +func (b *readBuffer) reset() { + unprocessed := b.end - len(b.data) + copy(b.data[:unprocessed], b.data[len(b.data):b.end]) + b.end = unprocessed + b.data = b.data[:0] +} + +// read reads at least n bytes from r, returning the bytes. +// The returned slice is valid until the next call to reset. +func (b *readBuffer) read(r io.Reader, n int) ([]byte, error) { + offset := len(b.data) + have := b.end - len(b.data) + + // If n bytes are available in the buffer, there is no need to read from r at all. + if have >= n { + b.data = b.data[:offset+n] + return b.data[offset : offset+n], nil + } + + // Make buffer space available. + need := n - have + b.grow(need) + + // Read. + rn, err := io.ReadAtLeast(r, b.data[b.end:cap(b.data)], need) + if err != nil { + return nil, err + } + b.end += rn + b.data = b.data[:offset+n] + return b.data[offset : offset+n], nil +} + +// grow ensures the buffer has at least n bytes of unused space. +func (b *readBuffer) grow(n int) { + if cap(b.data)-b.end >= n { + return + } + need := n - (cap(b.data) - b.end) + offset := len(b.data) + b.data = append(b.data[:cap(b.data)], make([]byte, need)...) + b.data = b.data[:offset] +} + +// writeBuffer implements buffering for network writes. This is essentially +// a convenience wrapper around a byte slice. +type writeBuffer struct { + data []byte +} + +func (b *writeBuffer) reset() { + b.data = b.data[:0] +} + +func (b *writeBuffer) appendZero(n int) []byte { + offset := len(b.data) + b.data = append(b.data, make([]byte, n)...) + return b.data[offset : offset+n] +} + +func (b *writeBuffer) Write(data []byte) (int, error) { + b.data = append(b.data, data...) + return len(data), nil +} + +const maxUint24 = int(^uint32(0) >> 8) + +func readUint24(b []byte) uint32 { + return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 +} + +func putUint24(v uint32, b []byte) { + b[0] = byte(v >> 16) + b[1] = byte(v >> 8) + b[2] = byte(v) +} + +// growslice ensures b has the wanted length by either expanding it to its capacity +// or allocating a new slice if b has insufficient capacity. +func growslice(b []byte, wantLength int) []byte { + if len(b) >= wantLength { + return b + } + if cap(b) >= wantLength { + return b[:cap(b)] + } + return make([]byte, wantLength) +} diff --git a/p2p/rlpx/buffer_test.go b/p2p/rlpx/buffer_test.go new file mode 100644 index 0000000000..9fee4172bd --- /dev/null +++ b/p2p/rlpx/buffer_test.go @@ -0,0 +1,51 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rlpx + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" +) + +func TestReadBufferReset(t *testing.T) { + reader := bytes.NewReader(hexutil.MustDecode("0x010202030303040505")) + var b readBuffer + + s1, _ := b.read(reader, 1) + s2, _ := b.read(reader, 2) + s3, _ := b.read(reader, 3) + + assert.Equal(t, []byte{1}, s1) + assert.Equal(t, []byte{2, 2}, s2) + assert.Equal(t, []byte{3, 3, 3}, s3) + + b.reset() + + s4, _ := b.read(reader, 1) + s5, _ := b.read(reader, 2) + + assert.Equal(t, []byte{4}, s4) + assert.Equal(t, []byte{5, 5}, s5) + + s6, err := b.read(reader, 2) + + assert.EqualError(t, err, "EOF") + assert.Nil(t, s6) +} diff --git a/p2p/rlpx/rlpx.go b/p2p/rlpx/rlpx.go index 2021bf08be..326c7c4941 100644 --- a/p2p/rlpx/rlpx.go +++ b/p2p/rlpx/rlpx.go @@ -48,19 +48,45 @@ import ( // This type is not generally safe for concurrent use, but reading and writing of messages // may happen concurrently after the handshake. type Conn struct { - dialDest *ecdsa.PublicKey - conn net.Conn - handshake *handshakeState - snappy bool + dialDest *ecdsa.PublicKey + conn net.Conn + session *sessionState + + // These are the buffers for snappy compression. + // Compression is enabled if they are non-nil. + snappyReadBuffer []byte + snappyWriteBuffer []byte } -type handshakeState struct { +// sessionState contains the session keys. +type sessionState struct { enc cipher.Stream dec cipher.Stream - macCipher cipher.Block - egressMAC hash.Hash - ingressMAC hash.Hash + egressMAC hashMAC + ingressMAC hashMAC + rbuf readBuffer + wbuf writeBuffer +} + +// hashMAC holds the state of the RLPx v4 MAC contraption. +type hashMAC struct { + cipher cipher.Block + hash hash.Hash + aesBuffer [16]byte + hashBuffer [32]byte + seedBuffer [32]byte +} + +func newHashMAC(cipher cipher.Block, h hash.Hash) hashMAC { + m := hashMAC{cipher: cipher, hash: h} + if cipher.BlockSize() != len(m.aesBuffer) { + panic(fmt.Errorf("invalid MAC cipher block size %d", cipher.BlockSize())) + } + if h.Size() != len(m.hashBuffer) { + panic(fmt.Errorf("invalid MAC digest size %d", h.Size())) + } + return m } // NewConn wraps the given network connection. If dialDest is non-nil, the connection @@ -76,7 +102,13 @@ func NewConn(conn net.Conn, dialDest *ecdsa.PublicKey) *Conn { // after the devp2p Hello message exchange when the negotiated version indicates that // compression is available on both ends of the connection. func (c *Conn) SetSnappy(snappy bool) { - c.snappy = snappy + if snappy { + c.snappyReadBuffer = []byte{} + c.snappyWriteBuffer = []byte{} + } else { + c.snappyReadBuffer = nil + c.snappyWriteBuffer = nil + } } // SetReadDeadline sets the deadline for all future read operations. @@ -95,12 +127,13 @@ func (c *Conn) SetDeadline(time time.Time) error { } // Read reads a message from the connection. +// The returned data buffer is valid until the next call to Read. func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) { - if c.handshake == nil { + if c.session == nil { panic("can't ReadMsg before handshake") } - frame, err := c.handshake.readFrame(c.conn) + frame, err := c.session.readFrame(c.conn) if err != nil { return 0, nil, 0, err } @@ -111,7 +144,7 @@ func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) { wireSize = len(data) // If snappy is enabled, verify and decompress message. - if c.snappy { + if c.snappyReadBuffer != nil { var actualSize int actualSize, err = snappy.DecodedLen(data) if err != nil { @@ -120,51 +153,55 @@ func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) { if actualSize > maxUint24 { return code, nil, 0, errPlainMessageTooLarge } - data, err = snappy.Decode(nil, data) + c.snappyReadBuffer = growslice(c.snappyReadBuffer, actualSize) + data, err = snappy.Decode(c.snappyReadBuffer, data) } return code, data, wireSize, err } -func (h *handshakeState) readFrame(conn io.Reader) ([]byte, error) { - // read the header - headbuf := make([]byte, 32) - if _, err := io.ReadFull(conn, headbuf); err != nil { +func (h *sessionState) readFrame(conn io.Reader) ([]byte, error) { + h.rbuf.reset() + + // Read the frame header. + header, err := h.rbuf.read(conn, 32) + if err != nil { return nil, err } - // verify header mac - shouldMAC := updateMAC(h.ingressMAC, h.macCipher, headbuf[:16]) - if !hmac.Equal(shouldMAC, headbuf[16:]) { + // Verify header MAC. + wantHeaderMAC := h.ingressMAC.computeHeader(header[:16]) + if !hmac.Equal(wantHeaderMAC, header[16:]) { return nil, errors.New("bad header MAC") } - h.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted - fsize := readInt24(headbuf) - // ignore protocol type for now - // read the frame content - var rsize = fsize // frame size rounded up to 16 byte boundary + // Decrypt the frame header to get the frame size. + h.dec.XORKeyStream(header[:16], header[:16]) + fsize := readUint24(header[:16]) + // Frame size rounded up to 16 byte boundary for padding. + rsize := fsize if padding := fsize % 16; padding > 0 { rsize += 16 - padding } - framebuf := make([]byte, rsize) - if _, err := io.ReadFull(conn, framebuf); err != nil { + + // Read the frame content. + frame, err := h.rbuf.read(conn, int(rsize)) + if err != nil { return nil, err } - // read and validate frame MAC. we can re-use headbuf for that. - h.ingressMAC.Write(framebuf) - fmacseed := h.ingressMAC.Sum(nil) - if _, err := io.ReadFull(conn, headbuf[:16]); err != nil { + // Validate frame MAC. + frameMAC, err := h.rbuf.read(conn, 16) + if err != nil { return nil, err } - shouldMAC = updateMAC(h.ingressMAC, h.macCipher, fmacseed) - if !hmac.Equal(shouldMAC, headbuf[:16]) { + wantFrameMAC := h.ingressMAC.computeFrame(frame) + if !hmac.Equal(wantFrameMAC, frameMAC) { return nil, errors.New("bad frame MAC") } - // decrypt frame content - h.dec.XORKeyStream(framebuf, framebuf) - return framebuf[:fsize], nil + // Decrypt the frame data. + h.dec.XORKeyStream(frame, frame) + return frame[:fsize], nil } // Write writes a message to the connection. @@ -172,83 +209,90 @@ func (h *handshakeState) readFrame(conn io.Reader) ([]byte, error) { // Write returns the written size of the message data. This may be less than or equal to // len(data) depending on whether snappy compression is enabled. func (c *Conn) Write(code uint64, data []byte) (uint32, error) { - if c.handshake == nil { + if c.session == nil { panic("can't WriteMsg before handshake") } if len(data) > maxUint24 { return 0, errPlainMessageTooLarge } - if c.snappy { - data = snappy.Encode(nil, data) + if c.snappyWriteBuffer != nil { + // Ensure the buffer has sufficient size. + // Package snappy will allocate its own buffer if the provided + // one is smaller than MaxEncodedLen. + c.snappyWriteBuffer = growslice(c.snappyWriteBuffer, snappy.MaxEncodedLen(len(data))) + data = snappy.Encode(c.snappyWriteBuffer, data) } wireSize := uint32(len(data)) - err := c.handshake.writeFrame(c.conn, code, data) + err := c.session.writeFrame(c.conn, code, data) return wireSize, err } -func (h *handshakeState) writeFrame(conn io.Writer, code uint64, data []byte) error { - ptype, _ := rlp.EncodeToBytes(code) +func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) error { + h.wbuf.reset() - // write header - headbuf := make([]byte, 32) - fsize := len(ptype) + len(data) + // Write header. + fsize := rlp.IntSize(code) + len(data) if fsize > maxUint24 { return errPlainMessageTooLarge } - putInt24(uint32(fsize), headbuf) - copy(headbuf[3:], zeroHeader) - h.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted + header := h.wbuf.appendZero(16) + putUint24(uint32(fsize), header) + copy(header[3:], zeroHeader) + h.enc.XORKeyStream(header, header) - // write header MAC - copy(headbuf[16:], updateMAC(h.egressMAC, h.macCipher, headbuf[:16])) - if _, err := conn.Write(headbuf); err != nil { - return err - } + // Write header MAC. + h.wbuf.Write(h.egressMAC.computeHeader(header)) - // write encrypted frame, updating the egress MAC hash with - // the data written to conn. - tee := cipher.StreamWriter{S: h.enc, W: io.MultiWriter(conn, h.egressMAC)} - if _, err := tee.Write(ptype); err != nil { - return err - } - if _, err := tee.Write(data); err != nil { - return err - } + // Encode and encrypt the frame data. + offset := len(h.wbuf.data) + h.wbuf.data = rlp.AppendUint64(h.wbuf.data, code) + h.wbuf.Write(data) if padding := fsize % 16; padding > 0 { - if _, err := tee.Write(zero16[:16-padding]); err != nil { - return err - } + h.wbuf.appendZero(16 - padding) } + framedata := h.wbuf.data[offset:] + h.enc.XORKeyStream(framedata, framedata) - // write frame MAC. egress MAC hash is up to date because - // frame content was written to it as well. - fmacseed := h.egressMAC.Sum(nil) - mac := updateMAC(h.egressMAC, h.macCipher, fmacseed) - _, err := conn.Write(mac) + // Write frame MAC. + h.wbuf.Write(h.egressMAC.computeFrame(framedata)) + + _, err := conn.Write(h.wbuf.data) return err } -func readInt24(b []byte) uint32 { - return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 +// computeHeader computes the MAC of a frame header. +func (m *hashMAC) computeHeader(header []byte) []byte { + sum1 := m.hash.Sum(m.hashBuffer[:0]) + return m.compute(sum1, header) } -func putInt24(v uint32, b []byte) { - b[0] = byte(v >> 16) - b[1] = byte(v >> 8) - b[2] = byte(v) +// computeFrame computes the MAC of framedata. +func (m *hashMAC) computeFrame(framedata []byte) []byte { + m.hash.Write(framedata) + seed := m.hash.Sum(m.seedBuffer[:0]) + return m.compute(seed, seed[:16]) } -// updateMAC reseeds the given hash with encrypted seed. -// it returns the first 16 bytes of the hash sum after seeding. -func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte { - aesbuf := make([]byte, aes.BlockSize) - block.Encrypt(aesbuf, mac.Sum(nil)) - for i := range aesbuf { - aesbuf[i] ^= seed[i] +// compute computes the MAC of a 16-byte 'seed'. +// +// To do this, it encrypts the current value of the hash state, then XORs the ciphertext +// with seed. The obtained value is written back into the hash state and hash output is +// taken again. The first 16 bytes of the resulting sum are the MAC value. +// +// This MAC construction is a horrible, legacy thing. +func (m *hashMAC) compute(sum1, seed []byte) []byte { + if len(seed) != len(m.aesBuffer) { + panic("invalid MAC seed") + } + + m.cipher.Encrypt(m.aesBuffer[:], sum1) + for i := range m.aesBuffer { + m.aesBuffer[i] ^= seed[i] } - mac.Write(aesbuf) - return mac.Sum(nil)[:16] + m.hash.Write(m.aesBuffer[:]) + sum2 := m.hash.Sum(m.hashBuffer[:0]) + return sum2[:16] } // Handshake performs the handshake. This must be called before any data is written @@ -257,23 +301,26 @@ func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) { var ( sec Secrets err error + h handshakeState ) if c.dialDest != nil { - sec, err = initiatorEncHandshake(c.conn, prv, c.dialDest) + sec, err = h.runInitiator(c.conn, prv, c.dialDest) } else { - sec, err = receiverEncHandshake(c.conn, prv) + sec, err = h.runRecipient(c.conn, prv) } if err != nil { return nil, err } c.InitWithSecrets(sec) + c.session.rbuf = h.rbuf + c.session.wbuf = h.wbuf return sec.remote, err } // InitWithSecrets injects connection secrets as if a handshake had // been performed. This cannot be called after the handshake. func (c *Conn) InitWithSecrets(sec Secrets) { - if c.handshake != nil { + if c.session != nil { panic("can't handshake twice") } macc, err := aes.NewCipher(sec.MAC) @@ -287,12 +334,11 @@ func (c *Conn) InitWithSecrets(sec Secrets) { // we use an all-zeroes IV for AES because the key used // for encryption is ephemeral. iv := make([]byte, encc.BlockSize()) - c.handshake = &handshakeState{ + c.session = &sessionState{ enc: cipher.NewCTR(encc, iv), dec: cipher.NewCTR(encc, iv), - macCipher: macc, - egressMAC: sec.EgressMAC, - ingressMAC: sec.IngressMAC, + egressMAC: newHashMAC(macc, sec.EgressMAC), + ingressMAC: newHashMAC(macc, sec.IngressMAC), } } @@ -303,28 +349,18 @@ func (c *Conn) Close() error { // Constants for the handshake. const ( - maxUint24 = int(^uint32(0) >> 8) - sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 sigLen = crypto.SignatureLength // elliptic S256 pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte shaLen = 32 // hash length (for nonce etc) - authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 - authRespLen = pubLen + shaLen + 1 - eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */ - - encAuthMsgLen = authMsgLen + eciesOverhead // size of encrypted pre-EIP-8 initiator handshake - encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply ) var ( // this is used in place of actual frame header data. // TODO: replace this when Msg contains the protocol type code. zeroHeader = []byte{0xC2, 0x80, 0x80} - // sixteen zero bytes - zero16 = make([]byte, 16) // errPlainMessageTooLarge is returned if a decompressed message length exceeds // the allowed 24 bits (i.e. length >= 16MB). @@ -338,19 +374,20 @@ type Secrets struct { remote *ecdsa.PublicKey } -// encHandshake contains the state of the encryption handshake. -type encHandshake struct { +// handshakeState contains the state of the encryption handshake. +type handshakeState struct { initiator bool remote *ecies.PublicKey // remote-pubk initNonce, respNonce []byte // nonce randomPrivKey *ecies.PrivateKey // ecdhe-random remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk + + rbuf readBuffer + wbuf writeBuffer } // RLPx v4 handshake auth (defined in EIP-8). type authMsgV4 struct { - gotPlain bool // whether read packet had plain format. - Signature [sigLen]byte InitiatorPubkey [pubLen]byte Nonce [shaLen]byte @@ -370,17 +407,16 @@ type authRespV4 struct { Rest []rlp.RawValue `rlp:"tail"` } -// receiverEncHandshake negotiates a session token on conn. +// runRecipient negotiates a session token on conn. // it should be called on the listening side of the connection. // // prv is the local client's private key. -func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) { +func (h *handshakeState) runRecipient(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) { authMsg := new(authMsgV4) - authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn) + authPacket, err := h.readMsg(authMsg, prv, conn) if err != nil { return s, err } - h := new(encHandshake) if err := h.handleAuthMsg(authMsg, prv); err != nil { return s, err } @@ -389,22 +425,18 @@ func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, if err != nil { return s, err } - var authRespPacket []byte - if authMsg.gotPlain { - authRespPacket, err = authRespMsg.sealPlain(h) - } else { - authRespPacket, err = sealEIP8(authRespMsg, h) - } + authRespPacket, err := h.sealEIP8(authRespMsg) if err != nil { return s, err } if _, err = conn.Write(authRespPacket); err != nil { return s, err } + return h.secrets(authPacket, authRespPacket) } -func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { +func (h *handshakeState) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { // Import the remote identity. rpub, err := importPublicKey(msg.InitiatorPubkey[:]) if err != nil { @@ -438,7 +470,7 @@ func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) erro // secrets is called after the handshake is completed. // It extracts the connection secrets from the handshake values. -func (h *encHandshake) secrets(auth, authResp []byte) (Secrets, error) { +func (h *handshakeState) secrets(auth, authResp []byte) (Secrets, error) { ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen) if err != nil { return Secrets{}, err @@ -471,21 +503,23 @@ func (h *encHandshake) secrets(auth, authResp []byte) (Secrets, error) { // staticSharedSecret returns the static shared secret, the result // of key agreement between the local and remote static node key. -func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { +func (h *handshakeState) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen) } -// initiatorEncHandshake negotiates a session token on conn. +// runInitiator negotiates a session token on conn. // it should be called on the dialing side of the connection. // // prv is the local client's private key. -func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) { - h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)} +func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) { + h.initiator = true + h.remote = ecies.ImportECDSAPublic(remote) + authMsg, err := h.makeAuthMsg(prv) if err != nil { return s, err } - authPacket, err := sealEIP8(authMsg, h) + authPacket, err := h.sealEIP8(authMsg) if err != nil { return s, err } @@ -495,18 +529,19 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ec } authRespMsg := new(authRespV4) - authRespPacket, err := readHandshakeMsg(authRespMsg, encAuthRespLen, prv, conn) + authRespPacket, err := h.readMsg(authRespMsg, prv, conn) if err != nil { return s, err } if err := h.handleAuthResp(authRespMsg); err != nil { return s, err } + return h.secrets(authPacket, authRespPacket) } // makeAuthMsg creates the initiator handshake message. -func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { +func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { // Generate random initiator nonce. h.initNonce = make([]byte, shaLen) _, err := rand.Read(h.initNonce) @@ -538,13 +573,13 @@ func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) { return msg, nil } -func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) { +func (h *handshakeState) handleAuthResp(msg *authRespV4) (err error) { h.respNonce = msg.Nonce[:] h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:]) return err } -func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) { +func (h *handshakeState) makeAuthResp() (msg *authRespV4, err error) { // Generate random nonce. h.respNonce = make([]byte, shaLen) if _, err = rand.Read(h.respNonce); err != nil { @@ -558,81 +593,53 @@ func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) { return msg, nil } -func (msg *authMsgV4) decodePlain(input []byte) { - n := copy(msg.Signature[:], input) - n += shaLen // skip sha3(initiator-ephemeral-pubk) - n += copy(msg.InitiatorPubkey[:], input[n:]) - copy(msg.Nonce[:], input[n:]) - msg.Version = 4 - msg.gotPlain = true -} +// readMsg reads an encrypted handshake message, decoding it into msg. +func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) { + h.rbuf.reset() + h.rbuf.grow(512) -func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) { - buf := make([]byte, authRespLen) - n := copy(buf, msg.RandomPubkey[:]) - copy(buf[n:], msg.Nonce[:]) - return ecies.Encrypt(rand.Reader, hs.remote, buf, nil, nil) -} + // Read the size prefix. + prefix, err := h.rbuf.read(r, 2) + if err != nil { + return nil, err + } + size := binary.BigEndian.Uint16(prefix) -func (msg *authRespV4) decodePlain(input []byte) { - n := copy(msg.RandomPubkey[:], input) - copy(msg.Nonce[:], input[n:]) - msg.Version = 4 + // Read the handshake packet. + packet, err := h.rbuf.read(r, int(size)) + if err != nil { + return nil, err + } + dec, err := ecies.ImportECDSA(prv).Decrypt(packet, nil, prefix) + if err != nil { + return nil, err + } + // Can't use rlp.DecodeBytes here because it rejects + // trailing data (forward-compatibility). + s := rlp.NewStream(bytes.NewReader(dec), 0) + err = s.Decode(msg) + return h.rbuf.data[:len(prefix)+len(packet)], err } -var padSpace = make([]byte, 300) +// sealEIP8 encrypts a handshake message. +func (h *handshakeState) sealEIP8(msg interface{}) ([]byte, error) { + h.wbuf.reset() -func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) { - buf := new(bytes.Buffer) - if err := rlp.Encode(buf, msg); err != nil { + // Write the message plaintext. + if err := rlp.Encode(&h.wbuf, msg); err != nil { return nil, err } - // pad with random amount of data. the amount needs to be at least 100 bytes to make + // Pad with random amount of data. the amount needs to be at least 100 bytes to make // the message distinguishable from pre-EIP-8 handshakes. - pad := padSpace[:mrand.Intn(len(padSpace)-100)+100] - buf.Write(pad) + h.wbuf.appendZero(mrand.Intn(100) + 100) + prefix := make([]byte, 2) - binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead)) + binary.BigEndian.PutUint16(prefix, uint16(len(h.wbuf.data)+eciesOverhead)) - enc, err := ecies.Encrypt(rand.Reader, h.remote, buf.Bytes(), nil, prefix) + enc, err := ecies.Encrypt(rand.Reader, h.remote, h.wbuf.data, nil, prefix) return append(prefix, enc...), err } -type plainDecoder interface { - decodePlain([]byte) -} - -func readHandshakeMsg(msg plainDecoder, plainSize int, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) { - buf := make([]byte, plainSize) - if _, err := io.ReadFull(r, buf); err != nil { - return buf, err - } - // Attempt decoding pre-EIP-8 "plain" format. - key := ecies.ImportECDSA(prv) - if dec, err := key.Decrypt(buf, nil, nil); err == nil { - msg.decodePlain(dec) - return buf, nil - } - // Could be EIP-8 format, try that. - prefix := buf[:2] - size := binary.BigEndian.Uint16(prefix) - if size < uint16(plainSize) { - return buf, fmt.Errorf("size underflow, need at least %d bytes", plainSize) - } - buf = append(buf, make([]byte, size-uint16(plainSize)+2)...) - if _, err := io.ReadFull(r, buf[plainSize:]); err != nil { - return buf, err - } - dec, err := key.Decrypt(buf[2:], nil, prefix) - if err != nil { - return buf, err - } - // Can't use rlp.DecodeBytes here because it rejects - // trailing data (forward-compatibility). - s := rlp.NewStream(bytes.NewReader(dec), 0) - return buf, s.Decode(msg) -} - // importPublicKey unmarshals 512 bit public keys. func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) { var pubKey65 []byte diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go index 127a018164..28759f2b49 100644 --- a/p2p/rlpx/rlpx_test.go +++ b/p2p/rlpx/rlpx_test.go @@ -22,6 +22,7 @@ import ( "encoding/hex" "fmt" "io" + "math/rand" "net" "reflect" "strings" @@ -30,6 +31,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" + "github.com/ethereum/go-ethereum/p2p/simulations/pipes" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" ) @@ -124,7 +126,7 @@ func TestFrameReadWrite(t *testing.T) { IngressMAC: hash, EgressMAC: hash, }) - h := conn.handshake + h := conn.session golden := unhex(` 00828ddae471818bb0bfa6b551d1cb42 @@ -166,27 +168,11 @@ func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) } type handshakeAuthTest struct { input string - isPlain bool wantVersion uint wantRest []rlp.RawValue } var eip8HandshakeAuthTests = []handshakeAuthTest{ - // (Auth₁) RLPx v4 plain encoding - { - input: ` - 048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf - 913150cfc777ce0ce4af2758bf4810235f6e6ceccfee1acc6b22c005e9e3a49d6448610a58e98744 - ba3ac0399e82692d67c1f58849050b3024e21a52c9d3b01d871ff5f210817912773e610443a9ef14 - 2e91cdba0bd77b5fdf0769b05671fc35f83d83e4d3b0b000c6b2a1b1bba89e0fc51bf4e460df3105 - c444f14be226458940d6061c296350937ffd5e3acaceeaaefd3c6f74be8e23e0f45163cc7ebd7622 - 0f0128410fd05250273156d548a414444ae2f7dea4dfca2d43c057adb701a715bf59f6fb66b2d1d2 - 0f2c703f851cbf5ac47396d9ca65b6260bd141ac4d53e2de585a73d1750780db4c9ee4cd4d225173 - a4592ee77e2bd94d0be3691f3b406f9bba9b591fc63facc016bfa8 - `, - isPlain: true, - wantVersion: 4, - }, // (Auth₂) EIP-8 encoding { input: ` @@ -233,18 +219,6 @@ type handshakeAckTest struct { } var eip8HandshakeRespTests = []handshakeAckTest{ - // (Ack₁) RLPx v4 plain encoding - { - input: ` - 049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662 - b0ff2c08e9006d5a11a278b1b3331e5aaabf0a32f01281b6f4ede0e09a2d5f585b26513cb794d963 - 5a57563921c04a9090b4f14ee42be1a5461049af4ea7a7f49bf4c97a352d39c8d02ee4acc416388c - 1c66cec761d2bc1c72da6ba143477f049c9d2dde846c252c111b904f630ac98e51609b3b1f58168d - dca6505b7196532e5f85b259a20c45e1979491683fee108e9660edbf38f3add489ae73e3dda2c71b - d1497113d5c755e942d1 - `, - wantVersion: 4, - }, // (Ack₂) EIP-8 encoding { input: ` @@ -287,10 +261,13 @@ var eip8HandshakeRespTests = []handshakeAckTest{ }, } +var ( + keyA, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + keyB, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") +) + func TestHandshakeForwardCompatibility(t *testing.T) { var ( - keyA, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - keyB, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") pubA = crypto.FromECDSAPub(&keyA.PublicKey)[1:] pubB = crypto.FromECDSAPub(&keyB.PublicKey)[1:] ephA, _ = crypto.HexToECDSA("869d6ecf5211f1cc60418a13b9d870b22959d0c16f02bec714c960dd2298a32d") @@ -304,7 +281,7 @@ func TestHandshakeForwardCompatibility(t *testing.T) { _ = authSignature ) makeAuth := func(test handshakeAuthTest) *authMsgV4 { - msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest, gotPlain: test.isPlain} + msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest} copy(msg.Signature[:], authSignature) copy(msg.InitiatorPubkey[:], pubA) copy(msg.Nonce[:], nonceA) @@ -319,9 +296,10 @@ func TestHandshakeForwardCompatibility(t *testing.T) { // check auth msg parsing for _, test := range eip8HandshakeAuthTests { + var h handshakeState r := bytes.NewReader(unhex(test.input)) msg := new(authMsgV4) - ciphertext, err := readHandshakeMsg(msg, encAuthMsgLen, keyB, r) + ciphertext, err := h.readMsg(msg, keyB, r) if err != nil { t.Errorf("error for input %x:\n %v", unhex(test.input), err) continue @@ -337,10 +315,11 @@ func TestHandshakeForwardCompatibility(t *testing.T) { // check auth resp parsing for _, test := range eip8HandshakeRespTests { + var h handshakeState input := unhex(test.input) r := bytes.NewReader(input) msg := new(authRespV4) - ciphertext, err := readHandshakeMsg(msg, encAuthRespLen, keyA, r) + ciphertext, err := h.readMsg(msg, keyA, r) if err != nil { t.Errorf("error for input %x:\n %v", input, err) continue @@ -356,14 +335,14 @@ func TestHandshakeForwardCompatibility(t *testing.T) { // check derivation for (Auth₂, Ack₂) on recipient side var ( - hs = &encHandshake{ + hs = &handshakeState{ initiator: false, respNonce: nonceB, randomPrivKey: ecies.ImportECDSA(ephB), } - authCiphertext = unhex(eip8HandshakeAuthTests[1].input) - authRespCiphertext = unhex(eip8HandshakeRespTests[1].input) - authMsg = makeAuth(eip8HandshakeAuthTests[1]) + authCiphertext = unhex(eip8HandshakeAuthTests[0].input) + authRespCiphertext = unhex(eip8HandshakeRespTests[0].input) + authMsg = makeAuth(eip8HandshakeAuthTests[0]) wantAES = unhex("80e8632c05fed6fc2a13b0f8d31a3cf645366239170ea067065aba8e28bac487") wantMAC = unhex("2ea74ec5dae199227dff1af715362700e989d889d7a493cb0639691efb8e5f98") wantFooIngressHash = unhex("0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5") @@ -388,6 +367,74 @@ func TestHandshakeForwardCompatibility(t *testing.T) { } } +func BenchmarkHandshakeRead(b *testing.B) { + var input = unhex(eip8HandshakeAuthTests[0].input) + + for i := 0; i < b.N; i++ { + var ( + h handshakeState + r = bytes.NewReader(input) + msg = new(authMsgV4) + ) + if _, err := h.readMsg(msg, keyB, r); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkThroughput(b *testing.B) { + pipe1, pipe2, err := pipes.TCPPipe() + if err != nil { + b.Fatal(err) + } + + var ( + conn1, conn2 = NewConn(pipe1, nil), NewConn(pipe2, &keyA.PublicKey) + handshakeDone = make(chan error, 1) + msgdata = make([]byte, 1024) + rand = rand.New(rand.NewSource(1337)) + ) + rand.Read(msgdata) + + // Server side. + go func() { + defer conn1.Close() + // Perform handshake. + _, err := conn1.Handshake(keyA) + handshakeDone <- err + if err != nil { + return + } + conn1.SetSnappy(true) + // Keep sending messages until connection closed. + for { + if _, err := conn1.Write(0, msgdata); err != nil { + return + } + } + }() + + // Set up client side. + defer conn2.Close() + if _, err := conn2.Handshake(keyB); err != nil { + b.Fatal("client handshake error:", err) + } + conn2.SetSnappy(true) + if err := <-handshakeDone; err != nil { + b.Fatal("server hanshake error:", err) + } + + // Read N messages. + b.SetBytes(int64(len(msgdata))) + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _, _, err := conn2.Read() + if err != nil { + b.Fatal("read error:", err) + } + } +} + func unhex(str string) []byte { r := strings.NewReplacer("\t", "", " ", "", "\n", "") b, err := hex.DecodeString(r.Replace(str)) diff --git a/p2p/transport.go b/p2p/transport.go index 3f1cd7d64f..d594259866 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -25,6 +25,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p/rlpx" @@ -62,6 +63,10 @@ func (t *rlpxTransport) ReadMsg() (Msg, error) { t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout)) code, data, wireSize, err := t.conn.Read() if err == nil { + // Protocol messages are dispatched to subprotocol handlers asynchronously, + // but package rlpx may reuse the returned 'data' buffer on the next call + // to Read. Copy the message data to avoid this being an issue. + data = common.CopyBytes(data) msg = Msg{ ReceivedAt: time.Now(), Code: code, diff --git a/rlp/raw.go b/rlp/raw.go index 3071e99cab..f355efc144 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -34,6 +34,14 @@ func ListSize(contentSize uint64) uint64 { return uint64(headsize(contentSize)) + contentSize } +// IntSize returns the encoded size of the integer x. +func IntSize(x uint64) int { + if x < 0x80 { + return 1 + } + return 1 + intsize(x) +} + // Split returns the content of first RLP value and any // bytes after the value as subslices of b. func Split(b []byte) (k Kind, content, rest []byte, err error) { diff --git a/rlp/raw_test.go b/rlp/raw_test.go index c976c4f734..185e269d07 100644 --- a/rlp/raw_test.go +++ b/rlp/raw_test.go @@ -263,6 +263,12 @@ func TestAppendUint64(t *testing.T) { if !bytes.Equal(x, unhex(test.output)) { t.Errorf("AppendUint64(%v, %d): got %x, want %s", test.slice, test.input, x, test.output) } + + // Check that IntSize returns the appended size. + length := len(x) - len(test.slice) + if s := IntSize(test.input); s != length { + t.Errorf("IntSize(%d): got %d, want %d", test.input, s, length) + } } } From d836ad141ef4842be494cd5156fc6bd25a13e463 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Thu, 27 May 2021 11:57:49 +0200 Subject: [PATCH 582/948] cmd/devp2p/internal/ethtest: add block hash announcement test (#22535) --- cmd/devp2p/internal/ethtest/helpers.go | 103 +++++++++++++++++++++++++ cmd/devp2p/internal/ethtest/suite.go | 20 +++++ 2 files changed, 123 insertions(+) diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go index d99376124d..a9a213f337 100644 --- a/cmd/devp2p/internal/ethtest/helpers.go +++ b/cmd/devp2p/internal/ethtest/helpers.go @@ -633,3 +633,106 @@ func (s *Suite) maliciousStatus(conn *Conn) error { return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg)) } } + +func (s *Suite) hashAnnounce(isEth66 bool) error { + // create connections + sendConn, recvConn, err := s.createSendAndRecvConns(isEth66) + if err != nil { + return fmt.Errorf("failed to create connections: %v", err) + } + defer sendConn.Close() + defer recvConn.Close() + if err := sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + if err := recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) + } + // create NewBlockHashes announcement + nextBlock := s.fullChain.blocks[s.chain.Len()] + newBlockHash := &NewBlockHashes{ + {Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()}, + } + + if err := sendConn.Write(newBlockHash); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } + if isEth66 { + // expect GetBlockHeaders request, and respond + id, msg := sendConn.Read66() + switch msg := msg.(type) { + case GetBlockHeaders: + blockHeaderReq := msg + if blockHeaderReq.Amount != 1 { + return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) + } + if blockHeaderReq.Origin.Hash != nextBlock.Hash() { + return fmt.Errorf("unexpected block header requested: %v", pretty.Sdump(blockHeaderReq)) + } + resp := ð.BlockHeadersPacket66{ + RequestId: id, + BlockHeadersPacket: eth.BlockHeadersPacket{ + nextBlock.Header(), + }, + } + if err := sendConn.Write66(resp, BlockHeaders{}.Code()); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } + default: + return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) + } + } else { + // expect GetBlockHeaders request, and respond + switch msg := sendConn.Read().(type) { + case *GetBlockHeaders: + blockHeaderReq := *msg + if blockHeaderReq.Amount != 1 { + return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) + } + if blockHeaderReq.Origin.Hash != nextBlock.Hash() { + return fmt.Errorf("unexpected block header requested: %v", pretty.Sdump(blockHeaderReq)) + } + if err := sendConn.Write(&BlockHeaders{nextBlock.Header()}); err != nil { + return fmt.Errorf("failed to write to connection: %v", err) + } + default: + return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) + } + } + // wait for block announcement + msg := recvConn.readAndServe(s.chain, timeout) + switch msg := msg.(type) { + case *NewBlockHashes: + hashes := *msg + if len(hashes) != 1 { + return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes)) + } + if nextBlock.Hash() != hashes[0].Hash { + return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(), + hashes[0].Hash) + } + case *NewBlock: + // node should only propagate NewBlock without having requested the body if the body is empty + nextBlockBody := nextBlock.Body() + if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 { + return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg)) + } + if msg.Block.Hash() != nextBlock.Hash() { + return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v", + nextBlock.Hash(), msg.Block.Hash()) + } + // check to make sure header matches header that was sent to the node + if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) { + return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header()) + } + default: + return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) + } + // confirm node imported block + if err := s.waitForBlockImport(recvConn, nextBlock, isEth66); err != nil { + return fmt.Errorf("error waiting for node to import new block: %v", err) + } + // update the chain + s.chain.blocks = append(s.chain.blocks, nextBlock) + return nil +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index ad832dddd2..491bcda7e7 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -70,6 +70,8 @@ func (s *Suite) AllEthTests() []utesting.Test { {Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66}, {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, {Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66}, + {Name: "TestBlockHashAnnounce", Fn: s.TestBlockHashAnnounce}, + {Name: "TestBlockHashAnnounce66", Fn: s.TestBlockHashAnnounce66}, // malicious handshakes + status {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, @@ -93,6 +95,7 @@ func (s *Suite) EthTests() []utesting.Test { {Name: "TestBroadcast", Fn: s.TestBroadcast}, {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, + {Name: "TestBlockHashAnnounce", Fn: s.TestBlockHashAnnounce}, {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, {Name: "TestTransaction", Fn: s.TestTransaction}, @@ -112,6 +115,7 @@ func (s *Suite) Eth66Tests() []utesting.Test { {Name: "TestBroadcast66", Fn: s.TestBroadcast66}, {Name: "TestLargeAnnounce66", Fn: s.TestLargeAnnounce66}, {Name: "TestOldAnnounce66", Fn: s.TestOldAnnounce66}, + {Name: "TestBlockHashAnnounce66", Fn: s.TestBlockHashAnnounce66}, {Name: "TestMaliciousHandshake66", Fn: s.TestMaliciousHandshake66}, {Name: "TestMaliciousStatus66", Fn: s.TestMaliciousStatus66}, {Name: "TestTransaction66", Fn: s.TestTransaction66}, @@ -580,6 +584,22 @@ func (s *Suite) TestOldAnnounce66(t *utesting.T) { } } +// TestBlockHashAnnounce sends a new block hash announcement and expects +// the node to perform a `GetBlockHeaders` request. +func (s *Suite) TestBlockHashAnnounce(t *utesting.T) { + if err := s.hashAnnounce(eth65); err != nil { + t.Fatalf("block hash announcement failed: %v", err) + } +} + +// TestBlockHashAnnounce66 sends a new block hash announcement and expects +// the node to perform a `GetBlockHeaders` request. +func (s *Suite) TestBlockHashAnnounce66(t *utesting.T) { + if err := s.hashAnnounce(eth66); err != nil { + t.Fatalf("block hash announcement failed: %v", err) + } +} + // TestMaliciousHandshake tries to send malicious data during the handshake. func (s *Suite) TestMaliciousHandshake(t *utesting.T) { if err := s.maliciousHandshakes(t, eth65); err != nil { From 0703ef62d388eafa177540ff722c3a0871c4979d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 27 May 2021 13:30:25 +0200 Subject: [PATCH 583/948] crypto/secp256k1: fix undefined behavior in BitCurve.Add (#22621) This commit changes the behavior of BitCurve.Add to be more inline with btcd. It fixes two different bugs: 1) When adding a point at infinity to another point, the other point should be returned. While this is undefined behavior, it is better to be more inline with the go standard library. Thus (0,0) + (a, b) = (a,b) 2) Adding the same point to itself produced the point at infinity. This is incorrect, now doubleJacobian is used to correctly calculate it. Thus (a,b) + (a,b) == 2* (a,b) and not (0,0) anymore. The change also adds a differential fuzzer for Add, testing it against btcd. Co-authored-by: Felix Lange --- crypto/secp256k1/curve.go | 53 +++++------------------- crypto/secp256k1/panic_cb.go | 2 + crypto/secp256k1/scalar_mult_cgo.go | 56 ++++++++++++++++++++++++++ crypto/secp256k1/scalar_mult_nocgo.go | 13 ++++++ crypto/secp256k1/secp256.go | 2 + oss-fuzz.sh | 1 + tests/fuzzers/secp256k1/secp_fuzzer.go | 50 +++++++++++++++++++++++ tests/fuzzers/secp256k1/secp_test.go | 8 ++++ 8 files changed, 143 insertions(+), 42 deletions(-) create mode 100644 crypto/secp256k1/scalar_mult_cgo.go create mode 100644 crypto/secp256k1/scalar_mult_nocgo.go create mode 100644 tests/fuzzers/secp256k1/secp_fuzzer.go create mode 100644 tests/fuzzers/secp256k1/secp_test.go diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index 8f83cccad9..fa1b199a34 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -35,15 +35,8 @@ package secp256k1 import ( "crypto/elliptic" "math/big" - "unsafe" ) -/* -#include "libsecp256k1/include/secp256k1.h" -extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar); -*/ -import "C" - const ( // number of bits in a big.Word wordBits = 32 << (uint64(^big.Word(0)) >> 63) @@ -133,7 +126,18 @@ func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big. // Add returns the sum of (x1,y1) and (x2,y2) func (BitCurve *BitCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) { + // If one point is at infinity, return the other point. + // Adding the point at infinity to any point will preserve the other point. + if x1.Sign() == 0 && y1.Sign() == 0 { + return x2, y2 + } + if x2.Sign() == 0 && y2.Sign() == 0 { + return x1, y1 + } z := new(big.Int).SetInt64(1) + if x1.Cmp(x2) == 0 && y1.Cmp(y2) == 0 { + return BitCurve.affineFromJacobian(BitCurve.doubleJacobian(x1, y1, z)) + } return BitCurve.affineFromJacobian(BitCurve.addJacobian(x1, y1, z, x2, y2, z)) } @@ -242,41 +246,6 @@ func (BitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int, return x3, y3, z3 } -func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { - // Ensure scalar is exactly 32 bytes. We pad always, even if - // scalar is 32 bytes long, to avoid a timing side channel. - if len(scalar) > 32 { - panic("can't handle scalars > 256 bits") - } - // NOTE: potential timing issue - padded := make([]byte, 32) - copy(padded[32-len(scalar):], scalar) - scalar = padded - - // Do the multiplication in C, updating point. - point := make([]byte, 64) - readBits(Bx, point[:32]) - readBits(By, point[32:]) - - pointPtr := (*C.uchar)(unsafe.Pointer(&point[0])) - scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0])) - res := C.secp256k1_ext_scalar_mul(context, pointPtr, scalarPtr) - - // Unpack the result and clear temporaries. - x := new(big.Int).SetBytes(point[:32]) - y := new(big.Int).SetBytes(point[32:]) - for i := range point { - point[i] = 0 - } - for i := range padded { - scalar[i] = 0 - } - if res != 1 { - return nil, nil - } - return x, y -} - // ScalarBaseMult returns k*G, where G is the base point of the group and k is // an integer in big-endian form. func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) { diff --git a/crypto/secp256k1/panic_cb.go b/crypto/secp256k1/panic_cb.go index 6d59a1d247..262846fd89 100644 --- a/crypto/secp256k1/panic_cb.go +++ b/crypto/secp256k1/panic_cb.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found in // the LICENSE file. +// +build !gofuzz cgo + package secp256k1 import "C" diff --git a/crypto/secp256k1/scalar_mult_cgo.go b/crypto/secp256k1/scalar_mult_cgo.go new file mode 100644 index 0000000000..34998ad1a4 --- /dev/null +++ b/crypto/secp256k1/scalar_mult_cgo.go @@ -0,0 +1,56 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build !gofuzz cgo + +package secp256k1 + +import ( + "math/big" + "unsafe" +) + +/* + +#include "libsecp256k1/include/secp256k1.h" + +extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar); + +*/ +import "C" + +func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { + // Ensure scalar is exactly 32 bytes. We pad always, even if + // scalar is 32 bytes long, to avoid a timing side channel. + if len(scalar) > 32 { + panic("can't handle scalars > 256 bits") + } + // NOTE: potential timing issue + padded := make([]byte, 32) + copy(padded[32-len(scalar):], scalar) + scalar = padded + + // Do the multiplication in C, updating point. + point := make([]byte, 64) + readBits(Bx, point[:32]) + readBits(By, point[32:]) + + pointPtr := (*C.uchar)(unsafe.Pointer(&point[0])) + scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0])) + res := C.secp256k1_ext_scalar_mul(context, pointPtr, scalarPtr) + + // Unpack the result and clear temporaries. + x := new(big.Int).SetBytes(point[:32]) + y := new(big.Int).SetBytes(point[32:]) + for i := range point { + point[i] = 0 + } + for i := range padded { + scalar[i] = 0 + } + if res != 1 { + return nil, nil + } + return x, y +} diff --git a/crypto/secp256k1/scalar_mult_nocgo.go b/crypto/secp256k1/scalar_mult_nocgo.go new file mode 100644 index 0000000000..55756b5be8 --- /dev/null +++ b/crypto/secp256k1/scalar_mult_nocgo.go @@ -0,0 +1,13 @@ +// Copyright 2015 Jeffrey Wilcke, Felix Lange, Gustav Simonsson. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build gofuzz !cgo + +package secp256k1 + +import "math/big" + +func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) { + panic("ScalarMult is not available when secp256k1 is built without cgo") +} diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index 9a7c06d7ce..9e942ac6fe 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be found in // the LICENSE file. +// +build !gofuzz cgo + // Package secp256k1 wraps the bitcoin secp256k1 C library. package secp256k1 diff --git a/oss-fuzz.sh b/oss-fuzz.sh index a9bac03257..081a8e1d5a 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -102,6 +102,7 @@ compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi compile_fuzzer tests/fuzzers/les Fuzz fuzzLes +compile_fuzzer tests/fuzzers/secp265k1 Fuzz fuzzSecp256k1 compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add diff --git a/tests/fuzzers/secp256k1/secp_fuzzer.go b/tests/fuzzers/secp256k1/secp_fuzzer.go new file mode 100644 index 0000000000..53845b6433 --- /dev/null +++ b/tests/fuzzers/secp256k1/secp_fuzzer.go @@ -0,0 +1,50 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// build +gofuzz + +package secp256k1 + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcec" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + fuzz "github.com/google/gofuzz" +) + +func Fuzz(input []byte) int { + var ( + fuzzer = fuzz.NewFromGoFuzz(input) + curveA = secp256k1.S256() + curveB = btcec.S256() + dataP1 []byte + dataP2 []byte + ) + // first point + fuzzer.Fuzz(&dataP1) + x1, y1 := curveB.ScalarBaseMult(dataP1) + // second point + fuzzer.Fuzz(&dataP2) + x2, y2 := curveB.ScalarBaseMult(dataP2) + resAX, resAY := curveA.Add(x1, y1, x2, y2) + resBX, resBY := curveB.Add(x1, y1, x2, y2) + if resAX.Cmp(resBX) != 0 || resAY.Cmp(resBY) != 0 { + fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2) + panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY)) + } + return 0 +} diff --git a/tests/fuzzers/secp256k1/secp_test.go b/tests/fuzzers/secp256k1/secp_test.go new file mode 100644 index 0000000000..76bae87086 --- /dev/null +++ b/tests/fuzzers/secp256k1/secp_test.go @@ -0,0 +1,8 @@ +package secp256k1 + +import "testing" + +func TestFuzzer(t *testing.T) { + test := "00000000N0000000/R00000000000000000U0000S0000000mkhP000000000000000U" + Fuzz([]byte(test)) +} From 427175153c0e33a7f640f86bf2ce4c97f03ede72 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 27 May 2021 18:43:55 +0200 Subject: [PATCH 584/948] p2p/msgrate: return capacity as integer, clamp to max uint32 (#22943) * p2p/msgrate: return capacity as integer * eth/protocols/snap: remove conversions * p2p/msgrate: add overflow test * p2p/msgrate: make the capacity overflow test actually overflow * p2p/msgrate: clamp capacity to max int32 * p2p/msgrate: fix min/max confusion --- eth/downloader/peer.go | 48 +++++++++++++++++++------------------ eth/protocols/snap/sync.go | 30 +++++++++++------------ p2p/msgrate/msgrate.go | 14 ++++++++--- p2p/msgrate/msgrate_test.go | 28 ++++++++++++++++++++++ 4 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 p2p/msgrate/msgrate_test.go diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index b9c7716941..066a366315 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -21,7 +21,6 @@ package downloader import ( "errors" - "math" "math/big" "sort" "sync" @@ -232,7 +231,7 @@ func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) // HeaderCapacity retrieves the peers header download allowance based on its // previously discovered throughput. func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { - cap := int(math.Ceil(p.rates.Capacity(eth.BlockHeadersMsg, targetRTT))) + cap := p.rates.Capacity(eth.BlockHeadersMsg, targetRTT) if cap > MaxHeaderFetch { cap = MaxHeaderFetch } @@ -242,7 +241,7 @@ func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { // BlockCapacity retrieves the peers block download allowance based on its // previously discovered throughput. func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int { - cap := int(math.Ceil(p.rates.Capacity(eth.BlockBodiesMsg, targetRTT))) + cap := p.rates.Capacity(eth.BlockBodiesMsg, targetRTT) if cap > MaxBlockFetch { cap = MaxBlockFetch } @@ -252,7 +251,7 @@ func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int { // ReceiptCapacity retrieves the peers receipt download allowance based on its // previously discovered throughput. func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { - cap := int(math.Ceil(p.rates.Capacity(eth.ReceiptsMsg, targetRTT))) + cap := p.rates.Capacity(eth.ReceiptsMsg, targetRTT) if cap > MaxReceiptFetch { cap = MaxReceiptFetch } @@ -262,7 +261,7 @@ func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { // NodeDataCapacity retrieves the peers state download allowance based on its // previously discovered throughput. func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int { - cap := int(math.Ceil(p.rates.Capacity(eth.NodeDataMsg, targetRTT))) + cap := p.rates.Capacity(eth.NodeDataMsg, targetRTT) if cap > MaxStateFetch { cap = MaxStateFetch } @@ -411,7 +410,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { idle := func(p *peerConnection) bool { return atomic.LoadInt32(&p.headerIdle) == 0 } - throughput := func(p *peerConnection) float64 { + throughput := func(p *peerConnection) int { return p.rates.Capacity(eth.BlockHeadersMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) @@ -423,7 +422,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { idle := func(p *peerConnection) bool { return atomic.LoadInt32(&p.blockIdle) == 0 } - throughput := func(p *peerConnection) float64 { + throughput := func(p *peerConnection) int { return p.rates.Capacity(eth.BlockBodiesMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) @@ -435,7 +434,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { idle := func(p *peerConnection) bool { return atomic.LoadInt32(&p.receiptIdle) == 0 } - throughput := func(p *peerConnection) float64 { + throughput := func(p *peerConnection) int { return p.rates.Capacity(eth.ReceiptsMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) @@ -447,7 +446,7 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { idle := func(p *peerConnection) bool { return atomic.LoadInt32(&p.stateIdle) == 0 } - throughput := func(p *peerConnection) float64 { + throughput := func(p *peerConnection) int { return p.rates.Capacity(eth.NodeDataMsg, time.Second) } return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput) @@ -455,45 +454,48 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { // idlePeers retrieves a flat list of all currently idle peers satisfying the // protocol version constraints, using the provided function to check idleness. -// The resulting set of peers are sorted by their measure throughput. -func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) { +// The resulting set of peers are sorted by their capacity. +func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, capacity func(*peerConnection) int) ([]*peerConnection, int) { ps.lock.RLock() defer ps.lock.RUnlock() - idle, total := make([]*peerConnection, 0, len(ps.peers)), 0 - tps := make([]float64, 0, len(ps.peers)) + var ( + total = 0 + idle = make([]*peerConnection, 0, len(ps.peers)) + tps = make([]int, 0, len(ps.peers)) + ) for _, p := range ps.peers { if p.version >= minProtocol && p.version <= maxProtocol { if idleCheck(p) { idle = append(idle, p) - tps = append(tps, throughput(p)) + tps = append(tps, capacity(p)) } total++ } } + // And sort them - sortPeers := &peerThroughputSort{idle, tps} + sortPeers := &peerCapacitySort{idle, tps} sort.Sort(sortPeers) return sortPeers.p, total } -// peerThroughputSort implements the Sort interface, and allows for -// sorting a set of peers by their throughput -// The sorted data is with the _highest_ throughput first -type peerThroughputSort struct { +// peerCapacitySort implements sort.Interface. +// It sorts peer connections by capacity (descending). +type peerCapacitySort struct { p []*peerConnection - tp []float64 + tp []int } -func (ps *peerThroughputSort) Len() int { +func (ps *peerCapacitySort) Len() int { return len(ps.p) } -func (ps *peerThroughputSort) Less(i, j int) bool { +func (ps *peerCapacitySort) Less(i, j int) bool { return ps.tp[i] > ps.tp[j] } -func (ps *peerThroughputSort) Swap(i, j int) { +func (ps *peerCapacitySort) Swap(i, j int) { ps.p[i], ps.p[j] = ps.p[j], ps.p[i] ps.tp[i], ps.tp[j] = ps.tp[j], ps.tp[i] } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index c57fcd71f6..646df03887 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -861,7 +861,7 @@ func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *ac // Sort the peers by download capacity to use faster ones if many available idlers := &capacitySort{ ids: make([]string, 0, len(s.accountIdlers)), - caps: make([]float64, 0, len(s.accountIdlers)), + caps: make([]int, 0, len(s.accountIdlers)), } targetTTL := s.rates.TargetTimeout() for id := range s.accountIdlers { @@ -958,7 +958,7 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * // Sort the peers by download capacity to use faster ones if many available idlers := &capacitySort{ ids: make([]string, 0, len(s.bytecodeIdlers)), - caps: make([]float64, 0, len(s.bytecodeIdlers)), + caps: make([]int, 0, len(s.bytecodeIdlers)), } targetTTL := s.rates.TargetTimeout() for id := range s.bytecodeIdlers { @@ -1012,11 +1012,11 @@ func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan * if cap > maxCodeRequestCount { cap = maxCodeRequestCount } - hashes := make([]common.Hash, 0, int(cap)) + hashes := make([]common.Hash, 0, cap) for hash := range task.codeTasks { delete(task.codeTasks, hash) hashes = append(hashes, hash) - if len(hashes) >= int(cap) { + if len(hashes) >= cap { break } } @@ -1061,7 +1061,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st // Sort the peers by download capacity to use faster ones if many available idlers := &capacitySort{ ids: make([]string, 0, len(s.storageIdlers)), - caps: make([]float64, 0, len(s.storageIdlers)), + caps: make([]int, 0, len(s.storageIdlers)), } targetTTL := s.rates.TargetTimeout() for id := range s.storageIdlers { @@ -1120,7 +1120,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st if cap < minRequestSize { // Don't bother with peers below a bare minimum performance cap = minRequestSize } - storageSets := int(cap / 1024) + storageSets := cap / 1024 var ( accounts = make([]common.Hash, 0, storageSets) @@ -1217,7 +1217,7 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai // Sort the peers by download capacity to use faster ones if many available idlers := &capacitySort{ ids: make([]string, 0, len(s.trienodeHealIdlers)), - caps: make([]float64, 0, len(s.trienodeHealIdlers)), + caps: make([]int, 0, len(s.trienodeHealIdlers)), } targetTTL := s.rates.TargetTimeout() for id := range s.trienodeHealIdlers { @@ -1284,9 +1284,9 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai cap = maxTrieRequestCount } var ( - hashes = make([]common.Hash, 0, int(cap)) - paths = make([]trie.SyncPath, 0, int(cap)) - pathsets = make([]TrieNodePathSet, 0, int(cap)) + hashes = make([]common.Hash, 0, cap) + paths = make([]trie.SyncPath, 0, cap) + pathsets = make([]TrieNodePathSet, 0, cap) ) for hash, pathset := range s.healer.trieTasks { delete(s.healer.trieTasks, hash) @@ -1295,7 +1295,7 @@ func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fai paths = append(paths, pathset) pathsets = append(pathsets, [][]byte(pathset)) // TODO(karalabe): group requests by account hash - if len(hashes) >= int(cap) { + if len(hashes) >= cap { break } } @@ -1341,7 +1341,7 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai // Sort the peers by download capacity to use faster ones if many available idlers := &capacitySort{ ids: make([]string, 0, len(s.bytecodeHealIdlers)), - caps: make([]float64, 0, len(s.bytecodeHealIdlers)), + caps: make([]int, 0, len(s.bytecodeHealIdlers)), } targetTTL := s.rates.TargetTimeout() for id := range s.bytecodeHealIdlers { @@ -1407,12 +1407,12 @@ func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fai if cap > maxCodeRequestCount { cap = maxCodeRequestCount } - hashes := make([]common.Hash, 0, int(cap)) + hashes := make([]common.Hash, 0, cap) for hash := range s.healer.codeTasks { delete(s.healer.codeTasks, hash) hashes = append(hashes, hash) - if len(hashes) >= int(cap) { + if len(hashes) >= cap { break } } @@ -2852,7 +2852,7 @@ func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) { // of highest capacity being at the front. type capacitySort struct { ids []string - caps []float64 + caps []int } func (s *capacitySort) Len() int { diff --git a/p2p/msgrate/msgrate.go b/p2p/msgrate/msgrate.go index 7cd172c566..5bfa27b433 100644 --- a/p2p/msgrate/msgrate.go +++ b/p2p/msgrate/msgrate.go @@ -19,6 +19,7 @@ package msgrate import ( "errors" + "math" "sort" "sync" "time" @@ -162,7 +163,7 @@ func NewTracker(caps map[uint64]float64, rtt time.Duration) *Tracker { // the load proportionally to the requested items, so fetching a bit more might // still take the same RTT. By forcefully overshooting by a small amount, we can // avoid locking into a lower-that-real capacity. -func (t *Tracker) Capacity(kind uint64, targetRTT time.Duration) float64 { +func (t *Tracker) Capacity(kind uint64, targetRTT time.Duration) int { t.lock.RLock() defer t.lock.RUnlock() @@ -171,7 +172,14 @@ func (t *Tracker) Capacity(kind uint64, targetRTT time.Duration) float64 { // Return an overestimation to force the peer out of a stuck minima, adding // +1 in case the item count is too low for the overestimator to dent - return 1 + capacityOverestimation*throughput + return roundCapacity(1 + capacityOverestimation*throughput) +} + +// roundCapacity gives the integer value of a capacity. +// The result fits int32, and is guaranteed to be positive. +func roundCapacity(cap float64) int { + const maxInt32 = float64(1<<31 - 1) + return int(math.Min(maxInt32, math.Max(1, math.Ceil(cap)))) } // Update modifies the peer's capacity values for a specific data type with a new @@ -435,7 +443,7 @@ func (t *Trackers) detune() { // Capacity is a helper function to access a specific tracker without having to // track it explicitly outside. -func (t *Trackers) Capacity(id string, kind uint64, targetRTT time.Duration) float64 { +func (t *Trackers) Capacity(id string, kind uint64, targetRTT time.Duration) int { t.lock.RLock() defer t.lock.RUnlock() diff --git a/p2p/msgrate/msgrate_test.go b/p2p/msgrate/msgrate_test.go new file mode 100644 index 0000000000..a5c8dd0518 --- /dev/null +++ b/p2p/msgrate/msgrate_test.go @@ -0,0 +1,28 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package msgrate + +import "testing" + +func TestCapacityOverflow(t *testing.T) { + tracker := NewTracker(nil, 1) + tracker.Update(1, 1, 100000) + cap := tracker.Capacity(1, 10000000) + if int32(cap) < 0 { + t.Fatalf("Negative: %v", int32(cap)) + } +} From 04cb5e2be30e1aa6c0cca657e10fec7239a6334f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 27 May 2021 18:45:13 +0200 Subject: [PATCH 585/948] cmd/puppeth: remove outdated mist support (#22940) --- cmd/puppeth/module_dashboard.go | 64 +--------- cmd/puppeth/module_wallet.go | 201 -------------------------------- cmd/puppeth/wizard_dashboard.go | 8 +- cmd/puppeth/wizard_netstats.go | 8 -- cmd/puppeth/wizard_network.go | 9 +- cmd/puppeth/wizard_wallet.go | 113 ------------------ 6 files changed, 5 insertions(+), 398 deletions(-) delete mode 100644 cmd/puppeth/module_wallet.go delete mode 100644 cmd/puppeth/wizard_wallet.go diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go index a76ee19a06..b238af0316 100644 --- a/cmd/puppeth/module_dashboard.go +++ b/cmd/puppeth/module_dashboard.go @@ -80,12 +80,10 @@ var dashboardContent = `

8CN1t#~qBXNA2 z1xpuVBlOcae4xAAvt4_ z$hMU(mLlzj7m+~jGq~RGXKhTA#Jf8;O@TF87$BD%|F?$SivPaGH_<=&8}s*k&%I*7 zoojn$(LcU}7x@tX6MlwVXGlU3^m~LXP2XaS=QlN#8G#Ln6;H)Ue#So(gt~3{l27*Q zUZ@#sY0nvE>}{C6wQd=(z2MK;&6O_&1SpEl)?&`V6=jHxnPL6Hrne9aY7zKf z7Q%1#JIUM4vynt-7|kAK;=?|~E{S>O-qAw5r*kcD9XoHkEh=8W1 z2Ycj^X*EGEM1UeI)tq9438P7 znl?LC&fIy)l;uL5C83>Pfm$-;j{l}yk>tB=!M~i3*7}{Qk44Qt%qjv;m;evJ#{sC~ zg9djgQY+ai!TuD`JN(iLKQ7@}f3jzg8Nz0zaEY}rKAQ_3PAwl4Qil4+?jA&y zD0+_b;X%a|^Xh-qe%n-o`a=QmD{3dSS()!4^*pDJ9CF(I8zB0!$=Tu;q)l`FPE{l zmk`(gj?P|+_4k2eCefgFklk_l@(;cc<^F%MoA@8XAo^)v^>O2>b>Ui?!sT6biFwR zC$(fm$E99NUSl=x0{X;lA;mL zw|dhMJ4R36jhX2T)t9o75kUKZp8Zf*rG-t;j~RctpRdO{mLAwwZP75RGe6;J`p2J0 zm3(Da14M# z=1g>6+_)hYE3fY{N~({K(0AU^=r$eszegRr?QL@lf?$@xT_eq&Lk+KGeMLICM_`~D ztv2{J?&M@wl~X)jU2915pm)l;r??|iL({s{^F z;Cog#tgt@p`lSG%`w9&K_FkhgCl~O%PwUn@pTmza_RpHp7sb7iS;?`vv>O7^5IuNp z@CH&qUTOi)Cy*cezdkG`=d9Ma7oEhqJG2eqi8w{cdFR%M!FQukfo4>HR=^`1u@O>N zb2ofi-={C$f{qB8R)Vr$KO}YItoWbhNo1f4-Pcn6`kN>}uf%+mtP*=H1yiG2HCrHB z`-XU|zf>~>LW6G_1l-zM)9K3ZK6n#X8VWwnF1TZRuhl=TW1kBu!K*R+Mt?qyeX?J> z{f1HJ1;TQR8+6}-p!M$XF(yD=u?h9oNx0_tQg1&|34VhRzH@{p(`r*?B+H|0vrsGzRE&@M-Zk+e5)@jKGWzk!tbu88p6V=CiX73 z{6g26{eKCGV!ai%?|$4@?t<1<<(Af;Jmk3$Nw_pw~S%JOlLbX6}11@Fz(2gFb%Z~oPx8a4j9LVM2juZH`kA z#PG6#1$9$*yWqXxn@_;B3H23MN+@M>Z#%OGEouYi0$EX@*We~8b^;a^r~f4p6!-@^ ztV!-1_zK12nzE`SM<-PGs<{(6XODj!#$4`$ALVF^ARnp}jqfgoR8 z-zhENyEc(Ng#*e{5ahju_DsbSdg#N{Sd$d z#))o`jt5+5i|Q0z0VH4sh*$w!<@G=ixaq4XH{6EzlI?R?lip3oYxp|hk!G+pxyLqH zUP8J+$GOmHvJv@%4o zNXECT0)EZqdu^0OXm(-=m+As4>YwgH?aMUN0hUJ>fIHv;{rb<`2iKW(ku(Bx98|WH z(hsjuPgXv0&t3K6evraI;Jpw9x|N|2`*>vSRr-unb+&NF#G>vq&2nw5ag#wxz`L|< z8i5XyAHZTENpF<|adL3tBle=#PzY;O`sE<3RY>rh{4%k`Y?}UQ?v*D+*<7{@(EqF? z!EKp-=4CiFF7rV}`ME$bcr#8szrU@LmwXK0* zrcm^aWSoK2Y&MoZod-sSArj88?BfG6ZK+nox1GH|42`WINfYn*UIqJPPcIS!Aw9}UGS zjKm-;pW?3LTuhHR-Seh@XqGxc2VEA&wjOA8wLr!+gw>$HMhv!&-P&rkVNWqviD}*7 zJLK~OT$X?3^g-Td&|ktj-ca}sc^zxgT$WT8b8%SM)BxTvKl$N{)A>6~jp~1+ddBKH z7eh{Y%y|{Q$+6Q_@G;bSA5ujZb(p@>_xhjF^fJFh^y&m883P(8 zUn)}a6?MAUCi6L^-91Jsv7k0MI;$}b(Hi}Y>dtc0AoMiHUz5r2G_U zy)&MY7JSkQ&B%}d8=%YqJeh?G36^}3v?`lYn~vd^(w4yy178=dZ{0$g)gv-`NCmb2 z!tRofjw?5FW;{LxW=vn0V^VDz$&4Lf?qE923!%l$vpF)%(#wB6%{FBPUb!{dl9KaN zh@l0`^+`tWieFvuUfQYC%<5Kgw=~M^Qr(+i1I3_}4(Rn=0OCjn@+(4Uyg>Y5C7(mp z!L8($I}tUWDo;`b(T3O{zBSU0&-a^1L$&o6ZuN`mwYQ6DSAO-hsWW{YSl`laopEvC>?Zuk@j0eEI)B@4SiI z1x%D@XN4Q!`*87iySBLjd-SiU?P-@LAi%yxCfym)!f04OpcyA9Of5CR1t`=+mC#cw z4m29{eELivaMDpEcBf!js{#&ZV5ip9sUZ*s?7qPl?kM$RD!D_jFsItdQ5rZ(I$rdc z5YK1OmcVHZ{IA;{kn7Opm~f^M!za@vwNaZBt)Abu&9h#wiM}XHao5qDLe}WidnA!N_!bOF3uhX$B3`PF9T4umKwSRs{+%VF=^&Nid|x{hSe=o|~et#bzxX_Lq{ z_EK&>*Vz0R2&DR0D^rrtZT#BUg;@~{XmRlHGq-zgCIwF&q*|{4(F2Rmp93Hg=dO-BlOV5@1nCJN4$b_Y67Yclfpt zH&a*HTkEvGU471Qwp$yatsz+6S1@t$2MZ$FewAQ>H#%MNoaU_j_vXGO_j`=2Y}4Ci@d#C6eL;Op!KduhElBc8l(brQgw zA&rC6z8?i%Py}Ozp34Ndp#@-nx}u?E4HmI>X8KY8HnxwWu}8~u=lBQ*`s}Jf%!F&K z>{+t@q3^HNK8`Ftd^BnbegE+jpPyV7ss*w?fp6g3h}EJHped1i!1z`YjqC_g{5a2e zQmGmKhe3}_rp+=1TbaoC30nO8^C)!2D%`Q$-7NY`L`@ zS#k-6f8+mLA>KWS3BGPlE~Tu>EaqmqNq{wrGizsH zceEakfdJ)pV=H8gYr6q*9?>eP_}I-{XH6*th;+gq0Fe z8$VxuoHU+awXh~hzfmoxG-Re6_lrVf3-o(@rdxx6Lx2Iv^K=h)R>uBqd*UWle!k~R z-mtw24#cc=-_>bX|IMJ&E&S+YOafKU=>oXIqeKsunOdns{2U6koTho4Jl>u{le?8zynXX*^>q|j^{;LTEYAPu$)d_&D?1&?#qC42F z#~oKHz!|FAB6<(X;Q0j;!hjIe5pCiN?t&h~Ob&VY86Ub8E4t=p($C+o#)#l3vrJF`mI0AH%a@I2V3ac4$?}?IFNt!;r0q7l0%$7nrK7Hl#_TJ zO-k8G{@eUS*>b!Wp-L4>CjG5;da(fV-KyrSK>?+H!1ZpB;W)ZfO`Hh&@riISG|M*> zEQ^dfhN#_!#q+xCiZ1zf#`qAEvcVI;@iy~j9Hb0gmr#n;hqJeoVzn_3|H&{P@;2ngMBMj$j7Z{>GDw(w>*EGPgMzhTQyZbs@ ze(X&#iRluDYbj4&W_;0A;T#?Q@$a_{VY4e*#4$@0yD9d=?{J_GhrFLGY6k2@H5QR1 zIK5P2OQ5{5Wu44e9#|ap`5ln)-bu4tgG8Bs0C#l%3NV~t3SMSGx#9j3c5<~2V_P^e@+>4@TC(HXzachqc1 zAr}~4kH}qHA1ms&?Hbrl*a5)aUHfPD1MHd$YR8Z)Y%$3#dvJ!Vzk`nBqQhi-v$FnJ z?j&TD6C_bUB^b&v@WGN;B8PT@a{iOdUaCY~j4^R@gc!M@6XB#(H}$t4IJk8lY%qSD zC(1n!O|KMw&1`v#_Ud+t2)qzrpNkods^d=tdSzM*@#(^Fz%KzG{vd>J;vI#>zuoQ) z^>ISW61t+5l~^=>Hg33hQoLL9Qwucs6%0iNW9EHQpX_CCok1mck36qu)E{qAi?)LL zSjE)AU}uCPtHHG4fKK6=jggX-Nx`UWq9LN7f&G{xVKq8FzaX*HxVq=Ie=BGpK*kCO zdP?2=712y)%^dbBZjVVK#bt!dTbRgt?VMQZxu$i_gZBQH8*D&t^ML^W-FkG+VSwJp z3Xv6L$UH*^E5870FNP5m@_9PB&s z)MUI0Tg8s*RCMwbDMCbH0EIWDnn!NGHJvIiEOY{v0+rebrQQnI00^VamcpVl3rf}9MYf}k3*DB_sp_)w%E(i{ERV@}= zZ#1y|>21@h(v}p~6Rzf6fY5X(t`*{G_u4DM4L7zs@UjYVcl{A6K;X!Oyo|ggDV}d{ zwW68(dOmvZI@^;4US*TuTdY%uEYqB4tTkM3<0BhD5Cn>5O%bn85|&j!!B-<|LZD>b zJ(;xG<6QfI=Gg;7>^t@)&x1yrS<4isoOx}~6g4KozEt3aCG;~fcMe;-C-kq#H&Bp? z2jsG}z8*t_{LIj$8gip4>pdN1Yww!OVSTxd3pZnOWYoF7zeypx3uR7DUKJkmGEq>b zg;l3uh?X~%_6L=T5!oFm z`epJj2qdT}6mu^myYtOcDFAIh;H>%aM{>Ny&{lCwCEBZn@fdN7X+{_23LlXTnl`8sH zWuz~sZ|B0L%AfL^7EiJ9a4q!z-UbeYLb3R{07`^1%B8$=$!7!#jZSh&o8Pdw&zs7& zSGNWvv7fyop{W7Fte9-Q7+}KxC;Wo!%ao`D zuEP5GHg-T5+p%UEVs+Ke9dYu-yIEV4bga&T*iw5phq zY7;iJ%I@s+`RCi@+aG;<_mfDgk6d=hW=h2yTJv0aQ(qUq=C<8yP}?+D7u}3}{bHaf zf1~nC?TH2NJ-b+6$LiwY2FDoOiL^D6Gu9eS|K{v>J{hq)L@~F%G4Q<84kSeRE`_Zw z6}vD;Mq7FMf@rJW{PCNsQnuKsxpQd4i$rrrYLWb>`A0tUHJS7fFUf%(FGB1p+Gkx- zE0UQIqWy%=KND-X9_LQJXZG!wp6`$%Q@=Yf{8fl1OK8w+Yo%H|82?igXAuh;Se0T& zjagDD8ChhmSL5yyEsefwFmwaawygP<`WwYWG?-@RX-5BYDG9p?O| zy5VRZhAteasY0TLh&;CA-|t+8MqVQ|Rrot9dDi4N1E5}aCE&E0n4g7+jaMmEVCjZA4_1p zCAZ-63(WsKuR#>Yz=GBo>Q&;r+j7tUKHVlu4}Djt+sU0(w5)L4?B-9?XFp*`Sw2@P zQCQ~OLh%*+kg-RIU)N9=!nAdgsMnUM8(J`IWvJ-t%DW><+*2PD6I70 z-B0p=#6!bayi&A6y!w@N0|5tRZb3W%frO2jNMxE_eDQa;jbU+j6)TS(uq{kaYk-MV zwOEJNTjCtiOw7I5VPU&z+n(T(`AxRy=hu+fA2=Y9TwZLWW=Z__;LKnRYO4AHT!>Ze zP$WwAN{!Z4-_0!MPxy59?0P=nCcpH=;Z4Pke4xw4_`spk=Q%q4jocyAGiEp>u0r%hb^Y`x3< z;bFw?_u%|Ck&&ed*1vJB+{MpN+q;e8R{POz`6T48<8r+{p^`=Duv)uAA<$I;ot`p| zOyzMKNqp|K?aGr)n~2?gt|TT6k53*k28PZXq~*n<6t+?cee=;Aqh^ z^j>}o1Ibez|F3ys(H%=|$N8Dx-oal}3kWP`h6J%!t#muD;wOGlbkyh32#wENl=W?| z!lD1g-0emYOVI-N>BG5$JJgBvrkBEYp*Spi<$iNF$=pUdb<3v4M7)V|#TUy8A=Ha` z*4NKMa9s}O*o0-DeZ(KGpO((EJ6AQ<%9C+qf5$#H?*;~M-FCf4DqAnau2*I_xQ2%M z(dAs9%(^|LE{gckTq}(ZzBYYrF3eaJebHk(U?=T$zKaP*-3L}MJNbz%_I~0Qxz7gsL;1_n)b*Uq zm6ZQG<+D?)E)Gtx{-0L78C@Q}3vX!$?y2C|uFjX#@#3xQ!eOIQr7{NY?^RTDMbUUA zh(j)b-^4_iU9%>lg?AnwGetzdfx*bzIKE@fh(NM!W&+bWiZEbPjUjwQU0nQDneWW6 zytncSb)O>8pJKiug-Yq3I&*(hZh9cZmIKK9`wFR0OtQW3IEGOTw+wiVY;G0F+qCNR0oF-d~1c|FNY_kE4P~C%^`H10j zs2LO(Vz06-W?z_ag_CEhcF38oT06lqx9Mq6$9~evUKhPn8|vMz&KiNd*GK3Cl#F%G zmm1Dxi_yl6D}`l?bCRMA&<>^Z7lA4Xy;lqXc8s9wG6)}S1q4ouoXoA362CNKRDHSZ zzS+8x6>bY@*SH8T5q4(OY9xXK$$DVFioK|(5s0DLh)k@N-F7+7HjmJAs%;ys;aSt6 zFQ{`~0Yu9X>Mu5EcTsYAMK1Et6TvZkU%!$ioTqQ|daYu_rjpyS%X4e(*fL3zeMdJL z>mB0(jz~tgl2J>x51e^sVp(O4t#Gjv&FXoq*Q=TFI5Xv{tXRPw6$BE!~^%iUc0a(vAcuhL=iOraYmKS`=e>iw%4<|lE6;Ar0 zlT-v`O`#6E3t63!as zRSW=kwMjN;nd>eIOAH{{Z|COTr1TZJ(G~>)u{0O-$zII^H{7`I zZY;(IboAywn{depSsn&n?XZ%=F>e_?9|^S$}6)Fz%1Zrj^2*`3_C{g z^=wKq$8do<9sQz<>YhXip?M}E>nAM38Mq)`BQ^J>niz_xMrLK#o;w1++_C1`O*^7A zsJXDTPu1 zk>AG;NFa`!pbfXQZ*NvXf?t&=yPtpk2*(PQikM@3alGl9G0z5g2@oFEZSNSYQH23b znYP;I&u(If5ay+5I|gBOLn(|N8hO7K;u@cP(02RZK_GBI?<*_@u3A;GIK}09G62nj z85*V9e7~bzl65W58P66QSw{1!Kt<@AW|yJ0Z@UtrJJt6x6!`||qp??r$!;v@h<~?P zEYmb>M6pInk1vJ)0q$aTDNX!Yy1(f|mChWWltq|@GZ)Za-6-NStn)O|C#Q+>F6~Kc zpsxq<8CJhl&`%Mykw_P&G2*}|Z7lA2LWS?VnS7-(myuEjZ?Nm6Xu#R2H^BO~)i)(s z`;kU=A+es>sCu?G@JZ8V)vEGmg~QQ6DhUd*rZ8L=OL?N`iu4eM#$T@~qfy~;+K<+~ zNJY@JL}5k}OUs-8gTS0M2&P0>kk@bCO5QvSTzuT@hu;o}R!0t)K%h$GJ_28fc#S}K zWY-tv?^EVd;U=e-AWMHrd-M+ERyg*_uv??9&|fy5wL1-n{74^l>?Se%KT|UCu*?!= zChIj6*@9x%epV!7$Qo&xX>i36bbL;bSV1{N^d$fyv;gERBK=UJlRPs7QU`y`v7HI) zoKkv;7>mZ~VC=B_%jdvjBW@;lAEv+sh~7ajyX{Sp6{zd1fyGJ|IDh9CaXQAAI;fv> zbaoSaMoj|L!Ec0d=(Yw1IZTRD8|<+D>S2-oDw(Cg-rnbKVXa;WjNgHIcVVb+vgUik zy?=)2hw3h?u^(8iMhW*;_@k~045qPun~#=%MfbTaTN`v_kjrwP2@=FxU9_qhWZ#3_mS7)4rmV1K~dyn5l zN9H6~>njXH{|j4x6~ZT##2=o zkHZ#BJ;V9Au)r>sAQb}~=S9>F<^8VGknuOkfl~c(ER=ElQ~d&66vw#uH)vT4V)fcp z6yi6|Ztg_v`cjq6Pha~{2G1w7?mADnO+v_~dJ02?g>Pe``UHyP?ZLe!314snAY@x4mQ1o#28!9kF`?UbH?}gcHo5G_g)8FMHf@|;-OKfnHEK5zUbJ=3x<_5 zm~{CbD3KBgbL^o-n1a#lJhb){c>4*U&Ew5X`)eNg)Z>DeiqKa)P%$1JZd0705P>Jk zC+dJr48=tG;4XIVTi%-=U(z~l-EzvY6O?#ygP#5>?Ia7Pq@W{1a0kxbZdiPHK!jKi zDK~4Qg1g}&Y-Aj1;V>5v#NS3kU3+VnB5Acs;gRV|FlvwPrB3>O0<`h0BI}IZrC)ob zf4|~+h-UnoooQ6=SmoAPz8}}2!W#JlENEmQ+H11PzOiddYPC6RVj^Y#cjd<`fj?e_ zm;(D6{)hi-L%mvn88%TD?ASV9dZdPNbM=O518Dfh<9rYc0^g|Ow5P#Yf5Vw4*;D_y+0)RZFcs+|g+HjC}lOVre(UDJ|lThfTxjXr!nXBpex- zu!v(PXDSuGt)&~KWrg?QC;cf&2Ew~RM_YrAhWvosoL>!poTIalspm4pDN2%e1EaYr zg5&Tz;BfEj=3DFh;7iR)q$2PvP6>l+f^TEmDNP?ny3d0CvL2-qO>DA@E!VvYW4Pp) z+L0Dy3h`!YOcbWtPfJ05Eh#u}mS4?eF~mZ$MK(pFUEE4w?<)QNreLoyf_|DPaD(3N z>zrzP-?;R4%Zlb|@v--hT(OllliSj`khjw~nSY1?kUiE+_46<4EIyh&{c<-&5KL(Ci$CUNhr49{>3hvnaNzh zAhH#}aY-Z z%#d=QI@uP^JvO%#s*XBVz?rnez6(2m13*_q5CRUybBiB4)-7qXr^I#&vV6a`hgk>Z zQ^_pCA^$FeoBGa_>d5mjJkR})2ViesU?+1wy+7K_FLO;SZr~9|`l#njwKR(|nN4Uw z=oJX|#jT|MgWx^YfgYwZ%@O(>r+tIgQ2()x$Ov`qZ_*6Cx;F0>0A7KpVt;yrVREUK z8o@ntD~qxhNO(p_4E9^SsCUE(TyFVv6;s#(^#`2z*DXeU73i_+9%QB;uXvyl^J zK(n~szUQ%Mg4S2@UnQ*aXNq9n03Z$dF7#^wUvB_9B~^#O8qg(g7<;e@YrTO`t*2QM zJNLh|@2~_ZG2r%mi?k6qyxU|e*iW_xK-wyMp%hgV?V7{<3 z00o{U#T>%7Sqljd_4_iRnC8Tdym$Sfyg0}w3)DLzR-6Tp#0Y6H=xZHBJ^_!3@${In z+vYgRC9g8xYwLt8gSt|FFz>iP(i$LB1pE00Z~1L&InIcTLQNC52Ytg~iw)Eu4tYq- zDhhY0?}8)|mnEec%LBZ|MbjH=x+=e2W_S!>-{jekrK&0To8o= zCEUw-M-?uixtD!nU}4gzX!&eHPs9p8~$agEgnKu!m7+n!M)j%qhr2U9k3JHqCuO`ukbrdddgUoaavSuApB zFIHN8*)WB3Le@5rG)p7qgQuJ1)WP=_lvJq_^_72Zc}}~kH=<>sW`)g)x+sv#F$2HH zi!Ex=_n%q{hQ}8!TqBCd<9jeQ>mnp2O9%NaM3_<$mE?r8kN= zLuahlYIo#x$nmLUrnjDa!4SK*T;{jb3MIO5Yw&w2DfqT6_Z}arxSi_h(C48!%ep|8 z;bfzzbkF?hr}6F%#*;g)j&mOs^I7d7;?K5Cy|*WC?*35nm}h)EQ?5_ixIVuXedzAy zAJoFtMJhA>H)18j$pV1Is?Py@aUaFMmb@6x=$|r3q3j~lVh!`D&^RAGeZ?61Kc9+eJ<476@7VwTRpxyI5fJX?z4a-v92{}f$^LylQR=2!9rfPwlCEH=V@0!R z#O&YZYxBVdLd-c_7(O6@1JEr|{NcMbWqxi_>qvX_(VNI&263;Hvjvr>TZ zp}&ilakRQqH}lapu6f(ON3Zr0JB!;nu0|j$=&e%;cw4>mQ-;VlkIzKzDLi&A7v3f# z9HzNiWaI)VDVTx0LMG1m^>2EkelYvvtaEk8Xx?oXcAntRRwfXZtP_jnxdE#JR%%JcHZApgY4iwt} z1v&NpfN^V2D56s4E+0hL0Yy+|_v3ipuo60&Xj1HD*`26HntpAXZ%VZ41mPM;A7g7_ zm-?>0Af!ZhxbN7!eSF*h8lBOEKWN}A&wk;EH2^Twbx1Nu&NG%Cyct9&?In~+OiLqx z=69{R{H1&N!|0#Qdrp>q8%q*K%@ z8wd1GP7Deg^n6DD=RQ5US7iljEn8B9-p+GWC|f^siW%Z@3B+Ba5Gv zkJejBnvwQ(mNeoOq)Olxud#jUhLk|@L~3T`_b%;4Eno$M2tdYZ?H94K?>_7k0V<+Z z%QDN&zb6Lyw$eG}mI>E|LgRl38IvKrp}a;BBz_#6EB?S`dOAH{l)J3O%78T#&_Kg+G480m0$ z2*<>_CfzAyM=m5<(=CTlWUPFi5Dc{P5UQu#61S=08_dxNElT~5*Gz{mWM_#EaHy!X znUGR26OL+KQKt-EM_Py&W$l#c+A9@60jv;SnHTP6uf;N+s!5l>=U)&}6MI^1T5rqa z=?FjPG%No9l;7?rnL|SmI7VMdg%6uzo(}TPB`Za^RO)MxMS60Kh{uG8WC!#h6b@U= zGNG^uwZB3;`bcL}2fylv9nib%HLx3PCja_K6hY`Uq=wPqd3FvR0Kg!p8)%dw&x-e( zIQGe$^-&rM?tDzmOk9h8c={^KMD_lZ;JbmY0_3(nt(bW3=1(|m4Fmz*z zs;%6&u`Jfn?_R=>!))_Z3Ed}=O7MYQL4(9PAhl%+A*&-_&z`9`AP_Ir-uDzrz)NSmb3 z4tDv;saHP#d#PsZ`ho#nV>VonbdqlX9p*AbcrwJxIGo4HF&#V9O?Lp>q5q8H`sDT+ zp2AZwTdk>NpgVCUi!wqR4ey-`tFQ>*&1mb@Kx&9zQ0^FUwZ&ZX=7h=50PEYkVO7a@K9juYeMe5oM^4^y}X6kc5-i6CGGux z3b9I~2V*TSzp#<8o^!iWY|e;IP`Xeihd8B1eq#BilnBZNvtJWaSlwm-pv;!`9 zcNx1AHK>aS`TcZ*&vt9@NjmM&Lz30f>1bxY^>+oW*`LIg2Y!!u8SI;i7)sysEk%*6 z-tYtWB{IOk68}Zxn4S;X@xZYNoZzG+>TK*Fp7lwrz~i_?#qX&PqRpAk78Eo0(Rkd} zlvRwt6=1szkx~kP)GJQfq9Ks_=~gY5ymQK9^>ze5CL5cpD*irJKlyVcK80KrI-#ehwz?R=A+pv=#B-Z4WNy(#bqa!gO$B5aK$R{dyGznw(q&1^-L9zbGJ+wK78IWv43`;;!N4yi7zys^!7SM2J{$U*YD0<~(A>ZznCI#7627 zvWyAB9cZ!v;ZyqhhOw+1Nqfoj3>7sHKd7YHZdh4Ycfy*idI{VN92xGoLH84-6Wn!I zyS_p*ksWxd6$W?wp?&XC#MHfrvx5;5K5AEa@~u zl6j)Q&0D26r}3n+Sly6XQWBG2kWMOFyu_NF;LH}biSATq59xj?S7eItU~6HY6-mzysC`5L-$Z=TyfZ=E4>(kG9+7QXy>V8C&FRo_}_92_@< zGRgzc_Y%~!?)-{P?rc{Fi&2nD7Wfp_Vm^!Am(^z&P&t&L?)QTI##5e3Ill^XU)?q% zk=vM{WFhrIs18uc`pd1#k5D`QHK>QS$HBbj#a{aTD_`JxQ_ zFE{|8ATSQVa>?zmmS?D|7buf48Y0T7WVY5s^yg%Y&1~ zhj|ARsK17hD8%A;J`5hMHoGInxN81Q;nw=;V-IFwqXBa~K%Tbs=O<&~=x4*DC@rV8 z8*v5*fF4$7x%=J{D2Q^ueZer|3OEzh^aCf<)Bu-c+VP$!B;qkq60 zdY3|vMl-0k2eHca@2p&2-@Aed?qLa1;KC8TewfKkrs|g(-h?QU9QiRCY&~uhg4mM|b+4LE*VdT1YlJ3z?V#f!f3Fh7=A(8j-cSKg-*cp+a zCf@B;z!UEw2S;;t_!e6D3Ymp_O{PkP#GfA8&l)i7a(wTo0Nep>tBDFh)vEr9ZR$YY zu8%!=e*in079XcyX0Jh9!yrT0;z$;=D{c@Ud2b^5G<(x;v%2yBnmtKkyfvYdhP$dpq0NHt{i5^+(s%3nsjG zZw`6$W|j|-punxlvYh;pn$FI9CnNo=5xF5-;Cx!-SIMTE&W6burtStXqBRg;SMYda z`*S&P|K&ea2GM-9>mg&W`=KGz_5 zP7bw<%?jtat3%+=9lgAVNRqdOp$pJZ(KNr)&ZU9^GN-(~$|nBnrw?LUSI1q$WE@Ff ze%s8Ahh-`B-+q+^_p}|X5dXArnK{Qz4iBFcN=AEz@o@Kazt^o-(sq*G6Et1)hdgq< z?YdJ6DtLihlmDjfZE^OERB_B{z@lI8zxt0Mc`fNel8!@?yp0YcrEtO(9-f&t95(YP zeP>UXyQj3H4{MZt9i~jRHxRf;b34ZZ64k#QSUo_N`($CskV>ApA06k8oz(3dQuMeD zBErB?5Sk*Lyx@mQGMSO`!*&dQvGqthlP{V}*n}k$57o2At{*~s=nIr=UoBcaK~q;Fj1n^j;j`rgj|w$K)D=2!2YLf1k1yFv6ZfTwbqX0X~k#wu%p1YN1ZEoA^l&z zmgkq3S;wyKk2U0hR$N$!aG?I47w(!r=3ur04&tW(0Jn*sQvRL{TAPvS;qohS%Ey0< zSD_vNe?tX_Yebk0^?K#6^GBW;(!JcqC6_0Ul4iA(WZ^KW=hpr8|NeK*l`><-=tKtw zZTjT_*0sc;wk7-7ubjVQfRz6{yWRDzh7 zcdd0(b(OCXVgtne$KF#LNNLo%Edn>&+}YW)A9uW~f`J%;RZnpHg9Qn;vyeI3<_WM?mjRu*1FlFk(OVZ!zk(W+-Q~ZZ8~fzlu^0qZ%Rf#LO+z9Wa*>U=6n`zC7Y0;ebN5wj+sEg40h|^L z&42^McKmR6Muqv;CFsooH>^M|`~zGs&sND>QjE#Hja8!@H0OtG&pzW~BUfS%tzPHt zfM;%PEd~AKjaFZ}cl3;I1p&-2Fd$05U0dEvDbme46IlvxY-4|AeH@yqv!GFo=XIF-&@YTva$?rr~sW!yVfGHA8Yt02XQfFI?5*d~voT$rN2d5|K7|DZUQ zmsshF?0LyZXCBpGYKKGFTMf9(_?z;*fO^!x^WVwM(5T@61ZHBaqnb&`UE4#j=5^be zm^W78IV#ES1i=cXksD;D1ORKl)p`qe+^hrnXW~_AYva2&Z^!FtfeF_NY1IenSmtzP zq3$oUCc#%Q(V0+;vx7})(+aU+mPhGrNVrRcL~d3)5ie)0B66J?QM&N@|4x4UlurYI zx@&c7C93eaF6iU2RjZCaZ?s7+T=HwL^t4oGzZDWnL`V(^%)@Js9o|3&M_8=*eXX`w z&Nee|`f0}dOV=;Wa*vg9IX-7i(8i&c5)L$8uIXGy&|hHBn^o;CXUQ-voS?JLerP>a zWnf;(TEFp+7zE-2Zil+4*{lg8+6kJ!nAN$ZXG)A+chyWr!@MOmF174yl-cCk5hEH( zJ`NV{?Yh7U8BDTs-cE9gtLqg1Jv3*e!6c2Y-1bm#Jgl4vyxmPRY z$hE!CwsDNMD34@8Q#gT7{l|a3pHlwO{2mP;p@1aY&SK?S1ho2+Gd2XbrcW1D%zkqZ z5vF|I($QWe?gr7M``joTl#fD8a|%i3#{oiX=G86;7kV~j%o}od1ZisgGMP1A8eUN(qp(aI}{HIL2CoXU5HjixZ+H>Zj?!i=Yl7mceL?^0@4)1xfbv`%8Mvfr#C4s&B_ zz{veaN$#=1Oek>s1XRx;YE@s3%<}rNnqrsUGDj{gzm}=HPJOBGWCKl5G7mvqSFJM( z&y3lHIA!G+0%LWu))b7ccTuypY?=!3M%#i~QMbE}FsFQ^P!%c9w?!djGfAJ!+$-^f z30UW*Pn#o#TTEeX%;Gc(gnj+ZKi5(uVfEw;__(Hrj=itU#viDJc7NQ6%oemko_!Z?bCA8?sZdN~7eg4) zoJuF&jN_EydqC6I7O}!mLPmvHiW*X(Mu_5sT z-f`RfZExtyTdZnf+IQhK5}@Ju5RA)~0xv+4KU}cat6prQQdK+Nasmq9)D#`HmquR& zCC6t8W7r#K6S2W5xvGh)wj4w?v|2&l>X9xTQm-_@kp=)A2TgU!BrQTqpE8Q&Dv@B9 z;gTEaYWU19rVO#snuIGRY4$<#Z+`?KgYcn9EM-HEH`R`JP|aJTR`$fkJK7x;1h?4c zbK)0cTt1pZ3g&F1q?y)>G(&=!_HrY&j`^KGRd@a@dMsn&S@e&h?dTf=(Fii=qW|?n z?TG%Afbrj0Uw9Z9Qw6wd?`BUEOMs0&mcq$VSSGIV1plA5?GQSYF`zsvP5vsIZfp04 z-wc0CsYg3B$VVzs5z7ppa&KU)IS5e z#1_(_griBDJ2cRu!JGaotC=pV52xZwtjN2}nq=ihZ2qwGWVsMzY3B9~F0-k)T@@p@~*(F(h?8q87Lk6RTKt7eh@9A8I1^|mj zk{vVb!-yF*+s+qdrw7ZRg672Q09U$krZU7f-Fkf{;$5Mf+!yBUb^EM(_btl-`S$@r z0v6~`gm@YIGm>pgVZHhpe4OFG%7^4?iJ!iwi)ku^$0WMl)0V)>|C!#E4&@k zxb{e|0ORXFD)hE8_ydxRun-%|3~_MpHWvRYPFdsEbhj`YIW7Nob3C=?0>LoNkVz;H z?Z23$swS-$i}Vd@!6~});p?@2 zjBiU2;J`G5mUkI{YOI+iWj2=s(LzTFBf}%b=3HE7n>gvx zbMhl05X-|zYvMlUmsQgsY?vF;yy4rZ>PVb?WBg}1Vn8)0m!ve3jr%i`C<>M=U4NB@ zGp?jn7dD%9`rC}M5hthfoUOkRlrYNM%3v_MpDU_-GTObROA*099}I(zz4%J~X#syK zDLmbm|6FM*bXg-|>jW`yD;HZEbRcCmDa!s#zP?rn6x^JzGcQ*Jej%!^ZdLz_Jor^o z!GkdXs2F3m-G|LOtNd&>;hEG?`4H>LDvIVBHgt>Py0CQ3iTM7)FJl908HWbFXxVw) zlrJQ3{ddUQBx8?*=R?7^>n5n8J$mu+i+=4sv_?kOUGe=n)*5JDpQUEolh;oBMatUY zXiDO@6dfH*5IvujN@?rB4jOEpbLOn@B<}RP1PlG!@UKOSIMS1RLH5QT%?H_qA zcp1XIyOf*_h&~Jq@TSc&{W*|%H8^E_J@9_z0w3u$EioH61sxDfSv7g^P@{DJI7#K% z<82*}XHpMA<%l^% zGxsUVfJ9;mR=V?tbj}3!xenUrGEfUX*`Iv2SzhWn)n zcQdnE3&C9Z-Tfm(gs-70==2-pKxN7HNR3sik}5(rKmsH53KIGmfC@Oq+zVIDYlL2h zZ}K93j?;^^e}!tAy2Lsql5KK1<;4sKOJHpkKCXtp=KGbZaNJe@ZJvWyOoSQU-G*}y zc=-OMTYc3!JIhW@Ca)b5186JJ5maE&e>U%FnQDj>yUtFfWfQ|vH~!6Uj@!8gFGT}{ zfS6sO_Wq@PDQUXZiQ*(!}416#i8ml(9 z1V0t44cN1~+d>YD4JE)3U|gUZ7Wj%w0!Zxj+1!#=MnFkk=o6 zeY19E*Qh^QPiE1ctQwT4D4VsYqSKv2&LaSCN9O4T^G{}XsC7?R;Jk~2q$P9mQaE)g ze^<3Qo0_s`W?bY|IG7@ZKFzK;8ew`*!iPLbe&`7*+fe9Q?(A$}m4EmWX_eV)k_o%X z#E3Y>pK$5*iFJa00|rAVF~0VhBxC8(2r46KA~rdQ{wy>ALNZoVg6J@p#dOX<9zccE zeS*{N#Q@Z4h2q)C8GIQ;&&e6 znIjsSMjZ0uH2Xl*4I(;3!h@vvu9IfJNhn11FUh=q*hqrK_2bgcp0ZsCzIeVa#QqO? z>ZE8c-lpWo+GV)HZoFn{`EnzVE$Jfcz`?z6h{&)&0$@rUXnY379ivbBN<2w3Pbq&E zzu+HTSO07F6x2M~ zm|euF&`+5{2wCMjkV4(diTDC7cFa1K6(fV5v2ZowGhQBe?0L@}WmPU<7b}_LyYWnyv5kTW`~g(?Ad-h+!4<~k%Y)*mxLd1# zEf|hpx2I3Rznoe~(K`9jn)sn)&;X=ft+;^k7_66881;FmqY;iCB z0wh$_@flE>ViRT~4PLDk=}i*$I?x?01AADRVs!dWo_5jkltjSCgR7HvUw_@4tf}5r zo_%Af?fLGkb{Zx@1{7{z;4Gc=1-k242v%aK7vUrW3U_N?x7Z}-!?hKCPNanFNd=oG z&n;t&Dn!V_IUok>MLQr(1^ zPOx+Ja=ru(-Jb#^ka#Aa!A8v<)kvT$b6Si0P7Kh ze+T^|FT?`$-7W{_#BJRT{=LhoG-Ys{i~0&L~@j!AEdw(2vX1}S1VO?A*adZvj3 z$c8}ls42FYpX*E&O?q#A!*m$8wKd=Ea`3=6K*G@!;h;`;aX2(nMC>uhoeBQ4lKXJgkEhDTjk#Td zBe?PS#}C~3nK--fVC#iz3l-L2!+-#=%A~yx-`&m82eN%DWX|e~*L3OhEI)XmdFk(k zmXY|kTVjNG-Oi<>NA&{uX)qa^L@6txL;>BvJA_qu0rFJ1AAp{go31&UtLBsm=JRx7 z90f$atf| zAF23PSD(M|&^08mjDAwH!_*A;Kx31tZ(%<~iE6w@!6za>^`z%(Mu}LA`A^%;ZxH4T zM1Q1?djy!SS#Muf_qE7Clk+RmGn-Q`ttJg?u5}Rz6yU|2{34iTmZ#S`|E-4IS11W5 zWqd}TOW!rBYK8_0L!UT0i5yuWQY}|WgcJ8WljVkhW08{uyA5{HFR^0h%~cw&$l{nQ8vOyk10f7QET>1_EzNj){ZIHzu$V|B?Nhay#vk)nuT#Uzj3 z;2-hIh^F&Hmq3 zGwVG0K;$d3MOW-Vk}vvJm^TAjmQ@x&fr3X51MhttvLm26_tm5Qm;9{@&S#Iole&wS z5u+L&o@4;AB=82ET)SS2=fA{SP(1Cyh%FwRA(jIy_pr)1Chx>M8|pYm>lh{hDEb?? z9;Fw*$22*fygbRys2<2rxd;5B`-vSC`cfEXjw7#_wZh3-rgRMt?w^)YfhmjnreO*V zUHeoUY+WmtdA3~2=r z%dUNvI$LN{;4T~|TzxI3)0Zer>{)-WK5rVE{6X^^^mn^A(1oxyKrDV5s%`1w>ZmEf ze!-#5yJp*+=!?u0eWie8Z}w1ow{2T*sOT&NV8m34s17nEP&>tLjH0cfsIA4zTC;pi z_$9G>wzRbb6_&>PRy_zSMQj}&;5Q=(wfLKTF5FbhSMRuofHy_^^d}63hAaN7O#3gW zj>xbCurIA;++Jj?8NndK%Xu-D#o)L{CPX8Ty!E*iw{r-_JBBsz0fm3i0WcVwL34S} zGLlp9aia2f@oH?Dblu+@m&<$!tZ<`+h7}0ANX3ge#g@ZNKzAv`DhzdM*CZrqdmwT> z1^G|(Ms`)=DsiX>d3)_2%{S`l^w;r*T8|JqcNJ5VRb6}Nq}AF>uPgpuaC?nk*ir!F z-L}^s;5wdhflu)C1Fu&)E7UJ9UZ7B-y5{(*w4C`KAua22{->m=>tP|QIeeC&3bwTAmyg*uyx$maD?>&`Rf?cZ44Iyd7I2x}oI zlS28=yt_xKR_q8Ku>ke-$CuR?{f%tQ&3dm9nKAg2f42P$5=u}a1hzAOqt=>VN*Z4% zWdFA(O)tx6;BrJv=-wRIKJxl2x%?8~FOApqSH(xn0{)7R&5YfPmdhQZDZC&PV2)%V zs17&_n~Kor>VNe(08YL$hmL#M{#rwoJPnbw>5cX8k{ZQq3hDp-+)qy4eyrlp^zY~` zw2s>>U|hJw>T$vW<-jk$CPHqJufl3;O@v#gmz_e{Mq@pUv=EdF`iseO-z^8;ci3JKUGu`@?qITT!hMBe}@L7EIm=6^(s4 z7na#n55*4Ecox@Iv9b0&`8K23h`zb~RU`Kgbx**1oE#Ge0G79|^Fc3WmJ+#u=lBy8 z_EOKD2a6tlrqrqL7^|Lfs8}t=+1xHe>yGP>+tdIgOiNkO6$Xg&7=yf7tzyBRvE)Hn) z0OOR>vquCJPz|?A1_o$}N2? zexq_hK+8$yM1^?H9hCNTg8RcRi&D|(pMmY62|d#T6eLe+O#JK6b2^9_de$a8CiB91 zq4`4A&NJJ3Au(9D1KF4Z zEbd$p><1x4RK?EBbx(~)9Sa1k93pT5}$jZn=%E1w2C z9kp6UE-^ZH`I_d(|MXv34Uq%Z{T65g*8@u$P;cm0D3UL&JR@UUy=qHu+rtSYFB<6H zvgd-S7e4jqTZ<$0BYCmbzdT;OS0*|+83xib8lI691{MFJKWA#-KM9m4YmP+ z9(#+CGJ;C$Tl`;}b2aBM7+%+Dik!Wn5ouLi@VPx^)9%qdryc?KUQ_yXIV2cPaA)qF z=?is`gHK+GAVEpqh`_@YP?e9uZ+o3U2qz7A`o7P!99xB<~H z)_L`1r|>Us?Lz8IpoL((KtDY01^9twA!p1VZbFAOdiQI6&J2L?eWx*WxUN z1t)z;V67e+P2f`y+o1L`F}3}}^22i~nQ=T{E^n(KVtcv$3yEKVWWKQvu1)!c)y-%h z#^Z&;+U{c%;(v{C%vp~uPMuHylMUS$LV|}@MX@ygOGbaeP#cYzRoW%Cg`;kV4kJ>g zccG#90aGvzBshLYQ;Q(XnT3)nc_Kl}$GX3Al1HpHO_3(fidZwYP6qbE?y$#vyOfQ1;>%yF?u{bkb) za=ysgiTpNJ-W6|t9(*WN;71Bea);K@#|DJ>8XNQ%*A>saOl<@3R!(Q^OPN};vV}V& z%|HG_CZ<8Z<3n-DO^D-uh6i(|wLJbx(c6IuBI8WEpbyrcn@h=K5xcYrV;@WOC0sX# zdbVycx!-6}r0f>I!2YVq09EcsqVS)a5SJPF+A!P#3J&@$ROk?aWL}`aWj-4b72Ss$ zGV}{$P`cE^DjOe13Y-^h4{TV@X}`XGK-CpBHpIml|2zcSF|NHy{$Gb9BEt913S`5o zRg1^n%dNnq!u4f$hp0xvhMt%J7sAUlRy)^{#!_RQXtr@zw;o5%c}bz{{3dW7_%!&P z4gsFPIE?}vQ1v3G4qSQJS(K?hLD^mS z4$B)A!T|f;sE8ft%xW~&@43lih5ctuZ{t(A2I^vkAA%!?gMB`1K5Zc6riIT&L+S+#|=^r4Q8oB5c z$D4EilezG{$vb5h- z?VG|~m?7g}xuzY!hkR0X>)78aFPs&ApFRoHpeK&M?Suuvc>&`AE{FhW(!J9muNhL2 zyxO$EN2#;jrie{>jHi|ca~+%qMrIx)!z%)9P23#&C)f@tl!f{|j}u)nJK`Pay-_K7 zsFC_bQ@t$W3-XYWB3fOD^$WHl*VFV*ra+5Jjg@xf{>L0G+e!tcn%~(9F8KsL@bA~G z7X>^)0ogQR1Kfu_zXA8Et+h!G3>D>pn8qCe(^UimQ9PwqHN3Ye@p4S5ddt@l$eLPO zi~z2;AwKH_Z^p!?R|`moqqgo`jik|RY4%1L52zFO2L0a#c5zMiIz6b(rG$JQwiS39 zn3bqG=G0!LnF{s)3N!(N5c8wZ;K9tX(~=_RmN|jUKb4tG6_SN2`d6{($}SX!S>B~Y z=7^qOOjV!H{jTreh1}7k{hX(3GIEdf)6h3~yP66Swgi=E;MN8_O`4T_DE;6`I1;b4 zhCEbndTg%C)$*ET*o7+Kn)g4ao!r3sv)IMv%3vOB4=T5#NP1%}yS)LlU=3pG)HT+lU#-Zd*&f)4`hj-LXrxgkdqDH1U zSv&Z$-?z_crM{;79-oD#A;HAuxuEPnnVCC57y8Z!tkDX58I%WHRp$l%)l7O2!lzv{ku$#G#1;l$b`8;fLDtl-7AXHl%lFcvn0*JL0?O6Q zl`e{scUpfWQ49xp9-oDcE@6e6wi{{_=FZ{8?0k7Y`idOv3JZh?ejmWOu3mUt`(gbU ztHz(U1jjRYdg?Kpni4*;t;*&O7G)K|8`QXOlLJfxxs&A)G&w46q>T`Ml+g-*z^d5b zLrw;%k%)eokK})=0e*ESUHN)uh8GoJJFvfcohvvOnRoG8;dmySFt}H>iTJKqo>mc^r0c;d!&BZWo|d+)eD{bRr)`_8XVMyc-j-eoi- zk-}V|b`2x#gSbZ9$w(kVP^#uizUVK1rD_DLqK)<{U~Yzih}i3X$CVkR6odTsZ$>bW z-q?7>>0EYVkfyZB3SlKt;%m~DeIC`}KiIa50;qUE*-Z%D91nX2Sj?gA0(!P}OxL?o z?FgxnN&KH^yOA@)${`MiKChT*2k1QG!++84GOp`wT=jn&GoXOhLD)Xxxmm<wC5e@F6eiM zvggx6H8mi-T3M`v@8&XzPMo&hr%$#@K!$Q-j%(Xhc++-Cb!lI5 zWEtrg;@K3x;j9|xnVI$Qi+bM^_1(bGiGdol36RjsUIOLXx%zznYCJjbAKa0{xQ9b@ z=QMrGQPhTS3*HDYo4t=KzdKqMy&=w2rX_c9drFxxGp_C?w5gvSpwH`8 zoSHu}gPzPLXii^IE}4eE7mgoVU{7tiyEYcOh~F@3UgoOq*^L+J_Jbtcq0Zbs0*91; zbaLs)XUGw*1bGD0Nh1%!I(!S@*>!es58SD?5D=zA{3#zXvK`K$Nb+vl{YGU=>yb;C zpa#!wgkv@zz;bG0WC3Cx20dSi`#Uo}c-M7F@L1GpDC{6`*aCSS(L8k@EJoB-tIM~ztd zkD_HGUp;BR(fC7yQj+(}+?j%|UI`({C6T_*T6q5rg(8Aw$s3%zHfy|XO@36e4MsQK zak%}sJW~M)!XNIa*73JHXp3kgwv;)WeQ{q5atw-Vqs(aBxbb^SpTXr(Z=n&C&9!cl zm&Joa<3C_(VB{RrStk9QZgC|`$WU$VwTP|ZFJ$Hi$-{34)9fSpbIXX%ej=U8chs&e z?_?4)2LDUC82Et`DGcWURN0HB3qhhoiGk{)C?BiLy;LmIL&80IyM|H}iLz@Wh=IJV ze1G{5Jr5zl$fMrCe&!;HaJPnwN?Omb?Wq{3=FFvA*w{79}eR8!k zpAOdAg!UmK)|PN%KRB*+J@>yqOoD5~0TMo7pXWv9bMrD%y|St6)^AD76-u(cUE7&7 znYJ|f&cUc@5ut6kw+3pW^hs(UFYK{=ryY8fCMw&~3P*G*7}(=Map@IN)c(XDQ2uVYo?Hw)`|kNB8nCy?KBHfBP&46o%~2^!^xZ zpgKrU%y$RuDaJ=8+;u%|pUzPOJj@WWb*_C2cQy03gL*AK z-A6*t+@eZ}3!{B4Wz+M#x)7%L=X|>63~WOP)KJYUw5N1@r6&$o#kFb0cGoy{xG;jkW{LN%85ho7iMI_Mvz*U9A@zGNOk9 z14nM0EHFBSNPR81mFXQmqwnE#384V^pq;@xSY;g;b#1G3_vV|h-m@X=mX;r+HwS0K zES--iGUEQyUU7&wTq2v2=-Xm67mng5T>RrdGf18;gS!kch*g{dpR=ePv(2 zOZLE@X_p%qHBZLK(Ie^!omW@{O>vptUqKm4ncy#3hYScD59lt@1ctDkjYWj!?$ zD-#eOqW(-*`cxycl37hlaCQ0t5fr17X(4NGTg4ALBT=%}yb#{&u!Sg=#F{H9M}OvC z)I*W_QDQ%|*Kr28(0qQihI*c&bSu;?G)iXqAqMz?B}I0$70{u(!iECU-ErRi*^nQ? z6b%9#-!_q?F^MQ0t#CDQFnNj5eAHINFk&PtZnVlbdnOeDiJp5{(JmL#=>;)f9mSHs z_Y-ufNIgBKsrHy`eO5p{!CA?Z9)Qgj?91l#hMtAVo^tfC4wc=<%GQKR#pTk%>>z0k zCwYD+ZH~1z#eY$90R?DExdTX=ngS(Qs5!h6lI+T<8u{V<8Z!SI^Co@ps3j&s?!PI4 z`2-ioLyD=1DgZi**gd&`zC5{P5d*5i$M1+8V5Ga6#9Pts&&6z>%tk7&}rh??Pmbm z>iE+*p99x=H$3Xkn2Hx&=YMBUf({+Yy4H z6(-a5`Q)`Lgs#r%s=BKE3gIHGpkXI(>p;X`$`X8POpi=9*@W8y^ySfPyn_qYwTXs$ zWR=gOnk_rGOd3b9Zf8SEiwc&9emo9S@5Q0@C33J45&*ZICH)B&)79Xo{5+Gm$HGf+ ze%ow1=W1rT;b1e1ry$CHfUKGTu?FrfR7Z4&hSZ%(oYpS^1Lw1xEa7~k$}x5X&7Imu zrVruEFeJ%<;p17=LjPjHYGynQS1MsTRNV)Lkz9kde7za2wZAL>3B8lvRY-8w2MqwV z^iCi;M*>e4OCu)NW66+zc$HAmZ^O00Lht*a3q?969NqNWuXBPtyYNE(bB4dS(T zi3YIcOZ&dV_LDduI{?Vi2$n+(*IFu~KHpNG@~)OyZdutDU~8zZR?nt>8Yb1bt)^}pdh5byRC7zMhCR z4imXYZ|88po2jGZa~i=_cnN{g5m{q&;7Ayj8a!MQ5E^eFta;X7?`=@2hp*4_;viT; z^3FVI+=21nytjvMgRlNvil10AOD)qghH9~Un$+L+gyuf@L3XP_D#~G?cs}nUJUq~i zbx{MJ4=1}})xl+AhL7CoDZZ5>rxZ2Ka8d~~yw`Fq-Bj&<@IYml7!6`3?(Xr~PIWVu z+%7#fc*8n%)c5J552*CM=IC3^(WzZo6c@>K*=e=g=CU9$(#VD6^@kb1D%QI=W6#Wie)S%zndM1;!r$k5>720>($H11{4H&~FXxP2BRg;@qmDQoqI12!Xq@K^ zBP`!c%krr#E``6jO6WHPVF37qNNl{+QQ?hS*4e{MYG#XRuM)NlmA|yDN{(u43fTUc z*a0M1XOJF{4-G=K9sfL63p@Q;&@1X@g#Rfm1v*Efb^50_RoOr|dp6&JZ?R+OU3+M@ zsQl8gs_)MFPhv6W9meP3BOL}7&uW*le#ab^d_NuC4yfqVa<9*53_sg!_IR*+)`Bto zV$Sgl*z2whj4F_-Z_#V`3G9J3K?qY?Nbn$Yolk%zWo)LVO|WM<)sz-nu5zim+-2?P zOFX;%1to01r$0e#nl=9!R0lnI&YJvC%$E&6I(Y_${N6pWhryyy=cF<=(7@xz1qd0S z^T$(qJ(EBj3@)+H-9{iKUj>_(M>nN}%CeMPjI1&oQm7vRxC$0{rk4W7B@?W;2qs(A z+4v(9oUk>@DNm~7kJ8iHw!ZfID_Z+=lhli+kmodv9#I3&2s%$6+ps$ff7g}R$YzR_ zFsn(neGtuFr&&aNb^8WqZ6q^u1bu7`oPp?De9=^j~ z*qzynT8PZ1`4Q_(Aggo?$5r0Py3jRv>ohLjMxU%{CRjk<7WUfZmW}KC9z~KHN zB7cyiq4~X_D)%x+^*;+c2Xxo(iGh7yqBA9ZP8OXnDGj&#lYT`0{lPa$)u?}&x#voh zUpJ`a3v6eFzrj-6K!H<$j9&~?MSWi0>faMK8#DGdcm@7I#6ku&Et(ahi;KxOIM6G{xWE15%t^2+$#U0S=rBqKJhR3sRz*}*`8;A*baN$PE zrz5aa{ri+c_D&3x1OeRXO#nRYP-+~)W1~G;j+)9OQDgn5U9gTA@2-@pbjgp>4z3-= zFw0<4XUBrm3xZ@%@Ma_Mcp)@o(D(ZXu@44Xjv}$&4np>-k>3`ctx~slLZOc33N%Q5 zR9a=i=I9#cJ2@zQ0w*mu?p1|`0+KlUIO}JL%~Hhv*$0eX>HAspUnI_8))B!o)!RbHyZ(P zG6UNNVFg4ld_xgK3oA{^dXP>j?`@NxTZF#(V%m6J@8Ro_oG z;${Y+YD-n_2c_*hA}gABl_x4PG7Rix4?s2c+~2?7C6M1+O5b-ol-JQresKlsFCJ)2M()qPXw(LA)@S(d@4%1b^9cf<+#{A3YJIsV14 znxbYEeFMG&lfllvP|IH)!Uow#1&K$oYr&&Z@$d_S`>`C8ZuuYSHw?49c8N`r2r#XT zHP3ZYzhY;mxFe#>{Z)W?FGBfS80sTBG&gXK0tCS~g0g>%$eM-ja5tJ*ThJC5(l3*7 zsX<>rO>p!qyF-cI7)l6zZfN@JKb-301!}JPqd-r5jaFX$w{<@e*38Nwe~vN2`x*Kd zmHnw3eA!#cwKM(;5N4O^;tfe zS~KKWb6LR7`JNGLt#ant!7GDsy6D%8Grl>%)$CN%7g0~x%_?xGkt6D`H%r7x<2=m@ImHuS?@b0J3BruA)>j5`tw$c7-~-%c~(P@ zabfrKHT%Ma&Z5i)WLA{}!cNzDZUi@Y_Y(VQzhP^v<}Jd32&#si2_?>C+Ku0qvgq^H z?0*S08_3f4f7&9J<>}~%J?Zsdu>LC<t8Zrh&U35{D+Ur^s|`?DZKG8qWGo~sil z@vGuvOg{9K@}mWPNs%btaG`dPWSK#F+uE`<49~dKBdF7};~4 z^=ol6i9&N@cVRm*gH(CEYN5zmPh5Qr^S8UKBpx(a8S;?&Gb)hkS!^i~&JI|U696@D=RLi~OMmn*lL ze7IwHFq`qu8u%}r6;Qc)f=L(E+lIxRs_{yFotJUdp)n^0wzm+g^7NkKh_EWk$H*=j3^TZNG4c!SDAVp|MTTV``ptPpTon069)#uf#@IFDw*DpLlMx_$KO`b5?NcsE5!-LkbWMpNq_%{-2|Tv0}^0e4~ ztgGC?{3N7ay<8lo^CX8H{A&55aU_IRyA+x7_RkX>wbWa!_qyNr%z~t@bEv$Vz?ubz zdjQ*>*}j>8y++NHw~lEi`X7WngF3hdRRB|pO@?mO@}f_{hA$fmN8uiwX;c@`q=N1^ z?h{XRH8~jB*A|rAU5O``)PN=xcV7zmYI*(vgXSfib%+LHd?u`#E)?;vP3`W;@=xF; ze45UV*589p-I$o4CJx6Aqi1t1nK^Fj5at+MY8#%nG~WGr-xX^800O|8Q#={Q!~9k! zwzBK4dOM|7l-(wy);+AB%n7Z{zG4ZHfTME`*NEQF4`Q~9L{O^ZR+yZljp?W!e%TpX zv@H))Df>;ibGdpkxA~UQ!PT^DV#0E*btgT0J@bn#V^o)x{m;==uvN7M(R1nU?nY8T zIxi*NEnP~7bYCQuk_G|kZUO1;?(R~ChzthMIM*)v>sZY#Z$BIR!4EH`Fv z{r-##%(f9JVDF#h>K4?z&KzC-1tu9GdV9ZBuYjO=cdH8~)<^dw`Y!N{m=&)dS>CP^ z5Euto?lem1958QqHB?~XH=w;osuFwjyiUYQwwf~_2|#QM42W_qZ7J3}u>J&FvK?Vi z@-;n<`@(VvKVRt&9E_&fn=}}^0*u6X002u`jvXu;8_i)ybudqIp|h0TQ)3SDiD*^oX24=I8W6!@l^XooHW_^WA z!bg6xOw9X7g7-|B6v7234_r_;))oxhHI3r%8(gDItJ?3pe$5Im~8o`a{FhFopQ!%Wq{eAb(y&QwCuG}Bi@G2}ah~B^(oP!DO8l8Eu z<1PIn#&k7piAbJUb#nvzhM$k#Fv>e1;Gao%5FK}SCX=rm@yEJ5N)LQ(3!>(qX37t$ zyHU14^ z_2PUB&1OrF9Yr`+&F*=YacW z^ra9~XfXge>R~qYc%5XYXdZWapeB2;bzJE~I1_4FL3(<>u&CpTV8*~@tVPuzcBe&> zoVxo99HI6rq4#Fyb@YyPm^>`cZ>ZVkJvPfEwK)x%B!EEk_5Hqs0ohly-`DJFrOZaQ zk+EhKfe&{b=5XZ7jJR)wY+E@zFd4c6jHQ45_A^=j?&qw6xI*hh3HpYaZtia$L{>7| zipjIaBLQgAfq^Tm$hgYiKqy{QnZnk^rI{GZg|H%NIXmlO9?28I^@E$DeK2YJtUep) z^q6xuA%Az`%et#@Vv&_aXvB+VNvTUdHf@2TkZ{tfXAu$B|Zb%#V3eEMHAuMhM^1D)`mr z9~B_*GSjS~t<20ivqXi*PZxZe*lbQcOqVS0JQ&=-TufX8Xf6$R!;aI13=z@TXBL^}0jP@HcibXy-apI?2tA7!)CQlRLbvK%(dwZ!T zLJ`h~D2cdO^-K@=PeyEn>fqaLq@8%K^L9w>oEf>*NN=+DR>+Kli+Uc6pb*GgF%Pua z-l0FR%4Ews=k`9VU&;+?j-t0$CY*}##;m@P2HQPg6sZXZU~Q%9%C?t&wx%BG{UL*% z6z6`dw_Zqt`^WKewV=j(ja#`AId5rj5qlpIjD130Zv0g&g*~h;ehn&wi0{&w9o5l# z;}pnlMn+Me_ZWXKihpMEmQtOka4w3Y0PaBdY{zW02bVYAtwB_jXvse(L{;&YYBw^Z= z8Ovz7jZa1^@DrNt889MqAp?p??r~tK_X0bQ*x?tgaOL~3^Q()$e!B}#=%^U*5t>7L zmykQL<@k&B31*Ek?ip8*^_PkC0l$z)baaoXW`(ZT)9G}Hn3EQU8mUmoSX-Wv*b9cR=uuKz)-iyBB*0Sf4q>eZZN-pkQZugdc-(`rR9 zB*6Ebx(=qdH`CMoGr@j$=zP+{vH9--CgZ2$>#KyF85o~m*_D&*6B`QRBSfwYMtQq@ zTpyvZDLLwR{8zEhzH@LH+Qy=(HP9T)lE<1q)xVeLR(u+HTxtIG8~H7^>0aBhb^tg? z>JYe!0iX`(0eDYqi7>r%aA>Xj zU;)?T`Vf6c!I7N2W7sbaTVAScZX+o76TGPn9Epev|th| z6+%$yfaFb+&$x((8t!;q>w(k*Db0z(GBE==E@hLCp#}6o%5U*?0T`)##sV6;H0}FD zLBmG9IzDRjC`r$1#ftleS7OtfTX!^T0)7C7+|#KOmD`sMYzQAHK32kBz|3H1DMLmK3;^ z{(m|@AgqP31DEZNhFY}ZW^m$+o4bSj5#^%WS9O@2)x@pXrb99gUj-qWVWkOyJ|ea+ zPf+3J@pkzAc-a(wT@#a~R%FZyd{-yt?M*R!F*9r%a5r?P$`FHw7SynL>^nj+USTmg z<_3P${_x{JLWVJji$dA|J9|h7(B1$;Lska-chJqYLexLSTNHFQ_pIK=rI>p>l+U|z z_4p%&2KYM!e-W|R?o3Mu=90@+HQi6;s5U8c$fupdWN+?Rpc1;-2zVBd=nrfe+lG-` zC2`!orVRhq=&Aa|vYT2h5clQV%~eIR8+q|sRQpW8TPNdA3V4Zt2KS#p7jC!a`8=^n zWm-+}Nhc`XMDTd2mYaz&MI~5-VFCRTt;5ZkiL#`SLqP$hN$-jU4OE(38g(Y+k(Wyt$cBBQ z-KS1Mtfv+=##7<^7_M-A=}%hWnOfa4y(hN;NOd60U>m)U%a_9*C^R@~eL9Ihg41jN zaSwR7vZhC%#_{a@k0Tq@kWg{};~VUY-Jkgs8FTxWReu7NWjPw>LXHx_ld9i-q`!>{ zJYeH6q{AA`Fw{MvDDwOhhHXwpgk8GJuALf^E0&0>928;lqKC)jtsY{L>SQv>I%Nto zEn0f-WW!BPdLkIXh|GH3}G&lWXo z&g=b|gKPi$u8hs!w{C%&&Qw3Ie85B=-Gh_#K(pP#H$wK%qA!U4v!s@(So|#Bt)<~> zE9lA}{RrY|ia^+q3ck^=bG?-(0$j_RM<-}c&sB$(Ece<)RA%(ev&CAi|F>gX$w76^ z0A$8*i7d1a6>079%Sh^Z`-y(=_0k9DAV*?Lr^i*97;Qrc*>JhBFv?|=2#ELiN4r)f z+2t7-PM7(~I05y})OdLkMrq%}U_lKP?$|41(Ae9cBG=^{Y!r5+|Cqe1J^oYd-x}R; zu1+vV@GiGN_N|gNy2ETEHw2{7M=Fx{P9}{FK=XSuTq=> zTlOXSiKD!8n2g6M#1$V=#>Ayoi{4?aVsOgm)iTeTdT{y}OPRkV3+~cJ--st{IkLw4 zoEPCGmU&cMUUaOn{~PIhHN>Of=k=kMD{?=HYE-IGXP`HMWfbnI19&TU#Em{7=utQ9#cIh@$fn z|HMRLXgJsQ!OO+Dw&#}-7MC-xiZJH*sTOTg7#pb4Lcur9B5PU%q)jQ0@08eFip#0z zA@}r4SxfzF&o>Hu)w*=`1?}mby>mNE#^MHCL1D^SqMK5Sv5$*4TV-GjABisI(3i%U z+n5hS9-kBYrrl+5d3QPdc<~cWC?om zJm^_qGkJn2i%6toW$X1$a@buR)c?QdCrt2%SH11`e7AV;Ju79FSFn9{a$9M{OT;+} znxj*|R`|QDAI&Okk@1rR-EY;=XCAC;y_KaNcx>x|?cT!Y3rZFHt7SVEe#Bn+(nQeR zM8O(suydQ4RkPH*EweaL)~`v-s3?(swQFASvEFwAGJI~hkZV{lT3Zh^gbNJ{cxUF8 zrUlpRHiRTWvzcE|c8@DssUAp$e3q}REKJg=>ytqnzojLJ{^iCGlf6-jjkF_IN3L-5 z=5h{d)jn7Y-2{Vkc+ph{w=~&$JWuv7|RNcA(P|9;|j)#7oUckIKn&&Dg4(q1zg|m`j$NKJpUjpxXW)xl18@9n^GX@?4*(z&%o5~6PJZa1oabPAjbNA@#OIsN zN5=g=ezoI-+5RhtQK6t|CMC$!0}5=LylLt);wt*b!glLF!d}*?LFStr;&gs^0GO)Pn5PgG_5nXx?4^ z_(ssDyP&q749*u|89Fi`~0wKN5F*e{#Yt#h0*N)9mSU>&E|MF0sPg;Pv?k z@i1Te>qK%lj4MxS#N%QBZ40#yHYZ$iOJSIc6Fa|P^;o$+i^Q={PMFTJI0N+63Ey< z%v`hVPmjFg%;(%v-CCb_!;_uumZ*up+!8h8%@coz!+XnT*H`T+2Y@Fn-3>qklKa7` zsG6u!sU@*AZ!vts{y!OU3Uxrs68b&U7_pohmlz!1~k|MeZm$O5`%{SNU>i)XH7R^e$ z$mR<9M63T~dceXX!x-G2E1d=E9@oJ>>~H+MK87bnb^Tl2=RtnEz3!@^f)%MU(=(~*Ld7#$O-DYJ_8svDDiW4=TePL0E6*ytD$~jAQDipK zo7Ie+33mn?dgJg;yfR^oE|F-LDrc5^#89OO)h?S=c)mE9AX*aHj|HmJ|H^1&AOVJe zqZfE1@e=^%FV)GDG0Uvv`mhE2$$PLg<`lkBrz2VC&!60fz4v+?SQXHD&;0rVi{ijL zq)`}lPfcE&X$3XQJ0DVZ$6^QKQ?jQAYr@nP)t(ucf7C|LI+bl-2xXc_PzO&B&G7!9 z?L&=XIQHr~C5K?5Rx&sMY|su4@N2{%gvVRa)!Nq0I|NaL$#0xV!QNPp0oAS+p7~;% zM1D7Q+!`PzefItp!_C%{sBgPRjdn^L4=sr7YM+#8b$vN_jo1V?s7-!Tt5D8Bpdo~i&KJODz{V6GNRqJ zwi!8|6Fu%cK|_i+F~897dSOTk_dd%2=rM!g)}(28ZO##MYgTy#ENNE49ow$&kD7rE zv(iPxuCFz}f3b8P#nB0Hr8{*Vp>lUTzGCiAF_ce%t8Y~q)^6TNa57;@R~(N0ZPp2D zLunf)g*2#Q)bY=mtlsLoZWJl(;$$B`$Z>>G%NyShtzuJfSju0cxPZ5*TV=;4)XWip zgZJ8>%btqZ3vyWbf}D=KOnbKu1gCUPQ z_nbTeIQZ8!cL!;b__RT9HgCS#?D)aI?Rl=RDUIb%?92y5(K+()K}|LL)2rYaXGKZ5wh1N{iGT-Ye-LE{ss< zagZGPNp`T>(lj2JB{Y&>Q}ILhf4s4@viV&>`1~`LDF!4Fv|>iFk8j?*fBNHFTSNuc|?%F}PI14eLbmuTP^4uqy0o{Fnk-lGT`rRd24M#XPvq^^ecpCPt0 znU;x23wk0D`INEL1uFq$5ad10o$5E1;sDP&e%tQqK-RC#e;k>EpN!7q9qCpDMxEY) zXd=#QFe>V%OoZV>2hg|OLI>Cy*}fz*+Y(-3H+Ba9U3)wDNCXfdFrjcTNR?x`_NhI~hiK7PH~HC5nQ?z2Vf zG|aiv98GSri)Qkor^cHJa~jS{$C`_-<;S+`GL>2pjXW(S7SPJ`!!$O{tlYFoV%>7R z0@}eBer?(mgV*QVu*Z^)Z`U4=B4>e;8tzhTtBSlt?h5Ij}9E`+@ZtT7(RV2z%iT|r!hu|l^09F zl_$4Zm!LR9bz0hLQtdiJcu-$D2)3mw%(0eIy?Bu2dIL|rXLzI88f+L=gr}qhJb{mQ z*Yxd(Zm)SAw@XQR)ekG~>M^TevdzH1mR8tfLg;Zp21W7ylcUvVMzD&a?A9--96L;X z3}`33wtk<~-#dfNQ5?v$5Wh!FV~MB~*tiuW6^+vb)14?7e#-IfQl79!T(53DfA}}T z&~gJb>%m5?M3iLF&>9Wa1Kg|R|B~AY&}9a;dEdb&8W`jyh7SCNI`8JIeGxXr7X>V~ zgVqRU2urrPg8nhtwFvFlENo3);jQ6P&Q^!xo328zAm#GRF+@tOaCUCXBiAQ!y5qN! z(+DH^3Faf}3a+{Wr=bLMpxYaAh)@*~Mjghs;L0jxrzMGlh-~Eb&_Eu{o~`HMw3ShLmsuqS+eWD4dZCY?6jjy>1jB{ z9H{ONy{!cf6~UWfGVRRyqPzCjBDYhs_{J>#8BYMJ5HaT{UhuePr0xq;j>~8@tX}d0 z1)$igW?@?aS0$3tdh3!o&yjd`G=1prAKaoxeO9j6PmQ7sb@~J4>To-Z@7=*&J}+0~ zVL?nWU%toWv_q~y>gt~HHtgF?*SY}>m|&ysaL-^lNtcQJh{>cx@{uJ3{RkpuR^4(0 z1p&M-gVu2Vz2|x-?>v`7D1ao>hna%f;--}MC0T~=$E7i{t?jFhzJ1O5F~UJtzM0vK z+O%+IO?_lInd%o7OV~mmJg~)NN_Hw>*wn?%GZEua2%rI)fHKB2v;)PJ2q4_2noMJ= z@9t+QcIfp$*gM(vEBa_JB$-<0ch>9Ewnd1l9P&_`$r1{;jmTCU~UFya9wq1wH}yujWhnK#*-Xb?AhrB%8{`IDlTNS_fY!m z4L*T0H1O~X87MtaxbdDaygK_MpH!NX z7cdc2|9IF5X<5^)nos`4Wc3zf;Cu8o%GnnKC`m%vd#9(TL$**jGHW}cpOSazQ^7AN z9as!Q;``<&5->v;E8&<1h{<3;4|}q;(g>~#pG}1GKDo;!D(yk&hRb(!S=p11oT5(@ zg%*NYDV0agw3@Jd;P=w-SSVU$Rb0jj<0SmAmSm!}(hsWojj$lz@ENCm>HZ<3CVewK zGy!ROIJ|Bsm_zv$spQQ_R{i^3ew_YdOz9|lXt_Q|4kVf1fKxU`7Cc6;J9oMumf45F zPd$G(s%nL2SNZJ{beYm?AH;M?`cvM|Kl_lvZyI(a)mcOj;)W885gzI24{>yw4NM6f z1-|M7AnpYqQE~EbGpR38jI;A(_$pidiK7mHlT7Y6Vn+VRxh_B)W0)!ptVzmHkv?i& zBEtZg47HzlJSYn$sABJaEjdiVSb8uS-dG;;kH7Nc>@)bh2YyW?I7q|f{uHV|mqa7FZ7-+N%4dy*pD}s=@VNdkz+=a%L zDvHD_UpTG^+9vW>O6JDuPEd19ojx$Ftyq(0mu=gInrMNEwu~GsB@7E&J)|_m%iG=ryu)B$Pd#Th>PVdk!@353iFx$ zxe<{@FpoS4Ij2Pg9znN^%XYR>$}ncpDKqzV41`Lnw_@T^dXvpl70sar9!nem z!>w}8=UjcQ7ep+$AT1tkzlGBIo;8?}$FQ8eZb!TPP5S~nq!7UTRr}&je!!X+&|3~# z6s-1p96Di|SWw~HL@Frw88u3di&8;_vgWAm>3zVf8=c$7|D*^QH37)0K1zytMc+DiGV}2 zwrG3=!N{VMhboHY9T0Zc`t@;Z)E5e1LK1d% z&&8g{R8oiLTp=Kw&s~Nz#;aF>e^ZRuxzIFo_;tZ0X?Ilavbhbddtnz91rLo-PHg3( zmw>yn8f`0X)dt*~v*%twypDEo{t*icG+o&KcM(yrTKd7rOdQ{InI`asq}tSQVvb** zw`VANeLEfvT|@(&hICmw_8t4Ab{ftoAYtvS4H$=c*oN#yg@rV&mDuE~zXRDEaMG(w zg#;5J@jCg9vnl3Id#>`DRouf?uFNVmF63aL0iOYfW1xh?zirA5k z#C&mdLhJ&-cNarA0@jv6NfS{1ENG18VaaFw?0e}7S}muOQyL#ckquJ1ANjt3%eBUN z5Y{mN6)}Xi2m}GQ*T>QG&wTpb%u>46P1jas-y>2RqU)XJuf*MEZO%m~-homRat=p? ziP*g^e@=^^?Bq&sgkh2-^OMg)C<8A`@^d`@$L$w#NEJN{MMZmqdJ6J+?lV~X{6@(6 z5BwrMRO5w~yAXW|ffk?plq0~@ob;&LK_B$6t=#ND*!3>MXYiI!ibb{^S}zL;Grw;- zzsz*=S@SSu>>AejBHhaF!z2GZEdB9$8Jf)47i@uFVSZV7a@$?ktk0b2Zy^Tb2DFev z+F-nJbdg4HP9Nd39uH9$Loe6hjj%OAc|$6~f?!4VvLya&h|Ehl`HYfk<5#sdDNb3pDIj2~omPt5lHwVQ5Uvb(=q#G>l z;~N;W?Svm=@rDsR{+?J*;Y5!;SL&vg!@mW0=9mY?z;@{IU8h!paM>ED-7I$1f5mT`7hbLtmd~GF|*bGu+`88I~ z*4T4+x2yG2+nrhHx5qmdHSf#Z=F&Y$^f+K>HHrMuH3?y{K_a*PQ+O@T4?V7lPWS9w zX6tJ~J>il6ZI(U;rpyA`G#I*gZ2fXyQ1F#mr9Y)hec&Y2a<2Io3JaTm>aA~Z4m%YP zO57(le=^z8+mCm|AEL%!t>s%B=@XUd$UN??9>>W1F3%VeXK{*+EDIzk9$ENe4N;4G zu%@#eMn(PA%vPz%5&GNCes9L41;z9h+ox+HKw^^w1>5doa2(ssQ!+fJzATcg7irZ50^lFs$0yXMs6TlU5k+>{#Akqd?}btn#t z*rE+RK4@qH29_xqw1wv1V)KFQg(H-pOL+^c%D!Sr!Juq}V?9cgTO zlt`7V8Ex&S0}KU|a-TlsGO?#IZ7m*4%r~R^PJpdH{%t;{XM>$_322(8t-eiRH3!VMY(z)jEveAsZsV%R3p- zH9duG&X3OL|%R;BV$PaxQO{7wGOn(`B5YI$17@2*h$ zY?LfJ4o+yI?9^2-lQ1aK}g;*I^$LCbNRy|kd%pvr}Il_E{46R8qi(CHs_~) zca!n;35r&2C0uO^#K<6deem&2%Z?5OI0%SOamU6u>UnV)C>S0G!wUN9k!XJo+m8P6 z6SGGbi55a+)(xzN2Vmr!RuQ48z_l*-eqzuv_QiEH=9$(M`*Hat8Brcv;Qb`vdU8 zx%BAlP9I2tU|8uU#AxT*UrNT)`~kDhec$u4Rf@H7WY36zNR!^T?=pGJGOGV)#g0{#+E?tPgpPp!^hcj6TIzC$2o@Ao{JjhpQh{< ziwuk_VkNfH_C*cQL(x^SCFiURMFEEoQIV{cYcj?Ia#(%p^&;0N^gOcrP8LtIN>1`@ z7f_IEZsi60`yum!GoKFVVYc$iBeGcX3F^#NtyIAUc_=D`b+z_avhspo3r2%k(N%WI z)Q9cBYN%f;esAn2KqNZEbk(v(hX8@na+ylV%{0^sFfCUZ z$da#vUWj-9^6C8B4!P&G?9~Gp(I{J7k9EzND>QU0x}ENaE*7ukKFtJ?Wl?25T(_3r zUcg&f)$jr=!7)SH3{0Mg%4bxn4; z^!fPnC;IwfYjm2U+kpwHC*kL*@xhY2L8l)aV6pHt&_=FMk5*t2!q2_MB9#;HQS zd*w=2stPkwBiA+i$RYoAl)3W*?A~L%Z%06hL-&-UsjiD_1n;o~h8C`n=6|ie1659O zAmSn^YAQp`eQ@wRkJkUuQ(tHCkWg*T<4P-nDEa4cqw=X~kCZ-^< z=-QJfO=jS~?GAs@-t-asI(PyCfor`yTigVCYngri*L{z~svxFh=L}-1j268E^TGrv zPj$_4IZ2W{#*Zp8VF7mPImLlG6t~e;PY3cscZHt2{ixhY=27V`c0guZK?ULtu zs(;Ijx0QM@_`nBHI0mTEl)|C*iriFAm&1eVB@?ww>4NwKSNJX^LT|&M&Nzq-_h%HK zcPH>`UO+Ao1BCc_$Pk(C|hXWI;H;^=;;(e~sqS$Pb6$H_KXAlLmBSifIJaUcCF@Rf@g#b}}KA(J&E= zLG)(r6CJn9#ijC9uHt*V{zTt{Y$$TK@vVrcNk_f`M##_EmdUft9js8u@z0Qc`&OLh zTkaQ|2DY3~l`CcBT_1udr8G`BE>sWVYH>;r^qP9dO3wCfQkErVMXjBrfu-#>swOJkO?CbY=L}Zb`C$Rg zn!Mplo>|2Mj|6z3)!O^XmvsSe8!n70%z0CV2%2g&F&Fe&TgN5~5KMLXsQcpNwuu!_!D0qB5VkcTIFp z;hz`ua7p{|r*x#j%;M%3v?)N5MW#PVxfeyE?Yq=_1t{4x3X$kEV$5q)damNjo3K?Y zKG}=G$|1(Ds$+KWkU9+|aZFM|GCFnLX1J6pUs-Kw*t`KwE_i}q0ms#L*fx1!F%2;L zaQ-3$`wchm7g?+ijDxI$QmegTv%PtadIYBE%TXXB_J)oNA^~*`aghTN2N))_XME!Q znj;$fOnzndaNU5>?dDuMGWUyTLV2t7Al@=UE^t#u2IqHJmv1;83PrW0E`Q(7&Pu@d zW2V#j(NNzpbZr%?0=o?jldT30Dse=m_@T+MLYg{45qH}orhWXMpt>tlW_n4pHKtH( zDtf4`3}jC+xb&nIu65*!eW$8VlAP+q?2e$sbk1P;bUofg?x#|jv_`6^7gJsPRw416 z2Ai;?DbqOf$&!9l`O_|{JM&K*$sN(`1VX|07_Lyf$1oWL;osV}%5lrKZ*$ zU_`ByJPVP1is8mRv45DiZatmCR9=qXMtQ*A1u!eZ2zU&#WvbfCQq#!Ynl@#yR8=jp zoUhuV>}hMNNdjL@9#uY5*wAke;FdVN7-roF#^plfk>C0=mU}q#*BA!mZpC>7;~)kT zJ_2bY$bAsox;jZpS|%1kY@7*wkPYZmiGMF@sI1g`oV--c{4G_ z%}hY_0~wzp0MY}7U>uea!PBB3fBcS#OBoo(fHMmzE5oeK?ns|^#pro;1oED#FWcMN z58RL&&57qD_}{1!ZO9Qr6}xH_Xbmk5xH#sOdtld_>^ztes^Yn$%en2u%Ohd(qw=EsR`}`DF=V2Ulf*8MHWPEql5~c0vloVGCk#ec>0j)90FyW zLcMxfi!P+!kRs3Ddt`4@Ir0wVD|p{5D6jcG6e5+l0Q*<>&}+Q^QRNHr-SYQuHpkpE zV&7{Sw%`;7FU^#Z5H@;jL?1CQv^QidtSV2PJQIolH9q?)hO4n3_P8fi1aCH*?A2)biVeVpI!-N#^fj#Q=qqH0qOL zzV}so`H?CI#X#UE;MN>BilW}`wu#bHDR&E#3mNS~IINBcx%w>~b`gFktPj$&^z{E6 zyx_G(1pBu(SM+}%LB<>#g@<~`e#A~#(4a9(6qG_?^fisa(na~5_F)arj`rCZ_eKZL z{cDU%KTDtw5+;U)?^WYhD^msGesyx}O4|Fpu>!Tvu`AUvX8d)wJ!&|zAl;mIaI3$E zJavtAKYTDJ8sH)&6I6Je-g&ut-2V4?9FgaLn_ur)iwNX@NLYn*{~GAuF%_`-}8sWEy~=Z0?gdrO~kjPEEm0l%Ujc4I~r)j{Fgs z%cxAj%cdR1HYCoQ>!U@vcFQlIgGnkP&iTIWg8js`x@&9WrcgJY@*4er!XT&0U?PJ# zj+Io|>cz4|p9AM4rDrd&Ry$0cMV*YqSl*ARDF z!M7{d`Zr%wIM4&)Bl~_(Bz94^YQ7j&Zbb77++XHkyB$>0VTnZ2G4f(RCQ%STb3S>g z$gNaWiNVBK{F8^5tLqm;{-9r`X0%bbfH3J>l3OGjcD~CvG!cR?3%U#Y#JkM_wJJPB z@T^@h=Cf>}rjT1ke4Aw6+m)b%kq2gV%~9mtaM@ zBe!>G-@#)1J>?nY3Uu5t%Ww}`95RI9A@&^V5MKzdi@g6nOnU)J}0) zPIzAO8MP6qaGYEdJ*#B9YjF&%?%!-lb&~7H4qgLRP_>OHv8uDa9WahAmK0iX% zOO*Fr;t1Y&3L_Z7;vNd>r7XV9-Wl84Lx;KwQ&FD0zB2E<#?wX0PcWj=0%DK>Ynz#0 zwnt3I2|T8e^Vm0r0fMYDrtI#^1V_zv9DMQoQPno$UADo}W$0IPtM1G2WVTlq6LD<_nD1Rcs6 z!^ZICoky6~&yzV z@B00pD|G(%b|W@~ESK6as5G- z07GiVe54^oc>IKKaIsV=74M@Nnia_L;H3qK` z8xO{%G{`abpc>J}?a1}1gbrDGf0s6l&u(g$LJDRtzt@*DJ8M)FCWlDhx?ve*DeoVC zMFO0>ru<`-lS_gx_N>NVx##w&6hIb)#u+zlfC8HrZlHa~WT{UQsOx239@(N(Bj`*R z_cfCE+go1AghH(FG3GQ}w@OAQM6M+0wiMuyzg-KPc*gbCTIn~WlM#e#473BI7x6Wz z=vSH;pmgOge%g<@FcZDEA*|_Uf;yzUjkSxKdU8Eg6D!<*%U5Au5 zp(dHno@OE|z18e~GO00uE6T7IR;YEXz=Fh(*+%i}R-JS4$FJ2^2-5P_M)WQ@Jl#m^ zoA7MQ767?>J5jLNagEXkWrZYvV;=2(pczf<=Js?VBCW^2C~MdzBKCIy%5SyM4q$qy zLm@_mzOE}X{9dZ#z7F9fMalBtD~LPt)+gz_)!-6lnW0~WL7}IW#Wktr-$p6HPs(3< zAAP{e-{+ZkcpIyJ{ky5Xw*B8`Xv|X`JQ!EUXicN|I+-73DdVabx{?oFxP%0+D~B6s=BW;QP}@82rR}Z?D?*6wvyh@&OE(?1G_^69a^DLI%k zS2^C4b#nRoy1l2a8~;#^O&NVQWh>~A+7dDL53bH2PtUGNMLAK9%%?vIq4$)=OLl)2 zYWOP5?QFMMbz2V2hgHJadPArihWgGrHOngYFFE989wjH&r)ghghNmk`i=!6pX#Y1E z9#i=a1%{g->p7%6S=0mTu-KYzs^8|E(W^tVWi*+NPBM!yUYLOG%uaBWJ+--&lzXU^ zG;&HOVPaIIgh)fwb04fqcor9)*_$k=qALE-j2{OzVk=F3>AxR78&Vp#sZV7u3hJ~s z9aWkchxY9ej(w7Wdc%=(Hz)*m2GKQeX+v0Ii;UfzMB?L?3QR{LiD0$+R3-k*y}6RT z77h-8t6LZ$Z(U3h3aD1Ws%s$y--*2bV&ll6hnHh06|P$}IndgmpjOh0KoP78rh{*_ z%y8tssdm!WzdPuDws0Qy&$O?Rh!l8g-eE!xZs4dcJ`HSVz0B7W^ zYEFBIqZea!SdAG!n>X6K0Y#a)rEsF`Y?*Ky=o6}(AJ zSdxytEczheozAzIfs-+#>A6v=&q!H0UljcyTGu7`hdu}2nb}&2=$Ko|KLq~G<{z&% zd$@wv($kT>sziD`i#|#*FpV0mV~Oap(0x`U*mpPStr3zHgbT!(|Ix*U*8B~@$RHBo zQKo2aE6D6@EW0a9;qW{yf$(BH?#Fm*V&%0#nKBY@hLi`hNdDzOY1rwZD|T~|9J``r ze~P|!qp(ort^uaT+eK%MrY7PZ5Lc1bR#k6snpL_7RWg;ktO1J z<&Nm!iJ7x9*5sB>Rb{r6UF*K`B>+{`QyJZ)hC{)quu8~mXd69&$?1T||F1r$2)~8c z;EU+mp#g}YEV8Dyqx8Z;YfFgAF0Z8$deS!f|AW2hA)#6@Fl6gAW+#oIY3s~-tfT3> zo%+$*WZ_llz9+$9%R}YgyWl7!vvq3YNMprdy3lCDGe%*4$#1X2L^bjKKvuiTahJ$Q zAr1>ZJ8^V9qkadgPRVfN+02xnh`y9^W<|c4hLSzA$ z77iv?>C8fALxJ6mu^jriG4^M(;g|1Sl+i-9%NztAh|0W`y-Z>Es>qmZINN`cjCC!e zE@1VejvVHUrn)u`Psp=g=Z#lZ*nQ2V&%^gmLQPDN=VyH>lECU{)O@@U6KQADWh-&1f{h;&C6K&PyAdm|3 zhvj=bWkfeN6%e2Z7lMWeTjVcN&4Ny9 zQ#-=JDWjmT=3;Rbbeq{c z`kYWhz15`-YMR~er77p3*Hu=nG^jN{166@tZmZ$qjAmQpEV)nd8Fj8_^eL`F$JRwq zuuxL1tJ|F?aIYjuN|kXyj$k9lvrTg%yghw4G=C57{RmMKuQ0V8?6*7Bo)`%ANG7JB z&5_V!0NX(j+_^~i3!1F2BLXDqt^&F?kL795h7}s(td5G3W|{lM4wR98_Q~UA-=Fsw z6ByHqIqf9M_I~D&{_XV+y|HrsB|@_c{mt29b=aq1BUl)+fA8v%E(@+=3WuXi{p`;7 z*;=#y$n8M0$HvwE4e-OlHga|{Pc7#2e4$G^eV@IJ<1y;UcamJ;`gOF^#o7BRt9u4h z(dy5yUOK-_g&3=r8C%Au^W0+FKV|=MbPeovZ9(+j8{1}MHbxuUZrreqZ6`N&W81c! z#%XN3NgCUF$@>N8+p}k{wFYO#onk<#T5p5|x3625Wxc0Sk-*{}`2$H(QGs*Xq|D8P zy?oipnHcmd_Xg$~6veVnr8wQWv$dB9`=^pk=n(bTn%nEnF9W`?*|BsIKaH>mk;Y=I zGqzXKl^}erWUn(hcAw$;we>w{k>0oBhAQk^NDnvtnxO8Ax-*k!#4CqiifE(Ou6AN) z!VS&peP?Izd&*8 z^+qaqsbGPa;~j-u>^!PuR(g^bf_#>w?;N<tI~d*i&qtt?!VdDO&ldywr*8UGa+7)>62qZ(x`#Eb6gm zbCrf<-%^g}B$`chrYIIiu+sUzTN_O60ve+1^zD}68nIXP$3iT(i=95nj39Q{^iHL2 zawS>W;f@Q{Ntd@{u|b!7 z3lvUckSqMt!~yk}f~;hMk66C5B8P^GD*L4w|M-Rdky2gU?wm#vn#TT!%wv%3LSQW2dF#8rT&gQw- zOa$v#OCJ}18&IT~zvkscbH#&h(UR3*LpJ>23I!~+*l`*%`O>YMUomiABRsN5BZfVY zKV-r68KfdFD}-;+ELXxjR4H!kpIBiHbjl+4ueZiBSM_TTqr@E2qJgvkeuHt-nM%4I zRABhO?@cxIz}GCcBwPvAo08~X0&}!&9Lbn@(!{1XI3g>oy~4sN(KI9x#$4q&+7ctm zGq~4*VrlA(#5^TiGedEWavwOt42CuJ0fD-9b{Xv#q$8dSa+B>?Vh1{yE5AO~YEmKc zWsdpr_L*VYro1KX^3El>;$;ytvoG7mpoj`1LQAM?>b zh2-o`XrN%fozUU_Tah*$B3Hx+J7*q?JFS^rAziZ1|InS_A{P+N?upQ*khF12Zzh7t zh90e-e_QZ(dnAd$3LU@#xro@R0zJv<-BWx#^C*Mr^yn+lAJ_kexn2DUt*u)Q6 zpbv{SnE?c;V9#oOQ*mG(v!tGBslas6AA5OLo?|~D-!TZrOPQaA+uSO(PjzehyPgH( z`euC9H@(u}p3~7619j|Uxq4VHF;fILNEM^8RdI_pdH?lpT7!iEVKBRxs*I3ftY z#9R*L_zD8+>`ufvYnF(H00K8QA6_w8XW>Tm%c8yx`y@X$hv#<6JbP@-y%}~HZV*cY z>7J+>7H+VLAPG5R^Am&z_MMqpcGP*adDeA;rX+SJ{s&JM(e7>)RM%|dzAv-OLxy+H z3l1-p3OE~-H&bm>8?Ne|qMM(tkA{H9&9FNzC>>`CfmU+P7N6yE6R6De+N@`to@k*c{O$BJUbBHj>kli~HZWfq@{DUO|M##K zjB`aVILOMzriM-4{s{faNWShUSY(JK4`0~!?`xA69R)~DgJr;t3dW}JsfoLlorJ+u zM114Cf!d*zhSaYwT}#j9o*7HQXML1VyYVg208mz2f<8$hnS$e?geVk*!#6~Yk4D16OQ$+# zAaZq~tE6TF_^@*XNNr(ILcEXy#GF^#cs|asiwes3@>gW{=~( zAhoE>XNAm-a!)${Qocgu_*bXF@?^{qbEA!YcEd07N+i$SgeslsZW(LY0Q?|~L>G|h z*l$v1N&@&#g1P}kctO2NPMlhjtT%bX-lOZ?4CXF#mBjD= z#G|3Oguw)-%$^SK9#&2Y!nPLF=YR;CHE_0Ikb`a{;*z#5gU8>e`|jc{k$J5DGf3l$ zAt_DxcfX$^E-+->gHA^i3JdYZx_Q$N@+K%mHfw>6MGdnn;Xbm@`8a{2*<&cSD%S$Z zQe0j;3N2_C>GtXpi#DZ!-{KYj)_$h^_ZP-f`dt^4pFvJ8LHl)o5=EKEei_5RdgQ@u z`#v}rBB5=q@i&U#5i1y8+CfhZkoe**K=$kFnL_{r9k+RKsmMRaKjB{dKPl0b2fJe6 zyH%P*wWk&5Z$e?RF;P4Bst$Q+>ZG@y(;$QCIoA|XWgfOzKDTYm=xnV?knH~N+R$YB z{*aL36sNKRH~3#Wom2;?mD1We>~n21Nu4h>J;{PrjBncU&e_6f5QK9dfoRqJ{eHA7 z3x5`ST@3{f%UCGcHt)X;=uxG($Zt>|8v*Jy4a)`>a1NHuiWdvwhU=E1I4%DgqqHRm z6xoGdMYFVB|JF9vr|!O$2XtI*zI8$csd*0(QAipPB>3?tR6yz0h36xPhv@_`-m{T^ zRhb2pXHKMwn~Io`g7$){-VXbwt}-Y+8$D#N3rH6_T08pjiDVabG{zKvAzL*z*9LU= zmHmjSq!*v*2}b+ln5DD>AL$ejd(uk?0G0aNe}HGTKkVWJjIa9lRQarDSshKg_eQV= zV8=$Pl$PX|a5dgZYhJy=t0~Z^Q>WLzB>HxRm?hSeqFvouAu>Htpn!Rs-JKaf=8a=< zuE4x)j5ig5266%T59{Nfk$+6ZVsGO9X-))5cT*_ehEqxYh(63;A*vDg{(t6Ic|dPC zJL>(&m7R1wi%w700im3PW0Bi1rde@WswDK{VIcv$2NpZH{(VVuV6GcT6r4A+cu4(7 z2*p5A^Ci|$s2VPq6&xvu$$p>`&V9ZKTn1EfzliMRvFe?I?Y4e@7C4LEuF&{2pKJ z=&95y?Xn#CsuQcJ)RBQW1Eon>HR$#K;N~g=U_2U3%3M;Et>XajKdkHFP&i8`uf5GY zJU72Ip;?nW3)FC*>jk2eoYt3CkwS!kF{zrAF4_<87~sOc;nn8H?#`zntJ5T`!j@(7 z@kv<#$5Tx0lQ@v+M%sdloc!^Et3eL?f!pt8Rl7CP^Ix+( zNs21z-ZdKxxCVCpeN?f#I4TOBpoj0A<}2+Uw1Bvj`mnztx9}kVVD3ayeOK5;!_qn> z&3NT3*+Szq`7=?dSqZZ8lRn=g{Bl~3#)d0-<+MMW$9V&IMr0;}^=eTw^=y4DV^`uD zN8;SfZV}G51EX6Q6A2XDroXN~=R{k#FS=laHKSF@U16Xza7Hi1H+f<4lhq5*ypZh8 zdAI2fhju=fqP5%mxso>dFF@6>iw|BW`2_5e2ygP(dt6TgI)aJ^A$q7}@v5dbCwp#| z35B!=$kI_WxqEktq6wH?`~Rat^JatO0a~L153EVghpoLhMe5XUih`I%CG4b7YN~Y) zjkITBlv0?9C%XQ{fc)l%K4S;AYzRXRnRLZ$&QUVZOx*r>8h@SedfGcTa;!JCpMkIo z7;X|zRfN~zTzTZGe*L{geX&Ckza{qUW|`%8Bd%pE7o*pu165q|Q9bE?kp~=!pr9_A z@}36E8MPz1tg~%tG5BWF*}}>Ap%vOD9+9u>%Gb++>BINKj{MAU$Vl~0HA4ha7kyWH zXe8-2Etf6p9bV>~gkfS4`org5pwMZce%7zFA6h*|Utgpf$p3kJUT|``?oDmQc8ez$ zXJo~;zK=jXhIAE#{7dQAJxir3H?W4;f@XphDl^<8?TFs2E|3}K>0!w}=Oq6p0BOh? ze&v%b%_l*~e>{%$t|OTzhmbdghs5m-2x8(seuaMiSDc7OKP%7JNtP4w(rDKM&QAVB zj;MFdqamN{SqU)#Ak?8X6!H0wN!qZNIM2^h~#fGauG%w(7yye@N*Li?K~6$ zl9T6E-#Ma+LI%j&TY*4Hr-c0ab)-|Zag29eK%6GzY$b(G~;)4_+>_WP&(UtJaV z)jl&6jMf+CRIS>{1kY3YV!Ai+?kryrmmMFbVRy?7x~Tm;obRtg5^bXP>9zQku*%_* z`Qp)|m6jF%59x4RK#LCuLYvJ4Y1tDMi@hx|4UFlM5Cn&wy%b*>c$G$9TfPSe8Eydf zo&h{5D+O718=w>4t({9;=Mpz~lC-7uG-cZJ+xt!z4BeEy$D17#DWZ7L^b8GaGm8MT zm0pB95-%lLzA`N1n{`7EjrZk{`uw>9^+%I|s|ciL0?4;Q7xhx>?tKIr&Oa3vO2s9P z*{e=LJ05`=G2Ci&k{M^{`=`-p!y`teQB;r-Mb^wee|{xn`A&&wLoSgA~#Q1=aBrf8YmvX7K6kG0d+Da*}?@q?yZNtK>4_F#h!z`(n+9*P92&x>-P8 zqsDfd^@Ndg;0Phge$%*K2hs6$z{v17Rsv&=dPQfx*aA1^1j721Fs-2Y!2$Gb)@1HZ zSjxYhq*64;2Fy_eT3>fKb)u)xptW;Hw?(+~HjM6vW@5;3_o~9Px>t}=;obh+ckDU# zufA0KBhK;?13Ki@-@YxYgW*j$7>`GH_|>@$lbY`96Vf<{8X{8EQ)+&1TW%g!K=zAa zzoc6&SaH;c#vUCd2T7=S!(LX+U=u*<1SY7WR^yMC`Z(3-6yHYo_ofxTYcUcs{bmwK zWX$!XO2GI)60+Yov$Z+1Gg|3bn4EA?Vx^~XaE;pp&~sLep+1y?Is^a*W5e)CU_RoW z$z32mG6}x@10||?O_2o#3GjUQr40?KrGPlw}bA?YL%wUBvO3G?&^KMDX;4{1X z%W^3glb`$@K7lTv=4%C^O|ty=e~<`lH!-=^2V;jPf?(Va#UyXEnr5a@gbuncmw_+- zEFLx&VMZYU^tQl!MZ6wDDccYc1UP(94iJe2m? z=8&Vt`fcU5>xs7IVc~T4nqEiLv9tg2o>5`Zm>E$(>gJ+^M^d~_TzCw2|3q1Bjv&ou zHe;`OMRTt?q&@#>}_yer$KD4(Exx=3wL0BaEeSx0hv#5 za2g+N8;y?|ZEc&M-`3U^BiP84M0@oOpl&6e1rVqf#lOUEd-JIYu+->~Z8 zTQ{g4F*vM6U`}mBc7=5gQ(pfJ4_~68Y)N~Sqm(FZS6!uf$;wHf zisbUf{@AK%7)ft2?H73V{vCey6mXJyYMGtG9Gir`nWI8e=)YRhUMcaNnA6J zxnZH=7ipHrKP|iQxLtaH?$1$6H|!im5;;|F8{x?q6EzYqY5cQ@(5 zJi&>6ZuP5t!LZJ^e~o^1n&J5OnENg;nLGaF5)3H4vxKVEo9+f(a-06yHA9G~_-7IE zN)>A|i204ir5*9A=-EUvWPy(i$t-cvZf8RKD4k;hi!1wKvgu5Rgtzqdd#<=uUwR*= zm&rX827m+jst@#G1Ckz4i*yO5I@wNac*j#!aDBP>+ZKyl&c;ahH~;U=MOF+%tKJZy zESz?fA5?slxs4n%ZWh*!7SoY395+k7(<{Plg^Eh+(j^|Jb*+_VrG<8_MQC*nGR4D3N)MxUXoBeS47M zWPEx?L4{}TjK$iVdEp_0@_E{h&|Q_ydm57@RSlY1#i`TsoV4eZ1jAt03;$mIFh2iU1P&#akRpU4QS;@UNtbEKKN2+WKM*MIOCgi&7jP@qlE@b#E5#cYBN)f^P@1e0bYis65CryOCYQAsOZs@cpNP z)(3smed02x)0H*rLIMx9>bx7ao97hHC{D||)?=eYY4`!(~tMsM?9(^mV4 z*B>Zk6c&}75G)s`&~#kC{Emrep->C;K%@ZBW3`!D!MV%fTsYWOPIxIlED0Z}Ipt)U z+{{+N`-oim_lLPB{Q=gchJcVK!jc(*-5;E|rXI~KS zsnjyYv)E2+j0&f=D{_M9AHrRZWVp*R-1ka*jQr$8z3mgil9mf@U0e@8Hq;J|yM?#Yq1O0Z<0N z0It0~n%hlQz09vGyw$i#JV2=fC)WC1>@Uuv<4+^4W2)K13nm;CVty1+cx2)HIR1C5 zZs4DXW}VGFsabM+L0_It)>;zJwnvnWxkxdY4I-@Qf?S+G(YI$K=C?N_&~I0o}`!%QmYi9pVwsz$)VSe24O~4sX7mA7(gi%W{d{ zsQ0H)?+3R8xtRAACB6^k{e08-r|wf2jqlk_U^fh~z3LE{{q4;+YVM=~*)>ikB(o#1 zcSrI~bd9Urg`XjB(d>cC0pQh=gzPvPZBOD6B)mGLZAynTG+k?rZkJB&uML>{6p{EP zlcY%|r052f)C)SyZ3@UO*b4O$1?Ap5KKZYI2sd+zB5J-P$4 z_hs{NI^i4*&0g3Z8vU_lxJ-u6juE;7=nFL$UZrqg0l6Au`4y-v=U^zrx5d~;_J_j< z(V66|$zgOt&+)ASg2^k3%cT*<<8gd%j&j&xYH^eM3?l*>TZXX zCL|qISqcFEf0+dGoc0X}c1H79dyD?#7GX;gM}Ix19L)SGG;0?VTIiZ$Td#Wxdw>5s@DA+FfAamxFq@f5^y|c zW}x{XSCS{g&(mC}E6zJSk+CK}53ykI?qZv48r|tM0MTrdD~vvhF8A1*c*Pr%wWH=_ znc3w|RYl&BpTAV7U_M1}{!sKJZMlEDL6CG}^-81l_PJ4cF(>eM1a~%gEK;0QM+x@ApnsM zxpok?^)dASGV{nTo#*?G_0f2eN1D^f9cu@byiGv(x-w=M$#K`dP~W*OS3S#*{-A5w z9edNq{%JOpxTYFk3#~O!d+W1;JT=05Iu&aB+X#u(Tl<>6Hnna%YY)uzmK; z;7#NC2K?_JQuy9@!!WxKS)t3>aD2xL@aR|eOwH2c7HD!wmIdv8XsdQ>L@TV>#oHpb zjoGr*8muVTk8h?$?0+QJ9pba~jpXjuz#+1}|H9s}-y~7Yy;cy&}^>4boAt7WMM@O)uNi2G(g|CTwlcC&wkVuschH~gCH z+HcnT!B(g*q=L+ocmVW&(GZ^WF0!A=)WlS~`d)BCb;F1Eb;<>J(L){Nzox?H-xX>) zx)_^+5MYP#u?FqKNUag_vWdm|UwV=&lhv(EbVvspsAl~QBB2g8wl-;I*O!M@p@;{D z%R||jB8bH>O}Lw;gYQItXOG8RSS^O5z1K5&$?-$ux5{fBB?-Vt%X%3>#WYZwsP0jdjYipFBZ ztmYGDwccLq9r{sKJ18n(7)C|f1}CS5&uXuachtGphyoKy`hV|+LXd=o0h_j@n7Nu+ zY>sdOc8el(v;I;cP}f*&@%hkuzf4lpyTksJQbF%{J}P)@{z7MFNHxOKMpE3oNlqOy z)EB9faTZl(*UvI)@4I0;3!4M(D)_GObpKv`3m*>RjSekh>#y)cY~F>@31pCi5_U%V zpE6E`z|3M?0L*gp@r7rt&TO{GWS`Q4j7nsIRj!>5|G&5h113ic^Zwn`@iW@*eXKM3 zz^wHPu_$cZFi$W32otrN8u^3bBrWun-+QJqOjk!;s{F7}j)fiydQZeCaSN6beNe8U z?B18$_$ES54yn1#+pXK*1L44_VApD}C+qu~l26fzO2|rvVV&+SOgvkNO~M}T_#)i} zDPAP|{I>h3rk;6U)Hr9TA+eb=OzC)7xo~(fznJ#RM^-Pr%ts{|xn#EzXF^w}YNCJ@ z1>+hIs;&~sLN9n+$f@Z zU>K#5_-9evVuaf+CF)+Y|Ic0gOX(TcWHI+sBt+x%#Gs}D%WN}x)A% zJlix5WpTDXvgLXYYtUsP3gs2P2@MJ0$q{YZ1_m5hD9FF4k6oswdeEC2@MgIVvoCXq zu%gni3in~SP0#*Am-HXPmx2!#)r$`XFqW)Rtx06-Rk_uK5IaVnK?Q=ZDGW8ucp|DD zhQg7l0;npQmo@xzcIpPP(yt$_tY#i4Z0|r18;%=S^{Wttl#xB&*tgTMj7ku{Ly46o z^4m#j7f@^LSDQ;b-keZ75&69Dg2P35%1IHAo&lf8Bt|qMHghTp01cm;BBaY5ThaS) zK;yNk@^)!J@(QeM_eOkI=yjxRjg_tW0N_+{zD3pmr*HS46!X6ww?8dznvbXU(!F8a z>kYu2&}0yBPss54e;Pw~aJhw(&mq9_sgsl6ZL1tO{$9oq`gFa7u@ur0Aa1#sL3$0D z&kKt>^S+Y0hd_dJXcff9(XW*eLB}<|30->Tb=sSb8-ipNjZ%#Zo-Hf6+jW(TA|Mxe zxk*M;;+GBcQoAz`SUZ3VgE>@bdvI0NYv{jsrcnKa3C@%ZN~EaN<2qqt>r9W8a3^+M zS!t@^^D{p_vNzo8hy9R4CHlZ4O~B;wUYp$J{Ld9P%ULtaxaszZ?l$Kgg@Cux3p0+m zo4R}7Gi4XmIDr^(FcbyV9urxxI&s_QktUxm)Mq?#+a)Ct1*^L5#5US&37CWObK_ot zJ!;0g<bC8iD$ zqOId6#As!qt`Qt=x&!$vZQNR!VnmW>BDF>gv>LC;w1RdlQp55A z5A=aJt^h?Q6&la&$+@fspFg5mKYe^mH? zlVwM6fA-T{=ft?q?kz&3AlBQ=q`!|H9PMjTr@k~9d{9R81F0b42{It;&J;@l)#UlR z_zm0XVjDHrlNwn`x?OkB?O|tKJFWx%Q2bXLP3jy(6zpyjc-*6oSH;JrhtaM*9PM)f zg+RBq?jpl-+Cg=)A=VpIc-t=(>T=H36@>a*&gRzCB(%*h-qA-MGcVLO_YO z82S9MLG-!iWYF!pezVfjKK+Heb~&$|hFiUhk;9Fc1Af#KHU8=85$Y5t5wDPOmsKg5 zXiB8JJ-NNe#Nd8$+KWz+O%1pv72FPF0sOtwtYiZe`>=(pQ6E~n>Q7N0i4Xqs@ZDa1 zrhnCxSj3z>#Z{oW%IbvB0MHF={yK79bi7)@bX<*|Ciwa3&>NLR%o0z14Ecw`GG+j1 zcJ|?9Q#A&`{U{%@ST8v0CriG1bcPqV$f&?PEsuZjE}XDJH&-=yI|+cw{F80kTn0!? zVsP~5XCWta4G$=K`Ir5JHXT+x+w=t7&z;}6J)XZWKn$DVTu~?(BRYLEGagSI4IM_2 zQ%2@hR%^V*&1ZhW-TiQqaryVZ;%1-w>G17m#V^;7an8`j&W!g=(X$XbM=#ggru{#t zsvHIw3+e$xSI#zACTZ*UOrz0;y$m|^qO#3@Gmo&YI)B1`yc)VfB}H$yy8LYW!XiZ1_gtKuj`Ks;<9}R%0LmqIQgaz&UlokN0rsY7%2RWl z2sL3;9U6Z(`x-$*Gy6YR5in${KmhDfZi$(`@`$N$_ck@pN7ZPWxCg+twZl$6_}7| z?~ga&-yjXx4*GMF4mimLWZszy+LWD~dpv7244Pfk3|QE$@tWkSxj6g`N1})oE$}~! zjqeItxW)bP`kEK42#Ac(l7Ze~m+}wyIYq@UY~E{^im+oS#Y#$COJhPjscr&XL;m%H z8m)j3OwZDZoflAejNsp55^lPWoOm`kN#Vu63s?xMc8pBo#fm0}(3|>tZjQ>(P57IZ zE)3WbRa)flz%-OjVpmy&ee464BCuYKImR+6PFXN|Lrk2K`ln4d6h*N<3~ z@N70{j$IwBaYp?a1LY%bgPIUGj1JwuZPS)Q{r{R_5biq=0G}|S7PK+pagC}1xnF4E z$INPy6>#Y0r1oyJQ5M6_n_=uZs15s)p?UY<47 zcH6&4JmN==k27QP!bS@zj;ptLGbofeS~8zw6&m0Mg3-=~f+bffl%bwY3yk}b*&Z-1Hj+DRL`_8=YIX|*^h zeG)MU%wO3dzqx2AeLYKH-OGhby%tg)zPlxeJehSA(y`IwWe&0!K36z z#p(D8Lz}GP_u}H8tG=7 zU$-TO8*(FOOTL^P<|s*!(0QkW^>NMd@7Iih3keoZqt?udXA3j}_=FD5;N9Q$|EU`I zJ|8G~R!-26(jpB3g_wv_75qZk4MMhm;!v)HiS)+r(c{RaFv;tvaD6#(Gw%-4z`$0L@K?qKv5iNHe zvrT3W-@c^kgxz_}Z_tgu%bm&co-sl0-v#hP`BDWv(=RY24BbC2k^tifp7rPHH+&Fv zH9yv;>m0XK9ZyE-73?LxYf0`yoO)HC$3t|mTLO^?Fl#wlE%^^SDBGoeoFx=)*z$J8 z27I7rEez1++aDlljS&&q#Thx^s|?d^$nV)K{~TvW z)TGKDfwu%NU27&pjnX9obp#)bkxj_3Sp*NbcCa2<%p4PP@3Xg09++~>Fkk8P4we_g zvM{`?PYY#%D4uw;5ss~JIA#%{P-9?uQmn(dZ`zsDJx;q+V8s$@K88}D54a@ySNkpi zlcI*H218SK*xH>+xrU9uIHr6{bv!|x-9Yn6*ea{A%mRLhUMv*QUn>>>=}u#k%1=zT zb11iVOn1gzh7PN)4i%@3b%{^Ao_2eGc|n&|)FZkP%Pjn_IQsPAG`#qUW~8Q%-{p%I z1(=*=^F2h<@N7Dg8jRFZfCuaPdlD5-g^JBH^B%5GNmA0S5iWLP#w$61n=nI?a4oZs z@VOWgfqMf6OffL(pX_cu9(;HgR!B#$Cso*qbmA$M@>`k=7BhG!7LXq2%s* z+|yMr|1Wd0GoFU{?E&-SQI!%y(m-0j-VrGtqseTDn*XeJ&7Y3MLH&FxJ>`YQ=FDHuGTVGtRyjOF+Fv zd1W{ch^bzHAt~+bK+xyPMAw|d?sArqou9?@S=Wy`@{1C?SF_HtltLjK;+1V3tswSIr&6xV=?oaz zCNUHtu%m~O0H4BZwz=A+50yh52hu~iRNO9BVgEU!EX>)yD521tL&RAsNX_9g zfI%%kAz6C+_Jd!4?xIfZ$Fi>av+=TX!K85KoIHY6BS6D+er9ZXXor^%G zbhaDszGnj*MIFy{`C+37{4vyDJfX>l9|u-B!{*{921ba%wNLM+DKSL#wEqv z9FCe;r1-iV))AeyRU`J9u^#Kg_7EXheQ&x6PZbkYajyaBScDq~&r&b$ca zit|L2p-@VYqoACpY#Zs269+!K#kO@pw6taz!&cu}c7x=2mu8I|6c_mQm6WHQ*9{ zH}*R}OF_c|`PvSXPi7A7ocNL~b+0K>mFqxd~%B^`(m zEufA^C5vsjC-qz0=w6mPp8szpNy7^Vbq%wS@sC^1_q=DbtW{fn@-dlB!=Cp_l{y9P z+p3p>hijFzREL<*axo+ZfDeeI4-~iulJ+pmc<%DUxBhzFf;PTWI1|~~-}bE4bSV5+ zwzN?FeZuz_B-x=oV+Nu=P+nOE;Y1+|A@=duyt2O3cq%MT^fQ*U!nq%qXytQY^d4N8 zeB}()ud0!1SlhX3EzDUtnEZl>R-bXNR5C3SPWOucaU>E26fc(r5>$5SBtIm)A38$2 z+r`r`CC+qkK<;lG2U!~;h&ib~>G>W)il#n_XbDyWP<>Y$@}fQCZx9Xqe!`#_KsHQh z6|NpOTDt$4*ls>{hJ{kZ|IAwRJn*%cXX$r%h2AC`yR&7=v!8*xuqQoDd|NTZ2ZG7z z1G`Y(JP;gLifs{WYdzT*EOfof)aCX1Ok>PcDi3*PWiv_7)9_6>nXPqSFI$NiHnD{& z9i|x#NrzH$AvzRz3*I=*?SEy424>UbGB)b60)>HlTzEnU`qa?pV|Nhq$-Rr_R2K;qD#hkW zv&$Q*uZcC%cs<-hHmOTE%$pV`E%#750YmOk(MChsKKWjTL%A?W15Wp1k_GlXqp$Wv zzh<7u2Th%C1&O;#MmAbCCR-(S8RP^XH!SsS@*^dcy(M85cJLnCn}C5dMdz4ET19p=1IBCU0Y34FTA`U8 zsRE@85i7|Li|Wb&6`s^Y(S_Q+fI=12 zf!+jgjaX_>9OMRZf5V$V(f{A@dyq`w&X4%_wqP_v;Bg6?Mvy5+UQa2>%GX-*QT zJVVmNUoF|O$Z9%q3zR!$r~$2*^BKz&S}DFzj(57h+Gt8DJ5{}u4hl}f!84p~m()yV zka-$E6gaJ$AcTQ`&}mmh1##AGOSIM;tn~apiHE{slm;R}0+1i;DiB~kIP#w>YWC+2 zL;&RwNc?JsjCcYlyqTR>;?*#B2m<{i3_ccd;d4;0&=F^f`5vh9vTj{LG6C7H#~7-6lAw> z@+10qVbT%GhYEA_gL;;P0xR1SnR1G{I)#eL(g+(@?@N6S&Lz}z8frCj0*U9y%$5rwnS#hb*tN+6*$1k2s8Bj}06$458~R3v7hzT9jpm&h@?vQ8{B zLsZ@vQM{*HzxabdG7kd(^3o2D>%Cu|!3+}D>tkf&w5Y%6_IUq|Zh0tB zKX~!-|2BgOf4~@60T8V%NWWfwsBV1SKrKl9j1BP$K6Zeby|A;My*JEPP#+VVZNA>T zxephw1{=@wMy~WDxXRQ!J6CQo7U7vh^`Ix-=22l1vk=J|0%4&R(e61dF7@}m&3peX z&o~p*>zWYLY3b1((bC3Ne>+4AU5TtG-la2G^M9*#;7u#2f zUp@8FnNUB6G_pVtS+F5PKZbw*fGqxvhHLy&c_uT9+jn+X%%G!p5@Qku8{iHNm@E21 z#gw(pPWIM$Z$(p~O3P*5)ImP)uTn7ZOtBJg3pJ!uHkA35`68LNwFklc@48Bp%HGnD zvBgGt=HCOaMz!M{tEzvSOh$Nma5-Rl8*R+j-D~hw7W9t=G|H+yo1)`HHm?nN`$d7Z z<6Z9mvm~!Dz-2;RKs21e?)~=j-dJ6M)^Qxtas!jVODHCxEf+bG9m6F0nHm_`oY7V5 zK|SgLc%oJn*%D6qj5(l!*b5!(QPQ=WG-{$!<%Luta6!IU5u^wXew*@qAj@#JPU1`W zSE*$YhESrvSf{-Zvo0UVP$y#k;bFJ{1XB?Zc#=?JSb~tZ)xGbtnr~r-&_=6B(dZ#e zz7r)s+6-(F1rVv|kkNE%i&()w0#*bXYoiruoV_9LEJ95~thVeJn|kA?(&r3L*S$ffg78j(fVZwpu`OfCkX@~TItwDm4+^3tE$GD00o@6eeU_X7Ew& z$k^wd{unTfaazvQ{$^l-mv~lLZDZbyfbY*|%bUw1V`*oo@RKW%TN)CuV}mQn!~|0_ z$X;TO}!L5{&_~hs_*%v~CR$4^8TCTYgn#q1sj2aUdgh zTsRxY#?*;2z|h5Lc6yB&VM1XucRi^GVZ05>oB7UfY}g#$<{C|e^clmOv@RiLaG8MM zK4B_$&S5K+GqZ6fexvFevC8z?>`LG#__-msR^Q|!`NR+X5n_mUgHA1h*^MJ#@oT1h zeGP`iQ;G>$-w}xPrsI$P#=I&lM_^SW<)n(E!-80IDkej>R=h}MCafU$N(`gB5v;_g zSbCavMqi1&$_`v@{dJ^w)MoH0e}Hp(zyV)27#_LaBh68dl6Zf@i@E-?KarU)*}c5? zZtQCzu$8V-8}AjQmLp=0EWg>6kWVUodOX% z`{t7>&%=Eud$dySES3pE*#m>{a*S4ggx-uumVmO*d+Jdye_<;(7e|-fqJweElE)#J zC^Y(&Zp)4j1lX)C@(}HLZ{6u$^apr_*#Z8|CoQ;8x2>1`9BHqi#Vc{CNuZUaIG?P< zFxYe4n8?6@wdsDMj42Z}lF_@9D6$jt|7xC#bU9I)X ziz!`0CQk$gFw$wv%u&O?i+3~JG7Ry3Aw88(Rc@7w>roj1nb*1*LQ%0A&e1qErOwfM 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%0DYI9;5PX*DkFgl`ApG=a(AmHx>W2PGCef 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 ?*P;xLSmft+f-sD>q#W*}5Tm^{-48pv7c-AM^n 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�znTxNLIBD8&TY8B41|8NenD#Qxb9AN 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�JFOi#k#23}q6?4pV)2UaJ1Zs{BY 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 literal 0 HcmV?d00001 diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp.gz new file mode 100644 index 0000000000000000000000000000000000000000..82d5271361e21f5094b58ada0c83e203b81e3d48 GIT binary patch literal 126219 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 literal 0 HcmV?d00001 diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index b8fa0f8f86..69367cb6cd 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -42,10 +42,14 @@ type Error struct { err error } -func (e *Error) Unwrap() error { return e.err } -func (e *Error) Error() string { return e.err.Error() } -func (e *Error) Code() int { return -1 } -func (e *Error) GoString() string { return e.Error() } +func (e *Error) Unwrap() error { return e.err } +func (e *Error) Error() string { return e.err.Error() } +func (e *Error) Code() int { return -1 } +func (e *Error) String() string { return e.Error() } + +func errorf(format string, args ...interface{}) *Error { + return &Error{fmt.Errorf(format, args...)} +} // Hello is the RLP structure of the protocol handshake. type Hello struct { @@ -174,7 +178,7 @@ type Conn struct { func (c *Conn) Read() Message { code, rawData, _, err := c.Conn.Read() if err != nil { - return &Error{fmt.Errorf("could not read from connection: %v", err)} + return errorf("could not read from connection: %v", err) } var msg Message @@ -202,20 +206,22 @@ func (c *Conn) Read() Message { case (NewBlockHashes{}).Code(): msg = new(NewBlockHashes) default: - return &Error{fmt.Errorf("invalid message code: %d", code)} + return errorf("invalid message code: %d", code) } if err := rlp.DecodeBytes(rawData, msg); err != nil { - return &Error{fmt.Errorf("could not rlp decode message: %v", err)} + return errorf("could not rlp decode message: %v", err) } - return msg } // ReadAndServe serves GetBlockHeaders requests while waiting // on another message from the node. -func (c *Conn) ReadAndServe(chain *Chain) Message { - for { +func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message { + start := time.Now() + for time.Since(start) < timeout { + timeout := time.Now().Add(10 * time.Second) + c.SetReadDeadline(timeout) switch msg := c.Read().(type) { case *Ping: c.Write(&Pong{}) @@ -223,16 +229,17 @@ func (c *Conn) ReadAndServe(chain *Chain) Message { req := *msg headers, err := chain.GetHeaders(req) if err != nil { - return &Error{fmt.Errorf("could not get headers for inbound header request: %v", err)} + return errorf("could not get headers for inbound header request: %v", err) } if err := c.Write(headers); err != nil { - return &Error{fmt.Errorf("could not write to connection: %v", err)} + return errorf("could not write to connection: %v", err) } default: return msg } } + return errorf("no message received within %v", timeout) } func (c *Conn) Write(msg Message) error { @@ -308,7 +315,7 @@ loop: switch msg := c.Read().(type) { case *Status: if msg.Head != chain.blocks[chain.Len()-1].Hash() { - t.Fatalf("wrong head in status: %v", msg.Head) + t.Fatalf("wrong head block in status: %s", msg.Head.String()) } if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { t.Fatalf("wrong TD in status: %v", msg.TD) @@ -324,7 +331,7 @@ loop: c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error // (PINGs should not be a response upon fresh connection) default: - t.Fatalf("bad status message: %#v", msg) + t.Fatalf("bad status message: %s", pretty.Sdump(msg)) } } // make sure eth protocol version is set for negotiation @@ -366,7 +373,7 @@ func (c *Conn) waitForBlock(block *types.Block) error { } time.Sleep(100 * time.Millisecond) default: - return fmt.Errorf("invalid message: %v", msg) + return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) } } } From 175506e7fd9601b66355b5cd3f7df4256637586f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 4 Nov 2020 19:29:24 +0100 Subject: [PATCH 116/948] core/types, rlp: optimize derivesha (#21728) This PR contains a minor optimization in derivesha, by exposing the RLP int-encoding and making use of it to write integers directly to a buffer (an RLP integer is known to never require more than 9 bytes total). rlp.AppendUint64 might be useful in other places too. The code assumes, just as before, that the hasher (a trie) will copy the key internally, which it does when doing keybytesToHex(key). Co-authored-by: Felix Lange --- core/types/derive_sha.go | 20 +++++------ rlp/raw.go | 71 ++++++++++++++++++++++++++++++++++++++++ rlp/raw_test.go | 38 +++++++++++++++++++++ 3 files changed, 117 insertions(+), 12 deletions(-) diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go index 51b8506bce..51a10f3f3d 100644 --- a/core/types/derive_sha.go +++ b/core/types/derive_sha.go @@ -17,8 +17,6 @@ package types import ( - "bytes" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rlp" ) @@ -37,26 +35,24 @@ type Hasher interface { func DeriveSha(list DerivableList, hasher Hasher) common.Hash { hasher.Reset() - keybuf := new(bytes.Buffer) // StackTrie requires values to be inserted in increasing // hash order, which is not the order that `list` provides // hashes in. This insertion sequence ensures that the // order is correct. + + var buf []byte for i := 1; i < list.Len() && i <= 0x7f; i++ { - keybuf.Reset() - rlp.Encode(keybuf, uint(i)) - hasher.Update(keybuf.Bytes(), list.GetRlp(i)) + buf = rlp.AppendUint64(buf[:0], uint64(i)) + hasher.Update(buf, list.GetRlp(i)) } if list.Len() > 0 { - keybuf.Reset() - rlp.Encode(keybuf, uint(0)) - hasher.Update(keybuf.Bytes(), list.GetRlp(0)) + buf = rlp.AppendUint64(buf[:0], 0) + hasher.Update(buf, list.GetRlp(0)) } for i := 0x80; i < list.Len(); i++ { - keybuf.Reset() - rlp.Encode(keybuf, uint(i)) - hasher.Update(keybuf.Bytes(), list.GetRlp(i)) + buf = rlp.AppendUint64(buf[:0], uint64(i)) + hasher.Update(buf, list.GetRlp(i)) } return hasher.Hash() } diff --git a/rlp/raw.go b/rlp/raw.go index c2a8517f62..3071e99cab 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -180,3 +180,74 @@ func readSize(b []byte, slen byte) (uint64, error) { } return s, nil } + +// AppendUint64 appends the RLP encoding of i to b, and returns the resulting slice. +func AppendUint64(b []byte, i uint64) []byte { + if i == 0 { + return append(b, 0x80) + } else if i < 128 { + return append(b, byte(i)) + } + switch { + case i < (1 << 8): + return append(b, 0x81, byte(i)) + case i < (1 << 16): + return append(b, 0x82, + byte(i>>8), + byte(i), + ) + case i < (1 << 24): + return append(b, 0x83, + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 32): + return append(b, 0x84, + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 40): + return append(b, 0x85, + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + + case i < (1 << 48): + return append(b, 0x86, + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + case i < (1 << 56): + return append(b, 0x87, + byte(i>>48), + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + + default: + return append(b, 0x88, + byte(i>>56), + byte(i>>48), + byte(i>>40), + byte(i>>32), + byte(i>>24), + byte(i>>16), + byte(i>>8), + byte(i), + ) + } +} diff --git a/rlp/raw_test.go b/rlp/raw_test.go index cdae4ff088..c976c4f734 100644 --- a/rlp/raw_test.go +++ b/rlp/raw_test.go @@ -21,6 +21,7 @@ import ( "io" "reflect" "testing" + "testing/quick" ) func TestCountValues(t *testing.T) { @@ -239,3 +240,40 @@ func TestReadSize(t *testing.T) { } } } + +func TestAppendUint64(t *testing.T) { + tests := []struct { + input uint64 + slice []byte + output string + }{ + {0, nil, "80"}, + {1, nil, "01"}, + {2, nil, "02"}, + {127, nil, "7F"}, + {128, nil, "8180"}, + {129, nil, "8181"}, + {0xFFFFFF, nil, "83FFFFFF"}, + {127, []byte{1, 2, 3}, "0102037F"}, + {0xFFFFFF, []byte{1, 2, 3}, "01020383FFFFFF"}, + } + + for _, test := range tests { + x := AppendUint64(test.slice, test.input) + if !bytes.Equal(x, unhex(test.output)) { + t.Errorf("AppendUint64(%v, %d): got %x, want %s", test.slice, test.input, x, test.output) + } + } +} + +func TestAppendUint64Random(t *testing.T) { + fn := func(i uint64) bool { + enc, _ := EncodeToBytes(i) + encAppend := AppendUint64(nil, i) + return bytes.Equal(enc, encAppend) + } + config := quick.Config{MaxCountScale: 50} + if err := quick.Check(fn, &config); err != nil { + t.Fatal(err) + } +} From 6cfe494276c097c0e6f5ca31092da747b44943e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 5 Nov 2020 11:52:35 +0200 Subject: [PATCH 117/948] build: stop verbose output to keep travis from overflowing --- build/ci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci.go b/build/ci.go index ab153eb85e..1395df56c0 100644 --- a/build/ci.go +++ b/build/ci.go @@ -980,7 +980,7 @@ func doXCodeFramework(cmdline []string) { if *deploy != "" { meta := newPodMetadata(env, archive) build.Render("build/pod.podspec", "Geth.podspec", 0755, meta) - build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings", "--verbose") + build.MustRunCommand("pod", *deploy, "push", "Geth.podspec", "--allow-warnings") } } From 81ff7000775b279e9c6ccf2ca3a3ebd6f406a2f0 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 9 Nov 2020 11:48:18 +0100 Subject: [PATCH 118/948] consensus/ethash: fix the percentage progress report --- consensus/ethash/algorithm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go index d6c871092e..e79c702dc8 100644 --- a/consensus/ethash/algorithm.go +++ b/consensus/ethash/algorithm.go @@ -295,7 +295,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { var pend sync.WaitGroup pend.Add(threads) - var progress uint32 + var progress uint64 for i := 0; i < threads; i++ { go func(id int) { defer pend.Done() @@ -311,7 +311,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { limit = uint32(size / hashBytes) } // Calculate the dataset segment - percent := uint32(size / hashBytes / 100) + percent := size / hashBytes / 100 for index := first; index < limit; index++ { item := generateDatasetItem(cache, index, keccak512) if swapped { @@ -319,8 +319,8 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { } copy(dataset[index*hashBytes:], item) - if status := atomic.AddUint32(&progress, 1); status%percent == 0 { - logger.Info("Generating DAG in progress", "percentage", uint64(status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start))) + if status := atomic.AddUint64(&progress, 1); status%percent == 0 { + logger.Info("Generating DAG in progress", "percentage", (status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start))) } } }(i) From 7b7b327ff294201fa4f7cf460c4a73a4212c06cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 9 Nov 2020 16:03:58 +0200 Subject: [PATCH 119/948] core/state/snapshot: update generator marker in sync with flushes --- core/blockchain_snapshot_test.go | 4 +-- core/state/snapshot/generate.go | 48 ++++++++++++++++++++++++++++++-- core/state/snapshot/journal.go | 30 ++++++-------------- core/state/snapshot/snapshot.go | 18 ++---------- 4 files changed, 58 insertions(+), 42 deletions(-) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 5a986bf52c..e8d3b2470a 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -675,7 +675,7 @@ func testSnapshot(t *testing.T, tt *snapshotTest) { if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } - // Set the flag for writing legacy journal if ncessary + // Set the flag for writing legacy journal if necessary if tt.legacy { chain.writeLegacyJournal = true } @@ -708,7 +708,6 @@ func testSnapshot(t *testing.T, tt *snapshotTest) { } else if tt.gapped > 0 { // Insert blocks without enabling snapshot if gapping is required. chain.Stop() - gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, gendb, tt.gapped, func(i int, b *BlockGen) {}) // Insert a few more blocks without enabling snapshot @@ -766,6 +765,7 @@ func testSnapshot(t *testing.T, tt *snapshotTest) { defer chain.Stop() } else { chain.Stop() + // Restart the chain normally chain, err = NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil) if err != nil { diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 566f7d94a8..92c7640c40 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -19,6 +19,7 @@ package snapshot import ( "bytes" "encoding/binary" + "fmt" "math/big" "time" @@ -116,6 +117,38 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i return base } +// journalProgress persists the generator stats into the database to resume later. +func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) { + // Write out the generator marker. Note it's a standalone disk layer generator + // which is not mixed with journal. It's ok if the generator is persisted while + // journal is not. + entry := journalGenerator{ + Done: marker == nil, + Marker: marker, + } + if stats != nil { + entry.Wiping = (stats.wiping != nil) + entry.Accounts = stats.accounts + entry.Slots = stats.slots + entry.Storage = uint64(stats.storage) + } + blob, err := rlp.EncodeToBytes(entry) + if err != nil { + panic(err) // Cannot happen, here to catch dev errors + } + var logstr string + switch len(marker) { + case 0: + logstr = "done" + case common.HashLength: + logstr = fmt.Sprintf("%#x", marker) + default: + logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:]) + } + log.Debug("Journalled generator progress", "progress", logstr) + rawdb.WriteSnapshotGenerator(db, blob) +} + // generate is a background thread that iterates over the state and storage tries, // constructing the state snapshot. All the arguments are purely for statistics // gethering and logging, since the method surfs the blocks as they arrive, often @@ -187,11 +220,15 @@ func (dl *diskLayer) generate(stats *generatorStats) { if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { // Only write and set the marker if we actually did something useful if batch.ValueSize() > 0 { + // Ensure the generator entry is in sync with the data + marker := accountHash[:] + journalProgress(batch, marker, stats) + batch.Write() batch.Reset() dl.lock.Lock() - dl.genMarker = accountHash[:] + dl.genMarker = marker dl.lock.Unlock() } if abort != nil { @@ -228,11 +265,15 @@ func (dl *diskLayer) generate(stats *generatorStats) { if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { // Only write and set the marker if we actually did something useful if batch.ValueSize() > 0 { + // Ensure the generator entry is in sync with the data + marker := append(accountHash[:], storeIt.Key...) + journalProgress(batch, marker, stats) + batch.Write() batch.Reset() dl.lock.Lock() - dl.genMarker = append(accountHash[:], storeIt.Key...) + dl.genMarker = marker dl.lock.Unlock() } if abort != nil { @@ -264,6 +305,9 @@ func (dl *diskLayer) generate(stats *generatorStats) { } // Snapshot fully generated, set the marker to nil if batch.ValueSize() > 0 { + // Ensure the generator entry is in sync with the data + journalProgress(batch, nil, stats) + batch.Write() } log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 3fbecdefdc..178ba08902 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -276,8 +276,8 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r) } -// Journal writes the persistent layer generator stats into a buffer to be stored -// in the database as the snapshot journal. +// Journal terminates any in-progress snapshot generation, also implicitly pushing +// the progress into the database. func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { // If the snapshot is currently being generated, abort it var stats *generatorStats @@ -296,25 +296,10 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { if dl.stale { return common.Hash{}, ErrSnapshotStale } - // Write out the generator marker. Note it's a standalone disk layer generator - // which is not mixed with journal. It's ok if the generator is persisted while - // journal is not. - entry := journalGenerator{ - Done: dl.genMarker == nil, - Marker: dl.genMarker, - } - if stats != nil { - entry.Wiping = (stats.wiping != nil) - entry.Accounts = stats.accounts - entry.Slots = stats.slots - entry.Storage = uint64(stats.storage) - } - blob, err := rlp.EncodeToBytes(entry) - if err != nil { - return common.Hash{}, err - } - log.Debug("Journalled disk layer", "root", dl.root, "complete", dl.genMarker == nil) - rawdb.WriteSnapshotGenerator(dl.diskdb, blob) + // Ensure the generator stats is written even if none was ran this cycle + journalProgress(dl.diskdb, dl.genMarker, stats) + + log.Debug("Journalled disk layer", "root", dl.root) return dl.root, nil } @@ -401,6 +386,7 @@ func (dl *diskLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { entry.Slots = stats.slots entry.Storage = uint64(stats.storage) } + log.Debug("Legacy journalled disk layer", "root", dl.root) if err := rlp.Encode(buffer, entry); err != nil { return common.Hash{}, err } @@ -455,6 +441,6 @@ func (dl *diffLayer) LegacyJournal(buffer *bytes.Buffer) (common.Hash, error) { if err := rlp.Encode(buffer, storage); err != nil { return common.Hash{}, err } - log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root()) + log.Debug("Legacy journalled disk layer", "root", dl.root, "parent", dl.parent.Root()) return base, nil } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 6ad4451ea3..60b4158b56 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -512,22 +512,8 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // Update the snapshot block marker and write any remainder data rawdb.WriteSnapshotRoot(batch, bottom.root) - // Write out the generator marker - entry := journalGenerator{ - Done: base.genMarker == nil, - Marker: base.genMarker, - } - if stats != nil { - entry.Wiping = (stats.wiping != nil) - entry.Accounts = stats.accounts - entry.Slots = stats.slots - entry.Storage = uint64(stats.storage) - } - blob, err := rlp.EncodeToBytes(entry) - if err != nil { - panic(fmt.Sprintf("Failed to RLP encode generator %v", err)) - } - rawdb.WriteSnapshotGenerator(batch, blob) + // Write out the generator progress marker and report + journalProgress(batch, base.genMarker, stats) // Flush all the updates in the single db operation. Ensure the // disk layer transition is atomic. From 81678971dbd578751896c71f8724fb61f8f22ad7 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 9 Nov 2020 15:08:12 +0100 Subject: [PATCH 120/948] trie, tests/fuzzers: implement a stacktrie fuzzer + stacktrie fixes (#21799) * trie: fix error in stacktrie not committing small roots * fuzzers: make trie-fuzzer use correct returnvalues * trie: improved tests * tests/fuzzers: fuzzer for stacktrie vs regular trie * test/fuzzers: make stacktrie fuzzer use 32-byte keys * trie: fix error in stacktrie with small nodes * trie: add (skipped) testcase for stacktrie * tests/fuzzers: address review comments for stacktrie fuzzer * trie: fix docs in stacktrie --- tests/fuzzers/stacktrie/debug/main.go | 23 +++ tests/fuzzers/stacktrie/trie_fuzzer.go | 197 +++++++++++++++++++++++++ tests/fuzzers/trie/trie-fuzzer.go | 11 +- trie/stacktrie.go | 39 +++-- trie/stacktrie_test.go | 49 ++++++ trie/trie_test.go | 36 +++++ 6 files changed, 341 insertions(+), 14 deletions(-) create mode 100644 tests/fuzzers/stacktrie/debug/main.go create mode 100644 tests/fuzzers/stacktrie/trie_fuzzer.go diff --git a/tests/fuzzers/stacktrie/debug/main.go b/tests/fuzzers/stacktrie/debug/main.go new file mode 100644 index 0000000000..1ec28a8ef1 --- /dev/null +++ b/tests/fuzzers/stacktrie/debug/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/tests/fuzzers/stacktrie" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: debug ") + os.Exit(1) + } + crasher := os.Args[1] + data, err := ioutil.ReadFile(crasher) + if err != nil { + fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) + os.Exit(1) + } + stacktrie.Debug(data) +} diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go new file mode 100644 index 0000000000..a072ff772d --- /dev/null +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -0,0 +1,197 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package stacktrie + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "hash" + "io" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +type fuzzer struct { + input io.Reader + exhausted bool + debugging bool +} + +func (f *fuzzer) read(size int) []byte { + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +func (f *fuzzer) readSlice(min, max int) []byte { + var a uint16 + binary.Read(f.input, binary.LittleEndian, &a) + size := min + int(a)%(max-min) + out := make([]byte, size) + if _, err := f.input.Read(out); err != nil { + f.exhausted = true + } + return out +} + +// spongeDb is a dummy db backend which accumulates writes in a sponge +type spongeDb struct { + sponge hash.Hash + debug bool +} + +func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") } +func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") } +func (s *spongeDb) Delete(key []byte) error { panic("implement me") } +func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} } +func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") } +func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } +func (s *spongeDb) Close() error { return nil } + +func (s *spongeDb) Put(key []byte, value []byte) error { + if s.debug { + fmt.Printf("db.Put %x : %x\n", key, value) + } + s.sponge.Write(key) + s.sponge.Write(value) + return nil +} +func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } + +// spongeBatch is a dummy batch which immediately writes to the underlying spongedb +type spongeBatch struct { + db *spongeDb +} + +func (b *spongeBatch) Put(key, value []byte) error { + b.db.Put(key, value) + return nil +} +func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } +func (b *spongeBatch) ValueSize() int { return 100 } +func (b *spongeBatch) Write() error { return nil } +func (b *spongeBatch) Reset() {} +func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil } + +type kv struct { + k, v []byte +} +type kvs []kv + +func (k kvs) Len() int { + return len(k) +} + +func (k kvs) Less(i, j int) bool { + return bytes.Compare(k[i].k, k[j].k) < 0 +} + +func (k kvs) Swap(i, j int) { + k[j], k[i] = k[i], k[j] +} + +// The function must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise +// other values are reserved for future use. +func Fuzz(data []byte) int { + f := fuzzer{ + input: bytes.NewReader(data), + exhausted: false, + } + return f.fuzz() +} + +func Debug(data []byte) int { + f := fuzzer{ + input: bytes.NewReader(data), + exhausted: false, + debugging: true, + } + return f.fuzz() +} + +func (f *fuzzer) fuzz() int { + + // This spongeDb is used to check the sequence of disk-db-writes + var ( + spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()} + dbA = trie.NewDatabase(spongeA) + trieA, _ = trie.New(common.Hash{}, dbA) + spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()} + trieB = trie.NewStackTrie(spongeB) + vals kvs + useful bool + maxElements = 10000 + ) + // Fill the trie with elements + for i := 0; !f.exhausted && i < maxElements; i++ { + k := f.read(32) + v := f.readSlice(1, 500) + if f.exhausted { + // If it was exhausted while reading, the value may be all zeroes, + // thus 'deletion' which is not supported on stacktrie + break + } + vals = append(vals, kv{k: k, v: v}) + trieA.Update(k, v) + useful = true + } + if !useful { + return 0 + } + // Flush trie -> database + rootA, err := trieA.Commit(nil) + if err != nil { + panic(err) + } + // Flush memdb -> disk (sponge) + dbA.Commit(rootA, false, nil) + + // Stacktrie requires sorted insertion + sort.Sort(vals) + for _, kv := range vals { + if f.debugging { + fmt.Printf("{\"0x%x\" , \"0x%x\"} // stacktrie.Update\n", kv.k, kv.v) + } + trieB.Update(kv.k, kv.v) + } + rootB := trieB.Hash() + if _, err := trieB.Commit(); err != nil { + panic(err) + } + if rootA != rootB { + panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB)) + } + sumA := spongeA.sponge.Sum(nil) + sumB := spongeB.sponge.Sum(nil) + if !bytes.Equal(sumA, sumB) { + panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB)) + } + return 1 +} diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go index 9818838053..762ab5f347 100644 --- a/tests/fuzzers/trie/trie-fuzzer.go +++ b/tests/fuzzers/trie/trie-fuzzer.go @@ -122,15 +122,22 @@ func Generate(input []byte) randTest { return steps } +// The function must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise +// other values are reserved for future use. func Fuzz(input []byte) int { program := Generate(input) if len(program) == 0 { - return -1 + return 0 } if err := runRandTest(program); err != nil { panic(err) } - return 0 + return 1 } func runRandTest(rt randTest) error { diff --git a/trie/stacktrie.go b/trie/stacktrie.go index ffccbbf4ac..575a04022f 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -314,19 +314,22 @@ func (st *StackTrie) hash() { panic(err) } case extNode: + st.children[0].hash() h = newHasher(false) defer returnHasherToPool(h) h.tmp.Reset() - st.children[0].hash() - // This is also possible: - //sz := hexToCompactInPlace(st.key) - //n := [][]byte{ - // st.key[:sz], - // st.children[0].val, - //} - n := [][]byte{ - hexToCompact(st.key), - st.children[0].val, + var valuenode node + if len(st.children[0].val) < 32 { + valuenode = rawNode(st.children[0].val) + } else { + valuenode = hashNode(st.children[0].val) + } + n := struct { + Key []byte + Val node + }{ + Key: hexToCompact(st.key), + Val: valuenode, } if err := rlp.Encode(&h.tmp, n); err != nil { panic(err) @@ -406,6 +409,18 @@ func (st *StackTrie) Commit() (common.Hash, error) { return common.Hash{}, ErrCommitDisabled } st.hash() - h := common.BytesToHash(st.val) - return h, nil + if len(st.val) != 32 { + // If the node's RLP isn't 32 bytes long, the node will not + // be hashed (and committed), and instead contain the rlp-encoding of the + // node. For the top level node, we need to force the hashing+commit. + ret := make([]byte, 32) + h := newHasher(false) + defer returnHasherToPool(h) + h.sha.Reset() + h.sha.Write(st.val) + h.sha.Read(ret) + st.db.Put(ret, st.val) + return common.BytesToHash(ret), nil + } + return common.BytesToHash(st.val), nil } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index 26e3bade27..d4488b4029 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -240,3 +240,52 @@ func TestDerivableList(t *testing.T) { } } } + +// TestUpdateSmallNodes tests a case where the leaves are small (both key and value), +// which causes a lot of node-within-node. This case was found via fuzzing. +func TestUpdateSmallNodes(t *testing.T) { + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + kvs := []struct { + K string + V string + }{ + {"63303030", "3041"}, // stacktrie.Update + {"65", "3000"}, // stacktrie.Update + } + for _, kv := range kvs { + nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + } + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} + +// TestUpdateVariableKeys contains a case which stacktrie fails: when keys of different +// sizes are used, and the second one has the same prefix as the first, then the +// stacktrie fails, since it's unable to 'expand' on an already added leaf. +// For all practical purposes, this is fine, since keys are fixed-size length +// in account and storage tries. +// +// The test is marked as 'skipped', and exists just to have the behaviour documented. +// This case was found via fuzzing. +func TestUpdateVariableKeys(t *testing.T) { + t.SkipNow() + st := NewStackTrie(nil) + nt, _ := New(common.Hash{}, NewDatabase(memorydb.New())) + kvs := []struct { + K string + V string + }{ + {"0x33303534636532393561313031676174", "303030"}, + {"0x3330353463653239356131303167617430", "313131"}, + } + for _, kv := range kvs { + nt.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + st.TryUpdate(common.FromHex(kv.K), common.FromHex(kv.V)) + } + if nt.Hash() != st.Hash() { + t.Fatalf("error %x != %x", st.Hash(), nt.Hash()) + } +} diff --git a/trie/trie_test.go b/trie/trie_test.go index 539451fbf4..682dec157c 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -853,6 +853,42 @@ func TestCommitSequenceStackTrie(t *testing.T) { } } +// TestCommitSequenceSmallRoot tests that a trie which is essentially only a +// small (<32 byte) shortnode with an included value is properly committed to a +// database. +// This case might not matter, since in practice, all keys are 32 bytes, which means +// that even a small trie which contains a leaf will have an extension making it +// not fit into 32 bytes, rlp-encoded. However, it's still the correct thing to do. +func TestCommitSequenceSmallRoot(t *testing.T) { + s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} + db := NewDatabase(s) + trie, _ := New(common.Hash{}, db) + // Another sponge is used for the stacktrie commits + stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} + stTrie := NewStackTrie(stackTrieSponge) + // Add a single small-element to the trie(s) + key := make([]byte, 5) + key[0] = 1 + trie.TryUpdate(key, []byte{0x1}) + stTrie.TryUpdate(key, []byte{0x1}) + // Flush trie -> database + root, _ := trie.Commit(nil) + // Flush memdb -> disk (sponge) + db.Commit(root, false, nil) + // And flush stacktrie -> disk + stRoot, err := stTrie.Commit() + if err != nil { + t.Fatalf("Failed to commit stack trie %v", err) + } + if stRoot != root { + t.Fatalf("root wrong, got %x exp %x", stRoot, root) + } + fmt.Printf("root: %x\n", stRoot) + if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) { + t.Fatalf("test, disk write sequence wrong:\ngot %x exp %x\n", got, exp) + } +} + // BenchmarkCommitAfterHashFixedSize benchmarks the Commit (after Hash) of a fixed number of updates to a trie. // This benchmark is meant to capture the difference on efficiency of small versus large changes. Typically, // storage tries are small (a couple of entries), whereas the full post-block account trie update is large (a couple From eb694ea7067b275058d41061bf175ef2da62491d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 9 Nov 2020 17:39:42 +0200 Subject: [PATCH 121/948] travis: drop Go 1.13 builders as it's not supported any more --- .travis.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 245f52b362..ec63963cef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,16 +24,6 @@ jobs: script: - go run build/ci.go lint - - stage: build - os: linux - dist: xenial - go: 1.13.x - env: - - GO111MODULE=on - script: - - go run build/ci.go install - - go run build/ci.go test -coverage $TEST_PACKAGES - - stage: build os: linux dist: xenial From 1143dc6e295e6f312b18542b3454f52ccd46f53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 9 Nov 2020 17:43:54 +0200 Subject: [PATCH 122/948] build: stop building for Ubuntu Eoan, not supported any more --- build/ci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ci.go b/build/ci.go index 1395df56c0..9522d29e4c 100644 --- a/build/ci.go +++ b/build/ci.go @@ -135,11 +135,11 @@ var ( // Note: artful is unsupported because it was officially deprecated on Launchpad. // Note: cosmic is unsupported because it was officially deprecated on Launchpad. // Note: disco is unsupported because it was officially deprecated on Launchpad. + // Note: eoan is unsupported because it was officially deprecated on Launchpad. debDistroGoBoots = map[string]string{ "trusty": "golang-1.11", "xenial": "golang-go", "bionic": "golang-go", - "eoan": "golang-go", "focal": "golang-go", "groovy": "golang-go", } From c52dfd55fb375a798ef2e3d2620dbb38d83624c1 Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 10 Nov 2020 21:19:44 +0800 Subject: [PATCH 123/948] p2p/simulations/adapters/exec: fix some issues (#21801) - Remove the ws:// prefix from the status endpoint since the ws:// is already included in the stack.WSEndpoint(). - Don't register the services again in the node start. Registration is already done in the initialization stage. - Expose admin namespace via websocket. This namespace is necessary for connecting the peers via websocket. - Offer logging relevant options for exec adapter. It's really painful to mix all log output in the single console. So this PR offers two additional options for exec adapter in this case testers can config the log output(e.g. file output) and log level for each p2p node. --- p2p/simulations/adapters/exec.go | 54 +++++++++++++++++++++++++++---- p2p/simulations/adapters/types.go | 24 ++++++++++++-- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 7ef908814d..0ed3deab38 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -184,7 +184,19 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { if err != nil { return fmt.Errorf("error generating node config: %s", err) } - + // expose the admin namespace via websocket if it's not enabled + exposed := confCopy.Stack.WSExposeAll + if !exposed { + for _, api := range confCopy.Stack.WSModules { + if api == "admin" { + exposed = true + break + } + } + } + if !exposed { + confCopy.Stack.WSModules = append(confCopy.Stack.WSModules, "admin") + } // start the one-shot server that waits for startup information ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -362,13 +374,44 @@ type execNodeConfig struct { PeerAddrs map[string]string `json:"peer_addrs,omitempty"` } +func initLogging() { + // Initialize the logging by default first. + glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) + glogger.Verbosity(log.LvlInfo) + log.Root().SetHandler(glogger) + + confEnv := os.Getenv(envNodeConfig) + if confEnv == "" { + return + } + var conf execNodeConfig + if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { + return + } + var writer = os.Stderr + if conf.Node.LogFile != "" { + logWriter, err := os.Create(conf.Node.LogFile) + if err != nil { + return + } + writer = logWriter + } + var verbosity = log.LvlInfo + if conf.Node.LogVerbosity <= log.LvlTrace && conf.Node.LogVerbosity >= log.LvlCrit { + verbosity = conf.Node.LogVerbosity + } + // Reinitialize the logger + glogger = log.NewGlogHandler(log.StreamHandler(writer, log.TerminalFormat(true))) + glogger.Verbosity(verbosity) + log.Root().SetHandler(glogger) +} + // execP2PNode starts a simulation node when the current binary is executed with // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2] // and the node config from an environment variable. func execP2PNode() { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) - glogger.Verbosity(log.LvlInfo) - log.Root().SetHandler(glogger) + initLogging() + statusURL := os.Getenv(envStatusURL) if statusURL == "" { log.Crit("missing " + envStatusURL) @@ -380,7 +423,7 @@ func execP2PNode() { if stackErr != nil { status.Err = stackErr.Error() } else { - status.WSEndpoint = "ws://" + stack.WSEndpoint() + status.WSEndpoint = stack.WSEndpoint() status.NodeInfo = stack.Server().NodeInfo() } @@ -454,7 +497,6 @@ func startExecNodeStack() (*node.Node, error) { return nil, err } services[name] = service - stack.RegisterLifecycle(service) } // Add the snapshot API. diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index b6af370089..1da464a10d 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -120,6 +120,17 @@ type NodeConfig struct { Reachable func(id enode.ID) bool Port uint16 + + // LogFile is the log file name of the p2p node at runtime. + // + // The default value is empty so that the default log writer + // is the system standard output. + LogFile string + + // LogVerbosity is the log verbosity of the p2p node at runtime. + // + // The default verbosity is INFO. + LogVerbosity log.Lvl } // nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding @@ -128,10 +139,12 @@ type nodeConfigJSON struct { ID string `json:"id"` PrivateKey string `json:"private_key"` Name string `json:"name"` - Services []string `json:"services"` + Lifecycles []string `json:"lifecycles"` Properties []string `json:"properties"` EnableMsgEvents bool `json:"enable_msg_events"` Port uint16 `json:"port"` + LogFile string `json:"logfile"` + LogVerbosity int `json:"log_verbosity"` } // MarshalJSON implements the json.Marshaler interface by encoding the config @@ -140,10 +153,12 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { confJSON := nodeConfigJSON{ ID: n.ID.String(), Name: n.Name, - Services: n.Lifecycles, + Lifecycles: n.Lifecycles, Properties: n.Properties, Port: n.Port, EnableMsgEvents: n.EnableMsgEvents, + LogFile: n.LogFile, + LogVerbosity: int(n.LogVerbosity), } if n.PrivateKey != nil { confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey)) @@ -178,10 +193,12 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { } n.Name = confJSON.Name - n.Lifecycles = confJSON.Services + n.Lifecycles = confJSON.Lifecycles n.Properties = confJSON.Properties n.Port = confJSON.Port n.EnableMsgEvents = confJSON.EnableMsgEvents + n.LogFile = confJSON.LogFile + n.LogVerbosity = log.Lvl(confJSON.LogVerbosity) return nil } @@ -211,6 +228,7 @@ func RandomNodeConfig() *NodeConfig { Name: fmt.Sprintf("node_%s", enodId.String()), Port: port, EnableMsgEvents: true, + LogVerbosity: log.LvlInfo, } } From 941d8b5c5c3b797251378c30125630313f041106 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 10 Nov 2020 15:21:41 +0100 Subject: [PATCH 124/948] scripts: create oss-fuzz script in go-ethereum (#21808) --- oss-fuzz.sh | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 oss-fuzz.sh diff --git a/oss-fuzz.sh b/oss-fuzz.sh new file mode 100644 index 0000000000..23fb4dd412 --- /dev/null +++ b/oss-fuzz.sh @@ -0,0 +1,56 @@ +#/bin/bash -eu +# Copyright 2020 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +# This file is for integration with Google OSS-Fuzz. +# The following ENV variables are available when executing on OSS-fuzz: +# +# /out/ $OUT Directory to store build artifacts (fuzz targets, dictionaries, options files, seed corpus archives). +# /src/ $SRC Directory to checkout source files. +# /work/ $WORK Directory to store intermediate files. +# +# $CC, $CXX, $CCC The C and C++ compiler binaries. +# $CFLAGS, $CXXFLAGS C and C++ compiler flags. +# $LIB_FUZZING_ENGINE C++ compiler argument to link fuzz target against the prebuilt engine library (e.g. libFuzzer). + +function compile_fuzzer { + path=$SRC/go-ethereum/$1 + func=$2 + fuzzer=$3 + echo "Building $fuzzer" + (cd $path && \ + go-fuzz -func $func -o $WORK/$fuzzer.a . && \ + echo "First stage built OK" && \ + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer && \ + echo "Second stage built ok" ) + +} + +compile_fuzzer common/bitutil Fuzz fuzzBitutilCompress +compile_fuzzer crypto/bn256 FuzzAdd fuzzBn256Add +compile_fuzzer crypto/bn256 FuzzMul fuzzBn256Mul +compile_fuzzer crypto/bn256 FuzzPair fuzzBn256Pair +compile_fuzzer core/vm/runtime Fuzz fuzzVmRuntime +compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b +compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore +compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher +compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp +compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie +compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie + +# This doesn't work very well @TODO +#compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi + From 70868b1e4ab099d36d68ca86e1a569f9ebfc12dc Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 10 Nov 2020 20:54:59 +0100 Subject: [PATCH 125/948] fuzzers: removed fuzzbuzz configuration (#21813) We decided to move our fuzzing efforts to oss-fuzz since fuzzbuzz is still early access. --- fuzzbuzz.yaml | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 fuzzbuzz.yaml diff --git a/fuzzbuzz.yaml b/fuzzbuzz.yaml deleted file mode 100644 index 2a4f0c296f..0000000000 --- a/fuzzbuzz.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# bmt keystore rlp trie whisperv6 - -base: ubuntu:16.04 -targets: - - name: rlp - language: go - version: "1.13" - corpus: ./fuzzers/rlp/corpus - harness: - function: Fuzz - package: github.com/ethereum/go-ethereum/tests/fuzzers/rlp - checkout: github.com/ethereum/go-ethereum/ - - name: keystore - language: go - version: "1.13" - corpus: ./fuzzers/keystore/corpus - harness: - function: Fuzz - package: github.com/ethereum/go-ethereum/tests/fuzzers/keystore - checkout: github.com/ethereum/go-ethereum/ - - name: trie - language: go - version: "1.13" - corpus: ./fuzzers/trie/corpus - harness: - function: Fuzz - package: github.com/ethereum/go-ethereum/tests/fuzzers/trie - checkout: github.com/ethereum/go-ethereum/ - - name: txfetcher - language: go - version: "1.13" - corpus: ./fuzzers/txfetcher/corpus - harness: - function: Fuzz - package: github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher - checkout: github.com/ethereum/go-ethereum/ - - name: whisperv6 - language: go - version: "1.13" - corpus: ./fuzzers/whisperv6/corpus - harness: - function: Fuzz - package: github.com/ethereum/go-ethereum/tests/fuzzers/whisperv6 - checkout: github.com/ethereum/go-ethereum/ From 27d93c1848846b75d0e67fcac284a0d417acd47c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 11 Nov 2020 14:34:43 +0100 Subject: [PATCH 126/948] build: add -dlgo flag in ci.go (#21824) This new flag downloads a known version of Go and builds with it. This is meant for environments where we can't easily upgrade the installed Go version. * .travis.yml: remove install step for PR test builders We added this step originally to avoid re-building everything for every test. go test has become much smarter in recent go releases, so we no longer need to install anything here. --- .travis.yml | 20 ++-- appveyor.yml | 2 +- build/checksums.txt | 9 +- build/ci.go | 195 ++++++++++++++++++++++++-------------- internal/build/archive.go | 94 ++++++++++++++---- internal/build/util.go | 27 ++++++ 6 files changed, 246 insertions(+), 101 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec63963cef..16c1e51741 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,6 @@ jobs: env: - GO111MODULE=on script: - - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES # These are the latest Go versions. @@ -43,7 +42,6 @@ jobs: env: - GO111MODULE=on script: - - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES - stage: build @@ -55,7 +53,6 @@ jobs: env: - GO111MODULE=on script: - - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES - stage: build @@ -74,7 +71,6 @@ jobs: - ulimit -S -n $NOFILE - ulimit -n - unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703 - - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES # This builder does the Ubuntu PPA upload @@ -99,7 +95,7 @@ jobs: - python-paramiko script: - echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts - - go run build/ci.go debsrc -goversion 1.15 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " + - go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " # This builder does the Linux Azure uploads - stage: build @@ -119,22 +115,22 @@ jobs: - gcc-multilib script: # Build for the primary platforms that Trusty can manage - - go run build/ci.go install + - go run build/ci.go install -dlgo - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds - - go run build/ci.go install -arch 386 + - go run build/ci.go install -dlgo -arch 386 - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds # Switch over GCC to cross compilation (breaks 386, hence why do it here only) - sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross - sudo ln -s /usr/include/asm-generic /usr/include/asm - - GOARM=5 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc + - GOARM=5 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds - - GOARM=6 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc + - GOARM=6 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabi-gcc - GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds - - GOARM=7 go run build/ci.go install -arch arm -cc arm-linux-gnueabihf-gcc + - GOARM=7 go run build/ci.go install -dlgo -arch arm -cc arm-linux-gnueabihf-gcc - GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds - - go run build/ci.go install -arch arm64 -cc aarch64-linux-gnu-gcc + - go run build/ci.go install -dlgo -arch arm64 -cc aarch64-linux-gnu-gcc - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds # This builder does the Linux Azure MIPS xgo uploads @@ -219,7 +215,7 @@ jobs: git: submodules: false # avoid cloning ethereum/tests script: - - go run build/ci.go install + - go run build/ci.go install -dlgo - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure diff --git a/appveyor.yml b/appveyor.yml index 7d6bf87639..eec726a65d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,7 +30,7 @@ install: - gcc --version build_script: - - go run build\ci.go install + - go run build\ci.go install -dlgo after_build: - go run build\ci.go archive -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds diff --git a/build/checksums.txt b/build/checksums.txt index 39f855cd0c..d7a07d1ef8 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,6 +1,13 @@ # This file contains sha256 checksums of optional build dependencies. -69438f7ed4f532154ffaf878f3dfd83747e7a00b70b3556eddabf7aaee28ac3a go1.15.src.tar.gz +063da6a9a4186b8118a0e584532c8c94e65582e2cd951ed078bfd595d27d2367 go1.15.4.src.tar.gz +aaf8c5323e0557211680960a8f51bedf98ab9a368775a687d6cf1f0079232b1d go1.15.4.darwin-amd64.tar.gz +6b2f6d8afddfb198bf0e36044084dc4db4cb0be1107375240b34d215aa5ff6ad go1.15.4.linux-386.tar.gz +eb61005f0b932c93b424a3a4eaa67d72196c79129d9a3ea8578047683e2c80d5 go1.15.4.linux-amd64.tar.gz +6f083b453484fc5f95afb345547a58ccc957cde91348b7a7c68f5b060e488c85 go1.15.4.linux-arm64.tar.gz +fe449ad3e121472e5db2f70becc0fef9d1a7188616c0605ada63f1e3bbad280e go1.15.4.linux-armv6l.tar.gz +3be3cfc08ccc7e7056fdee17b6f5d18e9d7f3d1351dcfec8de34b1c95cb05b50 go1.15.4.windows-386.zip +3593204e3851be577e4209900ece031b36f1e9ce1671f3f3221c9af7a090a941 go1.15.4.windows-amd64.zip d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index 9522d29e4c..4b6df88a49 100644 --- a/build/ci.go +++ b/build/ci.go @@ -46,12 +46,11 @@ import ( "encoding/base64" "flag" "fmt" - "go/parser" - "go/token" "io/ioutil" "log" "os" "os/exec" + "path" "path/filepath" "regexp" "runtime" @@ -148,6 +147,11 @@ var ( "golang-1.11": "/usr/lib/go-1.11", "golang-go": "/usr/lib/go", } + + // This is the version of go that will be downloaded by + // + // go run ci.go install -dlgo + dlgoVersion = "1.15.4" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) @@ -198,19 +202,19 @@ func main() { func doInstall(cmdline []string) { var ( + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") arch = flag.String("arch", "", "Architecture to cross build for") cc = flag.String("cc", "", "C compiler to cross build with") ) flag.CommandLine.Parse(cmdline) env := build.Env() - // Check Go version. People regularly open issues about compilation + // Check local Go version. People regularly open issues about compilation // failure with outdated Go. This should save them the trouble. if !strings.Contains(runtime.Version(), "devel") { // Figure out the minor version number since we can't textually compare (1.10 < 1.9) var minor int fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) - if minor < 13 { log.Println("You have Go version", runtime.Version()) log.Println("go-ethereum requires at least Go version 1.13 and cannot") @@ -218,90 +222,108 @@ func doInstall(cmdline []string) { os.Exit(1) } } - // Compile packages given as arguments, or everything if there are no arguments. - packages := []string{"./..."} - if flag.NArg() > 0 { - packages = flag.Args() + + // Choose which go command we're going to use. + var gobuild *exec.Cmd + if !*dlgo { + // Default behavior: use the go version which runs ci.go right now. + gobuild = goTool("build") + } else { + // Download of Go requested. This is for build environments where the + // installed version is too old and cannot be upgraded easily. + cachedir := filepath.Join("build", "cache") + goroot := downloadGo(runtime.GOARCH, runtime.GOOS, cachedir) + gobuild = localGoTool(goroot, "build") } - if *arch == "" || *arch == runtime.GOARCH { - goinstall := goTool("install", buildFlags(env)...) - if runtime.GOARCH == "arm64" { - goinstall.Args = append(goinstall.Args, "-p", "1") - } - goinstall.Args = append(goinstall.Args, "-trimpath") - goinstall.Args = append(goinstall.Args, "-v") - goinstall.Args = append(goinstall.Args, packages...) - build.MustRun(goinstall) - return + // Configure environment for cross build. + if *arch != "" || *arch != runtime.GOARCH { + gobuild.Env = append(gobuild.Env, "CGO_ENABLED=1") + gobuild.Env = append(gobuild.Env, "GOARCH="+*arch) } - // Seems we are cross compiling, work around forbidden GOBIN - goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...) - goinstall.Args = append(goinstall.Args, "-trimpath") - goinstall.Args = append(goinstall.Args, "-v") - goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...) - goinstall.Args = append(goinstall.Args, packages...) - build.MustRun(goinstall) + // Configure C compiler. + if *cc == "" { + gobuild.Env = append(gobuild.Env, "CC="+*cc) + } else if os.Getenv("CC") != "" { + gobuild.Env = append(gobuild.Env, "CC="+os.Getenv("CC")) + } - if cmds, err := ioutil.ReadDir("cmd"); err == nil { - for _, cmd := range cmds { - pkgs, err := parser.ParseDir(token.NewFileSet(), filepath.Join(".", "cmd", cmd.Name()), nil, parser.PackageClauseOnly) - if err != nil { - log.Fatal(err) - } - for name := range pkgs { - if name == "main" { - gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...) - gobuild.Args = append(gobuild.Args, "-v") - gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...) - gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name())) - build.MustRun(gobuild) - break - } - } - } + // arm64 CI builders are memory-constrained and can't handle concurrent builds, + // better disable it. This check isn't the best, it should probably + // check for something in env instead. + if runtime.GOARCH == "arm64" { + gobuild.Args = append(gobuild.Args, "-p", "1") + } + + // Put the default settings in. + gobuild.Args = append(gobuild.Args, buildFlags(env)...) + + // Show packages during build. + gobuild.Args = append(gobuild.Args, "-v") + + // Now we choose what we're even building. + // Default: collect all 'main' packages in cmd/ and build those. + packages := flag.Args() + if len(packages) == 0 { + packages = build.FindMainPackages("./cmd") + } + + // Do the build! + for _, pkg := range packages { + args := make([]string, len(gobuild.Args)) + copy(args, gobuild.Args) + args = append(args, "-o", executablePath(path.Base(pkg))) + args = append(args, pkg) + build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) } } +// buildFlags returns the go tool flags for building. func buildFlags(env build.Environment) (flags []string) { var ld []string if env.Commit != "" { ld = append(ld, "-X", "main.gitCommit="+env.Commit) ld = append(ld, "-X", "main.gitDate="+env.Date) } + // Strip DWARF on darwin. This used to be required for certain things, + // and there is no downside to this, so we just keep doing it. if runtime.GOOS == "darwin" { ld = append(ld, "-s") } - if len(ld) > 0 { flags = append(flags, "-ldflags", strings.Join(ld, " ")) } + // We use -trimpath to avoid leaking local paths into the built executables. + flags = append(flags, "-trimpath") return flags } +// goTool returns the go tool. This uses the Go version which runs ci.go. func goTool(subcmd string, args ...string) *exec.Cmd { - return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...) + cmd := build.GoTool(subcmd, args...) + goToolSetEnv(cmd) + return cmd } -func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd { - cmd := build.GoTool(subcmd, args...) - if arch == "" || arch == runtime.GOARCH { - cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) - } else { - cmd.Env = append(cmd.Env, "CGO_ENABLED=1") - cmd.Env = append(cmd.Env, "GOARCH="+arch) - } - if cc != "" { - cmd.Env = append(cmd.Env, "CC="+cc) - } +// localGoTool returns the go tool from the given GOROOT. +func localGoTool(goroot string, subcmd string, args ...string) *exec.Cmd { + gotool := filepath.Join(goroot, "bin", "go") + cmd := exec.Command(gotool, subcmd) + goToolSetEnv(cmd) + cmd.Env = append(cmd.Env, "GOROOT="+goroot) + cmd.Args = append(cmd.Args, args...) + return cmd +} + +// goToolSetEnv forwards the build environment to the go tool. +func goToolSetEnv(cmd *exec.Cmd) { for _, e := range os.Environ() { - if strings.HasPrefix(e, "GOBIN=") { + if strings.HasPrefix(e, "GOBIN=") || strings.HasPrefix(e, "CC=") { continue } cmd.Env = append(cmd.Env, e) } - return cmd } // Running The Tests @@ -363,7 +385,7 @@ func downloadLinter(cachedir string) string { if err := csdb.DownloadFile(url, archivePath); err != nil { log.Fatal(err) } - if err := build.ExtractTarballArchive(archivePath, cachedir); err != nil { + if err := build.ExtractArchive(archivePath, cachedir); err != nil { log.Fatal(err) } return filepath.Join(cachedir, base, "golangci-lint") @@ -469,13 +491,12 @@ func maybeSkipArchive(env build.Environment) { // Debian Packaging func doDebianSource(cmdline []string) { var ( - goversion = flag.String("goversion", "", `Go version to build with (will be included in the source package)`) - cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`) - signer = flag.String("signer", "", `Signing key name, also used as package author`) - upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`) - sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`) - workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) - now = time.Now() + cachedir = flag.String("cachedir", "./build/cache", `Filesystem path to cache the downloaded Go bundles at`) + signer = flag.String("signer", "", `Signing key name, also used as package author`) + upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`) + sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`) + workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) + now = time.Now() ) flag.CommandLine.Parse(cmdline) *workdir = makeWorkdir(*workdir) @@ -490,7 +511,7 @@ func doDebianSource(cmdline []string) { } // Download and verify the Go source package. - gobundle := downloadGoSources(*goversion, *cachedir) + gobundle := downloadGoSources(*cachedir) // Download all the dependencies needed to build the sources and run the ci script srcdepfetch := goTool("install", "-n", "./...") @@ -509,7 +530,7 @@ func doDebianSource(cmdline []string) { pkgdir := stageDebianSource(*workdir, meta) // Add Go source code - if err := build.ExtractTarballArchive(gobundle, pkgdir); err != nil { + if err := build.ExtractArchive(gobundle, pkgdir); err != nil { log.Fatalf("Failed to extract Go sources: %v", err) } if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { @@ -541,9 +562,10 @@ func doDebianSource(cmdline []string) { } } -func downloadGoSources(version string, cachedir string) string { +// downloadGoSources downloads the Go source tarball. +func downloadGoSources(cachedir string) string { csdb := build.MustLoadChecksums("build/checksums.txt") - file := fmt.Sprintf("go%s.src.tar.gz", version) + file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) url := "https://dl.google.com/go/" + file dst := filepath.Join(cachedir, file) if err := csdb.DownloadFile(url, dst); err != nil { @@ -552,6 +574,41 @@ func downloadGoSources(version string, cachedir string) string { return dst } +// downloadGo downloads the Go binary distribution and unpacks it into a temporary +// directory. It returns the GOROOT of the unpacked toolchain. +func downloadGo(goarch, goos, cachedir string) string { + if goarch == "arm" { + goarch = "armv6l" + } + + csdb := build.MustLoadChecksums("build/checksums.txt") + file := fmt.Sprintf("go%s.%s-%s", dlgoVersion, goos, goarch) + if goos == "windows" { + file += ".zip" + } else { + file += ".tar.gz" + } + url := "https://golang.org/dl/" + file + dst := filepath.Join(cachedir, file) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Fatal(err) + } + + ucache, err := os.UserCacheDir() + if err != nil { + log.Fatal(err) + } + godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", dlgoVersion, goos, goarch)) + if err := build.ExtractArchive(dst, godir); err != nil { + log.Fatal(err) + } + goroot, err := filepath.Abs(filepath.Join(godir, "go")) + if err != nil { + log.Fatal(err) + } + return goroot +} + func ppaUpload(workdir, ppa, sshUser string, files []string) { p := strings.Split(ppa, "/") if len(p) != 2 { diff --git a/internal/build/archive.go b/internal/build/archive.go index a00258d999..8b3ac23d1d 100644 --- a/internal/build/archive.go +++ b/internal/build/archive.go @@ -184,24 +184,35 @@ func (a *TarballArchive) Close() error { return a.file.Close() } -func ExtractTarballArchive(archive string, dest string) error { - // We're only interested in gzipped archives, wrap the reader now +// ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory. +func ExtractArchive(archive string, dest string) error { ar, err := os.Open(archive) if err != nil { return err } defer ar.Close() + switch { + case strings.HasSuffix(archive, ".tar.gz"): + return extractTarball(ar, dest) + case strings.HasSuffix(archive, ".zip"): + return extractZip(ar, dest) + default: + return fmt.Errorf("unhandled archive type %s", archive) + } +} + +// extractTarball unpacks a .tar.gz file. +func extractTarball(ar io.Reader, dest string) error { gzr, err := gzip.NewReader(ar) if err != nil { return err } defer gzr.Close() - // Iterate over all the files in the tarball tr := tar.NewReader(gzr) for { - // Fetch the next tarball header and abort if needed + // Move to the next file header. header, err := tr.Next() if err != nil { if err == io.EOF { @@ -209,22 +220,69 @@ func ExtractTarballArchive(archive string, dest string) error { } return err } - // Figure out the target and create it - target := filepath.Join(dest, header.Name) - - switch header.Typeflag { - case tar.TypeReg: - if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { - return err - } - file, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + // We only care about regular files, directory modes + // and special file types are not supported. + if header.Typeflag == tar.TypeReg { + armode := header.FileInfo().Mode() + err := extractFile(header.Name, armode, tr, dest) if err != nil { - return err - } - if _, err := io.Copy(file, tr); err != nil { - return err + return fmt.Errorf("extract %s: %v", header.Name, err) } - file.Close() } } } + +// extractZip unpacks the given .zip file. +func extractZip(ar *os.File, dest string) error { + info, err := ar.Stat() + if err != nil { + return err + } + zr, err := zip.NewReader(ar, info.Size()) + if err != nil { + return err + } + + for _, zf := range zr.File { + if !zf.Mode().IsRegular() { + continue + } + + data, err := zf.Open() + if err != nil { + return err + } + err = extractFile(zf.Name, zf.Mode(), data, dest) + data.Close() + if err != nil { + return fmt.Errorf("extract %s: %v", zf.Name, err) + } + } + return nil +} + +// extractFile extracts a single file from an archive. +func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) error { + // Check that path is inside destination directory. + target := filepath.Join(dest, filepath.FromSlash(arpath)) + if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) { + return fmt.Errorf("path %q escapes archive destination", target) + } + + // Ensure the destination directory exists. + if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { + return err + } + + // Copy file data. + file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, armode) + if err != nil { + return err + } + if _, err := io.Copy(file, data); err != nil { + file.Close() + os.Remove(target) + return err + } + return file.Close() +} diff --git a/internal/build/util.go b/internal/build/util.go index fc559760b2..91149926f7 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -20,6 +20,8 @@ import ( "bytes" "flag" "fmt" + "go/parser" + "go/token" "io" "io/ioutil" "log" @@ -152,3 +154,28 @@ func UploadSFTP(identityFile, host, dir string, files []string) error { stdin.Close() return sftp.Wait() } + +// FindMainPackages finds all 'main' packages in the given directory and returns their +// package paths. +func FindMainPackages(dir string) []string { + var commands []string + cmds, err := ioutil.ReadDir(dir) + if err != nil { + log.Fatal(err) + } + for _, cmd := range cmds { + pkgdir := filepath.Join(dir, cmd.Name()) + pkgs, err := parser.ParseDir(token.NewFileSet(), pkgdir, nil, parser.PackageClauseOnly) + if err != nil { + log.Fatal(err) + } + for name := range pkgs { + if name == "main" { + path := "./" + filepath.ToSlash(pkgdir) + commands = append(commands, path) + break + } + } + } + return commands +} From d990df909d7839640143344e79356754384dcdd0 Mon Sep 17 00:00:00 2001 From: Slava Karpenko Date: Wed, 11 Nov 2020 21:13:12 +0100 Subject: [PATCH 127/948] consensus/ethash: use 64bit indexes for the DAG generation (#21793) * Bit boundary fix for the DAG generation routine * Fix unnecessary conversion warnings Co-authored-by: Sergey Pavlov --- consensus/ethash/algorithm.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go index e79c702dc8..47d7e51b59 100644 --- a/consensus/ethash/algorithm.go +++ b/consensus/ethash/algorithm.go @@ -304,16 +304,16 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { keccak512 := makeHasher(sha3.NewLegacyKeccak512()) // Calculate the data segment this thread should generate - batch := uint32((size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads))) - first := uint32(id) * batch + batch := (size + hashBytes*uint64(threads) - 1) / (hashBytes * uint64(threads)) + first := uint64(id) * batch limit := first + batch - if limit > uint32(size/hashBytes) { - limit = uint32(size / hashBytes) + if limit > size/hashBytes { + limit = size / hashBytes } // Calculate the dataset segment percent := size / hashBytes / 100 for index := first; index < limit; index++ { - item := generateDatasetItem(cache, index, keccak512) + item := generateDatasetItem(cache, uint32(index), keccak512) if swapped { swap(item) } From d49407427d1b2642184166bd36ae43070db13e36 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 11 Nov 2020 22:08:22 +0100 Subject: [PATCH 128/948] build: fix regressions with the -dlgo change (#21831) This fixes cross-build and mobile framework failures. It also disables the mac test builder because it was failing all the time in hard to understand ways and we can't afford it anymore under Travis CI's new pricing. --- .travis.yml | 18 ------------------ build/ci.go | 10 ++++++---- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 16c1e51741..03eada24af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,24 +55,6 @@ jobs: script: - go run build/ci.go test -coverage $TEST_PACKAGES - - stage: build - os: osx - osx_image: xcode11.3 - go: 1.15.x - env: - - GO111MODULE=on - script: - - echo "Increase the maximum number of open file descriptors on macOS" - - NOFILE=20480 - - sudo sysctl -w kern.maxfiles=$NOFILE - - sudo sysctl -w kern.maxfilesperproc=$NOFILE - - sudo launchctl limit maxfiles $NOFILE $NOFILE - - sudo launchctl limit maxfiles - - ulimit -S -n $NOFILE - - ulimit -n - - unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703 - - go run build/ci.go test -coverage $TEST_PACKAGES - # This builder does the Ubuntu PPA upload - stage: build if: type = push diff --git a/build/ci.go b/build/ci.go index 4b6df88a49..9fa6c71429 100644 --- a/build/ci.go +++ b/build/ci.go @@ -243,7 +243,7 @@ func doInstall(cmdline []string) { } // Configure C compiler. - if *cc == "" { + if *cc != "" { gobuild.Env = append(gobuild.Env, "CC="+*cc) } else if os.Getenv("CC") != "" { gobuild.Env = append(gobuild.Env, "CC="+os.Getenv("CC")) @@ -259,6 +259,9 @@ func doInstall(cmdline []string) { // Put the default settings in. gobuild.Args = append(gobuild.Args, buildFlags(env)...) + // We use -trimpath to avoid leaking local paths into the built executables. + gobuild.Args = append(gobuild.Args, "-trimpath") + // Show packages during build. gobuild.Args = append(gobuild.Args, "-v") @@ -294,8 +297,6 @@ func buildFlags(env build.Environment) (flags []string) { if len(ld) > 0 { flags = append(flags, "-ldflags", strings.Join(ld, " ")) } - // We use -trimpath to avoid leaking local paths into the built executables. - flags = append(flags, "-trimpath") return flags } @@ -318,6 +319,7 @@ func localGoTool(goroot string, subcmd string, args ...string) *exec.Cmd { // goToolSetEnv forwards the build environment to the go tool. func goToolSetEnv(cmd *exec.Cmd) { + cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) for _, e := range os.Environ() { if strings.HasPrefix(e, "GOBIN=") || strings.HasPrefix(e, "CC=") { continue @@ -514,7 +516,7 @@ func doDebianSource(cmdline []string) { gobundle := downloadGoSources(*cachedir) // Download all the dependencies needed to build the sources and run the ci script - srcdepfetch := goTool("install", "-n", "./...") + srcdepfetch := goTool("mod", "download") srcdepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath")) build.MustRun(srcdepfetch) From 920a28711746c8e44ef3e6270a41f038d767d92b Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 11 Nov 2020 23:52:50 +0100 Subject: [PATCH 129/948] .travis.yml: move test builders after install builders (#21833) --- .travis.yml | 62 ++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03eada24af..132a3f1eb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,37 +24,6 @@ jobs: script: - go run build/ci.go lint - - stage: build - os: linux - dist: xenial - go: 1.14.x - env: - - GO111MODULE=on - script: - - go run build/ci.go test -coverage $TEST_PACKAGES - - # These are the latest Go versions. - - stage: build - os: linux - arch: amd64 - dist: xenial - go: 1.15.x - env: - - GO111MODULE=on - script: - - go run build/ci.go test -coverage $TEST_PACKAGES - - - stage: build - if: type = pull_request - os: linux - arch: arm64 - dist: xenial - go: 1.15.x - env: - - GO111MODULE=on - script: - - go run build/ci.go test -coverage $TEST_PACKAGES - # This builder does the Ubuntu PPA upload - stage: build if: type = push @@ -215,6 +184,37 @@ jobs: - export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc' - go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds + # These builders run the tests + - stage: build + os: linux + arch: amd64 + dist: xenial + go: 1.15.x + env: + - GO111MODULE=on + script: + - go run build/ci.go test -coverage $TEST_PACKAGES + + - stage: build + if: type = pull_request + os: linux + arch: arm64 + dist: xenial + go: 1.15.x + env: + - GO111MODULE=on + script: + - go run build/ci.go test -coverage $TEST_PACKAGES + + - stage: build + os: linux + dist: xenial + go: 1.14.x + env: + - GO111MODULE=on + script: + - go run build/ci.go test -coverage $TEST_PACKAGES + # This builder does the Azure archive purges to avoid accumulating junk - stage: build if: type = cron From cc05b050df5f88e80bb26aaf6d2f339c49c2d702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 12 Nov 2020 22:10:15 +0200 Subject: [PATCH 130/948] params: release Geth v1.9.24 with Go 1.15.5 (#21842) --- .travis.yml | 2 +- appveyor.yml | 4 ++-- build/checksums.txt | 16 ++++++++-------- build/ci.go | 2 +- params/version.go | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 132a3f1eb5..fd31e3d506 100644 --- a/.travis.yml +++ b/.travis.yml @@ -139,7 +139,7 @@ jobs: git: submodules: false # avoid cloning ethereum/tests before_install: - - curl https://dl.google.com/go/go1.15.linux-amd64.tar.gz | tar -xz + - curl https://dl.google.com/go/go1.15.5.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go diff --git a/appveyor.yml b/appveyor.yml index eec726a65d..2bf67d4568 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -24,8 +24,8 @@ environment: install: - git submodule update --init - rmdir C:\go /s /q - - appveyor DownloadFile https://dl.google.com/go/go1.15.windows-%GETH_ARCH%.zip - - 7z x go1.15.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://dl.google.com/go/go1.15.5.windows-%GETH_ARCH%.zip + - 7z x go1.15.5.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/build/checksums.txt b/build/checksums.txt index d7a07d1ef8..32b376519f 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,13 +1,13 @@ # This file contains sha256 checksums of optional build dependencies. -063da6a9a4186b8118a0e584532c8c94e65582e2cd951ed078bfd595d27d2367 go1.15.4.src.tar.gz -aaf8c5323e0557211680960a8f51bedf98ab9a368775a687d6cf1f0079232b1d go1.15.4.darwin-amd64.tar.gz -6b2f6d8afddfb198bf0e36044084dc4db4cb0be1107375240b34d215aa5ff6ad go1.15.4.linux-386.tar.gz -eb61005f0b932c93b424a3a4eaa67d72196c79129d9a3ea8578047683e2c80d5 go1.15.4.linux-amd64.tar.gz -6f083b453484fc5f95afb345547a58ccc957cde91348b7a7c68f5b060e488c85 go1.15.4.linux-arm64.tar.gz -fe449ad3e121472e5db2f70becc0fef9d1a7188616c0605ada63f1e3bbad280e go1.15.4.linux-armv6l.tar.gz -3be3cfc08ccc7e7056fdee17b6f5d18e9d7f3d1351dcfec8de34b1c95cb05b50 go1.15.4.windows-386.zip -3593204e3851be577e4209900ece031b36f1e9ce1671f3f3221c9af7a090a941 go1.15.4.windows-amd64.zip +c1076b90cf94b73ebed62a81d802cd84d43d02dea8c07abdc922c57a071c84f1 go1.15.5.src.tar.gz +359a4334b8c8f5e3067e5a76f16419791ac3fef4613d8e8e1eac0b9719915f6d go1.15.5.darwin-amd64.tar.gz +4c8179d406136979724c71732009c7e2e7c794dbeaaa2a043c00da34d4be0559 go1.15.5.linux-386.tar.gz +9a58494e8da722c3aef248c9227b0e9c528c7318309827780f16220998180a0d go1.15.5.linux-amd64.tar.gz +a72a0b036beb4193a0214bca3fca4c5d68a38a4ccf098c909f7ce8bf08567c48 go1.15.5.linux-arm64.tar.gz +5ea6456620d3efed5dda99238c7f23866eafdd915e5348736e631bc283c0238a go1.15.5.linux-armv6l.tar.gz +d812436c7e3482ba3c97172edf26afaf35aca60a5621ff4a5f6a08386505ab9c go1.15.5.windows-386.zip +1d24be3a200201a74be25e4134fbec467750e834e84e9c7789a9fc13248c5507 go1.15.5.windows-amd64.zip d998a84eea42f2271aca792a7b027ca5c1edfcba229e8e5a844c9ac3f336df35 golangci-lint-1.27.0-linux-armv7.tar.gz bf781f05b0d393b4bf0a327d9e62926949a4f14d7774d950c4e009fc766ed1d4 golangci-lint.exe-1.27.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index 9fa6c71429..0cffb903aa 100644 --- a/build/ci.go +++ b/build/ci.go @@ -151,7 +151,7 @@ var ( // This is the version of go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.15.4" + dlgoVersion = "1.15.5" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) diff --git a/params/version.go b/params/version.go index 656a78e58f..c0f356889a 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 24 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 24 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 919229d63c9c138160816d007866bf3daeb6c6f0 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 12 Nov 2020 21:21:24 +0100 Subject: [PATCH 131/948] params: begin v1.9.25 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index c0f356889a..a2ea188170 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 24 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 25 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From a19b4235c71a1d440e585b13be90677e3572261e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 13 Nov 2020 09:27:57 +0100 Subject: [PATCH 132/948] crypto/bn256: improve bn256 fuzzer (#21815) * crypto/cloudflare: fix nil deref in random G1/G2 reading * crypto/bn256: improve fuzzer * crypto/bn256: fix some flaws in fuzzer --- crypto/bn256/bn256_fuzz.go | 117 ++++++++++++++----------------- crypto/bn256/cloudflare/bn256.go | 2 +- 2 files changed, 53 insertions(+), 66 deletions(-) diff --git a/crypto/bn256/bn256_fuzz.go b/crypto/bn256/bn256_fuzz.go index 6aa1421170..585d509bf4 100644 --- a/crypto/bn256/bn256_fuzz.go +++ b/crypto/bn256/bn256_fuzz.go @@ -8,42 +8,52 @@ package bn256 import ( "bytes" + "fmt" + "io" "math/big" cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" google "github.com/ethereum/go-ethereum/crypto/bn256/google" ) -// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. -func FuzzAdd(data []byte) int { - // Ensure we have enough data in the first place - if len(data) != 128 { - return 0 +func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1) { + _, xc, err := cloudflare.RandomG1(input) + if err != nil { + // insufficient input + return nil, nil } - // Ensure both libs can parse the first curve point - xc := new(cloudflare.G1) - _, errc := xc.Unmarshal(data[:64]) - xg := new(google.G1) - _, errg := xg.Unmarshal(data[:64]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { - return 0 + if _, err := xg.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) } - // Ensure both libs can parse the second curve point - yc := new(cloudflare.G1) - _, errc = yc.Unmarshal(data[64:]) + return xc, xg +} - yg := new(google.G1) - _, errg = yg.Unmarshal(data[64:]) +func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2) { + _, xc, err := cloudflare.RandomG2(input) + if err != nil { + // insufficient input + return nil, nil + } + xg := new(google.G2) + if _, err := xg.Unmarshal(xc.Marshal()); err != nil { + panic(fmt.Sprintf("Could not marshal cloudflare -> google:", err)) + } + return xc, xg +} - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { +// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. +func FuzzAdd(data []byte) int { + input := bytes.NewReader(data) + xc, xg := getG1Points(input) + if xc == nil { return 0 } + yc, yg := getG1Points(input) + if yc == nil { + return 0 + } + // Ensure both libs can parse the second curve point // Add the two points and ensure they result in the same output rc := new(cloudflare.G1) rc.Add(xc, yc) @@ -54,73 +64,50 @@ func FuzzAdd(data []byte) int { if !bytes.Equal(rc.Marshal(), rg.Marshal()) { panic("add mismatch") } - return 0 + return 1 } // FuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare // libraries. func FuzzMul(data []byte) int { - // Ensure we have enough data in the first place - if len(data) != 96 { + input := bytes.NewReader(data) + pc, pg := getG1Points(input) + if pc == nil { return 0 } - // Ensure both libs can parse the curve point - pc := new(cloudflare.G1) - _, errc := pc.Unmarshal(data[:64]) - - pg := new(google.G1) - _, errg := pg.Unmarshal(data[:64]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { + // Add the two points and ensure they result in the same output + remaining := input.Len() + if remaining == 0 { return 0 } - // Add the two points and ensure they result in the same output + buf := make([]byte, remaining) + input.Read(buf) + rc := new(cloudflare.G1) - rc.ScalarMult(pc, new(big.Int).SetBytes(data[64:])) + rc.ScalarMult(pc, new(big.Int).SetBytes(buf)) rg := new(google.G1) - rg.ScalarMult(pg, new(big.Int).SetBytes(data[64:])) + rg.ScalarMult(pg, new(big.Int).SetBytes(buf)) if !bytes.Equal(rc.Marshal(), rg.Marshal()) { panic("scalar mul mismatch") } - return 0 + return 1 } func FuzzPair(data []byte) int { - // Ensure we have enough data in the first place - if len(data) != 192 { + input := bytes.NewReader(data) + pc, pg := getG1Points(input) + if pc == nil { return 0 } - // Ensure both libs can parse the curve point - pc := new(cloudflare.G1) - _, errc := pc.Unmarshal(data[:64]) - - pg := new(google.G1) - _, errg := pg.Unmarshal(data[:64]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { - return 0 - } - // Ensure both libs can parse the twist point - tc := new(cloudflare.G2) - _, errc = tc.Unmarshal(data[64:]) - - tg := new(google.G2) - _, errg = tg.Unmarshal(data[64:]) - - if (errc == nil) != (errg == nil) { - panic("parse mismatch") - } else if errc != nil { + tc, tg := getG2Points(input) + if tc == nil { return 0 } // Pair the two points and ensure thet result in the same output if cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) { panic("pair mismatch") } - return 0 + return 1 } diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go index 38822a76bf..a6dd972ba8 100644 --- a/crypto/bn256/cloudflare/bn256.go +++ b/crypto/bn256/cloudflare/bn256.go @@ -23,7 +23,7 @@ import ( func randomK(r io.Reader) (k *big.Int, err error) { for { k, err = rand.Int(r, Order) - if k.Sign() > 0 || err != nil { + if err != nil || k.Sign() > 0 { return } } From 9ded4e33c5b6976657018b23ba4cc76939c165c7 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 13 Nov 2020 10:17:23 +0100 Subject: [PATCH 133/948] crypto/bn256: better comments for u, P and Order (#21836) --- crypto/bn256/google/constants.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crypto/bn256/google/constants.go b/crypto/bn256/google/constants.go index ab649d7f3f..0db432c9cf 100644 --- a/crypto/bn256/google/constants.go +++ b/crypto/bn256/google/constants.go @@ -13,13 +13,14 @@ func bigFromBase10(s string) *big.Int { return n } -// u is the BN parameter that determines the prime: 1868033³. +// u is the BN parameter that determines the prime. var u = bigFromBase10("4965661367192848881") -// p is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. +// P is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1. var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") // Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") // xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. From 0703c91fbad6653dc7aa809816e5698b0b868693 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 13 Nov 2020 12:36:38 +0100 Subject: [PATCH 134/948] tests/fuzzers: improve the fuzzers (#21829) * tests/fuzzers, common/bitutil: make fuzzers use correct returnvalues + remove output * tests/fuzzers/stacktrie: fix duplicate-key insertion in stacktrie (false positive) * tests/fuzzers/stacktrie: fix compilation error * tests/fuzzers: linter nits --- common/bitutil/compress_fuzz.go | 6 +++--- tests/fuzzers/keystore/keystore-fuzzer.go | 2 +- tests/fuzzers/rlp/rlp_fuzzer.go | 16 +++++++-------- tests/fuzzers/stacktrie/trie_fuzzer.go | 7 +++++++ tests/fuzzers/txfetcher/txfetcher_fuzzer.go | 22 +++++++++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/common/bitutil/compress_fuzz.go b/common/bitutil/compress_fuzz.go index 1b87f50edc..714bbcd131 100644 --- a/common/bitutil/compress_fuzz.go +++ b/common/bitutil/compress_fuzz.go @@ -24,7 +24,7 @@ import "bytes" // invocations. func Fuzz(data []byte) int { if len(data) == 0 { - return -1 + return 0 } if data[0]%2 == 0 { return fuzzEncode(data[1:]) @@ -39,7 +39,7 @@ func fuzzEncode(data []byte) int { if !bytes.Equal(data, proc) { panic("content mismatch") } - return 0 + return 1 } // fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and @@ -52,5 +52,5 @@ func fuzzDecode(data []byte) int { if comp := bitsetEncodeBytes(blob); !bytes.Equal(comp, data) { panic("content mismatch") } - return 0 + return 1 } diff --git a/tests/fuzzers/keystore/keystore-fuzzer.go b/tests/fuzzers/keystore/keystore-fuzzer.go index 704f29dc48..e3bcae92e1 100644 --- a/tests/fuzzers/keystore/keystore-fuzzer.go +++ b/tests/fuzzers/keystore/keystore-fuzzer.go @@ -33,5 +33,5 @@ func Fuzz(input []byte) int { panic(err) } os.Remove(a.URL.Path) - return 0 + return 1 } diff --git a/tests/fuzzers/rlp/rlp_fuzzer.go b/tests/fuzzers/rlp/rlp_fuzzer.go index 534540476c..18b36287b5 100644 --- a/tests/fuzzers/rlp/rlp_fuzzer.go +++ b/tests/fuzzers/rlp/rlp_fuzzer.go @@ -37,17 +37,17 @@ func decodeEncode(input []byte, val interface{}, i int) { } func Fuzz(input []byte) int { + if len(input) == 0 { + return 0 + } + var i int { - if len(input) > 0 { - rlp.Split(input) - } + rlp.Split(input) } { - if len(input) > 0 { - if elems, _, err := rlp.SplitList(input); err == nil { - rlp.CountValues(elems) - } + if elems, _, err := rlp.SplitList(input); err == nil { + rlp.CountValues(elems) } } @@ -123,5 +123,5 @@ func Fuzz(input []byte) int { var rs types.Receipts decodeEncode(input, &rs, i) } - return 0 + return 1 } diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go index a072ff772d..5cea7769c2 100644 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -148,6 +148,8 @@ func (f *fuzzer) fuzz() int { vals kvs useful bool maxElements = 10000 + // operate on unique keys only + keys = make(map[string]struct{}) ) // Fill the trie with elements for i := 0; !f.exhausted && i < maxElements; i++ { @@ -158,6 +160,11 @@ func (f *fuzzer) fuzz() int { // thus 'deletion' which is not supported on stacktrie break } + if _, present := keys[string(k)]; present { + // This key is a duplicate, ignore it + continue + } + keys[string(k)] = struct{}{} vals = append(vals, kv{k: k, v: v}) trieA.Update(k, v) useful = true diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go index 10c7eb9424..d1d6fdc665 100644 --- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -51,8 +51,9 @@ func init() { func Fuzz(input []byte) int { // Don't generate insanely large test cases, not much value in them if len(input) > 16*1024 { - return -1 + return 0 } + verbose := false r := bytes.NewReader(input) // Reduce the problem space for certain fuzz runs. Small tx space is better @@ -124,7 +125,9 @@ func Fuzz(input []byte) int { announceIdxs[i] = (int(annBuf[0])*256 + int(annBuf[1])) % len(txs) announces[i] = txs[announceIdxs[i]].Hash() } - fmt.Println("Notify", peer, announceIdxs) + if verbose { + fmt.Println("Notify", peer, announceIdxs) + } if err := f.Notify(peer, announces); err != nil { panic(err) } @@ -163,8 +166,9 @@ func Fuzz(input []byte) int { return 0 } direct := (directFlag % 2) == 0 - - fmt.Println("Enqueue", peer, deliverIdxs, direct) + if verbose { + fmt.Println("Enqueue", peer, deliverIdxs, direct) + } if err := f.Enqueue(peer, deliveries, direct); err != nil { panic(err) } @@ -177,8 +181,9 @@ func Fuzz(input []byte) int { return 0 } peer := peers[int(peerIdx)%len(peers)] - - fmt.Println("Drop", peer) + if verbose { + fmt.Println("Drop", peer) + } if err := f.Drop(peer); err != nil { panic(err) } @@ -191,8 +196,9 @@ func Fuzz(input []byte) int { return 0 } tick := time.Duration(tickCnt) * 100 * time.Millisecond - - fmt.Println("Sleep", tick) + if verbose { + fmt.Println("Sleep", tick) + } clock.Run(tick) } } From 6f4cccf8d27265e197261c717e95ae10e30d7abe Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 13 Nov 2020 13:39:59 +0100 Subject: [PATCH 135/948] core/vm, protocol_params: implement eip-2565 modexp repricing (#21607) * core/vm, protocol_params: implement eip-2565 modexp repricing * core/vm: fix review concerns --- core/vm/contracts.go | 80 +++++++++--- core/vm/contracts_test.go | 27 +++- .../testdata/precompiles/modexp_eip2565.json | 121 ++++++++++++++++++ params/protocol_params.go | 1 - 4 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 core/vm/testdata/precompiles/modexp_eip2565.json diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 35faa7b83d..0c828b1cd9 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -58,7 +58,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, @@ -71,7 +71,7 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, @@ -85,7 +85,7 @@ var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, @@ -222,14 +222,19 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { } // bigModExp implements a native big integer exponential modular operation. -type bigModExp struct{} +type bigModExp struct { + eip2565 bool +} var ( big0 = big.NewInt(0) big1 = big.NewInt(1) + big3 = big.NewInt(3) big4 = big.NewInt(4) + big7 = big.NewInt(7) big8 = big.NewInt(8) big16 = big.NewInt(16) + big20 = big.NewInt(20) big32 = big.NewInt(32) big64 = big.NewInt(64) big96 = big.NewInt(96) @@ -239,6 +244,34 @@ var ( big199680 = big.NewInt(199680) ) +// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198 +// +// def mult_complexity(x): +// if x <= 64: return x ** 2 +// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072 +// else: return x ** 2 // 16 + 480 * x - 199680 +// +// where is x is max(length_of_MODULUS, length_of_BASE) +func modexpMultComplexity(x *big.Int) *big.Int { + switch { + case x.Cmp(big64) <= 0: + x.Mul(x, x) // x ** 2 + case x.Cmp(big1024) <= 0: + // (x ** 2 // 4 ) + ( 96 * x - 3072) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big4), + new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072), + ) + default: + // (x ** 2 // 16) + (480 * x - 199680) + x = new(big.Int).Add( + new(big.Int).Div(new(big.Int).Mul(x, x), big16), + new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680), + ) + } + return x +} + // RequiredGas returns the gas required to execute the pre-compiled contract. func (c *bigModExp) RequiredGas(input []byte) uint64 { var ( @@ -273,25 +306,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { adjExpLen.Mul(big8, adjExpLen) } adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) - // Calculate the gas cost of the operation gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) - switch { - case gas.Cmp(big64) <= 0: + if c.eip2565 { + // EIP-2565 has three changes + // 1. Different multComplexity (inlined here) + // in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565): + // + // def mult_complexity(x): + // ceiling(x/8)^2 + // + //where is x is max(length_of_MODULUS, length_of_BASE) + gas = gas.Add(gas, big7) + gas = gas.Div(gas, big8) gas.Mul(gas, gas) - case gas.Cmp(big1024) <= 0: - gas = new(big.Int).Add( - new(big.Int).Div(new(big.Int).Mul(gas, gas), big4), - new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072), - ) - default: - gas = new(big.Int).Add( - new(big.Int).Div(new(big.Int).Mul(gas, gas), big16), - new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680), - ) + + gas.Mul(gas, math.BigMax(adjExpLen, big1)) + // 2. Different divisor (`GQUADDIVISOR`) (3) + gas.Div(gas, big3) + if gas.BitLen() > 64 { + return math.MaxUint64 + } + // 3. Minimum price of 200 gas + if gas.Uint64() < 200 { + return 200 + } + return gas.Uint64() } + gas = modexpMultComplexity(gas) gas.Mul(gas, math.BigMax(adjExpLen, big1)) - gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv)) + gas.Div(gas, big20) if gas.BitLen() > 64 { return math.MaxUint64 diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index ed0d675a69..30d9b49f71 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -43,7 +43,29 @@ type precompiledFailureTest struct { Name string } -var allPrecompiles = PrecompiledContractsYoloV2 +// allPrecompiles does not map to the actual set of precompiles, as it also contains +// repriced versions of precompiles at certain slots +var allPrecompiles = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, + common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{10}): &bls12381G1Add{}, + common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{13}): &bls12381G2Add{}, + common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{16}): &bls12381Pairing{}, + common.BytesToAddress([]byte{17}): &bls12381MapG1{}, + common.BytesToAddress([]byte{18}): &bls12381MapG2{}, +} // EIP-152 test vectors var blake2FMalformedInputTests = []precompiledFailureTest{ @@ -213,6 +235,9 @@ func BenchmarkPrecompiledIdentity(bench *testing.B) { func TestPrecompiledModExp(t *testing.T) { testJson("modexp", "05", t) } func BenchmarkPrecompiledModExp(b *testing.B) { benchJson("modexp", "05", b) } +func TestPrecompiledModExpEip2565(t *testing.T) { testJson("modexp_eip2565", "f5", t) } +func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) } + // Tests the sample inputs from the elliptic curve addition EIP 213. func TestPrecompiledBn256Add(t *testing.T) { testJson("bn256Add", "06", t) } func BenchmarkPrecompiledBn256Add(b *testing.B) { benchJson("bn256Add", "06", b) } diff --git a/core/vm/testdata/precompiles/modexp_eip2565.json b/core/vm/testdata/precompiles/modexp_eip2565.json new file mode 100644 index 0000000000..c55441439e --- /dev/null +++ b/core/vm/testdata/precompiles/modexp_eip2565.json @@ -0,0 +1,121 @@ +[ + { + "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000001", + "Name": "eip_example1", + "Gas": 1360, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "Expected": "0000000000000000000000000000000000000000000000000000000000000000", + "Name": "eip_example2", + "Gas": 1360, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", + "Name": "nagydani-1-square", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", + "Name": "nagydani-1-qube", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", + "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", + "Name": "nagydani-1-pow0x10001", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", + "Name": "nagydani-2-square", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", + "Name": "nagydani-2-qube", + "Gas": 200, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", + "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", + "Name": "nagydani-2-pow0x10001", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", + "Name": "nagydani-3-square", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", + "Name": "nagydani-3-qube", + "Gas": 341, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", + "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", + "Name": "nagydani-3-pow0x10001", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", + "Name": "nagydani-4-square", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", + "Name": "nagydani-4-qube", + "Gas": 1365, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", + "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", + "Name": "nagydani-4-pow0x10001", + "Gas": 21845, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", + "Name": "nagydani-5-square", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", + "Name": "nagydani-5-qube", + "Gas": 5461, + "NoBenchmark": false + }, + { + "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", + "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", + "Name": "nagydani-5-pow0x10001", + "Gas": 87381, + "NoBenchmark": false + } +] \ No newline at end of file diff --git a/params/protocol_params.go b/params/protocol_params.go index fd5452bf15..ffb901ec3d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -116,7 +116,6 @@ const ( Ripemd160PerWordGas uint64 = 120 // Per-word price for a RIPEMD160 operation IdentityBaseGas uint64 = 15 // Base price for a data copy operation IdentityPerWordGas uint64 = 3 // Per-work price for a data copy operation - ModExpQuadCoeffDiv uint64 = 20 // Divisor for the quadratic particle of the big int modular exponentiation Bn256AddGasByzantium uint64 = 500 // Byzantium gas needed for an elliptic curve addition Bn256AddGasIstanbul uint64 = 150 // Gas needed for an elliptic curve addition From 2045a2bba3cd2f93fd913c692be146adabd8940c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 13 Nov 2020 13:42:19 +0100 Subject: [PATCH 136/948] core, all: split vm.Context into BlockContext and TxContext (#21672) * all: core: split vm.Config into BlockConfig and TxConfig * core: core/vm: reset EVM between tx in block instead of creating new * core/vm: added docs --- accounts/abi/bind/backends/simulated.go | 5 ++- cmd/evm/internal/t8ntool/execution.go | 10 ++--- cmd/geth/retesteth.go | 10 +++-- core/evm.go | 16 ++++--- core/state_prefetcher.go | 5 ++- core/state_processor.go | 48 ++++++++++++-------- core/state_transition.go | 8 ++-- core/vm/evm.go | 41 +++++++++++------ core/vm/gas_table_test.go | 4 +- core/vm/instructions.go | 16 +++---- core/vm/instructions_test.go | 14 +++--- core/vm/logger_test.go | 2 +- core/vm/runtime/env.go | 10 +++-- core/vm/runtime/runtime.go | 6 +-- eth/api_backend.go | 5 ++- eth/api_tracer.go | 60 ++++++++++++------------- eth/tracers/tracer.go | 2 +- eth/tracers/tracer_test.go | 4 +- eth/tracers/tracers_test.go | 21 +++++---- les/api_backend.go | 5 ++- les/odr_test.go | 10 +++-- light/odr_test.go | 5 ++- tests/state_test_util.go | 5 ++- tests/vm_test_util.go | 10 +++-- 24 files changed, 183 insertions(+), 139 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index c7efca440b..695d81bf03 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -542,10 +542,11 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // Execute the call. msg := callMsg{call} - evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) + txContext := core.NewEVMTxContext(msg) + evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmEnv := vm.NewEVM(evmContext, stateDB, b.config, vm.Config{}) + vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{}) gasPool := new(core.GasPool).AddGas(math.MaxUint64) return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index d8b93d291a..171443e156 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -110,7 +110,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txIndex = 0 ) gaspool.AddGas(pre.Env.GasLimit) - vmContext := vm.Context{ + vmContext := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, Coinbase: pre.Env.Coinbase, @@ -119,7 +119,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, Difficulty: pre.Env.Difficulty, GasLimit: pre.Env.GasLimit, GetHash: getHash, - // GasPrice and Origin needs to be set per transaction } // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's // done in StateProcessor.Process(block, ...), right before transactions are applied. @@ -143,10 +142,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmConfig.Tracer = tracer vmConfig.Debug = (tracer != nil) statedb.Prepare(tx.Hash(), blockHash, txIndex) - vmContext.GasPrice = msg.GasPrice() - vmContext.Origin = msg.From() + txContext := core.NewEVMTxContext(msg) - evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) if chainConfig.IsYoloV2(vmContext.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { @@ -185,7 +183,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, receipt.GasUsed = msgResult.UsedGas // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { - receipt.ContractAddress = crypto.CreateAddress(evm.Context.Origin, tx.Nonce()) + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index debee1182b..d3e824bd68 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -671,12 +671,13 @@ func (api *RetestethAPI) AccountRange(ctx context.Context, } // Recompute transactions up to the target index. signer := types.MakeSigner(api.blockchain.Config(), block.Number()) + context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) + txContext := core.NewEVMTxContext(msg) // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } @@ -781,12 +782,13 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context, } // Recompute transactions up to the target index. signer := types.MakeSigner(api.blockchain.Config(), block.Number()) + context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) + txContext := core.NewEVMTxContext(msg) // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/core/evm.go b/core/evm.go index 8abe5a0477..8f69d51499 100644 --- a/core/evm.go +++ b/core/evm.go @@ -35,8 +35,8 @@ type ChainContext interface { GetHeader(common.Hash, uint64) *types.Header } -// NewEVMContext creates a new context for use in the EVM. -func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context { +// NewEVMBlockContext creates a new context for use in the EVM. +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { // If we don't have an explicit author (i.e. not mining), extract from the header var beneficiary common.Address if author == nil { @@ -44,17 +44,23 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author } else { beneficiary = *author } - return vm.Context{ + return vm.BlockContext{ CanTransfer: CanTransfer, Transfer: Transfer, GetHash: GetHashFn(header, chain), - Origin: msg.From(), Coinbase: beneficiary, BlockNumber: new(big.Int).Set(header.Number), Time: new(big.Int).SetUint64(header.Time), Difficulty: new(big.Int).Set(header.Difficulty), GasLimit: header.GasLimit, - GasPrice: new(big.Int).Set(msg.GasPrice()), + } +} + +// NewEVMTxContext creates a new transaction context for a single transaction. +func NewEVMTxContext(msg Message) vm.TxContext { + return vm.TxContext{ + Origin: msg.From(), + GasPrice: new(big.Int).Set(msg.GasPrice()), } } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 1c550fa8bc..6fa52c2d9b 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -86,8 +86,9 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co return err } // Create the EVM and execute the transaction - context := NewEVMContext(msg, header, bc, author) - vm := vm.NewEVM(context, statedb, config, cfg) + context := NewEVMBlockContext(header, bc, author) + txContext := NewEVMTxContext(msg) + vm := vm.NewEVM(context, txContext, statedb, config, cfg) _, err = ApplyMessage(vm, msg, gaspool) return err diff --git a/core/state_processor.go b/core/state_processor.go index ac6046b717..d992dfb9cb 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -65,10 +65,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } + blockContext := NewEVMBlockContext(header, p.bc, nil) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Iterate over and process the individual transactions for i, tx := range block.Transactions() { + msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number)) + if err != nil { + return nil, nil, 0, err + } statedb.Prepare(tx.Hash(), block.Hash(), i) - receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg) + receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, err } @@ -81,34 +87,25 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return receipts, allLogs, *usedGas, nil } -// ApplyTransaction attempts to apply a transaction to the given state database -// and uses the input parameters for its environment. It returns the receipt -// for the transaction, gas used and an error if the transaction failed, -// indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { - msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) - if err != nil { - return nil, err - } +func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) { // Create a new context to be used in the EVM environment - context := NewEVMContext(msg, header, bc, author) - // Create a new environment which holds all relevant information - // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(context, statedb, config, cfg) - + txContext := NewEVMTxContext(msg) + // Add addresses to access list if applicable if config.IsYoloV2(header.Number) { statedb.AddAddressToAccessList(msg.From()) if dst := msg.To(); dst != nil { statedb.AddAddressToAccessList(*dst) // If it's a create-tx, the destination will be added inside evm.create } - for _, addr := range vmenv.ActivePrecompiles() { + for _, addr := range evm.ActivePrecompiles() { statedb.AddAddressToAccessList(addr) } } + // Update the evm with the new transaction context. + evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env) - result, err := ApplyMessage(vmenv, msg, gp) + result, err := ApplyMessage(evm, msg, gp) if err != nil { return nil, err } @@ -128,7 +125,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo receipt.GasUsed = result.UsedGas // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { - receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) @@ -139,3 +136,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return receipt, err } + +// ApplyTransaction attempts to apply a transaction to the given state database +// and uses the input parameters for its environment. It returns the receipt +// for the transaction, gas used and an error if the transaction failed, +// indicating the block was invalid. +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { + msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) + if err != nil { + return nil, err + } + // Create a new context to be used in the EVM environment + blockContext := NewEVMBlockContext(header, bc, author) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) + return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv) +} diff --git a/core/state_transition.go b/core/state_transition.go index 9a9bf475e9..72e8b02a1c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -230,8 +230,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } msg := st.msg sender := vm.AccountRef(msg.From()) - homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber) - istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber) + homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) + istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) contractCreation := msg.To() == nil // Check clauses 4-5, subtract intrinsic gas if everything is correct @@ -245,7 +245,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.gas -= gas // Check clause 6 - if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) { + if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { return nil, ErrInsufficientFundsForTransfer } var ( @@ -260,7 +260,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } st.refundGas() - st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) + st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) return &ExecutionResult{ UsedGas: st.gasUsed(), diff --git a/core/vm/evm.go b/core/vm/evm.go index 8f6e603aee..6c7b2370a5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -91,9 +91,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return nil, errors.New("no compatible interpreter") } -// Context provides the EVM with auxiliary information. Once provided +// BlockContext provides the EVM with auxiliary information. Once provided // it shouldn't be modified. -type Context struct { +type BlockContext struct { // CanTransfer returns whether the account contains // sufficient ether to transfer the value CanTransfer CanTransferFunc @@ -102,10 +102,6 @@ type Context struct { // GetHash returns the hash corresponding to n GetHash GetHashFunc - // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE - // Block information Coinbase common.Address // Provides information for COINBASE GasLimit uint64 // Provides information for GASLIMIT @@ -114,6 +110,14 @@ type Context struct { Difficulty *big.Int // Provides information for DIFFICULTY } +// TxContext provides the EVM with information about a transaction. +// All fields can change between transactions. +type TxContext struct { + // Message information + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE +} + // EVM is the Ethereum Virtual Machine base object and provides // the necessary tools to run a contract on the given state with // the provided context. It should be noted that any error @@ -125,7 +129,8 @@ type Context struct { // The EVM should never be reused and is not thread safe. type EVM struct { // Context provides auxiliary blockchain related information - Context + Context BlockContext + TxContext // StateDB gives access to the underlying state StateDB StateDB // Depth is the current call stack @@ -153,17 +158,18 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { +func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { evm := &EVM{ - Context: ctx, + Context: blockCtx, + TxContext: txCtx, StateDB: statedb, vmConfig: vmConfig, chainConfig: chainConfig, - chainRules: chainConfig.Rules(ctx.BlockNumber), + chainRules: chainConfig.Rules(blockCtx.BlockNumber), interpreters: make([]Interpreter, 0, 1), } - if chainConfig.IsEWASM(ctx.BlockNumber) { + if chainConfig.IsEWASM(blockCtx.BlockNumber) { // to be implemented by EVM-C and Wagon PRs. // if vmConfig.EWASMInterpreter != "" { // extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":") @@ -187,6 +193,13 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon return evm } +// Reset resets the EVM with a new transaction context.Reset +// This is not threadsafe and should only be done very cautiously. +func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { + evm.TxContext = txCtx + evm.StateDB = statedb +} + // Cancel cancels any running EVM operation. This may be called concurrently and // it's safe to be called multiple times. func (evm *EVM) Cancel() { @@ -233,7 +246,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), addr, value) + evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { @@ -426,7 +439,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } - if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { + if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } nonce := evm.StateDB.GetNonce(caller.Address()) @@ -447,7 +460,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } - evm.Transfer(evm.StateDB, caller.Address(), address, value) + evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 419c903062..6cd126c9b4 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -87,11 +87,11 @@ func TestEIP2200(t *testing.T) { statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) statedb.Finalise(true) // Push the state into the "original" slot - vmctx := Context{ + vmctx := BlockContext{ CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true }, Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, } - vmenv := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int)) if err != tt.failure { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index adf44b7f48..4ded0239d9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -438,14 +438,14 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) return nil, nil } var upper, lower uint64 - upper = interpreter.evm.BlockNumber.Uint64() + upper = interpreter.evm.Context.BlockNumber.Uint64() if upper < 257 { lower = 0 } else { lower = upper - 256 } if num64 >= lower && num64 < upper { - num.SetBytes(interpreter.evm.GetHash(num64).Bytes()) + num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) } else { num.Clear() } @@ -453,30 +453,30 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) } func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Coinbase.Bytes())) + callContext.stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes())) return nil, nil } func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(interpreter.evm.Time) + v, _ := uint256.FromBig(interpreter.evm.Context.Time) callContext.stack.push(v) return nil, nil } func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(interpreter.evm.BlockNumber) + v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber) callContext.stack.push(v) return nil, nil } func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - v, _ := uint256.FromBig(interpreter.evm.Difficulty) + v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty) callContext.stack.push(v) return nil, nil } func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { - callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.GasLimit)) + callContext.stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit)) return nil, nil } @@ -842,7 +842,7 @@ func makeLog(size int) executionFunc { Data: d, // This is a non-consensus field, but assigned here because // core/state doesn't know the current block number. - BlockNumber: interpreter.evm.BlockNumber.Uint64(), + BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(), }) return nil, nil diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 0b6fb1f486..985d5a5156 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -92,7 +92,7 @@ func init() { func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() rstack = newReturnStack() pc = uint64(0) @@ -192,7 +192,7 @@ func TestSAR(t *testing.T) { func TestAddMod(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) pc = uint64(0) @@ -231,7 +231,7 @@ func TestAddMod(t *testing.T) { // getResult is a convenience function to generate the expected values func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() pc = uint64(0) interpreter = env.interpreter.(*EVMInterpreter) @@ -281,7 +281,7 @@ func TestJsonTestcases(t *testing.T) { func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) ) @@ -515,7 +515,7 @@ func BenchmarkOpIsZero(b *testing.B) { func TestOpMstore(t *testing.T) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) @@ -539,7 +539,7 @@ func TestOpMstore(t *testing.T) { func BenchmarkOpMstore(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) @@ -560,7 +560,7 @@ func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpSHA3(bench *testing.B) { var ( - env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) stack, rstack = newstack(), newReturnStack() mem = NewMemory() evmInterpreter = NewEVMInterpreter(env, env.vmConfig) diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index e287f0c7aa..bf7d5358f8 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -51,7 +51,7 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 } func TestStoreCapture(t *testing.T) { var ( - env = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{}) + env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 38ee448904..6c4c72eeac 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -22,18 +22,20 @@ import ( ) func NewEnv(cfg *Config) *vm.EVM { - context := vm.Context{ + txContext := vm.TxContext{ + Origin: cfg.Origin, + GasPrice: cfg.GasPrice, + } + blockContext := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, GetHash: cfg.GetHashFn, - Origin: cfg.Origin, Coinbase: cfg.Coinbase, BlockNumber: cfg.BlockNumber, Time: cfg.Time, Difficulty: cfg.Difficulty, GasLimit: cfg.GasLimit, - GasPrice: cfg.GasPrice, } - return vm.NewEVM(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig) + return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index d99e8f3b2b..8abd378cef 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -113,7 +113,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { @@ -150,7 +150,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) - if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) for _, addr := range vmenv.ActivePrecompiles() { cfg.State.AddAddressToAccessList(addr) @@ -178,7 +178,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) - if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + if cfg.ChainConfig.IsYoloV2(vmenv.Context.BlockNumber) { cfg.State.AddAddressToAccessList(cfg.Origin) cfg.State.AddAddressToAccessList(address) for _, addr := range vmenv.ActivePrecompiles() { diff --git a/eth/api_backend.go b/eth/api_backend.go index 0e91691d8f..e7f676f178 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -194,8 +194,9 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { vmError := func() error { return nil } - context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) - return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) + return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 90d4a95c14..1a8c405cf4 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -203,13 +203,11 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Fetch and execute the next block trace tasks for task := range tasks { signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number()) - + blockCtx := core.NewEVMBlockContext(task.block.Header(), api.eth.blockchain, nil) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { msg, _ := tx.AsMessage(signer) - vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil) - - res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) + res, err := api.traceTx(ctx, msg, blockCtx, task.statedb, config) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -473,17 +471,15 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, if threads > len(txs) { threads = len(txs) } + blockCtx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) for th := 0; th < threads; th++ { pend.Add(1) go func() { defer pend.Done() - // Fetch and execute the next transaction trace tasks for task := range jobs { msg, _ := txs[task.index].AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) - - res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config) + res, err := api.traceTx(ctx, msg, blockCtx, task.statedb, config) if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue @@ -500,9 +496,9 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer) - vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + txContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(blockCtx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { failed = err break @@ -565,6 +561,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) dumps []string chainConfig = api.eth.blockchain.Config() + vmctx = core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -587,13 +584,12 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution var ( - msg, _ = tx.AsMessage(signer) - vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) - - vmConf vm.Config - dump *os.File - writer *bufio.Writer - err error + msg, _ = tx.AsMessage(signer) + txContext = core.NewEVMTxContext(msg) + vmConf vm.Config + dump *os.File + writer *bufio.Writer + err error ) // If the transaction needs tracing, swap out the configs if tx.Hash() == txHash || txHash == (common.Hash{}) { @@ -617,7 +613,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } } // Execute the transaction and flush any traces to disk - vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vmConf) + vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() @@ -776,18 +772,19 @@ func (api *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, // Execute the trace msg := args.ToMessage(api.eth.APIBackend.RPCGasCap()) - vmctx := core.NewEVMContext(msg, header, api.eth.blockchain, nil) + vmctx := core.NewEVMBlockContext(header, api.eth.blockchain, nil) return api.traceTx(ctx, msg, vmctx, statedb, config) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( - tracer vm.Tracer - err error + tracer vm.Tracer + err error + txContext = core.NewEVMTxContext(message) ) switch { case config != nil && config.Tracer != nil: @@ -817,7 +814,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) if err != nil { @@ -847,19 +844,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v } // computeTxEnv returns the execution environment of a certain transaction. -func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) { +func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { // Create the parent state database parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) if parent == nil { - return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) } statedb, err := api.computeStateDB(parent, reexec) if err != nil { - return nil, vm.Context{}, nil, err + return nil, vm.BlockContext{}, nil, err } if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.Context{}, statedb, nil + return nil, vm.BlockContext{}, statedb, nil } // Recompute transactions up to the target index. @@ -868,18 +865,19 @@ func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec for idx, tx := range block.Transactions() { // Assemble the transaction call message and return if the requested offset msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) if idx == txIndex { return msg, context, statedb, nil } // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, api.eth.blockchain.Config(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) } - return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 050fb05159..16a8c7698f 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -545,7 +545,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost if jst.err == nil { // Initialize the context if it wasn't done yet if !jst.inited { - jst.ctx["block"] = env.BlockNumber.Uint64() + jst.ctx["block"] = env.Context.BlockNumber.Uint64() jst.inited = true } // If tracing was interrupted, set the error and stop diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index b4de998651..554b2282f1 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -51,7 +51,7 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } func runTrace(tracer *Tracer) (json.RawMessage, error) { - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} @@ -166,7 +166,7 @@ func TestHaltBetweenSteps(t *testing.T) { t.Fatal(err) } - env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) + env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 18f8eb12aa..b749d832b4 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -143,16 +143,18 @@ func TestPrestateTracerCreate2(t *testing.T) { result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7 */ origin, _ := signer.Sender(tx) - context := vm.Context{ + txContext := vm.TxContext{ + Origin: origin, + GasPrice: big.NewInt(1), + } + context := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, - Origin: origin, Coinbase: common.Address{}, BlockNumber: new(big.Int).SetUint64(8000000), Time: new(big.Int).SetUint64(5), Difficulty: big.NewInt(0x30000), GasLimit: uint64(6000000), - GasPrice: big.NewInt(1), } alloc := core.GenesisAlloc{} @@ -175,7 +177,7 @@ func TestPrestateTracerCreate2(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { @@ -230,17 +232,18 @@ func TestCallTracer(t *testing.T) { } signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) origin, _ := signer.Sender(tx) - - context := vm.Context{ + txContext := vm.TxContext{ + Origin: origin, + GasPrice: tx.GasPrice(), + } + context := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, - Origin: origin, Coinbase: test.Context.Miner, BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), Time: new(big.Int).SetUint64(uint64(test.Context.Time)), Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), - GasPrice: tx.GasPrice(), } _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) @@ -249,7 +252,7 @@ func TestCallTracer(t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - evm := vm.NewEVM(context, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) + evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer) if err != nil { diff --git a/les/api_backend.go b/les/api_backend.go index 75bea56da6..9fbc21f459 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -171,8 +171,9 @@ func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { } func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { - context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) - return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(header, b.eth.blockchain, nil) + return vm.NewEVM(context, txContext, state, b.eth.chainConfig, vm.Config{}), state.Error, nil } func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { diff --git a/les/odr_test.go b/les/odr_test.go index ccd220d692..c157507dd2 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -130,8 +130,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, bc, nil) - vmenv := vm.NewEVM(context, statedb, config, vm.Config{}) + context := core.NewEVMBlockContext(header, bc, nil) + txContext := core.NewEVMTxContext(msg) + vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) @@ -143,8 +144,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai state := light.NewState(ctx, header, lc.Odr()) state.SetBalance(bankAddr, math.MaxBig256) msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, lc, nil) - vmenv := vm.NewEVM(context, state, config, vm.Config{}) + context := core.NewEVMBlockContext(header, lc, nil) + txContext := core.NewEVMTxContext(msg) + vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp) if state.Error() == nil { diff --git a/light/odr_test.go b/light/odr_test.go index 5f7f4d96cb..cb22334fdc 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -195,8 +195,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain // Perform read-only call. st.SetBalance(testBankAddress, math.MaxBig256) msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, chain, nil) - vmenv := vm.NewEVM(context, st, config, vm.Config{}) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(header, chain, nil) + vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) result, _ := core.ApplyMessage(vmenv, msg, gp) res = append(res, result.Return()...) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 28a5313129..96c7316969 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -182,9 +182,10 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh if err != nil { return nil, nil, common.Hash{}, err } - context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash - evm := vm.NewEVM(context, statedb, config, vmconfig) + evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) if config.IsYoloV2(context.BlockNumber) { statedb.AddAddressToAccessList(msg.From()) diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index ad124b7b25..418cc67168 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -138,20 +138,22 @@ func (t *VMTest) newEVM(statedb *state.StateDB, vmconfig vm.Config) *vm.EVM { return core.CanTransfer(db, address, amount) } transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {} - context := vm.Context{ + txContext := vm.TxContext{ + Origin: t.json.Exec.Origin, + GasPrice: t.json.Exec.GasPrice, + } + context := vm.BlockContext{ CanTransfer: canTransfer, Transfer: transfer, GetHash: vmTestBlockHash, - Origin: t.json.Exec.Origin, Coinbase: t.json.Env.Coinbase, BlockNumber: new(big.Int).SetUint64(t.json.Env.Number), Time: new(big.Int).SetUint64(t.json.Env.Timestamp), GasLimit: t.json.Env.GasLimit, Difficulty: t.json.Env.Difficulty, - GasPrice: t.json.Exec.GasPrice, } vmconfig.NoRecursion = true - return vm.NewEVM(context, statedb, params.MainnetChainConfig, vmconfig) + return vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vmconfig) } func vmTestBlockHash(n uint64) common.Hash { From cf856ea1ad96ac39ea477087822479b63417036a Mon Sep 17 00:00:00 2001 From: Nicolas Feignon Date: Fri, 13 Nov 2020 13:43:15 +0100 Subject: [PATCH 137/948] accounts/abi: template: set events Raw field in Parse methods (#21807) --- accounts/abi/bind/template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 5329b3ebc3..8dac11f79f 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -541,6 +541,7 @@ var ( if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { return nil, err } + event.Raw = log return event, nil } From 92c56eb820eff839e062947cd6231cc8593823e5 Mon Sep 17 00:00:00 2001 From: Pascal Dierich Date: Mon, 16 Nov 2020 14:08:13 +0100 Subject: [PATCH 138/948] common: fix documentation of Address.SetBytes (#21814) --- common/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/types.go b/common/types.go index cdcc6c20ad..94cf622e8c 100644 --- a/common/types.go +++ b/common/types.go @@ -240,7 +240,7 @@ func (a Address) Format(s fmt.State, c rune) { } // SetBytes sets the address to the value of b. -// If b is larger than len(a) it will panic. +// If b is larger than len(a), b will be cropped from the left. func (a *Address) SetBytes(b []byte) { if len(b) > len(a) { b = b[len(b)-AddressLength:] From 1ea7537997d1c5444f78ec87f8b309b2f908c76e Mon Sep 17 00:00:00 2001 From: Sad Pencil Date: Tue, 17 Nov 2020 16:51:36 +0800 Subject: [PATCH 139/948] crypto/bn256: refine comments according to #19577, #21595, and #21836 (#21847) --- crypto/bn256/cloudflare/bn256.go | 9 +++++++-- crypto/bn256/google/bn256.go | 5 +++-- crypto/bn256/google/constants.go | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go index a6dd972ba8..4f607af2ad 100644 --- a/crypto/bn256/cloudflare/bn256.go +++ b/crypto/bn256/cloudflare/bn256.go @@ -9,8 +9,13 @@ // // This package specifically implements the Optimal Ate pairing over a 256-bit // Barreto-Naehrig curve as described in -// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible -// with the implementation described in that paper. +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. +// +// (This package previously claimed to operate at a 128-bit security level. +// However, recent improvements in attacks mean that is no longer true. See +// https://moderncrypto.org/mail-archive/curves/2016/000740.html.) package bn256 import ( diff --git a/crypto/bn256/google/bn256.go b/crypto/bn256/google/bn256.go index e0402e51f0..0a9d5cd35d 100644 --- a/crypto/bn256/google/bn256.go +++ b/crypto/bn256/google/bn256.go @@ -12,8 +12,9 @@ // // This package specifically implements the Optimal Ate pairing over a 256-bit // Barreto-Naehrig curve as described in -// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible -// with the implementation described in that paper. +// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not +// compatible with the implementation described in that paper, as different +// parameters are chosen. // // (This package previously claimed to operate at a 128-bit security level. // However, recent improvements in attacks mean that is no longer true. See diff --git a/crypto/bn256/google/constants.go b/crypto/bn256/google/constants.go index 0db432c9cf..2990bd9512 100644 --- a/crypto/bn256/google/constants.go +++ b/crypto/bn256/google/constants.go @@ -20,7 +20,9 @@ var u = bigFromBase10("4965661367192848881") var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583") // Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1. +// Needs to be highly 2-adic for efficient SNARK key and proof generation. // Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081. +// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters. var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617") // xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. From 844485ec6aa2da15f203dda43b919f842c866750 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Tue, 17 Nov 2020 01:35:58 -0800 Subject: [PATCH 140/948] consensus/ethash: fix usage of *reflect.SliceHeader (#21372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * consensus/ethash: only use *reflect.SliceHeader, not reflect.SliceHeader. See comment here: https://github.com/golang/go/issues/40397\#issuecomment-663748689 * consensus/ethash: pr feedback from @mdempsky, makes a copy of dest such that is not mutated * consensus/ethash: remove noop assign * consensus/ethash: apply same fix to another location Co-authored-by: Péter Szilágyi Co-authored-by: Martin Holst Swende --- consensus/ethash/algorithm.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go index 47d7e51b59..80379597e2 100644 --- a/consensus/ethash/algorithm.go +++ b/consensus/ethash/algorithm.go @@ -151,10 +151,12 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) { logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed)) }() // Convert our destination slice to a byte buffer - header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest)) - header.Len *= 4 - header.Cap *= 4 - cache := *(*[]byte)(unsafe.Pointer(&header)) + var cache []byte + cacheHdr := (*reflect.SliceHeader)(unsafe.Pointer(&cache)) + dstHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest)) + cacheHdr.Data = dstHdr.Data + cacheHdr.Len = dstHdr.Len * 4 + cacheHdr.Cap = dstHdr.Cap * 4 // Calculate the number of theoretical rows (we'll store in one buffer nonetheless) size := uint64(len(cache)) @@ -283,10 +285,12 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) { swapped := !isLittleEndian() // Convert our destination slice to a byte buffer - header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest)) - header.Len *= 4 - header.Cap *= 4 - dataset := *(*[]byte)(unsafe.Pointer(&header)) + var dataset []byte + datasetHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dataset)) + destHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest)) + datasetHdr.Data = destHdr.Data + datasetHdr.Len = destHdr.Len * 4 + datasetHdr.Cap = destHdr.Cap * 4 // Generate the dataset on many goroutines since it takes a while threads := runtime.NumCPU() From d513584e5280096efb096a92d512f6dc8771a658 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 17 Nov 2020 11:44:38 +0100 Subject: [PATCH 141/948] cmd/geth: remove retesteth --- cmd/geth/main.go | 2 - cmd/geth/retesteth.go | 918 -------------------------------- cmd/geth/retesteth_copypaste.go | 148 ----- 3 files changed, 1068 deletions(-) delete mode 100644 cmd/geth/retesteth.go delete mode 100644 cmd/geth/retesteth_copypaste.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 38e48534dc..d12457176a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -244,8 +244,6 @@ func init() { licenseCommand, // See config.go dumpConfigCommand, - // See retesteth.go - retestethCommand, // See cmd/utils/flags_legacy.go utils.ShowDeprecated, } diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go deleted file mode 100644 index d3e824bd68..0000000000 --- a/cmd/geth/retesteth.go +++ /dev/null @@ -1,918 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "context" - "fmt" - "math/big" - "os" - "os/signal" - "time" - - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/consensus/misc" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" - - cli "gopkg.in/urfave/cli.v1" -) - -var ( - rpcPortFlag = cli.IntFlag{ - Name: "rpcport", - Usage: "HTTP-RPC server listening port", - Value: node.DefaultHTTPPort, - } - retestethCommand = cli.Command{ - Action: utils.MigrateFlags(retesteth), - Name: "retesteth", - Usage: "Launches geth in retesteth mode", - ArgsUsage: "", - Flags: []cli.Flag{rpcPortFlag}, - Category: "MISCELLANEOUS COMMANDS", - Description: `Launches geth in retesteth mode (no database, no network, only retesteth RPC interface)`, - } -) - -type RetestethTestAPI interface { - SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) - MineBlocks(ctx context.Context, number uint64) (bool, error) - ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) - ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) - RewindToBlock(ctx context.Context, number uint64) (bool, error) - GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) -} - -type RetestethEthAPI interface { - SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) - BlockNumber(ctx context.Context) (uint64, error) - GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) - GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) - GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) - GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) - GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) -} - -type RetestethDebugAPI interface { - AccountRange(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - addressHash *math.HexOrDecimal256, maxResults uint64, - ) (AccountRangeResult, error) - StorageRangeAt(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - address common.Address, - begin *math.HexOrDecimal256, maxResults uint64, - ) (StorageRangeResult, error) -} - -type RetestWeb3API interface { - ClientVersion(ctx context.Context) (string, error) -} - -type RetestethAPI struct { - ethDb ethdb.Database - db state.Database - chainConfig *params.ChainConfig - author common.Address - extraData []byte - genesisHash common.Hash - engine *NoRewardEngine - blockchain *core.BlockChain - txMap map[common.Address]map[uint64]*types.Transaction // Sender -> Nonce -> Transaction - txSenders map[common.Address]struct{} // Set of transaction senders - blockInterval uint64 -} - -type ChainParams struct { - SealEngine string `json:"sealEngine"` - Params CParamsParams `json:"params"` - Genesis CParamsGenesis `json:"genesis"` - Accounts map[common.Address]CParamsAccount `json:"accounts"` -} - -type CParamsParams struct { - AccountStartNonce math.HexOrDecimal64 `json:"accountStartNonce"` - HomesteadForkBlock *math.HexOrDecimal64 `json:"homesteadForkBlock"` - EIP150ForkBlock *math.HexOrDecimal64 `json:"EIP150ForkBlock"` - EIP158ForkBlock *math.HexOrDecimal64 `json:"EIP158ForkBlock"` - DaoHardforkBlock *math.HexOrDecimal64 `json:"daoHardforkBlock"` - ByzantiumForkBlock *math.HexOrDecimal64 `json:"byzantiumForkBlock"` - ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"` - ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"` - IstanbulBlock *math.HexOrDecimal64 `json:"istanbulForkBlock"` - ChainID *math.HexOrDecimal256 `json:"chainID"` - MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"` - TieBreakingGas bool `json:"tieBreakingGas"` - MinGasLimit math.HexOrDecimal64 `json:"minGasLimit"` - MaxGasLimit math.HexOrDecimal64 `json:"maxGasLimit"` - GasLimitBoundDivisor math.HexOrDecimal64 `json:"gasLimitBoundDivisor"` - MinimumDifficulty math.HexOrDecimal256 `json:"minimumDifficulty"` - DifficultyBoundDivisor math.HexOrDecimal256 `json:"difficultyBoundDivisor"` - DurationLimit math.HexOrDecimal256 `json:"durationLimit"` - BlockReward math.HexOrDecimal256 `json:"blockReward"` - NetworkID math.HexOrDecimal256 `json:"networkID"` -} - -type CParamsGenesis struct { - Nonce math.HexOrDecimal64 `json:"nonce"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - MixHash *math.HexOrDecimal256 `json:"mixHash"` - Author common.Address `json:"author"` - Timestamp math.HexOrDecimal64 `json:"timestamp"` - ParentHash common.Hash `json:"parentHash"` - ExtraData hexutil.Bytes `json:"extraData"` - GasLimit math.HexOrDecimal64 `json:"gasLimit"` -} - -type CParamsAccount struct { - Balance *math.HexOrDecimal256 `json:"balance"` - Precompiled *CPAccountPrecompiled `json:"precompiled"` - Code hexutil.Bytes `json:"code"` - Storage map[string]string `json:"storage"` - Nonce *math.HexOrDecimal64 `json:"nonce"` -} - -type CPAccountPrecompiled struct { - Name string `json:"name"` - StartingBlock math.HexOrDecimal64 `json:"startingBlock"` - Linear *CPAPrecompiledLinear `json:"linear"` -} - -type CPAPrecompiledLinear struct { - Base uint64 `json:"base"` - Word uint64 `json:"word"` -} - -type AccountRangeResult struct { - AddressMap map[common.Hash]common.Address `json:"addressMap"` - NextKey common.Hash `json:"nextKey"` -} - -type StorageRangeResult struct { - Complete bool `json:"complete"` - Storage map[common.Hash]SRItem `json:"storage"` -} - -type SRItem struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type NoRewardEngine struct { - inner consensus.Engine - rewardsOn bool -} - -func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) { - return e.inner.Author(header) -} - -func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { - return e.inner.VerifyHeader(chain, header, seal) -} - -func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { - return e.inner.VerifyHeaders(chain, headers, seals) -} - -func (e *NoRewardEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { - return e.inner.VerifyUncles(chain, block) -} - -func (e *NoRewardEngine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Header) error { - return e.inner.VerifySeal(chain, header) -} - -func (e *NoRewardEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { - return e.inner.Prepare(chain, header) -} - -func (e *NoRewardEngine) accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { - // Simply touch miner and uncle coinbase accounts - reward := big.NewInt(0) - for _, uncle := range uncles { - state.AddBalance(uncle.Coinbase, reward) - } - state.AddBalance(header.Coinbase, reward) -} - -func (e *NoRewardEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction, - uncles []*types.Header) { - if e.rewardsOn { - e.inner.Finalize(chain, header, statedb, txs, uncles) - } else { - e.accumulateRewards(chain.Config(), statedb, header, uncles) - header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - } -} - -func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction, - uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - if e.rewardsOn { - return e.inner.FinalizeAndAssemble(chain, header, statedb, txs, uncles, receipts) - } else { - e.accumulateRewards(chain.Config(), statedb, header, uncles) - header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - - // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts, new(trie.Trie)), nil - } -} - -func (e *NoRewardEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { - return e.inner.Seal(chain, block, results, stop) -} - -func (e *NoRewardEngine) SealHash(header *types.Header) common.Hash { - return e.inner.SealHash(header) -} - -func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { - return e.inner.CalcDifficulty(chain, time, parent) -} - -func (e *NoRewardEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API { - return e.inner.APIs(chain) -} - -func (e *NoRewardEngine) Close() error { - return e.inner.Close() -} - -func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) { - // Clean up - if api.blockchain != nil { - api.blockchain.Stop() - } - if api.engine != nil { - api.engine.Close() - } - if api.ethDb != nil { - api.ethDb.Close() - } - ethDb := rawdb.NewMemoryDatabase() - accounts := make(core.GenesisAlloc) - for address, account := range chainParams.Accounts { - balance := big.NewInt(0) - if account.Balance != nil { - balance.Set((*big.Int)(account.Balance)) - } - var nonce uint64 - if account.Nonce != nil { - nonce = uint64(*account.Nonce) - } - if account.Precompiled == nil || account.Balance != nil { - storage := make(map[common.Hash]common.Hash) - for k, v := range account.Storage { - storage[common.HexToHash(k)] = common.HexToHash(v) - } - accounts[address] = core.GenesisAccount{ - Balance: balance, - Code: account.Code, - Nonce: nonce, - Storage: storage, - } - } - } - chainId := big.NewInt(1) - if chainParams.Params.ChainID != nil { - chainId.Set((*big.Int)(chainParams.Params.ChainID)) - } - var ( - homesteadBlock *big.Int - daoForkBlock *big.Int - eip150Block *big.Int - eip155Block *big.Int - eip158Block *big.Int - byzantiumBlock *big.Int - constantinopleBlock *big.Int - petersburgBlock *big.Int - istanbulBlock *big.Int - ) - if chainParams.Params.HomesteadForkBlock != nil { - homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock)) - } - if chainParams.Params.DaoHardforkBlock != nil { - daoForkBlock = big.NewInt(int64(*chainParams.Params.DaoHardforkBlock)) - } - if chainParams.Params.EIP150ForkBlock != nil { - eip150Block = big.NewInt(int64(*chainParams.Params.EIP150ForkBlock)) - } - if chainParams.Params.EIP158ForkBlock != nil { - eip158Block = big.NewInt(int64(*chainParams.Params.EIP158ForkBlock)) - eip155Block = eip158Block - } - if chainParams.Params.ByzantiumForkBlock != nil { - byzantiumBlock = big.NewInt(int64(*chainParams.Params.ByzantiumForkBlock)) - } - if chainParams.Params.ConstantinopleForkBlock != nil { - constantinopleBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleForkBlock)) - } - if chainParams.Params.ConstantinopleFixForkBlock != nil { - petersburgBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleFixForkBlock)) - } - if constantinopleBlock != nil && petersburgBlock == nil { - petersburgBlock = big.NewInt(100000000000) - } - if chainParams.Params.IstanbulBlock != nil { - istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock)) - } - - genesis := &core.Genesis{ - Config: ¶ms.ChainConfig{ - ChainID: chainId, - HomesteadBlock: homesteadBlock, - DAOForkBlock: daoForkBlock, - DAOForkSupport: true, - EIP150Block: eip150Block, - EIP155Block: eip155Block, - EIP158Block: eip158Block, - ByzantiumBlock: byzantiumBlock, - ConstantinopleBlock: constantinopleBlock, - PetersburgBlock: petersburgBlock, - IstanbulBlock: istanbulBlock, - }, - Nonce: uint64(chainParams.Genesis.Nonce), - Timestamp: uint64(chainParams.Genesis.Timestamp), - ExtraData: chainParams.Genesis.ExtraData, - GasLimit: uint64(chainParams.Genesis.GasLimit), - Difficulty: big.NewInt(0).Set((*big.Int)(chainParams.Genesis.Difficulty)), - Mixhash: common.BigToHash((*big.Int)(chainParams.Genesis.MixHash)), - Coinbase: chainParams.Genesis.Author, - ParentHash: chainParams.Genesis.ParentHash, - Alloc: accounts, - } - chainConfig, genesisHash, err := core.SetupGenesisBlock(ethDb, genesis) - if err != nil { - return false, err - } - fmt.Printf("Chain config: %v\n", chainConfig) - - var inner consensus.Engine - switch chainParams.SealEngine { - case "NoProof", "NoReward": - inner = ethash.NewFaker() - case "Ethash": - inner = ethash.New(ethash.Config{ - CacheDir: "ethash", - CachesInMem: 2, - CachesOnDisk: 3, - CachesLockMmap: false, - DatasetsInMem: 1, - DatasetsOnDisk: 2, - DatasetsLockMmap: false, - }, nil, false) - default: - return false, fmt.Errorf("unrecognised seal engine: %s", chainParams.SealEngine) - } - engine := &NoRewardEngine{inner: inner, rewardsOn: chainParams.SealEngine != "NoReward"} - - blockchain, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil, nil) - if err != nil { - return false, err - } - - api.chainConfig = chainConfig - api.genesisHash = genesisHash - api.author = chainParams.Genesis.Author - api.extraData = chainParams.Genesis.ExtraData - api.ethDb = ethDb - api.engine = engine - api.blockchain = blockchain - api.db = state.NewDatabase(api.ethDb) - api.txMap = make(map[common.Address]map[uint64]*types.Transaction) - api.txSenders = make(map[common.Address]struct{}) - api.blockInterval = 0 - return true, nil -} - -func (api *RetestethAPI) SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) { - tx := new(types.Transaction) - if err := rlp.DecodeBytes(rawTx, tx); err != nil { - // Return nil is not by mistake - some tests include sending transaction where gasLimit overflows uint64 - return common.Hash{}, nil - } - signer := types.MakeSigner(api.chainConfig, big.NewInt(int64(api.currentNumber()))) - sender, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } - if nonceMap, ok := api.txMap[sender]; ok { - nonceMap[tx.Nonce()] = tx - } else { - nonceMap = make(map[uint64]*types.Transaction) - nonceMap[tx.Nonce()] = tx - api.txMap[sender] = nonceMap - } - api.txSenders[sender] = struct{}{} - return tx.Hash(), nil -} - -func (api *RetestethAPI) MineBlocks(ctx context.Context, number uint64) (bool, error) { - for i := 0; i < int(number); i++ { - if err := api.mineBlock(); err != nil { - return false, err - } - } - fmt.Printf("Mined %d blocks\n", number) - return true, nil -} - -func (api *RetestethAPI) currentNumber() uint64 { - if current := api.blockchain.CurrentBlock(); current != nil { - return current.NumberU64() - } - return 0 -} - -func (api *RetestethAPI) mineBlock() error { - number := api.currentNumber() - parentHash := rawdb.ReadCanonicalHash(api.ethDb, number) - parent := rawdb.ReadBlock(api.ethDb, parentHash, number) - var timestamp uint64 - if api.blockInterval == 0 { - timestamp = uint64(time.Now().Unix()) - } else { - timestamp = parent.Time() + api.blockInterval - } - gasLimit := core.CalcGasLimit(parent, 9223372036854775807, 9223372036854775807) - header := &types.Header{ - ParentHash: parent.Hash(), - Number: big.NewInt(int64(number + 1)), - GasLimit: gasLimit, - Extra: api.extraData, - Time: timestamp, - } - header.Coinbase = api.author - if api.engine != nil { - api.engine.Prepare(api.blockchain, header) - } - // If we are care about TheDAO hard-fork check whether to override the extra-data or not - if daoBlock := api.chainConfig.DAOForkBlock; daoBlock != nil { - // Check whether the block is among the fork extra-override range - limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) - if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { - // Depending whether we support or oppose the fork, override differently - if api.chainConfig.DAOForkSupport { - header.Extra = common.CopyBytes(params.DAOForkBlockExtra) - } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { - header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data - } - } - } - statedb, err := api.blockchain.StateAt(parent.Root()) - if err != nil { - return err - } - if api.chainConfig.DAOForkSupport && api.chainConfig.DAOForkBlock != nil && api.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 { - misc.ApplyDAOHardFork(statedb) - } - gasPool := new(core.GasPool).AddGas(header.GasLimit) - txCount := 0 - var txs []*types.Transaction - var receipts []*types.Receipt - var blockFull = gasPool.Gas() < params.TxGas - for address := range api.txSenders { - if blockFull { - break - } - m := api.txMap[address] - for nonce := statedb.GetNonce(address); ; nonce++ { - if tx, ok := m[nonce]; ok { - // Try to apply transactions to the state - statedb.Prepare(tx.Hash(), common.Hash{}, txCount) - snap := statedb.Snapshot() - - receipt, err := core.ApplyTransaction( - api.chainConfig, - api.blockchain, - &api.author, - gasPool, - statedb, - header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(), - ) - if err != nil { - statedb.RevertToSnapshot(snap) - break - } - txs = append(txs, tx) - receipts = append(receipts, receipt) - delete(m, nonce) - if len(m) == 0 { - // Last tx for the sender - delete(api.txMap, address) - delete(api.txSenders, address) - } - txCount++ - if gasPool.Gas() < params.TxGas { - blockFull = true - break - } - } else { - break // Gap in the nonces - } - } - } - block, err := api.engine.FinalizeAndAssemble(api.blockchain, header, statedb, txs, []*types.Header{}, receipts) - if err != nil { - return err - } - return api.importBlock(block) -} - -func (api *RetestethAPI) importBlock(block *types.Block) error { - if _, err := api.blockchain.InsertChain([]*types.Block{block}); err != nil { - return err - } - fmt.Printf("Imported block %d, head is %d\n", block.NumberU64(), api.currentNumber()) - return nil -} - -func (api *RetestethAPI) ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) { - api.blockInterval = interval - return true, nil -} - -func (api *RetestethAPI) ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) { - block := new(types.Block) - if err := rlp.DecodeBytes(rawBlock, block); err != nil { - return common.Hash{}, err - } - fmt.Printf("Importing block %d with parent hash: %x, genesisHash: %x\n", block.NumberU64(), block.ParentHash(), api.genesisHash) - if err := api.importBlock(block); err != nil { - return common.Hash{}, err - } - return block.Hash(), nil -} - -func (api *RetestethAPI) RewindToBlock(ctx context.Context, newHead uint64) (bool, error) { - if err := api.blockchain.SetHead(newHead); err != nil { - return false, err - } - // When we rewind, the transaction pool should be cleaned out. - api.txMap = make(map[common.Address]map[uint64]*types.Transaction) - api.txSenders = make(map[common.Address]struct{}) - return true, nil -} - -var emptyListHash common.Hash = common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") - -func (api *RetestethAPI) GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) { - receipt, _, _, _ := rawdb.ReadReceipt(api.ethDb, txHash, api.chainConfig) - if receipt == nil { - return emptyListHash, nil - } else { - if logListRlp, err := rlp.EncodeToBytes(receipt.Logs); err != nil { - return common.Hash{}, err - } else { - return common.BytesToHash(crypto.Keccak256(logListRlp)), nil - } - } -} - -func (api *RetestethAPI) BlockNumber(ctx context.Context) (uint64, error) { - return api.currentNumber(), nil -} - -func (api *RetestethAPI) GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) { - block := api.blockchain.GetBlockByNumber(uint64(blockNr)) - if block != nil { - response, err := RPCMarshalBlock(block, true, fullTx) - if err != nil { - return nil, err - } - response["author"] = response["miner"] - response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), uint64(blockNr))) - return response, err - } - return nil, fmt.Errorf("block %d not found", blockNr) -} - -func (api *RetestethAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) { - block := api.blockchain.GetBlockByHash(blockHash) - if block != nil { - response, err := RPCMarshalBlock(block, true, fullTx) - if err != nil { - return nil, err - } - response["author"] = response["miner"] - response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), block.Number().Uint64())) - return response, err - } - return nil, fmt.Errorf("block 0x%x not found", blockHash) -} - -func (api *RetestethAPI) AccountRange(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - addressHash *math.HexOrDecimal256, maxResults uint64, -) (AccountRangeResult, error) { - var ( - header *types.Header - block *types.Block - ) - if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 { - blockHash := common.BigToHash((*big.Int)(blockHashOrNumber)) - header = api.blockchain.GetHeaderByHash(blockHash) - block = api.blockchain.GetBlockByHash(blockHash) - //fmt.Printf("Account range: %x, txIndex %d, start: %x, maxResults: %d\n", blockHash, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults) - } else { - blockNumber := (*big.Int)(blockHashOrNumber).Uint64() - header = api.blockchain.GetHeaderByNumber(blockNumber) - block = api.blockchain.GetBlockByNumber(blockNumber) - //fmt.Printf("Account range: %d, txIndex %d, start: %x, maxResults: %d\n", blockNumber, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults) - } - parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var root common.Hash - var statedb *state.StateDB - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - root = header.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return AccountRangeResult{}, err - } - } else { - root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return AccountRangeResult{}, err - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.blockchain.Config(), block.Number()) - context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number())) - if idx == int(txIndex) { - // This is to make sure root can be opened by OpenTrie - root, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number())) - if err != nil { - return AccountRangeResult{}, err - } - break - } - } - } - accountTrie, err := statedb.Database().OpenTrie(root) - if err != nil { - return AccountRangeResult{}, err - } - it := trie.NewIterator(accountTrie.NodeIterator(common.BigToHash((*big.Int)(addressHash)).Bytes())) - result := AccountRangeResult{AddressMap: make(map[common.Hash]common.Address)} - for i := 0; i < int(maxResults) && it.Next(); i++ { - if preimage := accountTrie.GetKey(it.Key); preimage != nil { - result.AddressMap[common.BytesToHash(it.Key)] = common.BytesToAddress(preimage) - } - } - //fmt.Printf("Number of entries returned: %d\n", len(result.AddressMap)) - // Add the 'next key' so clients can continue downloading. - if it.Next() { - next := common.BytesToHash(it.Key) - result.NextKey = next - } - return result, nil -} - -func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) { - //fmt.Printf("GetBalance %x, block %d\n", address, blockNr) - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - return (*math.HexOrDecimal256)(statedb.GetBalance(address)), nil -} - -func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) { - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - return statedb.GetCode(address), nil -} - -func (api *RetestethAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) { - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return 0, err - } - return statedb.GetNonce(address), nil -} - -func (api *RetestethAPI) StorageRangeAt(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - address common.Address, - begin *math.HexOrDecimal256, maxResults uint64, -) (StorageRangeResult, error) { - var ( - header *types.Header - block *types.Block - ) - if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 { - blockHash := common.BigToHash((*big.Int)(blockHashOrNumber)) - header = api.blockchain.GetHeaderByHash(blockHash) - block = api.blockchain.GetBlockByHash(blockHash) - //fmt.Printf("Storage range: %x, txIndex %d, addr: %x, start: %x, maxResults: %d\n", - // blockHash, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults) - } else { - blockNumber := (*big.Int)(blockHashOrNumber).Uint64() - header = api.blockchain.GetHeaderByNumber(blockNumber) - block = api.blockchain.GetBlockByNumber(blockNumber) - //fmt.Printf("Storage range: %d, txIndex %d, addr: %x, start: %x, maxResults: %d\n", - // blockNumber, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults) - } - parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var root common.Hash - var statedb *state.StateDB - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - root = header.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return StorageRangeResult{}, err - } - } else { - root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return StorageRangeResult{}, err - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.blockchain.Config(), block.Number()) - context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - txContext := core.NewEVMTxContext(msg) - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, api.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - _ = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number())) - if idx == int(txIndex) { - // This is to make sure root can be opened by OpenTrie - _, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number())) - if err != nil { - return StorageRangeResult{}, err - } - } - } - } - storageTrie := statedb.StorageTrie(address) - it := trie.NewIterator(storageTrie.NodeIterator(common.BigToHash((*big.Int)(begin)).Bytes())) - result := StorageRangeResult{Storage: make(map[common.Hash]SRItem)} - for i := 0; /*i < int(maxResults) && */ it.Next(); i++ { - if preimage := storageTrie.GetKey(it.Key); preimage != nil { - key := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(preimage)) - v, _, err := rlp.SplitString(it.Value) - if err != nil { - return StorageRangeResult{}, err - } - value := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(v)) - ks, _ := key.MarshalText() - vs, _ := value.MarshalText() - if len(ks)%2 != 0 { - ks = append(append(append([]byte{}, ks[:2]...), byte('0')), ks[2:]...) - } - if len(vs)%2 != 0 { - vs = append(append(append([]byte{}, vs[:2]...), byte('0')), vs[2:]...) - } - result.Storage[common.BytesToHash(it.Key)] = SRItem{ - Key: string(ks), - Value: string(vs), - } - } - } - if it.Next() { - result.Complete = false - } else { - result.Complete = true - } - return result, nil -} - -func (api *RetestethAPI) ClientVersion(ctx context.Context) (string, error) { - return "Geth-" + params.VersionWithCommit(gitCommit, gitDate), nil -} - -func retesteth(ctx *cli.Context) error { - log.Info("Welcome to retesteth!") - // register signer API with server - var ( - extapiURL string - ) - apiImpl := &RetestethAPI{} - var testApi RetestethTestAPI = apiImpl - var ethApi RetestethEthAPI = apiImpl - var debugApi RetestethDebugAPI = apiImpl - var web3Api RetestWeb3API = apiImpl - rpcAPI := []rpc.API{ - { - Namespace: "test", - Public: true, - Service: testApi, - Version: "1.0", - }, - { - Namespace: "eth", - Public: true, - Service: ethApi, - Version: "1.0", - }, - { - Namespace: "debug", - Public: true, - Service: debugApi, - Version: "1.0", - }, - { - Namespace: "web3", - Public: true, - Service: web3Api, - Version: "1.0", - }, - } - vhosts := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPVirtualHostsFlag.Name)) - cors := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPCORSDomainFlag.Name)) - - // register apis and create handler stack - srv := rpc.NewServer() - err := node.RegisterApisFromWhitelist(rpcAPI, []string{"test", "eth", "debug", "web3"}, srv, false) - if err != nil { - utils.Fatalf("Could not register RPC apis: %w", err) - } - handler := node.NewHTTPHandlerStack(srv, cors, vhosts) - - // start http server - var RetestethHTTPTimeouts = rpc.HTTPTimeouts{ - ReadTimeout: 120 * time.Second, - WriteTimeout: 120 * time.Second, - IdleTimeout: 120 * time.Second, - } - httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.HTTPListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name)) - httpServer, _, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler) - if err != nil { - utils.Fatalf("Could not start RPC api: %v", err) - } - extapiURL = fmt.Sprintf("http://%s", httpEndpoint) - log.Info("HTTP endpoint opened", "url", extapiURL) - - defer func() { - // Don't bother imposing a timeout here. - httpServer.Shutdown(context.Background()) - log.Info("HTTP endpoint closed", "url", httpEndpoint) - }() - - abortChan := make(chan os.Signal, 11) - signal.Notify(abortChan, os.Interrupt) - - sig := <-abortChan - log.Info("Exiting...", "signal", sig) - return nil -} diff --git a/cmd/geth/retesteth_copypaste.go b/cmd/geth/retesteth_copypaste.go deleted file mode 100644 index e2795af7f9..0000000000 --- a/cmd/geth/retesteth_copypaste.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" -) - -// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction -type RPCTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` -} - -// newRPCTransaction returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - v, r, s := tx.RawSignatureValues() - - result := &RPCTransaction{ - From: from, - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: hexutil.Uint64(tx.Nonce()), - To: tx.To(), - Value: (*hexutil.Big)(tx.Value()), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = hexutil.Uint(index) - } - return result -} - -// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { - txs := b.Transactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) -} - -// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { - for idx, tx := range b.Transactions() { - if tx.Hash() == hash { - return newRPCTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are -// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain -// transaction hashes. -func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { - head := b.Header() // copies the header once - fields := map[string]interface{}{ - "number": (*hexutil.Big)(head.Number), - "hash": b.Hash(), - "parentHash": head.ParentHash, - "nonce": head.Nonce, - "mixHash": head.MixDigest, - "sha3Uncles": head.UncleHash, - "logsBloom": head.Bloom, - "stateRoot": head.Root, - "miner": head.Coinbase, - "difficulty": (*hexutil.Big)(head.Difficulty), - "extraData": hexutil.Bytes(head.Extra), - "size": hexutil.Uint64(b.Size()), - "gasLimit": hexutil.Uint64(head.GasLimit), - "gasUsed": hexutil.Uint64(head.GasUsed), - "timestamp": hexutil.Uint64(head.Time), - "transactionsRoot": head.TxHash, - "receiptsRoot": head.ReceiptHash, - } - - if inclTx { - formatTx := func(tx *types.Transaction) (interface{}, error) { - return tx.Hash(), nil - } - if fullTx { - formatTx = func(tx *types.Transaction) (interface{}, error) { - return newRPCTransactionFromBlockHash(b, tx.Hash()), nil - } - } - txs := b.Transactions() - transactions := make([]interface{}, len(txs)) - var err error - for i, tx := range txs { - if transactions[i], err = formatTx(tx); err != nil { - return nil, err - } - } - fields["transactions"] = transactions - } - - uncles := b.Uncles() - uncleHashes := make([]common.Hash, len(uncles)) - for i, uncle := range uncles { - uncleHashes[i] = uncle.Hash() - } - fields["uncles"] = uncleHashes - - return fields, nil -} From db87223269ca0986af312df78b84257ed9836dcf Mon Sep 17 00:00:00 2001 From: Abd ar-Rahman Hamidi Date: Tue, 17 Nov 2020 15:47:17 +0500 Subject: [PATCH 142/948] crypto/secp256k1: add checking z sign in affineFromJacobian (#18419) The z == 0 check is hit whenever we Add two points with the same x1/x2 coordinate. crypto/elliptic uses the same check in their affineFromJacobian function. This change does not affect block processing or tx signature verification in any way, because it does not use the Add or Double methods. --- crypto/secp256k1/curve.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index 5409ee1d2c..8f83cccad9 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -116,6 +116,10 @@ func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool { // affineFromJacobian reverses the Jacobian transform. See the comment at the // top of the file. func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) { + if z.Sign() == 0 { + return new(big.Int), new(big.Int) + } + zinv := new(big.Int).ModInverse(z, BitCurve.P) zinvsq := new(big.Int).Mul(zinv, zinv) From 6b9858085f8e10d50df3bf09d0a5dfd59b683d05 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 17 Nov 2020 12:01:19 +0100 Subject: [PATCH 143/948] cmd/geth: improve les test on windows (#21860) --- cmd/geth/les_test.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 259d4a8067..2425646510 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -2,7 +2,10 @@ package main import ( "context" + "os" "path/filepath" + "runtime" + "strings" "testing" "time" @@ -95,6 +98,27 @@ func (g *gethrpc) waitSynced() { } } +// ipcEndpoint resolves an IPC endpoint based on a configured value, taking into +// account the set data folders as well as the designated platform we're currently +// running on. +func ipcEndpoint(ipcPath, datadir string) string { + // On windows we can only use plain top-level pipes + if runtime.GOOS == "windows" { + if strings.HasPrefix(ipcPath, `\\.\pipe\`) { + return ipcPath + } + return `\\.\pipe\` + ipcPath + } + // Resolve names into the data directory full paths otherwise + if filepath.Base(ipcPath) == ipcPath { + if datadir == "" { + return filepath.Join(os.TempDir(), ipcPath) + } + return filepath.Join(datadir, ipcPath) + } + return ipcPath +} + func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { g := &gethrpc{name: name} args = append([]string{"--networkid=42", "--port=0", "--nousb"}, args...) @@ -103,10 +127,10 @@ func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { // wait before we can attach to it. TODO: probe for it properly time.Sleep(1 * time.Second) var err error - ipcpath := filepath.Join(g.geth.Datadir, "geth.ipc") + ipcpath := ipcEndpoint("geth.ipc", g.geth.Datadir) g.rpc, err = rpc.Dial(ipcpath) if err != nil { - t.Fatalf("%v rpc connect: %v", name, err) + t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) } return g } From 23524f8900bceb0c921b61cbf6f51f38325e2971 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 18 Nov 2020 17:51:33 +0800 Subject: [PATCH 144/948] all: disable recording preimage of trie keys (#21402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * cmd, core, eth, light, trie: disable recording preimage by default * core, eth: fix unit tests * core: fix import * all: change to nopreimage * cmd, core, eth, trie: use cache.preimages flag * cmd: enable preimages for archive node * cmd/utils, trie: simplify preimage tracking a bit * core: fix linter Co-authored-by: Péter Szilágyi --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 15 ++++++++ core/blockchain.go | 15 +++++--- core/genesis.go | 2 +- core/state/database.go | 10 ++--- core/state/state_test.go | 4 +- eth/api_test.go | 2 +- eth/api_tracer.go | 4 +- eth/backend.go | 1 + eth/config.go | 1 + eth/gen_config.go | 6 +++ light/postprocess.go | 4 +- trie/database.go | 81 +++++++++++++++++++++++++++------------- trie/secure_trie.go | 11 +++--- 15 files changed, 110 insertions(+), 48 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 38e48534dc..ca67859a91 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -113,6 +113,7 @@ var ( utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, utils.ListenPortFlag, utils.MaxPeersFlag, utils.MaxPendingPeersFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 237cb8d516..a9b6f53e72 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -114,6 +114,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e5ccfd7435..1b86771db8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -383,6 +383,10 @@ var ( Name: "cache.noprefetch", Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", } + CachePreimagesFlag = cli.BoolTFlag{ + Name: "cache.preimages", + Usage: "Enable recording the SHA3/keccak preimages of trie keys (default: true)", + } // Miner settings MiningEnabledFlag = cli.BoolFlag{ Name: "mine", @@ -1526,6 +1530,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(CacheNoPrefetchFlag.Name) { cfg.NoPrefetch = ctx.GlobalBool(CacheNoPrefetchFlag.Name) } + // Read the value from the flag no matter if it's set or not. + cfg.Preimages = ctx.GlobalBool(CachePreimagesFlag.Name) + if cfg.NoPruning && !cfg.Preimages { + cfg.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") + } if ctx.GlobalIsSet(TxLookupLimitFlag.Name) { cfg.TxLookupLimit = ctx.GlobalUint64(TxLookupLimitFlag.Name) } @@ -1835,6 +1845,11 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readOnly bool) (chain *core.B TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive", TrieTimeLimit: eth.DefaultConfig.TrieTimeout, SnapshotLimit: eth.DefaultConfig.SnapshotCache, + Preimages: ctx.GlobalBool(CachePreimagesFlag.Name), + } + if cache.TrieDirtyDisabled && !cache.Preimages { + cache.Preimages = true + log.Info("Enabling recording of key preimages since archive mode is used") } if !ctx.GlobalIsSet(SnapshotFlag.Name) { cache.SnapshotLimit = 0 // Disabled diff --git a/core/blockchain.go b/core/blockchain.go index 1c8a7fe60a..c52be68354 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -129,6 +129,7 @@ type CacheConfig struct { TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory + Preimages bool // Whether to store preimage of trie key to the disk SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } @@ -229,11 +230,15 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par badBlocks, _ := lru.New(badBlockLimit) bc := &BlockChain{ - chainConfig: chainConfig, - cacheConfig: cacheConfig, - db: db, - triegc: prque.New(nil), - stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit, cacheConfig.TrieCleanJournal), + chainConfig: chainConfig, + cacheConfig: cacheConfig, + db: db, + triegc: prque.New(nil), + stateCache: state.NewDatabaseWithConfig(db, &trie.Config{ + Cache: cacheConfig.TrieCleanLimit, + Journal: cacheConfig.TrieCleanJournal, + Preimages: cacheConfig.Preimages, + }), quit: make(chan struct{}), shouldPreserve: shouldPreserve, bodyCache: bodyCache, diff --git a/core/genesis.go b/core/genesis.go index 0535d7ee3a..908a969afd 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -175,7 +175,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig // We have the genesis block in database(perhaps in ancient database) // but the corresponding state is missing. header := rawdb.ReadHeader(db, stored, 0) - if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0, ""), nil); err != nil { + if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil), nil); err != nil { if genesis == nil { genesis = DefaultGenesisBlock() } diff --git a/core/state/database.go b/core/state/database.go index a9342f5179..83f7b2a839 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -104,18 +104,18 @@ type Trie interface { // NewDatabase creates a backing store for state. The returned database is safe for // concurrent use, but does not retain any recent trie nodes in memory. To keep some -// historical state in memory, use the NewDatabaseWithCache constructor. +// historical state in memory, use the NewDatabaseWithConfig constructor. func NewDatabase(db ethdb.Database) Database { - return NewDatabaseWithCache(db, 0, "") + return NewDatabaseWithConfig(db, nil) } -// NewDatabaseWithCache creates a backing store for state. The returned database +// NewDatabaseWithConfig creates a backing store for state. The returned database // is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a // large memory cache. -func NewDatabaseWithCache(db ethdb.Database, cache int, journal string) Database { +func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { csc, _ := lru.New(codeSizeCacheSize) return &cachingDB{ - db: trie.NewDatabaseWithCache(db, cache, journal), + db: trie.NewDatabaseWithConfig(db, config), codeSizeCache: csc, codeCache: fastcache.New(codeCacheSize), } diff --git a/core/state/state_test.go b/core/state/state_test.go index 0dc4c0ad63..526d7f8177 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -41,7 +41,9 @@ func newStateTest() *stateTest { } func TestDump(t *testing.T) { - s := newStateTest() + db := rawdb.NewMemoryDatabase() + sdb, _ := New(common.Hash{}, NewDatabaseWithConfig(db, nil), nil) + s := &stateTest{db: db, state: sdb} // generate a few entries obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01})) diff --git a/eth/api_test.go b/eth/api_test.go index 42f71e261e..2c9a2e54e8 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -58,7 +58,7 @@ func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j func TestAccountRange(t *testing.T) { var ( - statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) + statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil) state, _ = state.New(common.Hash{}, statedb, nil) addrs = [AccountRangeMaxResults * 2]common.Address{} m = map[common.Address]bool{} diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 1a8c405cf4..804d26b0b9 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -148,7 +148,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl // Ensure we have a valid starting state before doing any work origin := start.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16, "") // Chain tracing will probably start at genesis + database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) if number := start.NumberU64(); number > 0 { start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) @@ -659,7 +659,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* } // Otherwise try to reexec blocks until we find a state or reach our limit origin := block.NumberU64() - database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16, "") + database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) for i := uint64(0); i < reexec; i++ { block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) diff --git a/eth/backend.go b/eth/backend.go index 3fd027137c..01e6cadd1f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -169,6 +169,7 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) { TrieDirtyDisabled: config.NoPruning, TrieTimeLimit: config.TrieTimeout, SnapshotLimit: config.SnapshotCache, + Preimages: config.Preimages, } ) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) diff --git a/eth/config.go b/eth/config.go index 0d99c2a3f1..0d90376d94 100644 --- a/eth/config.go +++ b/eth/config.go @@ -149,6 +149,7 @@ type Config struct { TrieDirtyCache int TrieTimeout time.Duration SnapshotCache int + Preimages bool // Mining options Miner miner.Config diff --git a/eth/gen_config.go b/eth/gen_config.go index 0093439d14..b0674c7d77 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -43,6 +43,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TrieDirtyCache int TrieTimeout time.Duration SnapshotCache int + Preimages bool Miner miner.Config Ethash ethash.Config TxPool core.TxPoolConfig @@ -83,6 +84,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TrieDirtyCache = c.TrieDirtyCache enc.TrieTimeout = c.TrieTimeout enc.SnapshotCache = c.SnapshotCache + enc.Preimages = c.Preimages enc.Miner = c.Miner enc.Ethash = c.Ethash enc.TxPool = c.TxPool @@ -127,6 +129,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TrieDirtyCache *int TrieTimeout *time.Duration SnapshotCache *int + Preimages *bool Miner *miner.Config Ethash *ethash.Config TxPool *core.TxPoolConfig @@ -222,6 +225,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.SnapshotCache != nil { c.SnapshotCache = *dec.SnapshotCache } + if dec.Preimages != nil { + c.Preimages = *dec.Preimages + } if dec.Miner != nil { c.Miner = *dec.Miner } diff --git a/light/postprocess.go b/light/postprocess.go index de207ad4a3..891c8a5869 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -147,7 +147,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, dis diskdb: db, odr: odr, trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1, ""), // Use a tiny cache only to keep memory down + triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down trieset: mapset.NewSet(), sectionSize: size, disablePruning: disablePruning, @@ -340,7 +340,7 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin diskdb: db, odr: odr, trieTable: trieTable, - triedb: trie.NewDatabaseWithCache(trieTable, 1, ""), // Use a tiny cache only to keep memory down + triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down trieset: mapset.NewSet(), parentSize: parentSize, size: size, diff --git a/trie/database.go b/trie/database.go index c0c8870f8f..d8fe45f444 100644 --- a/trie/database.go +++ b/trie/database.go @@ -272,33 +272,43 @@ func expandNode(hash hashNode, n node) node { } } +// Config defines all necessary options for database. +type Config struct { + Cache int // Memory allowance (MB) to use for caching trie nodes in memory + Journal string // Journal of clean cache to survive node restarts + Preimages bool // Flag whether the preimage of trie key is recorded +} + // NewDatabase creates a new trie database to store ephemeral trie content before // its written out to disk or garbage collected. No read cache is created, so all // data retrievals will hit the underlying disk database. func NewDatabase(diskdb ethdb.KeyValueStore) *Database { - return NewDatabaseWithCache(diskdb, 0, "") + return NewDatabaseWithConfig(diskdb, nil) } -// NewDatabaseWithCache creates a new trie database to store ephemeral trie content +// NewDatabaseWithConfig creates a new trie database to store ephemeral trie content // before its written out to disk or garbage collected. It also acts as a read cache // for nodes loaded from disk. -func NewDatabaseWithCache(diskdb ethdb.KeyValueStore, cache int, journal string) *Database { +func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database { var cleans *fastcache.Cache - if cache > 0 { - if journal == "" { - cleans = fastcache.New(cache * 1024 * 1024) + if config != nil && config.Cache > 0 { + if config.Journal == "" { + cleans = fastcache.New(config.Cache * 1024 * 1024) } else { - cleans = fastcache.LoadFromFileOrNew(journal, cache*1024*1024) + cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024) } } - return &Database{ + db := &Database{ diskdb: diskdb, cleans: cleans, dirties: map[common.Hash]*cachedNode{{}: { children: make(map[common.Hash]uint16), }}, - preimages: make(map[common.Hash][]byte), } + if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future + db.preimages = make(map[common.Hash][]byte) + } + return db } // DiskDB retrieves the persistent storage backing the trie database. @@ -345,6 +355,11 @@ func (db *Database) insert(hash common.Hash, size int, node node) { // // Note, this method assumes that the database's lock is held! func (db *Database) insertPreimage(hash common.Hash, preimage []byte) { + // Short circuit if preimage collection is disabled + if db.preimages == nil { + return + } + // Track the preimage if a yet unknown one if _, ok := db.preimages[hash]; ok { return } @@ -431,6 +446,10 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { // preimage retrieves a cached trie node pre-image from memory. If it cannot be // found cached, the method queries the persistent database for the content. func (db *Database) preimage(hash common.Hash) []byte { + // Short circuit if preimage collection is disabled + if db.preimages == nil { + return nil + } // Retrieve the node from cache if available db.lock.RLock() preimage := db.preimages[hash] @@ -588,12 +607,16 @@ func (db *Database) Cap(limit common.StorageSize) error { // leave for later to deduplicate writes. flushPreimages := db.preimagesSize > 4*1024*1024 if flushPreimages { - rawdb.WritePreimages(batch, db.preimages) - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - return err + if db.preimages == nil { + log.Error("Attempted to write preimages whilst disabled") + } else { + rawdb.WritePreimages(batch, db.preimages) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() } - batch.Reset() } } // Keep committing nodes from the flush-list until we're below allowance @@ -630,7 +653,11 @@ func (db *Database) Cap(limit common.StorageSize) error { defer db.lock.Unlock() if flushPreimages { - db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + if db.preimages == nil { + log.Error("Attempted to reset preimage cache whilst disabled") + } else { + db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + } } for db.oldest != oldest { node := db.dirties[db.oldest] @@ -674,20 +701,21 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H batch := db.diskdb.NewBatch() // Move all of the accumulated preimages into a write batch - rawdb.WritePreimages(batch, db.preimages) - if batch.ValueSize() > ethdb.IdealBatchSize { + if db.preimages != nil { + rawdb.WritePreimages(batch, db.preimages) + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + } + // Since we're going to replay trie node writes into the clean cache, flush out + // any batched pre-images before continuing. if err := batch.Write(); err != nil { return err } batch.Reset() } - // Since we're going to replay trie node writes into the clean cache, flush out - // any batched pre-images before continuing. - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - // Move the trie itself into the batch, flushing if enough data is accumulated nodes, storage := len(db.dirties), db.dirtiesSize @@ -709,8 +737,9 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H batch.Reset() // Reset the storage counters and bumpd metrics - db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 - + if db.preimages != nil { + db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0 + } memcacheCommitTimeTimer.Update(time.Since(start)) memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize)) memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties))) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 87b364fb1b..e38471c1b7 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -147,12 +147,13 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte { func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { - t.trie.db.lock.Lock() - for hk, key := range t.secKeyCache { - t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key) + if t.trie.db.preimages != nil { // Ugly direct check but avoids the below write lock + t.trie.db.lock.Lock() + for hk, key := range t.secKeyCache { + t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key) + } + t.trie.db.lock.Unlock() } - t.trie.db.lock.Unlock() - t.secKeyCache = make(map[string][]byte) } // Commit the trie to its intermediate node database From b9ff57c59e3705eb963d39001192ab3a0ecd2d1e Mon Sep 17 00:00:00 2001 From: gary rong Date: Thu, 19 Nov 2020 04:50:11 +0800 Subject: [PATCH 145/948] metrics: fix the panic for reading empty cpu stats (#21864) --- metrics/cpu_enabled.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/metrics/cpu_enabled.go b/metrics/cpu_enabled.go index 52a3c2e966..02192928b7 100644 --- a/metrics/cpu_enabled.go +++ b/metrics/cpu_enabled.go @@ -31,6 +31,10 @@ func ReadCPUStats(stats *CPUStats) { log.Error("Could not read cpu stats", "err", err) return } + if len(timeStats) == 0 { + log.Error("Empty cpu stats") + return + } // requesting all cpu times will always return an array with only one time stats entry timeStat := timeStats[0] stats.GlobalTime = int64((timeStat.User + timeStat.Nice + timeStat.System) * cpu.ClocksPerSec) From f1e1d9f874eca8430ccf2fbfd1b899592a2fcdac Mon Sep 17 00:00:00 2001 From: wbt Date: Thu, 19 Nov 2020 08:54:49 -0500 Subject: [PATCH 146/948] node: support expressive origin rules in ws.origins (#21481) * Only compare hostnames in ws.origins Also using a helper function for ToLower consolidates all preparation steps in one function for more maintainable consistency. Spaces => tabs Remove a semicolon Add space at start of comment Remove parens around conditional Handle case wehre parsed hostname is empty When passing a single word like "localhost" the parsed hostname is an empty string. Handle this and the error-parsing case together as default, and the nonempty hostname case in the conditional. Refactor with new originIsAllowed functions Adds originIsAllowed() & ruleAllowsOrigin(); removes prepOriginForComparison Remove blank line Added tests for simple allowed-orign rule which does not specify a protocol or port, just a hostname Fix copy-paste: `:=` => `=` Remove parens around conditional Remove autoadded whitespace on blank lines Compare scheme, hostname, and port with rule if the rule specifies those portions. Remove one autoadded trailing whitespace Better handle case where only origin host is given e.g. "localhost" Remove parens around conditional Refactor: attemptWebsocketConnectionFromOrigin DRY Include return type on helper function Provide srv obj in helper fn Provide srv to helper fn Remove stray underscore Remove blank line parent 93e666b4c1e7e49b8406dc83ed93f4a02ea49ac1 author wbt 1598559718 -0400 committer Martin Holst Swende 1605602257 +0100 gpgsig -----BEGIN PGP SIGNATURE----- iQFFBAABCAAvFiEEypmrtbNuJK1doP1AaDtDjAWl3fAFAl+zi9ARHG1hcnRpbkBz d2VuZGUuc2UACgkQaDtDjAWl3fDRiwgAoMtzU8dwRV7Q9xkCwWEx9Wz2f3n6jUr2 VWBycDKGKwRkPPOER3oc9kzjGU/P1tFlK07PjfnAKZ9KWzxpDcJZwYM3xCBurG7A 16y4YsQnzgPNONv3xIkdi3RZtDBIiPFFEmdZFFvZ/jKexfI6JIYPngCAoqdTIFb9 On/aPvvVWQn1ExfmarsvvJ7kUDUG77tZipuacEH5FfFsfelBWOEYPe+I9ToUHskv +qO6rOkV1Ojk8eBc6o0R1PnApwCAlEhJs7aM/SEOg4B4ZJJneiFuEXBIG9+0yS2I NOicuDPLGucOB5nBsfIKI3USPeE+3jxdT8go2lN5Nrhm6MimoILDsQ== =sgUp -----END PGP SIGNATURE----- Refactor: drop err var for more concise test lines Add several tests for new WebSocket origin checks Remove autoadded whitespace on blank lines Restore TestWebsocketOrigins originally-named test and rename the others to be helpers rather than full tests Remove autoadded whitespace on blank line Temporarily comment out new test sets Uncomment test around origin rule with scheme Remove tests without scheme on browser origin per https://github.com/ethereum/go-ethereum/pull/21481/files#r479371498 Uncomment tests with port; remove some blank lines Handle when browser does not specify scheme/port Uncomment test for including scheme & port in rule Add IP tests * node: more tests + table-driven, ws origin changes Co-authored-by: Martin Holst Swende --- node/rpcstack_test.go | 125 ++++++++++++++++++++++++++++++++++++------ rpc/websocket.go | 65 +++++++++++++++++++++- 2 files changed, 170 insertions(+), 20 deletions(-) diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 0ee120efd7..8267fb2f1d 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -19,6 +19,7 @@ package node import ( "bytes" "net/http" + "strings" "testing" "github.com/ethereum/go-ethereum/internal/testlog" @@ -52,25 +53,104 @@ func TestVhosts(t *testing.T) { assert.Equal(t, resp2.StatusCode, http.StatusForbidden) } -// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. -func TestWebsocketOrigins(t *testing.T) { - srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: []string{"test"}}) - defer srv.stop() +type originTest struct { + spec string + expOk []string + expFail []string +} - dialer := websocket.DefaultDialer - _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ - "Content-type": []string{"application/json"}, - "Sec-WebSocket-Version": []string{"13"}, - "Origin": []string{"test"}, - }) - assert.NoError(t, err) +// splitAndTrim splits input separated by a comma +// and trims excessive white space from the substrings. +// Copied over from flags.go +func splitAndTrim(input string) (ret []string) { + l := strings.Split(input, ",") + for _, r := range l { + r = strings.TrimSpace(r) + if len(r) > 0 { + ret = append(ret, r) + } + } + return ret +} - _, _, err = dialer.Dial("ws://"+srv.listenAddr(), http.Header{ - "Content-type": []string{"application/json"}, - "Sec-WebSocket-Version": []string{"13"}, - "Origin": []string{"bad"}, - }) - assert.Error(t, err) +// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. +func TestWebsocketOrigins(t *testing.T) { + tests := []originTest{ + { + spec: "*", // allow all + expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540", + "http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, + }, + { + spec: "test", + expOk: []string{"http://test", "https://test", "http://test:8540", "https://test:8540"}, + expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, + }, + // scheme tests + { + spec: "https://test", + expOk: []string{"https://test", "https://test:9999"}, + expFail: []string{ + "test", // no scheme, required by spec + "http://test", // wrong scheme + "http://test.foo", "https://a.test.x", // subdomain variatoins + "http://testx:8540", "https://xtest:8540"}, + }, + // ip tests + { + spec: "https://12.34.56.78", + expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"}, + expFail: []string{ + "http://12.34.56.78", // wrong scheme + "http://12.34.56.78:443", // wrong scheme + "http://1.12.34.56.78", // wrong 'domain name' + "http://12.34.56.78.a", // wrong 'domain name' + "https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"}, + }, + // port tests + { + spec: "test:8540", + expOk: []string{"http://test:8540", "https://test:8540"}, + expFail: []string{ + "http://test", "https://test", // spec says port required + "http://test:8541", "https://test:8541", // wrong port + "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, + }, + // scheme and port + { + spec: "https://test:8540", + expOk: []string{"https://test:8540"}, + expFail: []string{ + "https://test", // missing port + "http://test", // missing port, + wrong scheme + "http://test:8540", // wrong scheme + "http://test:8541", "https://test:8541", // wrong port + "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, + }, + // several allowed origins + { + spec: "localhost,http://127.0.0.1", + expOk: []string{"localhost", "http://localhost", "https://localhost:8443", + "http://127.0.0.1", "http://127.0.0.1:8080"}, + expFail: []string{ + "https://127.0.0.1", // wrong scheme + "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, + }, + } + for _, tc := range tests { + srv := createAndStartServer(t, httpConfig{}, true, wsConfig{Origins: splitAndTrim(tc.spec)}) + for _, origin := range tc.expOk { + if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err != nil { + t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) + } + } + for _, origin := range tc.expFail { + if err := attemptWebsocketConnectionFromOrigin(t, srv, origin); err == nil { + t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) + } + } + srv.stop() + } } // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly. @@ -103,6 +183,17 @@ func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfi return srv } +func attemptWebsocketConnectionFromOrigin(t *testing.T, srv *httpServer, browserOrigin string) error { + t.Helper() + dialer := websocket.DefaultDialer + _, _, err := dialer.Dial("ws://"+srv.listenAddr(), http.Header{ + "Content-type": []string{"application/json"}, + "Sec-WebSocket-Version": []string{"13"}, + "Origin": []string{browserOrigin}, + }) + return err +} + func testRequest(t *testing.T, key, value, host string, srv *httpServer) *http.Response { t.Helper() diff --git a/rpc/websocket.go b/rpc/websocket.go index a716383be9..cd60eeb613 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -75,14 +75,14 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { allowAllOrigins = true } if origin != "" { - origins.Add(strings.ToLower(origin)) + origins.Add(origin) } } // allow localhost if no allowedOrigins are specified. if len(origins.ToSlice()) == 0 { origins.Add("http://localhost") if hostname, err := os.Hostname(); err == nil { - origins.Add("http://" + strings.ToLower(hostname)) + origins.Add("http://" + hostname) } } log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice())) @@ -97,7 +97,7 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { } // Verify origin against whitelist. origin := strings.ToLower(req.Header.Get("Origin")) - if allowAllOrigins || origins.Contains(origin) { + if allowAllOrigins || originIsAllowed(origins, origin) { return true } log.Warn("Rejected WebSocket connection", "origin", origin) @@ -120,6 +120,65 @@ func (e wsHandshakeError) Error() string { return s } +func originIsAllowed(allowedOrigins mapset.Set, browserOrigin string) bool { + it := allowedOrigins.Iterator() + for origin := range it.C { + if ruleAllowsOrigin(origin.(string), browserOrigin) { + return true + } + } + return false +} + +func ruleAllowsOrigin(allowedOrigin string, browserOrigin string) bool { + var ( + allowedScheme, allowedHostname, allowedPort string + browserScheme, browserHostname, browserPort string + err error + ) + allowedScheme, allowedHostname, allowedPort, err = parseOriginURL(allowedOrigin) + if err != nil { + log.Warn("Error parsing allowed origin specification", "spec", allowedOrigin, "error", err) + return false + } + browserScheme, browserHostname, browserPort, err = parseOriginURL(browserOrigin) + if err != nil { + log.Warn("Error parsing browser 'Origin' field", "Origin", browserOrigin, "error", err) + return false + } + if allowedScheme != "" && allowedScheme != browserScheme { + return false + } + if allowedHostname != "" && allowedHostname != browserHostname { + return false + } + if allowedPort != "" && allowedPort != browserPort { + return false + } + return true +} + +func parseOriginURL(origin string) (string, string, string, error) { + parsedURL, err := url.Parse(strings.ToLower(origin)) + if err != nil { + return "", "", "", err + } + var scheme, hostname, port string + if strings.Contains(origin, "://") { + scheme = parsedURL.Scheme + hostname = parsedURL.Hostname() + port = parsedURL.Port() + } else { + scheme = "" + hostname = parsedURL.Scheme + port = parsedURL.Opaque + if hostname == "" { + hostname = origin + } + } + return scheme, hostname, port, nil +} + // DialWebsocketWithDialer creates a new RPC client that communicates with a JSON-RPC server // that is listening on the given endpoint using the provided dialer. func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, dialer websocket.Dialer) (*Client, error) { From 6f88d6530a819a3a65c4b95681e6c52115365622 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 19 Nov 2020 22:50:47 +0100 Subject: [PATCH 147/948] trie, rpc, cmd/geth: fix tests on 32-bit and windows + minor rpc fixes (#21871) * trie: fix tests to work on 32-bit systems * les: make test work on 32-bit platform * cmd/geth: fix windows-issues on tests * trie: improve balance * cmd/geth: make account tests less verbose + less mem intense * rpc: make debug-level log output less verbose * cmd/geth: lint --- cmd/geth/accountcmd_test.go | 29 +++++++++--------- cmd/geth/les_test.go | 35 +++++++++++++++------- cmd/geth/run_test.go | 8 ++--- internal/cmdtest/test_cmd.go | 15 ++++++---- node/rpcstack.go | 1 + rpc/endpoints.go | 14 +++++++-- trie/trie_test.go | 57 ++++++++++++++++++++---------------- 7 files changed, 98 insertions(+), 61 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 6213e5195d..2f15915b08 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -180,8 +180,8 @@ Fatal: could not decrypt key with given password func TestUnlockFlag(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "256", "--ipcdisable", + "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -204,8 +204,8 @@ Password: {{.InputLine "foobar"}} func TestUnlockFlagWrongPassword(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -223,9 +223,8 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could func TestUnlockFlagMultiIndex(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "0,2", - "js", "testdata/empty.js") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--unlock", "0,2", "js", "testdata/empty.js") geth.Expect(` Unlocking account 0 | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -250,8 +249,8 @@ Password: {{.InputLine "foobar"}} func TestUnlockFlagPasswordFile(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/passwords.txt", "--unlock", "0,2", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--password", "testdata/passwords.txt", "--unlock", "0,2", "js", "testdata/empty.js") geth.ExpectExit() @@ -270,8 +269,8 @@ func TestUnlockFlagPasswordFile(t *testing.T) { func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--datadir", datadir, "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") defer geth.ExpectExit() geth.Expect(` Fatal: Failed to unlock account 0 (could not decrypt key with given password) @@ -281,8 +280,8 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) func TestUnlockFlagAmbiguous(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") defer geth.ExpectExit() @@ -319,8 +318,8 @@ In order to avoid this warning, you need to remove the following duplicate key f func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", "--nousb", "--cache", "128", "--ipcdisable", + "--keystore", store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") defer geth.ExpectExit() // Helper for the expect template, returns absolute keystore path. diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 2425646510..e4fc2d4d01 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -2,10 +2,12 @@ package main import ( "context" + "fmt" "os" "path/filepath" "runtime" "strings" + "sync/atomic" "testing" "time" @@ -119,24 +121,36 @@ func ipcEndpoint(ipcPath, datadir string) string { return ipcPath } +// nextIPC ensures that each ipc pipe gets a unique name. +// On linux, it works well to use ipc pipes all over the filesystem (in datadirs), +// but windows require pipes to sit in "\\.\pipe\". Therefore, to run several +// nodes simultaneously, we need to distinguish between them, which we do by +// the pipe filename instead of folder. +var nextIPC = uint32(0) + func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { - g := &gethrpc{name: name} - args = append([]string{"--networkid=42", "--port=0", "--nousb"}, args...) + ipcName := fmt.Sprintf("geth-%d.ipc", atomic.AddUint32(&nextIPC, 1)) + args = append([]string{"--networkid=42", "--port=0", "--nousb", "--ipcpath", ipcName}, args...) t.Logf("Starting %v with rpc: %v", name, args) - g.geth = runGeth(t, args...) + + g := &gethrpc{ + name: name, + geth: runGeth(t, args...), + } // wait before we can attach to it. TODO: probe for it properly time.Sleep(1 * time.Second) var err error - ipcpath := ipcEndpoint("geth.ipc", g.geth.Datadir) - g.rpc, err = rpc.Dial(ipcpath) - if err != nil { + ipcpath := ipcEndpoint(ipcName, g.geth.Datadir) + if g.rpc, err = rpc.Dial(ipcpath); err != nil { t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) } return g } func initGeth(t *testing.T) string { - g := runGeth(t, "--nousb", "--networkid=42", "init", "./testdata/clique.json") + args := []string{"--nousb", "--networkid=42", "init", "./testdata/clique.json"} + t.Logf("Initializing geth: %v ", args) + g := runGeth(t, args...) datadir := g.Datadir g.WaitExit() return datadir @@ -144,15 +158,16 @@ func initGeth(t *testing.T) string { func startLightServer(t *testing.T) *gethrpc { datadir := initGeth(t) + t.Logf("Importing keys to geth") runGeth(t, "--nousb", "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" - server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1") + server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") return server } func startClient(t *testing.T, name string) *gethrpc { datadir := initGeth(t) - return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1") + return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4") } func TestPriorityClient(t *testing.T) { @@ -175,7 +190,7 @@ func TestPriorityClient(t *testing.T) { prioCli := startClient(t, "prioCli") defer prioCli.killAndWait() // 3_000_000_000 once we move to Go 1.13 - tokens := 3000000000 + tokens := uint64(3000000000) lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens) prioCli.addPeer(lightServer) diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index f7b735b84c..79b892c59b 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -70,12 +70,12 @@ func runGeth(t *testing.T, args ...string) *testgeth { tt := &testgeth{} tt.TestCmd = cmdtest.NewTestCmd(t, tt) for i, arg := range args { - switch { - case arg == "-datadir" || arg == "--datadir": + switch arg { + case "--datadir": if i < len(args)-1 { tt.Datadir = args[i+1] } - case arg == "-etherbase" || arg == "--etherbase": + case "--etherbase": if i < len(args)-1 { tt.Etherbase = args[i+1] } @@ -84,7 +84,7 @@ func runGeth(t *testing.T, args ...string) *testgeth { if tt.Datadir == "" { tt.Datadir = tmpdir(t) tt.Cleanup = func() { os.RemoveAll(tt.Datadir) } - args = append([]string{"-datadir", tt.Datadir}, args...) + args = append([]string{"--datadir", tt.Datadir}, args...) // Remove the temporary datadir if something fails below. defer func() { if t.Failed() { diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go index 0edfccec5a..82ad9c15b6 100644 --- a/internal/cmdtest/test_cmd.go +++ b/internal/cmdtest/test_cmd.go @@ -27,6 +27,7 @@ import ( "regexp" "strings" "sync" + "sync/atomic" "syscall" "testing" "text/template" @@ -55,10 +56,13 @@ type TestCmd struct { Err error } +var id int32 + // Run exec's the current binary using name as argv[0] which will trigger the // reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go) func (tt *TestCmd) Run(name string, args ...string) { - tt.stderr = &testlogger{t: tt.T} + id := atomic.AddInt32(&id, 1) + tt.stderr = &testlogger{t: tt.T, name: fmt.Sprintf("%d", id)} tt.cmd = &exec.Cmd{ Path: reexec.Self(), Args: append([]string{name}, args...), @@ -238,16 +242,17 @@ func (tt *TestCmd) withKillTimeout(fn func()) { // testlogger logs all written lines via t.Log and also // collects them for later inspection. type testlogger struct { - t *testing.T - mu sync.Mutex - buf bytes.Buffer + t *testing.T + mu sync.Mutex + buf bytes.Buffer + name string } func (tl *testlogger) Write(b []byte) (n int, err error) { lines := bytes.Split(b, []byte("\n")) for _, line := range lines { if len(line) > 0 { - tl.t.Logf("(stderr) %s", line) + tl.t.Logf("(stderr:%v) %s", tl.name, line) } } tl.mu.Lock() diff --git a/node/rpcstack.go b/node/rpcstack.go index 731e807aca..81e054ec99 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -448,6 +448,7 @@ func (is *ipcServer) start(apis []rpc.API) error { } listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis) if err != nil { + is.log.Warn("IPC opening failed", "url", is.endpoint, "error", err) return err } is.log.Info("IPC endpoint opened", "url", is.endpoint) diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 9fc0705172..d78ebe2858 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -18,6 +18,7 @@ package rpc import ( "net" + "strings" "github.com/ethereum/go-ethereum/log" ) @@ -25,13 +26,22 @@ import ( // StartIPCEndpoint starts an IPC endpoint. func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) { // Register all the APIs exposed by the services. - handler := NewServer() + var ( + handler = NewServer() + regMap = make(map[string]struct{}) + registered []string + ) for _, api := range apis { if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + log.Info("IPC registration failed", "namespace", api.Namespace, "error", err) return nil, nil, err } - log.Debug("IPC registered", "namespace", api.Namespace) + if _, ok := regMap[api.Namespace]; !ok { + registered = append(registered, api.Namespace) + regMap[api.Namespace] = struct{}{} + } } + log.Debug("IPCs registered", "namespaces", strings.Join(registered, ",")) // All APIs registered, start the IPC listener. listener, err := ipcListen(ipcEndpoint) if err != nil { diff --git a/trie/trie_test.go b/trie/trie_test.go index 682dec157c..ddbdcbbd5b 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -594,21 +594,20 @@ func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) { func TestTinyTrie(t *testing.T) { // Create a realistic account trie to hash - _, accounts := makeAccounts(10000) + _, accounts := makeAccounts(5) trie := newEmpty() trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3]) - if exp, root := common.HexToHash("4fa6efd292cffa2db0083b8bedd23add2798ae73802442f52486e95c3df7111c"), trie.Hash(); exp != root { - t.Fatalf("1: got %x, exp %x", root, exp) + if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root { + t.Errorf("1: got %x, exp %x", root, exp) } trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4]) - if exp, root := common.HexToHash("cb5fb1213826dad9e604f095f8ceb5258fe6b5c01805ce6ef019a50699d2d479"), trie.Hash(); exp != root { - t.Fatalf("2: got %x, exp %x", root, exp) + if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root { + t.Errorf("2: got %x, exp %x", root, exp) } trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4]) - if exp, root := common.HexToHash("ed7e06b4010057d8703e7b9a160a6d42cf4021f9020da3c8891030349a646987"), trie.Hash(); exp != root { - t.Fatalf("3: got %x, exp %x", root, exp) + if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { + t.Errorf("3: got %x, exp %x", root, exp) } - checktr, _ := New(common.Hash{}, trie.db) it := NewIterator(trie.NodeIterator(nil)) for it.Next() { @@ -630,7 +629,7 @@ func TestCommitAfterHash(t *testing.T) { trie.Hash() trie.Commit(nil) root := trie.Hash() - exp := common.HexToHash("e5e9c29bb50446a4081e6d1d748d2892c6101c1e883a1f77cf21d4094b697822") + exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6") if exp != root { t.Errorf("got %x, exp %x", root, exp) } @@ -646,19 +645,27 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) { // Create a realistic account trie to hash addresses = make([][20]byte, size) for i := 0; i < len(addresses); i++ { - for j := 0; j < len(addresses[i]); j++ { - addresses[i][j] = byte(random.Intn(256)) - } + data := make([]byte, 20) + random.Read(data) + copy(addresses[i][:], data) } accounts = make([][]byte, len(addresses)) for i := 0; i < len(accounts); i++ { var ( - nonce = uint64(random.Int63()) - balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) - root = emptyRoot - code = crypto.Keccak256(nil) + nonce = uint64(random.Int63()) + root = emptyRoot + code = crypto.Keccak256(nil) ) - accounts[i], _ = rlp.EncodeToBytes(&account{nonce, balance, root, code}) + // The big.Rand function is not deterministic with regards to 64 vs 32 bit systems, + // and will consume different amount of data from the rand source. + //balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) + // Therefore, we instead just read via byte buffer + numBytes := random.Uint32() % 33 // [0, 32] bytes + balanceBytes := make([]byte, numBytes) + random.Read(balanceBytes) + balance := new(big.Int).SetBytes(balanceBytes) + data, _ := rlp.EncodeToBytes(&account{nonce, balance, root, code}) + accounts[i] = data } return addresses, accounts } @@ -714,12 +721,12 @@ func TestCommitSequence(t *testing.T) { expWriteSeqHash []byte expCallbackSeqHash []byte }{ - {20, common.FromHex("68c495e45209e243eb7e4f4e8ca8f9f7be71003bd9cafb8061b4534373740193"), - common.FromHex("01783213033d6b7781a641ab499e680d959336d025ac16f44d02f4f0c021bbf5")}, - {200, common.FromHex("3b20d16c13c4bc3eb3b8d0ad7a169fef3b1600e056c0665895d03d3d2b2ff236"), - common.FromHex("fb8db0ec82e8f02729f11228940885b181c3047ab0d654ed0110291ca57111a8")}, - {2000, common.FromHex("34eff3d1048bebdf77e9ae8bd939f2e7c742edc3dcd1173cff1aad9dbd20451a"), - common.FromHex("1c981604b1a9f8ffa40e0ae66b14830a87f5a4ed8345146a3912e6b2dcb05e63")}, + {20, common.FromHex("873c78df73d60e59d4a2bcf3716e8bfe14554549fea2fc147cb54129382a8066"), + common.FromHex("ff00f91ac05df53b82d7f178d77ada54fd0dca64526f537034a5dbe41b17df2a")}, + {200, common.FromHex("ba03d891bb15408c940eea5ee3d54d419595102648d02774a0268d892add9c8e"), + common.FromHex("f3cd509064c8d319bbdd1c68f511850a902ad275e6ed5bea11547e23d492a926")}, + {2000, common.FromHex("f7a184f20df01c94f09537401d11e68d97ad0c00115233107f51b9c287ce60c7"), + common.FromHex("ff795ea898ba1e4cfed4a33b4cf5535a347a02cf931f88d88719faf810f9a1c9")}, } { addresses, accounts := makeAccounts(tc.count) // This spongeDb is used to check the sequence of disk-db-writes @@ -740,10 +747,10 @@ func TestCommitSequence(t *testing.T) { callbackSponge.Write(c[:]) }) if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { - t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) + t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp) } if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) { - t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) + t.Errorf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp) } } } From ebb9591c4d860e85fe1a015e6c9d7e85b7500b9a Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 20 Nov 2020 08:53:10 +0100 Subject: [PATCH 148/948] crypto/bn256: fix bn256Mul fuzzer to not hang on large input (#21872) * crypto/bn256: fix bn256Mul fuzzer to not hang on large input * Update crypto/bn256/bn256_fuzz.go Co-authored-by: ligi Co-authored-by: ligi --- crypto/bn256/bn256_fuzz.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/bn256/bn256_fuzz.go b/crypto/bn256/bn256_fuzz.go index 585d509bf4..b34043487f 100644 --- a/crypto/bn256/bn256_fuzz.go +++ b/crypto/bn256/bn256_fuzz.go @@ -80,6 +80,12 @@ func FuzzMul(data []byte) int { if remaining == 0 { return 0 } + if remaining > 128 { + // The evm only ever uses 32 byte integers, we need to cap this otherwise + // we run into slow exec. A 236Kb byte integer cause oss-fuzz to report it as slow. + // 128 bytes should be fine though + return 0 + } buf := make([]byte, remaining) input.Read(buf) From 3ef52775c4756b7cbdc28b807eb4600127a9a873 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 20 Nov 2020 15:14:25 +0100 Subject: [PATCH 149/948] p2p: avoid spinning loop on out-of-handles (#21878) * p2p: avoid busy-loop on temporary errors * p2p: address review concerns --- p2p/server.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/p2p/server.go b/p2p/server.go index dd52297f8a..275cb5ea5c 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -854,13 +854,18 @@ func (srv *Server) listenLoop() { <-slots var ( - fd net.Conn - err error + fd net.Conn + err error + lastLog time.Time ) for { fd, err = srv.listener.Accept() if netutil.IsTemporaryError(err) { - srv.log.Debug("Temporary read error", "err", err) + if time.Since(lastLog) > 1*time.Second { + srv.log.Debug("Temporary read error", "err", err) + lastLog = time.Now() + } + time.Sleep(time.Millisecond * 200) continue } else if err != nil { srv.log.Debug("Read error", "err", err) From bddf5aaa2f737569e5f96de3676c5123b82e60f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 23 Nov 2020 10:18:33 +0100 Subject: [PATCH 150/948] les/utils: protect against WeightedRandomSelect overflow (#21839) Also fixes a bug in les/flowcontrol that caused the overflow. --- les/flowcontrol/control.go | 4 ++++ les/utils/weighted_select.go | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go index 490013677c..4f0de82318 100644 --- a/les/flowcontrol/control.go +++ b/les/flowcontrol/control.go @@ -19,6 +19,7 @@ package flowcontrol import ( "fmt" + "math" "sync" "time" @@ -316,6 +317,9 @@ func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) { node.lock.RLock() defer node.lock.RUnlock() + if node.params.BufLimit == 0 { + return time.Duration(math.MaxInt64), 0 + } now := node.clock.Now() node.recalcBLE(now) maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst) diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go index d6db3c0e65..9b4413afb5 100644 --- a/les/utils/weighted_select.go +++ b/les/utils/weighted_select.go @@ -17,7 +17,10 @@ package utils import ( + "math" "math/rand" + + "github.com/ethereum/go-ethereum/log" ) type ( @@ -54,6 +57,14 @@ func (w *WeightedRandomSelect) IsEmpty() bool { // setWeight sets an item's weight to a specific value (removes it if zero) func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { + if weight > math.MaxInt64-w.root.sumWeight { + // old weight is still included in sumWeight, remove and check again + w.setWeight(item, 0) + if weight > math.MaxInt64-w.root.sumWeight { + log.Error("WeightedRandomSelect overflow", "sumWeight", w.root.sumWeight, "new weight", weight) + weight = math.MaxInt64 - w.root.sumWeight + } + } idx, ok := w.idx[item] if ok { w.root.setWeight(idx, weight) From f6e1aed504d70197884da3031ae003755ef8e4d5 Mon Sep 17 00:00:00 2001 From: ligi Date: Mon, 23 Nov 2020 13:12:42 +0100 Subject: [PATCH 151/948] github: Add new style of issue-templates closes #20024 --- .../bug.md} | 12 +++++++----- .github/ISSUE_TEMPLATE/feature.md | 17 +++++++++++++++++ .github/ISSUE_TEMPLATE/question.md | 9 +++++++++ .github/ISSUE_TEMPLATE/vulnerability.md | 13 +++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/bug.md} (50%) create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/ISSUE_TEMPLATE/vulnerability.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug.md similarity index 50% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug.md index 59285e456d..c5a3654bde 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,8 +1,10 @@ -Hi there, - -Please note that this is an issue tracker reserved for bug reports and feature requests. - -For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. +--- +name: Report a bug +about: Something with go-ethereum is not working as expected +title: '' +labels: 'type:bug' +assignees: '' +--- #### System information diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000000..aacd885f9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,17 @@ +--- +name: Request a feature +about: Report a missing feature - e.g. as a step before submitting a PR +title: '' +labels: 'type:feature' +assignees: '' +--- + +# Rationale + +Why should this feature exist? +What are the use-cases? + +# Implementation + +Do you have ideas regarding the implementation of this feature? +Are you willing to implement this feature? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..8f460ab558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Ask a question +about: Something is unclear +title: '' +labels: 'type:docs' +assignees: '' +--- + +This should only be used in very rare cases e.g. if you are not 100% sure if something is a bug or asking a question that leads to improving the documentation. For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. diff --git a/.github/ISSUE_TEMPLATE/vulnerability.md b/.github/ISSUE_TEMPLATE/vulnerability.md new file mode 100644 index 0000000000..f6bfbe59c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/vulnerability.md @@ -0,0 +1,13 @@ +--- +name: Report a vulnerability +about: There is a bug in go-ethereum that can be exploited +title: '' +labels: 'type:security' +assignees: '' +--- + +Please do not submit these in this public issue tracker! + +To find out how to disclose a vulnerability in Ethereum visit https://bounty.ethereum.org or email bounty@ethereum.org. + +Please read [Reporting a vulnerability](https://github.com/ethereum/go-ethereum/security/policy#reporting-a-vulnerability) for more information. From 6104ab6b6d201df9e9d6372d1c35835d10c8f6ff Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 23 Nov 2020 15:49:16 +0100 Subject: [PATCH 152/948] tests/fuzzers/bls1381: add bls fuzzer (#21796) * added bls fuzzer * crypto/bls12381: revert bls-changes, fixup fuzzer tests * fuzzers: split bls fuzzing into 8 different units * fuzzers/bls: remove (now stale) corpus * crypto/bls12381: added blsfuzz corpus * fuzzers/bls12381: fix the bls corpus * fuzzers: fix oss-fuzz script * tests/fuzzers: fixups on bls corpus * test/fuzzers: remove leftover corpus Co-authored-by: Marius van der Wijden --- crypto/bls12381/fp_test.go | 9 ++ oss-fuzz.sh | 19 +++- tests/fuzzers/bls12381/bls_fuzzer.go | 101 ++++++++++++++++++ .../testdata/fuzz_g1_add_seed_corpus.zip | Bin 0 -> 41620 bytes .../testdata/fuzz_g1_mul_seed_corpus.zip | Bin 0 -> 37687 bytes .../testdata/fuzz_g1_multiexp_seed_corpus.zip | Bin 0 -> 521055 bytes .../testdata/fuzz_g2_add_seed_corpus.zip | Bin 0 -> 65856 bytes .../testdata/fuzz_g2_mul_seed_corpus.zip | Bin 0 -> 48203 bytes .../testdata/fuzz_g2_multiexp_seed_corpus.zip | Bin 0 -> 1377268 bytes .../testdata/fuzz_map_g1_seed_corpus.zip | Bin 0 -> 28135 bytes .../testdata/fuzz_map_g2_seed_corpus.zip | Bin 0 -> 31393 bytes .../testdata/fuzz_pairing_seed_corpus.zip | Bin 0 -> 55784 bytes 12 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tests/fuzzers/bls12381/bls_fuzzer.go create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g1_multiexp_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_mul_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip create mode 100644 tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip diff --git a/crypto/bls12381/fp_test.go b/crypto/bls12381/fp_test.go index 14bb4d7d65..97528d9db3 100644 --- a/crypto/bls12381/fp_test.go +++ b/crypto/bls12381/fp_test.go @@ -1393,6 +1393,15 @@ func BenchmarkMultiplication(t *testing.B) { } } +func BenchmarkInverse(t *testing.B) { + a, _ := new(fe).rand(rand.Reader) + b, _ := new(fe).rand(rand.Reader) + t.ResetTimer() + for i := 0; i < t.N; i++ { + inverse(a, b) + } +} + func padBytes(in []byte, size int) []byte { out := make([]byte, size) if len(in) > size { diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 23fb4dd412..e0a293a6d6 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -30,13 +30,20 @@ function compile_fuzzer { path=$SRC/go-ethereum/$1 func=$2 fuzzer=$3 - echo "Building $fuzzer" + corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" + echo "Building $fuzzer (expecting corpus at $corpusfile)" (cd $path && \ go-fuzz -func $func -o $WORK/$fuzzer.a . && \ echo "First stage built OK" && \ $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer && \ echo "Second stage built ok" ) + ## Check if there exists a seed corpus file + if [ -f $corpusfile ] + then + cp $corpusfile $OUT/ + echo "Found seed corpus: $corpusfile" + fi } compile_fuzzer common/bitutil Fuzz fuzzBitutilCompress @@ -51,6 +58,16 @@ compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie +compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add +compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul +compile_fuzzer tests/fuzzers/bls12381 FuzzG1MultiExp fuzz_g1_multiexp +compile_fuzzer tests/fuzzers/bls12381 FuzzG2Add fuzz_g2_add +compile_fuzzer tests/fuzzers/bls12381 FuzzG2Mul fuzz_g2_mul +compile_fuzzer tests/fuzzers/bls12381 FuzzG2MultiExp fuzz_g2_multiexp +compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing +compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1 +compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 + # This doesn't work very well @TODO #compile_fuzzertests/fuzzers/abi Fuzz fuzzAbi diff --git a/tests/fuzzers/bls12381/bls_fuzzer.go b/tests/fuzzers/bls12381/bls_fuzzer.go new file mode 100644 index 0000000000..7e3f94c2aa --- /dev/null +++ b/tests/fuzzers/bls12381/bls_fuzzer.go @@ -0,0 +1,101 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package bls + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +const ( + blsG1Add = byte(10) + blsG1Mul = byte(11) + blsG1MultiExp = byte(12) + blsG2Add = byte(13) + blsG2Mul = byte(14) + blsG2MultiExp = byte(15) + blsPairing = byte(16) + blsMapG1 = byte(17) + blsMapG2 = byte(18) +) + +func FuzzG1Add(data []byte) int { return fuzz(blsG1Add, data) } +func FuzzG1Mul(data []byte) int { return fuzz(blsG1Mul, data) } +func FuzzG1MultiExp(data []byte) int { return fuzz(blsG1MultiExp, data) } +func FuzzG2Add(data []byte) int { return fuzz(blsG2Add, data) } +func FuzzG2Mul(data []byte) int { return fuzz(blsG2Mul, data) } +func FuzzG2MultiExp(data []byte) int { return fuzz(blsG2MultiExp, data) } +func FuzzPairing(data []byte) int { return fuzz(blsPairing, data) } +func FuzzMapG1(data []byte) int { return fuzz(blsMapG1, data) } +func FuzzMapG2(data []byte) int { return fuzz(blsMapG2, data) } + +func checkInput(id byte, inputLen int) bool { + switch id { + case blsG1Add: + return inputLen == 256 + case blsG1Mul: + return inputLen == 160 + case blsG1MultiExp: + return inputLen%160 == 0 + case blsG2Add: + return inputLen == 512 + case blsG2Mul: + return inputLen == 288 + case blsG2MultiExp: + return inputLen%288 == 0 + case blsPairing: + return inputLen%384 == 0 + case blsMapG1: + return inputLen == 64 + case blsMapG2: + return inputLen == 128 + } + panic("programmer error") +} + +// The fuzzer functions must return +// 1 if the fuzzer should increase priority of the +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// -1 if the input must not be added to corpus even if gives new coverage; and +// 0 otherwise +// other values are reserved for future use. +func fuzz(id byte, data []byte) int { + // Even on bad input, it should not crash, so we still test the gas calc + precompile := vm.PrecompiledContractsYoloV2[common.BytesToAddress([]byte{id})] + gas := precompile.RequiredGas(data) + if !checkInput(id, len(data)) { + return 0 + } + // If the gas cost is too large (25M), bail out + if gas > 25*1000*1000 { + return 0 + } + cpy := make([]byte, len(data)) + copy(cpy, data) + _, err := precompile.Run(cpy) + if !bytes.Equal(cpy, data) { + panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy)) + } + if err != nil { + return 0 + } + return 1 +} diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..16498c1cba89a3c9944b3f8b8e70f6ffd88aac08 GIT binary patch literal 41620 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 Z`g%BdES%#UHOd}-U4dP=a<8F3{RhNC0y6*r literal 0 HcmV?d00001 diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip new file mode 100644 index 0000000000000000000000000000000000000000..57f9d6696d8c3f73a31d8098ee337eebd7f39d26 GIT binary patch literal 37687 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 k`oqG#csO_!mjxc7| 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

8CN1t#~qBXNA2 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%0A&e1qErOwfM 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%0DYI9;5PX*DkFgl`ApG=a(AmHx>W2PGCef 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 literal 0 HcmV?d00001 diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json new file mode 100644 index 0000000000..ea5e2725b5 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -0,0 +1,26 @@ +{ + "config": { + "chainId": 1, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "ethash": {} + }, + "nonce": "0xdeadbeefdeadbeef", + "timestamp": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x8000000", + "difficulty": "0x10", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "71562b71999873db5b286df957af199ec94617f7": { + "balance": "0xf4240" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index ef2c52ddfd..b6298e8083 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -1,14 +1,36 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + package ethtest import ( + "crypto/ecdsa" "fmt" "io" "math/big" + "reflect" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" ) @@ -20,9 +42,10 @@ type Error struct { err error } -func (e *Error) Unwrap() error { return e.err } -func (e *Error) Error() string { return e.err.Error() } -func (e *Error) Code() int { return -1 } +func (e *Error) Unwrap() error { return e.err } +func (e *Error) Error() string { return e.err.Error() } +func (e *Error) Code() int { return -1 } +func (e *Error) GoString() string { return e.Error() } // Hello is the RLP structure of the protocol handshake. type Hello struct { @@ -45,6 +68,14 @@ type Disconnect struct { func (d Disconnect) Code() int { return 0x01 } +type Ping struct{} + +func (p Ping) Code() int { return 0x02 } + +type Pong struct{} + +func (p Pong) Code() int { return 0x03 } + // Status is the network packet for the status message for eth/64 and later. type Status struct { ProtocolVersion uint32 @@ -132,3 +163,204 @@ func (gbb GetBlockBodies) Code() int { return 21 } type BlockBodies []*types.Body func (bb BlockBodies) Code() int { return 22 } + +// Conn represents an individual connection with a peer +type Conn struct { + *rlpx.Conn + ourKey *ecdsa.PrivateKey + ethProtocolVersion uint +} + +func (c *Conn) Read() Message { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return &Error{fmt.Errorf("could not read from connection: %v", err)} + } + + var msg Message + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + msg = new(GetBlockHeaders) + case (BlockHeaders{}).Code(): + msg = new(BlockHeaders) + case (GetBlockBodies{}).Code(): + msg = new(GetBlockBodies) + case (BlockBodies{}).Code(): + msg = new(BlockBodies) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + default: + return &Error{fmt.Errorf("invalid message code: %d", code)} + } + + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return &Error{fmt.Errorf("could not rlp decode message: %v", err)} + } + + return msg +} + +// ReadAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) ReadAndServe(chain *Chain) Message { + for { + switch msg := c.Read().(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + req := *msg + headers, err := chain.GetHeaders(req) + if err != nil { + return &Error{fmt.Errorf("could not get headers for inbound header request: %v", err)} + } + + if err := c.Write(headers); err != nil { + return &Error{fmt.Errorf("could not write to connection: %v", err)} + } + default: + return msg + } + } +} + +func (c *Conn) Write(msg Message) error { + payload, err := rlp.EncodeToBytes(msg) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(msg.Code()), payload) + return err + +} + +// handshake checks to make sure a `HELLO` is received. +func (c *Conn) handshake(t *utesting.T) Message { + // write protoHandshake to client + pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &Hello{ + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: pub0, + } + if err := c.Write(ourHandshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // read protoHandshake from client + switch msg := c.Read().(type) { + case *Hello: + // set snappy if version is at least 5 + if msg.Version >= 5 { + c.SetSnappy(true) + } + + c.negotiateEthProtocol(msg.Caps) + if c.ethProtocolVersion == 0 { + t.Fatalf("unexpected eth protocol version") + } + return msg + default: + t.Fatalf("bad handshake: %#v", msg) + return nil + } +} + +// negotiateEthProtocol sets the Conn's eth protocol version +// to highest advertised capability from peer +func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { + var highestEthVersion uint + for _, capability := range caps { + if capability.Name != "eth" { + continue + } + if capability.Version > highestEthVersion && capability.Version <= 65 { + highestEthVersion = capability.Version + } + } + c.ethProtocolVersion = highestEthVersion +} + +// statusExchange performs a `Status` message exchange with the given +// node. +func (c *Conn) statusExchange(t *utesting.T, chain *Chain) Message { + // read status message from client + var message Message + +loop: + for { + switch msg := c.Read().(type) { + case *Status: + if msg.Head != chain.blocks[chain.Len()-1].Hash() { + t.Fatalf("wrong head in status: %v", msg.Head) + } + if msg.TD.Cmp(chain.TD(chain.Len())) != 0 { + t.Fatalf("wrong TD in status: %v", msg.TD) + } + if !reflect.DeepEqual(msg.ForkID, chain.ForkID()) { + t.Fatalf("wrong fork ID in status: %v", msg.ForkID) + } + message = msg + break loop + case *Disconnect: + t.Fatalf("disconnect received: %v", msg.Reason) + case *Ping: + c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error + // (PINGs should not be a response upon fresh connection) + default: + t.Fatalf("bad status message: %#v", msg) + } + } + // make sure eth protocol version is set for negotiation + if c.ethProtocolVersion == 0 { + t.Fatalf("eth protocol version must be set in Conn") + } + // write status message to client + status := Status{ + ProtocolVersion: uint32(c.ethProtocolVersion), + NetworkID: 1, + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + if err := c.Write(status); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + return message +} + +// waitForBlock waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock(block *types.Block) error { + for { + req := &GetBlockHeaders{Origin: hashOrNumber{Hash: block.Hash()}, Amount: 1} + if err := c.Write(req); err != nil { + return err + } + + switch msg := c.Read().(type) { + case *BlockHeaders: + if len(*msg) > 0 { + return nil + } + time.Sleep(100 * time.Millisecond) + default: + return fmt.Errorf("invalid message: %v", msg) + } + } +} From 015e78928a374afb016a12db137d9ca7f1a37a03 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 7 Oct 2020 20:05:14 +0200 Subject: [PATCH 071/948] node: relax websocket connection header check (#21646) This makes it accept the "upgrade,keep-alive" header value, which apparently is a thing. --- node/rpcstack.go | 2 +- node/rpcstack_test.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index caf7e5b7a8..731e807aca 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -313,7 +313,7 @@ func (h *httpServer) wsAllowed() bool { // isWebsocket checks the header of an http request for a websocket upgrade request. func isWebsocket(r *http.Request) bool { return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && - strings.ToLower(r.Header.Get("Connection")) == "upgrade" + strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") } // NewHTTPHandlerStack returns wrapped http-related handlers diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index efab5b37dc..0ee120efd7 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -73,6 +73,21 @@ func TestWebsocketOrigins(t *testing.T) { assert.Error(t, err) } +// TestIsWebsocket tests if an incoming websocket upgrade request is handled properly. +func TestIsWebsocket(t *testing.T) { + r, _ := http.NewRequest("GET", "/", nil) + + assert.False(t, isWebsocket(r)) + r.Header.Set("upgrade", "websocket") + assert.False(t, isWebsocket(r)) + r.Header.Set("connection", "upgrade") + assert.True(t, isWebsocket(r)) + r.Header.Set("connection", "upgrade,keep-alive") + assert.True(t, isWebsocket(r)) + r.Header.Set("connection", " UPGRADE,keep-alive") + assert.True(t, isWebsocket(r)) +} + func createAndStartServer(t *testing.T, conf httpConfig, ws bool, wsConf wsConfig) *httpServer { t.Helper() From 6d29e192e92ed51beaad3792b33a0b6c5043a00c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 8 Oct 2020 10:10:58 +0200 Subject: [PATCH 072/948] signer/core: don't mismatch reject and no accounts (#21677) * signer/core: don't mismatch reject and zero accounts, fixes #21674 * signer/core: docs --- signer/core/api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/signer/core/api.go b/signer/core/api.go index 6b7d18dfde..43926a75ff 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -383,7 +383,9 @@ func (api *SignerAPI) startUSBListener() { // List returns the set of wallet this signer manages. Each wallet can contain // multiple accounts. func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { - var accs []accounts.Account + var accs = make([]accounts.Account, 0) + // accs is initialized as empty list, not nil. We use 'nil' to signal + // rejection, as opposed to an empty list. for _, wallet := range api.am.Wallets() { accs = append(accs, wallet.Accounts()...) } @@ -393,13 +395,11 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { } if result.Accounts == nil { return nil, ErrRequestDenied - } addresses := make([]common.Address, 0) for _, acc := range result.Accounts { addresses = append(addresses, acc.Address) } - return addresses, nil } From 5e86e4ed291ef0133670f0974b5511618e43e297 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 8 Oct 2020 11:19:54 +0200 Subject: [PATCH 073/948] p2p/discover: remove use of shared hash instance for key derivation (#21673) For some reason, using the shared hash causes a cryptographic incompatibility when using Go 1.15. I noticed this during the development of Discovery v5.1 when I added test vector verification. The go library commit that broke this is golang/go@97240d5, but the way we used HKDF is slightly dodgy anyway and it's not a regression. --- p2p/discover/v5_encoding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/discover/v5_encoding.go b/p2p/discover/v5_encoding.go index 842234e790..c1a790dd2c 100644 --- a/p2p/discover/v5_encoding.go +++ b/p2p/discover/v5_encoding.go @@ -383,7 +383,7 @@ func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecd info := []byte("discovery v5 key agreement") info = append(info, n1[:]...) info = append(info, n2[:]...) - kdf := hkdf.New(c.sha256reset, eph, challenge.IDNonce[:], info) + kdf := hkdf.New(sha256.New, eph, challenge.IDNonce[:], info) sec := handshakeSecrets{ writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize), From 43cd31ea9f57e26f8f67aa8bd03bbb0a50814465 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Thu, 8 Oct 2020 14:03:24 +0200 Subject: [PATCH 074/948] core/vm: dedup config check in markdown logger (#21655) * core/vm: dedup config check * review feedback: reuse buffer --- core/vm/logger.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/vm/logger.go b/core/vm/logger.go index e1d7c67ef1..3b166b5d26 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -322,20 +322,21 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error { fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) - if !t.cfg.DisableStack { // format stack + if !t.cfg.DisableStack { + // format stack var a []string for _, elem := range stack.data { a = append(a, fmt.Sprintf("%d", elem)) } b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) - } - if !t.cfg.DisableStack { // format return stack - var a []string + + // format return stack + a = a[:0] for _, elem := range rStack.data { a = append(a, fmt.Sprintf("%2d", elem)) } - b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + b = fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) } fmt.Fprintln(t.out, "") From 905ed109edfda340ccd3641ebad4e0ff5dc73440 Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 9 Oct 2020 14:58:30 +0800 Subject: [PATCH 075/948] eth/downloader: fix data race around the ancientlimit (#21681) * eth/downloader: fix data race around the ancientlimit * eth/downloader: initialize the ancientlimit as 0 --- eth/downloader/downloader.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index c6ca9af013..7c572289b2 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -515,6 +515,8 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I d.ancientLimit = d.checkpoint } else if height > fullMaxForkAncestry+1 { d.ancientLimit = height - fullMaxForkAncestry - 1 + } else { + d.ancientLimit = 0 } frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. @@ -606,9 +608,6 @@ func (d *Downloader) cancel() { func (d *Downloader) Cancel() { d.cancel() d.cancelWg.Wait() - - d.ancientLimit = 0 - log.Debug("Reset ancient limit to zero") } // Terminate interrupts the downloader, canceling all pending operations. From 19a1c950467db58171532bbfcdc354f50a6e5f8e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 9 Oct 2020 09:09:10 +0200 Subject: [PATCH 076/948] eth/downloader: cache parent hash instead of recomputing (#21678) --- eth/downloader/queue.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 51a57f0d4c..d2ec8ba694 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -712,6 +712,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh } } if accepted { + parentHash := headers[0].Hash() for i, header := range headers[1:] { hash := header.Hash() if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { @@ -719,11 +720,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh accepted = false break } - if headers[i].Hash() != header.ParentHash { + if parentHash != header.ParentHash { log.Warn("Header broke chain ancestry", "peer", id, "number", header.Number, "hash", hash) accepted = false break } + // Set-up parent hash for next round + parentHash = hash } } // If the batch of headers wasn't accepted, mark as unavailable From 706f5e3b98a419e8b93653d21876b74a60ab3ee6 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 9 Oct 2020 11:23:46 +0200 Subject: [PATCH 077/948] core: fix txpool off-by-one error (#21683) --- core/tx_pool.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/tx_pool.go b/core/tx_pool.go index 0fe1d3db5b..e3ffe103cf 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -817,6 +817,7 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { nilSlot++ } errs[nilSlot] = err + nilSlot++ } // Reorg the pool internals if needed and return done := pool.requestPromoteExecutables(dirtyAddrs) From 86dd005544179818edd78ef6c9396b9574e8a614 Mon Sep 17 00:00:00 2001 From: gary rong Date: Mon, 12 Oct 2020 18:08:04 +0800 Subject: [PATCH 078/948] trie: polish commit function (#21692) * trie: polish commit function * trie: fix typo --- trie/stacktrie.go | 23 +++++++++++++++-------- trie/trie_test.go | 5 ++++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/trie/stacktrie.go b/trie/stacktrie.go index fc653101ae..33fa990077 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -17,6 +17,7 @@ package trie import ( + "errors" "fmt" "sync" @@ -26,6 +27,8 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +var ErrCommitDisabled = errors.New("no database for committing") + var stPool = sync.Pool{ New: func() interface{} { return NewStackTrie(nil) @@ -391,14 +394,18 @@ func (st *StackTrie) Hash() (h common.Hash) { return common.BytesToHash(st.val) } -// Commit will commit the current node to database db -func (st *StackTrie) Commit(db ethdb.KeyValueStore) common.Hash { - oldDb := st.db - st.db = db - defer func() { - st.db = oldDb - }() +// Commit will firstly hash the entrie trie if it's still not hashed +// and then commit all nodes to the associated database. Actually most +// of the trie nodes MAY have been committed already. The main purpose +// here is to commit the root node. +// +// The associated database is expected, otherwise the whole commit +// functionality should be disabled. +func (st *StackTrie) Commit() (common.Hash, error) { + if st.db == nil { + return common.Hash{}, ErrCommitDisabled + } st.hash() h := common.BytesToHash(st.val) - return h + return h, nil } diff --git a/trie/trie_test.go b/trie/trie_test.go index 03ec0cab89..539451fbf4 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -831,7 +831,10 @@ func TestCommitSequenceStackTrie(t *testing.T) { // Flush memdb -> disk (sponge) db.Commit(root, false, nil) // And flush stacktrie -> disk - stRoot := stTrie.Commit(stTrie.db) + stRoot, err := stTrie.Commit() + if err != nil { + t.Fatalf("Failed to commit stack trie %v", err) + } if stRoot != root { t.Fatalf("root wrong, got %x exp %x", stRoot, root) } From 66c3eb2f1a5c6bb4b5a678ce819f9984ac09e246 Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Mon, 12 Oct 2020 21:02:38 +0800 Subject: [PATCH 079/948] accouts, consensus, core: fix some comments (#21617) --- accounts/accounts.go | 2 +- accounts/scwallet/wallet.go | 2 +- accounts/usbwallet/wallet.go | 2 +- consensus/clique/snapshot_test.go | 2 +- core/state/state_object.go | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/accounts/accounts.go b/accounts/accounts.go index 7a14e4e3e5..dc85cba174 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -88,7 +88,7 @@ type Wallet interface { // to discover non zero accounts and automatically add them to list of tracked // accounts. // - // Note, self derivaton will increment the last component of the specified path + // Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 80009fc5eb..85fae8c114 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -637,7 +637,7 @@ func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun // to discover non zero accounts and automatically add them to list of tracked // accounts. // -// Note, self derivaton will increment the last component of the specified path +// Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 993c599346..e39c6bdf34 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -493,7 +493,7 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun // to discover non zero accounts and automatically add them to list of tracked // accounts. // -// Note, self derivaton will increment the last component of the specified path +// Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 3890fc51dd..039ba919bf 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -423,7 +423,7 @@ func TestClique(t *testing.T) { }) // Iterate through the blocks and seal them individually for j, block := range blocks { - // Geth the header and prepare it for signing + // Get the header and prepare it for signing header := block.Header() if j > 0 { header.ParentHash = blocks[j-1].Hash() diff --git a/core/state/state_object.go b/core/state/state_object.go index 26ab67e1ad..d0d3b4513e 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -299,7 +299,7 @@ func (s *stateObject) updateTrie(db Database) Trie { if len(s.pendingStorage) == 0 { return s.trie } - // Track the amount of time wasted on updating the storge trie + // Track the amount of time wasted on updating the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) } @@ -347,7 +347,7 @@ func (s *stateObject) updateRoot(db Database) { if s.updateTrie(db) == nil { return } - // Track the amount of time wasted on hashing the storge trie + // Track the amount of time wasted on hashing the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now()) } @@ -364,7 +364,7 @@ func (s *stateObject) CommitTrie(db Database) error { if s.dbErr != nil { return s.dbErr } - // Track the amount of time wasted on committing the storge trie + // Track the amount of time wasted on committing the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) } From 32341f88e30f55e722f9d335938c4d1ab099f892 Mon Sep 17 00:00:00 2001 From: Hanjiang Yu Date: Tue, 13 Oct 2020 16:55:57 +0800 Subject: [PATCH 080/948] console: fix admin.sleepBlocks (#21629) --- console/bridge.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/console/bridge.go b/console/bridge.go index 9303496b28..1a23269194 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -353,14 +353,14 @@ func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { } // Poll the current block number until either it or a timeout is reached. - var ( - deadline = time.Now().Add(time.Duration(sleep) * time.Second) - lastNumber = ^hexutil.Uint64(0) - ) + deadline := time.Now().Add(time.Duration(sleep) * time.Second) + var lastNumber hexutil.Uint64 + if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil { + return nil, err + } for time.Now().Before(deadline) { var number hexutil.Uint64 - err := b.client.Call(&number, "eth_blockNumber") - if err != nil { + if err := b.client.Call(&number, "eth_blockNumber"); err != nil { return nil, err } if number != lastNumber { From c37e68e7c114c4783557eff727b8fb5e657549d2 Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Tue, 13 Oct 2020 16:58:41 +0800 Subject: [PATCH 081/948] all: replace RWMutex with Mutex in places where RLock is not used (#21622) --- accounts/keystore/file_cache.go | 2 +- core/chain_indexer.go | 2 +- eth/downloader/downloader.go | 2 +- eth/downloader/downloader_test.go | 3 +-- les/txrelay.go | 2 +- miner/unconfirmed.go | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go index 73ff6ae9ee..8b309321d3 100644 --- a/accounts/keystore/file_cache.go +++ b/accounts/keystore/file_cache.go @@ -32,7 +32,7 @@ import ( type fileCache struct { all mapset.Set // Set of all files from the keystore folder lastMod time.Time // Last time instance when a file was modified - mu sync.RWMutex + mu sync.Mutex } // scan performs a new scan on the given directory, compares against the already diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 066bca1000..4b326c970b 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -94,7 +94,7 @@ type ChainIndexer struct { throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources log log.Logger - lock sync.RWMutex + lock sync.Mutex } // NewChainIndexer creates a new chain indexer to do background processing on diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 7c572289b2..686c1ace14 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -153,7 +153,7 @@ type Downloader struct { cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. quitCh chan struct{} // Quit channel to signal termination - quitLock sync.RWMutex // Lock to prevent double closes + quitLock sync.Mutex // Lock to prevent double closes // Testing hooks syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 4bf1e4d469..7645f04e4f 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -411,7 +411,6 @@ func (dl *downloadTester) dropPeer(id string) { type downloadTesterPeer struct { dl *downloadTester id string - lock sync.RWMutex chain *testChain missingStates map[common.Hash]bool // State entries that fast sync should not return } @@ -1601,7 +1600,7 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { {15000, 13006, []int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999}, }, - //Remote is pretty close to us. We don't have to fetch as many + // Remote is pretty close to us. We don't have to fetch as many {1200, 1150, []int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199}, }, diff --git a/les/txrelay.go b/les/txrelay.go index 4f6c15025e..57f2412eba 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -35,7 +35,7 @@ type lesTxRelay struct { txPending map[common.Hash]struct{} peerList []*serverPeer peerStartPos int - lock sync.RWMutex + lock sync.Mutex stop chan struct{} retriever *retrieveManager diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go index 3a176e8bd6..0489f1ea4a 100644 --- a/miner/unconfirmed.go +++ b/miner/unconfirmed.go @@ -50,7 +50,7 @@ type unconfirmedBlocks struct { chain chainRetriever // Blockchain to verify canonical status through depth uint // Depth after which to discard previous blocks blocks *ring.Ring // Block infos to allow canonical chain cross checks - lock sync.RWMutex // Protects the fields from concurrent access + lock sync.Mutex // Protects the fields from concurrent access } // newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks. From 94d1f5888af495a2ce08d16cbd753cc1cc947d11 Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Tue, 13 Oct 2020 17:00:42 +0800 Subject: [PATCH 082/948] consensus/clique: unexport calcDifficulty and improve comment (#21619) --- consensus/clique/clique.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 02f2451133..c05f84cc2e 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -520,7 +520,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header c.lock.RUnlock() } // Set the correct difficulty - header.Difficulty = CalcDifficulty(snap, c.signer) + header.Difficulty = calcDifficulty(snap, c.signer) // Ensure the extra data has all its components if len(header.Extra) < extraVanity { @@ -652,20 +652,18 @@ func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, res } // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty -// that a new block should have based on the previous blocks in the chain and the -// current signer. +// that a new block should have: +// * DIFF_NOTURN(2) if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX +// * DIFF_INTURN(1) if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil) if err != nil { return nil } - return CalcDifficulty(snap, c.signer) + return calcDifficulty(snap, c.signer) } -// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty -// that a new block should have based on the previous blocks in the chain and the -// current signer. -func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int { +func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int { if snap.inturn(snap.Number+1, signer) { return new(big.Int).Set(diffInTurn) } From 348c3bc47d8173ce24c5b92d697a5be289eea9a0 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 13 Oct 2020 13:21:25 +0200 Subject: [PATCH 083/948] trie: fix flaw in stacktrie pool reuse (#21699) --- trie/stacktrie.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 33fa990077..ffccbbf4ac 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -113,7 +113,7 @@ func (st *StackTrie) Update(key, value []byte) { func (st *StackTrie) Reset() { st.db = nil st.key = st.key[:0] - st.val = st.val[:0] + st.val = nil for i := range st.children { st.children[i] = nil } From 5c6155f9f49bfef173c4107979185aec59806c38 Mon Sep 17 00:00:00 2001 From: mr_franklin Date: Tue, 13 Oct 2020 19:24:08 +0800 Subject: [PATCH 084/948] internal/web3ext: improve some web3 apis (#21639) * imporve some web3-ext apis * Update web3ext.go Co-authored-by: Felix Lange --- internal/web3ext/web3ext.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 20e0ea11f5..77954bbbf0 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -237,7 +237,8 @@ web3._extend({ new web3._extend.Method({ name: 'printBlock', call: 'debug_printBlock', - params: 1 + params: 1, + outputFormatter: console.log }), new web3._extend.Method({ name: 'getBlockRlp', @@ -248,7 +249,7 @@ web3._extend({ name: 'testSignCliqueBlock', call: 'debug_testSignCliqueBlock', params: 2, - inputFormatters: [web3._extend.formatters.inputAddressFormatter, null], + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null], }), new web3._extend.Method({ name: 'setHead', @@ -263,7 +264,8 @@ web3._extend({ new web3._extend.Method({ name: 'dumpBlock', call: 'debug_dumpBlock', - params: 1 + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'chaindbProperty', @@ -415,7 +417,7 @@ web3._extend({ name: 'traceBlockByNumber', call: 'debug_traceBlockByNumber', params: 2, - inputFormatter: [null, null] + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, null] }), new web3._extend.Method({ name: 'traceBlockByHash', @@ -522,7 +524,8 @@ web3._extend({ new web3._extend.Method({ name: 'getHeaderByNumber', call: 'eth_getHeaderByNumber', - params: 1 + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'getHeaderByHash', From 7a5a822905d95456a108db52ee68871a73dc54f1 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 13 Oct 2020 13:28:24 +0200 Subject: [PATCH 085/948] eth, p2p: use truncated names (#21698) * peer: return localAddr instead of name to prevent spam We currently use the name (which can be freely set by the peer) in several log messages. This enables malicious actors to write spam into your geth log. This commit returns the localAddr instead of the freely settable name. * p2p: reduce usage of peer.Name in warn messages * eth, p2p: use truncated names * Update peer.go Co-authored-by: Marius van der Wijden Co-authored-by: Felix Lange --- p2p/peer.go | 13 +++++++++++-- p2p/server.go | 9 +-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/p2p/peer.go b/p2p/peer.go index 54fb653e24..a9c3cf01da 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -138,8 +138,17 @@ func (p *Peer) Node() *enode.Node { return p.rw.node } -// Name returns the node name that the remote node advertised. +// Name returns an abbreviated form of the name func (p *Peer) Name() string { + s := p.rw.name + if len(s) > 20 { + return s[:20] + "..." + } + return s +} + +// Fullname returns the node name that the remote node advertised. +func (p *Peer) Fullname() string { return p.rw.name } @@ -463,7 +472,7 @@ func (p *Peer) Info() *PeerInfo { info := &PeerInfo{ Enode: p.Node().URLv4(), ID: p.ID().String(), - Name: p.Name(), + Name: p.Fullname(), Caps: caps, Protocols: make(map[string]interface{}), } diff --git a/p2p/server.go b/p2p/server.go index a343f4320a..dd52297f8a 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -757,7 +757,7 @@ running: // The handshakes are done and it passed all checks. p := srv.launchPeer(c) peers[c.node.ID()] = p - srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", truncateName(c.name)) + srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name()) srv.dialsched.peerAdded(c) if p.Inbound() { inboundCount++ @@ -996,13 +996,6 @@ func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node { return enode.NewV4(pubkey, ip, port, port) } -func truncateName(s string) string { - if len(s) > 20 { - return s[:20] + "..." - } - return s -} - // checkpoint sends the conn to run, which performs the // post-handshake checks for the stage (posthandshake, addpeer). func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error { From 2a9ea6be87a282ae3d0171a631076ea983d02477 Mon Sep 17 00:00:00 2001 From: Giuseppe Bertone Date: Tue, 13 Oct 2020 13:33:10 +0200 Subject: [PATCH 086/948] cmd/geth, cmd/utils: fixed flags name (#21700) --- cmd/geth/main.go | 4 ++-- cmd/geth/usage.go | 4 ++-- cmd/utils/flags.go | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 677a19eed2..8c1f7c4c22 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -188,8 +188,8 @@ var ( utils.IPCDisabledFlag, utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, - utils.RPCGlobalGasCap, - utils.RPCGlobalTxFeeCap, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalTxFeeCapFlag, } whisperFlags = []cli.Flag{ diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 334a729c24..288c453597 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -144,8 +144,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.GraphQLEnabledFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, - utils.RPCGlobalGasCap, - utils.RPCGlobalTxFeeCap, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalTxFeeCapFlag, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 55b751d625..80da518c2b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -454,12 +454,12 @@ var ( Name: "allow-insecure-unlock", Usage: "Allow insecure account unlocking when account-related RPCs are exposed by http", } - RPCGlobalGasCap = cli.Uint64Flag{ + RPCGlobalGasCapFlag = cli.Uint64Flag{ Name: "rpc.gascap", Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)", Value: eth.DefaultConfig.RPCGasCap, } - RPCGlobalTxFeeCap = cli.Float64Flag{ + RPCGlobalTxFeeCapFlag = cli.Float64Flag{ Name: "rpc.txfeecap", Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)", Value: eth.DefaultConfig.RPCTxFeeCap, @@ -1563,16 +1563,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(EVMInterpreterFlag.Name) { cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name) } - if ctx.GlobalIsSet(RPCGlobalGasCap.Name) { - cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCap.Name) + if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) { + cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name) } if cfg.RPCGasCap != 0 { log.Info("Set global gas cap", "cap", cfg.RPCGasCap) } else { log.Info("Global gas cap disabled") } - if ctx.GlobalIsSet(RPCGlobalTxFeeCap.Name) { - cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCap.Name) + if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) { + cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name) } if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) { urls := ctx.GlobalString(DNSDiscoveryFlag.Name) From 1e10489196fd42ed553ba4ef8bbb547c38042eed Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 13 Oct 2020 14:12:06 +0200 Subject: [PATCH 087/948] miner: don't interrupt mining after successful sync (#21701) * miner: exit loop when downloader Done or Failed Following the logic of the comment at the method, this fixes a regression introduced at 7cf56d6f064869cb62b1673f9ee437020c595391 , which would allow external parties to DoS with blocks, preventing mining progress. Signed-off-by: meows * miner: remove ineff assign (lint) Signed-off-by: meows * miner: update test re downloader events Signed-off-by: meows * Revert "miner: remove ineff assign (lint)" This reverts commit eaefcd34ab4862ebc936fb8a07578aa2744bc058. * Revert "miner: exit loop when downloader Done or Failed" This reverts commit 23abd34265aa246c38fc390bb72572ad6ae9fe3b. * miner: add test showing imprecise TestMiner Signed-off-by: meows * miner: fix waitForMiningState precision This helper function would return an affirmation on the first positive match on a desired bool. This was imprecise; it return false positives by not waiting initially for an 'updated' value. This fix causes TestMiner_2 to fail, which is expected. Signed-off-by: meows * miner: remove TestMiner_2 demonstrating broken test This test demonstrated the imprecision of the test helper function waitForMiningState. This function has been fixed with 6d365c2851, and this test test may now be removed. Signed-off-by: meows * miner: fix test regarding downloader event/mining expectations See comment for logic. Signed-off-by: meows * miner: add test describing expectations for downloader/mining events We expect that once the downloader emits a DoneEvent, signaling a successful sync, that subsequent StartEvents are not longer permitted to stop the miner. This prevents a security vulnerability where forced syncs via fake high blocks would stall mining operation. Signed-off-by: meows * miner: use 'canStop' state to fix downloader event handling - Break downloader event handling into event separating Done and Failed events. We need to treat these cases differently since a DoneEvent should prevent the miner from being stopped on subsequent downloader Start events. - Use canStop state to handle the one-off case when a downloader first succeeds. Signed-off-by: meows * miner: improve comment wording Signed-off-by: meows * miner: start mining on downloader events iff not already mining Signed-off-by: meows * miner: refactor miner update logic w/r/t downloader events This makes mining pause/start logic regarding downloader events more explicit. Instead of eternally handling downloader events after the first done event, the subscription is closed when downloader events are no longer actionable. Signed-off-by: meows * miner: fix handling downloader events on subcription closed Signed-off-by: meows * miner: (lint:gosimple) use range over chan instead of for/select Signed-off-by: meows * miner: refactor update loop to remove race condition The go routine handling the downloader events handling vars in parallel with the parent routine, causing a race condition. This change, though ugly, remove the condition while still allowing the downloader event subscription to be closed when the miner has no further use for it (ie DoneEvent). * miner: alternate fix for miner-flaw Co-authored-by: meows --- miner/miner.go | 23 +++++++++++++--- miner/miner_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/miner/miner.go b/miner/miner.go index 8cbd70b424..35c036ba75 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -85,15 +85,22 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even // and halt your mining operation for as long as the DOS continues. func (miner *Miner) update() { events := miner.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{}) - defer events.Unsubscribe() + defer func() { + if !events.Closed() { + events.Unsubscribe() + } + }() shouldStart := false canStart := true + dlEventCh := events.Chan() for { select { - case ev := <-events.Chan(): + case ev := <-dlEventCh: if ev == nil { - return + // Unsubscription done, stop listening + dlEventCh = nil + continue } switch ev.Data.(type) { case downloader.StartEvent: @@ -105,12 +112,20 @@ func (miner *Miner) update() { shouldStart = true log.Info("Mining aborted due to sync") } - case downloader.DoneEvent, downloader.FailedEvent: + case downloader.FailedEvent: + canStart = true + if shouldStart { + miner.SetEtherbase(miner.coinbase) + miner.worker.start() + } + case downloader.DoneEvent: canStart = true if shouldStart { miner.SetEtherbase(miner.coinbase) miner.worker.start() } + // Stop reacting to downloader events + events.Unsubscribe() } case addr := <-miner.startCh: if canStart { diff --git a/miner/miner_test.go b/miner/miner_test.go index 2ed03a2397..20bf2534c2 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -89,12 +89,75 @@ func TestMiner(t *testing.T) { // Stop the downloader and wait for the update loop to run mux.Post(downloader.DoneEvent{}) waitForMiningState(t, miner, true) - // Start the downloader and wait for the update loop to run + + // Subsequent downloader events after a successful DoneEvent should not cause the + // miner to start or stop. This prevents a security vulnerability + // that would allow entities to present fake high blocks that would + // stop mining operations by causing a downloader sync + // until it was discovered they were invalid, whereon mining would resume. + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, true) + + mux.Post(downloader.FailedEvent{}) + waitForMiningState(t, miner, true) +} + +// TestMinerDownloaderFirstFails tests that mining is only +// permitted to run indefinitely once the downloader sees a DoneEvent (success). +// An initial FailedEvent should allow mining to stop on a subsequent +// downloader StartEvent. +func TestMinerDownloaderFirstFails(t *testing.T) { + miner, mux := createMiner(t) + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Start the downloader mux.Post(downloader.StartEvent{}) waitForMiningState(t, miner, false) + // Stop the downloader and wait for the update loop to run mux.Post(downloader.FailedEvent{}) waitForMiningState(t, miner, true) + + // Since the downloader hasn't yet emitted a successful DoneEvent, + // we expect the miner to stop on next StartEvent. + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + + // Downloader finally succeeds. + mux.Post(downloader.DoneEvent{}) + waitForMiningState(t, miner, true) + + // Downloader starts again. + // Since it has achieved a DoneEvent once, we expect miner + // state to be unchanged. + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, true) + + mux.Post(downloader.FailedEvent{}) + waitForMiningState(t, miner, true) +} + +func TestMinerStartStopAfterDownloaderEvents(t *testing.T) { + miner, mux := createMiner(t) + + miner.Start(common.HexToAddress("0x12345")) + waitForMiningState(t, miner, true) + // Start the downloader + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + + // Downloader finally succeeds. + mux.Post(downloader.DoneEvent{}) + waitForMiningState(t, miner, true) + + miner.Stop() + waitForMiningState(t, miner, false) + + miner.Start(common.HexToAddress("0x678910")) + waitForMiningState(t, miner, true) + + miner.Stop() + waitForMiningState(t, miner, false) } func TestStartWhileDownload(t *testing.T) { @@ -137,10 +200,10 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) { var state bool for i := 0; i < 100; i++ { + time.Sleep(10 * time.Millisecond) if state = m.Mining(); state == mining { return } - time.Sleep(10 * time.Millisecond) } t.Fatalf("Mining() == %t, want %t", state, mining) } From 1fed2234833aa49ff728474208a65cbc23682ee4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 13 Oct 2020 19:46:43 +0200 Subject: [PATCH 088/948] accounts/keystore: fix flaky test (#21703) * accounts/keystore: add timeout to test to prevent failure on travis The TestWalletNotifications test sporadically fails on travis. This is because we shutdown the event collection before all events are received. Adding a small timeout (10 milliseconds) allows the collector to be scheduled and to consume all pending events before we shut it down. * accounts/keystore: added newlines back in * accounts/keystore: properly fix the walletNotifications test --- accounts/keystore/keystore_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index 29c251d7c1..cb5de11c0d 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -336,7 +336,9 @@ func TestWalletNotifications(t *testing.T) { // Shut down the event collector and check events. sub.Unsubscribe() - <-updates + for ev := range updates { + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + } checkAccounts(t, live, ks.Wallets()) checkEvents(t, wantEvents, events) } From bdc755491874426323d245918478e49b1998e427 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 14 Oct 2020 17:57:37 +0800 Subject: [PATCH 089/948] params: update CHTs (#21706) --- params/config.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/params/config.go b/params/config.go index a69d513080..6cae8cc0b0 100644 --- a/params/config.go +++ b/params/config.go @@ -73,10 +73,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 333, - SectionHead: common.HexToHash("0xb80784cbe88077e5911b446765edc814dd67ca3f6bdd33b6ec72d66058df4a11"), - CHTRoot: common.HexToHash("0x4da9cde840dd3de39916620f7a97674c5747a89a9359e6b918e134d199a8dd45"), - BloomRoot: common.HexToHash("0xdd0f4fef7fa2a5cc05d49568e38f15dab24098ffc7677a2e35d1a8d67f5458af"), + SectionIndex: 336, + SectionHead: common.HexToHash("0xd42b78902b6527a80337bf1bc372a3ccc3db97e9cc7cf421ca047ae9076c716b"), + CHTRoot: common.HexToHash("0xd97f3b30f7e0cb958e4c67c53ec27745e5a165e33e56821b86523dfee62b783a"), + BloomRoot: common.HexToHash("0xf3cbfd070fababfe2adc9b23fc02c731f6ca2cce6646b3ede4ef2db06092ccce"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -112,10 +112,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 262, - SectionHead: common.HexToHash("0x12b068f285789b966a983b632266484f1bc93803df6c78773538a5777f57a236"), - CHTRoot: common.HexToHash("0x14000a1407e866f174f3a20fe9f271acd704bcf929b5205d83b70a1bba8c82c2"), - BloomRoot: common.HexToHash("0x2f4f4a34a55e35d0691c79a79e39b6f661259345080fb880da5195c11c2413be"), + SectionIndex: 269, + SectionHead: common.HexToHash("0x290a9eb65e65c64601d1b05522533ed502098a246736b348502a170818a33d64"), + CHTRoot: common.HexToHash("0x530ebac02264227277d0a16b0819ef96a2011a6e1e66523ebff8040f4a3437ca"), + BloomRoot: common.HexToHash("0x480cd5b3198a0767022902130546854a2e8867cce573c1cf0ce54e67a7bf5efb"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -154,10 +154,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 220, - SectionHead: common.HexToHash("0x9513befa126a83c96a6408ee8b34502699094a49b2bf1064b2de31b010a03798"), - CHTRoot: common.HexToHash("0x490a17d3bfbfc9bca9de087c5ee9c9f69dc2359cad9c1fe68cab639fdbcfccee"), - BloomRoot: common.HexToHash("0x56bf5fda940ca4ca8346e42ef86f9092c82268c304c03c4093b21c1aa07190fc"), + SectionIndex: 223, + SectionHead: common.HexToHash("0x03ca0d5e3a931c77cd7a97bbaa2d9e4edc4549c621dc1d223a29f10c86a4a16a"), + CHTRoot: common.HexToHash("0x6573dbdd91b2958b446bd04d67c23e5f14b2510ac96e8df1b6a894dc49e37c6c"), + BloomRoot: common.HexToHash("0x28a35042a4e88efbac55fe566faf7fce000dc436f17fd4cb4b081c9cd793e1a7"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -194,10 +194,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 105, - SectionHead: common.HexToHash("0x695f5b67d1985fb13d177c56d20ded0622d7f63a1623959fb4b5c5e38dc6bbee"), - CHTRoot: common.HexToHash("0x4c281ef1ca63e6f9bb4ce8e46e80e478787c91da95c3727550ee418886dd6415"), - BloomRoot: common.HexToHash("0xa02463cc6ee54f12990e9adb019e34696ad1efe2694cf07187d7ce0802cd653d"), + SectionIndex: 107, + SectionHead: common.HexToHash("0xff3ae39199fa191894de419e7f673c8627aa8cc7af924b90f36635b6add375f2"), + CHTRoot: common.HexToHash("0x27d59d60c652425b6b593a882f55a4ff57f24e470a810a6e3c8ba71833a20220"), + BloomRoot: common.HexToHash("0x3c14066d8bb3733780c06b8165768dbb9dd23b75f56012fe5f2fb3c2fb70cadb"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From 4eb01b21c8691e277ea9d4048038ede7f9621a82 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 14 Oct 2020 11:59:11 +0200 Subject: [PATCH 090/948] miner: set etherbase even if mining isn't possible at the moment (#21707) --- miner/miner.go | 2 +- miner/miner_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/miner/miner.go b/miner/miner.go index 35c036ba75..20169f5007 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -128,8 +128,8 @@ func (miner *Miner) update() { events.Unsubscribe() } case addr := <-miner.startCh: + miner.SetEtherbase(addr) if canStart { - miner.SetEtherbase(addr) miner.worker.start() } shouldStart = true diff --git a/miner/miner_test.go b/miner/miner_test.go index 20bf2534c2..43b31872a4 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -192,6 +192,28 @@ func TestCloseMiner(t *testing.T) { waitForMiningState(t, miner, false) } +// TestMinerSetEtherbase checks that etherbase becomes set even if mining isn't +// possible at the moment +func TestMinerSetEtherbase(t *testing.T) { + miner, mux := createMiner(t) + // Start with a 'bad' mining address + miner.Start(common.HexToAddress("0xdead")) + waitForMiningState(t, miner, true) + // Start the downloader + mux.Post(downloader.StartEvent{}) + waitForMiningState(t, miner, false) + // Now user tries to configure proper mining address + miner.Start(common.HexToAddress("0x1337")) + // Stop the downloader and wait for the update loop to run + mux.Post(downloader.DoneEvent{}) + + waitForMiningState(t, miner, true) + // The miner should now be using the good address + if got, exp := miner.coinbase, common.HexToAddress("0x1337"); got != exp { + t.Fatalf("Wrong coinbase, got %x expected %x", got, exp) + } +} + // waitForMiningState waits until either // * the desired mining state was reached // * a timeout was reached which fails the test From 524aaf5ec65826e19c35f66ddaf1882ad85caf9c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 14 Oct 2020 12:28:17 +0200 Subject: [PATCH 091/948] p2p/discover: implement v5.1 wire protocol (#21647) This change implements the Discovery v5.1 wire protocol and also adds an interactive test suite for this protocol. --- cmd/devp2p/README.md | 86 +++ cmd/devp2p/discv4cmd.go | 12 +- cmd/devp2p/discv5cmd.go | 35 + cmd/devp2p/internal/v5test/discv5tests.go | 377 ++++++++++ cmd/devp2p/internal/v5test/framework.go | 263 +++++++ internal/utesting/utesting.go | 26 +- p2p/discover/node.go | 5 +- p2p/discover/table_util_test.go | 6 +- p2p/discover/v4_lookup_test.go | 16 +- p2p/discover/v5_encoding.go | 659 ------------------ p2p/discover/v5_encoding_test.go | 373 ---------- p2p/discover/v5_udp.go | 461 ++++++------ p2p/discover/v5_udp_test.go | 317 ++++++--- p2p/discover/v5wire/crypto.go | 180 +++++ p2p/discover/v5wire/crypto_test.go | 124 ++++ p2p/discover/v5wire/encoding.go | 648 +++++++++++++++++ p2p/discover/v5wire/encoding_test.go | 636 +++++++++++++++++ p2p/discover/v5wire/msg.go | 249 +++++++ .../{v5_session.go => v5wire/session.go} | 81 ++- .../testdata/v5.1-ping-handshake-enr.txt | 27 + .../v5wire/testdata/v5.1-ping-handshake.txt | 23 + .../v5wire/testdata/v5.1-ping-message.txt | 10 + .../v5wire/testdata/v5.1-whoareyou.txt | 9 + p2p/netutil/error.go | 8 + 24 files changed, 3220 insertions(+), 1411 deletions(-) create mode 100644 cmd/devp2p/README.md create mode 100644 cmd/devp2p/internal/v5test/discv5tests.go create mode 100644 cmd/devp2p/internal/v5test/framework.go delete mode 100644 p2p/discover/v5_encoding.go delete mode 100644 p2p/discover/v5_encoding_test.go create mode 100644 p2p/discover/v5wire/crypto.go create mode 100644 p2p/discover/v5wire/crypto_test.go create mode 100644 p2p/discover/v5wire/encoding.go create mode 100644 p2p/discover/v5wire/encoding_test.go create mode 100644 p2p/discover/v5wire/msg.go rename p2p/discover/{v5_session.go => v5wire/session.go} (58%) create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-ping-message.txt create mode 100644 p2p/discover/v5wire/testdata/v5.1-whoareyou.txt diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md new file mode 100644 index 0000000000..2763c75085 --- /dev/null +++ b/cmd/devp2p/README.md @@ -0,0 +1,86 @@ +# The devp2p command + +The devp2p command line tool is a utility for low-level peer-to-peer debugging and +protocol development purposes. It can do many things. + +### ENR Decoding + +Use `devp2p enrdump ` to verify and display an Ethereum Node Record. + +### Node Key Management + +The `devp2p key ...` command family deals with node key files. + +Run `devp2p key generate mynode.key` to create a new node key in the `mynode.key` file. + +Run `devp2p key to-enode mynode.key -ip 127.0.0.1 -tcp 30303` to create an enode:// URL +corresponding to the given node key and address information. + +### Maintaining DNS Discovery Node Lists + +The devp2p command can create and publish DNS discovery node lists. + +Run `devp2p dns sign ` to update the signature of a DNS discovery tree. + +Run `devp2p dns sync ` to download a complete DNS discovery tree. + +Run `devp2p dns to-cloudflare ` to publish a tree to CloudFlare DNS. + +Run `devp2p dns to-route53 ` to publish a tree to Amazon Route53. + +You can find more information about these commands in the [DNS Discovery Setup Guide][dns-tutorial]. + +### Discovery v4 Utilities + +The `devp2p discv4 ...` command family deals with the [Node Discovery v4][discv4] +protocol. + +Run `devp2p discv4 ping ` to ping a node. + +Run `devp2p discv4 resolve ` to find the most recent node record of a node in +the DHT. + +Run `devp2p discv4 crawl ` to create or update a JSON node set. + +### Discovery v5 Utilities + +The `devp2p discv5 ...` command family deals with the [Node Discovery v5][discv5] +protocol. This protocol is currently under active development. + +Run `devp2p discv5 ping ` to ping a node. + +Run `devp2p discv5 resolve ` to find the most recent node record of a node in +the discv5 DHT. + +Run `devp2p discv5 listen` to run a Discovery v5 node. + +Run `devp2p discv5 crawl ` to create or update a JSON node set containing +discv5 nodes. + +### Discovery Test Suites + +The devp2p command also contains interactive test suites for Discovery v4 and Discovery +v5. + +To run these tests against your implementation, you need to set up a networking +environment where two separate UDP listening addresses are available on the same machine. +The two listening addresses must also be routed such that they are able to reach the node +you want to test. + +For example, if you want to run the test on your local host, and the node under test is +also on the local host, you need to assign two IP addresses (or a larger range) to your +loopback interface. On macOS, this can be done by executing the following command: + + sudo ifconfig lo0 add 127.0.0.2 + +You can now run either test suite as follows: Start the node under test first, ensuring +that it won't talk to the Internet (i.e. disable bootstrapping). An easy way to prevent +unintended connections to the global DHT is listening on `127.0.0.1`. + +Now get the ENR of your node and store it in the `NODE` environment variable. + +Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0.0.2 $NODE`. + +[dns-tutorial]: https://geth.ethereum.org/docs/developers/dns-discovery-setup +[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md +[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 99b0957ab3..467c20deb5 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -286,7 +286,11 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { } usocket := socket.(*net.UDPConn) uaddr := socket.LocalAddr().(*net.UDPAddr) - ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + if uaddr.IP.IsUnspecified() { + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + } else { + ln.SetFallbackIP(uaddr.IP) + } ln.SetFallbackUDP(uaddr.Port) return usocket } @@ -294,7 +298,11 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { s := params.RinkebyBootnodes if ctx.IsSet(bootnodesFlag.Name) { - s = strings.Split(ctx.String(bootnodesFlag.Name), ",") + input := ctx.String(bootnodesFlag.Name) + if input == "" { + return nil, nil + } + s = strings.Split(input, ",") } nodes := make([]*enode.Node, len(s)) var err error diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index f871821ea2..1d7442144f 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -18,9 +18,13 @@ package main import ( "fmt" + "os" "time" + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" "gopkg.in/urfave/cli.v1" ) @@ -33,6 +37,7 @@ var ( discv5PingCommand, discv5ResolveCommand, discv5CrawlCommand, + discv5TestCommand, discv5ListenCommand, }, } @@ -53,6 +58,12 @@ var ( Action: discv5Crawl, Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, } + discv5TestCommand = cli.Command{ + Name: "test", + Usage: "Runs protocol tests against a node", + Action: discv5Test, + Flags: []cli.Flag{testPatternFlag, testListen1Flag, testListen2Flag}, + } discv5ListenCommand = cli.Command{ Name: "listen", Usage: "Runs a node", @@ -103,6 +114,30 @@ func discv5Crawl(ctx *cli.Context) error { return nil } +func discv5Test(ctx *cli.Context) error { + // Disable logging unless explicitly enabled. + if !ctx.GlobalIsSet("verbosity") && !ctx.GlobalIsSet("vmodule") { + log.Root().SetHandler(log.DiscardHandler()) + } + + // Filter and run test cases. + suite := &v5test.Suite{ + Dest: getNodeArg(ctx), + Listen1: ctx.String(testListen1Flag.Name), + Listen2: ctx.String(testListen2Flag.Name), + } + tests := suite.AllTests() + if ctx.IsSet(testPatternFlag.Name) { + tests = utesting.MatchTests(tests, ctx.String(testPatternFlag.Name)) + } + results := utesting.RunTests(tests, os.Stdout) + if fails := utesting.CountFailures(results); fails > 0 { + return fmt.Errorf("%v/%v tests passed.", len(tests)-fails, len(tests)) + } + fmt.Printf("%v/%v passed\n", len(tests), len(tests)) + return nil +} + func discv5Listen(ctx *cli.Context) error { disc := startV5(ctx) defer disc.Close() diff --git a/cmd/devp2p/internal/v5test/discv5tests.go b/cmd/devp2p/internal/v5test/discv5tests.go new file mode 100644 index 0000000000..7866498f73 --- /dev/null +++ b/cmd/devp2p/internal/v5test/discv5tests.go @@ -0,0 +1,377 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package v5test + +import ( + "bytes" + "net" + "sync" + "time" + + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/netutil" +) + +// Suite is the discv5 test suite. +type Suite struct { + Dest *enode.Node + Listen1, Listen2 string // listening addresses +} + +func (s *Suite) listen1(log logger) (*conn, net.PacketConn) { + c := newConn(s.Dest, log) + l := c.listen(s.Listen1) + return c, l +} + +func (s *Suite) listen2(log logger) (*conn, net.PacketConn, net.PacketConn) { + c := newConn(s.Dest, log) + l1, l2 := c.listen(s.Listen1), c.listen(s.Listen2) + return c, l1, l2 +} + +func (s *Suite) AllTests() []utesting.Test { + return []utesting.Test{ + {Name: "Ping", Fn: s.TestPing}, + {Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID}, + {Name: "PingMultiIP", Fn: s.TestPingMultiIP}, + {Name: "PingHandshakeInterrupted", Fn: s.TestPingHandshakeInterrupted}, + {Name: "TalkRequest", Fn: s.TestTalkRequest}, + {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, + {Name: "FindnodeResults", Fn: s.TestFindnodeResults}, + } +} + +// This test sends PING and expects a PONG response. +func (s *Suite) TestPing(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping, l1) + default: + t.Fatal("expected PONG, got", resp.Name()) + } +} + +func checkPong(t *utesting.T, pong *v5wire.Pong, ping *v5wire.Ping, c net.PacketConn) { + if !bytes.Equal(pong.ReqID, ping.ReqID) { + t.Fatalf("wrong request ID %x in PONG, want %x", pong.ReqID, ping.ReqID) + } + if !pong.ToIP.Equal(laddr(c).IP) { + t.Fatalf("wrong destination IP %v in PONG, want %v", pong.ToIP, laddr(c).IP) + } + if int(pong.ToPort) != laddr(c).Port { + t.Fatalf("wrong destination port %v in PONG, want %v", pong.ToPort, laddr(c).Port) + } +} + +// This test sends PING with a 9-byte request ID, which isn't allowed by the spec. +// The remote node should not respond. +func (s *Suite) TestPingLargeRequestID(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: make([]byte, 9)} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + t.Errorf("PONG response with unknown request ID %x", resp.ReqID) + case *readError: + if resp.err == v5wire.ErrInvalidReqID { + t.Error("response with oversized request ID") + } else if !netutil.IsTimeout(resp.err) { + t.Error(resp) + } + } +} + +// In this test, a session is established from one IP as usual. The session is then reused +// on another IP, which shouldn't work. The remote node should respond with WHOAREYOU for +// the attempt from a different IP. +func (s *Suite) TestPingMultiIP(t *utesting.T) { + conn, l1, l2 := s.listen2(t) + defer conn.close() + + // Create the session on l1. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + resp := conn.reqresp(l1, ping) + if resp.Kind() != v5wire.PongMsg { + t.Fatal("expected PONG, got", resp) + } + checkPong(t, resp.(*v5wire.Pong), ping, l1) + + // Send on l2. This reuses the session because there is only one codec. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l2, ping2, nil) + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l2).IP, laddr(l1).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + resp.Node = s.Dest + conn.write(l2, ping2, resp) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Catch the PONG on l2. + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l2) + default: + t.Fatal("expected PONG, got", resp) + } + + // Try on l1 again. + ping3 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping3, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l1).IP, laddr(l2).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// This test starts a handshake, but doesn't finish it and sends a second ordinary message +// packet instead of a handshake message packet. The remote node should respond with +// another WHOAREYOU challenge for the second packet. +func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // First PING triggers challenge. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for PING") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Send second PING. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l1) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// This test sends TALKREQ and expects an empty TALKRESP response. +func (s *Suite) TestTalkRequest(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // Non-empty request ID. + id := conn.nextReqID() + resp := conn.reqresp(l1, &v5wire.TalkRequest{ReqID: id, Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if !bytes.Equal(resp.ReqID, id) { + t.Fatalf("wrong request ID %x in TALKRESP, want %x", resp.ReqID, id) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } + + // Empty request ID. + resp = conn.reqresp(l1, &v5wire.TalkRequest{Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if len(resp.ReqID) > 0 { + t.Fatalf("wrong request ID %x in TALKRESP, want empty byte array", resp.ReqID) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } +} + +// This test checks that the remote node returns itself for FINDNODE with distance zero. +func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + nodes, err := conn.findnode(l1, []uint{0}) + if err != nil { + t.Fatal(err) + } + if len(nodes) != 1 { + t.Fatalf("remote returned more than one node for FINDNODE [0]") + } + if nodes[0].ID() != conn.remote.ID() { + t.Errorf("ID of response node is %v, want %v", nodes[0].ID(), conn.remote.ID()) + } +} + +// In this test, multiple nodes ping the node under test. After waiting for them to be +// accepted into the remote table, the test checks that they are returned by FINDNODE. +func (s *Suite) TestFindnodeResults(t *utesting.T) { + // Create bystanders. + nodes := make([]*bystander, 5) + added := make(chan enode.ID, len(nodes)) + for i := range nodes { + nodes[i] = newBystander(t, s, added) + defer nodes[i].close() + } + + // Get them added to the remote table. + timeout := 60 * time.Second + timeoutCh := time.After(timeout) + for count := 0; count < len(nodes); { + select { + case id := <-added: + t.Logf("bystander node %v added to remote table", id) + count++ + case <-timeoutCh: + t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes)) + t.Logf("this can happen if the node has a non-empty table from previous runs") + return + } + } + t.Logf("all %d bystander nodes were added", len(nodes)) + + // Collect our nodes by distance. + var dists []uint + expect := make(map[enode.ID]*enode.Node) + for _, bn := range nodes { + n := bn.conn.localNode.Node() + expect[n.ID()] = n + d := uint(enode.LogDist(n.ID(), s.Dest.ID())) + if !containsUint(dists, d) { + dists = append(dists, d) + } + } + + // Send FINDNODE for all distances. + conn, l1 := s.listen1(t) + defer conn.close() + foundNodes, err := conn.findnode(l1, dists) + if err != nil { + t.Fatal(err) + } + t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists) + for _, n := range foundNodes { + delete(expect, n.ID()) + } + if len(expect) > 0 { + t.Errorf("missing %d nodes in FINDNODE result", len(expect)) + t.Logf("this can happen if the test is run multiple times in quick succession") + t.Logf("and the remote node hasn't removed dead nodes from previous runs yet") + } else { + t.Logf("all %d expected nodes were returned", len(nodes)) + } +} + +// A bystander is a node whose only purpose is filling a spot in the remote table. +type bystander struct { + dest *enode.Node + conn *conn + l net.PacketConn + + addedCh chan enode.ID + done sync.WaitGroup +} + +func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander { + conn, l := s.listen1(t) + conn.setEndpoint(l) // bystander nodes need IP/port to get pinged + bn := &bystander{ + conn: conn, + l: l, + dest: s.Dest, + addedCh: added, + } + bn.done.Add(1) + go bn.loop() + return bn +} + +// id returns the node ID of the bystander. +func (bn *bystander) id() enode.ID { + return bn.conn.localNode.ID() +} + +// close shuts down loop. +func (bn *bystander) close() { + bn.conn.close() + bn.done.Wait() +} + +// loop answers packets from the remote node until quit. +func (bn *bystander) loop() { + defer bn.done.Done() + + var ( + lastPing time.Time + wasAdded bool + ) + for { + // Ping the remote node. + if !wasAdded && time.Since(lastPing) > 10*time.Second { + bn.conn.reqresp(bn.l, &v5wire.Ping{ + ReqID: bn.conn.nextReqID(), + ENRSeq: bn.dest.Seq(), + }) + lastPing = time.Now() + } + // Answer packets. + switch p := bn.conn.read(bn.l).(type) { + case *v5wire.Ping: + bn.conn.write(bn.l, &v5wire.Pong{ + ReqID: p.ReqID, + ENRSeq: bn.conn.localNode.Seq(), + ToIP: bn.dest.IP(), + ToPort: uint16(bn.dest.UDP()), + }, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.Findnode: + bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, Total: 1}, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.TalkRequest: + bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil) + case *readError: + if !netutil.IsTemporaryError(p.err) { + bn.conn.logf("shutting down: %v", p.err) + return + } + } + } +} + +func (bn *bystander) notifyAdded() { + if bn.addedCh != nil { + bn.addedCh <- bn.id() + bn.addedCh = nil + } +} diff --git a/cmd/devp2p/internal/v5test/framework.go b/cmd/devp2p/internal/v5test/framework.go new file mode 100644 index 0000000000..9eac37520f --- /dev/null +++ b/cmd/devp2p/internal/v5test/framework.go @@ -0,0 +1,263 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package v5test + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" +) + +// readError represents an error during packet reading. +// This exists to facilitate type-switching on the result of conn.read. +type readError struct { + err error +} + +func (p *readError) Kind() byte { return 99 } +func (p *readError) Name() string { return fmt.Sprintf("error: %v", p.err) } +func (p *readError) Error() string { return p.err.Error() } +func (p *readError) Unwrap() error { return p.err } +func (p *readError) RequestID() []byte { return nil } +func (p *readError) SetRequestID([]byte) {} + +// readErrorf creates a readError with the given text. +func readErrorf(format string, args ...interface{}) *readError { + return &readError{fmt.Errorf(format, args...)} +} + +// This is the response timeout used in tests. +const waitTime = 300 * time.Millisecond + +// conn is a connection to the node under test. +type conn struct { + localNode *enode.LocalNode + localKey *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr + listeners []net.PacketConn + + log logger + codec *v5wire.Codec + lastRequest v5wire.Packet + lastChallenge *v5wire.Whoareyou + idCounter uint32 +} + +type logger interface { + Logf(string, ...interface{}) +} + +// newConn sets up a connection to the given node. +func newConn(dest *enode.Node, log logger) *conn { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + db, err := enode.OpenDB("") + if err != nil { + panic(err) + } + ln := enode.NewLocalNode(db, key) + + return &conn{ + localKey: key, + localNode: ln, + remote: dest, + remoteAddr: &net.UDPAddr{IP: dest.IP(), Port: dest.UDP()}, + codec: v5wire.NewCodec(ln, key, mclock.System{}), + log: log, + } +} + +func (tc *conn) setEndpoint(c net.PacketConn) { + tc.localNode.SetStaticIP(laddr(c).IP) + tc.localNode.SetFallbackUDP(laddr(c).Port) +} + +func (tc *conn) listen(ip string) net.PacketConn { + l, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", ip)) + if err != nil { + panic(err) + } + tc.listeners = append(tc.listeners, l) + return l +} + +// close shuts down all listeners and the local node. +func (tc *conn) close() { + for _, l := range tc.listeners { + l.Close() + } + tc.localNode.Database().Close() +} + +// nextReqID creates a request id. +func (tc *conn) nextReqID() []byte { + id := make([]byte, 4) + tc.idCounter++ + binary.BigEndian.PutUint32(id, tc.idCounter) + return id +} + +// reqresp performs a request/response interaction on the given connection. +// The request is retried if a handshake is requested. +func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet { + reqnonce := tc.write(c, req, nil) + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + if resp.Nonce != reqnonce { + return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:]) + } + resp.Node = tc.remote + tc.write(c, req, resp) + return tc.read(c) + default: + return resp + } +} + +// findnode sends a FINDNODE request and waits for its responses. +func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) { + var ( + findnode = &v5wire.Findnode{ReqID: tc.nextReqID(), Distances: dists} + reqnonce = tc.write(c, findnode, nil) + first = true + total uint8 + results []*enode.Node + ) + for n := 1; n > 0; { + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + // Handle handshake. + if resp.Nonce == reqnonce { + resp.Node = tc.remote + tc.write(c, findnode, resp) + } else { + return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:]) + } + case *v5wire.Ping: + // Handle ping from remote. + tc.write(c, &v5wire.Pong{ + ReqID: resp.ReqID, + ENRSeq: tc.localNode.Seq(), + }, nil) + case *v5wire.Nodes: + // Got NODES! Check request ID. + if !bytes.Equal(resp.ReqID, findnode.ReqID) { + return nil, fmt.Errorf("NODES response has wrong request id %x", resp.ReqID) + } + // Check total count. It should be greater than one + // and needs to be the same across all responses. + if first { + if resp.Total == 0 || resp.Total > 6 { + return nil, fmt.Errorf("invalid NODES response 'total' %d (not in (0,7))", resp.Total) + } + total = resp.Total + n = int(total) - 1 + first = false + } else { + n-- + if resp.Total != total { + return nil, fmt.Errorf("invalid NODES response 'total' %d (!= %d)", resp.Total, total) + } + } + // Check nodes. + nodes, err := checkRecords(resp.Nodes) + if err != nil { + return nil, fmt.Errorf("invalid node in NODES response: %v", err) + } + results = append(results, nodes...) + default: + return nil, fmt.Errorf("expected NODES, got %v", resp) + } + } + return results, nil +} + +// write sends a packet on the given connection. +func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce { + packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err)) + } + if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil { + tc.logf("Can't send %s: %v", p.Name(), err) + } else { + tc.logf(">> %s", p.Name()) + } + return nonce +} + +// read waits for an incoming packet on the given connection. +func (tc *conn) read(c net.PacketConn) v5wire.Packet { + buf := make([]byte, 1280) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return &readError{err} + } + n, fromAddr, err := c.ReadFrom(buf) + if err != nil { + return &readError{err} + } + _, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String()) + if err != nil { + return &readError{err} + } + tc.logf("<< %s", p.Name()) + return p +} + +// logf prints to the test log. +func (tc *conn) logf(format string, args ...interface{}) { + if tc.log != nil { + tc.log.Logf("(%s) %s", tc.localNode.ID().TerminalString(), fmt.Sprintf(format, args...)) + } +} + +func laddr(c net.PacketConn) *net.UDPAddr { + return c.LocalAddr().(*net.UDPAddr) +} + +func checkRecords(records []*enr.Record) ([]*enode.Node, error) { + nodes := make([]*enode.Node, len(records)) + for i := range records { + n, err := enode.New(enode.ValidSchemes, records[i]) + if err != nil { + return nil, err + } + nodes[i] = n + } + return nodes, nil +} + +func containsUint(ints []uint, x uint) bool { + for i := range ints { + if ints[i] == x { + return true + } + } + return false +} diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go index 23c748cae9..4de0ecf99a 100644 --- a/internal/utesting/utesting.go +++ b/internal/utesting/utesting.go @@ -65,10 +65,17 @@ func MatchTests(tests []Test, expr string) []Test { func RunTests(tests []Test, report io.Writer) []Result { results := make([]Result, len(tests)) for i, test := range tests { + var output io.Writer + buffer := new(bytes.Buffer) + output = buffer + if report != nil { + output = io.MultiWriter(buffer, report) + } start := time.Now() results[i].Name = test.Name - results[i].Failed, results[i].Output = Run(test) + results[i].Failed = run(test, output) results[i].Duration = time.Since(start) + results[i].Output = buffer.String() if report != nil { printResult(results[i], report) } @@ -80,7 +87,6 @@ func printResult(r Result, w io.Writer) { pd := r.Duration.Truncate(100 * time.Microsecond) if r.Failed { fmt.Fprintf(w, "-- FAIL %s (%v)\n", r.Name, pd) - fmt.Fprintln(w, r.Output) } else { fmt.Fprintf(w, "-- OK %s (%v)\n", r.Name, pd) } @@ -99,7 +105,13 @@ func CountFailures(rr []Result) int { // Run executes a single test. func Run(test Test) (bool, string) { - t := new(T) + output := new(bytes.Buffer) + failed := run(test, output) + return failed, output.String() +} + +func run(test Test, output io.Writer) bool { + t := &T{output: output} done := make(chan struct{}) go func() { defer close(done) @@ -114,7 +126,7 @@ func Run(test Test) (bool, string) { test.Fn(t) }() <-done - return t.failed, t.output.String() + return t.failed } // T is the value given to the test function. The test can signal failures @@ -122,7 +134,7 @@ func Run(test Test) (bool, string) { type T struct { mu sync.Mutex failed bool - output bytes.Buffer + output io.Writer } // FailNow marks the test as having failed and stops its execution by calling @@ -151,7 +163,7 @@ func (t *T) Failed() bool { func (t *T) Log(vs ...interface{}) { t.mu.Lock() defer t.mu.Unlock() - fmt.Fprintln(&t.output, vs...) + fmt.Fprintln(t.output, vs...) } // Logf formats its arguments according to the format, analogous to Printf, and records @@ -162,7 +174,7 @@ func (t *T) Logf(format string, vs ...interface{}) { if len(format) == 0 || format[len(format)-1] != '\n' { format += "\n" } - fmt.Fprintf(&t.output, format, vs...) + fmt.Fprintf(t.output, format, vs...) } // Error is equivalent to Log followed by Fail. diff --git a/p2p/discover/node.go b/p2p/discover/node.go index e635c64ac9..9ffe101ccf 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -46,7 +46,10 @@ func encodePubkey(key *ecdsa.PublicKey) encPubkey { return e } -func decodePubkey(curve elliptic.Curve, e encPubkey) (*ecdsa.PublicKey, error) { +func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { + if len(e) != len(encPubkey{}) { + return nil, errors.New("wrong size public key data") + } p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} half := len(e) / 2 p.X.SetBytes(e[:half]) diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 44b62e751b..47a2e7ac3c 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -146,7 +146,6 @@ func (t *pingRecorder) updateRecord(n *enode.Node) { func (t *pingRecorder) Self() *enode.Node { return nullNode } func (t *pingRecorder) lookupSelf() []*enode.Node { return nil } func (t *pingRecorder) lookupRandom() []*enode.Node { return nil } -func (t *pingRecorder) close() {} // ping simulates a ping request. func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) { @@ -188,15 +187,16 @@ func hasDuplicates(slice []*node) bool { return false } +// checkNodesEqual checks whether the two given node lists contain the same nodes. func checkNodesEqual(got, want []*enode.Node) error { if len(got) == len(want) { for i := range got { if !nodeEqual(got[i], want[i]) { goto NotEqual } - return nil } } + return nil NotEqual: output := new(bytes.Buffer) @@ -227,6 +227,7 @@ func sortedByDistanceTo(distbase enode.ID, slice []*node) bool { }) } +// hexEncPrivkey decodes h as a private key. func hexEncPrivkey(h string) *ecdsa.PrivateKey { b, err := hex.DecodeString(h) if err != nil { @@ -239,6 +240,7 @@ func hexEncPrivkey(h string) *ecdsa.PrivateKey { return key } +// hexEncPubkey decodes h as a public key. func hexEncPubkey(h string) (ret encPubkey) { b, err := hex.DecodeString(h) if err != nil { diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index 2009385262..a00de9ca18 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -34,7 +34,7 @@ func TestUDPv4_Lookup(t *testing.T) { test := newUDPTest(t) // Lookup on empty table returns no nodes. - targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target) + targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target[:]) if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } @@ -279,17 +279,21 @@ func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node { return result } -func (tn *preminedTestnet) neighborsAtDistance(base *enode.Node, distance uint, elems int) []*enode.Node { - nodes := nodesByDistance{target: base.ID()} +func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node { + var result []*enode.Node for d := range lookupTestnet.dists { for i := range lookupTestnet.dists[d] { n := lookupTestnet.node(d, i) - if uint(enode.LogDist(n.ID(), base.ID())) == distance { - nodes.push(wrapNode(n), elems) + d := enode.LogDist(base.ID(), n.ID()) + if containsUint(uint(d), distances) { + result = append(result, n) + if len(result) >= elems { + return result + } } } } - return unwrapNodes(nodes.entries) + return result } func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { diff --git a/p2p/discover/v5_encoding.go b/p2p/discover/v5_encoding.go deleted file mode 100644 index c1a790dd2c..0000000000 --- a/p2p/discover/v5_encoding.go +++ /dev/null @@ -1,659 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discover - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/ecdsa" - "crypto/elliptic" - crand "crypto/rand" - "crypto/sha256" - "errors" - "fmt" - "hash" - "net" - "time" - - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/hkdf" -) - -// TODO concurrent WHOAREYOU tie-breaker -// TODO deal with WHOAREYOU amplification factor (min packet size?) -// TODO add counter to nonce -// TODO rehandshake after X packets - -// Discovery v5 packet types. -const ( - p_pingV5 byte = iota + 1 - p_pongV5 - p_findnodeV5 - p_nodesV5 - p_requestTicketV5 - p_ticketV5 - p_regtopicV5 - p_regconfirmationV5 - p_topicqueryV5 - p_unknownV5 = byte(255) // any non-decryptable packet - p_whoareyouV5 = byte(254) // the WHOAREYOU packet -) - -// Discovery v5 packet structures. -type ( - // unknownV5 represents any packet that can't be decrypted. - unknownV5 struct { - AuthTag []byte - } - - // WHOAREYOU contains the handshake challenge. - whoareyouV5 struct { - AuthTag []byte - IDNonce [32]byte // To be signed by recipient. - RecordSeq uint64 // ENR sequence number of recipient - - node *enode.Node - sent mclock.AbsTime - } - - // PING is sent during liveness checks. - pingV5 struct { - ReqID []byte - ENRSeq uint64 - } - - // PONG is the reply to PING. - pongV5 struct { - ReqID []byte - ENRSeq uint64 - ToIP net.IP // These fields should mirror the UDP envelope address of the ping - ToPort uint16 // packet, which provides a way to discover the the external address (after NAT). - } - - // FINDNODE is a query for nodes in the given bucket. - findnodeV5 struct { - ReqID []byte - Distance uint - } - - // NODES is the reply to FINDNODE and TOPICQUERY. - nodesV5 struct { - ReqID []byte - Total uint8 - Nodes []*enr.Record - } - - // REQUESTTICKET requests a ticket for a topic queue. - requestTicketV5 struct { - ReqID []byte - Topic []byte - } - - // TICKET is the response to REQUESTTICKET. - ticketV5 struct { - ReqID []byte - Ticket []byte - } - - // REGTOPIC registers the sender in a topic queue using a ticket. - regtopicV5 struct { - ReqID []byte - Ticket []byte - ENR *enr.Record - } - - // REGCONFIRMATION is the reply to REGTOPIC. - regconfirmationV5 struct { - ReqID []byte - Registered bool - } - - // TOPICQUERY asks for nodes with the given topic. - topicqueryV5 struct { - ReqID []byte - Topic []byte - } -) - -const ( - // Encryption/authentication parameters. - authSchemeName = "gcm" - aesKeySize = 16 - gcmNonceSize = 12 - idNoncePrefix = "discovery-id-nonce" - handshakeTimeout = time.Second -) - -var ( - errTooShort = errors.New("packet too short") - errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake") - errHandshakeNonceMismatch = errors.New("wrong nonce in auth response") - errInvalidAuthKey = errors.New("invalid ephemeral pubkey") - errUnknownAuthScheme = errors.New("unknown auth scheme in handshake") - errNoRecord = errors.New("expected ENR in handshake but none sent") - errInvalidNonceSig = errors.New("invalid ID nonce signature") - zeroNonce = make([]byte, gcmNonceSize) -) - -// wireCodec encodes and decodes discovery v5 packets. -type wireCodec struct { - sha256 hash.Hash - localnode *enode.LocalNode - privkey *ecdsa.PrivateKey - myChtagHash enode.ID - myWhoareyouMagic []byte - - sc *sessionCache -} - -type handshakeSecrets struct { - writeKey, readKey, authRespKey []byte -} - -type authHeader struct { - authHeaderList - isHandshake bool -} - -type authHeaderList struct { - Auth []byte // authentication info of packet - IDNonce [32]byte // IDNonce of WHOAREYOU - Scheme string // name of encryption/authentication scheme - EphemeralKey []byte // ephemeral public key - Response []byte // encrypted authResponse -} - -type authResponse struct { - Version uint - Signature []byte - Record *enr.Record `rlp:"nil"` // sender's record -} - -func (h *authHeader) DecodeRLP(r *rlp.Stream) error { - k, _, err := r.Kind() - if err != nil { - return err - } - if k == rlp.Byte || k == rlp.String { - return r.Decode(&h.Auth) - } - h.isHandshake = true - return r.Decode(&h.authHeaderList) -} - -// ephemeralKey decodes the ephemeral public key in the header. -func (h *authHeaderList) ephemeralKey(curve elliptic.Curve) *ecdsa.PublicKey { - var key encPubkey - copy(key[:], h.EphemeralKey) - pubkey, _ := decodePubkey(curve, key) - return pubkey -} - -// newWireCodec creates a wire codec. -func newWireCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *wireCodec { - c := &wireCodec{ - sha256: sha256.New(), - localnode: ln, - privkey: key, - sc: newSessionCache(1024, clock), - } - // Create magic strings for packet matching. - self := ln.ID() - c.myWhoareyouMagic = c.sha256sum(self[:], []byte("WHOAREYOU")) - copy(c.myChtagHash[:], c.sha256sum(self[:])) - return c -} - -// encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The -// 'challenge' parameter should be the most recently received WHOAREYOU packet from that -// node. -func (c *wireCodec) encode(id enode.ID, addr string, packet packetV5, challenge *whoareyouV5) ([]byte, []byte, error) { - if packet.kind() == p_whoareyouV5 { - p := packet.(*whoareyouV5) - enc, err := c.encodeWhoareyou(id, p) - if err == nil { - c.sc.storeSentHandshake(id, addr, p) - } - return enc, nil, err - } - // Ensure calling code sets node if needed. - if challenge != nil && challenge.node == nil { - panic("BUG: missing challenge.node in encode") - } - writeKey := c.sc.writeKey(id, addr) - if writeKey != nil || challenge != nil { - return c.encodeEncrypted(id, addr, packet, writeKey, challenge) - } - return c.encodeRandom(id) -} - -// encodeRandom encodes a random packet. -func (c *wireCodec) encodeRandom(toID enode.ID) ([]byte, []byte, error) { - tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID()) - r := make([]byte, 44) // TODO randomize size - if _, err := crand.Read(r); err != nil { - return nil, nil, err - } - nonce := make([]byte, gcmNonceSize) - if _, err := crand.Read(nonce); err != nil { - return nil, nil, fmt.Errorf("can't get random data: %v", err) - } - b := new(bytes.Buffer) - b.Write(tag[:]) - rlp.Encode(b, nonce) - b.Write(r) - return b.Bytes(), nonce, nil -} - -// encodeWhoareyou encodes WHOAREYOU. -func (c *wireCodec) encodeWhoareyou(toID enode.ID, packet *whoareyouV5) ([]byte, error) { - // Sanity check node field to catch misbehaving callers. - if packet.RecordSeq > 0 && packet.node == nil { - panic("BUG: missing node in whoareyouV5 with non-zero seq") - } - b := new(bytes.Buffer) - b.Write(c.sha256sum(toID[:], []byte("WHOAREYOU"))) - err := rlp.Encode(b, packet) - return b.Bytes(), err -} - -// encodeEncrypted encodes an encrypted packet. -func (c *wireCodec) encodeEncrypted(toID enode.ID, toAddr string, packet packetV5, writeKey []byte, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) { - nonce := make([]byte, gcmNonceSize) - if _, err := crand.Read(nonce); err != nil { - return nil, nil, fmt.Errorf("can't get random data: %v", err) - } - - var headEnc []byte - if challenge == nil { - // Regular packet, use existing key and simply encode nonce. - headEnc, _ = rlp.EncodeToBytes(nonce) - } else { - // We're answering WHOAREYOU, generate new keys and encrypt with those. - header, sec, err := c.makeAuthHeader(nonce, challenge) - if err != nil { - return nil, nil, err - } - if headEnc, err = rlp.EncodeToBytes(header); err != nil { - return nil, nil, err - } - c.sc.storeNewSession(toID, toAddr, sec.readKey, sec.writeKey) - writeKey = sec.writeKey - } - - // Encode the packet. - body := new(bytes.Buffer) - body.WriteByte(packet.kind()) - if err := rlp.Encode(body, packet); err != nil { - return nil, nil, err - } - tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID()) - headsize := len(tag) + len(headEnc) - headbuf := make([]byte, headsize) - copy(headbuf[:], tag[:]) - copy(headbuf[len(tag):], headEnc) - - // Encrypt the body. - enc, err = encryptGCM(headbuf, writeKey, nonce, body.Bytes(), tag[:]) - return enc, nonce, err -} - -// encodeAuthHeader creates the auth header on a call packet following WHOAREYOU. -func (c *wireCodec) makeAuthHeader(nonce []byte, challenge *whoareyouV5) (*authHeaderList, *handshakeSecrets, error) { - resp := &authResponse{Version: 5} - - // Add our record to response if it's newer than what remote - // side has. - ln := c.localnode.Node() - if challenge.RecordSeq < ln.Seq() { - resp.Record = ln.Record() - } - - // Create the ephemeral key. This needs to be first because the - // key is part of the ID nonce signature. - var remotePubkey = new(ecdsa.PublicKey) - if err := challenge.node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { - return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") - } - ephkey, err := crypto.GenerateKey() - if err != nil { - return nil, nil, fmt.Errorf("can't generate ephemeral key") - } - ephpubkey := encodePubkey(&ephkey.PublicKey) - - // Add ID nonce signature to response. - idsig, err := c.signIDNonce(challenge.IDNonce[:], ephpubkey[:]) - if err != nil { - return nil, nil, fmt.Errorf("can't sign: %v", err) - } - resp.Signature = idsig - - // Create session keys. - sec := c.deriveKeys(c.localnode.ID(), challenge.node.ID(), ephkey, remotePubkey, challenge) - if sec == nil { - return nil, nil, fmt.Errorf("key derivation failed") - } - - // Encrypt the authentication response and assemble the auth header. - respRLP, err := rlp.EncodeToBytes(resp) - if err != nil { - return nil, nil, fmt.Errorf("can't encode auth response: %v", err) - } - respEnc, err := encryptGCM(nil, sec.authRespKey, zeroNonce, respRLP, nil) - if err != nil { - return nil, nil, fmt.Errorf("can't encrypt auth response: %v", err) - } - head := &authHeaderList{ - Auth: nonce, - Scheme: authSchemeName, - IDNonce: challenge.IDNonce, - EphemeralKey: ephpubkey[:], - Response: respEnc, - } - return head, sec, err -} - -// deriveKeys generates session keys using elliptic-curve Diffie-Hellman key agreement. -func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, challenge *whoareyouV5) *handshakeSecrets { - eph := ecdh(priv, pub) - if eph == nil { - return nil - } - - info := []byte("discovery v5 key agreement") - info = append(info, n1[:]...) - info = append(info, n2[:]...) - kdf := hkdf.New(sha256.New, eph, challenge.IDNonce[:], info) - sec := handshakeSecrets{ - writeKey: make([]byte, aesKeySize), - readKey: make([]byte, aesKeySize), - authRespKey: make([]byte, aesKeySize), - } - kdf.Read(sec.writeKey) - kdf.Read(sec.readKey) - kdf.Read(sec.authRespKey) - for i := range eph { - eph[i] = 0 - } - return &sec -} - -// signIDNonce creates the ID nonce signature. -func (c *wireCodec) signIDNonce(nonce, ephkey []byte) ([]byte, error) { - idsig, err := crypto.Sign(c.idNonceHash(nonce, ephkey), c.privkey) - if err != nil { - return nil, fmt.Errorf("can't sign: %v", err) - } - return idsig[:len(idsig)-1], nil // remove recovery ID -} - -// idNonceHash computes the hash of id nonce with prefix. -func (c *wireCodec) idNonceHash(nonce, ephkey []byte) []byte { - h := c.sha256reset() - h.Write([]byte(idNoncePrefix)) - h.Write(nonce) - h.Write(ephkey) - return h.Sum(nil) -} - -// decode decodes a discovery packet. -func (c *wireCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) { - // Delete timed-out handshakes. This must happen before decoding to avoid - // processing the same handshake twice. - c.sc.handshakeGC() - - if len(input) < 32 { - return enode.ID{}, nil, nil, errTooShort - } - if bytes.HasPrefix(input, c.myWhoareyouMagic) { - p, err := c.decodeWhoareyou(input) - return enode.ID{}, nil, p, err - } - sender := xorTag(input[:32], c.myChtagHash) - p, n, err := c.decodeEncrypted(sender, addr, input) - return sender, n, p, err -} - -// decodeWhoareyou decode a WHOAREYOU packet. -func (c *wireCodec) decodeWhoareyou(input []byte) (packetV5, error) { - packet := new(whoareyouV5) - err := rlp.DecodeBytes(input[32:], packet) - return packet, err -} - -// decodeEncrypted decodes an encrypted discovery packet. -func (c *wireCodec) decodeEncrypted(fromID enode.ID, fromAddr string, input []byte) (packetV5, *enode.Node, error) { - // Decode packet header. - var head authHeader - r := bytes.NewReader(input[32:]) - err := rlp.Decode(r, &head) - if err != nil { - return nil, nil, err - } - - // Decrypt and process auth response. - readKey, node, err := c.decodeAuth(fromID, fromAddr, &head) - if err != nil { - return nil, nil, err - } - - // Decrypt and decode the packet body. - headsize := len(input) - r.Len() - bodyEnc := input[headsize:] - body, err := decryptGCM(readKey, head.Auth, bodyEnc, input[:32]) - if err != nil { - if !head.isHandshake { - // Can't decrypt, start handshake. - return &unknownV5{AuthTag: head.Auth}, nil, nil - } - return nil, nil, fmt.Errorf("handshake failed: %v", err) - } - if len(body) == 0 { - return nil, nil, errTooShort - } - p, err := decodePacketBodyV5(body[0], body[1:]) - return p, node, err -} - -// decodeAuth processes an auth header. -func (c *wireCodec) decodeAuth(fromID enode.ID, fromAddr string, head *authHeader) ([]byte, *enode.Node, error) { - if !head.isHandshake { - return c.sc.readKey(fromID, fromAddr), nil, nil - } - - // Remote is attempting handshake. Verify against our last WHOAREYOU. - challenge := c.sc.getHandshake(fromID, fromAddr) - if challenge == nil { - return nil, nil, errUnexpectedHandshake - } - if head.IDNonce != challenge.IDNonce { - return nil, nil, errHandshakeNonceMismatch - } - sec, n, err := c.decodeAuthResp(fromID, fromAddr, &head.authHeaderList, challenge) - if err != nil { - return nil, n, err - } - // Swap keys to match remote. - sec.readKey, sec.writeKey = sec.writeKey, sec.readKey - c.sc.storeNewSession(fromID, fromAddr, sec.readKey, sec.writeKey) - c.sc.deleteHandshake(fromID, fromAddr) - return sec.readKey, n, err -} - -// decodeAuthResp decodes and verifies an authentication response. -func (c *wireCodec) decodeAuthResp(fromID enode.ID, fromAddr string, head *authHeaderList, challenge *whoareyouV5) (*handshakeSecrets, *enode.Node, error) { - // Decrypt / decode the response. - if head.Scheme != authSchemeName { - return nil, nil, errUnknownAuthScheme - } - ephkey := head.ephemeralKey(c.privkey.Curve) - if ephkey == nil { - return nil, nil, errInvalidAuthKey - } - sec := c.deriveKeys(fromID, c.localnode.ID(), c.privkey, ephkey, challenge) - respPT, err := decryptGCM(sec.authRespKey, zeroNonce, head.Response, nil) - if err != nil { - return nil, nil, fmt.Errorf("can't decrypt auth response header: %v", err) - } - var resp authResponse - if err := rlp.DecodeBytes(respPT, &resp); err != nil { - return nil, nil, fmt.Errorf("invalid auth response: %v", err) - } - - // Verify response node record. The remote node should include the record - // if we don't have one or if ours is older than the latest version. - node := challenge.node - if resp.Record != nil { - if node == nil || node.Seq() < resp.Record.Seq() { - n, err := enode.New(enode.ValidSchemes, resp.Record) - if err != nil { - return nil, nil, fmt.Errorf("invalid node record: %v", err) - } - if n.ID() != fromID { - return nil, nil, fmt.Errorf("record in auth respose has wrong ID: %v", n.ID()) - } - node = n - } - } - if node == nil { - return nil, nil, errNoRecord - } - - // Verify ID nonce signature. - err = c.verifyIDSignature(challenge.IDNonce[:], head.EphemeralKey, resp.Signature, node) - if err != nil { - return nil, nil, err - } - return sec, node, nil -} - -// verifyIDSignature checks that signature over idnonce was made by the node with given record. -func (c *wireCodec) verifyIDSignature(nonce, ephkey, sig []byte, n *enode.Node) error { - switch idscheme := n.Record().IdentityScheme(); idscheme { - case "v4": - var pk ecdsa.PublicKey - n.Load((*enode.Secp256k1)(&pk)) // cannot fail because record is valid - if !crypto.VerifySignature(crypto.FromECDSAPub(&pk), c.idNonceHash(nonce, ephkey), sig) { - return errInvalidNonceSig - } - return nil - default: - return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) - } -} - -// decodePacketBody decodes the body of an encrypted discovery packet. -func decodePacketBodyV5(ptype byte, body []byte) (packetV5, error) { - var dec packetV5 - switch ptype { - case p_pingV5: - dec = new(pingV5) - case p_pongV5: - dec = new(pongV5) - case p_findnodeV5: - dec = new(findnodeV5) - case p_nodesV5: - dec = new(nodesV5) - case p_requestTicketV5: - dec = new(requestTicketV5) - case p_ticketV5: - dec = new(ticketV5) - case p_regtopicV5: - dec = new(regtopicV5) - case p_regconfirmationV5: - dec = new(regconfirmationV5) - case p_topicqueryV5: - dec = new(topicqueryV5) - default: - return nil, fmt.Errorf("unknown packet type %d", ptype) - } - if err := rlp.DecodeBytes(body, dec); err != nil { - return nil, err - } - return dec, nil -} - -// sha256reset returns the shared hash instance. -func (c *wireCodec) sha256reset() hash.Hash { - c.sha256.Reset() - return c.sha256 -} - -// sha256sum computes sha256 on the concatenation of inputs. -func (c *wireCodec) sha256sum(inputs ...[]byte) []byte { - c.sha256.Reset() - for _, b := range inputs { - c.sha256.Write(b) - } - return c.sha256.Sum(nil) -} - -func xorTag(a []byte, b enode.ID) enode.ID { - var r enode.ID - for i := range r { - r[i] = a[i] ^ b[i] - } - return r -} - -// ecdh creates a shared secret. -func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { - secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) - if secX == nil { - return nil - } - sec := make([]byte, 33) - sec[0] = 0x02 | byte(secY.Bit(0)) - math.ReadBits(secX, sec[1:]) - return sec -} - -// encryptGCM encrypts pt using AES-GCM with the given key and nonce. -func encryptGCM(dest, key, nonce, pt, authData []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - panic(fmt.Errorf("can't create block cipher: %v", err)) - } - aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) - if err != nil { - panic(fmt.Errorf("can't create GCM: %v", err)) - } - return aesgcm.Seal(dest, nonce, pt, authData), nil -} - -// decryptGCM decrypts ct using AES-GCM with the given key and nonce. -func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, fmt.Errorf("can't create block cipher: %v", err) - } - if len(nonce) != gcmNonceSize { - return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) - } - aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) - if err != nil { - return nil, fmt.Errorf("can't create GCM: %v", err) - } - pt := make([]byte, 0, len(ct)) - return aesgcm.Open(pt, nonce, ct, authData) -} diff --git a/p2p/discover/v5_encoding_test.go b/p2p/discover/v5_encoding_test.go deleted file mode 100644 index 77e6bae6ae..0000000000 --- a/p2p/discover/v5_encoding_test.go +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discover - -import ( - "bytes" - "crypto/ecdsa" - "encoding/hex" - "fmt" - "net" - "reflect" - "testing" - - "github.com/davecgh/go-spew/spew" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -var ( - testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f") - testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628") - testIDnonce = [32]byte{5, 6, 7, 8, 9, 10, 11, 12} -) - -func TestDeriveKeysV5(t *testing.T) { - t.Parallel() - - var ( - n1 = enode.ID{1} - n2 = enode.ID{2} - challenge = &whoareyouV5{} - db, _ = enode.OpenDB("") - ln = enode.NewLocalNode(db, testKeyA) - c = newWireCodec(ln, testKeyA, mclock.System{}) - ) - defer db.Close() - - sec1 := c.deriveKeys(n1, n2, testKeyA, &testKeyB.PublicKey, challenge) - sec2 := c.deriveKeys(n1, n2, testKeyB, &testKeyA.PublicKey, challenge) - if sec1 == nil || sec2 == nil { - t.Fatal("key agreement failed") - } - if !reflect.DeepEqual(sec1, sec2) { - t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) - } -} - -// This test checks the basic handshake flow where A talks to B and A has no secrets. -func TestHandshakeV5(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - // A -> B RANDOM PACKET - packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - resp := net.nodeB.expectDecode(t, p_unknownV5, packet) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{ - AuthTag: resp.(*unknownV5).AuthTag, - IDNonce: testIDnonce, - RecordSeq: 0, - } - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE - findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - if len(net.nodeB.c.sc.handshakes) > 0 { - t.Fatalf("node B didn't remove handshake from challenge map") - } - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// This test checks that handshake attempts are removed within the timeout. -func TestHandshakeV5_timeout(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - // A -> B RANDOM PACKET - packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - resp := net.nodeB.expectDecode(t, p_unknownV5, packet) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{ - AuthTag: resp.(*unknownV5).AuthTag, - IDNonce: testIDnonce, - RecordSeq: 0, - } - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE after timeout - net.clock.Run(handshakeTimeout + 1) - findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) -} - -// This test checks handshake behavior when no record is sent in the auth response. -func TestHandshakeV5_norecord(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - // A -> B RANDOM PACKET - packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - resp := net.nodeB.expectDecode(t, p_unknownV5, packet) - - // A <- B WHOAREYOU - nodeA := net.nodeA.n() - if nodeA.Seq() == 0 { - t.Fatal("need non-zero sequence number") - } - challenge := &whoareyouV5{ - AuthTag: resp.(*unknownV5).AuthTag, - IDNonce: testIDnonce, - RecordSeq: nodeA.Seq(), - node: nodeA, - } - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE - findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// In this test, A tries to send FINDNODE with existing secrets but B doesn't know -// anything about A. -func TestHandshakeV5_rekey(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - initKeys := &handshakeSecrets{ - readKey: []byte("BBBBBBBBBBBBBBBB"), - writeKey: []byte("AAAAAAAAAAAAAAAA"), - } - net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeys.readKey, initKeys.writeKey) - - // A -> B FINDNODE (encrypted with zero keys) - findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) - net.nodeB.expectDecode(t, p_unknownV5, findnode) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce} - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // Check that new keys haven't been stored yet. - if s := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()); !bytes.Equal(s.writeKey, initKeys.writeKey) || !bytes.Equal(s.readKey, initKeys.readKey) { - t.Fatal("node A stored keys too early") - } - if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { - t.Fatal("node B stored keys too early") - } - - // A -> B FINDNODE encrypted with new keys - findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// In this test A and B have different keys before the handshake. -func TestHandshakeV5_rekey2(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - initKeysA := &handshakeSecrets{ - readKey: []byte("BBBBBBBBBBBBBBBB"), - writeKey: []byte("AAAAAAAAAAAAAAAA"), - } - initKeysB := &handshakeSecrets{ - readKey: []byte("CCCCCCCCCCCCCCCC"), - writeKey: []byte("DDDDDDDDDDDDDDDD"), - } - net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA.readKey, initKeysA.writeKey) - net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB.readKey, initKeysA.writeKey) - - // A -> B FINDNODE encrypted with initKeysA - findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{Distance: 3}) - net.nodeB.expectDecode(t, p_unknownV5, findnode) - - // A <- B WHOAREYOU - challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce} - whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) - net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) - - // A -> B FINDNODE encrypted with new keys - findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) - net.nodeB.expectDecode(t, p_findnodeV5, findnode) - - // A <- B NODES - nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) - net.nodeA.expectDecode(t, p_nodesV5, nodes) -} - -// This test checks some malformed packets. -func TestDecodeErrorsV5(t *testing.T) { - t.Parallel() - net := newHandshakeTest() - defer net.close() - - net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) - // TODO some more tests would be nice :) -} - -// This benchmark checks performance of authHeader decoding, verification and key derivation. -func BenchmarkV5_DecodeAuthSecp256k1(b *testing.B) { - net := newHandshakeTest() - defer net.close() - - var ( - idA = net.nodeA.id() - addrA = net.nodeA.addr() - challenge = &whoareyouV5{AuthTag: []byte("authresp"), RecordSeq: 0, node: net.nodeB.n()} - nonce = make([]byte, gcmNonceSize) - ) - header, _, _ := net.nodeA.c.makeAuthHeader(nonce, challenge) - challenge.node = nil // force ENR signature verification in decoder - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _, _, err := net.nodeB.c.decodeAuthResp(idA, addrA, header, challenge) - if err != nil { - b.Fatal(err) - } - } -} - -// This benchmark checks how long it takes to decode an encrypted ping packet. -func BenchmarkV5_DecodePing(b *testing.B) { - net := newHandshakeTest() - defer net.close() - - r := []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17} - w := []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134} - net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), r, w) - net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), w, r) - addrB := net.nodeA.addr() - ping := &pingV5{ReqID: []byte("reqid"), ENRSeq: 5} - enc, _, err := net.nodeA.c.encode(net.nodeB.id(), addrB, ping, nil) - if err != nil { - b.Fatalf("can't encode: %v", err) - } - b.ResetTimer() - - for i := 0; i < b.N; i++ { - _, _, p, _ := net.nodeB.c.decode(enc, addrB) - if _, ok := p.(*pingV5); !ok { - b.Fatalf("wrong packet type %T", p) - } - } -} - -var pp = spew.NewDefaultConfig() - -type handshakeTest struct { - nodeA, nodeB handshakeTestNode - clock mclock.Simulated -} - -type handshakeTestNode struct { - ln *enode.LocalNode - c *wireCodec -} - -func newHandshakeTest() *handshakeTest { - t := new(handshakeTest) - t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock) - t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock) - return t -} - -func (t *handshakeTest) close() { - t.nodeA.ln.Database().Close() - t.nodeB.ln.Database().Close() -} - -func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) { - db, _ := enode.OpenDB("") - n.ln = enode.NewLocalNode(db, key) - n.ln.SetStaticIP(ip) - n.c = newWireCodec(n.ln, key, clock) -} - -func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p packetV5) ([]byte, []byte) { - t.Helper() - return n.encodeWithChallenge(t, to, nil, p) -} - -func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *whoareyouV5, p packetV5) ([]byte, []byte) { - t.Helper() - // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. - var challenge *whoareyouV5 - if c != nil { - challengeCopy := *c - challenge = &challengeCopy - challenge.node = to.n() - } - // Encode to destination. - enc, authTag, err := n.c.encode(to.id(), to.addr(), p, challenge) - if err != nil { - t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) - } - t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.name(), hex.Dump(enc)) - return enc, authTag -} - -func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) packetV5 { - t.Helper() - dec, err := n.decode(p) - if err != nil { - t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) - } - t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) - if dec.kind() != ptype { - t.Fatalf("expected packet type %d, got %d", ptype, dec.kind()) - } - return dec -} - -func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { - t.Helper() - if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) { - t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) - } -} - -func (n *handshakeTestNode) decode(input []byte) (packetV5, error) { - _, _, p, err := n.c.decode(input, "127.0.0.1") - return p, err -} - -func (n *handshakeTestNode) n() *enode.Node { - return n.ln.Node() -} - -func (n *handshakeTestNode) addr() string { - return n.ln.Node().IP().String() -} - -func (n *handshakeTestNode) id() enode.ID { - return n.ln.ID() -} diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index d53375b48b..c95317a005 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -38,36 +39,24 @@ import ( const ( lookupRequestLimit = 3 // max requests against a single node during lookup - findnodeResultLimit = 15 // applies in FINDNODE handler + findnodeResultLimit = 16 // applies in FINDNODE handler totalNodesResponseLimit = 5 // applies in waitForNodes nodesResponseItemLimit = 3 // applies in sendNodes respTimeoutV5 = 700 * time.Millisecond ) -// codecV5 is implemented by wireCodec (and testCodec). +// codecV5 is implemented by v5wire.Codec (and testCodec). // // The UDPv5 transport is split into two objects: the codec object deals with // encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns. type codecV5 interface { - // encode encodes a packet. The 'challenge' parameter is non-nil for calls which got a - // WHOAREYOU response. - encode(fromID enode.ID, fromAddr string, p packetV5, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) + // Encode encodes a packet. + Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) - // decode decodes a packet. It returns an *unknownV5 packet if decryption fails. - // The fromNode return value is non-nil when the input contains a handshake response. - decode(input []byte, fromAddr string) (fromID enode.ID, fromNode *enode.Node, p packetV5, err error) -} - -// packetV5 is implemented by all discv5 packet type structs. -type packetV5 interface { - // These methods provide information and set the request ID. - name() string - kind() byte - setreqid([]byte) - // handle should perform the appropriate action to handle the packet, i.e. this is the - // place to send the response. - handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) + // decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails. + // The *enode.Node return value is non-nil when the input contains a handshake response. + Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error) } // UDPv5 is the implementation of protocol version 5. @@ -83,6 +72,10 @@ type UDPv5 struct { clock mclock.Clock validSchemes enr.IdentityScheme + // talkreq handler registry + trlock sync.Mutex + trhandlers map[string]func([]byte) []byte + // channels into dispatch packetInCh chan ReadPacket readNextCh chan struct{} @@ -93,7 +86,7 @@ type UDPv5 struct { // state of dispatch codec codecV5 activeCallByNode map[enode.ID]*callV5 - activeCallByAuth map[string]*callV5 + activeCallByAuth map[v5wire.Nonce]*callV5 callQueue map[enode.ID][]*callV5 // shutdown stuff @@ -106,16 +99,16 @@ type UDPv5 struct { // callV5 represents a remote procedure call against another node. type callV5 struct { node *enode.Node - packet packetV5 + packet v5wire.Packet responseType byte // expected packet type of response reqid []byte - ch chan packetV5 // responses sent here - err chan error // errors sent here + ch chan v5wire.Packet // responses sent here + err chan error // errors sent here // Valid for active calls only: - authTag []byte // authTag of request packet - handshakeCount int // # times we attempted handshake for this call - challenge *whoareyouV5 // last sent handshake challenge + nonce v5wire.Nonce // nonce of request packet + handshakeCount int // # times we attempted handshake for this call + challenge *v5wire.Whoareyou // last sent handshake challenge timeout mclock.Timer } @@ -152,6 +145,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { log: cfg.Log, validSchemes: cfg.ValidSchemes, clock: cfg.Clock, + trhandlers: make(map[string]func([]byte) []byte), // channels into dispatch packetInCh: make(chan ReadPacket, 1), readNextCh: make(chan struct{}, 1), @@ -159,9 +153,9 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { callDoneCh: make(chan *callV5), respTimeoutCh: make(chan *callTimeout), // state of dispatch - codec: newWireCodec(ln, cfg.PrivateKey, cfg.Clock), + codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock), activeCallByNode: make(map[enode.ID]*callV5), - activeCallByAuth: make(map[string]*callV5), + activeCallByAuth: make(map[v5wire.Nonce]*callV5), callQueue: make(map[enode.ID][]*callV5), // shutdown closeCtx: closeCtx, @@ -236,6 +230,29 @@ func (t *UDPv5) LocalNode() *enode.LocalNode { return t.localNode } +// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called +// whenever a request for the given protocol is received and should return the response +// data or nil. +func (t *UDPv5) RegisterTalkHandler(protocol string, handler func([]byte) []byte) { + t.trlock.Lock() + defer t.trlock.Unlock() + t.trhandlers[protocol] = handler +} + +// TalkRequest sends a talk request to n and waits for a response. +func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) { + req := &v5wire.TalkRequest{Protocol: protocol, Message: request} + resp := t.call(n, v5wire.TalkResponseMsg, req) + defer t.callDone(resp) + select { + case respMsg := <-resp.ch: + return respMsg.(*v5wire.TalkResponse).Message, nil + case err := <-resp.err: + return nil, err + } +} + +// RandomNodes returns an iterator that finds random nodes in the DHT. func (t *UDPv5) RandomNodes() enode.Iterator { if t.tab.len() == 0 { // All nodes were dropped, refresh. The very first query will hit this @@ -283,16 +300,14 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { nodes = nodesByDistance{target: target} err error ) - for i := 0; i < lookupRequestLimit && len(nodes.entries) < findnodeResultLimit; i++ { - var r []*enode.Node - r, err = t.findnode(unwrapNode(destNode), dists[i]) - if err == errClosed { - return nil, err - } - for _, n := range r { - if n.ID() != t.Self().ID() { - nodes.push(wrapNode(n), findnodeResultLimit) - } + var r []*enode.Node + r, err = t.findnode(unwrapNode(destNode), dists) + if err == errClosed { + return nil, err + } + for _, n := range r { + if n.ID() != t.Self().ID() { + nodes.push(wrapNode(n), findnodeResultLimit) } } return nodes.entries, err @@ -301,15 +316,15 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { // lookupDistances computes the distance parameter for FINDNODE calls to dest. // It chooses distances adjacent to logdist(target, dest), e.g. for a target // with logdist(target, dest) = 255 the result is [255, 256, 254]. -func lookupDistances(target, dest enode.ID) (dists []int) { +func lookupDistances(target, dest enode.ID) (dists []uint) { td := enode.LogDist(target, dest) - dists = append(dists, td) + dists = append(dists, uint(td)) for i := 1; len(dists) < lookupRequestLimit; i++ { if td+i < 256 { - dists = append(dists, td+i) + dists = append(dists, uint(td+i)) } if td-i > 0 { - dists = append(dists, td-i) + dists = append(dists, uint(td-i)) } } return dists @@ -317,11 +332,13 @@ func lookupDistances(target, dest enode.ID) (dists []int) { // ping calls PING on a node and waits for a PONG response. func (t *UDPv5) ping(n *enode.Node) (uint64, error) { - resp := t.call(n, p_pongV5, &pingV5{ENRSeq: t.localNode.Node().Seq()}) + req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()} + resp := t.call(n, v5wire.PongMsg, req) defer t.callDone(resp) + select { case pong := <-resp.ch: - return pong.(*pongV5).ENRSeq, nil + return pong.(*v5wire.Pong).ENRSeq, nil case err := <-resp.err: return 0, err } @@ -329,7 +346,7 @@ func (t *UDPv5) ping(n *enode.Node) (uint64, error) { // requestENR requests n's record. func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { - nodes, err := t.findnode(n, 0) + nodes, err := t.findnode(n, []uint{0}) if err != nil { return nil, err } @@ -339,26 +356,14 @@ func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { return nodes[0], nil } -// requestTicket calls REQUESTTICKET on a node and waits for a TICKET response. -func (t *UDPv5) requestTicket(n *enode.Node) ([]byte, error) { - resp := t.call(n, p_ticketV5, &pingV5{}) - defer t.callDone(resp) - select { - case response := <-resp.ch: - return response.(*ticketV5).Ticket, nil - case err := <-resp.err: - return nil, err - } -} - // findnode calls FINDNODE on a node and waits for responses. -func (t *UDPv5) findnode(n *enode.Node, distance int) ([]*enode.Node, error) { - resp := t.call(n, p_nodesV5, &findnodeV5{Distance: uint(distance)}) - return t.waitForNodes(resp, distance) +func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) { + resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances}) + return t.waitForNodes(resp, distances) } // waitForNodes waits for NODES responses to the given call. -func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { +func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) { defer t.callDone(c) var ( @@ -369,11 +374,11 @@ func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { for { select { case responseP := <-c.ch: - response := responseP.(*nodesV5) + response := responseP.(*v5wire.Nodes) for _, record := range response.Nodes { - node, err := t.verifyResponseNode(c, record, distance, seen) + node, err := t.verifyResponseNode(c, record, distances, seen) if err != nil { - t.log.Debug("Invalid record in "+response.name(), "id", c.node.ID(), "err", err) + t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err) continue } nodes = append(nodes, node) @@ -391,7 +396,7 @@ func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { } // verifyResponseNode checks validity of a record in a NODES response. -func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen map[enode.ID]struct{}) (*enode.Node, error) { +func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) { node, err := enode.New(t.validSchemes, r) if err != nil { return nil, err @@ -402,9 +407,10 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen if c.node.UDP() <= 1024 { return nil, errLowPort } - if distance != -1 { - if d := enode.LogDist(c.node.ID(), node.ID()); d != distance { - return nil, fmt.Errorf("wrong distance %d", d) + if distances != nil { + nd := enode.LogDist(c.node.ID(), node.ID()) + if !containsUint(uint(nd), distances) { + return nil, errors.New("does not match any requested distance") } } if _, ok := seen[node.ID()]; ok { @@ -414,20 +420,29 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen return node, nil } -// call sends the given call and sets up a handler for response packets (of type c.responseType). -// Responses are dispatched to the call's response channel. -func (t *UDPv5) call(node *enode.Node, responseType byte, packet packetV5) *callV5 { +func containsUint(x uint, xs []uint) bool { + for _, v := range xs { + if x == v { + return true + } + } + return false +} + +// call sends the given call and sets up a handler for response packets (of message type +// responseType). Responses are dispatched to the call's response channel. +func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 { c := &callV5{ node: node, packet: packet, responseType: responseType, reqid: make([]byte, 8), - ch: make(chan packetV5, 1), + ch: make(chan v5wire.Packet, 1), err: make(chan error, 1), } // Assign request ID. crand.Read(c.reqid) - packet.setreqid(c.reqid) + packet.SetRequestID(c.reqid) // Send call to dispatch. select { case t.callCh <- c: @@ -482,7 +497,7 @@ func (t *UDPv5) dispatch() { panic("BUG: callDone for inactive call") } c.timeout.Stop() - delete(t.activeCallByAuth, string(c.authTag)) + delete(t.activeCallByAuth, c.nonce) delete(t.activeCallByNode, id) t.sendNextCall(id) @@ -502,7 +517,7 @@ func (t *UDPv5) dispatch() { for id, c := range t.activeCallByNode { c.err <- errClosed delete(t.activeCallByNode, id) - delete(t.activeCallByAuth, string(c.authTag)) + delete(t.activeCallByAuth, c.nonce) } return } @@ -548,38 +563,37 @@ func (t *UDPv5) sendNextCall(id enode.ID) { // sendCall encodes and sends a request packet to the call's recipient node. // This performs a handshake if needed. func (t *UDPv5) sendCall(c *callV5) { - if len(c.authTag) > 0 { - // The call already has an authTag from a previous handshake attempt. Remove the - // entry for the authTag because we're about to generate a new authTag for this - // call. - delete(t.activeCallByAuth, string(c.authTag)) + // The call might have a nonce from a previous handshake attempt. Remove the entry for + // the old nonce because we're about to generate a new nonce for this call. + if c.nonce != (v5wire.Nonce{}) { + delete(t.activeCallByAuth, c.nonce) } addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()} - newTag, _ := t.send(c.node.ID(), addr, c.packet, c.challenge) - c.authTag = newTag - t.activeCallByAuth[string(c.authTag)] = c + newNonce, _ := t.send(c.node.ID(), addr, c.packet, c.challenge) + c.nonce = newNonce + t.activeCallByAuth[newNonce] = c t.startResponseTimeout(c) } // sendResponse sends a response packet to the given node. // This doesn't trigger a handshake even if no keys are available. -func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet packetV5) error { +func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error { _, err := t.send(toID, toAddr, packet, nil) return err } // send sends a packet to the given node. -func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet packetV5, c *whoareyouV5) ([]byte, error) { +func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) { addr := toAddr.String() - enc, authTag, err := t.codec.encode(toID, addr, packet, c) + enc, nonce, err := t.codec.Encode(toID, addr, packet, c) if err != nil { - t.log.Warn(">> "+packet.name(), "id", toID, "addr", addr, "err", err) - return authTag, err + t.log.Warn(">> "+packet.Name(), "id", toID, "addr", addr, "err", err) + return nonce, err } _, err = t.conn.WriteToUDP(enc, toAddr) - t.log.Trace(">> "+packet.name(), "id", toID, "addr", addr) - return authTag, err + t.log.Trace(">> "+packet.Name(), "id", toID, "addr", addr) + return nonce, err } // readLoop runs in its own goroutine and reads packets from the network. @@ -617,7 +631,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool { // handlePacket decodes and processes an incoming packet from the network. func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { addr := fromAddr.String() - fromID, fromNode, packet, err := t.codec.decode(rawpacket, addr) + fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr) if err != nil { t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err) return err @@ -626,31 +640,32 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { // Handshake succeeded, add to table. t.tab.addSeenNode(wrapNode(fromNode)) } - if packet.kind() != p_whoareyouV5 { - // WHOAREYOU logged separately to report the sender ID. - t.log.Trace("<< "+packet.name(), "id", fromID, "addr", addr) + if packet.Kind() != v5wire.WhoareyouPacket { + // WHOAREYOU logged separately to report errors. + t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", addr) } - packet.handle(t, fromID, fromAddr) + t.handle(packet, fromID, fromAddr) return nil } // handleCallResponse dispatches a response packet to the call waiting for it. -func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, reqid []byte, p packetV5) { +func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool { ac := t.activeCallByNode[fromID] - if ac == nil || !bytes.Equal(reqid, ac.reqid) { - t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.name()), "id", fromID, "addr", fromAddr) - return + if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) { + t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr) + return false } if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() { - t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.name()), "id", fromID, "addr", fromAddr) - return + t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr) + return false } - if p.kind() != ac.responseType { - t.log.Debug(fmt.Sprintf("Wrong disv5 response type %s", p.name()), "id", fromID, "addr", fromAddr) - return + if p.Kind() != ac.responseType { + t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr) + return false } t.startResponseTimeout(ac) ac.ch <- p + return true } // getNode looks for a node record in table and database. @@ -664,50 +679,65 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node { return nil } -// UNKNOWN - -func (p *unknownV5) name() string { return "UNKNOWN/v5" } -func (p *unknownV5) kind() byte { return p_unknownV5 } -func (p *unknownV5) setreqid(id []byte) {} +// handle processes incoming packets according to their message type. +func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) { + switch p := p.(type) { + case *v5wire.Unknown: + t.handleUnknown(p, fromID, fromAddr) + case *v5wire.Whoareyou: + t.handleWhoareyou(p, fromID, fromAddr) + case *v5wire.Ping: + t.handlePing(p, fromID, fromAddr) + case *v5wire.Pong: + if t.handleCallResponse(fromID, fromAddr, p) { + t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}) + } + case *v5wire.Findnode: + t.handleFindnode(p, fromID, fromAddr) + case *v5wire.Nodes: + t.handleCallResponse(fromID, fromAddr, p) + case *v5wire.TalkRequest: + t.handleTalkRequest(p, fromID, fromAddr) + case *v5wire.TalkResponse: + t.handleCallResponse(fromID, fromAddr, p) + } +} -func (p *unknownV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - challenge := &whoareyouV5{AuthTag: p.AuthTag} +// handleUnknown initiates a handshake by responding with WHOAREYOU. +func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) { + challenge := &v5wire.Whoareyou{Nonce: p.Nonce} crand.Read(challenge.IDNonce[:]) if n := t.getNode(fromID); n != nil { - challenge.node = n + challenge.Node = n challenge.RecordSeq = n.Seq() } t.sendResponse(fromID, fromAddr, challenge) } -// WHOAREYOU - -func (p *whoareyouV5) name() string { return "WHOAREYOU/v5" } -func (p *whoareyouV5) kind() byte { return p_whoareyouV5 } -func (p *whoareyouV5) setreqid(id []byte) {} +var ( + errChallengeNoCall = errors.New("no matching call") + errChallengeTwice = errors.New("second handshake") +) -func (p *whoareyouV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - c, err := p.matchWithCall(t, p.AuthTag) +// handleWhoareyou resends the active call as a handshake packet. +func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) { + c, err := t.matchWithCall(fromID, p.Nonce) if err != nil { - t.log.Debug("Invalid WHOAREYOU/v5", "addr", fromAddr, "err", err) + t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err) return } + // Resend the call that was answered by WHOAREYOU. - t.log.Trace("<< "+p.name(), "id", c.node.ID(), "addr", fromAddr) + t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr) c.handshakeCount++ c.challenge = p - p.node = c.node + p.Node = c.node t.sendCall(c) } -var ( - errChallengeNoCall = errors.New("no matching call") - errChallengeTwice = errors.New("second handshake") -) - -// matchWithCall checks whether the handshake attempt matches the active call. -func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) { - c := t.activeCallByAuth[string(authTag)] +// matchWithCall checks whether a handshake attempt matches the active call. +func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) { + c := t.activeCallByAuth[nonce] if c == nil { return nil, errChallengeNoCall } @@ -717,14 +747,9 @@ func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) { return c, nil } -// PING - -func (p *pingV5) name() string { return "PING/v5" } -func (p *pingV5) kind() byte { return p_pingV5 } -func (p *pingV5) setreqid(id []byte) { p.ReqID = id } - -func (p *pingV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.sendResponse(fromID, fromAddr, &pongV5{ +// handlePing sends a PONG response. +func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) { + t.sendResponse(fromID, fromAddr, &v5wire.Pong{ ReqID: p.ReqID, ToIP: fromAddr.IP, ToPort: uint16(fromAddr.Port), @@ -732,121 +757,81 @@ func (p *pingV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { }) } -// PONG - -func (p *pongV5) name() string { return "PONG/v5" } -func (p *pongV5) kind() byte { return p_pongV5 } -func (p *pongV5) setreqid(id []byte) { p.ReqID = id } - -func (p *pongV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}) - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) +// handleFindnode returns nodes to the requester. +func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) { + nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit) + for _, resp := range packNodes(p.ReqID, nodes) { + t.sendResponse(fromID, fromAddr, resp) + } } -// FINDNODE +// collectTableNodes creates a FINDNODE result set for the given distances. +func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node { + var nodes []*enode.Node + var processed = make(map[uint]struct{}) + for _, dist := range distances { + // Reject duplicate / invalid distances. + _, seen := processed[dist] + if seen || dist > 256 { + continue + } -func (p *findnodeV5) name() string { return "FINDNODE/v5" } -func (p *findnodeV5) kind() byte { return p_findnodeV5 } -func (p *findnodeV5) setreqid(id []byte) { p.ReqID = id } + // Get the nodes. + var bn []*enode.Node + if dist == 0 { + bn = []*enode.Node{t.Self()} + } else if dist <= 256 { + t.tab.mutex.Lock() + bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries) + t.tab.mutex.Unlock() + } + processed[dist] = struct{}{} -func (p *findnodeV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - if p.Distance == 0 { - t.sendNodes(fromID, fromAddr, p.ReqID, []*enode.Node{t.Self()}) - return - } - if p.Distance > 256 { - p.Distance = 256 - } - // Get bucket entries. - t.tab.mutex.Lock() - nodes := unwrapNodes(t.tab.bucketAtDistance(int(p.Distance)).entries) - t.tab.mutex.Unlock() - if len(nodes) > findnodeResultLimit { - nodes = nodes[:findnodeResultLimit] + // Apply some pre-checks to avoid sending invalid nodes. + for _, n := range bn { + // TODO livenessChecks > 1 + if netutil.CheckRelayIP(rip, n.IP()) != nil { + continue + } + nodes = append(nodes, n) + if len(nodes) >= limit { + return nodes + } + } } - t.sendNodes(fromID, fromAddr, p.ReqID, nodes) + return nodes } -// sendNodes sends the given records in one or more NODES packets. -func (t *UDPv5) sendNodes(toID enode.ID, toAddr *net.UDPAddr, reqid []byte, nodes []*enode.Node) { - // TODO livenessChecks > 1 - // TODO CheckRelayIP +// packNodes creates NODES response packets for the given node list. +func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes { + if len(nodes) == 0 { + return []*v5wire.Nodes{{ReqID: reqid, Total: 1}} + } + total := uint8(math.Ceil(float64(len(nodes)) / 3)) - resp := &nodesV5{ReqID: reqid, Total: total, Nodes: make([]*enr.Record, 3)} - sent := false + var resp []*v5wire.Nodes for len(nodes) > 0 { + p := &v5wire.Nodes{ReqID: reqid, Total: total} items := min(nodesResponseItemLimit, len(nodes)) - resp.Nodes = resp.Nodes[:items] for i := 0; i < items; i++ { - resp.Nodes[i] = nodes[i].Record() + p.Nodes = append(p.Nodes, nodes[i].Record()) } - t.sendResponse(toID, toAddr, resp) nodes = nodes[items:] - sent = true - } - // Ensure at least one response is sent. - if !sent { - resp.Total = 1 - resp.Nodes = nil - t.sendResponse(toID, toAddr, resp) + resp = append(resp, p) } + return resp } -// NODES +// handleTalkRequest runs the talk request handler of the requested protocol. +func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAddr *net.UDPAddr) { + t.trlock.Lock() + handler := t.trhandlers[p.Protocol] + t.trlock.Unlock() -func (p *nodesV5) name() string { return "NODES/v5" } -func (p *nodesV5) kind() byte { return p_nodesV5 } -func (p *nodesV5) setreqid(id []byte) { p.ReqID = id } - -func (p *nodesV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) -} - -// REQUESTTICKET - -func (p *requestTicketV5) name() string { return "REQUESTTICKET/v5" } -func (p *requestTicketV5) kind() byte { return p_requestTicketV5 } -func (p *requestTicketV5) setreqid(id []byte) { p.ReqID = id } - -func (p *requestTicketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.sendResponse(fromID, fromAddr, &ticketV5{ReqID: p.ReqID}) -} - -// TICKET - -func (p *ticketV5) name() string { return "TICKET/v5" } -func (p *ticketV5) kind() byte { return p_ticketV5 } -func (p *ticketV5) setreqid(id []byte) { p.ReqID = id } - -func (p *ticketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) -} - -// REGTOPIC - -func (p *regtopicV5) name() string { return "REGTOPIC/v5" } -func (p *regtopicV5) kind() byte { return p_regtopicV5 } -func (p *regtopicV5) setreqid(id []byte) { p.ReqID = id } - -func (p *regtopicV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.sendResponse(fromID, fromAddr, ®confirmationV5{ReqID: p.ReqID, Registered: false}) -} - -// REGCONFIRMATION - -func (p *regconfirmationV5) name() string { return "REGCONFIRMATION/v5" } -func (p *regconfirmationV5) kind() byte { return p_regconfirmationV5 } -func (p *regconfirmationV5) setreqid(id []byte) { p.ReqID = id } - -func (p *regconfirmationV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { - t.handleCallResponse(fromID, fromAddr, p.ReqID, p) -} - -// TOPICQUERY - -func (p *topicqueryV5) name() string { return "TOPICQUERY/v5" } -func (p *topicqueryV5) kind() byte { return p_topicqueryV5 } -func (p *topicqueryV5) setreqid(id []byte) { p.ReqID = id } - -func (p *topicqueryV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + var response []byte + if handler != nil { + response = handler(p.Message) + } + resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response} + t.sendResponse(fromID, fromAddr, resp) } diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index 7d3915e2dc..d91a2097db 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -24,22 +24,25 @@ import ( "math/rand" "net" "reflect" + "sort" "testing" "time" "github.com/ethereum/go-ethereum/internal/testlog" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" ) // Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5. -func TestEndToEndV5(t *testing.T) { +func TestUDPv5_lookupE2E(t *testing.T) { t.Parallel() + const N = 5 var nodes []*UDPv5 - for i := 0; i < 5; i++ { + for i := 0; i < N; i++ { var cfg Config if len(nodes) > 0 { bn := nodes[0].Self() @@ -49,12 +52,22 @@ func TestEndToEndV5(t *testing.T) { nodes = append(nodes, node) defer node.Close() } + last := nodes[N-1] + target := nodes[rand.Intn(N-2)].Self() - last := nodes[len(nodes)-1] - target := nodes[rand.Intn(len(nodes)-2)].Self() + // It is expected that all nodes can be found. + expectedResult := make([]*enode.Node, len(nodes)) + for i := range nodes { + expectedResult[i] = nodes[i].Self() + } + sort.Slice(expectedResult, func(i, j int) bool { + return enode.DistCmp(target.ID(), expectedResult[i].ID(), expectedResult[j].ID()) < 0 + }) + + // Do the lookup. results := last.Lookup(target.ID()) - if len(results) == 0 || results[0].ID() != target.ID() { - t.Fatalf("lookup returned wrong results: %v", results) + if err := checkNodesEqual(results, expectedResult); err != nil { + t.Fatalf("lookup returned wrong results: %v", err) } } @@ -93,8 +106,8 @@ func TestUDPv5_pingHandling(t *testing.T) { test := newUDPV5Test(t) defer test.close() - test.packetIn(&pingV5{ReqID: []byte("foo")}) - test.waitPacketOut(func(p *pongV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&v5wire.Ping{ReqID: []byte("foo")}) + test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) { if !bytes.Equal(p.ReqID, []byte("foo")) { t.Error("wrong request ID in response:", p.ReqID) } @@ -110,13 +123,13 @@ func TestUDPv5_unknownPacket(t *testing.T) { test := newUDPV5Test(t) defer test.close() - authTag := [12]byte{1, 2, 3} - check := func(p *whoareyouV5, wantSeq uint64) { + nonce := v5wire.Nonce{1, 2, 3} + check := func(p *v5wire.Whoareyou, wantSeq uint64) { t.Helper() - if !bytes.Equal(p.AuthTag, authTag[:]) { - t.Error("wrong token in WHOAREYOU:", p.AuthTag, authTag[:]) + if p.Nonce != nonce { + t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce) } - if p.IDNonce == ([32]byte{}) { + if p.IDNonce == ([16]byte{}) { t.Error("all zero ID nonce") } if p.RecordSeq != wantSeq { @@ -125,8 +138,8 @@ func TestUDPv5_unknownPacket(t *testing.T) { } // Unknown packet from unknown node. - test.packetIn(&unknownV5{AuthTag: authTag[:]}) - test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) { + test.packetIn(&v5wire.Unknown{Nonce: nonce}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) { check(p, 0) }) @@ -134,8 +147,8 @@ func TestUDPv5_unknownPacket(t *testing.T) { n := test.getNode(test.remotekey, test.remoteaddr).Node() test.table.addSeenNode(wrapNode(n)) - test.packetIn(&unknownV5{AuthTag: authTag[:]}) - test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) { + test.packetIn(&v5wire.Unknown{Nonce: nonce}) + test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) { check(p, n.Seq()) }) } @@ -147,24 +160,40 @@ func TestUDPv5_findnodeHandling(t *testing.T) { defer test.close() // Create test nodes and insert them into the table. - nodes := nodesAtDistance(test.table.self().ID(), 253, 10) - fillTable(test.table, wrapNodes(nodes)) + nodes253 := nodesAtDistance(test.table.self().ID(), 253, 10) + nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4) + nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10) + fillTable(test.table, wrapNodes(nodes253)) + fillTable(test.table, wrapNodes(nodes249)) + fillTable(test.table, wrapNodes(nodes248)) // Requesting with distance zero should return the node's own record. - test.packetIn(&findnodeV5{ReqID: []byte{0}, Distance: 0}) + test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}}) test.expectNodes([]byte{0}, 1, []*enode.Node{test.udp.Self()}) - // Requesting with distance > 256 caps it at 256. - test.packetIn(&findnodeV5{ReqID: []byte{1}, Distance: 4234098}) + // Requesting with distance > 256 shouldn't crash. + test.packetIn(&v5wire.Findnode{ReqID: []byte{1}, Distances: []uint{4234098}}) test.expectNodes([]byte{1}, 1, nil) - // This request gets no nodes because the corresponding bucket is empty. - test.packetIn(&findnodeV5{ReqID: []byte{2}, Distance: 254}) + // Requesting with empty distance list shouldn't crash either. + test.packetIn(&v5wire.Findnode{ReqID: []byte{2}, Distances: []uint{}}) test.expectNodes([]byte{2}, 1, nil) - // This request gets all test nodes. - test.packetIn(&findnodeV5{ReqID: []byte{3}, Distance: 253}) - test.expectNodes([]byte{3}, 4, nodes) + // This request gets no nodes because the corresponding bucket is empty. + test.packetIn(&v5wire.Findnode{ReqID: []byte{3}, Distances: []uint{254}}) + test.expectNodes([]byte{3}, 1, nil) + + // This request gets all the distance-253 nodes. + test.packetIn(&v5wire.Findnode{ReqID: []byte{4}, Distances: []uint{253}}) + test.expectNodes([]byte{4}, 4, nodes253) + + // This request gets all the distance-249 nodes and some more at 248 because + // the bucket at 249 is not full. + test.packetIn(&v5wire.Findnode{ReqID: []byte{5}, Distances: []uint{249, 248}}) + var nodes []*enode.Node + nodes = append(nodes, nodes249...) + nodes = append(nodes, nodes248[:10]...) + test.expectNodes([]byte{5}, 5, nodes) } func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes []*enode.Node) { @@ -172,16 +201,17 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes for _, n := range wantNodes { nodeSet[n.ID()] = n.Record() } + for { - test.waitPacketOut(func(p *nodesV5, addr *net.UDPAddr, authTag []byte) { + test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, wantReqID) { + test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID) + } if len(p.Nodes) > 3 { test.t.Fatalf("too many nodes in response") } if p.Total != wantTotal { - test.t.Fatalf("wrong total response count %d", p.Total) - } - if !bytes.Equal(p.ReqID, wantReqID) { - test.t.Fatalf("wrong request ID in response: %v", p.ReqID) + test.t.Fatalf("wrong total response count %d, want %d", p.Total, wantTotal) } for _, record := range p.Nodes { n, _ := enode.New(enode.ValidSchemesForTesting, record) @@ -215,7 +245,7 @@ func TestUDPv5_pingCall(t *testing.T) { _, err := test.udp.ping(remote) done <- err }() - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {}) if err := <-done; err != errTimeout { t.Fatalf("want errTimeout, got %q", err) } @@ -225,8 +255,8 @@ func TestUDPv5_pingCall(t *testing.T) { _, err := test.udp.ping(remote) done <- err }() - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetInFrom(test.remotekey, test.remoteaddr, &pongV5{ReqID: p.ReqID}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID}) }) if err := <-done; err != nil { t.Fatal(err) @@ -237,9 +267,9 @@ func TestUDPv5_pingCall(t *testing.T) { _, err := test.udp.ping(remote) done <- err }() - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101} - test.packetInFrom(test.remotekey, wrongAddr, &pongV5{ReqID: p.ReqID}) + test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID}) }) if err := <-done; err != errTimeout { t.Fatalf("want errTimeout for reply from wrong IP, got %q", err) @@ -255,29 +285,29 @@ func TestUDPv5_findnodeCall(t *testing.T) { // Launch the request: var ( - distance = 230 - remote = test.getNode(test.remotekey, test.remoteaddr).Node() - nodes = nodesAtDistance(remote.ID(), distance, 8) - done = make(chan error, 1) - response []*enode.Node + distances = []uint{230} + remote = test.getNode(test.remotekey, test.remoteaddr).Node() + nodes = nodesAtDistance(remote.ID(), int(distances[0]), 8) + done = make(chan error, 1) + response []*enode.Node ) go func() { var err error - response, err = test.udp.findnode(remote, distance) + response, err = test.udp.findnode(remote, distances) done <- err }() // Serve the responses: - test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) { - if p.Distance != uint(distance) { - t.Fatalf("wrong bucket: %d", p.Distance) + test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) { + if !reflect.DeepEqual(p.Distances, distances) { + t.Fatalf("wrong distances in request: %v", p.Distances) } - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[:4]), }) - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[4:]), @@ -314,16 +344,16 @@ func TestUDPv5_callResend(t *testing.T) { }() // Ping answered by WHOAREYOU. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&whoareyouV5{AuthTag: authTag}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) // Ping should be re-sent. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&pongV5{ReqID: p.ReqID}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) }) // Answer the other ping. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&pongV5{ReqID: p.ReqID}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) { + test.packetIn(&v5wire.Pong{ReqID: p.ReqID}) }) if err := <-done; err != nil { t.Fatalf("unexpected ping error: %v", err) @@ -347,12 +377,12 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) { }() // Ping answered by WHOAREYOU. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&whoareyouV5{AuthTag: authTag}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) // Ping answered by WHOAREYOU again. - test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { - test.packetIn(&whoareyouV5{AuthTag: authTag}) + test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) { + test.packetIn(&v5wire.Whoareyou{Nonce: nonce}) }) if err := <-done; err != errTimeout { t.Fatalf("unexpected ping error: %q", err) @@ -367,27 +397,27 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { // Launch the request: var ( - distance = 230 + distance = uint(230) remote = test.getNode(test.remotekey, test.remoteaddr).Node() - nodes = nodesAtDistance(remote.ID(), distance, 8) + nodes = nodesAtDistance(remote.ID(), int(distance), 8) done = make(chan error, 1) ) go func() { - _, err := test.udp.findnode(remote, distance) + _, err := test.udp.findnode(remote, []uint{distance}) done <- err }() // Serve two responses, slowly. - test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) { + test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) { time.Sleep(respTimeout - 50*time.Millisecond) - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[:4]), }) time.Sleep(respTimeout - 50*time.Millisecond) - test.packetIn(&nodesV5{ + test.packetIn(&v5wire.Nodes{ ReqID: p.ReqID, Total: 2, Nodes: nodesToRecords(nodes[4:]), @@ -398,6 +428,97 @@ func TestUDPv5_callTimeoutReset(t *testing.T) { } } +// This test checks that TALKREQ calls the registered handler function. +func TestUDPv5_talkHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + var recvMessage []byte + test.udp.RegisterTalkHandler("test", func(message []byte) []byte { + recvMessage = message + return []byte("test response") + }) + + // Successful case: + test.packetIn(&v5wire.TalkRequest{ + ReqID: []byte("foo"), + Protocol: "test", + Message: []byte("test request"), + }) + test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("foo")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if string(p.Message) != "test response" { + t.Errorf("wrong talk response message: %q", p.Message) + } + if string(recvMessage) != "test request" { + t.Errorf("wrong message received in handler: %q", recvMessage) + } + }) + + // Check that empty response is returned for unregistered protocols. + recvMessage = nil + test.packetIn(&v5wire.TalkRequest{ + ReqID: []byte("2"), + Protocol: "wrong", + Message: []byte("test request"), + }) + test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) { + if !bytes.Equal(p.ReqID, []byte("2")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if string(p.Message) != "" { + t.Errorf("wrong talk response message: %q", p.Message) + } + if recvMessage != nil { + t.Errorf("handler was called for wrong protocol: %q", recvMessage) + } + }) +} + +// This test checks that outgoing TALKREQ calls work. +func TestUDPv5_talkRequest(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + + // This request times out. + go func() { + _, err := test.udp.TalkRequest(remote, "test", []byte("test request")) + done <- err + }() + test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {}) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout, got %q", err) + } + + // This request works. + go func() { + _, err := test.udp.TalkRequest(remote, "test", []byte("test request")) + done <- err + }() + test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) { + if p.Protocol != "test" { + t.Errorf("wrong protocol ID in talk request: %q", p.Protocol) + } + if string(p.Message) != "test request" { + t.Errorf("wrong message talk request: %q", p.Message) + } + test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{ + ReqID: p.ReqID, + Message: []byte("test response"), + }) + }) + if err := <-done; err != nil { + t.Fatal(err) + } +} + // This test checks that lookup works. func TestUDPv5_lookup(t *testing.T) { t.Parallel() @@ -417,7 +538,8 @@ func TestUDPv5_lookup(t *testing.T) { } // Seed table with initial node. - fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}) + initialNode := lookupTestnet.node(256, 0) + fillTable(test.table, []*node{wrapNode(initialNode)}) // Start the lookup. resultC := make(chan []*enode.Node, 1) @@ -427,22 +549,30 @@ func TestUDPv5_lookup(t *testing.T) { }() // Answer lookup packets. + asked := make(map[enode.ID]bool) for done := false; !done; { - done = test.waitPacketOut(func(p packetV5, to *net.UDPAddr, authTag []byte) { + done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) { recipient, key := lookupTestnet.nodeByAddr(to) switch p := p.(type) { - case *pingV5: - test.packetInFrom(key, to, &pongV5{ReqID: p.ReqID}) - case *findnodeV5: - nodes := lookupTestnet.neighborsAtDistance(recipient, p.Distance, 3) - response := &nodesV5{ReqID: p.ReqID, Total: 1, Nodes: nodesToRecords(nodes)} - test.packetInFrom(key, to, response) + case *v5wire.Ping: + test.packetInFrom(key, to, &v5wire.Pong{ReqID: p.ReqID}) + case *v5wire.Findnode: + if asked[recipient.ID()] { + t.Error("Asked node", recipient.ID(), "twice") + } + asked[recipient.ID()] = true + nodes := lookupTestnet.neighborsAtDistances(recipient, p.Distances, 16) + t.Logf("Got FINDNODE for %v, returning %d nodes", p.Distances, len(nodes)) + for _, resp := range packNodes(p.ReqID, nodes) { + test.packetInFrom(key, to, resp) + } } }) } // Verify result nodes. - checkLookupResults(t, lookupTestnet, <-resultC) + results := <-resultC + checkLookupResults(t, lookupTestnet, results) } // This test checks the local node can be utilised to set key-values. @@ -481,6 +611,7 @@ type udpV5Test struct { nodesByIP map[string]*enode.LocalNode } +// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption. type testCodec struct { test *udpV5Test id enode.ID @@ -489,46 +620,44 @@ type testCodec struct { type testCodecFrame struct { NodeID enode.ID - AuthTag []byte + AuthTag v5wire.Nonce Ptype byte Packet rlp.RawValue } -func (c *testCodec) encode(toID enode.ID, addr string, p packetV5, _ *whoareyouV5) ([]byte, []byte, error) { +func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) { c.ctr++ - authTag := make([]byte, 8) - binary.BigEndian.PutUint64(authTag, c.ctr) + var authTag v5wire.Nonce + binary.BigEndian.PutUint64(authTag[:], c.ctr) + penc, _ := rlp.EncodeToBytes(p) - frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.kind(), penc}) + frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc}) return frame, authTag, err } -func (c *testCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) { +func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) { frame, p, err := c.decodeFrame(input) if err != nil { return enode.ID{}, nil, nil, err } - if p.kind() == p_whoareyouV5 { - frame.NodeID = enode.ID{} // match wireCodec behavior - } return frame.NodeID, nil, p, nil } -func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p packetV5, err error) { +func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) { if err = rlp.DecodeBytes(input, &frame); err != nil { return frame, nil, fmt.Errorf("invalid frame: %v", err) } switch frame.Ptype { - case p_unknownV5: - dec := new(unknownV5) + case v5wire.UnknownPacket: + dec := new(v5wire.Unknown) err = rlp.DecodeBytes(frame.Packet, &dec) p = dec - case p_whoareyouV5: - dec := new(whoareyouV5) + case v5wire.WhoareyouPacket: + dec := new(v5wire.Whoareyou) err = rlp.DecodeBytes(frame.Packet, &dec) p = dec default: - p, err = decodePacketBodyV5(frame.Ptype, frame.Packet) + p, err = v5wire.DecodeMessage(frame.Ptype, frame.Packet) } return frame, p, err } @@ -561,20 +690,20 @@ func newUDPV5Test(t *testing.T) *udpV5Test { } // handles a packet as if it had been sent to the transport. -func (test *udpV5Test) packetIn(packet packetV5) { +func (test *udpV5Test) packetIn(packet v5wire.Packet) { test.t.Helper() test.packetInFrom(test.remotekey, test.remoteaddr, packet) } // handles a packet as if it had been sent to the transport by the key/endpoint. -func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet packetV5) { +func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) { test.t.Helper() ln := test.getNode(key, addr) codec := &testCodec{test: test, id: ln.ID()} - enc, _, err := codec.encode(test.udp.Self().ID(), addr.String(), packet, nil) + enc, _, err := codec.Encode(test.udp.Self().ID(), addr.String(), packet, nil) if err != nil { - test.t.Errorf("%s encode error: %v", packet.name(), err) + test.t.Errorf("%s encode error: %v", packet.Name(), err) } if test.udp.dispatchReadPacket(addr, enc) { <-test.udp.readNextCh // unblock UDPv5.dispatch @@ -596,8 +725,12 @@ func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode. return ln } +// waitPacketOut waits for the next output packet and handles it using the given 'validate' +// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is +// assignable to packetV5. func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) { test.t.Helper() + fn := reflect.ValueOf(validate) exptype := fn.Type().In(0) diff --git a/p2p/discover/v5wire/crypto.go b/p2p/discover/v5wire/crypto.go new file mode 100644 index 0000000000..fc0a0edef5 --- /dev/null +++ b/p2p/discover/v5wire/crypto.go @@ -0,0 +1,180 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + "errors" + "fmt" + "hash" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" + "golang.org/x/crypto/hkdf" +) + +const ( + // Encryption/authentication parameters. + aesKeySize = 16 + gcmNonceSize = 12 +) + +// Nonce represents a nonce used for AES/GCM. +type Nonce [gcmNonceSize]byte + +// EncodePubkey encodes a public key. +func EncodePubkey(key *ecdsa.PublicKey) []byte { + switch key.Curve { + case crypto.S256(): + return crypto.CompressPubkey(key) + default: + panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey") + } +} + +// DecodePubkey decodes a public key in compressed format. +func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { + switch curve { + case crypto.S256(): + if len(e) != 33 { + return nil, errors.New("wrong size public key data") + } + return crypto.DecompressPubkey(e) + default: + return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name) + } +} + +// idNonceHash computes the ID signature hash used in the handshake. +func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte { + h.Reset() + h.Write([]byte("discovery v5 identity proof")) + h.Write(challenge) + h.Write(ephkey) + h.Write(destID[:]) + return h.Sum(nil) +} + +// makeIDSignature creates the ID nonce signature. +func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) { + input := idNonceHash(hash, challenge, ephkey, destID) + switch key.Curve { + case crypto.S256(): + idsig, err := crypto.Sign(input, key) + if err != nil { + return nil, err + } + return idsig[:len(idsig)-1], nil // remove recovery ID + default: + return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name) + } +} + +// s256raw is an unparsed secp256k1 public key ENR entry. +type s256raw []byte + +func (s256raw) ENRKey() string { return "secp256k1" } + +// verifyIDSignature checks that signature over idnonce was made by the given node. +func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error { + switch idscheme := n.Record().IdentityScheme(); idscheme { + case "v4": + var pubkey s256raw + if n.Load(&pubkey) != nil { + return errors.New("no secp256k1 public key in record") + } + input := idNonceHash(hash, challenge, ephkey, destID) + if !crypto.VerifySignature(pubkey, input, sig) { + return errInvalidNonceSig + } + return nil + default: + return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) + } +} + +type hashFn func() hash.Hash + +// deriveKeys creates the session keys. +func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session { + const text = "discovery v5 key agreement" + var info = make([]byte, 0, len(text)+len(n1)+len(n2)) + info = append(info, text...) + info = append(info, n1[:]...) + info = append(info, n2[:]...) + + eph := ecdh(priv, pub) + if eph == nil { + return nil + } + kdf := hkdf.New(hash, eph, challenge, info) + sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)} + kdf.Read(sec.writeKey) + kdf.Read(sec.readKey) + for i := range eph { + eph[i] = 0 + } + return &sec +} + +// ecdh creates a shared secret. +func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { + secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) + if secX == nil { + return nil + } + sec := make([]byte, 33) + sec[0] = 0x02 | byte(secY.Bit(0)) + math.ReadBits(secX, sec[1:]) + return sec +} + +// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is +// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16 +// bytes longer than plaintext because it contains an authentication tag. +func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + panic(fmt.Errorf("can't create block cipher: %v", err)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + panic(fmt.Errorf("can't create GCM: %v", err)) + } + return aesgcm.Seal(dest, nonce, plaintext, authData), nil +} + +// decryptGCM decrypts ct using AES-GCM with the given key and nonce. +func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("can't create block cipher: %v", err) + } + if len(nonce) != gcmNonceSize { + return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + return nil, fmt.Errorf("can't create GCM: %v", err) + } + pt := make([]byte, 0, len(ct)) + return aesgcm.Open(pt, nonce, ct, authData) +} diff --git a/p2p/discover/v5wire/crypto_test.go b/p2p/discover/v5wire/crypto_test.go new file mode 100644 index 0000000000..72169b4314 --- /dev/null +++ b/p2p/discover/v5wire/crypto_test.go @@ -0,0 +1,124 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "reflect" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +func TestVector_ECDH(t *testing.T) { + var ( + staticKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + publicKey = hexPubkey(crypto.S256(), "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") + want = hexutil.MustDecode("0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e") + ) + result := ecdh(staticKey, publicKey) + check(t, "shared-secret", result, want) +} + +func TestVector_KDF(t *testing.T) { + var ( + ephKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") + net = newHandshakeTest() + ) + defer net.close() + + destKey := &testKeyB.PublicKey + s := deriveKeys(sha256.New, ephKey, destKey, net.nodeA.id(), net.nodeB.id(), cdata) + t.Logf("ephemeral-key = %#x", ephKey.D) + t.Logf("dest-pubkey = %#x", EncodePubkey(destKey)) + t.Logf("node-id-a = %#x", net.nodeA.id().Bytes()) + t.Logf("node-id-b = %#x", net.nodeB.id().Bytes()) + t.Logf("challenge-data = %#x", cdata) + check(t, "initiator-key", s.writeKey, hexutil.MustDecode("0xdccc82d81bd610f4f76d3ebe97a40571")) + check(t, "recipient-key", s.readKey, hexutil.MustDecode("0xac74bb8773749920b0d3a8881c173ec5")) +} + +func TestVector_IDSignature(t *testing.T) { + var ( + key = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736") + destID = enode.HexID("0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9") + ephkey = hexutil.MustDecode("0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231") + cdata = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000") + ) + + sig, err := makeIDSignature(sha256.New(), key, cdata, ephkey, destID) + if err != nil { + t.Fatal(err) + } + t.Logf("static-key = %#x", key.D) + t.Logf("challenge-data = %#x", cdata) + t.Logf("ephemeral-pubkey = %#x", ephkey) + t.Logf("node-id-B = %#x", destID.Bytes()) + expected := "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" + check(t, "id-signature", sig, hexutil.MustDecode(expected)) +} + +func TestDeriveKeys(t *testing.T) { + t.Parallel() + + var ( + n1 = enode.ID{1} + n2 = enode.ID{2} + cdata = []byte{1, 2, 3, 4} + ) + sec1 := deriveKeys(sha256.New, testKeyA, &testKeyB.PublicKey, n1, n2, cdata) + sec2 := deriveKeys(sha256.New, testKeyB, &testKeyA.PublicKey, n1, n2, cdata) + if sec1 == nil || sec2 == nil { + t.Fatal("key agreement failed") + } + if !reflect.DeepEqual(sec1, sec2) { + t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) + } +} + +func check(t *testing.T, what string, x, y []byte) { + t.Helper() + + if !bytes.Equal(x, y) { + t.Errorf("wrong %s: %#x != %#x", what, x, y) + } else { + t.Logf("%s = %#x", what, x) + } +} + +func hexPrivkey(input string) *ecdsa.PrivateKey { + key, err := crypto.HexToECDSA(strings.TrimPrefix(input, "0x")) + if err != nil { + panic(err) + } + return key +} + +func hexPubkey(curve elliptic.Curve, input string) *ecdsa.PublicKey { + key, err := DecodePubkey(curve, hexutil.MustDecode(input)) + if err != nil { + panic(err) + } + return key +} diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go new file mode 100644 index 0000000000..f502339e1e --- /dev/null +++ b/p2p/discover/v5wire/encoding.go @@ -0,0 +1,648 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + crand "crypto/rand" + "crypto/sha256" + "encoding/binary" + "errors" + "fmt" + "hash" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +// TODO concurrent WHOAREYOU tie-breaker +// TODO rehandshake after X packets + +// Header represents a packet header. +type Header struct { + IV [sizeofMaskingIV]byte + StaticHeader + AuthData []byte + + src enode.ID // used by decoder +} + +// StaticHeader contains the static fields of a packet header. +type StaticHeader struct { + ProtocolID [6]byte + Version uint16 + Flag byte + Nonce Nonce + AuthSize uint16 +} + +// Authdata layouts. +type ( + whoareyouAuthData struct { + IDNonce [16]byte // ID proof data + RecordSeq uint64 // highest known ENR sequence of requester + } + + handshakeAuthData struct { + h struct { + SrcID enode.ID + SigSize byte // ignature data + PubkeySize byte // offset of + } + // Trailing variable-size data. + signature, pubkey, record []byte + } + + messageAuthData struct { + SrcID enode.ID + } +) + +// Packet header flag values. +const ( + flagMessage = iota + flagWhoareyou + flagHandshake +) + +// Protocol constants. +const ( + version = 1 + minVersion = 1 + sizeofMaskingIV = 16 + + minMessageSize = 48 // this refers to data after static headers + randomPacketMsgSize = 20 +) + +var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'} + +// Errors. +var ( + errTooShort = errors.New("packet too short") + errInvalidHeader = errors.New("invalid packet header") + errInvalidFlag = errors.New("invalid flag value in header") + errMinVersion = errors.New("version of packet header below minimum") + errMsgTooShort = errors.New("message/handshake packet below minimum size") + errAuthSize = errors.New("declared auth size is beyond packet length") + errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake") + errInvalidAuthKey = errors.New("invalid ephemeral pubkey") + errNoRecord = errors.New("expected ENR in handshake but none sent") + errInvalidNonceSig = errors.New("invalid ID nonce signature") + errMessageTooShort = errors.New("message contains no data") + errMessageDecrypt = errors.New("cannot decrypt message") +) + +// Public errors. +var ( + ErrInvalidReqID = errors.New("request ID larger than 8 bytes") +) + +// Packet sizes. +var ( + sizeofStaticHeader = binary.Size(StaticHeader{}) + sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{}) + sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h) + sizeofMessageAuthData = binary.Size(messageAuthData{}) + sizeofStaticPacketData = sizeofMaskingIV + sizeofStaticHeader +) + +// Codec encodes and decodes Discovery v5 packets. +// This type is not safe for concurrent use. +type Codec struct { + sha256 hash.Hash + localnode *enode.LocalNode + privkey *ecdsa.PrivateKey + sc *SessionCache + + // encoder buffers + buf bytes.Buffer // whole packet + headbuf bytes.Buffer // packet header + msgbuf bytes.Buffer // message RLP plaintext + msgctbuf []byte // message data ciphertext + + // decoder buffer + reader bytes.Reader +} + +// NewCodec creates a wire codec. +func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec { + c := &Codec{ + sha256: sha256.New(), + localnode: ln, + privkey: key, + sc: NewSessionCache(1024, clock), + } + return c +} + +// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The +// 'challenge' parameter should be the most recently received WHOAREYOU packet from that +// node. +func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) { + // Create the packet header. + var ( + head Header + session *session + msgData []byte + err error + ) + switch { + case packet.Kind() == WhoareyouPacket: + head, err = c.encodeWhoareyou(id, packet.(*Whoareyou)) + case challenge != nil: + // We have an unanswered challenge, send handshake. + head, session, err = c.encodeHandshakeHeader(id, addr, challenge) + default: + session = c.sc.session(id, addr) + if session != nil { + // There is a session, use it. + head, err = c.encodeMessageHeader(id, session) + } else { + // No keys, send random data to kick off the handshake. + head, msgData, err = c.encodeRandom(id) + } + } + if err != nil { + return nil, Nonce{}, err + } + + // Generate masking IV. + if err := c.sc.maskingIVGen(head.IV[:]); err != nil { + return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err) + } + + // Encode header data. + c.writeHeaders(&head) + + // Store sent WHOAREYOU challenges. + if challenge, ok := packet.(*Whoareyou); ok { + challenge.ChallengeData = bytesCopy(&c.buf) + c.sc.storeSentHandshake(id, addr, challenge) + } else if msgData == nil { + headerData := c.buf.Bytes() + msgData, err = c.encryptMessage(session, packet, &head, headerData) + if err != nil { + return nil, Nonce{}, err + } + } + + enc, err := c.EncodeRaw(id, head, msgData) + return enc, head.Nonce, err +} + +// EncodeRaw encodes a packet with the given header. +func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) { + c.writeHeaders(&head) + + // Apply masking. + masked := c.buf.Bytes()[sizeofMaskingIV:] + mask := head.mask(id) + mask.XORKeyStream(masked[:], masked[:]) + + // Write message data. + c.buf.Write(msgdata) + return c.buf.Bytes(), nil +} + +func (c *Codec) writeHeaders(head *Header) { + c.buf.Reset() + c.buf.Write(head.IV[:]) + binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader) + c.buf.Write(head.AuthData) +} + +// makeHeader creates a packet header. +func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header { + var authsize int + switch flag { + case flagMessage: + authsize = sizeofMessageAuthData + case flagWhoareyou: + authsize = sizeofWhoareyouAuthData + case flagHandshake: + authsize = sizeofHandshakeAuthData + default: + panic(fmt.Errorf("BUG: invalid packet header flag %x", flag)) + } + authsize += authsizeExtra + if authsize > int(^uint16(0)) { + panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize)) + } + return Header{ + StaticHeader: StaticHeader{ + ProtocolID: protocolID, + Version: version, + Flag: flag, + AuthSize: uint16(authsize), + }, + } +} + +// encodeRandom encodes a packet with random content. +func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) { + head := c.makeHeader(toID, flagMessage, 0) + + // Encode auth data. + auth := messageAuthData{SrcID: c.localnode.ID()} + if _, err := crand.Read(head.Nonce[:]); err != nil { + return head, nil, fmt.Errorf("can't get random data: %v", err) + } + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, auth) + head.AuthData = c.headbuf.Bytes() + + // Fill message ciphertext buffer with random bytes. + c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...) + crand.Read(c.msgctbuf) + return head, c.msgctbuf, nil +} + +// encodeWhoareyou encodes a WHOAREYOU packet. +func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) { + // Sanity check node field to catch misbehaving callers. + if packet.RecordSeq > 0 && packet.Node == nil { + panic("BUG: missing node in whoareyou with non-zero seq") + } + + // Create header. + head := c.makeHeader(toID, flagWhoareyou, 0) + head.AuthData = bytesCopy(&c.buf) + head.Nonce = packet.Nonce + + // Encode auth data. + auth := &whoareyouAuthData{ + IDNonce: packet.IDNonce, + RecordSeq: packet.RecordSeq, + } + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, auth) + head.AuthData = c.headbuf.Bytes() + return head, nil +} + +// encodeHandshakeMessage encodes the handshake message packet header. +func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) { + // Ensure calling code sets challenge.node. + if challenge.Node == nil { + panic("BUG: missing challenge.Node in encode") + } + + // Generate new secrets. + auth, session, err := c.makeHandshakeAuth(toID, addr, challenge) + if err != nil { + return Header{}, nil, err + } + + // Generate nonce for message. + nonce, err := c.sc.nextNonce(session) + if err != nil { + return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err) + } + + // TODO: this should happen when the first authenticated message is received + c.sc.storeNewSession(toID, addr, session) + + // Encode the auth header. + var ( + authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record) + head = c.makeHeader(toID, flagHandshake, authsizeExtra) + ) + c.headbuf.Reset() + binary.Write(&c.headbuf, binary.BigEndian, &auth.h) + c.headbuf.Write(auth.signature) + c.headbuf.Write(auth.pubkey) + c.headbuf.Write(auth.record) + head.AuthData = c.headbuf.Bytes() + head.Nonce = nonce + return head, session, err +} + +// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU. +func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) { + auth := new(handshakeAuthData) + auth.h.SrcID = c.localnode.ID() + + // Create the ephemeral key. This needs to be first because the + // key is part of the ID nonce signature. + var remotePubkey = new(ecdsa.PublicKey) + if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { + return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") + } + ephkey, err := c.sc.ephemeralKeyGen() + if err != nil { + return nil, nil, fmt.Errorf("can't generate ephemeral key") + } + ephpubkey := EncodePubkey(&ephkey.PublicKey) + auth.pubkey = ephpubkey[:] + auth.h.PubkeySize = byte(len(auth.pubkey)) + + // Add ID nonce signature to response. + cdata := challenge.ChallengeData + idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID) + if err != nil { + return nil, nil, fmt.Errorf("can't sign: %v", err) + } + auth.signature = idsig + auth.h.SigSize = byte(len(auth.signature)) + + // Add our record to response if it's newer than what remote side has. + ln := c.localnode.Node() + if challenge.RecordSeq < ln.Seq() { + auth.record, _ = rlp.EncodeToBytes(ln.Record()) + } + + // Create session keys. + sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata) + if sec == nil { + return nil, nil, fmt.Errorf("key derivation failed") + } + return auth, sec, err +} + +// encodeMessage encodes an encrypted message packet. +func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) { + head := c.makeHeader(toID, flagMessage, 0) + + // Create the header. + nonce, err := c.sc.nextNonce(s) + if err != nil { + return Header{}, fmt.Errorf("can't generate nonce: %v", err) + } + auth := messageAuthData{SrcID: c.localnode.ID()} + c.buf.Reset() + binary.Write(&c.buf, binary.BigEndian, &auth) + head.AuthData = bytesCopy(&c.buf) + head.Nonce = nonce + return head, err +} + +func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) { + // Encode message plaintext. + c.msgbuf.Reset() + c.msgbuf.WriteByte(p.Kind()) + if err := rlp.Encode(&c.msgbuf, p); err != nil { + return nil, err + } + messagePT := c.msgbuf.Bytes() + + // Encrypt into message ciphertext buffer. + messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData) + if err == nil { + c.msgctbuf = messageCT + } + return messageCT, err +} + +// Decode decodes a discovery packet. +func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) { + // Unmask the static header. + if len(input) < sizeofStaticPacketData { + return enode.ID{}, nil, nil, errTooShort + } + var head Header + copy(head.IV[:], input[:sizeofMaskingIV]) + mask := head.mask(c.localnode.ID()) + staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData] + mask.XORKeyStream(staticHeader, staticHeader) + + // Decode and verify the static header. + c.reader.Reset(staticHeader) + binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader) + remainingInput := len(input) - sizeofStaticPacketData + if err := head.checkValid(remainingInput); err != nil { + return enode.ID{}, nil, nil, err + } + + // Unmask auth data. + authDataEnd := sizeofStaticPacketData + int(head.AuthSize) + authData := input[sizeofStaticPacketData:authDataEnd] + mask.XORKeyStream(authData, authData) + head.AuthData = authData + + // Delete timed-out handshakes. This must happen before decoding to avoid + // processing the same handshake twice. + c.sc.handshakeGC() + + // Decode auth part and message. + headerData := input[:authDataEnd] + msgData := input[authDataEnd:] + switch head.Flag { + case flagWhoareyou: + p, err = c.decodeWhoareyou(&head, headerData) + case flagHandshake: + n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData) + case flagMessage: + p, err = c.decodeMessage(addr, &head, headerData, msgData) + default: + err = errInvalidFlag + } + return head.src, n, p, err +} + +// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet. +func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) { + if len(head.AuthData) != sizeofWhoareyouAuthData { + return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData)) + } + var auth whoareyouAuthData + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth) + p := &Whoareyou{ + Nonce: head.Nonce, + IDNonce: auth.IDNonce, + RecordSeq: auth.RecordSeq, + ChallengeData: make([]byte, len(headerData)), + } + copy(p.ChallengeData, headerData) + return p, nil +} + +func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) { + node, auth, session, err := c.decodeHandshake(fromAddr, head) + if err != nil { + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return nil, nil, err + } + + // Decrypt the message using the new session keys. + msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey) + if err != nil { + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, err + } + + // Handshake OK, drop the challenge and store the new session keys. + c.sc.storeNewSession(auth.h.SrcID, fromAddr, session) + c.sc.deleteHandshake(auth.h.SrcID, fromAddr) + return node, msg, nil +} + +func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) { + if auth, err = c.decodeHandshakeAuthData(head); err != nil { + return nil, auth, nil, err + } + + // Verify against our last WHOAREYOU. + challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr) + if challenge == nil { + return nil, auth, nil, errUnexpectedHandshake + } + // Get node record. + n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record) + if err != nil { + return nil, auth, nil, err + } + // Verify ID nonce signature. + sig := auth.signature + cdata := challenge.ChallengeData + err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID()) + if err != nil { + return nil, auth, nil, err + } + // Verify ephemeral key is on curve. + ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey) + if err != nil { + return nil, auth, nil, errInvalidAuthKey + } + // Derive sesssion keys. + session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata) + session = session.keysFlipped() + return n, auth, session, nil +} + +// decodeHandshakeAuthData reads the authdata section of a handshake packet. +func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) { + // Decode fixed size part. + if len(head.AuthData) < sizeofHandshakeAuthData { + return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize) + } + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth.h) + head.src = auth.h.SrcID + + // Decode variable-size part. + var ( + vardata = head.AuthData[sizeofHandshakeAuthData:] + sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize) + keyOffset = int(auth.h.SigSize) + recOffset = keyOffset + int(auth.h.PubkeySize) + ) + if len(vardata) < sigAndKeySize { + return auth, errTooShort + } + auth.signature = vardata[:keyOffset] + auth.pubkey = vardata[keyOffset:recOffset] + auth.record = vardata[recOffset:] + return auth, nil +} + +// decodeHandshakeRecord verifies the node record contained in a handshake packet. The +// remote node should include the record if we don't have one or if ours is older than the +// latest sequence number. +func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) { + node := local + if len(remote) > 0 { + var record enr.Record + if err := rlp.DecodeBytes(remote, &record); err != nil { + return nil, err + } + if local == nil || local.Seq() < record.Seq() { + n, err := enode.New(enode.ValidSchemes, &record) + if err != nil { + return nil, fmt.Errorf("invalid node record: %v", err) + } + if n.ID() != wantID { + return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID()) + } + node = n + } + } + if node == nil { + return nil, errNoRecord + } + return node, nil +} + +// decodeMessage reads packet data following the header as an ordinary message packet. +func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) { + if len(head.AuthData) != sizeofMessageAuthData { + return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData)) + } + var auth messageAuthData + c.reader.Reset(head.AuthData) + binary.Read(&c.reader, binary.BigEndian, &auth) + head.src = auth.SrcID + + // Try decrypting the message. + key := c.sc.readKey(auth.SrcID, fromAddr) + msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key) + if err == errMessageDecrypt { + // It didn't work. Start the handshake since this is an ordinary message packet. + return &Unknown{Nonce: head.Nonce}, nil + } + return msg, err +} + +func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) { + msgdata, err := decryptGCM(readKey, nonce, input, headerData) + if err != nil { + return nil, errMessageDecrypt + } + if len(msgdata) == 0 { + return nil, errMessageTooShort + } + return DecodeMessage(msgdata[0], msgdata[1:]) +} + +// checkValid performs some basic validity checks on the header. +// The packetLen here is the length remaining after the static header. +func (h *StaticHeader) checkValid(packetLen int) error { + if h.ProtocolID != protocolID { + return errInvalidHeader + } + if h.Version < minVersion { + return errMinVersion + } + if h.Flag != flagWhoareyou && packetLen < minMessageSize { + return errMsgTooShort + } + if int(h.AuthSize) > packetLen { + return errAuthSize + } + return nil +} + +// headerMask returns a cipher for 'masking' / 'unmasking' packet headers. +func (h *Header) mask(destID enode.ID) cipher.Stream { + block, err := aes.NewCipher(destID[:16]) + if err != nil { + panic("can't create cipher") + } + return cipher.NewCTR(block, h.IV[:]) +} + +func bytesCopy(r *bytes.Buffer) []byte { + b := make([]byte, r.Len()) + copy(b, r.Bytes()) + return b +} diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go new file mode 100644 index 0000000000..d9c807e0a8 --- /dev/null +++ b/p2p/discover/v5wire/encoding_test.go @@ -0,0 +1,636 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// To regenerate discv5 test vectors, run +// +// go test -run TestVectors -write-test-vectors +// +var writeTestVectorsFlag = flag.Bool("write-test-vectors", false, "Overwrite discv5 test vectors in testdata/") + +var ( + testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f") + testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628") + testEphKey, _ = crypto.HexToECDSA("0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6") + testIDnonce = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} +) + +// This test checks that the minPacketSize and randomPacketMsgSize constants are well-defined. +func TestMinSizes(t *testing.T) { + var ( + gcmTagSize = 16 + emptyMsg = sizeofMessageAuthData + gcmTagSize + ) + t.Log("static header size", sizeofStaticPacketData) + t.Log("whoareyou size", sizeofStaticPacketData+sizeofWhoareyouAuthData) + t.Log("empty msg size", sizeofStaticPacketData+emptyMsg) + if want := emptyMsg; minMessageSize != want { + t.Fatalf("wrong minMessageSize %d, want %d", minMessageSize, want) + } + if sizeofMessageAuthData+randomPacketMsgSize < minMessageSize { + t.Fatalf("randomPacketMsgSize %d too small", randomPacketMsgSize) + } +} + +// This test checks the basic handshake flow where A talks to B and A has no secrets. +func TestHandshake(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + if len(net.nodeB.c.sc.handshakes) > 0 { + t.Fatalf("node B didn't remove handshake from challenge map") + } + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// This test checks that handshake attempts are removed within the timeout. +func TestHandshake_timeout(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) after timeout + net.clock.Run(handshakeTimeout + 1) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks handshake behavior when no record is sent in the auth response. +func TestHandshake_norecord(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + nodeA := net.nodeA.n() + if nodeA.Seq() == 0 { + t.Fatal("need non-zero sequence number") + } + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: nodeA.Seq(), + Node: nodeA, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// In this test, A tries to send FINDNODE with existing secrets but B doesn't know +// anything about A. +func TestHandshake_rekey(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + session := &session{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) + + // A -> B FINDNODE (encrypted with zero keys) + findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{}) + net.nodeB.expectDecode(t, UnknownPacket, findnode) + + // A <- B WHOAREYOU + challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // Check that new keys haven't been stored yet. + sa := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()) + if !bytes.Equal(sa.writeKey, session.writeKey) || !bytes.Equal(sa.readKey, session.readKey) { + t.Fatal("node A stored keys too early") + } + if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { + t.Fatal("node B stored keys too early") + } + + // A -> B FINDNODE encrypted with new keys + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +// In this test A and B have different keys before the handshake. +func TestHandshake_rekey2(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + initKeysA := &session{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + initKeysB := &session{ + readKey: []byte("CCCCCCCCCCCCCCCC"), + writeKey: []byte("DDDDDDDDDDDDDDDD"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB) + + // A -> B FINDNODE encrypted with initKeysA + findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}}) + net.nodeB.expectDecode(t, UnknownPacket, findnode) + + // A <- B WHOAREYOU + challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE (handshake packet) + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecode(t, FindnodeMsg, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1}) + net.nodeA.expectDecode(t, NodesMsg, nodes) +} + +func TestHandshake_BadHandshakeAttack(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{}) + resp := net.nodeB.expectDecode(t, UnknownPacket, packet) + + // A <- B WHOAREYOU + challenge := &Whoareyou{ + Nonce: resp.(*Unknown).Nonce, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou) + + // A -> B FINDNODE + incorrect_challenge := &Whoareyou{ + IDNonce: [16]byte{5, 6, 7, 8, 9, 6, 11, 12}, + RecordSeq: challenge.RecordSeq, + Node: challenge.Node, + sent: challenge.sent, + } + incorrect_findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, incorrect_challenge, &Findnode{}) + incorrect_findnode2 := make([]byte, len(incorrect_findnode)) + copy(incorrect_findnode2, incorrect_findnode) + + net.nodeB.expectDecodeErr(t, errInvalidNonceSig, incorrect_findnode) + + // Reject new findnode as previous handshake is now deleted. + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, incorrect_findnode2) + + // The findnode packet is again rejected even with a valid challenge this time. + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks some malformed packets. +func TestDecodeErrorsV5(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) + // TODO some more tests would be nice :) + // - check invalid authdata sizes + // - check invalid handshake data sizes +} + +// This test checks that all test vectors can be decoded. +func TestTestVectorsV5(t *testing.T) { + var ( + idA = enode.PubkeyToIDV4(&testKeyA.PublicKey) + idB = enode.PubkeyToIDV4(&testKeyB.PublicKey) + addr = "127.0.0.1" + session = &session{ + writeKey: hexutil.MustDecode("0x00000000000000000000000000000000"), + readKey: hexutil.MustDecode("0x01010101010101010101010101010101"), + } + challenge0A, challenge1A, challenge0B Whoareyou + ) + + // Create challenge packets. + c := Whoareyou{ + Nonce: Nonce{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + IDNonce: testIDnonce, + } + challenge0A, challenge1A, challenge0B = c, c, c + challenge1A.RecordSeq = 1 + net := newHandshakeTest() + challenge0A.Node = net.nodeA.n() + challenge0B.Node = net.nodeB.n() + challenge1A.Node = net.nodeA.n() + net.close() + + type testVectorTest struct { + name string // test vector name + packet Packet // the packet to be encoded + challenge *Whoareyou // handshake challenge passed to encoder + prep func(*handshakeTest) // called before encode/decode + } + tests := []testVectorTest{ + { + name: "v5.1-whoareyou", + packet: &challenge0B, + }, + { + name: "v5.1-ping-message", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 2, + }, + prep: func(net *handshakeTest) { + net.nodeA.c.sc.storeNewSession(idB, addr, session) + net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped()) + }, + }, + { + name: "v5.1-ping-handshake-enr", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 1, + }, + challenge: &challenge0A, + prep: func(net *handshakeTest) { + // Update challenge.Header.AuthData. + net.nodeA.c.Encode(idB, "", &challenge0A, nil) + net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge0A) + }, + }, + { + name: "v5.1-ping-handshake", + packet: &Ping{ + ReqID: []byte{0, 0, 0, 1}, + ENRSeq: 1, + }, + challenge: &challenge1A, + prep: func(net *handshakeTest) { + // Update challenge data. + net.nodeA.c.Encode(idB, "", &challenge1A, nil) + net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge1A) + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + net := newHandshakeTest() + defer net.close() + + // Override all random inputs. + net.nodeA.c.sc.nonceGen = func(counter uint32) (Nonce, error) { + return Nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, nil + } + net.nodeA.c.sc.maskingIVGen = func(buf []byte) error { + return nil // all zero + } + net.nodeA.c.sc.ephemeralKeyGen = func() (*ecdsa.PrivateKey, error) { + return testEphKey, nil + } + + // Prime the codec for encoding/decoding. + if test.prep != nil { + test.prep(net) + } + + file := filepath.Join("testdata", test.name+".txt") + if *writeTestVectorsFlag { + // Encode the packet. + d, nonce := net.nodeA.encodeWithChallenge(t, net.nodeB, test.challenge, test.packet) + comment := testVectorComment(net, test.packet, test.challenge, nonce) + writeTestVector(file, comment, d) + } + enc := hexFile(file) + net.nodeB.expectDecode(t, test.packet.Kind(), enc) + }) + } +} + +// testVectorComment creates the commentary for discv5 test vector files. +func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce Nonce) string { + o := new(strings.Builder) + printWhoareyou := func(p *Whoareyou) { + fmt.Fprintf(o, "whoareyou.challenge-data = %#x\n", p.ChallengeData) + fmt.Fprintf(o, "whoareyou.request-nonce = %#x\n", p.Nonce[:]) + fmt.Fprintf(o, "whoareyou.id-nonce = %#x\n", p.IDNonce[:]) + fmt.Fprintf(o, "whoareyou.enr-seq = %d\n", p.RecordSeq) + } + + fmt.Fprintf(o, "src-node-id = %#x\n", net.nodeA.id().Bytes()) + fmt.Fprintf(o, "dest-node-id = %#x\n", net.nodeB.id().Bytes()) + switch p := p.(type) { + case *Whoareyou: + // WHOAREYOU packet. + printWhoareyou(p) + case *Ping: + fmt.Fprintf(o, "nonce = %#x\n", nonce[:]) + fmt.Fprintf(o, "read-key = %#x\n", net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()).writeKey) + fmt.Fprintf(o, "ping.req-id = %#x\n", p.ReqID) + fmt.Fprintf(o, "ping.enr-seq = %d\n", p.ENRSeq) + if challenge != nil { + // Handshake message packet. + fmt.Fprint(o, "\nhandshake inputs:\n\n") + printWhoareyou(challenge) + fmt.Fprintf(o, "ephemeral-key = %#x\n", testEphKey.D.Bytes()) + fmt.Fprintf(o, "ephemeral-pubkey = %#x\n", crypto.CompressPubkey(&testEphKey.PublicKey)) + } + default: + panic(fmt.Errorf("unhandled packet type %T", p)) + } + return o.String() +} + +// This benchmark checks performance of handshake packet decoding. +func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + var ( + idA = net.nodeA.id() + challenge = &Whoareyou{Node: net.nodeB.n()} + message = &Ping{ReqID: []byte("reqid")} + ) + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), "", message, challenge) + if err != nil { + b.Fatal("can't encode handshake packet") + } + challenge.Node = nil // force ENR signature verification in decoder + b.ResetTimer() + + input := make([]byte, len(enc)) + for i := 0; i < b.N; i++ { + copy(input, enc) + net.nodeB.c.sc.storeSentHandshake(idA, "", challenge) + _, _, _, err := net.nodeB.c.Decode(input, "") + if err != nil { + b.Fatal(err) + } + } +} + +// This benchmark checks how long it takes to decode an encrypted ping packet. +func BenchmarkV5_DecodePing(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + session := &session{ + readKey: []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17}, + writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134}, + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped()) + addrB := net.nodeA.addr() + ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5} + enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil) + if err != nil { + b.Fatalf("can't encode: %v", err) + } + b.ResetTimer() + + input := make([]byte, len(enc)) + for i := 0; i < b.N; i++ { + copy(input, enc) + _, _, packet, _ := net.nodeB.c.Decode(input, addrB) + if _, ok := packet.(*Ping); !ok { + b.Fatalf("wrong packet type %T", packet) + } + } +} + +var pp = spew.NewDefaultConfig() + +type handshakeTest struct { + nodeA, nodeB handshakeTestNode + clock mclock.Simulated +} + +type handshakeTestNode struct { + ln *enode.LocalNode + c *Codec +} + +func newHandshakeTest() *handshakeTest { + t := new(handshakeTest) + t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock) + t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock) + return t +} + +func (t *handshakeTest) close() { + t.nodeA.ln.Database().Close() + t.nodeB.ln.Database().Close() +} + +func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) { + db, _ := enode.OpenDB("") + n.ln = enode.NewLocalNode(db, key) + n.ln.SetStaticIP(ip) + if n.ln.Node().Seq() != 1 { + panic(fmt.Errorf("unexpected seq %d", n.ln.Node().Seq())) + } + n.c = NewCodec(n.ln, key, clock) +} + +func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p Packet) ([]byte, Nonce) { + t.Helper() + return n.encodeWithChallenge(t, to, nil, p) +} + +func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *Whoareyou, p Packet) ([]byte, Nonce) { + t.Helper() + + // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. + var challenge *Whoareyou + if c != nil { + challengeCopy := *c + challenge = &challengeCopy + challenge.Node = to.n() + } + // Encode to destination. + enc, nonce, err := n.c.Encode(to.id(), to.addr(), p, challenge) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.Name(), hex.Dump(enc)) + return enc, nonce +} + +func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) Packet { + t.Helper() + + dec, err := n.decode(p) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) + if dec.Kind() != ptype { + t.Fatalf("expected packet type %d, got %d", ptype, dec.Kind()) + } + return dec +} + +func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { + t.Helper() + if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) { + t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) + } +} + +func (n *handshakeTestNode) decode(input []byte) (Packet, error) { + _, _, p, err := n.c.Decode(input, "127.0.0.1") + return p, err +} + +func (n *handshakeTestNode) n() *enode.Node { + return n.ln.Node() +} + +func (n *handshakeTestNode) addr() string { + return n.ln.Node().IP().String() +} + +func (n *handshakeTestNode) id() enode.ID { + return n.ln.ID() +} + +// hexFile reads the given file and decodes the hex data contained in it. +// Whitespace and any lines beginning with the # character are ignored. +func hexFile(file string) []byte { + fileContent, err := ioutil.ReadFile(file) + if err != nil { + panic(err) + } + + // Gather hex data, ignore comments. + var text []byte + for _, line := range bytes.Split(fileContent, []byte("\n")) { + line = bytes.TrimSpace(line) + if len(line) > 0 && line[0] == '#' { + continue + } + text = append(text, line...) + } + + // Parse the hex. + if bytes.HasPrefix(text, []byte("0x")) { + text = text[2:] + } + data := make([]byte, hex.DecodedLen(len(text))) + if _, err := hex.Decode(data, text); err != nil { + panic("invalid hex in " + file) + } + return data +} + +// writeTestVector writes a test vector file with the given commentary and binary data. +func writeTestVector(file, comment string, data []byte) { + fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(err) + } + defer fd.Close() + + if len(comment) > 0 { + for _, line := range strings.Split(strings.TrimSpace(comment), "\n") { + fmt.Fprintf(fd, "# %s\n", line) + } + fmt.Fprintln(fd) + } + for len(data) > 0 { + var chunk []byte + if len(data) < 32 { + chunk = data + } else { + chunk = data[:32] + } + data = data[len(chunk):] + fmt.Fprintf(fd, "%x\n", chunk) + } +} diff --git a/p2p/discover/v5wire/msg.go b/p2p/discover/v5wire/msg.go new file mode 100644 index 0000000000..7c3686111b --- /dev/null +++ b/p2p/discover/v5wire/msg.go @@ -0,0 +1,249 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package v5wire + +import ( + "fmt" + "net" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +// Packet is implemented by all message types. +type Packet interface { + Name() string // Name returns a string corresponding to the message type. + Kind() byte // Kind returns the message type. + RequestID() []byte // Returns the request ID. + SetRequestID([]byte) // Sets the request ID. +} + +// Message types. +const ( + PingMsg byte = iota + 1 + PongMsg + FindnodeMsg + NodesMsg + TalkRequestMsg + TalkResponseMsg + RequestTicketMsg + TicketMsg + RegtopicMsg + RegconfirmationMsg + TopicQueryMsg + + UnknownPacket = byte(255) // any non-decryptable packet + WhoareyouPacket = byte(254) // the WHOAREYOU packet +) + +// Protocol messages. +type ( + // Unknown represents any packet that can't be decrypted. + Unknown struct { + Nonce Nonce + } + + // WHOAREYOU contains the handshake challenge. + Whoareyou struct { + ChallengeData []byte // Encoded challenge + Nonce Nonce // Nonce of request packet + IDNonce [16]byte // Identity proof data + RecordSeq uint64 // ENR sequence number of recipient + + // Node is the locally known node record of recipient. + // This must be set by the caller of Encode. + Node *enode.Node + + sent mclock.AbsTime // for handshake GC. + } + + // PING is sent during liveness checks. + Ping struct { + ReqID []byte + ENRSeq uint64 + } + + // PONG is the reply to PING. + Pong struct { + ReqID []byte + ENRSeq uint64 + ToIP net.IP // These fields should mirror the UDP envelope address of the ping + ToPort uint16 // packet, which provides a way to discover the the external address (after NAT). + } + + // FINDNODE is a query for nodes in the given bucket. + Findnode struct { + ReqID []byte + Distances []uint + } + + // NODES is the reply to FINDNODE and TOPICQUERY. + Nodes struct { + ReqID []byte + Total uint8 + Nodes []*enr.Record + } + + // TALKREQ is an application-level request. + TalkRequest struct { + ReqID []byte + Protocol string + Message []byte + } + + // TALKRESP is the reply to TALKREQ. + TalkResponse struct { + ReqID []byte + Message []byte + } + + // REQUESTTICKET requests a ticket for a topic queue. + RequestTicket struct { + ReqID []byte + Topic []byte + } + + // TICKET is the response to REQUESTTICKET. + Ticket struct { + ReqID []byte + Ticket []byte + } + + // REGTOPIC registers the sender in a topic queue using a ticket. + Regtopic struct { + ReqID []byte + Ticket []byte + ENR *enr.Record + } + + // REGCONFIRMATION is the reply to REGTOPIC. + Regconfirmation struct { + ReqID []byte + Registered bool + } + + // TOPICQUERY asks for nodes with the given topic. + TopicQuery struct { + ReqID []byte + Topic []byte + } +) + +// DecodeMessage decodes the message body of a packet. +func DecodeMessage(ptype byte, body []byte) (Packet, error) { + var dec Packet + switch ptype { + case PingMsg: + dec = new(Ping) + case PongMsg: + dec = new(Pong) + case FindnodeMsg: + dec = new(Findnode) + case NodesMsg: + dec = new(Nodes) + case TalkRequestMsg: + dec = new(TalkRequest) + case TalkResponseMsg: + dec = new(TalkResponse) + case RequestTicketMsg: + dec = new(RequestTicket) + case TicketMsg: + dec = new(Ticket) + case RegtopicMsg: + dec = new(Regtopic) + case RegconfirmationMsg: + dec = new(Regconfirmation) + case TopicQueryMsg: + dec = new(TopicQuery) + default: + return nil, fmt.Errorf("unknown packet type %d", ptype) + } + if err := rlp.DecodeBytes(body, dec); err != nil { + return nil, err + } + if dec.RequestID() != nil && len(dec.RequestID()) > 8 { + return nil, ErrInvalidReqID + } + return dec, nil +} + +func (*Whoareyou) Name() string { return "WHOAREYOU/v5" } +func (*Whoareyou) Kind() byte { return WhoareyouPacket } +func (*Whoareyou) RequestID() []byte { return nil } +func (*Whoareyou) SetRequestID([]byte) {} + +func (*Unknown) Name() string { return "UNKNOWN/v5" } +func (*Unknown) Kind() byte { return UnknownPacket } +func (*Unknown) RequestID() []byte { return nil } +func (*Unknown) SetRequestID([]byte) {} + +func (*Ping) Name() string { return "PING/v5" } +func (*Ping) Kind() byte { return PingMsg } +func (p *Ping) RequestID() []byte { return p.ReqID } +func (p *Ping) SetRequestID(id []byte) { p.ReqID = id } + +func (*Pong) Name() string { return "PONG/v5" } +func (*Pong) Kind() byte { return PongMsg } +func (p *Pong) RequestID() []byte { return p.ReqID } +func (p *Pong) SetRequestID(id []byte) { p.ReqID = id } + +func (*Findnode) Name() string { return "FINDNODE/v5" } +func (*Findnode) Kind() byte { return FindnodeMsg } +func (p *Findnode) RequestID() []byte { return p.ReqID } +func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id } + +func (*Nodes) Name() string { return "NODES/v5" } +func (*Nodes) Kind() byte { return NodesMsg } +func (p *Nodes) RequestID() []byte { return p.ReqID } +func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id } + +func (*TalkRequest) Name() string { return "TALKREQ/v5" } +func (*TalkRequest) Kind() byte { return TalkRequestMsg } +func (p *TalkRequest) RequestID() []byte { return p.ReqID } +func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id } + +func (*TalkResponse) Name() string { return "TALKRESP/v5" } +func (*TalkResponse) Kind() byte { return TalkResponseMsg } +func (p *TalkResponse) RequestID() []byte { return p.ReqID } +func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id } + +func (*RequestTicket) Name() string { return "REQTICKET/v5" } +func (*RequestTicket) Kind() byte { return RequestTicketMsg } +func (p *RequestTicket) RequestID() []byte { return p.ReqID } +func (p *RequestTicket) SetRequestID(id []byte) { p.ReqID = id } + +func (*Regtopic) Name() string { return "REGTOPIC/v5" } +func (*Regtopic) Kind() byte { return RegtopicMsg } +func (p *Regtopic) RequestID() []byte { return p.ReqID } +func (p *Regtopic) SetRequestID(id []byte) { p.ReqID = id } + +func (*Ticket) Name() string { return "TICKET/v5" } +func (*Ticket) Kind() byte { return TicketMsg } +func (p *Ticket) RequestID() []byte { return p.ReqID } +func (p *Ticket) SetRequestID(id []byte) { p.ReqID = id } + +func (*Regconfirmation) Name() string { return "REGCONFIRMATION/v5" } +func (*Regconfirmation) Kind() byte { return RegconfirmationMsg } +func (p *Regconfirmation) RequestID() []byte { return p.ReqID } +func (p *Regconfirmation) SetRequestID(id []byte) { p.ReqID = id } + +func (*TopicQuery) Name() string { return "TOPICQUERY/v5" } +func (*TopicQuery) Kind() byte { return TopicQueryMsg } +func (p *TopicQuery) RequestID() []byte { return p.ReqID } +func (p *TopicQuery) SetRequestID(id []byte) { p.ReqID = id } diff --git a/p2p/discover/v5_session.go b/p2p/discover/v5wire/session.go similarity index 58% rename from p2p/discover/v5_session.go rename to p2p/discover/v5wire/session.go index 8a0eeb6977..d52b5c1181 100644 --- a/p2p/discover/v5_session.go +++ b/p2p/discover/v5wire/session.go @@ -14,22 +14,33 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package discover +package v5wire import ( + "crypto/ecdsa" crand "crypto/rand" + "encoding/binary" + "time" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/hashicorp/golang-lru/simplelru" ) -// The sessionCache keeps negotiated encryption keys and +const handshakeTimeout = time.Second + +// The SessionCache keeps negotiated encryption keys and // state for in-progress handshakes in the Discovery v5 wire protocol. -type sessionCache struct { +type SessionCache struct { sessions *simplelru.LRU - handshakes map[sessionID]*whoareyouV5 + handshakes map[sessionID]*Whoareyou clock mclock.Clock + + // hooks for overriding randomness. + nonceGen func(uint32) (Nonce, error) + maskingIVGen func([]byte) error + ephemeralKeyGen func() (*ecdsa.PrivateKey, error) } // sessionID identifies a session or handshake. @@ -45,27 +56,45 @@ type session struct { nonceCounter uint32 } -func newSessionCache(maxItems int, clock mclock.Clock) *sessionCache { +// keysFlipped returns a copy of s with the read and write keys flipped. +func (s *session) keysFlipped() *session { + return &session{s.readKey, s.writeKey, s.nonceCounter} +} + +func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { cache, err := simplelru.NewLRU(maxItems, nil) if err != nil { panic("can't create session cache") } - return &sessionCache{ - sessions: cache, - handshakes: make(map[sessionID]*whoareyouV5), - clock: clock, + return &SessionCache{ + sessions: cache, + handshakes: make(map[sessionID]*Whoareyou), + clock: clock, + nonceGen: generateNonce, + maskingIVGen: generateMaskingIV, + ephemeralKeyGen: crypto.GenerateKey, } } +func generateNonce(counter uint32) (n Nonce, err error) { + binary.BigEndian.PutUint32(n[:4], counter) + _, err = crand.Read(n[4:]) + return n, err +} + +func generateMaskingIV(buf []byte) error { + _, err := crand.Read(buf) + return err +} + // nextNonce creates a nonce for encrypting a message to the given session. -func (sc *sessionCache) nextNonce(id enode.ID, addr string) []byte { - n := make([]byte, gcmNonceSize) - crand.Read(n) - return n +func (sc *SessionCache) nextNonce(s *session) (Nonce, error) { + s.nonceCounter++ + return sc.nonceGen(s.nonceCounter) } // session returns the current session for the given node, if any. -func (sc *sessionCache) session(id enode.ID, addr string) *session { +func (sc *SessionCache) session(id enode.ID, addr string) *session { item, ok := sc.sessions.Get(sessionID{id, addr}) if !ok { return nil @@ -74,46 +103,36 @@ func (sc *sessionCache) session(id enode.ID, addr string) *session { } // readKey returns the current read key for the given node. -func (sc *sessionCache) readKey(id enode.ID, addr string) []byte { +func (sc *SessionCache) readKey(id enode.ID, addr string) []byte { if s := sc.session(id, addr); s != nil { return s.readKey } return nil } -// writeKey returns the current read key for the given node. -func (sc *sessionCache) writeKey(id enode.ID, addr string) []byte { - if s := sc.session(id, addr); s != nil { - return s.writeKey - } - return nil -} - // storeNewSession stores new encryption keys in the cache. -func (sc *sessionCache) storeNewSession(id enode.ID, addr string, r, w []byte) { - sc.sessions.Add(sessionID{id, addr}, &session{ - readKey: r, writeKey: w, - }) +func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) { + sc.sessions.Add(sessionID{id, addr}, s) } // getHandshake gets the handshake challenge we previously sent to the given remote node. -func (sc *sessionCache) getHandshake(id enode.ID, addr string) *whoareyouV5 { +func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou { return sc.handshakes[sessionID{id, addr}] } // storeSentHandshake stores the handshake challenge sent to the given remote node. -func (sc *sessionCache) storeSentHandshake(id enode.ID, addr string, challenge *whoareyouV5) { +func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) { challenge.sent = sc.clock.Now() sc.handshakes[sessionID{id, addr}] = challenge } // deleteHandshake deletes handshake data for the given node. -func (sc *sessionCache) deleteHandshake(id enode.ID, addr string) { +func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) { delete(sc.handshakes, sessionID{id, addr}) } // handshakeGC deletes timed-out handshakes. -func (sc *sessionCache) handshakeGC() { +func (sc *SessionCache) handshakeGC() { deadline := sc.clock.Now().Add(-handshakeTimeout) for key, challenge := range sc.handshakes { if challenge.sent < deadline { diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt new file mode 100644 index 0000000000..477f9e15a8 --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt @@ -0,0 +1,27 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x53b1c075f41876423154e157470c2f48 +# ping.req-id = 0x00000001 +# ping.enr-seq = 1 +# +# handshake inputs: +# +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 0 +# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6 +# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5 + +00000000000000000000000000000000088b3d4342774649305f313964a39e55 +ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856 +2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2 +1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1 +f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6 +cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1 +2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a +80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e +4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394 +71 diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt new file mode 100644 index 0000000000..b3f304766c --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt @@ -0,0 +1,23 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x4f9fac6de7567d1e3b1241dffe90f662 +# ping.req-id = 0x00000001 +# ping.enr-seq = 1 +# +# handshake inputs: +# +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 1 +# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6 +# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5 + +00000000000000000000000000000000088b3d4342774649305f313964a39e55 +ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef +268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb +a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1 +f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83 +9cf8 diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-message.txt b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt new file mode 100644 index 0000000000..f82b99c3bc --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt @@ -0,0 +1,10 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# nonce = 0xffffffffffffffffffffffff +# read-key = 0x00000000000000000000000000000000 +# ping.req-id = 0x00000001 +# ping.enr-seq = 2 + +00000000000000000000000000000000088b3d4342774649325f313964a39e55 +ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3 +4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc diff --git a/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt new file mode 100644 index 0000000000..1a75f525ee --- /dev/null +++ b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt @@ -0,0 +1,9 @@ +# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb +# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9 +# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000 +# whoareyou.request-nonce = 0x0102030405060708090a0b0c +# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10 +# whoareyou.enr-seq = 0 + +00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad +1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d diff --git a/p2p/netutil/error.go b/p2p/netutil/error.go index cb21b9cd4c..5d3d9bfd65 100644 --- a/p2p/netutil/error.go +++ b/p2p/netutil/error.go @@ -23,3 +23,11 @@ func IsTemporaryError(err error) bool { }) return ok && tempErr.Temporary() || isPacketTooBig(err) } + +// IsTimeout checks whether the given error is a timeout. +func IsTimeout(err error) bool { + timeoutErr, ok := err.(interface { + Timeout() bool + }) + return ok && timeoutErr.Timeout() +} From 8c2f271528f9cccf541c6ea1c022e98407f26872 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 15 Oct 2020 12:29:42 +0200 Subject: [PATCH 092/948] params: go-ethereum v1.9.23 stable --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index d9dbeea19a..77cbbe7800 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 23 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 23 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string. From 51d026ca8554a04fccd5c5008aadffbd5c0594c7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 15 Oct 2020 12:30:41 +0200 Subject: [PATCH 093/948] params: begin v1.9.24 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 77cbbe7800..656a78e58f 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 23 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 24 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From b305591e1491229c35a4551f18bd5e745405700f Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 16 Oct 2020 11:28:03 +0200 Subject: [PATCH 094/948] core/vm: marshall returnData as hexstring in trace logs (#21715) * core/vm: marshall returnData as hexstring in trace logs * core/vm: marshall returnData as hexstring in trace logs --- core/vm/gen_structlog.go | 6 +++--- core/vm/logger.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index ac1a9070c8..44da014de9 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -24,7 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { MemorySize int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"` - ReturnData []byte `json:"returnData"` + ReturnData hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -72,7 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { MemorySize *int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"` - ReturnData []byte `json:"returnData"` + ReturnData *hexutil.Bytes `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` @@ -113,7 +113,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { } } if dec.ReturnData != nil { - s.ReturnData = dec.ReturnData + s.ReturnData = *dec.ReturnData } if dec.Storage != nil { s.Storage = dec.Storage diff --git a/core/vm/logger.go b/core/vm/logger.go index 3b166b5d26..c07e7787ca 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -82,6 +82,7 @@ type structLogMarshaling struct { Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 Memory hexutil.Bytes + ReturnData hexutil.Bytes OpName string `json:"opName"` // adds call to OpName() in MarshalJSON ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON } From cef3e2dc5a563fccb4c54b7bea2fddc071bf9541 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 20 Oct 2020 10:56:51 +0200 Subject: [PATCH 095/948] console: don't exit on ctrl-c, only on ctrl-d (#21660) * add interrupt counter * remove interrupt counter, allow ctrl-C to clear ONLY, ctrl-D will terminate console, stop node * format * add instructions to exit * fix tests --- cmd/geth/consolecmd_test.go | 2 ++ console/console.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 6c100e18d9..913b060361 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -66,6 +66,7 @@ at block: 0 ({{niltime}}) datadir: {{.Datadir}} modules: {{apis}} +To exit, press ctrl-d > {{.InputLine "exit"}} `) geth.ExpectExit() @@ -159,6 +160,7 @@ at block: 0 ({{niltime}}){{if ipc}} datadir: {{datadir}}{{end}} modules: {{apis}} +To exit, press ctrl-d > {{.InputLine "exit" }} `) attach.ExpectExit() diff --git a/console/console.go b/console/console.go index 1dcad3065e..ae9f28da04 100644 --- a/console/console.go +++ b/console/console.go @@ -324,6 +324,7 @@ func (c *Console) Welcome() { sort.Strings(modules) message += " modules: " + strings.Join(modules, " ") + "\n" } + message += "\nTo exit, press ctrl-d" fmt.Fprintln(c.printer, message) } @@ -372,7 +373,7 @@ func (c *Console) Interactive() { return case err := <-inputErr: - if err == liner.ErrPromptAborted && indents > 0 { + if err == liner.ErrPromptAborted { // When prompting for multi-line input, the first Ctrl-C resets // the multi-line state. prompt, indents, input = c.prompt, 0, "" From 6e7137103c63f471bf577d857f179110622d8804 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 20 Oct 2020 10:58:26 +0200 Subject: [PATCH 096/948] miner: fixed race condition in tests (#21664) --- miner/miner_test.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/miner/miner_test.go b/miner/miner_test.go index 43b31872a4..127b4c7687 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -22,7 +22,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -243,26 +242,20 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux) { if err != nil { t.Fatalf("can't create new chain config: %v", err) } - // Create event Mux - mux := new(event.TypeMux) // Create consensus engine - engine := ethash.New(ethash.Config{}, []string{}, false) - engine.SetThreads(-1) - // Create isLocalBlock - isLocalBlock := func(block *types.Block) bool { - return true - } + engine := clique.New(chainConfig.Clique, chainDB) // Create Ethereum backend - limit := uint64(1000) - bc, err := core.NewBlockChain(chainDB, new(core.CacheConfig), chainConfig, engine, vm.Config{}, isLocalBlock, &limit) + bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("can't create new chain %v", err) } - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(chainDB), nil) blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} - pool := core.NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) + pool := core.NewTxPool(testTxPoolConfig, chainConfig, blockchain) backend := NewMockBackend(bc, pool) + // Create event Mux + mux := new(event.TypeMux) // Create Miner - return New(backend, &config, chainConfig, mux, engine, isLocalBlock), mux + return New(backend, &config, chainConfig, mux, engine, nil), mux } From 9d25f34263fa0621bd0b70a53057df7c6a5eaa41 Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 20 Oct 2020 22:34:50 +0800 Subject: [PATCH 097/948] core: track and improve tx indexing/unindexing (#21331) * core: add background indexer to waitgroup * core: make indexer stopable * core/rawdb: add unit tests * core/rawdb: fix lint * core/rawdb: fix tests * core/rawdb: fix linter --- core/blockchain.go | 18 ++- core/rawdb/chain_iterator.go | 180 ++++++++++++++++++++---------- core/rawdb/chain_iterator_test.go | 85 +++++++++++++- 3 files changed, 217 insertions(+), 66 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index ce1edd9b7f..356340ae19 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -345,6 +345,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par go bc.update() if txLookupLimit != nil { bc.txLookupLimit = *txLookupLimit + + bc.wg.Add(1) go bc.maintainTxIndex(txIndexBlock) } // If periodic cache journal is required, spin it up. @@ -2230,6 +2232,8 @@ func (bc *BlockChain) update() { // sync, Geth will automatically construct the missing indices and delete // the extra indices. func (bc *BlockChain) maintainTxIndex(ancients uint64) { + defer bc.wg.Done() + // Before starting the actual maintenance, we need to handle a special case, // where user might init Geth with an external ancient database. If so, we // need to reindex all necessary transactions before starting to process any @@ -2239,7 +2243,7 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit { from = ancients - bc.txLookupLimit } - rawdb.IndexTransactions(bc.db, from, ancients) + rawdb.IndexTransactions(bc.db, from, ancients, bc.quit) } // indexBlocks reindexes or unindexes transactions depending on user configuration indexBlocks := func(tail *uint64, head uint64, done chan struct{}) { @@ -2253,24 +2257,24 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { rawdb.WriteTxIndexTail(bc.db, 0) } else { // Prune all stale tx indices and record the tx index tail - rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1) + rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit) } return } // If a previous indexing existed, make sure that we fill in any missing entries if bc.txLookupLimit == 0 || head < bc.txLookupLimit { if *tail > 0 { - rawdb.IndexTransactions(bc.db, 0, *tail) + rawdb.IndexTransactions(bc.db, 0, *tail, bc.quit) } return } // Update the transaction index to the new chain state if head-bc.txLookupLimit+1 < *tail { // Reindex a part of missing indices and rewind index tail to HEAD-limit - rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail) + rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit) } else { // Unindex a part of stale indices and forward index tail to HEAD-limit - rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1) + rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit) } } // Any reindexing done, start listening to chain events and moving the index window @@ -2294,6 +2298,10 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { case <-done: done = nil case <-bc.quit: + if done != nil { + log.Info("Waiting background transaction indexer to exit") + <-done + } return } } diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go index 3130e922e8..393b72c26c 100644 --- a/core/rawdb/chain_iterator.go +++ b/core/rawdb/chain_iterator.go @@ -84,15 +84,17 @@ type blockTxHashes struct { } // iterateTransactions iterates over all transactions in the (canon) block -// number(s) given, and yields the hashes on a channel -func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool) (chan *blockTxHashes, chan struct{}) { +// number(s) given, and yields the hashes on a channel. If there is a signal +// received from interrupt channel, the iteration will be aborted and result +// channel will be closed. +func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes { // One thread sequentially reads data from db type numberRlp struct { number uint64 rlp rlp.RawValue } if to == from { - return nil, nil + return nil } threads := to - from if cpus := runtime.NumCPU(); threads > uint64(cpus) { @@ -101,7 +103,6 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool var ( rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh - abortCh = make(chan struct{}) ) // lookup runs in one instance lookup := func() { @@ -115,7 +116,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool // Feed the block to the aggregator, or abort on interrupt select { case rlpCh <- &numberRlp{n, data}: - case <-abortCh: + case <-interrupt: return } if reverse { @@ -168,7 +169,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool // Feed the block to the aggregator, or abort on interrupt select { case hashesCh <- result: - case <-abortCh: + case <-interrupt: return } } @@ -177,25 +178,28 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool for i := 0; i < int(threads); i++ { go process() } - return hashesCh, abortCh + return hashesCh } -// IndexTransactions creates txlookup indices of the specified block range. +// indexTransactions creates txlookup indices of the specified block range. // // This function iterates canonical chain in reverse order, it has one main advantage: // We can write tx index tail flag periodically even without the whole indexing // procedure is finished. So that we can resume indexing procedure next time quickly. -func IndexTransactions(db ethdb.Database, from uint64, to uint64) { +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { // short circuit for invalid range if from >= to { return } var ( - hashesCh, abortCh = iterateTransactions(db, from, to, true) - batch = db.NewBatch() - start = time.Now() - logged = start.Add(-7 * time.Second) - // Since we iterate in reverse, we expect the first number to come + hashesCh = iterateTransactions(db, from, to, true, interrupt) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + // Since we iterate in reverse, we expect the first number to come // in to be [to-1]. Therefore, setting lastNum to means that the // prqueue gap-evaluation will work correctly lastNum = to @@ -203,8 +207,6 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) { // for stats reporting blocks, txs = 0, 0 ) - defer close(abortCh) - for chanDelivery := range hashesCh { // Push the delivery into the queue and process contiguous ranges. // Since we iterate in reverse, so lower numbers have lower prio, and @@ -215,6 +217,10 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) { if _, priority := queue.Peek(); priority != int64(lastNum-1) { break } + // For testing + if hook != nil && !hook(lastNum-1) { + break + } // Next block available, pop it off and index it delivery := queue.PopItem().(*blockTxHashes) lastNum = delivery.number @@ -223,8 +229,7 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) { txs += len(delivery.hashes) // If enough data was accumulated in memory or we're at the last block, dump to disk if batch.ValueSize() > ethdb.IdealBatchSize { - // Also write the tail there - WriteTxIndexTail(batch, lastNum) + WriteTxIndexTail(batch, lastNum) // Also write the tail here if err := batch.Write(); err != nil { log.Crit("Failed writing batch to db", "error", err) return @@ -238,67 +243,122 @@ func IndexTransactions(db ethdb.Database, from uint64, to uint64) { } } } - if lastNum < to { - WriteTxIndexTail(batch, lastNum) - // No need to write the batch if we never entered the loop above... + // If there exists uncommitted data, flush them. + if batch.ValueSize() > 0 { + WriteTxIndexTail(batch, lastNum) // Also write the tail there if err := batch.Write(); err != nil { log.Crit("Failed writing batch to db", "error", err) return } } - log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + select { + case <-interrupt: + log.Debug("Transaction indexing interrupted", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + default: + log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + } } -// UnindexTransactions removes txlookup indices of the specified block range. -func UnindexTransactions(db ethdb.Database, from uint64, to uint64) { +// IndexTransactions creates txlookup indices of the specified block range. +// +// This function iterates canonical chain in reverse order, it has one main advantage: +// We can write tx index tail flag periodically even without the whole indexing +// procedure is finished. So that we can resume indexing procedure next time quickly. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func IndexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) { + indexTransactions(db, from, to, interrupt, nil) +} + +// indexTransactionsForTesting is the internal debug version with an additional hook. +func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + indexTransactions(db, from, to, interrupt, hook) +} + +// unindexTransactions removes txlookup indices of the specified block range. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { // short circuit for invalid range if from >= to { return } - // Write flag first and then unindex the transaction indices. Some indices - // will be left in the database if crash happens but it's fine. - WriteTxIndexTail(db, to) - // If only one block is unindexed, do it directly - //if from+1 == to { - // data := ReadCanonicalBodyRLP(db, uint64(from)) - // DeleteTxLookupEntries(db, ReadBlock(db, ReadCanonicalHash(db, from), from)) - // log.Info("Unindexed transactions", "blocks", 1, "tail", to) - // return - //} - // TODO @holiman, add this back (if we want it) var ( - hashesCh, abortCh = iterateTransactions(db, from, to, false) - batch = db.NewBatch() - start = time.Now() - logged = start.Add(-7 * time.Second) + hashesCh = iterateTransactions(db, from, to, false, interrupt) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + // we expect the first number to come in to be [from]. Therefore, setting + // nextNum to from means that the prqueue gap-evaluation will work correctly + nextNum = from + queue = prque.New(nil) + // for stats reporting + blocks, txs = 0, 0 ) - defer close(abortCh) // Otherwise spin up the concurrent iterator and unindexer - blocks, txs := 0, 0 for delivery := range hashesCh { - DeleteTxLookupEntries(batch, delivery.hashes) - txs += len(delivery.hashes) - blocks++ + // Push the delivery into the queue and process contiguous ranges. + queue.Push(delivery, -int64(delivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); -priority != int64(nextNum) { + break + } + // For testing + if hook != nil && !hook(nextNum) { + break + } + delivery := queue.PopItem().(*blockTxHashes) + nextNum = delivery.number + 1 + DeleteTxLookupEntries(batch, delivery.hashes) + txs += len(delivery.hashes) + blocks++ - // If enough data was accumulated in memory or we're at the last block, dump to disk - // A batch counts the size of deletion as '1', so we need to flush more - // often than that. - if blocks%1000 == 0 { - if err := batch.Write(); err != nil { - log.Crit("Failed writing batch to db", "error", err) - return + // If enough data was accumulated in memory or we're at the last block, dump to disk + // A batch counts the size of deletion as '1', so we need to flush more + // often than that. + if blocks%1000 == 0 { + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() } - batch.Reset() } - // If we've spent too much time already, notify the user of what we're doing - if time.Since(logged) > 8*time.Second { - log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) - logged = time.Now() + } + // Commit the last batch if there exists uncommitted data + if batch.ValueSize() > 0 { + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return } } - if err := batch.Write(); err != nil { - log.Crit("Failed writing batch to db", "error", err) - return + select { + case <-interrupt: + log.Debug("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + default: + log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) } - log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) +} + +// UnindexTransactions removes txlookup indices of the specified block range. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) { + unindexTransactions(db, from, to, interrupt, nil) +} + +// unindexTransactionsForTesting is the internal debug version with an additional hook. +func unindexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + unindexTransactions(db, from, to, interrupt, hook) } diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index c635cd2f12..90b2639d38 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -20,6 +20,7 @@ import ( "math/big" "reflect" "sort" + "sync" "testing" "github.com/ethereum/go-ethereum/common" @@ -59,7 +60,7 @@ func TestChainIterator(t *testing.T) { } for i, c := range cases { var numbers []int - hashCh, _ := iterateTransactions(chainDb, c.from, c.to, c.reverse) + hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil) if hashCh != nil { for h := range hashCh { numbers = append(numbers, int(h.number)) @@ -80,3 +81,85 @@ func TestChainIterator(t *testing.T) { } } } + +func TestIndexTransactions(t *testing.T) { + // Construct test chain db + chainDb := NewMemoryDatabase() + + var block *types.Block + var txs []*types.Transaction + for i := uint64(0); i <= 10; i++ { + if i == 0 { + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, nil, nil, nil, newHasher()) // Empty genesis block + } else { + tx := types.NewTransaction(i, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + } + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + } + // verify checks whether the tx indices in the range [from, to) + // is expected. + verify := func(from, to int, exist bool, tail uint64) { + for i := from; i < to; i++ { + if i == 0 { + continue + } + number := ReadTxLookupEntry(chainDb, txs[i-1].Hash()) + if exist && number == nil { + t.Fatalf("Transaction indice missing") + } + if !exist && number != nil { + t.Fatalf("Transaction indice is not deleted") + } + } + number := ReadTxIndexTail(chainDb) + if number == nil || *number != tail { + t.Fatalf("Transaction tail mismatch") + } + } + IndexTransactions(chainDb, 5, 11, nil) + verify(5, 11, true, 5) + verify(0, 5, false, 5) + + IndexTransactions(chainDb, 0, 5, nil) + verify(0, 11, true, 0) + + UnindexTransactions(chainDb, 0, 5, nil) + verify(5, 11, true, 5) + verify(0, 5, false, 5) + + UnindexTransactions(chainDb, 5, 11, nil) + verify(0, 11, false, 11) + + // Testing corner cases + signal := make(chan struct{}) + var once sync.Once + indexTransactionsForTesting(chainDb, 5, 11, signal, func(n uint64) bool { + if n <= 8 { + once.Do(func() { + close(signal) + }) + return false + } + return true + }) + verify(9, 11, true, 9) + verify(0, 9, false, 9) + IndexTransactions(chainDb, 0, 9, nil) + + signal = make(chan struct{}) + var once2 sync.Once + unindexTransactionsForTesting(chainDb, 0, 11, signal, func(n uint64) bool { + if n >= 8 { + once2.Do(func() { + close(signal) + }) + return false + } + return true + }) + verify(8, 11, true, 8) + verify(0, 8, false, 8) +} From 3e82c9ef67b9948bc90ba244b9537054188ebaf8 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Tue, 20 Oct 2020 14:19:21 -0400 Subject: [PATCH 098/948] eth/api: fix potential nil deref in AccountRange (#21710) * Fix potential nil pointer error when neither block number nor hash is specified to accountRange * Update error description --- eth/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eth/api.go b/eth/api.go index 76118e2d7f..fd35656476 100644 --- a/eth/api.go +++ b/eth/api.go @@ -389,6 +389,8 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta if err != nil { return state.IteratorDump{}, err } + } else { + return state.IteratorDump{}, errors.New("either block number or block hash must be specified") } if maxResults > AccountRangeMaxResults || maxResults <= 0 { From 85d81b2cdde6f5377fa3af6e108ca0b84a6266bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Wed, 21 Oct 2020 10:56:33 +0200 Subject: [PATCH 099/948] les: remove clientPeerSet and serverSet (#21566) * les: move NodeStateMachine from clientPool to LesServer * les: new header broadcaster * les: peerCommons.headInfo always contains last announced head * les: remove clientPeerSet and serverSet * les: fixed panic * les: fixed --nodiscover option * les: disconnect all peers at ns.Stop() * les: added comments and fixed signed broadcasts * les: removed unused parameter, fixed tests --- les/client_handler.go | 8 +- les/clientpool.go | 49 +++---- les/clientpool_test.go | 43 ++++-- les/enr_entry.go | 2 +- les/lespay/server/prioritypool.go | 6 +- les/peer.go | 223 ++++-------------------------- les/protocol.go | 10 +- les/server.go | 64 ++++++--- les/server_handler.go | 150 +++++++++++++------- les/test_helper.go | 16 ++- 10 files changed, 239 insertions(+), 332 deletions(-) diff --git a/les/client_handler.go b/les/client_handler.go index cfeec7a03c..77a0ea5c6f 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -102,13 +102,7 @@ func (h *clientHandler) handle(p *serverPeer) error { p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) // Execute the LES handshake - var ( - head = h.backend.blockchain.CurrentHeader() - hash = head.Hash() - number = head.Number.Uint64() - td = h.backend.blockchain.GetTd(hash, number) - ) - if err := p.Handshake(td, hash, number, h.backend.blockchain.Genesis().Hash(), nil); err != nil { + if err := p.Handshake(h.backend.blockchain.Genesis().Hash()); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } diff --git a/les/clientpool.go b/les/clientpool.go index 4f6e3fafe0..da0db6e622 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -18,7 +18,6 @@ package les import ( "fmt" - "reflect" "sync" "time" @@ -46,19 +45,6 @@ const ( inactiveTimeout = time.Second * 10 ) -var ( - clientPoolSetup = &nodestate.Setup{} - clientField = clientPoolSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{})) - connAddressField = clientPoolSetup.NewField("connAddr", reflect.TypeOf("")) - balanceTrackerSetup = lps.NewBalanceTrackerSetup(clientPoolSetup) - priorityPoolSetup = lps.NewPriorityPoolSetup(clientPoolSetup) -) - -func init() { - balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField) - priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority -} - // clientPool implements a client database that assigns a priority to each client // based on a positive and negative balance. Positive balance is externally assigned // to prioritized clients and is decreased with connection time and processed @@ -119,8 +105,7 @@ type clientInfo struct { } // newClientPool creates a new client pool -func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { - ns := nodestate.NewNodeStateMachine(nil, nil, clock, clientPoolSetup) +func newClientPool(ns *nodestate.NodeStateMachine, lespayDb ethdb.Database, minCap uint64, connectedBias time.Duration, clock mclock.Clock, removePeer func(enode.ID)) *clientPool { pool := &clientPool{ ns: ns, BalanceTrackerSetup: balanceTrackerSetup, @@ -147,7 +132,7 @@ func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Du }) ns.SubscribeState(pool.ActiveFlag.Or(pool.PriorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - c, _ := ns.GetField(node, clientField).(*clientInfo) + c, _ := ns.GetField(node, clientInfoField).(*clientInfo) if c == nil { return } @@ -172,7 +157,7 @@ func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Du if oldState.Equals(pool.ActiveFlag) && newState.Equals(pool.InactiveFlag) { clientDeactivatedMeter.Mark(1) log.Debug("Client deactivated", "id", node.ID()) - c, _ := ns.GetField(node, clientField).(*clientInfo) + c, _ := ns.GetField(node, clientInfoField).(*clientInfo) if c == nil || !c.peer.allowInactive() { pool.removePeer(node.ID()) } @@ -190,13 +175,11 @@ func newClientPool(lespayDb ethdb.Database, minCap uint64, connectedBias time.Du newCap, _ := newValue.(uint64) totalConnected += newCap - oldCap totalConnectedGauge.Update(int64(totalConnected)) - c, _ := ns.GetField(node, clientField).(*clientInfo) + c, _ := ns.GetField(node, clientInfoField).(*clientInfo) if c != nil { c.peer.updateCapacity(newCap) } }) - - ns.Start() return pool } @@ -210,7 +193,6 @@ func (f *clientPool) stop() { f.disconnectNode(node) }) f.bt.Stop() - f.ns.Stop() } // connect should be called after a successful handshake. If the connection was @@ -225,7 +207,7 @@ func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) { } // Dedup connected peers. node, freeID := peer.Node(), peer.freeClientId() - if f.ns.GetField(node, clientField) != nil { + if f.ns.GetField(node, clientInfoField) != nil { log.Debug("Client already connected", "address", freeID, "id", node.ID().String()) return 0, fmt.Errorf("Client already connected address=%s id=%s", freeID, node.ID().String()) } @@ -237,7 +219,7 @@ func (f *clientPool) connect(peer clientPoolPeer) (uint64, error) { connected: true, connectedAt: now, } - f.ns.SetField(node, clientField, c) + f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, freeID) if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { f.disconnect(peer) @@ -280,7 +262,7 @@ func (f *clientPool) disconnect(p clientPoolPeer) { // disconnectNode removes node fields and flags related to connected status func (f *clientPool) disconnectNode(node *enode.Node) { f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientField, nil) + f.ns.SetField(node, clientInfoField, nil) } // setDefaultFactors sets the default price factors applied to subsequently connected clients @@ -299,7 +281,8 @@ func (f *clientPool) capacityInfo() (uint64, uint64, uint64) { defer f.lock.Unlock() // total priority active cap will be supported when the token issuer module is added - return f.capLimit, f.pp.ActiveCapacity(), 0 + _, activeCap := f.pp.Active() + return f.capLimit, activeCap, 0 } // setLimits sets the maximum number and total capacity of connected clients, @@ -314,13 +297,13 @@ func (f *clientPool) setLimits(totalConn int, totalCap uint64) { // setCapacity sets the assigned capacity of a connected client func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint64, bias time.Duration, setCap bool) (uint64, error) { - c, _ := f.ns.GetField(node, clientField).(*clientInfo) + c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) if c == nil { if setCap { return 0, fmt.Errorf("client %064x is not connected", node.ID()) } c = &clientInfo{node: node} - f.ns.SetField(node, clientField, c) + f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, freeID) if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance == nil { log.Error("BalanceField is missing", "node", node.ID()) @@ -328,7 +311,7 @@ func (f *clientPool) setCapacity(node *enode.Node, freeID string, capacity uint6 } defer func() { f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientField, nil) + f.ns.SetField(node, clientInfoField, nil) }() } var ( @@ -370,7 +353,7 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { if len(ids) == 0 { f.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - c, _ := f.ns.GetField(node, clientField).(*clientInfo) + c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) if c != nil { cb(c) } @@ -381,12 +364,12 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { if node == nil { node = enode.SignNull(&enr.Record{}, id) } - c, _ := f.ns.GetField(node, clientField).(*clientInfo) + c, _ := f.ns.GetField(node, clientInfoField).(*clientInfo) if c != nil { cb(c) } else { c = &clientInfo{node: node} - f.ns.SetField(node, clientField, c) + f.ns.SetField(node, clientInfoField, c) f.ns.SetField(node, connAddressField, "") if c.balance, _ = f.ns.GetField(node, f.BalanceField).(*lps.NodeBalance); c.balance != nil { cb(c) @@ -394,7 +377,7 @@ func (f *clientPool) forClients(ids []enode.ID, cb func(client *clientInfo)) { log.Error("BalanceField is missing") } f.ns.SetField(node, connAddressField, nil) - f.ns.SetField(node, clientField, nil) + f.ns.SetField(node, clientInfoField, nil) } } } diff --git a/les/clientpool_test.go b/les/clientpool_test.go index cfd1486b43..b1c38d374c 100644 --- a/les/clientpool_test.go +++ b/les/clientpool_test.go @@ -64,6 +64,11 @@ type poolTestPeer struct { inactiveAllowed bool } +func testStateMachine() *nodestate.NodeStateMachine { + return nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) + +} + func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer { return &poolTestPeer{ index: i, @@ -91,7 +96,7 @@ func (i *poolTestPeer) allowInactive() bool { } func getBalance(pool *clientPool, p *poolTestPeer) (pos, neg uint64) { - temp := pool.ns.GetField(p.node, clientField) == nil + temp := pool.ns.GetField(p.node, clientInfoField) == nil if temp { pool.ns.SetField(p.node, connAddressField, p.freeClientId()) } @@ -128,8 +133,9 @@ func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, rando disconnFn = func(id enode.ID) { disconnCh <- int(id[0]) + int(id[1])<<8 } - pool = newClientPool(db, 1, 0, &clock, disconnFn) + pool = newClientPool(testStateMachine(), db, 1, 0, &clock, disconnFn) ) + pool.ns.Start() pool.setLimits(activeLimit, uint64(activeLimit)) pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -233,7 +239,8 @@ func TestConnectPaidClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -248,7 +255,8 @@ func TestConnectPaidClientToSmallPool(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -266,7 +274,8 @@ func TestConnectPaidClientToFullPool(t *testing.T) { db = rawdb.NewMemoryDatabase() ) removeFn := func(enode.ID) {} // Noop - pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -295,7 +304,8 @@ func TestPaidClientKickedOut(t *testing.T) { removeFn := func(id enode.ID) { kickedCh <- int(id[0]) } - pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool.ns.Start() pool.bt.SetExpirationTCs(0, 0) defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 @@ -325,7 +335,8 @@ func TestConnectFreeClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -341,7 +352,8 @@ func TestConnectFreeClientToFullPool(t *testing.T) { db = rawdb.NewMemoryDatabase() ) removeFn := func(enode.ID) {} // Noop - pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -370,7 +382,8 @@ func TestFreeClientKickedOut(t *testing.T) { kicked = make(chan int, 100) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } - pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -411,7 +424,8 @@ func TestPositiveBalanceCalculation(t *testing.T) { kicked = make(chan int, 10) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -434,7 +448,8 @@ func TestDowngradePriorityClient(t *testing.T) { kicked = make(chan int, 10) ) removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop - pool := newClientPool(db, 1, defaultConnectedBias, &clock, removeFn) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, removeFn) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) @@ -468,7 +483,8 @@ func TestNegativeBalanceCalculation(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool.ns.Start() defer pool.stop() pool.setLimits(10, uint64(10)) // Total capacity limit is 10 pool.setDefaultFactors(lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) @@ -503,7 +519,8 @@ func TestInactiveClient(t *testing.T) { clock mclock.Simulated db = rawdb.NewMemoryDatabase() ) - pool := newClientPool(db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool := newClientPool(testStateMachine(), db, 1, defaultConnectedBias, &clock, func(id enode.ID) {}) + pool.ns.Start() defer pool.stop() pool.setLimits(2, uint64(2)) diff --git a/les/enr_entry.go b/les/enr_entry.go index 65d0d1fdb4..11e6273be5 100644 --- a/les/enr_entry.go +++ b/les/enr_entry.go @@ -36,7 +36,7 @@ func (e lesEntry) ENRKey() string { // setupDiscovery creates the node discovery source for the eth protocol. func (eth *LightEthereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) { - if /*cfg.NoDiscovery || */ len(eth.config.DiscoveryURLs) == 0 { + if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 { return nil, nil } client := dnsdisc.NewClient(dnsdisc.Config{}) diff --git a/les/lespay/server/prioritypool.go b/les/lespay/server/prioritypool.go index 52224e093e..c0c33840ca 100644 --- a/les/lespay/server/prioritypool.go +++ b/les/lespay/server/prioritypool.go @@ -253,12 +253,12 @@ func (pp *PriorityPool) SetActiveBias(bias time.Duration) { pp.tryActivate() } -// ActiveCapacity returns the total capacity of currently active nodes -func (pp *PriorityPool) ActiveCapacity() uint64 { +// Active returns the number and total capacity of currently active nodes +func (pp *PriorityPool) Active() (uint64, uint64) { pp.lock.Lock() defer pp.lock.Unlock() - return pp.activeCap + return pp.activeCount, pp.activeCap } // inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue diff --git a/les/peer.go b/les/peer.go index 0549daf9a6..2b0117bedc 100644 --- a/les/peer.go +++ b/les/peer.go @@ -126,7 +126,7 @@ type peerCommons struct { frozen uint32 // Flag whether the peer is frozen. announceType uint64 // New block announcement type. serving uint32 // The status indicates the peer is served. - headInfo blockInfo // Latest block information. + headInfo blockInfo // Last announced block information. // Background task queue for caching peer tasks and executing in order. sendQueue *utils.ExecQueue @@ -255,6 +255,8 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g // Add some basic handshake fields send = send.add("protocolVersion", uint64(p.version)) send = send.add("networkId", p.network) + // Note: the head info announced at handshake is only used in case of server peers + // but dummy values are still announced by clients for compatibility with older servers send = send.add("headTd", td) send = send.add("headHash", head) send = send.add("headNum", headNum) @@ -273,24 +275,14 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g if size > allowedUpdateBytes { return errResp(ErrRequestRejected, "") } - var rGenesis, rHash common.Hash - var rVersion, rNetwork, rNum uint64 - var rTd *big.Int + var rGenesis common.Hash + var rVersion, rNetwork uint64 if err := recv.get("protocolVersion", &rVersion); err != nil { return err } if err := recv.get("networkId", &rNetwork); err != nil { return err } - if err := recv.get("headTd", &rTd); err != nil { - return err - } - if err := recv.get("headHash", &rHash); err != nil { - return err - } - if err := recv.get("headNum", &rNum); err != nil { - return err - } if err := recv.get("genesisHash", &rGenesis); err != nil { return err } @@ -303,7 +295,6 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g if int(rVersion) != p.version { return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) } - p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd} if recvCallback != nil { return recvCallback(recv) } @@ -569,9 +560,11 @@ func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) { } // Handshake executes the les protocol handshake, negotiating version number, -// network IDs, difficulties, head and genesis blocks. -func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error { - return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) { +// network IDs and genesis blocks. +func (p *serverPeer) Handshake(genesis common.Hash) error { + // Note: there is no need to share local head with a server but older servers still + // require these fields so we announce zero values. + return p.handshake(common.Big0, common.Hash{}, 0, genesis, func(lists *keyValueList) { // Add some client-specific handshake fields // // Enable signed announcement randomly even the server is not trusted. @@ -581,6 +574,21 @@ func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge } *lists = (*lists).add("announceType", p.announceType) }, func(recv keyValueMap) error { + var ( + rHash common.Hash + rNum uint64 + rTd *big.Int + ) + if err := recv.get("headTd", &rTd); err != nil { + return err + } + if err := recv.get("headHash", &rHash); err != nil { + return err + } + if err := recv.get("headNum", &rNum); err != nil { + return err + } + p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd} if recv.get("serveChainSince", &p.chainSince) != nil { p.onlyAnnounce = true } @@ -937,6 +945,9 @@ func (p *clientPeer) freezeClient() { // Handshake executes the les protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error { + // Note: clientPeer.headInfo should contain the last head announced to the client by us. + // The values announced in the handshake are dummy values for compatibility reasons and should be ignored. + p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td} return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) { // Add some information which services server can offer. if !server.config.UltraLightOnlyAnnounce { @@ -1009,145 +1020,6 @@ type serverPeerSubscriber interface { unregisterPeer(*serverPeer) } -// clientPeerSubscriber is an interface to notify services about added or -// removed client peers -type clientPeerSubscriber interface { - registerPeer(*clientPeer) - unregisterPeer(*clientPeer) -} - -// clientPeerSet represents the set of active client peers currently -// participating in the Light Ethereum sub-protocol. -type clientPeerSet struct { - peers map[string]*clientPeer - // subscribers is a batch of subscribers and peerset will notify - // these subscribers when the peerset changes(new client peer is - // added or removed) - subscribers []clientPeerSubscriber - closed bool - lock sync.RWMutex -} - -// newClientPeerSet creates a new peer set to track the client peers. -func newClientPeerSet() *clientPeerSet { - return &clientPeerSet{peers: make(map[string]*clientPeer)} -} - -// subscribe adds a service to be notified about added or removed -// peers and also register all active peers into the given service. -func (ps *clientPeerSet) subscribe(sub clientPeerSubscriber) { - ps.lock.Lock() - defer ps.lock.Unlock() - - ps.subscribers = append(ps.subscribers, sub) - for _, p := range ps.peers { - sub.registerPeer(p) - } -} - -// unSubscribe removes the specified service from the subscriber pool. -func (ps *clientPeerSet) unSubscribe(sub clientPeerSubscriber) { - ps.lock.Lock() - defer ps.lock.Unlock() - - for i, s := range ps.subscribers { - if s == sub { - ps.subscribers = append(ps.subscribers[:i], ps.subscribers[i+1:]...) - return - } - } -} - -// register adds a new peer into the peer set, or returns an error if the -// peer is already known. -func (ps *clientPeerSet) register(peer *clientPeer) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - if ps.closed { - return errClosed - } - if _, exist := ps.peers[peer.id]; exist { - return errAlreadyRegistered - } - ps.peers[peer.id] = peer - for _, sub := range ps.subscribers { - sub.registerPeer(peer) - } - return nil -} - -// unregister removes a remote peer from the peer set, disabling any further -// actions to/from that particular entity. It also initiates disconnection -// at the networking layer. -func (ps *clientPeerSet) unregister(id string) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - p, ok := ps.peers[id] - if !ok { - return errNotRegistered - } - delete(ps.peers, id) - for _, sub := range ps.subscribers { - sub.unregisterPeer(p) - } - p.Peer.Disconnect(p2p.DiscRequested) - return nil -} - -// ids returns a list of all registered peer IDs -func (ps *clientPeerSet) ids() []string { - ps.lock.RLock() - defer ps.lock.RUnlock() - - var ids []string - for id := range ps.peers { - ids = append(ids, id) - } - return ids -} - -// peer retrieves the registered peer with the given id. -func (ps *clientPeerSet) peer(id string) *clientPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// len returns if the current number of peers in the set. -func (ps *clientPeerSet) len() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return len(ps.peers) -} - -// allClientPeers returns all client peers in a list. -func (ps *clientPeerSet) allPeers() []*clientPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*clientPeer, 0, len(ps.peers)) - for _, p := range ps.peers { - list = append(list, p) - } - return list -} - -// close disconnects all peers. No new peers can be registered -// after close has returned. -func (ps *clientPeerSet) close() { - ps.lock.Lock() - defer ps.lock.Unlock() - - for _, p := range ps.peers { - p.Disconnect(p2p.DiscQuitting) - } - ps.closed = true -} - // serverPeerSet represents the set of active server peers currently // participating in the Light Ethereum sub-protocol. type serverPeerSet struct { @@ -1298,42 +1170,3 @@ func (ps *serverPeerSet) close() { } ps.closed = true } - -// serverSet is a special set which contains all connected les servers. -// Les servers will also be discovered by discovery protocol because they -// also run the LES protocol. We can't drop them although they are useless -// for us(server) but for other protocols(e.g. ETH) upon the devp2p they -// may be useful. -type serverSet struct { - lock sync.Mutex - set map[string]*clientPeer - closed bool -} - -func newServerSet() *serverSet { - return &serverSet{set: make(map[string]*clientPeer)} -} - -func (s *serverSet) register(peer *clientPeer) error { - s.lock.Lock() - defer s.lock.Unlock() - - if s.closed { - return errClosed - } - if _, exist := s.set[peer.id]; exist { - return errAlreadyRegistered - } - s.set[peer.id] = peer - return nil -} - -func (s *serverSet) close() { - s.lock.Lock() - defer s.lock.Unlock() - - for _, p := range s.set { - p.Disconnect(p2p.DiscQuitting) - } - s.closed = true -} diff --git a/les/protocol.go b/les/protocol.go index 4fd19f9bec..19a9561ce9 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -174,12 +174,6 @@ var errorToString = map[int]string{ ErrMissingKey: "Key missing from list", } -type announceBlock struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - Td *big.Int // Total difficulty of one particular block being announced -} - // announceData is the network packet for the block announcements. type announceData struct { Hash common.Hash // Hash of one particular block being announced @@ -199,7 +193,7 @@ func (a *announceData) sanityCheck() error { // sign adds a signature to the block announcement by the given privKey func (a *announceData) sign(privKey *ecdsa.PrivateKey) { - rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td}) + rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td}) sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey) a.Update = a.Update.add("sign", sig) } @@ -210,7 +204,7 @@ func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error { if err := update.get("sign", &sig); err != nil { return err } - rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td}) + rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td}) recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig) if err != nil { return err diff --git a/les/server.go b/les/server.go index 225a7ad1f0..cbedce136c 100644 --- a/les/server.go +++ b/les/server.go @@ -18,6 +18,7 @@ package les import ( "crypto/ecdsa" + "reflect" "time" "github.com/ethereum/go-ethereum/common/mclock" @@ -31,17 +32,32 @@ import ( "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/nodestate" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) +var ( + serverSetup = &nodestate.Setup{} + clientPeerField = serverSetup.NewField("clientPeer", reflect.TypeOf(&clientPeer{})) + clientInfoField = serverSetup.NewField("clientInfo", reflect.TypeOf(&clientInfo{})) + connAddressField = serverSetup.NewField("connAddr", reflect.TypeOf("")) + balanceTrackerSetup = lps.NewBalanceTrackerSetup(serverSetup) + priorityPoolSetup = lps.NewPriorityPoolSetup(serverSetup) +) + +func init() { + balanceTrackerSetup.Connect(connAddressField, priorityPoolSetup.CapacityField) + priorityPoolSetup.Connect(balanceTrackerSetup.BalanceField, balanceTrackerSetup.UpdateFlag) // NodeBalance implements nodePriority +} + type LesServer struct { lesCommons + ns *nodestate.NodeStateMachine archiveMode bool // Flag whether the ethereum node runs in archive mode. - peers *clientPeerSet - serverset *serverSet handler *serverHandler + broadcaster *broadcaster lesTopics []discv5.Topic privateKey *ecdsa.PrivateKey @@ -60,6 +76,7 @@ type LesServer struct { } func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesServer, error) { + ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) // Collect les protocol version information supported by local node. lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions)) for i, pv := range AdvertiseProtocolVersions { @@ -83,9 +100,9 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true), closeCh: make(chan struct{}), }, + ns: ns, archiveMode: e.ArchiveMode(), - peers: newClientPeerSet(), - serverset: newServerSet(), + broadcaster: newBroadcaster(ns), lesTopics: lesTopics, fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), @@ -116,7 +133,7 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer srv.maxCapacity = totalRecharge } srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) - srv.clientPool = newClientPool(srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, func(id enode.ID) { go srv.peers.unregister(id.String()) }) + srv.clientPool = newClientPool(ns, srv.chainDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, srv.dropClient) srv.clientPool.setDefaultFactors(lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}, lps.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}) checkpoint := srv.latestLocalCheckpoint() @@ -130,6 +147,13 @@ func NewLesServer(node *node.Node, e *eth.Ethereum, config *eth.Config) (*LesSer node.RegisterAPIs(srv.APIs()) node.RegisterLifecycle(srv) + // disconnect all peers at nsm shutdown + ns.SubscribeField(clientPeerField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { + if state.Equals(serverSetup.OfflineFlag()) && oldValue != nil { + oldValue.(*clientPeer).Peer.Disconnect(p2p.DiscRequested) + } + }) + ns.Start() return srv, nil } @@ -158,7 +182,7 @@ func (s *LesServer) APIs() []rpc.API { func (s *LesServer) Protocols() []p2p.Protocol { ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} { - if p := s.peers.peer(id.String()); p != nil { + if p := s.getClient(id); p != nil { return p.Info() } return nil @@ -173,6 +197,7 @@ func (s *LesServer) Protocols() []p2p.Protocol { // Start starts the LES server func (s *LesServer) Start() error { s.privateKey = s.p2pSrv.PrivateKey + s.broadcaster.setSignerKey(s.privateKey) s.handler.start() s.wg.Add(1) @@ -198,19 +223,11 @@ func (s *LesServer) Start() error { func (s *LesServer) Stop() error { close(s.closeCh) - // Disconnect existing connections with other LES servers. - s.serverset.close() - - // Disconnect existing sessions. - // This also closes the gate for any new registrations on the peer set. - // sessions which are already established but not added to pm.peers yet - // will exit when they try to register. - s.peers.close() - + s.clientPool.stop() + s.ns.Stop() s.fcManager.Stop() s.costTracker.stop() s.handler.stop() - s.clientPool.stop() // client pool should be closed after handler. s.servingQueue.stop() // Note, bloom trie indexer is closed by parent bloombits indexer. @@ -279,3 +296,18 @@ func (s *LesServer) capacityManagement() { } } } + +func (s *LesServer) getClient(id enode.ID) *clientPeer { + if node := s.ns.GetNode(id); node != nil { + if p, ok := s.ns.GetField(node, clientPeerField).(*clientPeer); ok { + return p + } + } + return nil +} + +func (s *LesServer) dropClient(id enode.ID) { + if p := s.getClient(id); p != nil { + p.Peer.Disconnect(p2p.DiscRequested) + } +} diff --git a/les/server_handler.go b/les/server_handler.go index 583df96008..c657d37f1d 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -17,6 +17,7 @@ package les import ( + "crypto/ecdsa" "encoding/binary" "encoding/json" "errors" @@ -36,6 +37,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nodestate" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -91,7 +94,7 @@ func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb et // start starts the server handler. func (h *serverHandler) start() { h.wg.Add(1) - go h.broadcastHeaders() + go h.broadcastLoop() } // stop stops the server handler. @@ -123,47 +126,58 @@ func (h *serverHandler) handle(p *clientPeer) error { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err } - if p.server { - if err := h.server.serverset.register(p); err != nil { - return err - } - // connected to another server, no messages expected, just wait for disconnection - _, err := p.rw.ReadMsg() - return err - } // Reject light clients if server is not synced. if !h.synced() { p.Log().Debug("Light server not synced, rejecting peer") return p2p.DiscRequested } - defer p.fcClient.Disconnect() + var registered bool + if err := h.server.ns.Operation(func() { + if h.server.ns.GetField(p.Node(), clientPeerField) != nil { + registered = true + } else { + h.server.ns.SetFieldSub(p.Node(), clientPeerField, p) + } + }); err != nil { + return err + } + if registered { + return errAlreadyRegistered + } + + defer func() { + h.server.ns.SetField(p.Node(), clientPeerField, nil) + if p.fcClient != nil { // is nil when connecting another server + p.fcClient.Disconnect() + } + }() + if p.server { + // connected to another server, no messages expected, just wait for disconnection + _, err := p.rw.ReadMsg() + return err + } // Disconnect the inbound peer if it's rejected by clientPool if cap, err := h.server.clientPool.connect(p); cap != p.fcParams.MinRecharge || err != nil { p.Log().Debug("Light Ethereum peer rejected", "err", errFullClientPool) return errFullClientPool } - p.balance, _ = h.server.clientPool.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance) + p.balance, _ = h.server.ns.GetField(p.Node(), h.server.clientPool.BalanceField).(*lps.NodeBalance) if p.balance == nil { return p2p.DiscRequested } - // Register the peer locally - if err := h.server.peers.register(p); err != nil { - h.server.clientPool.disconnect(p) - p.Log().Error("Light Ethereum peer registration failed", "err", err) - return err - } - clientConnectionGauge.Update(int64(h.server.peers.len())) + activeCount, _ := h.server.clientPool.pp.Active() + clientConnectionGauge.Update(int64(activeCount)) var wg sync.WaitGroup // Wait group used to track all in-flight task routines. connectedAt := mclock.Now() defer func() { wg.Wait() // Ensure all background task routines have exited. - h.server.peers.unregister(p.id) h.server.clientPool.disconnect(p) p.balance = nil - clientConnectionGauge.Update(int64(h.server.peers.len())) + activeCount, _ := h.server.clientPool.pp.Active() + clientConnectionGauge.Update(int64(activeCount)) connectionTimer.Update(time.Duration(mclock.Now() - connectedAt)) }() // Mark the peer starts to be served. @@ -911,11 +925,11 @@ func (h *serverHandler) txStatus(hash common.Hash) light.TxStatus { return stat } -// broadcastHeaders broadcasts new block information to all connected light +// broadcastLoop broadcasts new block information to all connected light // clients. According to the agreement between client and server, server should // only broadcast new announcement if the total difficulty is higher than the // last one. Besides server will add the signature if client requires. -func (h *serverHandler) broadcastHeaders() { +func (h *serverHandler) broadcastLoop() { defer h.wg.Done() headCh := make(chan core.ChainHeadEvent, 10) @@ -929,10 +943,6 @@ func (h *serverHandler) broadcastHeaders() { for { select { case ev := <-headCh: - peers := h.server.peers.allPeers() - if len(peers) == 0 { - continue - } header := ev.Block.Header() hash, number := header.Hash(), header.Number.Uint64() td := h.blockchain.GetTd(hash, number) @@ -944,33 +954,73 @@ func (h *serverHandler) broadcastHeaders() { reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(h.chainDb, header, lastHead).Number.Uint64() } lastHead, lastTd = header, td - log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg) - var ( - signed bool - signedAnnounce announceData - ) - announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg} - for _, p := range peers { - p := p - switch p.announceType { - case announceTypeSimple: - if !p.queueSend(func() { p.sendAnnounce(announce) }) { - log.Debug("Drop announcement because queue is full", "number", number, "hash", hash) - } - case announceTypeSigned: - if !signed { - signedAnnounce = announce - signedAnnounce.sign(h.server.privateKey) - signed = true - } - if !p.queueSend(func() { p.sendAnnounce(signedAnnounce) }) { - log.Debug("Drop announcement because queue is full", "number", number, "hash", hash) - } - } - } + h.server.broadcaster.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}) case <-h.closeCh: return } } } + +// broadcaster sends new header announcements to active client peers +type broadcaster struct { + ns *nodestate.NodeStateMachine + privateKey *ecdsa.PrivateKey + lastAnnounce, signedAnnounce announceData +} + +// newBroadcaster creates a new broadcaster +func newBroadcaster(ns *nodestate.NodeStateMachine) *broadcaster { + b := &broadcaster{ns: ns} + ns.SubscribeState(priorityPoolSetup.ActiveFlag, func(node *enode.Node, oldState, newState nodestate.Flags) { + if newState.Equals(priorityPoolSetup.ActiveFlag) { + // send last announcement to activated peers + b.sendTo(node) + } + }) + return b +} + +// setSignerKey sets the signer key for signed announcements. Should be called before +// starting the protocol handler. +func (b *broadcaster) setSignerKey(privateKey *ecdsa.PrivateKey) { + b.privateKey = privateKey +} + +// broadcast sends the given announcements to all active peers +func (b *broadcaster) broadcast(announce announceData) { + b.ns.Operation(func() { + // iterate in an Operation to ensure that the active set does not change while iterating + b.lastAnnounce = announce + b.ns.ForEach(priorityPoolSetup.ActiveFlag, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { + b.sendTo(node) + }) + }) +} + +// sendTo sends the most recent announcement to the given node unless the same or higher Td +// announcement has already been sent. +func (b *broadcaster) sendTo(node *enode.Node) { + if b.lastAnnounce.Td == nil { + return + } + if p, _ := b.ns.GetField(node, clientPeerField).(*clientPeer); p != nil { + if p.headInfo.Td == nil || b.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 { + switch p.announceType { + case announceTypeSimple: + if !p.queueSend(func() { p.sendAnnounce(b.lastAnnounce) }) { + log.Debug("Drop announcement because queue is full", "number", b.lastAnnounce.Number, "hash", b.lastAnnounce.Hash) + } + case announceTypeSigned: + if b.signedAnnounce.Hash != b.lastAnnounce.Hash { + b.signedAnnounce = b.lastAnnounce + b.signedAnnounce.sign(b.privateKey) + } + if !p.queueSend(func() { p.sendAnnounce(b.signedAnnounce) }) { + log.Debug("Drop announcement because queue is full", "number", b.lastAnnounce.Number, "hash", b.lastAnnounce.Hash) + } + } + p.headInfo = blockInfo{b.lastAnnounce.Hash, b.lastAnnounce.Number, b.lastAnnounce.Td} + } + } +} diff --git a/les/test_helper.go b/les/test_helper.go index 9f9b28721e..5a8d64f767 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -46,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/nodestate" "github.com/ethereum/go-ethereum/params" ) @@ -227,7 +228,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index return client.handler } -func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, peers *clientPeerSet, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) { +func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend) { var ( gspec = core.Genesis{ Config: params.AllEthashProtocolChanges, @@ -263,6 +264,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } oracle = checkpointoracle.New(checkpointConfig, getLocal) } + ns := nodestate.NewNodeStateMachine(nil, nil, mclock.System{}, serverSetup) server := &LesServer{ lesCommons: lesCommons{ genesis: genesis.Hash(), @@ -274,7 +276,8 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da oracle: oracle, closeCh: make(chan struct{}), }, - peers: peers, + ns: ns, + broadcaster: newBroadcaster(ns), servingQueue: newServingQueue(int64(time.Millisecond*10), 1), defParams: flowcontrol.ServerParams{ BufLimit: testBufLimit, @@ -284,13 +287,14 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da } server.costTracker, server.minCapacity = newCostTracker(db, server.config) server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism. - server.clientPool = newClientPool(db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {}) + server.clientPool = newClientPool(ns, db, testBufRecharge, defaultConnectedBias, clock, func(id enode.ID) {}) server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) if server.oracle != nil { server.oracle.Start(simulation) } server.servingQueue.setThreads(4) + ns.Start() server.handler.start() return server.handler, simulation } @@ -463,7 +467,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallba if simClock { clock = &mclock.Simulated{} } - handler, b := newTestServerHandler(blocks, indexers, db, newClientPeerSet(), clock) + handler, b := newTestServerHandler(blocks, indexers, db, clock) var peer *testPeer if newPeer { @@ -502,7 +506,7 @@ func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallba func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool, disablePruning bool) (*testServer, *testClient, func()) { sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase() - speers, cpeers := newServerPeerSet(), newClientPeerSet() + speers := newServerPeerSet() var clock mclock.Clock = &mclock.System{} if simClock { @@ -519,7 +523,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2] odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer) - server, b := newTestServerHandler(blocks, sindexers, sdb, cpeers, clock) + server, b := newTestServerHandler(blocks, sindexers, sdb, clock) client := newTestClientHandler(b, odr, cIndexers, cdb, speers, ulcServers, ulcFraction) scIndexer.Start(server.blockchain) From 91c460797967da491077cf23f9e25e76e0301d32 Mon Sep 17 00:00:00 2001 From: hwanjo <34005989+hwanjo@users.noreply.github.com> Date: Wed, 21 Oct 2020 23:53:30 +0900 Subject: [PATCH 100/948] core: fix blockchain insert report time interval calculation (#21723) --- core/blockchain_insert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 5685b0a4bd..cb8473c084 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -43,7 +43,7 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor // Fetch the timings for the batch var ( now = mclock.Now() - elapsed = time.Duration(now) - time.Duration(st.startTime) + elapsed = now.Sub(st.startTime) ) // If we're at the last block of the batch or report period reached, log if index == len(chain)-1 || elapsed >= statsReportLimit { From fb2c79df1995b4e8dfe79f9c75464d29d23aaaf4 Mon Sep 17 00:00:00 2001 From: Kristofer Peterson Date: Wed, 21 Oct 2020 15:56:45 +0100 Subject: [PATCH 101/948] accounts/usbwallet: fix ledger version check (#21733) The version check logic did not take into account the second digit (i.e. the '4' in v1.4.0) - this one line patch corrects this. --- accounts/usbwallet/ledger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index 64eae64f68..71f0f9392f 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -162,7 +162,7 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio return common.Address{}, nil, accounts.ErrWalletClosed } // Ensure the wallet is capable of signing the given transaction - if chainID != nil && w.version[0] <= 1 && w.version[2] <= 2 { + if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { //lint:ignore ST1005 brand name displayed on the console return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) } From 6487c002f6b47e08cb9814f16712c6789b313a97 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 23 Oct 2020 08:26:57 +0200 Subject: [PATCH 102/948] all: implement EIP-2929 (gas cost increases for state access opcodes) + yolo-v2 (#21509) * core/vm, core/state: implement EIP-2929 + YOLOv2 * core/state, core/vm: fix some review concerns * core/state, core/vm: address review concerns * core/vm: address review concerns * core/vm: better documentation * core/vm: unify sload cost as fully dynamic * core/vm: fix typo * core/vm/runtime: fix compilation flaw * core/vm/runtime: fix renaming-err leftovers * core/vm: renaming * params/config: use correct yolov2 chainid for config * core, params: use a proper new genesis for yolov2 * core/state/tests: golinter nitpicks --- cmd/evm/internal/t8ntool/execution.go | 10 ++ cmd/geth/chaincmd.go | 4 +- cmd/geth/consolecmd.go | 2 +- cmd/geth/main.go | 2 +- cmd/geth/usage.go | 2 +- cmd/puppeth/wizard_genesis.go | 4 +- cmd/utils/flags.go | 30 ++-- core/genesis.go | 11 +- core/state/access_list.go | 136 ++++++++++++++++ core/state/journal.go | 33 ++++ core/state/statedb.go | 47 ++++++ core/state/statedb_test.go | 188 ++++++++++++++++++++++ core/state_processor.go | 12 ++ core/vm/contracts.go | 28 +++- core/vm/contracts_test.go | 2 +- core/vm/eips.go | 39 +++++ core/vm/evm.go | 25 ++- core/vm/interface.go | 9 ++ core/vm/interpreter.go | 4 +- core/vm/jump_table.go | 10 +- core/vm/logger.go | 16 +- core/vm/operations_acl.go | 222 ++++++++++++++++++++++++++ core/vm/runtime/runtime.go | 24 ++- core/vm/runtime/runtime_test.go | 112 +++++++++++++ eth/api_tracer.go | 30 +++- params/config.go | 33 ++-- tests/init.go | 8 +- tests/state_test_util.go | 10 ++ 28 files changed, 980 insertions(+), 73 deletions(-) create mode 100644 core/state/access_list.go create mode 100644 core/vm/operations_acl.go diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 75586d588b..d8b93d291a 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -147,6 +147,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, vmContext.Origin = msg.From() evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) + if chainConfig.IsYoloV2(vmContext.BlockNumber) { + statedb.AddAddressToAccessList(msg.From()) + if dst := msg.To(); dst != nil { + statedb.AddAddressToAccessList(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range evm.ActivePrecompiles() { + statedb.AddAddressToAccessList(addr) + } + } snapshot := statedb.Snapshot() // (ret []byte, usedGas uint64, failed bool, err error) msgResult, err := core.ApplyMessage(evm, msg, gaspool) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index b98597e307..6418f90957 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -163,7 +163,7 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.RinkebyFlag, utils.TxLookupLimitFlag, utils.GoerliFlag, - utils.YoloV1Flag, + utils.YoloV2Flag, utils.LegacyTestnetFlag, }, Category: "BLOCKCHAIN COMMANDS", @@ -213,7 +213,7 @@ Use "ethereum dump 0" to dump the genesis block.`, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV1Flag, + utils.YoloV2Flag, utils.LegacyTestnetFlag, utils.SyncModeFlag, }, diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index e2f733f844..f15a306f17 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -136,7 +136,7 @@ func remoteConsole(ctx *cli.Context) error { path = filepath.Join(path, "rinkeby") } else if ctx.GlobalBool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") - } else if ctx.GlobalBool(utils.YoloV1Flag.Name) { + } else if ctx.GlobalBool(utils.YoloV2Flag.Name) { path = filepath.Join(path, "yolo-v1") } } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 8c1f7c4c22..38e48534dc 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -144,7 +144,7 @@ var ( utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, - utils.YoloV1Flag, + utils.YoloV2Flag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 288c453597..237cb8d516 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -42,7 +42,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.NetworkIdFlag, utils.GoerliFlag, utils.RinkebyFlag, - utils.YoloV1Flag, + utils.YoloV2Flag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index 40327d25d2..2d014e83bc 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -236,8 +236,8 @@ func (w *wizard) manageGenesis() { w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock) fmt.Println() - fmt.Printf("Which block should YOLOv1 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV1Block) - w.conf.Genesis.Config.YoloV1Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV1Block) + fmt.Printf("Which block should YOLOv2 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV2Block) + w.conf.Genesis.Config.YoloV2Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV2Block) out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ") fmt.Printf("Chain configuration updated:\n\n%s\n", out) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 80da518c2b..12562d8784 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -135,9 +135,9 @@ var ( Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", } - YoloV1Flag = cli.BoolFlag{ - Name: "yolov1", - Usage: "YOLOv1 network: pre-configured proof-of-authority shortlived test network.", + YoloV2Flag = cli.BoolFlag{ + Name: "yolov2", + Usage: "YOLOv2 network: pre-configured proof-of-authority shortlived test network.", } RinkebyFlag = cli.BoolFlag{ Name: "rinkeby", @@ -744,8 +744,8 @@ func MakeDataDir(ctx *cli.Context) string { if ctx.GlobalBool(GoerliFlag.Name) { return filepath.Join(path, "goerli") } - if ctx.GlobalBool(YoloV1Flag.Name) { - return filepath.Join(path, "yolo-v1") + if ctx.GlobalBool(YoloV2Flag.Name) { + return filepath.Join(path, "yolo-v2") } return path } @@ -803,7 +803,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls = params.RinkebyBootnodes case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV1Flag.Name): + case ctx.GlobalBool(YoloV2Flag.Name): urls = params.YoloV1Bootnodes case cfg.BootstrapNodes != nil: return // already set, don't apply defaults. @@ -839,7 +839,7 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) { urls = params.RinkebyBootnodes case ctx.GlobalBool(GoerliFlag.Name): urls = params.GoerliBootnodes - case ctx.GlobalBool(YoloV1Flag.Name): + case ctx.GlobalBool(YoloV2Flag.Name): urls = params.YoloV1Bootnodes case cfg.BootstrapNodesV5 != nil: return // already set, don't apply defaults. @@ -1269,8 +1269,8 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") - case ctx.GlobalBool(YoloV1Flag.Name) && cfg.DataDir == node.DefaultDataDir(): - cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v1") + case ctx.GlobalBool(YoloV2Flag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v2") } } @@ -1483,7 +1483,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV1Flag) + CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV2Flag) CheckExclusive(ctx, LegacyLightServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, GCModeFlag, "archive", TxLookupLimitFlag) @@ -1603,11 +1603,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { } cfg.Genesis = core.DefaultGoerliGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash) - case ctx.GlobalBool(YoloV1Flag.Name): + case ctx.GlobalBool(YoloV2Flag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = 133519467574833 // "yolov1" + cfg.NetworkId = 133519467574834 // "yolov2" } - cfg.Genesis = core.DefaultYoloV1GenesisBlock() + cfg.Genesis = core.DefaultYoloV2GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 @@ -1791,8 +1791,8 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { genesis = core.DefaultRinkebyGenesisBlock() case ctx.GlobalBool(GoerliFlag.Name): genesis = core.DefaultGoerliGenesisBlock() - case ctx.GlobalBool(YoloV1Flag.Name): - genesis = core.DefaultYoloV1GenesisBlock() + case ctx.GlobalBool(YoloV2Flag.Name): + genesis = core.DefaultYoloV2GenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): Fatalf("Developer chains are ephemeral") } diff --git a/core/genesis.go b/core/genesis.go index 4525b9c174..0535d7ee3a 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -243,8 +243,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig - case ghash == params.YoloV1GenesisHash: - return params.YoloV1ChainConfig + case ghash == params.YoloV2GenesisHash: + return params.YoloV2ChainConfig default: return params.AllEthashProtocolChanges } @@ -380,10 +380,11 @@ func DefaultGoerliGenesisBlock() *Genesis { } } -func DefaultYoloV1GenesisBlock() *Genesis { +func DefaultYoloV2GenesisBlock() *Genesis { + // TODO: Update with yolov2 values + regenerate alloc data return &Genesis{ - Config: params.YoloV1ChainConfig, - Timestamp: 0x5ed754f1, + Config: params.YoloV2ChainConfig, + Timestamp: 0x5f91b932, ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000008a37866fd3627c9205a37c8685666f32ec07bb1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), GasLimit: 0x47b760, Difficulty: big.NewInt(1), diff --git a/core/state/access_list.go b/core/state/access_list.go new file mode 100644 index 0000000000..4194691345 --- /dev/null +++ b/core/state/access_list.go @@ -0,0 +1,136 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type accessList struct { + addresses map[common.Address]int + slots []map[common.Hash]struct{} +} + +// ContainsAddress returns true if the address is in the access list. +func (al *accessList) ContainsAddress(address common.Address) bool { + _, ok := al.addresses[address] + return ok +} + +// Contains checks if a slot within an account is present in the access list, returning +// separate flags for the presence of the account and the slot respectively. +func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + idx, ok := al.addresses[address] + if !ok { + // no such address (and hence zero slots) + return false, false + } + if idx == -1 { + // address yes, but no slots + return true, false + } + _, slotPresent = al.slots[idx][slot] + return true, slotPresent +} + +// newAccessList creates a new accessList. +func newAccessList() *accessList { + return &accessList{ + addresses: make(map[common.Address]int), + } +} + +// Copy creates an independent copy of an accessList. +func (a *accessList) Copy() *accessList { + cp := newAccessList() + for k, v := range a.addresses { + cp.addresses[k] = v + } + cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) + for i, slotMap := range a.slots { + newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) + for k := range slotMap { + newSlotmap[k] = struct{}{} + } + cp.slots[i] = newSlotmap + } + return cp +} + +// AddAddress adds an address to the access list, and returns 'true' if the operation +// caused a change (addr was not previously in the list). +func (al *accessList) AddAddress(address common.Address) bool { + if _, present := al.addresses[address]; present { + return false + } + al.addresses[address] = -1 + return true +} + +// AddSlot adds the specified (addr, slot) combo to the access list. +// Return values are: +// - address added +// - slot added +// For any 'true' value returned, a corresponding journal entry must be made. +func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { + idx, addrPresent := al.addresses[address] + if !addrPresent || idx == -1 { + // Address not present, or addr present but no slots there + al.addresses[address] = len(al.slots) + slotmap := map[common.Hash]struct{}{slot: {}} + al.slots = append(al.slots, slotmap) + return !addrPresent, true + } + // There is already an (address,slot) mapping + slotmap := al.slots[idx] + if _, ok := slotmap[slot]; !ok { + slotmap[slot] = struct{}{} + // Journal add slot change + return false, true + } + // No changes required + return false, false +} + +// DeleteSlot removes an (address, slot)-tuple from the access list. +// This operation needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { + idx, addrOk := al.addresses[address] + // There are two ways this can fail + if !addrOk { + panic("reverting slot change, address not present in list") + } + slotmap := al.slots[idx] + delete(slotmap, slot) + // If that was the last (first) slot, remove it + // Since additions and rollbacks are always performed in order, + // we can delete the item without worrying about screwing up later indices + if len(slotmap) == 0 { + al.slots = al.slots[:idx] + al.addresses[address] = -1 + } +} + +// DeleteAddress removes an address from the access list. This operation +// needs to be performed in the same order as the addition happened. +// This method is meant to be used by the journal, which maintains ordering of +// operations. +func (al *accessList) DeleteAddress(address common.Address) { + delete(al.addresses, address) +} diff --git a/core/state/journal.go b/core/state/journal.go index f242dac5af..2070f30875 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -130,6 +130,14 @@ type ( touchChange struct { account *common.Address } + // Changes to the access list + accessListAddAccountChange struct { + address *common.Address + } + accessListAddSlotChange struct { + address *common.Address + slot *common.Hash + } ) func (ch createObjectChange) revert(s *StateDB) { @@ -234,3 +242,28 @@ func (ch addPreimageChange) revert(s *StateDB) { func (ch addPreimageChange) dirtied() *common.Address { return nil } + +func (ch accessListAddAccountChange) revert(s *StateDB) { + /* + One important invariant here, is that whenever a (addr, slot) is added, if the + addr is not already present, the add causes two journal entries: + - one for the address, + - one for the (address,slot) + Therefore, when unrolling the change, we can always blindly delete the + (addr) at this point, since no storage adds can remain when come upon + a single (addr) change. + */ + s.accessList.DeleteAddress(*ch.address) +} + +func (ch accessListAddAccountChange) dirtied() *common.Address { + return nil +} + +func (ch accessListAddSlotChange) revert(s *StateDB) { + s.accessList.DeleteSlot(*ch.address, *ch.slot) +} + +func (ch accessListAddSlotChange) dirtied() *common.Address { + return nil +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 36f7d863af..6fd7723a16 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -93,6 +93,9 @@ type StateDB struct { preimages map[common.Hash][]byte + // Per-transaction access list + accessList *accessList + // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal @@ -129,6 +132,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), journal: newJournal(), + accessList: newAccessList(), } if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { @@ -178,6 +182,7 @@ func (s *StateDB) Reset(root common.Hash) error { s.snapStorage = make(map[common.Hash]map[common.Hash][]byte) } } + s.accessList = newAccessList() return nil } @@ -697,6 +702,12 @@ func (s *StateDB) Copy() *StateDB { for hash, preimage := range s.preimages { state.preimages[hash] = preimage } + // Do we need to copy the access list? In practice: No. At the start of a + // transaction, the access list is empty. In practice, we only ever copy state + // _between_ transactions/blocks, never in the middle of a transaction. + // However, it doesn't cost us much to copy an empty list, so we do it anyway + // to not blow up if we ever decide copy it in the middle of a transaction + state.accessList = s.accessList.Copy() return state } @@ -798,6 +809,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { s.thash = thash s.bhash = bhash s.txIndex = ti + s.accessList = newAccessList() } func (s *StateDB) clearJournalAndRefund() { @@ -877,3 +889,38 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } return root, err } + +// AddAddressToAccessList adds the given address to the access list +func (s *StateDB) AddAddressToAccessList(addr common.Address) { + if s.accessList.AddAddress(addr) { + s.journal.append(accessListAddAccountChange{&addr}) + } +} + +// AddSlotToAccessList adds the given (address, slot)-tuple to the access list +func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + addrMod, slotMod := s.accessList.AddSlot(addr, slot) + if addrMod { + // In practice, this should not happen, since there is no way to enter the + // scope of 'address' without having the 'address' become already added + // to the access list (via call-variant, create, etc). + // Better safe than sorry, though + s.journal.append(accessListAddAccountChange{&addr}) + } + if slotMod { + s.journal.append(accessListAddSlotChange{ + address: &addr, + slot: &slot, + }) + } +} + +// AddressInAccessList returns true if the given address is in the access list. +func (s *StateDB) AddressInAccessList(addr common.Address) bool { + return s.accessList.ContainsAddress(addr) +} + +// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. +func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { + return s.accessList.Contains(addr, slot) +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 36ff271331..70d01ff3dd 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -328,6 +328,20 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { }, args: make([]int64, 1), }, + { + name: "AddAddressToAccessList", + fn: func(a testAction, s *StateDB) { + s.AddAddressToAccessList(addr) + }, + }, + { + name: "AddSlotToAccessList", + fn: func(a testAction, s *StateDB) { + s.AddSlotToAccessList(addr, + common.Hash{byte(a.args[0])}) + }, + args: make([]int64, 1), + }, } action := actions[r.Intn(len(actions))] var nameargs []string @@ -727,3 +741,177 @@ func TestMissingTrieNodes(t *testing.T) { t.Fatalf("expected error, got root :%x", root) } } + +func TestStateDBAccessList(t *testing.T) { + // Some helpers + addr := func(a string) common.Address { + return common.HexToAddress(a) + } + slot := func(a string) common.Hash { + return common.HexToHash(a) + } + + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + state.accessList = newAccessList() + + verifyAddrs := func(astrings ...string) { + t.Helper() + // convert to common.Address form + var addresses []common.Address + var addressMap = make(map[common.Address]struct{}) + for _, astring := range astrings { + address := addr(astring) + addresses = append(addresses, address) + addressMap[address] = struct{}{} + } + // Check that the given addresses are in the access list + for _, address := range addresses { + if !state.AddressInAccessList(address) { + t.Fatalf("expected %x to be in access list", address) + } + } + // Check that only the expected addresses are present in the acesslist + for address := range state.accessList.addresses { + if _, exist := addressMap[address]; !exist { + t.Fatalf("extra address %x in access list", address) + } + } + } + verifySlots := func(addrString string, slotStrings ...string) { + if !state.AddressInAccessList(addr(addrString)) { + t.Fatalf("scope missing address/slots %v", addrString) + } + var address = addr(addrString) + // convert to common.Hash form + var slots []common.Hash + var slotMap = make(map[common.Hash]struct{}) + for _, slotString := range slotStrings { + s := slot(slotString) + slots = append(slots, s) + slotMap[s] = struct{}{} + } + // Check that the expected items are in the access list + for i, s := range slots { + if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent { + t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString) + } + } + // Check that no extra elements are in the access list + index := state.accessList.addresses[address] + if index >= 0 { + stateSlots := state.accessList.slots[index] + for s := range stateSlots { + if _, slotPresent := slotMap[s]; !slotPresent { + t.Fatalf("scope has extra slot %v (address %v)", s, addrString) + } + } + } + } + + state.AddAddressToAccessList(addr("aa")) // 1 + state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3 + state.AddSlotToAccessList(addr("bb"), slot("02")) // 4 + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + + // Make a copy + stateCopy1 := state.Copy() + if exp, got := 4, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + + // same again, should cause no journal entries + state.AddSlotToAccessList(addr("bb"), slot("01")) + state.AddSlotToAccessList(addr("bb"), slot("02")) + state.AddAddressToAccessList(addr("aa")) + if exp, got := 4, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + // some new ones + state.AddSlotToAccessList(addr("bb"), slot("03")) // 5 + state.AddSlotToAccessList(addr("aa"), slot("01")) // 6 + state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8 + state.AddAddressToAccessList(addr("cc")) + if exp, got := 8, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + + verifyAddrs("aa", "bb", "cc") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + verifySlots("cc", "01") + + // now start rolling back changes + state.journal.revert(state, 7) + if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb", "cc") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 6) + if state.AddressInAccessList(addr("cc")) { + t.Fatalf("addr present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("aa", "01") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 5) + if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02", "03") + + state.journal.revert(state, 4) + if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + + state.journal.revert(state, 3) + if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + verifySlots("bb", "01") + + state.journal.revert(state, 2) + if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok { + t.Fatalf("slot present, expected missing") + } + verifyAddrs("aa", "bb") + + state.journal.revert(state, 1) + if state.AddressInAccessList(addr("bb")) { + t.Fatalf("addr present, expected missing") + } + verifyAddrs("aa") + + state.journal.revert(state, 0) + if state.AddressInAccessList(addr("aa")) { + t.Fatalf("addr present, expected missing") + } + if got, exp := len(state.accessList.addresses), 0; got != exp { + t.Fatalf("expected empty, got %d", got) + } + if got, exp := len(state.accessList.slots), 0; got != exp { + t.Fatalf("expected empty, got %d", got) + } + // Check the copy + // Make a copy + state = stateCopy1 + verifyAddrs("aa", "bb") + verifySlots("bb", "01", "02") + if got, exp := len(state.accessList.addresses), 2; got != exp { + t.Fatalf("expected empty, got %d", got) + } + if got, exp := len(state.accessList.slots), 1; got != exp { + t.Fatalf("expected empty, got %d", got) + } +} diff --git a/core/state_processor.go b/core/state_processor.go index e655d8f3bf..ac6046b717 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -95,6 +95,18 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. vmenv := vm.NewEVM(context, statedb, config, cfg) + + if config.IsYoloV2(header.Number) { + statedb.AddAddressToAccessList(msg.From()) + if dst := msg.To(); dst != nil { + statedb.AddAddressToAccessList(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range vmenv.ActivePrecompiles() { + statedb.AddAddressToAccessList(addr) + } + } + // Apply the transaction to the current state (included in the env) result, err := ApplyMessage(vmenv, msg, gp) if err != nil { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 8930a06266..35faa7b83d 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -78,9 +78,9 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } -// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum -// contracts used in the Yolo v1 test release. -var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{ +// PrecompiledContractsYoloV2 contains the default set of pre-compiled Ethereum +// contracts used in the Yolo v2 test release. +var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -101,6 +101,28 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{18}): &bls12381MapG2{}, } +var ( + PrecompiledAddressesYoloV2 []common.Address + PrecompiledAddressesIstanbul []common.Address + PrecompiledAddressesByzantium []common.Address + PrecompiledAddressesHomestead []common.Address +) + +func init() { + for k := range PrecompiledContractsHomestead { + PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k) + } + for k := range PrecompiledContractsByzantium { + PrecompiledAddressesHomestead = append(PrecompiledAddressesByzantium, k) + } + for k := range PrecompiledContractsIstanbul { + PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) + } + for k := range PrecompiledContractsYoloV2 { + PrecompiledAddressesYoloV2 = append(PrecompiledAddressesYoloV2, k) + } +} + // RunPrecompiledContract runs and evaluates the output of a precompiled contract. // It returns // - the returned bytes, diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 6320875e1a..ed0d675a69 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -43,7 +43,7 @@ type precompiledFailureTest struct { Name string } -var allPrecompiles = PrecompiledContractsYoloV1 +var allPrecompiles = PrecompiledContractsYoloV2 // EIP-152 test vectors var blake2FMalformedInputTests = []precompiledFailureTest{ diff --git a/core/vm/eips.go b/core/vm/eips.go index 6b5ba62aad..962c0f14b1 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -25,6 +25,7 @@ import ( ) var activators = map[int]func(*JumpTable){ + 2929: enable2929, 2200: enable2200, 1884: enable1884, 1344: enable1344, @@ -134,3 +135,41 @@ func enable2315(jt *JumpTable) { jumps: true, } } + +// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes" +// https://eips.ethereum.org/EIPS/eip-2929 +func enable2929(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreEIP2929 + + jt[SLOAD].constantGas = 0 + jt[SLOAD].dynamicGas = gasSLoadEIP2929 + + jt[EXTCODECOPY].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929 + + jt[EXTCODESIZE].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck + + jt[EXTCODEHASH].constantGas = WarmStorageReadCostEIP2929 + jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck + + jt[BALANCE].constantGas = WarmStorageReadCostEIP2929 + jt[BALANCE].dynamicGas = gasEip2929AccountCheck + + jt[CALL].constantGas = WarmStorageReadCostEIP2929 + jt[CALL].dynamicGas = gasCallEIP2929 + + jt[CALLCODE].constantGas = WarmStorageReadCostEIP2929 + jt[CALLCODE].dynamicGas = gasCallCodeEIP2929 + + jt[STATICCALL].constantGas = WarmStorageReadCostEIP2929 + jt[STATICCALL].dynamicGas = gasStaticCallEIP2929 + + jt[DELEGATECALL].constantGas = WarmStorageReadCostEIP2929 + jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929 + + // This was previously part of the dynamic cost, but we're using it as a constantGas + // factor here + jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 +} diff --git a/core/vm/evm.go b/core/vm/evm.go index f5469c500c..8f6e603aee 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -42,11 +42,26 @@ type ( GetHashFunc func(uint64) common.Hash ) +// ActivePrecompiles returns the addresses of the precompiles enabled with the current +// configuration +func (evm *EVM) ActivePrecompiles() []common.Address { + switch { + case evm.chainRules.IsYoloV2: + return PrecompiledAddressesYoloV2 + case evm.chainRules.IsIstanbul: + return PrecompiledAddressesIstanbul + case evm.chainRules.IsByzantium: + return PrecompiledAddressesByzantium + default: + return PrecompiledAddressesHomestead + } +} + func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { - case evm.chainRules.IsYoloV1: - precompiles = PrecompiledContractsYoloV1 + case evm.chainRules.IsYoloV2: + precompiles = PrecompiledContractsYoloV2 case evm.chainRules.IsIstanbul: precompiles = PrecompiledContractsIstanbul case evm.chainRules.IsByzantium: @@ -416,7 +431,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } nonce := evm.StateDB.GetNonce(caller.Address()) evm.StateDB.SetNonce(caller.Address(), nonce+1) - + // We add this to the access list _before_ taking a snapshot. Even if the creation fails, + // the access-list change should not be rolled back + if evm.chainRules.IsYoloV2 { + evm.StateDB.AddAddressToAccessList(address) + } // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { diff --git a/core/vm/interface.go b/core/vm/interface.go index dd401466ad..fb5bbca48f 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -57,6 +57,15 @@ type StateDB interface { // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + RevertToSnapshot(int) Snapshot() int diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 1e2a661deb..bffc5013a6 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { - case evm.chainRules.IsYoloV1: - jt = yoloV1InstructionSet + case evm.chainRules.IsYoloV2: + jt = yoloV2InstructionSet case evm.chainRules.IsIstanbul: jt = istanbulInstructionSet case evm.chainRules.IsConstantinople: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 9d9bc12b62..83fb2c1ed6 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,17 +56,19 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() - yoloV1InstructionSet = newYoloV1InstructionSet() + yoloV2InstructionSet = newYoloV2InstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation -func newYoloV1InstructionSet() JumpTable { +// newYoloV2InstructionSet creates an instructionset containing +// - "EIP-2315: Simple Subroutines" +// - "EIP-2929: Gas cost increases for state access opcodes" +func newYoloV2InstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() - enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315 - + enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929 return instructionSet } diff --git a/core/vm/logger.go b/core/vm/logger.go index c07e7787ca..962be6ec8e 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) var errTraceLimitReached = errors.New("the number of logs reached the specified limit") @@ -53,6 +54,8 @@ type LogConfig struct { DisableReturnData bool // disable return data capture Debug bool // print output during capture end Limit int // maximum length of output, but zero means unlimited + // Chain overrides, can be used to execute a trace using future fork rules + Overrides *params.ChainConfig `json:"overrides,omitempty"` } //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go @@ -314,8 +317,8 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b } fmt.Fprintf(t.out, ` -| Pc | Op | Cost | Stack | RStack | -|-------|-------------|------|-----------|-----------| +| Pc | Op | Cost | Stack | RStack | Refund | +|-------|-------------|------|-----------|-----------|---------| `) return nil } @@ -327,7 +330,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 // format stack var a []string for _, elem := range stack.data { - a = append(a, fmt.Sprintf("%d", elem)) + a = append(a, fmt.Sprintf("%v", elem.String())) } b := fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) @@ -340,6 +343,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64 b = fmt.Sprintf("[%v]", strings.Join(a, ",")) fmt.Fprintf(t.out, "%10v |", b) } + fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund()) fmt.Fprintln(t.out, "") if err != nil { fmt.Fprintf(t.out, "Error: %v\n", err) @@ -355,11 +359,7 @@ func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64 } func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error { - fmt.Fprintf(t.out, ` -Output: 0x%x -Consumed gas: %d -Error: %v -`, + fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", output, gasUsed, err) return nil } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go new file mode 100644 index 0000000000..41b0549c51 --- /dev/null +++ b/core/vm/operations_acl.go @@ -0,0 +1,222 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +const ( + ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST + ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST + WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST +) + +// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929" +// +// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys. +// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys. +// Additionally, modify the parameters defined in EIP 2200 as follows: +// +// Parameter Old value New value +// SLOAD_GAS 800 = WARM_STORAGE_READ_COST +// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST +// +//The other parameters defined in EIP 2200 are unchanged. +// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified +func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.peek() + slot = common.Hash(x.Bytes32()) + current = evm.StateDB.GetState(contract.Address(), slot) + cost = uint64(0) + ) + // Check slot presence in the access list + if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + cost = ColdSloadCostEIP2929 + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + if !addrPresent { + // Once we're done with YOLOv2 and schedule this for mainnet, might + // be good to remove this panic here, which is just really a + // canary to have during testing + panic("impossible case: address was not present in access list during sstore op") + } + } + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + // EIP 2200 original clause: + // return params.SloadGasEIP2200, nil + return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS + } + original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32())) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return cost + params.SstoreSetGasEIP2200, nil + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + // EIP-2200 original clause: + // return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929), nil // write existing slot (2.1.2) + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + // EIP 2200 Original clause: + //evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200) + evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929) + } else { // reset to original existing slot (2.2.2.2) + // EIP 2200 Original clause: + // evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200) + // - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST) + // - SLOAD_GAS redefined as WARM_STORAGE_READ_COST + // Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST + evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - ColdSloadCostEIP2929) - WarmStorageReadCostEIP2929) + } + } + // EIP-2200 original clause: + //return params.SloadGasEIP2200, nil // dirty update (2.2) + return cost + WarmStorageReadCostEIP2929, nil // dirty update (2.2) +} + +// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929 +// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract +// whose storage is being read) is not yet in accessed_storage_keys, +// charge 2100 gas and add the pair to accessed_storage_keys. +// If the pair is already in accessed_storage_keys, charge 100 gas. +func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + loc := stack.peek() + slot := common.Hash(loc.Bytes32()) + // Check slot presence in the access list + if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + // If the caller cannot afford the cost, this change will be rolled back + // If he does afford it, we can skip checking the same thing later on, during execution + evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + return ColdSloadCostEIP2929, nil + } + return WarmStorageReadCostEIP2929, nil +} + +// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929 +// EIP spec: +// > If the target is not in accessed_addresses, +// > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. +// > Otherwise, charge WARM_STORAGE_READ_COST gas. +func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // memory expansion first (dynamic part of pre-2929 implementation) + gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + var overflow bool + // We charge (cold-warm), since 'warm' is already charged as constantGas + if gas, overflow = math.SafeAdd(gas, ColdAccountAccessCostEIP2929-WarmStorageReadCostEIP2929); overflow { + return 0, ErrGasUintOverflow + } + return gas, nil + } + return gas, nil +} + +// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list. +// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it +// is also using 'warm' as constant factor. +// This method is used by: +// - extcodehash, +// - extcodesize, +// - (ext) balance +func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.peek().Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(addr) + // The warm storage read cost is already charged as constantGas + return ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, nil + } + return 0, nil +} + +func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(stack.Back(1).Bytes20()) + // Check slot presence in the access list + if !evm.StateDB.AddressInAccessList(addr) { + evm.StateDB.AddAddressToAccessList(addr) + // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost + if !contract.UseGas(ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929) { + return 0, ErrOutOfGas + } + } + // Now call the old calculator, which takes into account + // - create new account + // - transfer value + // - memory expansion + // - 63/64ths rule + return oldCalculator(evm, contract, stack, mem, memorySize) + } +} + +var ( + gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall) + gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall) + gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) + gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) +) + +func gasSelfdestructEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + address = common.Address(stack.peek().Bytes20()) + ) + if !evm.StateDB.AddressInAccessList(address) { + // If the caller cannot afford the cost, this change will be rolled back + evm.StateDB.AddAddressToAccessList(address) + gas = ColdAccountAccessCostEIP2929 + } + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + if !evm.StateDB.HasSuicided(contract.Address()) { + evm.StateDB.AddRefund(params.SelfdestructRefundGas) + } + return gas, nil + +} diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 7ebaa9a7e3..d99e8f3b2b 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -65,7 +65,7 @@ func setDefaults(cfg *Config) { PetersburgBlock: new(big.Int), IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), - YoloV1Block: nil, + YoloV2Block: nil, } } @@ -113,6 +113,14 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) + if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + cfg.State.AddAddressToAccessList(cfg.Origin) + cfg.State.AddAddressToAccessList(address) + for _, addr := range vmenv.ActivePrecompiles() { + cfg.State.AddAddressToAccessList(addr) + cfg.State.AddAddressToAccessList(addr) + } + } cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -142,6 +150,12 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) + if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + cfg.State.AddAddressToAccessList(cfg.Origin) + for _, addr := range vmenv.ActivePrecompiles() { + cfg.State.AddAddressToAccessList(addr) + } + } // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( @@ -164,6 +178,14 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er vmenv := NewEnv(cfg) sender := cfg.State.GetOrNewStateObject(cfg.Origin) + if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) { + cfg.State.AddAddressToAccessList(cfg.Origin) + cfg.State.AddAddressToAccessList(address) + for _, addr := range vmenv.ActivePrecompiles() { + cfg.State.AddAddressToAccessList(addr) + } + } + // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( sender, diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 108ee80e41..b185258dad 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -722,3 +722,115 @@ func BenchmarkSimpleLoop(b *testing.B) { //benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b) //benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b) } + +// TestEip2929Cases contains various testcases that are used for +// EIP-2929 about gas repricings +func TestEip2929Cases(t *testing.T) { + + id := 1 + prettyPrint := func(comment string, code []byte) { + + instrs := make([]string, 0) + it := asm.NewInstructionIterator(code) + for it.Next() { + if it.Arg() != nil && 0 < len(it.Arg()) { + instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg())) + } else { + instrs = append(instrs, fmt.Sprintf("%v", it.Op())) + } + } + ops := strings.Join(instrs, ", ") + fmt.Printf("### Case %d\n\n", id) + id++ + fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n", + comment, + code, ops) + Execute(code, nil, &Config{ + EVMConfig: vm.Config{ + Debug: true, + Tracer: vm.NewMarkdownLogger(nil, os.Stdout), + ExtraEips: []int{2929}, + }, + }) + } + + { // First eip testcase + code := []byte{ + // Three checks against a precompile + byte(vm.PUSH1), 1, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 2, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 3, byte(vm.BALANCE), byte(vm.POP), + // Three checks against a non-precompile + byte(vm.PUSH1), 0xf1, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 0xf2, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 0xf3, byte(vm.BALANCE), byte(vm.POP), + // Same three checks (should be cheaper) + byte(vm.PUSH1), 0xf2, byte(vm.EXTCODEHASH), byte(vm.POP), + byte(vm.PUSH1), 0xf3, byte(vm.EXTCODESIZE), byte(vm.POP), + byte(vm.PUSH1), 0xf1, byte(vm.BALANCE), byte(vm.POP), + // Check the origin, and the 'this' + byte(vm.ORIGIN), byte(vm.BALANCE), byte(vm.POP), + byte(vm.ADDRESS), byte(vm.BALANCE), byte(vm.POP), + + byte(vm.STOP), + } + prettyPrint("This checks `EXT`(codehash,codesize,balance) of precompiles, which should be `100`, "+ + "and later checks the same operations twice against some non-precompiles. "+ + "Those are cheaper second time they are accessed. Lastly, it checks the `BALANCE` of `origin` and `this`.", code) + } + + { // EXTCODECOPY + code := []byte{ + // extcodecopy( 0xff,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY), + // extcodecopy( 0xff,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY), + // extcodecopy( this,0,0,0,0) + byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset + byte(vm.ADDRESS), byte(vm.EXTCODECOPY), + + byte(vm.STOP), + } + prettyPrint("This checks `extcodecopy( 0xff,0,0,0,0)` twice, (should be expensive first time), "+ + "and then does `extcodecopy( this,0,0,0,0)`.", code) + } + + { // SLOAD + SSTORE + code := []byte{ + + // Add slot `0x1` to access list + byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP), // SLOAD( 0x1) (add to access list) + // Write to `0x1` which is already in access list + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x01, byte(vm.SSTORE), // SSTORE( loc: 0x01, val: 0x11) + // Write to `0x2` which is not in access list + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11) + // Write again to `0x2` + byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11) + // Read slot in access list (0x2) + byte(vm.PUSH1), 0x02, byte(vm.SLOAD), // SLOAD( 0x2) + // Read slot in access list (0x1) + byte(vm.PUSH1), 0x01, byte(vm.SLOAD), // SLOAD( 0x1) + } + prettyPrint("This checks `sload( 0x1)` followed by `sstore(loc: 0x01, val:0x11)`, then 'naked' sstore:"+ + "`sstore(loc: 0x02, val:0x11)` twice, and `sload(0x2)`, `sload(0x1)`. ", code) + } + { // Call variants + code := []byte{ + // identity precompile + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0x04, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP), + + // random account - call 1 + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP), + + // random account - call 2 + byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), + byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.STATICCALL), byte(vm.POP), + } + prettyPrint("This calls the `identity`-precompile (cheap), then calls an account (expensive) and `staticcall`s the same"+ + "account (cheap)", code) + } +} diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 748280951c..90d4a95c14 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -38,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" @@ -561,9 +562,28 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block // Execute transaction, either tracing all or just the requested one var ( - signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) - dumps []string + signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) + dumps []string + chainConfig = api.eth.blockchain.Config() + canon = true ) + // Check if there are any overrides: the caller may wish to enable a future + // fork when executing this block. Note, such overrides are only applicable to the + // actual specified block, not any preceding blocks that we have to go through + // in order to obtain the state. + // Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork` + + if config != nil && config.Overrides != nil { + // Copy the config, to not screw up the main config + // Note: the Clique-part is _not_ deep copied + chainConfigCopy := new(params.ChainConfig) + *chainConfigCopy = *chainConfig + chainConfig = chainConfigCopy + if yolov2 := config.Overrides.YoloV2Block; yolov2 != nil { + chainConfig.YoloV2Block = yolov2 + canon = false + } + } for i, tx := range block.Transactions() { // Prepare the trasaction for un-traced execution var ( @@ -579,7 +599,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block if tx.Hash() == txHash || txHash == (common.Hash{}) { // Generate a unique temporary file to dump it into prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) - + if !canon { + prefix = fmt.Sprintf("%valt-", prefix) + } dump, err = ioutil.TempFile(os.TempDir(), prefix) if err != nil { return nil, err @@ -595,7 +617,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } } // Execute the transaction and flush any traces to disk - vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) + vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vmConf) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if writer != nil { writer.Flush() diff --git a/params/config.go b/params/config.go index 6cae8cc0b0..ade81408a8 100644 --- a/params/config.go +++ b/params/config.go @@ -31,7 +31,8 @@ var ( RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") - YoloV1GenesisHash = common.HexToHash("0xc3fd235071f24f93865b0850bd2a2119b30f7224d18a0e34c7bbf549ad7e3d36") + // TODO: update with yolov2 values + YoloV2GenesisHash = common.HexToHash("0x498a7239036dd2cd09e2bb8a80922b78632017958c332b42044c250d603a8a3e") ) // TrustedCheckpoints associates each known checkpoint with the genesis hash of @@ -213,9 +214,9 @@ var ( Threshold: 2, } - // YoloV1ChainConfig contains the chain parameters to run a node on the YOLOv1 test network. - YoloV1ChainConfig = &ChainConfig{ - ChainID: big.NewInt(133519467574833), + // YoloV2ChainConfig contains the chain parameters to run a node on the YOLOv2 test network. + YoloV2ChainConfig = &ChainConfig{ + ChainID: big.NewInt(133519467574834), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: true, @@ -227,7 +228,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: nil, - YoloV1Block: big.NewInt(0), + YoloV2Block: big.NewInt(0), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -320,7 +321,7 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) - YoloV1Block *big.Int `json:"yoloV1Block,omitempty"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet) + YoloV2Block *big.Int `json:"yoloV2Block,omitempty"` // YOLO v2: Gas repricings TODO @holiman add EIP references EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) // Various consensus engines @@ -358,7 +359,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v2: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -371,7 +372,7 @@ func (c *ChainConfig) String() string { c.PetersburgBlock, c.IstanbulBlock, c.MuirGlacierBlock, - c.YoloV1Block, + c.YoloV2Block, engine, ) } @@ -428,9 +429,9 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { return isForked(c.IstanbulBlock, num) } -// IsYoloV1 returns whether num is either equal to the YoloV1 fork block or greater. -func (c *ChainConfig) IsYoloV1(num *big.Int) bool { - return isForked(c.YoloV1Block, num) +// IsYoloV2 returns whether num is either equal to the YoloV1 fork block or greater. +func (c *ChainConfig) IsYoloV2(num *big.Int) bool { + return isForked(c.YoloV2Block, num) } // IsEWASM returns whether num represents a block number after the EWASM fork @@ -476,7 +477,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "petersburgBlock", block: c.PetersburgBlock}, {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, - {name: "yoloV1Block", block: c.YoloV1Block}, + {name: "yoloV2Block", block: c.YoloV2Block}, } { if lastFork.name != "" { // Next one must be higher number @@ -540,8 +541,8 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) } - if isForkIncompatible(c.YoloV1Block, newcfg.YoloV1Block, head) { - return newCompatError("YOLOv1 fork block", c.YoloV1Block, newcfg.YoloV1Block) + if isForkIncompatible(c.YoloV2Block, newcfg.YoloV2Block, head) { + return newCompatError("YOLOv2 fork block", c.YoloV2Block, newcfg.YoloV2Block) } if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) @@ -613,7 +614,7 @@ type Rules struct { ChainID *big.Int IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsYoloV1 bool + IsYoloV2 bool } // Rules ensures c's ChainID is not nil. @@ -632,6 +633,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsConstantinople: c.IsConstantinople(num), IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), - IsYoloV1: c.IsYoloV1(num), + IsYoloV2: c.IsYoloV2(num), } } diff --git a/tests/init.go b/tests/init.go index d920c70e2e..607c69ddb3 100644 --- a/tests/init.go +++ b/tests/init.go @@ -141,7 +141,7 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(5), }, - "YOLOv1": { + "YOLOv2": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -151,9 +151,9 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV1Block: big.NewInt(0), + YoloV2Block: big.NewInt(0), }, - // This specification is subject to change, but is for now identical to YOLOv1 + // This specification is subject to change, but is for now identical to YOLOv2 // for cross-client testing purposes "Berlin": { ChainID: big.NewInt(1), @@ -165,7 +165,7 @@ var Forks = map[string]*params.ChainConfig{ ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), - YoloV1Block: big.NewInt(0), + YoloV2Block: big.NewInt(0), }, } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index a999cba471..238d204745 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -186,6 +186,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.GetHash = vmTestBlockHash evm := vm.NewEVM(context, statedb, config, vmconfig) + if config.IsYoloV2(context.BlockNumber) { + statedb.AddAddressToAccessList(msg.From()) + if dst := msg.To(); dst != nil { + statedb.AddAddressToAccessList(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range evm.ActivePrecompiles() { + statedb.AddAddressToAccessList(addr) + } + } gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) snapshot := statedb.Snapshot() From 95f720fffc9cb366b69abc52d1a64834e92f00a4 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 23 Oct 2020 13:34:44 +0200 Subject: [PATCH 103/948] cmd/devp2p/internal/ethtest: update test chain (#21742) The old one was wrong in two ways: the first block in chain.rlp was the genesis block, and the genesis difficulty was below minimum difficulty. This also contains some other fixes to the test. --- cmd/devp2p/internal/ethtest/chain.go | 37 +++++++++--------- .../internal/ethtest/testdata/chain.rlp.gz | Bin 247322 -> 244888 bytes .../internal/ethtest/testdata/genesis.json | 10 ++--- cmd/devp2p/internal/ethtest/types.go | 20 ++++++---- 4 files changed, 37 insertions(+), 30 deletions(-) mode change 100755 => 100644 cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 654888a4ca..d67387e80b 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -124,13 +124,22 @@ func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) { // loadChain takes the given chain.rlp file, and decodes and returns // the blocks from the file. func loadChain(chainfile string, genesis string) (*Chain, error) { - // Open the file handle and potentially unwrap the gzip stream + chainConfig, err := ioutil.ReadFile(genesis) + if err != nil { + return nil, err + } + var gen core.Genesis + if err := json.Unmarshal(chainConfig, &gen); err != nil { + return nil, err + } + gblock := gen.ToBlock(nil) + + // Load chain.rlp. fh, err := os.Open(chainfile) if err != nil { return nil, err } defer fh.Close() - var reader io.Reader = fh if strings.HasSuffix(chainfile, ".gz") { if reader, err = gzip.NewReader(reader); err != nil { @@ -138,29 +147,21 @@ func loadChain(chainfile string, genesis string) (*Chain, error) { } } stream := rlp.NewStream(reader, 0) - var blocks []*types.Block + var blocks = make([]*types.Block, 1) + blocks[0] = gblock for i := 0; ; i++ { var b types.Block if err := stream.Decode(&b); err == io.EOF { break } else if err != nil { - return nil, fmt.Errorf("at block %d: %v", i, err) + return nil, fmt.Errorf("at block index %d: %v", i, err) + } + if b.NumberU64() != uint64(i+1) { + return nil, fmt.Errorf("block at index %d has wrong number %d", i, b.NumberU64()) } blocks = append(blocks, &b) } - // Open the file handle and potentially unwrap the gzip stream - chainConfig, err := ioutil.ReadFile(genesis) - if err != nil { - return nil, err - } - var gen core.Genesis - if err := json.Unmarshal(chainConfig, &gen); err != nil { - return nil, err - } - - return &Chain{ - blocks: blocks, - chainConfig: gen.Config, - }, nil + c := &Chain{blocks: blocks, chainConfig: gen.Config} + return c, nil } diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz b/cmd/devp2p/internal/ethtest/testdata/chain.rlp.gz old mode 100755 new mode 100644 index bdd6290ce6e569c50323c02fcf3cb12b0d803d1e..957e53bc0fe248e8184d29eb7a4148930910dbe7 GIT binary patch literal 244888 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